├── .gitignore ├── .prettierignore ├── .vscode └── settings.json ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── migrations └── deploy.ts ├── package.json ├── programs ├── soundwork-bid │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── instructions │ │ ├── accept_bid.rs │ │ ├── edit_bid.rs │ │ ├── make_bid.rs │ │ ├── mod.rs │ │ ├── reject_bid.rs │ │ └── revoke_bid.rs │ │ ├── lib.rs │ │ └── state │ │ ├── bid.rs │ │ └── mod.rs ├── soundwork-create │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── instructions │ │ ├── create.rs │ │ └── mod.rs │ │ ├── lib.rs │ │ └── state │ │ └── mod.rs └── soundwork-list │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ ├── constants.rs │ ├── error.rs │ ├── helpers.rs │ ├── instructions │ ├── admin │ │ ├── init_escrow.rs │ │ ├── init_mp_config.rs │ │ └── mod.rs │ ├── buy_listing.rs │ ├── escrow_ix │ │ ├── deposit_sol.rs │ │ ├── deposit_token.rs │ │ ├── init_wallet.rs │ │ ├── mod.rs │ │ ├── withdraw_sol.rs │ │ └── withdraw_token.rs │ ├── list.rs │ ├── mod.rs │ ├── unlist.rs │ └── update_listing.rs │ ├── lib.rs │ └── state │ ├── escrow.rs │ ├── listing.rs │ ├── mod.rs │ ├── protocol.rs │ └── wallet.rs ├── tests ├── unit │ ├── soundwork-bid.ts │ ├── soundwork-create.ts │ └── soundwork-list.ts └── utils │ ├── constants.ts │ ├── helpers.ts │ ├── pda.ts │ └── programs.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | .yarn 9 | .env 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Futari", 4 | "Hitori", 5 | "Ijichi", 6 | "Kessoku", 7 | "lamports", 8 | "Seika", 9 | "unlist" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | anchor_version = "0.30.1" 3 | # solana_version = "1.18.11" system version. do not uncomment 4 | # solana_version = "1.17.28" 5 | 6 | [features] 7 | seeds = false 8 | skip-lint = false 9 | 10 | [programs.localnet] 11 | soundwork-bid = "4mFDYND4AVREYEJXCPhjq1LnbjELHHebJqG3NZechA7X" 12 | soundwork-create = "FegMMZtuFu8ZUTjdkt2yRR1TmGEAFZbjpDJWpQ4ueqyG" 13 | soundwork-list = "EA4ptgF3TYjDBGYJApAoZoyCbCYw6P5mGU5noCe1Z97" 14 | 15 | [registry] 16 | url = "https://api.apr.dev" 17 | 18 | [provider] 19 | cluster = "devnet" 20 | wallet = "~/.config/solana/id.json" 21 | 22 | [scripts] 23 | all = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 24 | create = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/soundwork-create.ts" 25 | list = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/soundwork-list.ts" 26 | bid = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/soundwork-bid.ts" 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [workspace] 3 | members = [ 4 | "programs/*" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Soundwork 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Soundwork Marketplace Contracts

4 |

5 | The Soundwork NFT Marketplace contracts. 6 |

7 |

8 | Discord Chat 9 | License 10 | SDK 11 |

12 |
13 | 14 |
15 |
16 | 17 | This repo contains the latest programs for [soundwork.io](https://soundwork.io/) protocol. Using the new [Metaplex core asset](https://developers.metaplex.com/core) users are able to perform all traditional operations of an NFT marketplace like list and bid operations trustlessly executed by our programs. 18 | 19 | ## Programs 20 | 21 | This project contains the following programs: 22 | 23 | **soundwork-list** 24 | 25 | 26 | 27 | - Devnet: `EA4ptgF3TYjDBGYJApAoZoyCbCYw6P5mGU5noCe1Z97` 28 | 29 | **soundwork-bid** 30 | 31 | 32 | 33 | - Devnet: `4mFDYND4AVREYEJXCPhjq1LnbjELHHebJqG3NZechA7X` 34 | 35 | ## Audits 36 | 37 | These programs are not audited, so fork, deploy and use them at your own risk. 38 | 39 | ## Developers 40 | 41 | You can interact with the list and bid programs via our SDK. 42 | 43 | Typescript SDK: [`@soundwork-oss/soundwork-sdk`](https://www.npmjs.com/package/@soundwork-oss/soundwork-sdk) 44 | 45 | 46 | 47 | ## Developing 48 | 49 | ### Environmental Setup 50 | 51 | 1. Install [Rust](https://rustup.rs/). 52 | 2. Install [Solana](https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool). 53 | 3. Install [Anchor](https://www.anchor-lang.com/docs/installation). 54 | 55 | ### Install Dependencies 56 | 57 | ``` 58 | yarn 59 | ``` 60 | 61 | ### Build the Program 62 | 63 | ``` 64 | anchor build 65 | ``` 66 | 67 | ### Testing 68 | 69 | ``` 70 | cargo clippy --all-targets -- -D warnings 71 | anchor test 72 | ``` 73 | 74 | ### Patches 75 | 76 | Should you encounter `failed to select a version for the requirement toml_edit = "^0.21.0"` 77 | 78 | ```bash 79 | cargo update -p toml_edit@0.21.1 --precise 0.21.0 80 | ``` 81 | -------------------------------------------------------------------------------- /migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@coral-xyz/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@coral-xyz/anchor": "^0.30.1", 8 | "@solana/spl-token": "^0.4.6", 9 | "@solana/web3.js": "^1.91.4", 10 | "dotenv": "^16.4.5" 11 | }, 12 | "devDependencies": { 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "chai": "^4.3.4", 17 | "mocha": "^9.0.3", 18 | "prettier": "^2.6.2", 19 | "ts-mocha": "^10.0.0", 20 | "typescript": "^4.3.5" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /programs/soundwork-bid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soundwork-bid" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "soundwork_bid" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = "0.30.1" 21 | anchor-spl = "0.30.1" 22 | mpl-core = "0.7.2" 23 | solana-security-txt = "1.1.1" 24 | soundwork-create = { path = "../soundwork-create", features = ["cpi"] } 25 | soundwork-list = { path = "../soundwork-list", features = ["cpi"] } 26 | ahash="=0.8.7" 27 | -------------------------------------------------------------------------------- /programs/soundwork-bid/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/constants.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[constant] 4 | pub const SEED_PREFIX: &[u8] = b"Kessoku"; 5 | 6 | #[constant] 7 | pub const SEED_BID_DATA: &[u8] = b"Futari"; 8 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum BidErrorCode { 5 | #[msg("Signer address does not math the initializer address")] 6 | UnrecognizedSigner, 7 | #[msg("Bid TimeStamp Expired")] 8 | BidExpired, 9 | #[msg("Insufficient Funds to make bid for item")] 10 | InsufficientFunds, 11 | #[msg("An account required for this operation is missing.")] 12 | MissingAccount, 13 | #[msg("The mint address provided does not match seller's provided mint address.")] 14 | PaymentMintAddressMismatch, 15 | #[msg("Operations resulted in an overflow.")] 16 | Overflow, 17 | } 18 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/instructions/accept_bid.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{Mint, Token, TokenAccount}, 5 | }; 6 | 7 | use soundwork_create::Core; 8 | use soundwork_list::{ 9 | cpi::{accounts::BuyAsset, buy_asset}, 10 | program::SoundworkList, 11 | state::ListingData, 12 | AssetManager, BuyAssetParams, MarketPlaceConfig, PaymentOption, 13 | }; 14 | 15 | use crate::{error::BidErrorCode, BidData}; 16 | 17 | /// Accept a placed Bid 18 | 19 | /// ### Accounts: 20 | /// 21 | /// 1. `[writeable, signer]` seller 22 | /// 2. `[writeable]` bidder 23 | /// 3. `[writeable]` asset 24 | /// 4. `[writeable]` collection 25 | /// 5. `[writeable]` bid data account 26 | /// 6. `[writeable]` bidder escrow wallet 27 | /// 7. `[writeable]` listing data account 28 | /// 8. `[writeable, optional]` bidder token account 29 | /// 9. `[writeable, optional]` seller token account 30 | /// 10. `[writeable, optional]` wallet token account 31 | /// 11. `[writeable, optional]` treasury token account 32 | /// 12. `[writeable]` treasury 33 | /// 13. `[writeable]` asset manager 34 | /// 14. `[writeable]` marketplace config account 35 | /// 15. `[writeable]` payment mint 36 | /// 16. `[]` soundwork list program 37 | /// 17. `[]` core program 38 | /// 18. `[]` token program 39 | /// 19. `[]` associated token program 40 | /// 20. `[]` system program 41 | /// 42 | #[derive(Accounts)] 43 | pub struct AcceptBid<'info> { 44 | #[account( 45 | mut, 46 | address = listing_data.authority @ BidErrorCode::UnrecognizedSigner 47 | )] 48 | pub seller: Signer<'info>, 49 | 50 | #[account( 51 | mut, 52 | address = bid_data.authority @ BidErrorCode::UnrecognizedSigner 53 | )] 54 | pub bidder: SystemAccount<'info>, 55 | 56 | /// CHECK: checked by us 57 | #[account(mut)] 58 | pub asset: AccountInfo<'info>, 59 | 60 | /// CHECK: checked by us 61 | #[account(mut)] 62 | pub collection: Option>, 63 | 64 | #[account( 65 | mut, 66 | close = bidder, 67 | )] 68 | pub bid_data: Account<'info, BidData>, 69 | 70 | /// CHECK: initialized by list program through the CPI 71 | #[account( 72 | mut, 73 | owner = soundwork_list.key() 74 | )] 75 | pub bidder_escrow_wallet: UncheckedAccount<'info>, 76 | 77 | #[account(mut)] 78 | pub listing_data: Account<'info, ListingData>, 79 | 80 | #[account(mut)] 81 | pub payment_mint: Option>>, 82 | 83 | #[account(mut)] 84 | pub bidder_token_account: Option>>, 85 | 86 | // unchecked because this might be uninitialized 87 | #[account(mut)] 88 | pub seller_token_account: Option>, 89 | 90 | // unchecked because this might be uninitialized 91 | #[account(mut)] 92 | pub wallet_token_account: Option>>, 93 | 94 | // unchecked because this might be uninitialized 95 | #[account(mut)] 96 | pub treasury_token_account: Option>, 97 | 98 | #[account( 99 | mut, 100 | address = marketplace_config.treasury_address 101 | )] 102 | pub treasury: SystemAccount<'info>, 103 | 104 | pub asset_manager: Box>, 105 | 106 | pub marketplace_config: Box>, 107 | 108 | pub soundwork_list: Program<'info, SoundworkList>, 109 | 110 | pub core_program: Program<'info, Core>, 111 | 112 | pub token_program: Program<'info, Token>, 113 | 114 | pub associated_token_program: Program<'info, AssociatedToken>, 115 | 116 | pub system_program: Program<'info, System>, 117 | } 118 | 119 | impl AcceptBid<'_> { 120 | /// validation helper for our IX 121 | pub fn validate(&self) -> Result<()> { 122 | match self.listing_data.payment_option { 123 | PaymentOption::Native => {} // default anchor account checks 124 | 125 | PaymentOption::Token { mint: _ } => { 126 | let payment_mint = self.payment_mint.as_ref(); 127 | let bidder_token_account = self.bidder_token_account.as_ref(); 128 | let wallet_token_account = self.wallet_token_account.as_ref(); 129 | let treasury_token_account = self.treasury_token_account.as_ref(); 130 | let seller_token_account = self.seller_token_account.as_ref(); 131 | 132 | if bidder_token_account.is_none() 133 | || payment_mint.is_none() 134 | || wallet_token_account.is_none() 135 | || seller_token_account.is_none() 136 | || treasury_token_account.is_none() 137 | { 138 | return Err(error!(BidErrorCode::MissingAccount)); 139 | } 140 | } // 141 | } 142 | 143 | return Ok(()); 144 | } 145 | 146 | /// Accept placed bid for an MPL core asset on the marketplace 147 | /// 148 | #[access_control(ctx.accounts.validate())] 149 | pub fn accept_bid(ctx: Context) -> Result<()> { 150 | let bid_data = &mut ctx.accounts.bid_data; 151 | let list_data = &mut ctx.accounts.listing_data; 152 | let cpi_program = ctx.accounts.soundwork_list.to_account_info(); 153 | 154 | // transfer sol or tokens back to bidders wallet 155 | 156 | match list_data.payment_option { 157 | PaymentOption::Native => { 158 | let buy_asset_cpi_accounts = BuyAsset { 159 | payer: ctx.accounts.seller.to_account_info(), 160 | buyer: ctx.accounts.bidder.to_account_info(), 161 | seller: ctx.accounts.seller.to_account_info(), 162 | wallet_as_buyer: ctx.accounts.bidder_escrow_wallet.to_account_info().into(), 163 | asset: ctx.accounts.asset.to_account_info(), 164 | collection: ctx.accounts.collection.clone(), 165 | payment_mint: None, // safe to unwrap 166 | wallet_token_account: None, // safe to unwrap 167 | buyer_token_account: None, 168 | seller_token_account: None, 169 | treasury_token_account: None, 170 | treasury: ctx.accounts.treasury.to_account_info(), 171 | listing_data: ctx.accounts.listing_data.to_account_info(), 172 | asset_manager: ctx.accounts.asset_manager.to_account_info(), 173 | marketplace_config: ctx.accounts.marketplace_config.to_account_info(), 174 | core_program: ctx.accounts.core_program.to_account_info(), 175 | token_program: ctx.accounts.token_program.to_account_info(), 176 | associated_token_program: ctx 177 | .accounts 178 | .associated_token_program 179 | .to_account_info(), 180 | system_program: ctx.accounts.system_program.to_account_info(), 181 | }; 182 | 183 | let buy_asset_ctx = CpiContext::new(cpi_program, buy_asset_cpi_accounts); 184 | 185 | buy_asset(buy_asset_ctx, None)?; 186 | } 187 | 188 | PaymentOption::Token { mint: _ } => { 189 | let buy_listing_cpi_accounts = BuyAsset { 190 | payer: ctx.accounts.seller.to_account_info(), 191 | buyer: ctx.accounts.bidder.to_account_info(), 192 | seller: ctx.accounts.seller.to_account_info(), 193 | wallet_as_buyer: ctx.accounts.bidder_escrow_wallet.to_account_info().into(), 194 | asset: ctx.accounts.asset.to_account_info(), 195 | collection: ctx.accounts.collection.clone(), 196 | payment_mint: Some( 197 | ctx.accounts.payment_mint.clone().unwrap().to_account_info(), 198 | ), // safe to unwrap 199 | wallet_token_account: Some( 200 | ctx.accounts 201 | .wallet_token_account 202 | .clone() 203 | .unwrap() 204 | .to_account_info(), 205 | ), // safe to unwrap 206 | buyer_token_account: Some( 207 | ctx.accounts 208 | .bidder_token_account 209 | .clone() 210 | .unwrap() 211 | .to_account_info(), 212 | ), 213 | seller_token_account: Some( 214 | ctx.accounts 215 | .seller_token_account 216 | .clone() 217 | .unwrap() 218 | .to_account_info(), 219 | ), 220 | treasury_token_account: Some( 221 | ctx.accounts 222 | .treasury_token_account 223 | .clone() 224 | .unwrap() 225 | .to_account_info(), 226 | ), 227 | treasury: ctx.accounts.treasury.to_account_info(), 228 | listing_data: ctx.accounts.listing_data.to_account_info(), 229 | asset_manager: ctx.accounts.asset_manager.to_account_info(), 230 | marketplace_config: ctx.accounts.marketplace_config.to_account_info(), 231 | core_program: ctx.accounts.core_program.to_account_info(), 232 | token_program: ctx.accounts.token_program.to_account_info(), 233 | associated_token_program: ctx 234 | .accounts 235 | .associated_token_program 236 | .to_account_info(), 237 | system_program: ctx.accounts.system_program.to_account_info(), 238 | }; 239 | 240 | let buy_asset_ctx = CpiContext::new(cpi_program, buy_listing_cpi_accounts); 241 | 242 | buy_asset( 243 | buy_asset_ctx, 244 | BuyAssetParams { 245 | bid_amount: bid_data.amount, 246 | } 247 | .into(), 248 | )?; 249 | } 250 | } 251 | 252 | Ok(()) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/instructions/edit_bid.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{Mint, Token, TokenAccount}, 5 | }; 6 | use soundwork_list::{ 7 | cpi::{ 8 | accounts::{DepositSol, DepositToken}, 9 | deposit_sol, deposit_token, 10 | }, 11 | program::SoundworkList, 12 | state::{ListingData, Wallet}, 13 | DepositSolParams, DepositTokenParams, PaymentOption, 14 | }; 15 | 16 | use crate::{error::BidErrorCode, BidData}; 17 | 18 | #[derive(AnchorSerialize, AnchorDeserialize)] 19 | pub struct EditBidParams { 20 | /// bid amount/price in lamports 21 | pub amount: Option, 22 | 23 | /// expiry timestamp 24 | pub expiry_ts: Option, 25 | } 26 | 27 | /// Edit Bid 28 | /// 29 | /// ### Accounts: 30 | /// 31 | /// 1. `[writeable, signer]` bidder 32 | /// 2. `[writeable]` asset 33 | /// 3. `[writeable]` bid data account 34 | /// 4. `[writeable]` bidder escrow wallet 35 | /// 5. `[writeable]` listing data account 36 | /// 6. `[writeable]` payment mint 37 | /// 7. `[writeable, optional]` bidder token account 38 | /// 8. `[writeable, options]` wallet token account 39 | /// 9. `[]` soundwork list program 40 | /// 10. `[]` token program 41 | /// 11. `[]` associated token program 42 | /// 12. `[]` system program 43 | /// 44 | /// ### Parameter 45 | /// 1. params: [EditBidParams] 46 | /// 47 | #[derive(Accounts)] 48 | #[instruction(params: EditBidParams)] 49 | pub struct EditBid<'info> { 50 | #[account(mut)] 51 | pub bidder: Signer<'info>, 52 | 53 | /// CHECK: checked by us 54 | #[account(mut)] 55 | pub asset: AccountInfo<'info>, 56 | 57 | /// CHECK: checked by us 58 | #[account(mut)] 59 | pub bid_data: Account<'info, BidData>, 60 | 61 | #[account(mut)] 62 | pub bidder_escrow_wallet: Box>, 63 | 64 | #[account(mut)] 65 | pub listing_data: Account<'info, ListingData>, 66 | 67 | #[account(mut)] 68 | pub payment_mint: Option>>, 69 | 70 | #[account(mut)] 71 | pub bidder_token_account: Option>>, 72 | 73 | #[account(mut)] 74 | pub wallet_token_account: Option>>, 75 | 76 | pub soundwork_list: Program<'info, SoundworkList>, 77 | 78 | pub token_program: Program<'info, Token>, 79 | 80 | pub associated_token_program: Program<'info, AssociatedToken>, 81 | 82 | pub system_program: Program<'info, System>, 83 | } 84 | 85 | impl EditBid<'_> { 86 | /// validation helper for our IX 87 | pub fn validate(&self, params: &EditBidParams) -> Result<()> { 88 | // check if bidder has enough amount 89 | 90 | if let Some(amt) = params.amount { 91 | let diff = amt.checked_sub(self.bid_data.amount); 92 | if diff.is_none() { 93 | return Err(error!(BidErrorCode::Overflow)); 94 | } 95 | 96 | match self.listing_data.payment_option { 97 | PaymentOption::Native => { 98 | if self.bidder.get_lamports() < diff.unwrap() { 99 | msg!("insufficient lamports"); 100 | return Err(error!(BidErrorCode::InsufficientFunds)); 101 | } 102 | } 103 | 104 | PaymentOption::Token { mint } => { 105 | let payment_mint = self.payment_mint.as_ref(); 106 | let bidder_token_account = self.bidder_token_account.as_ref(); 107 | 108 | if bidder_token_account.is_none() || payment_mint.is_none() { 109 | return Err(error!(BidErrorCode::MissingAccount)); 110 | } 111 | 112 | if mint != payment_mint.unwrap().key() { 113 | return Err(error!(BidErrorCode::PaymentMintAddressMismatch)); 114 | } 115 | 116 | if bidder_token_account.unwrap().amount < diff.unwrap() { 117 | msg!("insufficient tokens"); 118 | return Err(error!(BidErrorCode::InsufficientFunds)); 119 | } 120 | } // 121 | } 122 | } 123 | 124 | // todo: sanity checks for expiry timestamp 125 | 126 | return Ok(()); 127 | } 128 | 129 | /// Edit MPL core asset on the marketplace 130 | /// 131 | #[access_control(ctx.accounts.validate(¶ms))] 132 | pub fn edit_bid(ctx: Context, params: EditBidParams) -> Result<()> { 133 | let bid_data = &mut ctx.accounts.bid_data; 134 | let list_data = &mut ctx.accounts.listing_data; 135 | let cpi_program = ctx.accounts.soundwork_list.to_account_info(); 136 | 137 | let EditBidParams { amount, expiry_ts } = params; 138 | 139 | // update expiry 140 | if let Some(expire_ts) = expiry_ts { 141 | bid_data.expiry_ts = expire_ts; 142 | } 143 | 144 | // check if bidder increased his bid amt and if true, transfer the 145 | // difference with new price to escrow wallet to be able to cover buying 146 | // nft if seller accepts offer 147 | if let Some(amt) = amount { 148 | if amt > bid_data.amount { 149 | let diff = amt.checked_sub(bid_data.amount); 150 | if diff.is_none() { 151 | return Err(error!(BidErrorCode::Overflow)); 152 | } 153 | 154 | match list_data.payment_option { 155 | PaymentOption::Native => { 156 | let deposit_sol_cpi_accounts = DepositSol { 157 | authority: ctx.accounts.bidder.to_account_info(), 158 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 159 | system_program: ctx.accounts.system_program.to_account_info(), 160 | }; 161 | 162 | let deposit_sol_ctx = 163 | CpiContext::new(cpi_program, deposit_sol_cpi_accounts); 164 | 165 | deposit_sol( 166 | deposit_sol_ctx, 167 | DepositSolParams { 168 | amount: diff.unwrap(), 169 | }, 170 | )?; 171 | } 172 | 173 | PaymentOption::Token { mint: _ } => { 174 | let diff = amt.checked_sub(bid_data.amount); 175 | if diff.is_none() { 176 | return Err(error!(BidErrorCode::Overflow)); 177 | } 178 | 179 | let deposit_token_cpi_accounts = DepositToken { 180 | authority: ctx.accounts.bidder.to_account_info(), 181 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 182 | mint: ctx 183 | .accounts 184 | .payment_mint 185 | .as_ref() 186 | .unwrap() 187 | .to_account_info(), // safe to unwrap. checked in validator constraint 188 | authority_token_account: ctx 189 | .accounts 190 | .bidder_token_account 191 | .as_ref() 192 | .unwrap() 193 | .to_account_info(), // safe to unwrap here too 194 | wallet_token_account: ctx 195 | .accounts 196 | .wallet_token_account 197 | .as_ref() 198 | .unwrap() 199 | .to_account_info(), 200 | token_program: ctx.accounts.token_program.to_account_info(), 201 | associated_token_program: ctx 202 | .accounts 203 | .associated_token_program 204 | .to_account_info(), 205 | system_program: ctx.accounts.system_program.to_account_info(), 206 | }; 207 | 208 | let deposit_token_ctx = 209 | CpiContext::new(cpi_program, deposit_token_cpi_accounts); 210 | 211 | deposit_token( 212 | deposit_token_ctx, 213 | DepositTokenParams { 214 | amount: diff.unwrap(), 215 | }, 216 | )?; 217 | } 218 | } 219 | 220 | bid_data.amount += diff.unwrap(); // ! test me 221 | } 222 | 223 | // do nothing. escrow wallet has enough funds to purchase asset if accepted 224 | msg!("escrow wallet balance remains unchanged"); 225 | } 226 | 227 | Ok(()) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/instructions/make_bid.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{Mint, Token, TokenAccount}, 5 | }; 6 | use soundwork_list::{ 7 | cpi::{ 8 | accounts::{DepositSol, DepositToken}, 9 | deposit_sol, deposit_token, 10 | }, 11 | program::SoundworkList, 12 | state::{ListingData, Wallet}, 13 | DepositSolParams, DepositTokenParams, PaymentOption, 14 | }; 15 | 16 | use crate::{ 17 | constants::{SEED_BID_DATA, SEED_PREFIX}, 18 | // helpers::Core, 19 | // AssetManager, ListingData, PaymentOption, 20 | error::BidErrorCode, 21 | BidData, 22 | }; 23 | 24 | #[derive(AnchorSerialize, AnchorDeserialize)] 25 | pub struct MakeBidParams { 26 | /// bid amount/price in lamports 27 | pub amount: u64, 28 | 29 | /// expiry timestamp 30 | pub expiry_ts: i64, 31 | } 32 | 33 | /// Make a bid for an MPL core asset listed soundwork 34 | /// 35 | /// ### Accounts: 36 | /// 37 | /// 1. `[writeable, signer]` bidder 38 | /// 2. `[writeable]` asset 39 | /// 3. `[writeable]` bid data account 40 | /// 4. `[writeable]` bidder escrow wallet 41 | /// 5. `[writeable]` listing data account 42 | /// 6. `[writeable]` payment mint 43 | /// 7. `[writeable, optional]` bidder token account 44 | /// 8. `[writeable, options]` wallet token account 45 | /// 9. `[]` soundwork list program 46 | /// 10. `[]` token program 47 | /// 11. `[]` associated token program 48 | /// 12. `[]` system program 49 | /// 50 | /// ### Parameters 51 | /// 52 | /// 1. params: [MakeBidParams] 53 | /// 54 | #[derive(Accounts)] 55 | #[instruction(params: MakeBidParams)] 56 | pub struct MakeBid<'info> { 57 | #[account(mut)] 58 | pub bidder: Signer<'info>, 59 | 60 | /// CHECK: checked by us 61 | #[account(mut)] 62 | pub asset: AccountInfo<'info>, 63 | 64 | /// CHECK: checked by us 65 | #[account( 66 | init, 67 | payer = bidder, 68 | space = BidData::LEN, 69 | seeds = [SEED_PREFIX, SEED_BID_DATA, asset.key().as_ref()], 70 | bump 71 | )] 72 | pub bid_data: Account<'info, BidData>, 73 | 74 | #[account(mut)] 75 | pub bidder_escrow_wallet: Box>, 76 | 77 | #[account(mut)] 78 | pub listing_data: Account<'info, ListingData>, 79 | 80 | #[account(mut)] 81 | pub payment_mint: Option>>, 82 | 83 | #[account(mut)] 84 | pub bidder_token_account: Option>>, 85 | 86 | #[account( 87 | init_if_needed, 88 | payer = bidder, 89 | associated_token::mint = payment_mint, 90 | associated_token::authority = bidder_escrow_wallet, 91 | )] 92 | pub wallet_token_account: Option>>, 93 | 94 | pub soundwork_list: Program<'info, SoundworkList>, 95 | 96 | pub token_program: Program<'info, Token>, 97 | 98 | pub associated_token_program: Program<'info, AssociatedToken>, 99 | 100 | pub system_program: Program<'info, System>, 101 | } 102 | 103 | impl MakeBid<'_> { 104 | /// validation helper for our IX 105 | pub fn validate(&self, params: &MakeBidParams) -> Result<()> { 106 | // check if bidder has enough amount specified by the payment options 107 | 108 | match self.listing_data.payment_option { 109 | PaymentOption::Native => { 110 | if self.bidder.get_lamports() < params.amount { 111 | msg!("insufficient lamports"); 112 | return Err(error!(BidErrorCode::InsufficientFunds)); 113 | } 114 | } 115 | 116 | PaymentOption::Token { mint } => { 117 | let payment_mint = self.payment_mint.as_ref(); 118 | let bidder_token_account = self.bidder_token_account.as_ref(); 119 | 120 | if bidder_token_account.is_none() || payment_mint.is_none() { 121 | return Err(error!(BidErrorCode::MissingAccount)); 122 | } 123 | 124 | if mint != payment_mint.unwrap().key() { 125 | return Err(error!(BidErrorCode::PaymentMintAddressMismatch)); 126 | } 127 | 128 | if bidder_token_account.unwrap().amount < params.amount { 129 | msg!("insufficient tokens"); 130 | return Err(error!(BidErrorCode::InsufficientFunds)); 131 | } 132 | } // 133 | } 134 | 135 | // todo(Jimii): check expiry 136 | 137 | return Ok(()); 138 | } 139 | 140 | /// place bid for an MPL core asset on the marketplace 141 | /// 142 | #[access_control(ctx.accounts.validate(¶ms))] 143 | pub fn make_bid(ctx: Context, params: MakeBidParams) -> Result<()> { 144 | let bid_data = &mut ctx.accounts.bid_data; 145 | let list_data = &mut ctx.accounts.listing_data; 146 | 147 | **bid_data = BidData::new( 148 | ctx.bumps.bid_data, 149 | params.amount, 150 | ctx.accounts.bidder.key(), 151 | params.expiry_ts, 152 | ); 153 | 154 | // transfer sol or tokens 155 | 156 | match list_data.payment_option { 157 | PaymentOption::Native => { 158 | let cpi_program = ctx.accounts.soundwork_list.to_account_info(); 159 | 160 | let deposit_sol_cpi_accounts = DepositSol { 161 | authority: ctx.accounts.bidder.to_account_info(), 162 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 163 | system_program: ctx.accounts.system_program.to_account_info(), 164 | }; 165 | 166 | let deposit_sol_ctx = CpiContext::new(cpi_program, deposit_sol_cpi_accounts); 167 | 168 | deposit_sol( 169 | deposit_sol_ctx, 170 | DepositSolParams { 171 | amount: params.amount, 172 | }, 173 | )?; 174 | } 175 | 176 | PaymentOption::Token { mint: _ } => { 177 | let cpi_program = ctx.accounts.soundwork_list.to_account_info(); 178 | 179 | let deposit_token_cpi_accounts = DepositToken { 180 | authority: ctx.accounts.bidder.to_account_info(), 181 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 182 | mint: ctx 183 | .accounts 184 | .payment_mint 185 | .as_ref() 186 | .unwrap() 187 | .to_account_info(), // safe to unwrap. checked in validator constraint 188 | authority_token_account: ctx 189 | .accounts 190 | .bidder_token_account 191 | .as_ref() 192 | .unwrap() 193 | .to_account_info(), // safe to unwrap here too 194 | wallet_token_account: ctx 195 | .accounts 196 | .wallet_token_account 197 | .as_ref() 198 | .unwrap() 199 | .to_account_info(), 200 | token_program: ctx.accounts.token_program.to_account_info(), 201 | associated_token_program: ctx 202 | .accounts 203 | .associated_token_program 204 | .to_account_info(), 205 | system_program: ctx.accounts.system_program.to_account_info(), 206 | }; 207 | 208 | let deposit_token_ctx = CpiContext::new(cpi_program, deposit_token_cpi_accounts); 209 | 210 | deposit_token( 211 | deposit_token_ctx, 212 | DepositTokenParams { 213 | amount: params.amount, 214 | }, 215 | )?; 216 | } 217 | } 218 | 219 | Ok(()) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod accept_bid; 2 | pub mod edit_bid; 3 | pub mod make_bid; 4 | pub mod reject_bid; 5 | pub mod revoke_bid; 6 | 7 | pub use accept_bid::*; 8 | pub use edit_bid::*; 9 | pub use make_bid::*; 10 | pub use reject_bid::*; 11 | pub use revoke_bid::*; 12 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/instructions/reject_bid.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{Mint, Token, TokenAccount}, 5 | }; 6 | 7 | use soundwork_list::{ 8 | cpi::{ 9 | accounts::{WithdrawSol, WithdrawToken}, 10 | withdraw_sol, withdraw_token, 11 | }, 12 | program::SoundworkList, 13 | state::ListingData, 14 | PaymentOption, WithdrawSolParams, WithdrawTokenParams, 15 | }; 16 | 17 | use crate::{error::BidErrorCode, BidData}; 18 | 19 | /// Reject a placed Bid 20 | 21 | /// ### Accounts: 22 | /// 23 | /// 1. `[writeable, signer]` seller 24 | /// 2. `[writeable]` bidder 25 | /// 3. `[writeable]` asset 26 | /// 4. `[writeable]` bid data account 27 | /// 5. `[writeable]` bidder escrow wallet 28 | /// 6. `[writeable]` listing data account 29 | /// 7. `[writeable, optional]` bidder token account 30 | /// 8. `[writeable, optional]` wallet token account 31 | /// 9. `[writeable]` payment mint 32 | /// 10. `[]` soundwork list program 33 | /// 11. `[]` token program 34 | /// 12. `[]` associated token program 35 | /// 13. `[]` system program 36 | /// 37 | #[derive(Accounts)] 38 | pub struct RejectBid<'info> { 39 | #[account(mut)] 40 | pub seller: Signer<'info>, 41 | 42 | #[account( 43 | mut, 44 | address = bid_data.authority @ BidErrorCode::UnrecognizedSigner 45 | )] 46 | pub bidder: SystemAccount<'info>, 47 | 48 | /// CHECK: checked by us 49 | #[account(mut)] 50 | pub asset: AccountInfo<'info>, 51 | 52 | #[account( 53 | mut, 54 | close = bidder, 55 | )] 56 | pub bid_data: Account<'info, BidData>, 57 | 58 | /// CHECK: initialized by list program through the CPI 59 | #[account( 60 | mut, 61 | owner = soundwork_list.key() 62 | )] 63 | pub bidder_escrow_wallet: UncheckedAccount<'info>, 64 | 65 | #[account(mut)] 66 | pub listing_data: Account<'info, ListingData>, 67 | 68 | #[account(mut)] 69 | pub payment_mint: Option>>, 70 | 71 | #[account(mut)] 72 | pub bidder_token_account: Option>>, 73 | 74 | #[account(mut)] 75 | pub wallet_token_account: Option>>, 76 | 77 | pub soundwork_list: Program<'info, SoundworkList>, 78 | 79 | pub token_program: Program<'info, Token>, 80 | 81 | pub associated_token_program: Program<'info, AssociatedToken>, 82 | 83 | pub system_program: Program<'info, System>, 84 | } 85 | 86 | impl RejectBid<'_> { 87 | /// validation helper for our IX 88 | pub fn validate(&self) -> Result<()> { 89 | match self.listing_data.payment_option { 90 | PaymentOption::Native => {} // default anchor account checks 91 | 92 | PaymentOption::Token { mint: _ } => { 93 | let payment_mint = self.payment_mint.as_ref(); 94 | let bidder_token_account = self.bidder_token_account.as_ref(); 95 | 96 | if bidder_token_account.is_none() || payment_mint.is_none() { 97 | return Err(error!(BidErrorCode::MissingAccount)); 98 | } 99 | } // 100 | } 101 | 102 | return Ok(()); 103 | } 104 | 105 | /// reject previously placed bid for an MPL core asset on the marketplace 106 | /// 107 | #[access_control(ctx.accounts.validate())] 108 | pub fn reject_bid(ctx: Context) -> Result<()> { 109 | let bid_data = &mut ctx.accounts.bid_data; 110 | let list_data = &mut ctx.accounts.listing_data; 111 | let cpi_program = ctx.accounts.soundwork_list.to_account_info(); 112 | 113 | // transfer sol or tokens back to bidders wallet 114 | 115 | match list_data.payment_option { 116 | PaymentOption::Native => { 117 | let withdraw_sol_cpi_accounts = WithdrawSol { 118 | payer: ctx.accounts.bidder.to_account_info(), 119 | authority: ctx.accounts.bidder.to_account_info(), 120 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 121 | system_program: ctx.accounts.system_program.to_account_info(), 122 | }; 123 | 124 | let withdraw_sol_ctx = CpiContext::new(cpi_program, withdraw_sol_cpi_accounts); 125 | 126 | withdraw_sol( 127 | withdraw_sol_ctx, 128 | WithdrawSolParams { 129 | amount: bid_data.amount, 130 | } 131 | .into(), 132 | )?; 133 | } 134 | 135 | PaymentOption::Token { mint: _ } => { 136 | let withdraw_token_cpi_accounts = WithdrawToken { 137 | payer: ctx.accounts.bidder.to_account_info(), 138 | authority: ctx.accounts.bidder.to_account_info(), 139 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 140 | mint: ctx 141 | .accounts 142 | .payment_mint 143 | .as_ref() 144 | .unwrap() 145 | .to_account_info(), // safe to unwrap. checked in validator constraint 146 | authority_token_account: ctx 147 | .accounts 148 | .bidder_token_account 149 | .as_ref() 150 | .unwrap() 151 | .to_account_info(), // safe to unwrap here too 152 | wallet_token_account: ctx 153 | .accounts 154 | .wallet_token_account 155 | .as_ref() 156 | .unwrap() 157 | .to_account_info(), 158 | token_program: ctx.accounts.token_program.to_account_info(), 159 | associated_token_program: ctx 160 | .accounts 161 | .associated_token_program 162 | .to_account_info(), 163 | system_program: ctx.accounts.system_program.to_account_info(), 164 | }; 165 | 166 | let withdraw_token_ctx = CpiContext::new(cpi_program, withdraw_token_cpi_accounts); 167 | 168 | withdraw_token( 169 | withdraw_token_ctx, 170 | WithdrawTokenParams { 171 | amount: bid_data.amount, 172 | } 173 | .into(), 174 | )?; 175 | } 176 | } 177 | 178 | Ok(()) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/instructions/revoke_bid.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{Mint, Token, TokenAccount}, 5 | }; 6 | 7 | use soundwork_list::{ 8 | cpi::{ 9 | accounts::{WithdrawSol, WithdrawToken}, 10 | withdraw_sol, withdraw_token, 11 | }, 12 | program::SoundworkList, 13 | state::ListingData, 14 | PaymentOption, WithdrawSolParams, WithdrawTokenParams, 15 | }; 16 | 17 | use crate::{error::BidErrorCode, BidData}; 18 | 19 | /// Revoke placed Bid 20 | /// 21 | /// ### Accounts: 22 | /// 1. `[writeable, signer]` bidder 23 | /// 2. `[writeable]` asset 24 | /// 3. `[writeable]` bid data account 25 | /// 4. `[writeable]` bidder escrow wallet 26 | /// 5. `[writeable]` listing data account 27 | /// 6. `[writeable]` payment mint 28 | /// 7. `[writeable, optional]` bidder token account 29 | /// 8. `[writeable, options]` wallet token account 30 | /// 9. `[]` soundwork list program 31 | /// 10. `[]` token program 32 | /// 11. `[]` associated token program 33 | /// 12. `[]` system program 34 | /// 35 | #[derive(Accounts)] 36 | pub struct RevokeBid<'info> { 37 | #[account( 38 | mut, 39 | address = bid_data.authority @ BidErrorCode::UnrecognizedSigner 40 | )] 41 | pub bidder: Signer<'info>, 42 | 43 | /// CHECK: checked by us 44 | #[account(mut)] 45 | pub asset: AccountInfo<'info>, 46 | 47 | #[account( 48 | mut, 49 | close = bidder, 50 | )] 51 | pub bid_data: Account<'info, BidData>, 52 | 53 | /// CHECK: initialized by list program through the CPI 54 | #[account( 55 | mut, 56 | owner = soundwork_list.key() 57 | )] 58 | pub bidder_escrow_wallet: UncheckedAccount<'info>, 59 | 60 | #[account(mut)] 61 | pub listing_data: Account<'info, ListingData>, 62 | 63 | #[account(mut)] 64 | pub payment_mint: Option>>, 65 | 66 | #[account(mut)] 67 | pub bidder_token_account: Option>>, 68 | 69 | #[account(mut)] 70 | pub wallet_token_account: Option>>, 71 | 72 | pub soundwork_list: Program<'info, SoundworkList>, 73 | 74 | pub token_program: Program<'info, Token>, 75 | 76 | pub associated_token_program: Program<'info, AssociatedToken>, 77 | 78 | pub system_program: Program<'info, System>, 79 | } 80 | 81 | impl RevokeBid<'_> { 82 | /// validation helper for our IX 83 | pub fn validate(&self) -> Result<()> { 84 | match self.listing_data.payment_option { 85 | PaymentOption::Native => {} 86 | 87 | PaymentOption::Token { mint: _ } => { 88 | let payment_mint = self.payment_mint.as_ref(); 89 | let bidder_token_account = self.bidder_token_account.as_ref(); 90 | 91 | if bidder_token_account.is_none() || payment_mint.is_none() { 92 | return Err(error!(BidErrorCode::MissingAccount)); 93 | } 94 | } // 95 | } 96 | 97 | return Ok(()); 98 | } 99 | 100 | /// revoke previously placed bid for an MPL core asset on the marketplace 101 | /// 102 | #[access_control(ctx.accounts.validate())] 103 | pub fn revoke_bid(ctx: Context) -> Result<()> { 104 | let bid_data = &mut ctx.accounts.bid_data; 105 | let list_data = &mut ctx.accounts.listing_data; 106 | let cpi_program = ctx.accounts.soundwork_list.to_account_info(); 107 | 108 | // transfer sol or tokens back to bidders wallet 109 | 110 | match list_data.payment_option { 111 | PaymentOption::Native => { 112 | let withdraw_sol_cpi_accounts = WithdrawSol { 113 | payer: ctx.accounts.bidder.to_account_info(), 114 | authority: ctx.accounts.bidder.to_account_info(), 115 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 116 | system_program: ctx.accounts.system_program.to_account_info(), 117 | }; 118 | 119 | let withdraw_sol_ctx = CpiContext::new(cpi_program, withdraw_sol_cpi_accounts); 120 | 121 | withdraw_sol( 122 | withdraw_sol_ctx, 123 | WithdrawSolParams { 124 | amount: bid_data.amount, 125 | } 126 | .into(), 127 | )?; 128 | } 129 | 130 | PaymentOption::Token { mint: _ } => { 131 | let withdraw_token_cpi_accounts = WithdrawToken { 132 | payer: ctx.accounts.bidder.to_account_info(), 133 | authority: ctx.accounts.bidder.to_account_info(), 134 | wallet: ctx.accounts.bidder_escrow_wallet.to_account_info(), 135 | mint: ctx 136 | .accounts 137 | .payment_mint 138 | .as_ref() 139 | .unwrap() 140 | .to_account_info(), // safe to unwrap. checked in validator constraint 141 | authority_token_account: ctx 142 | .accounts 143 | .bidder_token_account 144 | .as_ref() 145 | .unwrap() 146 | .to_account_info(), // safe to unwrap here too 147 | wallet_token_account: ctx 148 | .accounts 149 | .wallet_token_account 150 | .as_ref() 151 | .unwrap() 152 | .to_account_info(), 153 | token_program: ctx.accounts.token_program.to_account_info(), 154 | associated_token_program: ctx 155 | .accounts 156 | .associated_token_program 157 | .to_account_info(), 158 | system_program: ctx.accounts.system_program.to_account_info(), 159 | }; 160 | 161 | let withdraw_token_ctx = CpiContext::new(cpi_program, withdraw_token_cpi_accounts); 162 | 163 | withdraw_token( 164 | withdraw_token_ctx, 165 | WithdrawTokenParams { 166 | amount: bid_data.amount, 167 | } 168 | .into(), 169 | )?; 170 | } 171 | } 172 | 173 | Ok(()) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod error; 3 | pub mod instructions; 4 | pub mod state; 5 | 6 | use anchor_lang::prelude::*; 7 | 8 | #[allow(unused_imports)] 9 | use solana_security_txt::security_txt; 10 | 11 | pub use constants::*; 12 | pub use instructions::*; 13 | pub use state::*; 14 | 15 | declare_id!("4mFDYND4AVREYEJXCPhjq1LnbjELHHebJqG3NZechA7X"); 16 | 17 | #[cfg(not(feature = "no-entrypoint"))] 18 | security_txt! { 19 | name: "Soundwork Bid Program", 20 | project_url: "https://soundwork.io", 21 | contacts: "email:info@soundwork.io, twitter:@SoundworkSounds", 22 | policy: "https://github.com/SoundWorkLabs/marketplace-contracts/blob/master/SECURITY.md", 23 | preferred_languages: "en", 24 | source_code: "https://github.com/SoundWorkLabs/marketplace-contracts" 25 | } 26 | 27 | /// SOUNDWORK BID 28 | /// 29 | #[program] 30 | pub mod soundwork_bid { 31 | use super::*; 32 | 33 | /// Place a bid for a listed MPL Core asset on Soundwork 34 | /// 35 | pub fn make_bid(ctx: Context, params: MakeBidParams) -> Result<()> { 36 | MakeBid::make_bid(ctx, params) 37 | } 38 | 39 | /// Edit a placed bid on Soundwork 40 | /// 41 | pub fn edit_bid(ctx: Context, params: EditBidParams) -> Result<()> { 42 | EditBid::edit_bid(ctx, params) 43 | } 44 | 45 | /// Revoke placed bid 46 | /// 47 | pub fn revoke_bid(ctx: Context) -> Result<()> { 48 | RevokeBid::revoke_bid(ctx) 49 | } 50 | 51 | /// Accept placed bid 52 | /// 53 | pub fn accept_bid(ctx: Context) -> Result<()> { 54 | AcceptBid::accept_bid(ctx) 55 | } 56 | 57 | /// Reject placed bid 58 | /// 59 | pub fn reject_bid(ctx: Context) -> Result<()> { 60 | RejectBid::reject_bid(ctx) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/state/bid.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct BidData { 5 | /// PDA bump 6 | pub bump: u8, 7 | 8 | /// amount in lamports asset is being listed for 9 | pub amount: u64, 10 | 11 | /// asset owner 12 | pub authority: Pubkey, 13 | 14 | /// unix timestamp listing expires 15 | pub expiry_ts: i64, 16 | 17 | /// Unused reserved byte space for additive future changes. 18 | pub _reserved: [u8; 128], 19 | } 20 | 21 | impl BidData { 22 | pub const LEN: usize = 8 // anchor discriminator 23 | + 1 // bump 24 | + 8 // amount 25 | + 32 // authority address 26 | + 8 // expiry timestamp 27 | + 128; // reserved 28 | 29 | /// instantiate the bid data account with provided args 30 | pub fn new(bump: u8, amount: u64, authority: Pubkey, expiry_ts: i64) -> Self { 31 | Self { 32 | bump, 33 | amount, 34 | authority, 35 | expiry_ts, 36 | _reserved: [0; 128], 37 | } 38 | } 39 | 40 | /// update bid data account 41 | pub fn update( 42 | &mut self, 43 | updated_amount: Option, 44 | updated_expiry_ts: Option, 45 | ) -> (u64, i64) { 46 | let amount = updated_amount.unwrap_or(self.amount); 47 | 48 | let expiry_ts = updated_expiry_ts.unwrap_or(self.expiry_ts); 49 | 50 | (amount, expiry_ts) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /programs/soundwork-bid/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bid; 2 | 3 | pub use bid::*; 4 | -------------------------------------------------------------------------------- /programs/soundwork-create/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soundwork-create" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "soundwork_create" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | idl-build = ["anchor-lang/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = "0.30.1" 21 | mpl-core = "0.7.2" 22 | solana-security-txt = "1.1.1" 23 | solana-program = "1.18.20" 24 | ahash="=0.8.7" 25 | -------------------------------------------------------------------------------- /programs/soundwork-create/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/soundwork-create/src/constants.rs: -------------------------------------------------------------------------------- 1 | // use anchor_lang::{prelude::*, solana_program::pubkey}; 2 | -------------------------------------------------------------------------------- /programs/soundwork-create/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum ErrorCode { 5 | #[msg("Custom error message")] 6 | CustomError, 7 | } 8 | -------------------------------------------------------------------------------- /programs/soundwork-create/src/instructions/create.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use mpl_core::instructions::CreateV1CpiBuilder; 3 | 4 | // todo: remove and use SPL typed account 5 | #[derive(Clone)] 6 | pub struct Core; 7 | 8 | impl anchor_lang::Id for Core { 9 | fn id() -> Pubkey { 10 | mpl_core::ID 11 | } 12 | } 13 | 14 | /// Create MPL Core Asset context 15 | /// 16 | /// Expects the following accounts: 17 | /// 1. `[writeable, signer]` payer 18 | /// 2. `[writeable, signer]` asset 19 | /// 3. `[]` core program 20 | /// 4. `[]` `system program` 21 | /// 22 | /// Expects the following arguments 23 | /// 1. name: string 24 | /// 2. uri: string 25 | #[derive(Accounts)] 26 | pub struct CreateAsset<'info> { 27 | pub payer: Signer<'info>, 28 | 29 | /// CHECK: we are passing this in ourselves 30 | #[account(mut, signer)] 31 | pub asset: UncheckedAccount<'info>, 32 | 33 | pub core_program: Program<'info, Core>, 34 | 35 | pub system_program: Program<'info, System>, 36 | } 37 | 38 | impl CreateAsset<'_> { 39 | /// validation helper for our IX 40 | pub fn validate(&self) -> Result<()> { 41 | return Ok(()); 42 | } 43 | 44 | /// CPI into mpl_core program and mint our asset. 45 | /// 46 | /// * name - the title of the asset being minted 47 | /// * uri – off-chain URI of the metadata 48 | /// 49 | #[access_control(ctx.accounts.validate())] 50 | pub fn create_asset(ctx: Context, name: String, uri: String) -> Result<()> { 51 | CreateV1CpiBuilder::new(&ctx.accounts.core_program) 52 | .asset(&ctx.accounts.asset) 53 | .collection(None) 54 | .authority(Some(&ctx.accounts.payer)) 55 | .payer(&ctx.accounts.payer) 56 | .owner(Some(&ctx.accounts.payer)) 57 | .update_authority(Some(&ctx.accounts.payer)) 58 | .system_program(&ctx.accounts.system_program) 59 | .name(name) 60 | .uri(uri) 61 | .invoke()?; 62 | 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /programs/soundwork-create/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create; 2 | 3 | pub use create::*; 4 | -------------------------------------------------------------------------------- /programs/soundwork-create/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod error; 3 | pub mod instructions; 4 | pub mod state; 5 | 6 | use anchor_lang::prelude::*; 7 | 8 | #[allow(unused_imports)] 9 | use solana_security_txt::security_txt; 10 | 11 | pub use instructions::*; 12 | 13 | declare_id!("FegMMZtuFu8ZUTjdkt2yRR1TmGEAFZbjpDJWpQ4ueqyG"); 14 | 15 | #[cfg(not(feature = "no-entrypoint"))] 16 | security_txt! { 17 | name: "Soundwork Create Program", 18 | project_url: "https://soundwork.io", 19 | contacts: "email:info@soundwork.io, twitter:@SoundworkSounds", 20 | policy: "https://github.com/SoundWorkLabs/marketplace-contracts/blob/master/SECURITY.md", 21 | preferred_languages: "en", 22 | source_code: "https://github.com/SoundWorkLabs/marketplace-contracts" 23 | } 24 | 25 | /// SOUNDWORK CREATE 26 | /// 27 | /// admin IXs to interact with the soundwork programs 28 | #[program] 29 | pub mod soundwork_create { 30 | use super::*; 31 | 32 | /// Create MPL Core Asset 33 | /// 34 | /// Expect 35 | /// 1. name - title of the asset 36 | /// 2. uri - off chain metadata uri 37 | pub fn create(ctx: Context, name: String, uri: String) -> Result<()> { 38 | CreateAsset::create_asset(ctx, name, uri) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /programs/soundwork-create/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /programs/soundwork-list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "soundwork-list" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "soundwork_list" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = { version = "0.30.1", features = ["init-if-needed"] } 21 | anchor-spl = "0.30.1" 22 | mpl-core = "0.7.2" 23 | solana-security-txt = "1.1.1" 24 | solana-program = "1.18.20" 25 | ahash="=0.8.7" 26 | -------------------------------------------------------------------------------- /programs/soundwork-list/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/constants.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{prelude::*, solana_program::pubkey}; 2 | 3 | #[constant] 4 | pub const SEED_PREFIX: &[u8] = b"Kessoku"; 5 | 6 | #[constant] 7 | pub const SEED_ASSET_MANAGER: &[u8] = b"Seika"; 8 | 9 | #[constant] 10 | pub const SEED_MARKETPLACE_CONFIG: &[u8] = b"Ijichi"; 11 | 12 | #[constant] 13 | pub const SEED_LISTING_DATA: &[u8] = b"Hitori"; 14 | 15 | #[constant] 16 | pub const SEED_WALLET: &[u8] = b"Yamada"; 17 | 18 | #[constant] 19 | pub const ADMIN_ADDRESS: Pubkey = pubkey!("4kg8oh3jdNtn7j2wcS7TrUua31AgbLzDVkBZgTAe44aF"); 20 | 21 | #[constant] 22 | pub const TREASURY_ADDRESS: Pubkey = pubkey!("4kg8oh3jdNtn7j2wcS7TrUua31AgbLzDVkBZgTAe44aF"); 23 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum ListErrorCode { 5 | #[msg("Insufficient funds to purchase asset")] 6 | InsufficientFunds, 7 | #[msg("Invalid operation!")] 8 | InvalidOperation, 9 | #[msg("You do no have authority to perform the requested operation!")] 10 | InvalidAuthority, 11 | #[msg("The value provided should not be zero.")] 12 | ZeroValueNotAllowed, 13 | #[msg("The mint address provided does not match seller's provided mint address.")] 14 | PaymentMintAddressMismatch, 15 | #[msg("An account required for this operation is missing.")] 16 | MissingAccount, 17 | } 18 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use std::result::Result as StdResult; 2 | 3 | use solana_program::pubkey::Pubkey; 4 | 5 | // todo(Jimii): use royalty plugin 6 | // selling price + protocol fee + royalty 7 | pub fn calculate_total_buy_fee(amount: u64, taker_fee_bps: u8) -> StdResult { 8 | let fee = amount 9 | .checked_mul(taker_fee_bps as u64) 10 | .ok_or("fee calculation overflow")? 11 | .checked_div(10000) 12 | .ok_or("fee calculation overflow")?; 13 | 14 | // todo: royalty 15 | 16 | let total = amount.checked_add(fee).ok_or("")?; 17 | 18 | Ok(total) 19 | } 20 | 21 | // export core program type 22 | // todo: remove and use SPL typed account 23 | #[derive(Clone)] 24 | pub struct Core; 25 | 26 | impl anchor_lang::Id for Core { 27 | fn id() -> Pubkey { 28 | mpl_core::ID 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/admin/init_escrow.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{AssetManager, constants::{SEED_ASSET_MANAGER, SEED_PREFIX, ADMIN_ADDRESS}, helpers::Core}; 4 | 5 | /// Initialize AssetManager escrow account 6 | /// 7 | /// ### Accounts: 8 | /// 9 | /// 1. `[writeable, signer]` payer 10 | /// 2. `[writeable, signer]` assetManager 11 | /// 3. `[]` core program 12 | /// 4. `[]` `system program` 13 | 14 | #[derive(Accounts)] 15 | pub struct InitEscrow<'info> { 16 | // todo: move this admin address check to the validate functions 17 | // find out a way to do this using a slice or vector of verified addresses 18 | #[account(mut, address = ADMIN_ADDRESS)] 19 | pub payer: Signer<'info>, 20 | 21 | #[account( 22 | init, 23 | payer=payer, 24 | space=AssetManager::LEN, 25 | seeds = [SEED_PREFIX, SEED_ASSET_MANAGER], 26 | bump 27 | )] 28 | pub asset_manager: Account<'info, AssetManager>, 29 | 30 | pub core_program: Program<'info, Core>, 31 | 32 | pub system_program: Program<'info, System>, 33 | } 34 | 35 | impl InitEscrow<'_> { 36 | /// validation helper for our IX 37 | pub fn validate(&self) -> Result<()> { 38 | return Ok(()); 39 | } 40 | 41 | /// Initialize the Asset Manager escrow account 42 | /// 43 | #[access_control(ctx.accounts.validate())] 44 | pub fn init_escrow(ctx: Context) -> Result<()> { 45 | msg!("initialized escrow account"); 46 | 47 | let asset_manager = &mut ctx.accounts.asset_manager; 48 | asset_manager.bump = ctx.bumps.asset_manager; // ? is this safe 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/admin/init_mp_config.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{ 4 | constants::{ADMIN_ADDRESS, SEED_MARKETPLACE_CONFIG, SEED_PREFIX}, 5 | MarketPlaceConfig, 6 | }; 7 | 8 | #[derive(AnchorSerialize, AnchorDeserialize)] 9 | pub struct InitMarketPlaceConfigParams { 10 | /// taker fee basis points, /100% 11 | pub taker_fee_bps: u8, 12 | 13 | /// treasury address 14 | pub treasury_address: Pubkey, 15 | } 16 | 17 | /// Initialize protocol state accounts 18 | /// 19 | /// ### Accounts: 20 | /// 21 | /// 1. `[writeable, signer]` payer 22 | /// 2. `[writeable, signer]` assetManager 23 | /// 3. `[]` `system program` 24 | /// 25 | /// ### Parameters 26 | /// 1. params: [InitMarketPlaceConfigParams] 27 | /// 28 | #[derive(Accounts)] 29 | #[instruction(params: InitMarketPlaceConfigParams)] 30 | pub struct InitMarketplaceConfig<'info> { 31 | // todo: move this admin address check to the validate functions 32 | // find out a way to do this using a slice or vector of verified addresses 33 | #[account(mut, address = ADMIN_ADDRESS)] 34 | pub payer: Signer<'info>, 35 | 36 | #[account( 37 | init, 38 | payer = payer, 39 | space = MarketPlaceConfig::LEN, 40 | seeds = [SEED_PREFIX, SEED_MARKETPLACE_CONFIG], 41 | bump 42 | )] 43 | pub marketplace_config: Account<'info, MarketPlaceConfig>, 44 | 45 | pub system_program: Program<'info, System>, 46 | } 47 | 48 | impl InitMarketplaceConfig<'_> { 49 | /// validation helper for our IX 50 | pub fn validate(&self) -> Result<()> { 51 | return Ok(()); 52 | } 53 | 54 | /// Initialize the marketplace config account 55 | /// 56 | #[access_control(ctx.accounts.validate())] 57 | pub fn init_marketplace_config( 58 | ctx: Context, 59 | params: InitMarketPlaceConfigParams, 60 | ) -> Result<()> { 61 | msg!("initialized marketplace config account"); 62 | 63 | let marketplace_config = &mut ctx.accounts.marketplace_config; 64 | **marketplace_config = MarketPlaceConfig::new( 65 | ctx.bumps.marketplace_config, 66 | params.treasury_address, 67 | params.taker_fee_bps, 68 | ); 69 | 70 | Ok(()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/admin/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod init_escrow; 2 | pub mod init_mp_config; 3 | 4 | pub use init_escrow::*; 5 | pub use init_mp_config::*; 6 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/buy_listing.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{ 2 | prelude::*, 3 | system_program::{self, Transfer as SOLTransfer}, 4 | }; 5 | use anchor_spl::{ 6 | associated_token::AssociatedToken, 7 | token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked as SPLTransferChecked}, 8 | }; 9 | use mpl_core::instructions::TransferV1CpiBuilder; 10 | 11 | use crate::{ 12 | constants::{SEED_ASSET_MANAGER, SEED_PREFIX}, 13 | error::ListErrorCode, 14 | helpers::{calculate_total_buy_fee, Core}, 15 | AssetManager, ListingData, MarketPlaceConfig, PaymentOption, Wallet, SEED_WALLET, 16 | }; 17 | 18 | #[derive(AnchorSerialize, AnchorDeserialize)] 19 | pub struct BuyAssetParams { 20 | /// only used when buying using a bid amount 21 | pub bid_amount: u64, 22 | } 23 | 24 | /// Buy a listed MPL core asset on Soundwork 25 | /// 26 | /// ### Accounts: 27 | /// 28 | /// 1. `[writeable, signer]` payer 29 | /// 2. `[writeable]` buyer 30 | /// 3. `[writeable]` seller 31 | /// 4. `[writeable, optional]` wallet as buyer 32 | /// 5. `[writeable]` asset 33 | /// 6. `[writeable]` collection 34 | /// 7. `[writeable, optional]` payment mint 35 | /// 8. `[writeable, optional]` wallet token account 36 | /// 9. `[writeable, optional]` buyer token account 37 | /// 10. `[writeable, optional]` seller token account 38 | /// 11. `[writeable, optional]` treasury token account 39 | /// 12. `[writeable]` treasury 40 | /// 13. `[writeable]` listing data account 41 | /// 14. `[]` asset manager 42 | /// 15. `[]` marketplace config 43 | /// 16. `[]` core program 44 | /// 17. `[]` token program 45 | /// 18. `[]` associated token program 46 | /// 19. `[]` system program 47 | /// 48 | /// ### Parameters 49 | /// 50 | /// 1. params: [BuyAssetParams] 51 | /// 52 | #[derive(Accounts)] 53 | pub struct BuyAsset<'info> { 54 | #[account(mut)] 55 | pub payer: Signer<'info>, 56 | 57 | #[account(mut)] 58 | pub buyer: SystemAccount<'info>, 59 | 60 | #[account(mut)] 61 | pub seller: SystemAccount<'info>, 62 | 63 | #[account(mut)] 64 | pub wallet_as_buyer: Option>>, 65 | 66 | /// CHECK: checked by us 67 | #[account(mut)] 68 | pub asset: UncheckedAccount<'info>, 69 | 70 | #[account(mut)] 71 | pub collection: Option>, 72 | 73 | #[account(mut)] 74 | pub payment_mint: Option>>, 75 | 76 | // we expect this to be initialized because this is used only for bids 77 | // a check before placing a bid, makes sure bidder has enough funds to make bid 78 | #[account(mut)] 79 | pub wallet_token_account: Option>>, 80 | 81 | #[account(mut)] 82 | pub buyer_token_account: Option>>, 83 | 84 | // maybe we offered seller a list of tokens to chose from, 85 | // hence seller token account might not be initialized 86 | #[account( 87 | init_if_needed, 88 | payer = buyer, 89 | associated_token::mint = payment_mint, 90 | associated_token::authority = seller, 91 | )] 92 | pub seller_token_account: Option>>, 93 | 94 | #[account( 95 | init_if_needed, 96 | payer = buyer, 97 | associated_token::mint = payment_mint, 98 | associated_token::authority = treasury, 99 | )] 100 | pub treasury_token_account: Option>>, 101 | 102 | #[account( 103 | mut, 104 | address = marketplace_config.treasury_address 105 | )] 106 | pub treasury: SystemAccount<'info>, 107 | 108 | #[account(mut, close = seller)] 109 | pub listing_data: Box>, 110 | 111 | pub asset_manager: Box>, 112 | 113 | pub marketplace_config: Box>, 114 | 115 | pub core_program: Program<'info, Core>, 116 | 117 | pub token_program: Program<'info, Token>, 118 | 119 | pub associated_token_program: Program<'info, AssociatedToken>, 120 | 121 | pub system_program: Program<'info, System>, 122 | } 123 | 124 | impl BuyAsset<'_> { 125 | /// validation helper for our IX 126 | pub fn validate(&self) -> Result<()> { 127 | return Ok(()); 128 | } 129 | 130 | /// buy a MPL core asset listed on the marketplace 131 | /// 132 | #[access_control(ctx.accounts.validate())] 133 | pub fn buy_asset(ctx: Context, params: Option) -> Result<()> { 134 | let listing_data = &mut ctx.accounts.listing_data; 135 | let asset_manager = &ctx.accounts.asset_manager; 136 | let escrow_wallet = &mut ctx.accounts.wallet_as_buyer; 137 | 138 | // using the optional escrow_wallet_as buyer account to check who will be paying for the NFT 139 | // if escrow wallet is provided, use to pay for the asset, else use payer 140 | match (escrow_wallet, params) { 141 | // only use escrow wallet to buy asset if both the account and bid_amount is provided 142 | (Some(wallet), Some(params)) => { 143 | let taker_fee_bps = ctx.accounts.marketplace_config.taker_fee_bps; 144 | let total_cost = calculate_total_buy_fee(params.bid_amount, taker_fee_bps).unwrap(); 145 | let protocol_take = total_cost.checked_sub(params.bid_amount).unwrap(); // if fee calculation OK, assume safe to unwrap here 146 | 147 | // if use wanted to pay with tokens 148 | match listing_data.payment_option { 149 | PaymentOption::Native => { 150 | if wallet.get_lamports() < total_cost { 151 | return Err(error!(ListErrorCode::InsufficientFunds)); 152 | } 153 | 154 | // transfer to seller 155 | wallet.sub_lamports(params.bid_amount)?; 156 | ctx.accounts.seller.add_lamports(params.bid_amount)?; 157 | 158 | // transfer to protocol treasury 159 | wallet.sub_lamports(protocol_take)?; 160 | ctx.accounts.treasury.add_lamports(protocol_take)?; 161 | } 162 | PaymentOption::Token { mint } => { 163 | let payment_mint = &ctx.accounts.payment_mint.as_ref(); 164 | let wallet_token_account = &ctx.accounts.wallet_token_account.as_ref(); 165 | let seller_token_account = &ctx.accounts.seller_token_account.as_ref(); 166 | let treasury_token_account = &ctx.accounts.treasury_token_account.as_ref(); 167 | 168 | // allow us to use unwrap() safely 169 | if payment_mint.is_none() 170 | || wallet_token_account.is_none() 171 | || seller_token_account.is_none() 172 | || treasury_token_account.is_none() 173 | { 174 | return Err(error!(ListErrorCode::MissingAccount)); 175 | } 176 | 177 | if payment_mint.unwrap().key() != mint { 178 | return Err(error!(ListErrorCode::PaymentMintAddressMismatch)); 179 | } 180 | 181 | if wallet_token_account.unwrap().amount < total_cost { 182 | return Err(error!(ListErrorCode::InsufficientFunds)); 183 | } 184 | 185 | // signer seeds 186 | let bump = &[wallet.bump]; 187 | let signer_seeds = &[&[ 188 | SEED_PREFIX, 189 | SEED_WALLET, 190 | ctx.accounts.buyer.key.as_ref(), // owns escrow wallet on soundwork 191 | bump, 192 | ][..]]; 193 | 194 | let cpi_program = ctx.accounts.token_program.to_account_info(); 195 | 196 | let seller_cpi_accounts = SPLTransferChecked { 197 | from: wallet_token_account.unwrap().to_account_info(), 198 | mint: payment_mint.unwrap().to_account_info(), 199 | to: seller_token_account.unwrap().to_account_info(), 200 | authority: wallet.to_account_info(), 201 | }; 202 | 203 | let seller_cpi_ctx = CpiContext::new_with_signer( 204 | cpi_program.clone(), 205 | seller_cpi_accounts, 206 | signer_seeds, 207 | ); 208 | 209 | transfer_checked( 210 | seller_cpi_ctx, 211 | params.bid_amount, 212 | payment_mint.unwrap().decimals, 213 | )?; 214 | 215 | // transfer to protocol 216 | let protocol_cpi_accounts = SPLTransferChecked { 217 | from: wallet_token_account.unwrap().to_account_info(), 218 | mint: payment_mint.unwrap().to_account_info(), 219 | to: treasury_token_account.unwrap().to_account_info(), 220 | authority: wallet.to_account_info(), 221 | }; 222 | 223 | let protocol_cpi_ctx = CpiContext::new_with_signer( 224 | cpi_program.clone(), 225 | protocol_cpi_accounts, 226 | signer_seeds, 227 | ); 228 | 229 | transfer_checked( 230 | protocol_cpi_ctx, 231 | protocol_take, 232 | payment_mint.unwrap().decimals, 233 | )?; 234 | } 235 | }; 236 | } 237 | 238 | // use buyers account to buy asset 239 | (None, None) => { 240 | // price calc 241 | let taker_fee_bps = ctx.accounts.marketplace_config.taker_fee_bps; 242 | let total_cost = 243 | calculate_total_buy_fee(listing_data.amount, taker_fee_bps).unwrap(); 244 | let protocol_take = total_cost.checked_sub(listing_data.amount).unwrap(); // if fee calculation OK, assume safe to unwrap here 245 | 246 | match listing_data.payment_option { 247 | PaymentOption::Native => { 248 | // accounts 249 | let buyer = ctx.accounts.buyer.as_ref(); 250 | let seller = ctx.accounts.seller.as_ref(); 251 | 252 | if buyer.get_lamports() < total_cost { 253 | return Err(error!(ListErrorCode::InsufficientFunds)); 254 | } 255 | 256 | // transfers 257 | let cpi_program = ctx.accounts.system_program.to_account_info(); 258 | 259 | // transfer to seller 260 | let seller_cpi_accounts = SOLTransfer { 261 | from: buyer.to_account_info(), 262 | to: seller.to_account_info(), 263 | }; 264 | 265 | let seller_cpi_context = 266 | CpiContext::new(cpi_program.clone(), seller_cpi_accounts); 267 | 268 | system_program::transfer(seller_cpi_context, listing_data.amount)?; 269 | 270 | // protocol take 271 | let protocol_cpi_accounts = SOLTransfer { 272 | from: buyer.to_account_info(), 273 | to: seller.to_account_info(), 274 | }; 275 | 276 | let protocol_cpi_context = 277 | CpiContext::new(cpi_program.clone(), protocol_cpi_accounts); 278 | 279 | system_program::transfer(protocol_cpi_context, protocol_take)?; 280 | } 281 | PaymentOption::Token { mint } => { 282 | // accounts 283 | let payment_mint = &ctx.accounts.payment_mint.as_ref(); 284 | let buyer = ctx.accounts.buyer.as_ref(); 285 | let buyer_token_account = ctx.accounts.buyer_token_account.as_ref(); 286 | let seller_token_account = ctx.accounts.seller_token_account.as_ref(); 287 | let treasury_token_account = ctx.accounts.treasury_token_account.as_ref(); 288 | 289 | // check if optional accounts exist so that we can safely `unwrap()` 290 | if payment_mint.is_none() 291 | || buyer_token_account.is_none() 292 | || seller_token_account.is_none() 293 | || treasury_token_account.is_none() 294 | { 295 | return Err(error!(ListErrorCode::MissingAccount)); 296 | } 297 | 298 | // check payment mint 299 | if payment_mint.unwrap().key() != mint { 300 | return Err(error!(ListErrorCode::PaymentMintAddressMismatch)); 301 | } 302 | 303 | // check buyer amount 304 | if buyer_token_account.unwrap().amount < total_cost { 305 | return Err(error!(ListErrorCode::InsufficientFunds)); 306 | } 307 | 308 | // transfers 309 | let cpi_program = ctx.accounts.token_program.to_account_info(); 310 | 311 | // seller 312 | let seller_cpi_accounts = SPLTransferChecked { 313 | from: buyer_token_account.unwrap().to_account_info(), 314 | mint: payment_mint.unwrap().to_account_info(), 315 | to: seller_token_account.unwrap().to_account_info(), 316 | authority: buyer.to_account_info(), 317 | }; 318 | 319 | let seller_cpi_ctx = 320 | CpiContext::new(cpi_program.clone(), seller_cpi_accounts); 321 | 322 | transfer_checked( 323 | seller_cpi_ctx, 324 | listing_data.amount, 325 | payment_mint.unwrap().decimals, 326 | )?; 327 | 328 | // protocol take 329 | let buyer_cpi_accounts = SPLTransferChecked { 330 | from: buyer_token_account.unwrap().to_account_info(), 331 | mint: payment_mint.unwrap().to_account_info(), 332 | to: treasury_token_account.unwrap().to_account_info(), 333 | authority: buyer.to_account_info(), 334 | }; 335 | 336 | let seller_cpi_ctx = 337 | CpiContext::new(cpi_program.clone(), buyer_cpi_accounts); 338 | 339 | transfer_checked( 340 | seller_cpi_ctx, 341 | protocol_take, 342 | payment_mint.unwrap().decimals, 343 | )?; 344 | } 345 | } 346 | } 347 | 348 | // invalid, panic 349 | (None, Some(_)) => todo!("Create suitable error message for this"), 350 | 351 | // invalid, panic 352 | (Some(_), None) => todo!("Create suitable error message for this"), 353 | }; 354 | 355 | // transfer to buyer 356 | let bump = &[asset_manager.bump]; 357 | let signer_seeds = &[&[SEED_PREFIX, SEED_ASSET_MANAGER, bump][..]]; 358 | 359 | TransferV1CpiBuilder::new(&ctx.accounts.core_program) 360 | .asset(&ctx.accounts.asset) 361 | .collection(ctx.accounts.collection.as_ref()) 362 | .payer(&ctx.accounts.payer) 363 | .authority(Some(&asset_manager.to_account_info())) 364 | .new_owner(&ctx.accounts.payer.to_account_info()) 365 | .system_program(Some(&ctx.accounts.system_program)) 366 | .invoke_signed(signer_seeds)?; 367 | 368 | Ok(()) 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/escrow_ix/deposit_sol.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{ 2 | prelude::*, 3 | system_program::{self, System, Transfer}, 4 | }; 5 | 6 | use crate::Wallet; 7 | 8 | #[derive(AnchorSerialize, AnchorDeserialize)] 9 | pub struct DepositSolParams { 10 | /// amount to deposit in lamports 11 | pub amount: u64, 12 | } 13 | 14 | /// Deposit Sol into escrow wallet managed by the list program 15 | /// 16 | /// ### Accounts: 17 | /// 18 | /// 1. `[writeable, signer]` authority 19 | /// 2. `[writeable]` wallet 20 | /// 3. `[]` system program 21 | /// 22 | /// ### Parameters 23 | /// 1. params: [DepositSolParams] 24 | /// 25 | #[derive(Accounts)] 26 | pub struct DepositSol<'info> { 27 | #[account(mut)] 28 | pub authority: Signer<'info>, 29 | 30 | #[account(mut)] 31 | pub wallet: Account<'info, Wallet>, 32 | 33 | pub system_program: Program<'info, System>, 34 | } 35 | 36 | impl DepositSol<'_> { 37 | /// validation helper for our IX 38 | pub fn validate(&self) -> Result<()> { 39 | return Ok(()); 40 | } 41 | 42 | /// Deposit native sol into user's wallet 43 | /// 44 | #[access_control(ctx.accounts.validate())] 45 | pub fn deposit_sol(ctx: Context, params: DepositSolParams) -> Result<()> { 46 | let cpi_accounts = Transfer { 47 | from: ctx.accounts.authority.to_account_info(), 48 | to: ctx.accounts.wallet.to_account_info(), 49 | }; 50 | let cpi_program = ctx.accounts.system_program.to_account_info(); 51 | 52 | let cpi_context = CpiContext::new(cpi_program, cpi_accounts); 53 | 54 | system_program::transfer(cpi_context, params.amount)?; 55 | 56 | Ok(()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/escrow_ix/deposit_token.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked}, 5 | }; 6 | 7 | use crate::Wallet; 8 | 9 | #[derive(AnchorSerialize, AnchorDeserialize)] 10 | pub struct DepositTokenParams { 11 | /// amount to deposit in lamports 12 | pub amount: u64, 13 | } 14 | 15 | /// Deposit Tokens into escrow wallet managed by the list program 16 | /// 17 | /// ### Accounts: 18 | /// 1. `[writeable, signer]` authority 19 | /// 2. `[writeable]` wallet 20 | /// 3. `[writeable, optional]` mint 21 | /// 4. `[writeable, optional]` authority associated token address 22 | /// 5. `[writeable, optional]` wallet associated token address 23 | /// 6. `[]` token program 24 | /// 7. `[]` associated token program 25 | /// 8. `[]` system program 26 | /// 27 | /// ### Parameters 28 | /// 1. params: [DepositTokenParams] 29 | /// 30 | #[derive(Accounts)] 31 | pub struct DepositToken<'info> { 32 | #[account(mut)] 33 | pub authority: Signer<'info>, 34 | 35 | #[account(mut)] 36 | pub wallet: Account<'info, Wallet>, 37 | 38 | #[account(mut)] 39 | pub mint: Account<'info, Mint>, 40 | 41 | #[account(mut)] 42 | pub authority_token_account: Account<'info, TokenAccount>, 43 | 44 | #[account( 45 | init_if_needed, 46 | payer = authority, 47 | associated_token::mint = mint, 48 | associated_token::authority = wallet, 49 | )] 50 | pub wallet_token_account: Account<'info, TokenAccount>, 51 | pub token_program: Program<'info, Token>, 52 | pub associated_token_program: Program<'info, AssociatedToken>, 53 | pub system_program: Program<'info, System>, 54 | } 55 | 56 | impl DepositToken<'_> { 57 | /// validation helper for our IX 58 | pub fn validate(&self) -> Result<()> { 59 | return Ok(()); 60 | } 61 | 62 | /// Deposit native sol into user's wallet 63 | /// 64 | #[access_control(ctx.accounts.validate())] 65 | pub fn deposit_token(ctx: Context, params: DepositTokenParams) -> Result<()> { 66 | let DepositTokenParams { amount } = params; 67 | 68 | let cpi_program = ctx.accounts.token_program.to_account_info(); 69 | let cpi_accounts = TransferChecked { 70 | from: ctx.accounts.authority_token_account.to_account_info(), 71 | mint: ctx.accounts.mint.to_account_info(), 72 | to: ctx.accounts.wallet_token_account.to_account_info(), 73 | authority: ctx.accounts.authority.to_account_info(), 74 | }; 75 | 76 | let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); 77 | 78 | transfer_checked(cpi_ctx, amount, ctx.accounts.mint.decimals)?; 79 | 80 | Ok(()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/escrow_ix/init_wallet.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{ 4 | constants::{SEED_PREFIX, SEED_WALLET}, 5 | Wallet, 6 | }; 7 | 8 | /// Initialize user escrow wallet 9 | /// 10 | /// ### Accounts: 11 | /// 12 | /// 1. `[writeable, signer]` authority 13 | /// 2. `[writeable]` wallet 14 | /// 3. `[]` system program 15 | /// 16 | #[derive(Accounts)] 17 | pub struct InitWallet<'info> { 18 | #[account(mut)] 19 | pub authority: Signer<'info>, 20 | 21 | #[account( 22 | init, 23 | payer = authority, 24 | space = Wallet::LEN, 25 | seeds = [SEED_PREFIX, SEED_WALLET, authority.key().as_ref()], 26 | bump 27 | )] 28 | pub wallet: Account<'info, Wallet>, 29 | 30 | pub system_program: Program<'info, System>, 31 | } 32 | 33 | impl InitWallet<'_> { 34 | /// validation helper for our IX 35 | pub fn validate(&self) -> Result<()> { 36 | return Ok(()); 37 | } 38 | 39 | /// Initialize our escrow marketplace wallet 40 | /// 41 | #[access_control(ctx.accounts.validate())] 42 | pub fn init_wallet(ctx: Context) -> Result<()> { 43 | let wallet = &mut ctx.accounts.wallet; 44 | 45 | **wallet = Wallet::new(&ctx.accounts.authority.key(), ctx.bumps.wallet); 46 | 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/escrow_ix/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod deposit_sol; 2 | pub mod deposit_token; 3 | pub mod init_wallet; 4 | pub mod withdraw_sol; 5 | pub mod withdraw_token; 6 | 7 | pub use deposit_sol::*; 8 | pub use deposit_token::*; 9 | pub use init_wallet::*; 10 | pub use withdraw_sol::*; 11 | pub use withdraw_token::*; 12 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/escrow_ix/withdraw_sol.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::{prelude::*, system_program::System}; 2 | 3 | use crate::{error::ListErrorCode, Wallet}; 4 | 5 | #[derive(AnchorSerialize, AnchorDeserialize)] 6 | pub struct WithdrawSolParams { 7 | /// amount to withdraw in lamports 8 | pub amount: u64, 9 | } 10 | 11 | /// Withdraw SOL from escrow wallet managed by the program. 12 | /// 13 | /// ### Accounts: 14 | /// 15 | /// 1. `[writeable, signer]` payer 16 | /// 2. `[writeable]` authority 17 | /// 3. `[writeable]` wallet 18 | /// 4. `[]` system program 19 | /// 20 | /// ### Parameters 21 | /// 22 | /// 1. params: [WithdrawSolParams] 23 | /// 24 | #[derive(Accounts)] 25 | pub struct WithdrawSol<'info> { 26 | #[account(mut)] 27 | pub payer: Signer<'info>, 28 | 29 | /// CHECK: checked in the ix 30 | #[account( 31 | mut, 32 | address = wallet.authority @ ListErrorCode::InvalidAuthority 33 | )] 34 | pub authority: UncheckedAccount<'info>, 35 | 36 | #[account(mut)] 37 | pub wallet: Account<'info, Wallet>, 38 | 39 | pub system_program: Program<'info, System>, 40 | } 41 | 42 | impl WithdrawSol<'_> { 43 | /// validation helper for our IX 44 | pub fn validate(&self) -> Result<()> { 45 | return Ok(()); 46 | } 47 | 48 | /// Deposit native sol into user's wallet 49 | /// 50 | /// todo(Jimii): withdraw to another wallet per user's request 51 | #[access_control(ctx.accounts.validate())] 52 | pub fn withdraw_sol( 53 | ctx: Context, 54 | params: Option, 55 | ) -> Result<()> { 56 | let wallet = &mut ctx.accounts.wallet; 57 | 58 | if let Some(WithdrawSolParams { amount }) = params { 59 | if amount > wallet.get_lamports() { 60 | return Err(error!(ListErrorCode::InsufficientFunds)); 61 | } 62 | 63 | // transfer requested amount 64 | wallet.sub_lamports(amount)?; 65 | ctx.accounts.authority.add_lamports(amount)?; 66 | 67 | return Ok(()); 68 | } 69 | 70 | // else close account and return everything to user 71 | wallet.close(ctx.accounts.authority.to_account_info())?; 72 | 73 | Ok(()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/escrow_ix/withdraw_token.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_spl::{ 3 | associated_token::AssociatedToken, 4 | token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked}, 5 | }; 6 | 7 | use crate::{ 8 | constants::{SEED_PREFIX, SEED_WALLET}, 9 | error::ListErrorCode, 10 | Wallet, 11 | }; 12 | 13 | #[derive(AnchorSerialize, AnchorDeserialize)] 14 | pub struct WithdrawTokenParams { 15 | /// amount to withdraw in lamports 16 | pub amount: u64, 17 | } 18 | 19 | /// Withdraw Tokens from escrow wallet managed by the list program 20 | /// 21 | /// ### Accounts: 22 | /// 1. `[writeable, signer]` payer 23 | /// 2. `[writeable]` authority 24 | /// 3. `[writeable]` wallet 25 | /// 4. `[writeable, optional]` mint 26 | /// 5. `[writeable, optional]` authority associated token address 27 | /// 6. `[writeable, optional]` wallet associated token address 28 | /// 7. `[]` token program 29 | /// 8. `[]` associated token program 30 | /// 9. `[]` system program 31 | /// 32 | /// ### Parameters 33 | /// 34 | /// 1. params: [WithdrawTokenParams] 35 | /// 36 | #[derive(Accounts)] 37 | pub struct WithdrawToken<'info> { 38 | #[account(mut)] 39 | pub payer: Signer<'info>, 40 | 41 | /// CHECK: checked in the ix 42 | #[account( 43 | mut, 44 | address = wallet.authority @ ListErrorCode::InvalidAuthority 45 | )] 46 | pub authority: UncheckedAccount<'info>, 47 | 48 | #[account(mut)] 49 | pub wallet: Account<'info, Wallet>, 50 | 51 | #[account(mut)] 52 | pub mint: Account<'info, Mint>, 53 | 54 | #[account(mut)] 55 | pub authority_token_account: Account<'info, TokenAccount>, 56 | 57 | #[account(mut)] 58 | pub wallet_token_account: Account<'info, TokenAccount>, 59 | 60 | pub token_program: Program<'info, Token>, 61 | pub associated_token_program: Program<'info, AssociatedToken>, 62 | pub system_program: Program<'info, System>, 63 | } 64 | 65 | impl WithdrawToken<'_> { 66 | /// validation helper for our IX 67 | pub fn validate(&self) -> Result<()> { 68 | return Ok(()); 69 | } 70 | 71 | /// Deposit native sol into user's wallet 72 | /// 73 | /// todo(Jimii): withdraw to another wallet per user's request 74 | #[access_control(ctx.accounts.validate())] 75 | pub fn withdraw_token(ctx: Context, params: WithdrawTokenParams) -> Result<()> { 76 | let wallet = &mut ctx.accounts.wallet; 77 | let authority = &mut ctx.accounts.authority; 78 | let WithdrawTokenParams { amount } = params; 79 | 80 | // sanity check 81 | if amount == 0 { 82 | return Err(error!(ListErrorCode::ZeroValueNotAllowed)); 83 | } 84 | 85 | let bump = &[wallet.bump]; 86 | let signer_seeds = &[&[SEED_PREFIX, SEED_WALLET, authority.key.as_ref(), bump][..]]; 87 | 88 | let cpi_program = ctx.accounts.token_program.to_account_info(); 89 | let cpi_accounts = TransferChecked { 90 | from: ctx.accounts.wallet_token_account.to_account_info(), 91 | mint: ctx.accounts.mint.to_account_info(), 92 | to: ctx.accounts.authority_token_account.to_account_info(), 93 | authority: wallet.to_account_info(), 94 | }; 95 | 96 | let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds); 97 | 98 | transfer_checked(cpi_ctx, amount, ctx.accounts.mint.decimals)?; 99 | 100 | Ok(()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/list.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use mpl_core::instructions::TransferV1CpiBuilder; 3 | 4 | use crate::{ 5 | constants::{SEED_LISTING_DATA, SEED_PREFIX}, 6 | helpers::Core, 7 | AssetManager, ListingData, PaymentOption, 8 | }; 9 | 10 | #[derive(AnchorSerialize, AnchorDeserialize)] 11 | pub struct ListAssetParams { 12 | /// listing amount/price in lamports 13 | pub amount: u64, 14 | 15 | /// which method can be used to purchase the listed asset 16 | pub payment_option: PaymentOption, 17 | } 18 | 19 | /// LIST an MPL core asset on soundwork 20 | /// 21 | /// ### Accounts: 22 | /// 23 | /// 1. `[writeable, signer]` payer 24 | /// 2. `[writeable]` asset 25 | /// 3. `[writeable]` listing data account 26 | /// 4. `[]` asset manager 27 | /// 5. `[]` core program 28 | /// 6. `[]` system program 29 | /// 30 | /// ### Parameters 31 | /// 32 | /// 1. params: [ListTokenParams] 33 | /// 34 | #[derive(Accounts)] 35 | #[instruction(params: ListAssetParams)] 36 | pub struct ListAsset<'info> { 37 | #[account(mut)] 38 | pub payer: Signer<'info>, 39 | 40 | /// CHECK: checked by us 41 | #[account(mut)] 42 | pub asset: AccountInfo<'info>, 43 | 44 | /// CHECK: checked by us 45 | #[account( 46 | init, 47 | payer = payer, 48 | space = ListingData::LEN, 49 | seeds = [SEED_PREFIX, SEED_LISTING_DATA, asset.key().as_ref()], 50 | bump 51 | )] 52 | pub listing_data: Account<'info, ListingData>, 53 | 54 | pub asset_manager: Account<'info, AssetManager>, 55 | 56 | /// CHECK: checked by us 57 | #[account(mut)] 58 | pub collection: Option>, 59 | 60 | pub core_program: Program<'info, Core>, 61 | 62 | pub system_program: Program<'info, System>, 63 | } 64 | 65 | impl ListAsset<'_> { 66 | /// validation helper for our IX 67 | pub fn validate(&self) -> Result<()> { 68 | return Ok(()); 69 | } 70 | 71 | /// list MPL core asset on the marketplace 72 | /// 73 | #[access_control(ctx.accounts.validate())] 74 | pub fn list_asset(ctx: Context, params: ListAssetParams) -> Result<()> { 75 | let listing_data = &mut ctx.accounts.listing_data; 76 | 77 | **listing_data = ListingData::new( 78 | ctx.bumps.listing_data, 79 | params.amount, 80 | ctx.accounts.payer.key(), 81 | ctx.accounts.asset.key(), 82 | params.payment_option, 83 | ); 84 | 85 | // transfer to assetManager 86 | 87 | TransferV1CpiBuilder::new(&ctx.accounts.core_program) 88 | .asset(&ctx.accounts.asset) 89 | .payer(&ctx.accounts.payer) 90 | .authority(Some(&ctx.accounts.payer)) 91 | .collection(ctx.accounts.collection.as_ref()) 92 | .new_owner(&ctx.accounts.asset_manager.to_account_info()) 93 | .system_program(Some(&ctx.accounts.system_program)) 94 | .invoke()?; 95 | 96 | Ok(()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod admin; 2 | pub mod buy_listing; 3 | pub mod escrow_ix; 4 | pub mod list; 5 | pub mod unlist; 6 | pub mod update_listing; 7 | 8 | pub use admin::*; 9 | pub use buy_listing::*; 10 | pub use escrow_ix::*; 11 | pub use list::*; 12 | pub use unlist::*; 13 | pub use update_listing::*; 14 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/unlist.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use mpl_core::instructions::TransferV1CpiBuilder; 3 | 4 | use crate::{constants::SEED_PREFIX, helpers::Core, AssetManager, ListingData, SEED_ASSET_MANAGER}; 5 | 6 | /// Un-list an MPL core asset on soundwork 7 | /// 8 | /// ### Accounts 9 | /// 10 | /// 1. `[writable, signer]` payer 11 | /// 2. `[writable]` asset 12 | /// 3. `[writable]` collection 13 | /// 4. `[writable]` listing data account 14 | /// 5. `[]` asset manager 15 | /// 6. `[]` core program 16 | /// 7. `[]` system program 17 | /// 18 | #[derive(Accounts)] 19 | pub struct UnListAsset<'info> { 20 | #[account(mut, address = listing_data.authority)] 21 | pub payer: Signer<'info>, 22 | 23 | /// CHECK: checked by us 24 | #[account(mut)] 25 | pub asset: AccountInfo<'info>, 26 | 27 | /// CHECK: checked by us 28 | #[account(mut)] 29 | pub collection: Option>, 30 | 31 | #[account(mut, close = payer)] 32 | pub listing_data: Account<'info, ListingData>, 33 | 34 | pub asset_manager: Account<'info, AssetManager>, 35 | 36 | pub core_program: Program<'info, Core>, 37 | 38 | pub system_program: Program<'info, System>, 39 | } 40 | 41 | impl UnListAsset<'_> { 42 | /// validation helper for our IX 43 | pub fn validate(&self, ctx: &Context) -> Result<()> { 44 | let asset_manager = Pubkey::create_program_address( 45 | &[ 46 | SEED_PREFIX, 47 | SEED_ASSET_MANAGER, 48 | &[ctx.accounts.asset_manager.bump], 49 | ], 50 | &ctx.program_id, 51 | ) 52 | .unwrap(); 53 | 54 | assert_eq!(asset_manager, ctx.accounts.asset_manager.key()); 55 | 56 | return Ok(()); 57 | } 58 | 59 | /// un-list MPL core asset on the marketplace 60 | /// 61 | #[access_control(ctx.accounts.validate(&ctx))] 62 | pub fn unlist(ctx: Context) -> Result<()> { 63 | let asset_manager = &ctx.accounts.asset_manager; 64 | // asset manager signer seeds 65 | let bump = &[asset_manager.bump]; 66 | let signer_seeds = &[&[SEED_PREFIX, SEED_ASSET_MANAGER, bump][..]]; 67 | 68 | // transfer asset back to owner 69 | TransferV1CpiBuilder::new(&ctx.accounts.core_program) 70 | .asset(&ctx.accounts.asset) 71 | .collection(ctx.accounts.collection.as_ref()) 72 | .payer(&ctx.accounts.payer) 73 | .authority(Some(&asset_manager.to_account_info())) 74 | .new_owner(&ctx.accounts.payer.to_account_info()) 75 | .system_program(Some(&ctx.accounts.system_program)) 76 | .invoke_signed(signer_seeds)?; 77 | 78 | Ok(()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/instructions/update_listing.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::{helpers::Core, AssetManager, ListingData}; 4 | 5 | #[derive(AnchorSerialize, AnchorDeserialize)] 6 | pub struct UpdateListingParams { 7 | /// edit listing amount/price in lamports 8 | pub amount: u64, 9 | } 10 | 11 | /// update a listed MPL core asset 12 | /// 13 | /// ### Accounts: 14 | /// 15 | /// 1. `[writeable, signer]` payer 16 | /// 2. `[writeable]` asset 17 | /// 3. `[writeable]` listing data account 18 | /// 4. `[]` asset manager 19 | /// 5. `[]` core program 20 | /// 6. `[]` system program 21 | /// 22 | /// ### Params 23 | /// 24 | /// 1. params: [UpdateListingParams] 25 | /// 26 | #[derive(Accounts)] 27 | #[instruction(params: UpdateListingParams)] 28 | pub struct UpdateListing<'info> { 29 | #[account(mut)] 30 | pub payer: Signer<'info>, 31 | 32 | /// CHECK: checked by us 33 | #[account(mut)] 34 | pub asset: AccountInfo<'info>, 35 | 36 | /// CHECK: checked by us 37 | #[account(mut)] 38 | pub listing_data: Account<'info, ListingData>, 39 | 40 | pub asset_manager: Account<'info, AssetManager>, 41 | 42 | pub core_program: Program<'info, Core>, 43 | 44 | pub system_program: Program<'info, System>, 45 | } 46 | 47 | impl UpdateListing<'_> { 48 | /// validation helper for our IX 49 | pub fn validate(&self) -> Result<()> { 50 | return Ok(()); 51 | } 52 | 53 | /// list MPL core asset on the marketplace 54 | /// 55 | #[access_control(ctx.accounts.validate())] 56 | pub fn update_listing(ctx: Context, params: UpdateListingParams) -> Result<()> { 57 | let listing_data = &mut ctx.accounts.listing_data; 58 | 59 | listing_data.amount = ListingData::update_amount(params.amount); 60 | 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod error; 3 | pub mod helpers; 4 | pub mod instructions; 5 | pub mod state; 6 | 7 | use anchor_lang::prelude::*; 8 | 9 | #[allow(unused_imports)] 10 | use solana_security_txt::security_txt; 11 | 12 | pub use constants::*; 13 | pub use instructions::*; 14 | pub use state::*; 15 | 16 | declare_id!("EA4ptgF3TYjDBGYJApAoZoyCbCYw6P5mGU5noCe1Z97"); 17 | 18 | #[cfg(not(feature = "no-entrypoint"))] 19 | security_txt! { 20 | name: "Soundwork List Program", 21 | project_url: "https://soundwork.io", 22 | contacts: "email:info@soundwork.io, twitter:@SoundworkSounds", 23 | policy: "https://github.com/SoundWorkLabs/marketplace-contracts/blob/master/SECURITY.md", 24 | preferred_languages: "en", 25 | source_code: "https://github.com/SoundWorkLabs/marketplace-contracts" 26 | } 27 | 28 | /// SOUNDWORK LIST 29 | /// 30 | #[program] 31 | pub mod soundwork_list { 32 | 33 | use super::*; 34 | 35 | /// Initialize asset manager escrow account. 36 | /// 37 | /// Note: Only admin address can call this function 38 | /// 39 | pub fn init_escrow_account(ctx: Context) -> Result<()> { 40 | InitEscrow::init_escrow(ctx) 41 | } 42 | 43 | /// Initialize marketplace config account. 44 | /// 45 | /// Note: Only admin address can call this function 46 | /// 47 | pub fn init_marketplace_config_account( 48 | ctx: Context, 49 | params: InitMarketPlaceConfigParams, 50 | ) -> Result<()> { 51 | InitMarketplaceConfig::init_marketplace_config(ctx, params) 52 | } 53 | 54 | /// Initialize user escrow wallet. 55 | /// 56 | pub fn init_user_escrow_wallet(ctx: Context) -> Result<()> { 57 | InitWallet::init_wallet(ctx) 58 | } 59 | 60 | /// Deposit SOL into the user escrow wallet. 61 | /// 62 | pub fn deposit_sol(ctx: Context, params: DepositSolParams) -> Result<()> { 63 | DepositSol::deposit_sol(ctx, params) 64 | } 65 | 66 | /// Withdraw SOL into the user's escrow wallet. 67 | /// 68 | pub fn withdraw_sol( 69 | ctx: Context, 70 | params: Option, 71 | ) -> Result<()> { 72 | WithdrawSol::withdraw_sol(ctx, params) 73 | } 74 | 75 | /// Deposit SOL into the user escrow wallet. 76 | /// 77 | pub fn deposit_token(ctx: Context, params: DepositTokenParams) -> Result<()> { 78 | DepositToken::deposit_token(ctx, params) 79 | } 80 | 81 | /// Withdraw tokens from the user escrow wallet. 82 | /// 83 | pub fn withdraw_token(ctx: Context, params: WithdrawTokenParams) -> Result<()> { 84 | WithdrawToken::withdraw_token(ctx, params) 85 | } 86 | 87 | /// List an MPL Core asset on Soundwork 88 | /// 89 | pub fn list_asset(ctx: Context, params: ListAssetParams) -> Result<()> { 90 | ListAsset::list_asset(ctx, params) 91 | } 92 | 93 | /// Remove MPL Core asset listed on our marketplace 94 | /// 95 | pub fn update_listing_amount( 96 | ctx: Context, 97 | params: UpdateListingParams, 98 | ) -> Result<()> { 99 | UpdateListing::update_listing(ctx, params) 100 | } 101 | 102 | /// Remove MPL Core asset listed on our marketplace 103 | /// 104 | pub fn unlist_asset(ctx: Context) -> Result<()> { 105 | UnListAsset::unlist(ctx) 106 | } 107 | 108 | /// Buy MPL Core asset listed on our marketplace 109 | /// 110 | pub fn buy_asset(ctx: Context, params: Option) -> Result<()> { 111 | BuyAsset::buy_asset(ctx, params) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/state/escrow.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct AssetManager { 5 | /// PDA bump 6 | pub bump: u8, 7 | 8 | /// Unused reserved byte space for additive future changes. 9 | pub _reserved: [u8; 128], 10 | } 11 | 12 | impl AssetManager { 13 | pub const LEN: usize = 14 | 8 + // anchor account discriminator 15 | 1 + // PDA bump 16 | 130 // reserved space 17 | ; 18 | } -------------------------------------------------------------------------------- /programs/soundwork-list/src/state/listing.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// When listed, how does the user want to receive funds 4 | /// 5 | #[derive(AnchorSerialize, AnchorDeserialize, Default, Clone)] 6 | pub enum PaymentOption { 7 | #[default] 8 | Native, 9 | Token { 10 | mint: Pubkey, 11 | }, 12 | // todo(both) a combination of tokens and sol 13 | } 14 | 15 | #[account] 16 | pub struct ListingData { 17 | /// PDA bump 18 | pub bump: u8, 19 | 20 | /// amount in lamports asset is being listed for 21 | pub amount: u64, 22 | 23 | /// asset owner 24 | pub authority: Pubkey, 25 | 26 | /// asset address 27 | pub asset_address: Pubkey, 28 | 29 | /// type of way user wants to get paid when listing is bought / bid made for asset 30 | pub payment_option: PaymentOption, 31 | 32 | /// Unused reserved byte space for additive future changes. 33 | pub _reserved: [u8; 128], 34 | } 35 | 36 | impl ListingData { 37 | pub const LEN: usize = 8 // anchor discriminator 38 | + 1 // bump 39 | + 8 // amount 40 | + 32 // authority address 41 | + 32 // asset address 42 | + 33 // payment option 43 | + 128; // reserved 44 | 45 | /// instantiate the listing data account with provided args 46 | pub fn new( 47 | bump: u8, 48 | amount: u64, 49 | authority: Pubkey, 50 | asset_address: Pubkey, 51 | payment_option: PaymentOption, 52 | ) -> Self { 53 | Self { 54 | bump, 55 | amount, 56 | authority, 57 | asset_address, 58 | payment_option, 59 | _reserved: [0; 128], 60 | } 61 | } 62 | 63 | // update listing data account 64 | pub fn update_amount(amount: u64) -> u64 { 65 | amount 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod escrow; 2 | pub mod listing; 3 | pub mod protocol; 4 | pub mod wallet; 5 | 6 | pub use escrow::*; 7 | pub use listing::*; 8 | pub use protocol::*; 9 | pub use wallet::*; 10 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/state/protocol.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct MarketPlaceConfig { 5 | /// PDA bump 6 | pub bump: u8, 7 | 8 | /// Taker fee percentage 9 | pub treasury_address: Pubkey, 10 | 11 | /// Taker fee basis points 12 | pub taker_fee_bps: u8, 13 | 14 | /// Unused reserved byte space for additive future changes. 15 | pub _reserved: [u8; 128], 16 | } 17 | 18 | impl MarketPlaceConfig { 19 | pub const LEN: usize = 8 + // anchor account discriminator 20 | 1 + // PDA bump 21 | 32 + // treasury address 22 | 1 + // taker fee percentage 23 | 130; // reserved space 24 | 25 | pub fn new(bump: u8, treasury_address: Pubkey, taker_fee_bps: u8) -> Self { 26 | Self { 27 | bump, 28 | treasury_address, 29 | taker_fee_bps, 30 | _reserved: [0; 128], 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /programs/soundwork-list/src/state/wallet.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[account] 4 | pub struct Wallet { 5 | pub authority: Pubkey, 6 | pub bump: u8, 7 | pub _reserved: [u8; 128], 8 | } 9 | 10 | impl Wallet { 11 | pub const LEN: usize = 8 // discriminator 12 | + 32 // owner address 13 | + 1 // wallet bump 14 | + 130; // reserved space 15 | 16 | // todo: (Jimii) should size be changed to accommodate paying with tokens" 17 | pub fn new(authority: &Pubkey, bump: u8) -> Self { 18 | Self { 19 | authority: authority.to_owned(), 20 | bump, 21 | _reserved: [0; 128], 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/unit/soundwork-bid.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, setProvider } from "@coral-xyz/anchor"; 2 | 3 | import { 4 | ASSOCIATED_TOKEN_PROGRAM_ID, 5 | getAssociatedTokenAddressSync, 6 | TOKEN_PROGRAM_ID, 7 | } from "@solana/spl-token"; 8 | import { 9 | Keypair, 10 | LAMPORTS_PER_SOL, 11 | sendAndConfirmTransaction, 12 | SystemProgram, 13 | Transaction, 14 | } from "@solana/web3.js"; 15 | import { BN } from "bn.js"; 16 | import { 17 | asset, 18 | CORE_PROGRAM_ID, 19 | PAYMENT_MINT, 20 | SOUNDWORK_LIST_ID, 21 | } from "../utils/constants"; 22 | import { KeyPairFile, loadKeypair } from "../utils/helpers"; 23 | import { 24 | findAssetManagerAddress, 25 | findBidDataAddress, 26 | findListingDataAddress, 27 | findMarketplaceConfigAddress, 28 | findWalletAddress, 29 | } from "../utils/pda"; 30 | import { BidProgram, ListProgram } from "../utils/programs"; 31 | 32 | describe("BID PROGRAM", () => { 33 | // get the signer keypairs 34 | let signer = loadKeypair(KeyPairFile.main); // used as treasury 35 | 36 | let seller = loadKeypair(KeyPairFile.main); 37 | let bidder = loadKeypair(KeyPairFile.secondary); 38 | 39 | // instantiate List Program, 40 | const listProgram = new ListProgram().getProgram(); 41 | // const program = listProgram.getProgram(); 42 | 43 | // instantiate BID Program, 44 | const bidProgram = new BidProgram(); 45 | const program = bidProgram.getProgram(); 46 | 47 | // --------------------------------------------------------------------------BID IXs 48 | 49 | // wants to be paid using tokens 50 | // it("Makes a bid on a listed asset!", async () => { 51 | // let expiryTs = new BN(new Date().getTime()); 52 | 53 | // /* const initWalletIX = await listProgram.methods 54 | // .initUserEscrowWallet() 55 | // .accountsStrict({ 56 | // authority: bidder.publicKey, 57 | // wallet: findWalletAddress(bidder.publicKey), 58 | // systemProgram: SystemProgram.programId, 59 | // }) 60 | // .instruction(); 61 | // */ 62 | // const bidIX = await program.methods 63 | // .makeBid({ 64 | // amount: new BN(1 * LAMPORTS_PER_SOL), 65 | // expiryTs, 66 | // }) 67 | // .accountsStrict({ 68 | // bidder: bidder.publicKey, 69 | // asset, 70 | // bidData: findBidDataAddress(asset), 71 | // bidderEscrowWallet: findWalletAddress(bidder.publicKey), 72 | // listingData: findListingDataAddress(asset), 73 | // paymentMint: PAYMENT_MINT, 74 | // bidderTokenAccount: getAssociatedTokenAddressSync( 75 | // PAYMENT_MINT, 76 | // bidder.publicKey 77 | // ), 78 | // // in sdk check that this is initialized and if not, call initialize wallet 79 | // // initializeBidderWalletAndBid() 80 | // walletTokenAccount: getAssociatedTokenAddressSync( 81 | // PAYMENT_MINT, 82 | // findWalletAddress(bidder.publicKey), 83 | // true 84 | // ), 85 | // soundworkList: SOUNDWORK_LIST_ID, 86 | // tokenProgram: TOKEN_PROGRAM_ID, 87 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 88 | // systemProgram: SystemProgram.programId, 89 | // }) 90 | // .instruction(); 91 | 92 | // const tx = new Transaction() /* .add(initWalletIX) */ 93 | // .add(bidIX); 94 | 95 | // let txHash = await sendAndConfirmTransaction( 96 | // program.provider.connection, 97 | // tx, 98 | // [bidder], 99 | // { 100 | // skipPreflight: true, 101 | // } 102 | // ); 103 | 104 | // console.log( 105 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 106 | // ); 107 | // }); 108 | 109 | // it("Edits a bid for a listed asset!", async () => { 110 | // let ix = await program.methods 111 | // .editBid({ amount: new BN(1000), expiryTs: null }) 112 | // .accountsStrict({ 113 | // bidder: bidder.publicKey, 114 | // asset, 115 | // bidData: findBidDataAddress(asset), 116 | // bidderEscrowWallet: findWalletAddress(bidder.publicKey), 117 | // listingData: findListingDataAddress(asset), 118 | // paymentMint: PAYMENT_MINT, 119 | // bidderTokenAccount: getAssociatedTokenAddressSync( 120 | // PAYMENT_MINT, 121 | // bidder.publicKey 122 | // ), 123 | // walletTokenAccount: getAssociatedTokenAddressSync( 124 | // PAYMENT_MINT, 125 | // findWalletAddress(bidder.publicKey), 126 | // true 127 | // ), 128 | // soundworkList: SOUNDWORK_LIST_ID, 129 | // tokenProgram: TOKEN_PROGRAM_ID, 130 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 131 | // systemProgram: SystemProgram.programId, 132 | // }) 133 | // .instruction(); 134 | 135 | // const tx = new Transaction().add(ix); 136 | 137 | // let txHash = await sendAndConfirmTransaction( 138 | // program.provider.connection, 139 | // tx, 140 | // [bidder], 141 | // { 142 | // skipPreflight: true, 143 | // } 144 | // ); 145 | 146 | // console.log( 147 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 148 | // ); 149 | // }); 150 | 151 | // it("Revoked bid on a listed asset!", async () => { 152 | // let ix = await program.methods 153 | // .revokeBid() 154 | // .accountsStrict({ 155 | // bidder: bidder.publicKey, 156 | // asset, 157 | // bidData: findBidDataAddress(asset), 158 | // bidderEscrowWallet: findWalletAddress(bidder.publicKey), 159 | // listingData: findListingDataAddress(asset), 160 | // paymentMint: PAYMENT_MINT, 161 | // bidderTokenAccount: getAssociatedTokenAddressSync( 162 | // PAYMENT_MINT, 163 | // bidder.publicKey 164 | // ), 165 | // walletTokenAccount: getAssociatedTokenAddressSync( 166 | // PAYMENT_MINT, 167 | // findWalletAddress(bidder.publicKey), 168 | // true 169 | // ), 170 | // soundworkList: SOUNDWORK_LIST_ID, 171 | // tokenProgram: TOKEN_PROGRAM_ID, 172 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 173 | // systemProgram: SystemProgram.programId, 174 | // }) 175 | // .instruction(); 176 | 177 | // const tx = new Transaction().add(ix); 178 | 179 | // let txHash = await sendAndConfirmTransaction( 180 | // program.provider.connection, 181 | // tx, 182 | // [bidder], 183 | // { 184 | // skipPreflight: true, 185 | // } 186 | // ); 187 | 188 | // console.log( 189 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 190 | // ); 191 | // }); 192 | 193 | // it("Accepts a bid for a listed asset!", async () => { 194 | // let txHash = await program.methods 195 | // .acceptBid() 196 | // .accountsStrict({ 197 | // seller: seller.publicKey, 198 | // bidder: bidder.publicKey, 199 | // asset, 200 | // bidData: findBidDataAddress(asset), 201 | // bidderEscrowWallet: findWalletAddress(bidder.publicKey), 202 | // listingData: findListingDataAddress(asset), 203 | // paymentMint: PAYMENT_MINT, 204 | // bidderTokenAccount: getAssociatedTokenAddressSync( 205 | // PAYMENT_MINT, 206 | // bidder.publicKey 207 | // ), 208 | // sellerTokenAccount: getAssociatedTokenAddressSync( 209 | // PAYMENT_MINT, 210 | // seller.publicKey 211 | // ), 212 | // walletTokenAccount: getAssociatedTokenAddressSync( 213 | // PAYMENT_MINT, 214 | // findWalletAddress(bidder.publicKey), 215 | // true 216 | // ), 217 | // treasuryTokenAccount: getAssociatedTokenAddressSync( 218 | // PAYMENT_MINT, 219 | // signer.publicKey 220 | // ), 221 | // treasury: signer.publicKey, 222 | // assetManager: findAssetManagerAddress(), 223 | // marketplaceConfig: findMarketplaceConfigAddress(), 224 | // soundworkList: SOUNDWORK_LIST_ID, 225 | // coreProgram: CORE_PROGRAM_ID, 226 | // tokenProgram: TOKEN_PROGRAM_ID, 227 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 228 | // systemProgram: SystemProgram.programId, 229 | // }) 230 | // .rpc({ skipPreflight: true }); 231 | 232 | // console.log( 233 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 234 | // ); 235 | // }); 236 | 237 | // it("Seller rejects bid on listed asset!", async () => { 238 | // let ix = await program.methods 239 | // .rejectBid() 240 | // .accountsStrict({ 241 | // seller: seller.publicKey, 242 | // bidder: bidder.publicKey, 243 | // asset, 244 | // bidData: findBidDataAddress(asset), 245 | // bidderEscrowWallet: findWalletAddress(bidder.publicKey), 246 | // listingData: findListingDataAddress(asset), 247 | // paymentMint: PAYMENT_MINT, 248 | // bidderTokenAccount: getAssociatedTokenAddressSync( 249 | // PAYMENT_MINT, 250 | // bidder.publicKey 251 | // ), 252 | // walletTokenAccount: getAssociatedTokenAddressSync( 253 | // PAYMENT_MINT, 254 | // findWalletAddress(bidder.publicKey), 255 | // true 256 | // ), 257 | // soundworkList: SOUNDWORK_LIST_ID, 258 | // tokenProgram: TOKEN_PROGRAM_ID, 259 | // associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 260 | // systemProgram: SystemProgram.programId, 261 | // }) 262 | // .rpc(); 263 | 264 | // console.log( 265 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 266 | // ); 267 | // }); 268 | }); 269 | -------------------------------------------------------------------------------- /tests/unit/soundwork-create.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, SystemProgram } from "@solana/web3.js"; 2 | import { CORE_PROGRAM_ID } from "../utils/constants"; 3 | import { KeyPairFile, loadKeypair } from "../utils/helpers"; 4 | import { CreateProgram } from "../utils/programs"; 5 | 6 | describe("CREATE PROGRAM", () => { 7 | // get the signer keypair 8 | let signer = loadKeypair(KeyPairFile.main); 9 | 10 | // instantiate CREATE Program, using default provider 11 | const createProgram = new CreateProgram(); 12 | const program = createProgram.getProgram(); 13 | 14 | it("Is Mints a Core Asset!", async () => { 15 | const asset = Keypair.generate(); 16 | 17 | const metadata = { 18 | name: "Kobeni Supremacy", 19 | uri: "https://raw.githubusercontent.com/687c/solana-nft-native-client/main/metadata.json", 20 | }; 21 | 22 | const txHash = await program.methods 23 | .create(metadata.name, metadata.uri) 24 | .accountsStrict({ 25 | payer: signer.publicKey, 26 | systemProgram: SystemProgram.programId, 27 | asset: asset.publicKey, 28 | coreProgram: CORE_PROGRAM_ID, 29 | }) 30 | .signers([asset]) 31 | .rpc(); 32 | 33 | console.log( 34 | `mint Address: https://explorer.solana.com/address/${asset.publicKey}?cluster=devnet\n` 35 | ); 36 | console.log( 37 | `mint tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet` 38 | ); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/unit/soundwork-list.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, setProvider } from "@coral-xyz/anchor"; 2 | 3 | import { ASSOCIATED_PROGRAM_ID } from "@coral-xyz/anchor/dist/cjs/utils/token"; 4 | import { 5 | getAssociatedTokenAddressSync, 6 | TOKEN_PROGRAM_ID, 7 | } from "@solana/spl-token"; 8 | import { 9 | Keypair, 10 | LAMPORTS_PER_SOL, 11 | PublicKey, 12 | SystemProgram, 13 | } from "@solana/web3.js"; 14 | import { BN } from "bn.js"; 15 | import { asset, CORE_PROGRAM_ID, PAYMENT_MINT } from "../utils/constants"; 16 | import { KeyPairFile, loadKeypair } from "../utils/helpers"; 17 | import { 18 | findAssetManagerAddress, 19 | findListingDataAddress, 20 | findMarketplaceConfigAddress, 21 | findWalletAddress, 22 | } from "../utils/pda"; 23 | import { ListProgram } from "../utils/programs"; 24 | 25 | describe("LIST PROGRAM", () => { 26 | // get the signer keypair 27 | let signer = loadKeypair(KeyPairFile.main); 28 | 29 | let seller = loadKeypair(KeyPairFile.main); 30 | let buyer = loadKeypair(KeyPairFile.secondary); 31 | 32 | // instantiate LIST Program, using default provider 33 | const listProgram = new ListProgram(); 34 | const program = listProgram.getProgram(); 35 | 36 | // ! get rid of me below 37 | // let asset = new PublicKey("H4gutS7fRDgb4c4sDhULvQ23PaN81d5qgQpkaapC7N8t"); 38 | 39 | // --------------------------------------------------------------------------ADMIN IXs 40 | 41 | it("Initializes asset manager escrow account!", async () => { 42 | // const txHash = await program.methods 43 | // .initEscrowAccount() 44 | // .accountsStrict({ 45 | // payer: signer.publicKey, 46 | // systemProgram: SystemProgram.programId, 47 | // assetManager: findAssetManagerAddress(), 48 | // coreProgram: CORE_PROGRAM_ID, 49 | // }) 50 | // .rpc({skipPreflight: true}); 51 | // console.log( 52 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 53 | // ); 54 | }); 55 | 56 | // it("Initializes Marketplace config account!", async () => { 57 | // const txHash = await program.methods 58 | // .initMarketplaceConfigAccount({ 59 | // takerFeeBps: 1, 60 | // treasuryAddress: signer.publicKey, // todo: update me 61 | // }) 62 | // .accountsStrict({ 63 | // payer: signer.publicKey, 64 | // marketplaceConfig: findMarketplaceConfigAddress(), 65 | // systemProgram: SystemProgram.programId, 66 | // }) 67 | // .rpc(); 68 | 69 | // console.log( 70 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 71 | // ); 72 | // }); 73 | 74 | // --------------------------------------------------------------------------USER IXs 75 | 76 | // it("Initializes buyer wallet escrow account!", async () => { 77 | // const txHash = await program.methods 78 | // .initUserEscrowWallet() 79 | // .accountsStrict({ 80 | // authority: buyer.publicKey, 81 | // wallet: findWalletAddress(buyer.publicKey), 82 | // systemProgram: SystemProgram.programId, 83 | // }) 84 | // .signers([buyer]) 85 | // .rpc(); 86 | 87 | // console.log( 88 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 89 | // ); 90 | // }); 91 | 92 | // it("Deposits SOL to buyer escrow wallet!", async () => { 93 | // const txHash = await program.methods 94 | // .depositSol({ amount: new BN(1 * LAMPORTS_PER_SOL) }) 95 | // .accountsStrict({ 96 | // authority: buyer.publicKey, 97 | // wallet: findWalletAddress(buyer.publicKey), 98 | // systemProgram: SystemProgram.programId, 99 | // }) 100 | // .signers([buyer]) 101 | // .rpc(); 102 | 103 | // console.log( 104 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 105 | // ); 106 | // }); 107 | 108 | // it("Withdraws SOL from buyer escrow wallet!", async () => { 109 | // const txHash = await program.methods 110 | // .withdrawSol({ amount: new BN(0.01 * LAMPORTS_PER_SOL) }) 111 | // .accountsStrict({ 112 | // payer: buyer.publicKey, 113 | // authority: buyer.publicKey, 114 | // wallet: findWalletAddress(buyer.publicKey), 115 | // systemProgram: SystemProgram.programId, 116 | // }) 117 | // .signers([buyer]) 118 | // .rpc(); 119 | 120 | // console.log( 121 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 122 | // ); 123 | // }); 124 | 125 | // it("Deposits Tokens to buyer escrow wallet!", async () => { 126 | // const txHash = await program.methods 127 | // .depositToken({ amount: new BN(1_000_000) }) // with 6 decimals, this is 1 USDC dev coin 128 | // .accountsStrict({ 129 | // authority: buyer.publicKey, 130 | // wallet: findWalletAddress(buyer.publicKey), 131 | // mint: PAYMENT_MINT, 132 | // authorityTokenAccount: getAssociatedTokenAddressSync( 133 | // PAYMENT_MINT, 134 | // buyer.publicKey 135 | // ), 136 | // walletTokenAccount: getAssociatedTokenAddressSync( 137 | // PAYMENT_MINT, 138 | // findWalletAddress(buyer.publicKey), 139 | // true 140 | // ), 141 | // tokenProgram: TOKEN_PROGRAM_ID, 142 | // associatedTokenProgram: ASSOCIATED_PROGRAM_ID, 143 | // systemProgram: SystemProgram.programId, 144 | // }) 145 | // .signers([buyer]) 146 | // .rpc({ skipPreflight: true }); 147 | 148 | // console.log( 149 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 150 | // ); 151 | // }); 152 | 153 | // it("Withdraws Tokens from buyer escrow wallet", async () => { 154 | // const txHash = await program.methods 155 | // .withdrawToken({ amount: new BN(1) }) 156 | // .accountsStrict({ 157 | // payer: buyer.publicKey, 158 | // authority: buyer.publicKey, 159 | // wallet: findWalletAddress(buyer.publicKey), 160 | // mint: PAYMENT_MINT, 161 | // authorityTokenAccount: getAssociatedTokenAddressSync( 162 | // PAYMENT_MINT, 163 | // buyer.publicKey 164 | // ), 165 | // walletTokenAccount: getAssociatedTokenAddressSync( 166 | // PAYMENT_MINT, 167 | // findWalletAddress(buyer.publicKey), 168 | // true 169 | // ), 170 | // tokenProgram: TOKEN_PROGRAM_ID, 171 | // associatedTokenProgram: ASSOCIATED_PROGRAM_ID, 172 | // systemProgram: SystemProgram.programId, 173 | // }) 174 | // .signers([buyer]) 175 | // .rpc({ skipPreflight: true }); 176 | 177 | // console.log( 178 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 179 | // ); 180 | // }); 181 | 182 | // --------------------------------------------------------------------------LIST IXs 183 | 184 | // it("Lists an MPL core asset with native payment option!", async () => { 185 | // const txHash = await program.methods 186 | // .listAsset({ 187 | // amount: new BN(1_0000_000), 188 | // paymentOption: { native: {} }, 189 | // }) 190 | // .accountsStrict({ 191 | // payer: signer.publicKey, 192 | // asset, 193 | // collection: null, 194 | // listingData: findListingDataAddress(asset), 195 | // assetManager: findAssetManagerAddress(), 196 | // coreProgram: CORE_PROGRAM_ID, 197 | // systemProgram: SystemProgram.programId, 198 | // }) 199 | // .rpc({ skipPreflight: true }); 200 | 201 | // console.log( 202 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 203 | // ); 204 | // }); 205 | 206 | // it("Lists an MPL core asset and uses tokens as payment option!", async () => { 207 | // const txHash = await program.methods 208 | // .listAsset({ 209 | // amount: new BN(1_000_000), // 1 USDC dev 210 | // paymentOption: { token: { mint: PAYMENT_MINT } }, 211 | // }) 212 | // .accountsStrict({ 213 | // payer: signer.publicKey, 214 | // asset, 215 | // collection: null, 216 | // listingData: findListingDataAddress(asset), 217 | // assetManager: findAssetManagerAddress(), 218 | // coreProgram: CORE_PROGRAM_ID, 219 | // systemProgram: SystemProgram.programId, 220 | // }) 221 | // .rpc({ skipPreflight: true }); 222 | 223 | // console.log( 224 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 225 | // ); 226 | // }); 227 | 228 | // it("UnLists an MPL core asset!", async () => { 229 | // const txHash = await program.methods 230 | // .unlistAsset() 231 | // .accountsStrict({ 232 | // payer: signer.publicKey, 233 | // asset, 234 | // collection: null, 235 | // listingData: findListingDataAddress(asset), 236 | // assetManager: findAssetManagerAddress(), 237 | // coreProgram: CORE_PROGRAM_ID, 238 | // systemProgram: SystemProgram.programId, 239 | // }) 240 | // .rpc({ skipPreflight: true }); 241 | 242 | // console.log( 243 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 244 | // ); 245 | // }); 246 | 247 | // it("Buys a listed NFT using Native SOL!", async () => { 248 | // const txHash = await program.methods 249 | // .buyAsset(null) // ! fails as expected 250 | // .accountsStrict({ 251 | // payer: buyer.publicKey, 252 | // buyer: buyer.publicKey, 253 | // seller: seller.publicKey, 254 | // walletAsBuyer: null, 255 | // asset, 256 | // collection: null, 257 | // paymentMint: null, 258 | // walletTokenAccount: null, 259 | // buyerTokenAccount: null, 260 | // sellerTokenAccount: null, 261 | // treasuryTokenAccount: null, // ! update to correct address 262 | // treasury: signer.publicKey, // ! update to correct address 263 | // listingData: findListingDataAddress(asset), 264 | // assetManager: findAssetManagerAddress(), 265 | // marketplaceConfig: findMarketplaceConfigAddress(), 266 | // coreProgram: CORE_PROGRAM_ID, 267 | // tokenProgram: TOKEN_PROGRAM_ID, 268 | // associatedTokenProgram: ASSOCIATED_PROGRAM_ID, 269 | // systemProgram: SystemProgram.programId, 270 | // }) 271 | // .signers([buyer]) 272 | // .rpc({ skipPreflight: true }); 273 | 274 | // console.log( 275 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 276 | // ); 277 | // }); 278 | 279 | // it("Buys a listed NFT using Tokens using Escrow Wallet!", async () => { 280 | // const txHash = await program.methods 281 | // .buyAsset(null) // ! fails as expected 282 | // // .buyAsset({ bidAmount: new BN(1_000_000) }) // ! works, but this is for seller when accepting bids 283 | // .accountsStrict({ 284 | // payer: buyer.publicKey, 285 | // buyer: buyer.publicKey, 286 | // seller: seller.publicKey, 287 | // walletAsBuyer: findWalletAddress(buyer.publicKey), 288 | // asset, 289 | // collection: null, 290 | // paymentMint: PAYMENT_MINT, 291 | // walletTokenAccount: getAssociatedTokenAddressSync( 292 | // PAYMENT_MINT, 293 | // findWalletAddress(buyer.publicKey), 294 | // true 295 | // ), 296 | // buyerTokenAccount: getAssociatedTokenAddressSync( 297 | // PAYMENT_MINT, 298 | // buyer.publicKey 299 | // ), 300 | // sellerTokenAccount: getAssociatedTokenAddressSync( 301 | // PAYMENT_MINT, 302 | // seller.publicKey 303 | // ), 304 | // treasuryTokenAccount: getAssociatedTokenAddressSync( 305 | // PAYMENT_MINT, 306 | // signer.publicKey 307 | // ), // ! update to correct address 308 | // treasury: signer.publicKey, // ! update to correct address 309 | // listingData: findListingDataAddress(asset), 310 | // assetManager: findAssetManagerAddress(), 311 | // marketplaceConfig: findMarketplaceConfigAddress(), 312 | // coreProgram: CORE_PROGRAM_ID, 313 | // tokenProgram: TOKEN_PROGRAM_ID, 314 | // associatedTokenProgram: ASSOCIATED_PROGRAM_ID, 315 | // systemProgram: SystemProgram.programId, 316 | // }) 317 | // .signers([buyer]) 318 | // .rpc({ skipPreflight: true }); 319 | 320 | // console.log( 321 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 322 | // ); 323 | // }); 324 | 325 | // it("Buys a listed NFT using Tokens!", async () => { 326 | // const txHash = await program.methods 327 | // .buyAsset(null) // ! fails as expected 328 | // .accountsStrict({ 329 | // payer: buyer.publicKey, 330 | // buyer: buyer.publicKey, 331 | // seller: seller.publicKey, 332 | // walletAsBuyer: null, 333 | 334 | // asset, 335 | // paymentMint: PAYMENT_MINT, 336 | // walletTokenAccount: null, 337 | // buyerTokenAccount: getAssociatedTokenAddressSync( 338 | // PAYMENT_MINT, 339 | // buyer.publicKey 340 | // ), 341 | // sellerTokenAccount: getAssociatedTokenAddressSync( 342 | // PAYMENT_MINT, 343 | // seller.publicKey 344 | // ), 345 | // treasuryTokenAccount: getAssociatedTokenAddressSync( 346 | // PAYMENT_MINT, 347 | // signer.publicKey 348 | // ), // ! update to correct address 349 | // treasury: signer.publicKey, // ! update to correct address 350 | // listingData: findListingDataAddress(asset), 351 | // assetManager: findAssetManagerAddress(), 352 | // marketplaceConfig: findMarketplaceConfigAddress(), 353 | // coreProgram: CORE_PROGRAM_ID, 354 | // tokenProgram: TOKEN_PROGRAM_ID, 355 | // associatedTokenProgram: ASSOCIATED_PROGRAM_ID, 356 | // systemProgram: SystemProgram.programId, 357 | // }) 358 | // .signers([buyer]) 359 | // .rpc({ skipPreflight: true }); 360 | 361 | // console.log( 362 | // `tx: https://explorer.solana.com/tx/${txHash}?cluster=devnet\n` 363 | // ); 364 | // }); 365 | }); 366 | -------------------------------------------------------------------------------- /tests/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, Provider } from "@coral-xyz/anchor"; 2 | import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js"; 3 | import dotenv from "dotenv"; 4 | 5 | dotenv.config(); 6 | 7 | // const connection = new Connection( 8 | // `https://devnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}` 9 | // ); 10 | 11 | // eclipse endpoint 12 | const connection = new Connection(clusterApiUrl("devnet")); 13 | 14 | // default provider provided by Anchor.toml 15 | export const defaultProvider: Provider = new AnchorProvider( 16 | connection, 17 | AnchorProvider.env().wallet, 18 | AnchorProvider.defaultOptions() 19 | ); 20 | 21 | // ------------------------------------------- programs 22 | 23 | export const SOUNDWORK_BID_ID = new PublicKey( 24 | "4mFDYND4AVREYEJXCPhjq1LnbjELHHebJqG3NZechA7X" 25 | ); 26 | export const SOUNDWORK_CREATE_ID = new PublicKey( 27 | "4iraDthfMHkgrvWsLz4mfCyHJY4JKc31TTxGMZKrc6r8" 28 | ); 29 | export const SOUNDWORK_LIST_ID = new PublicKey( 30 | "EA4ptgF3TYjDBGYJApAoZoyCbCYw6P5mGU5noCe1Z97" 31 | ); 32 | 33 | // external programs 34 | export const CORE_PROGRAM_ID = new PublicKey( 35 | "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" 36 | ); 37 | 38 | // ------------------------------------------- seeds 39 | export const SEED_PREFIX = "Kessoku"; 40 | 41 | export const ASSET_MANAGER_PREFIX = "Seika"; 42 | 43 | export const SEED_LISTING_DATA = "Hitori"; 44 | 45 | export const SEED_MARKETPLACE_CONFIG = "Ijichi"; 46 | 47 | export const SEED_WALLET = "Yamada"; 48 | 49 | export const SEED_BID_DATA = "Futari"; 50 | 51 | // --------------------------------------------------- accounts 52 | export const PAYMENT_MINT = new PublicKey( 53 | "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr" 54 | ); // USDC - dev 55 | 56 | // ------------------------------------------------------- testing 57 | export let asset = new PublicKey( 58 | "AWASHVbTkP4rHhPgeKUJwEf3J4ngoqZsCv7VRZSW4ERf" 59 | ); 60 | -------------------------------------------------------------------------------- /tests/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import { Idl } from "@coral-xyz/anchor"; 2 | import { Keypair } from "@solana/web3.js"; 3 | import { readFileSync } from "fs"; 4 | import { homedir } from "os"; 5 | 6 | export function loadProgramIdl(program: SupportedPrograms): Idl { 7 | switch (program) { 8 | case "bid": { 9 | return JSON.parse( 10 | readFileSync( 11 | process.cwd() + "/target/idl/soundwork_bid.json", 12 | "utf8" 13 | ) 14 | ); 15 | } 16 | case "create": { 17 | return JSON.parse( 18 | readFileSync( 19 | process.cwd() + "/target/idl/soundwork_create.json", 20 | "utf8" 21 | ) 22 | ); 23 | } 24 | case "list": { 25 | return JSON.parse( 26 | readFileSync( 27 | process.cwd() + "/target/idl/soundwork_list.json", 28 | "utf8" 29 | ) 30 | ); 31 | } 32 | default: { 33 | console.log("error: unknown program"); 34 | break; 35 | } 36 | } 37 | } 38 | 39 | type SupportedPrograms = "bid" | "create" | "list"; 40 | 41 | export function loadKeypair(file: KeyPairFile): Keypair { 42 | const data = readFileSync(homedir() + `/.config/solana/${file}`, "utf-8"); 43 | 44 | return Keypair.fromSecretKey(Buffer.from(JSON.parse(data))); 45 | } 46 | 47 | // ! change this accordingly 48 | 49 | export enum KeyPairFile { 50 | main = "id.json", 51 | secondary = "id-new.json", 52 | } 53 | -------------------------------------------------------------------------------- /tests/utils/pda.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { 3 | ASSET_MANAGER_PREFIX, 4 | SEED_BID_DATA, 5 | SEED_LISTING_DATA, 6 | SEED_MARKETPLACE_CONFIG, 7 | SEED_PREFIX, 8 | SEED_WALLET, 9 | SOUNDWORK_BID_ID, 10 | SOUNDWORK_LIST_ID, 11 | } from "./constants"; 12 | 13 | /** 14 | * Derive the asset manager account address 15 | * @returns {PublicKey} The asset Manager Address. 16 | */ 17 | export const findAssetManagerAddress = (): PublicKey => { 18 | return PublicKey.findProgramAddressSync( 19 | [Buffer.from(SEED_PREFIX), Buffer.from(ASSET_MANAGER_PREFIX)], 20 | SOUNDWORK_LIST_ID 21 | )[0]; 22 | }; 23 | 24 | /** 25 | * Derive the marketplace config account 26 | * @returns {PublicKey} listingData Address. 27 | */ 28 | export const findMarketplaceConfigAddress = (): PublicKey => { 29 | return PublicKey.findProgramAddressSync( 30 | [Buffer.from(SEED_PREFIX), Buffer.from(SEED_MARKETPLACE_CONFIG)], 31 | SOUNDWORK_LIST_ID 32 | )[0]; 33 | }; 34 | 35 | /** 36 | * Derive the listing data account address 37 | * @param asset Asset address 38 | * @returns {PublicKey} listingData Address. 39 | */ 40 | export const findListingDataAddress = (asset: PublicKey): PublicKey => { 41 | return PublicKey.findProgramAddressSync( 42 | [ 43 | Buffer.from(SEED_PREFIX), 44 | Buffer.from(SEED_LISTING_DATA), 45 | asset.toBuffer(), 46 | ], 47 | SOUNDWORK_LIST_ID 48 | )[0]; 49 | }; 50 | 51 | /** 52 | * Derive the user wallet escrow address 53 | * @param authority user's address 54 | * @returns {PublicKey} listingData Address. 55 | */ 56 | export const findWalletAddress = (authority: PublicKey): PublicKey => { 57 | return PublicKey.findProgramAddressSync( 58 | [ 59 | Buffer.from(SEED_PREFIX), 60 | Buffer.from(SEED_WALLET), 61 | authority.toBuffer(), 62 | ], 63 | SOUNDWORK_LIST_ID 64 | )[0]; 65 | }; 66 | 67 | /** 68 | * Derive the bid data account address 69 | * 70 | * @param asset asset's address 71 | * 72 | * @returns {PublicKey} The bid data Address. 73 | */ 74 | export const findBidDataAddress = (asset: PublicKey): PublicKey => { 75 | return PublicKey.findProgramAddressSync( 76 | [ 77 | Buffer.from(SEED_PREFIX), 78 | Buffer.from(SEED_BID_DATA), 79 | asset.toBuffer(), 80 | ], 81 | SOUNDWORK_BID_ID 82 | )[0]; 83 | }; 84 | -------------------------------------------------------------------------------- /tests/utils/programs.ts: -------------------------------------------------------------------------------- 1 | // our program instances 2 | 3 | import { AnchorProvider, Program, Provider } from "@coral-xyz/anchor"; 4 | 5 | import type { SoundworkBid } from "../../target/types/soundwork_bid"; 6 | import type { SoundworkCreate } from "../../target/types/soundwork_create"; 7 | import type { SoundworkList } from "../../target/types/soundwork_list"; 8 | 9 | // @ts-ignore 10 | import * as soundworkBidIDL from "../../target/idl/soundwork_bid.json"; 11 | // @ts-ignore 12 | import * as soundworkCreateIDL from "../../target/idl/soundwork_create.json"; 13 | // @ts-ignore 14 | import * as soundworkListIDL from "../../target/idl/soundwork_list.json"; 15 | 16 | import { 17 | defaultProvider, 18 | SOUNDWORK_BID_ID, 19 | SOUNDWORK_CREATE_ID, 20 | SOUNDWORK_LIST_ID, 21 | } from "./constants"; 22 | import { loadProgramIdl } from "./helpers"; 23 | 24 | export class BidProgram { 25 | constructor(readonly provider: Provider = defaultProvider) {} 26 | 27 | getProgram(): Program { 28 | const idl = loadProgramIdl("bid"); 29 | 30 | return new Program( 31 | soundworkBidIDL as unknown as soundworkBidIDL, 32 | this.provider 33 | ); 34 | } 35 | } 36 | 37 | export class CreateProgram { 38 | constructor(readonly provider: Provider = defaultProvider) {} 39 | 40 | getProgram(): Program { 41 | return new Program( 42 | soundworkCreateIDL as unknown as SoundworkCreate, 43 | this.provider 44 | ); 45 | } 46 | } 47 | 48 | export class ListProgram { 49 | constructor(readonly provider: Provider = defaultProvider) {} 50 | 51 | getProgram(): Program { 52 | return new Program( 53 | soundworkListIDL as unknown as SoundworkList, 54 | this.provider 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.17.2", "@babel/runtime@^7.23.4": 6 | version "7.24.4" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" 8 | integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== 9 | dependencies: 10 | regenerator-runtime "^0.14.0" 11 | 12 | "@babel/runtime@^7.24.5": 13 | version "7.24.5" 14 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" 15 | integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== 16 | dependencies: 17 | regenerator-runtime "^0.14.0" 18 | 19 | "@coral-xyz/anchor-errors@^0.30.1": 20 | version "0.30.1" 21 | resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz#bdfd3a353131345244546876eb4afc0e125bec30" 22 | integrity sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ== 23 | 24 | "@coral-xyz/anchor@^0.30.1": 25 | version "0.30.1" 26 | resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" 27 | integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== 28 | dependencies: 29 | "@coral-xyz/anchor-errors" "^0.30.1" 30 | "@coral-xyz/borsh" "^0.30.1" 31 | "@noble/hashes" "^1.3.1" 32 | "@solana/web3.js" "^1.68.0" 33 | bn.js "^5.1.2" 34 | bs58 "^4.0.1" 35 | buffer-layout "^1.2.2" 36 | camelcase "^6.3.0" 37 | cross-fetch "^3.1.5" 38 | crypto-hash "^1.3.0" 39 | eventemitter3 "^4.0.7" 40 | pako "^2.0.3" 41 | snake-case "^3.0.4" 42 | superstruct "^0.15.4" 43 | toml "^3.0.0" 44 | 45 | "@coral-xyz/borsh@^0.30.1": 46 | version "0.30.1" 47 | resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.30.1.tgz#869d8833abe65685c72e9199b8688477a4f6b0e3" 48 | integrity sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ== 49 | dependencies: 50 | bn.js "^5.1.2" 51 | buffer-layout "^1.2.0" 52 | 53 | "@noble/curves@^1.2.0", "@noble/curves@^1.4.0": 54 | version "1.4.0" 55 | resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" 56 | integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== 57 | dependencies: 58 | "@noble/hashes" "1.4.0" 59 | 60 | "@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0": 61 | version "1.4.0" 62 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" 63 | integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== 64 | 65 | "@solana/buffer-layout-utils@^0.2.0": 66 | version "0.2.0" 67 | resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca" 68 | integrity sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g== 69 | dependencies: 70 | "@solana/buffer-layout" "^4.0.0" 71 | "@solana/web3.js" "^1.32.0" 72 | bigint-buffer "^1.1.5" 73 | bignumber.js "^9.0.1" 74 | 75 | "@solana/buffer-layout@^4.0.0", "@solana/buffer-layout@^4.0.1": 76 | version "4.0.1" 77 | resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" 78 | integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== 79 | dependencies: 80 | buffer "~6.0.3" 81 | 82 | "@solana/codecs-core@2.0.0-preview.2": 83 | version "2.0.0-preview.2" 84 | resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-preview.2.tgz#689784d032fbc1fedbde40bb25d76cdcecf6553b" 85 | integrity sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg== 86 | dependencies: 87 | "@solana/errors" "2.0.0-preview.2" 88 | 89 | "@solana/codecs-data-structures@2.0.0-preview.2": 90 | version "2.0.0-preview.2" 91 | resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-preview.2.tgz#e82cb1b6d154fa636cd5c8953ff3f32959cc0370" 92 | integrity sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg== 93 | dependencies: 94 | "@solana/codecs-core" "2.0.0-preview.2" 95 | "@solana/codecs-numbers" "2.0.0-preview.2" 96 | "@solana/errors" "2.0.0-preview.2" 97 | 98 | "@solana/codecs-numbers@2.0.0-preview.2": 99 | version "2.0.0-preview.2" 100 | resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-preview.2.tgz#56995c27396cd8ee3bae8bd055363891b630bbd0" 101 | integrity sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw== 102 | dependencies: 103 | "@solana/codecs-core" "2.0.0-preview.2" 104 | "@solana/errors" "2.0.0-preview.2" 105 | 106 | "@solana/codecs-strings@2.0.0-preview.2": 107 | version "2.0.0-preview.2" 108 | resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-preview.2.tgz#8bd01a4e48614d5289d72d743c3e81305d445c46" 109 | integrity sha512-EgBwY+lIaHHgMJIqVOGHfIfpdmmUDNoNO/GAUGeFPf+q0dF+DtwhJPEMShhzh64X2MeCZcmSO6Kinx0Bvmmz2g== 110 | dependencies: 111 | "@solana/codecs-core" "2.0.0-preview.2" 112 | "@solana/codecs-numbers" "2.0.0-preview.2" 113 | "@solana/errors" "2.0.0-preview.2" 114 | 115 | "@solana/codecs@2.0.0-preview.2": 116 | version "2.0.0-preview.2" 117 | resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-preview.2.tgz#d6615fec98f423166fb89409f9a4ad5b74c10935" 118 | integrity sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA== 119 | dependencies: 120 | "@solana/codecs-core" "2.0.0-preview.2" 121 | "@solana/codecs-data-structures" "2.0.0-preview.2" 122 | "@solana/codecs-numbers" "2.0.0-preview.2" 123 | "@solana/codecs-strings" "2.0.0-preview.2" 124 | "@solana/options" "2.0.0-preview.2" 125 | 126 | "@solana/errors@2.0.0-preview.2": 127 | version "2.0.0-preview.2" 128 | resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-preview.2.tgz#e0ea8b008c5c02528d5855bc1903e5e9bbec322e" 129 | integrity sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA== 130 | dependencies: 131 | chalk "^5.3.0" 132 | commander "^12.0.0" 133 | 134 | "@solana/options@2.0.0-preview.2": 135 | version "2.0.0-preview.2" 136 | resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-preview.2.tgz#13ff008bf43a5056ef9a091dc7bb3f39321e867e" 137 | integrity sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w== 138 | dependencies: 139 | "@solana/codecs-core" "2.0.0-preview.2" 140 | "@solana/codecs-numbers" "2.0.0-preview.2" 141 | 142 | "@solana/spl-token-group@^0.0.4": 143 | version "0.0.4" 144 | resolved "https://registry.yarnpkg.com/@solana/spl-token-group/-/spl-token-group-0.0.4.tgz#4f45d9526c96a33b9a1929a264d0aa21c7e38a2d" 145 | integrity sha512-7+80nrEMdUKlK37V6kOe024+T7J4nNss0F8LQ9OOPYdWCCfJmsGUzVx2W3oeizZR4IHM6N4yC9v1Xqwc3BTPWw== 146 | dependencies: 147 | "@solana/codecs" "2.0.0-preview.2" 148 | "@solana/spl-type-length-value" "0.1.0" 149 | 150 | "@solana/spl-token-metadata@^0.1.4": 151 | version "0.1.4" 152 | resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.4.tgz#5cdc3b857a8c4a6877df24e24a8648c4132d22ba" 153 | integrity sha512-N3gZ8DlW6NWDV28+vCCDJoTqaCZiF/jDUnk3o8GRkAFzHObiR60Bs1gXHBa8zCPdvOwiG6Z3dg5pg7+RW6XNsQ== 154 | dependencies: 155 | "@solana/codecs" "2.0.0-preview.2" 156 | "@solana/spl-type-length-value" "0.1.0" 157 | 158 | "@solana/spl-token@^0.4.6": 159 | version "0.4.6" 160 | resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.6.tgz#eb44e5080ea7b6fc976abcb39457223211bd9076" 161 | integrity sha512-1nCnUqfHVtdguFciVWaY/RKcQz1IF4b31jnKgAmjU9QVN1q7dRUkTEWJZgTYIEtsULjVnC9jRqlhgGN39WbKKA== 162 | dependencies: 163 | "@solana/buffer-layout" "^4.0.0" 164 | "@solana/buffer-layout-utils" "^0.2.0" 165 | "@solana/spl-token-group" "^0.0.4" 166 | "@solana/spl-token-metadata" "^0.1.4" 167 | buffer "^6.0.3" 168 | 169 | "@solana/spl-type-length-value@0.1.0": 170 | version "0.1.0" 171 | resolved "https://registry.yarnpkg.com/@solana/spl-type-length-value/-/spl-type-length-value-0.1.0.tgz#b5930cf6c6d8f50c7ff2a70463728a4637a2f26b" 172 | integrity sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA== 173 | dependencies: 174 | buffer "^6.0.3" 175 | 176 | "@solana/web3.js@^1.32.0": 177 | version "1.91.8" 178 | resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.8.tgz#0d5eb69626a92c391b53e15bfbb0bad3f6858e51" 179 | integrity sha512-USa6OS1jbh8zOapRJ/CBZImZ8Xb7AJjROZl5adql9TpOoBN9BUzyyouS5oPuZHft7S7eB8uJPuXWYjMi6BHgOw== 180 | dependencies: 181 | "@babel/runtime" "^7.24.5" 182 | "@noble/curves" "^1.4.0" 183 | "@noble/hashes" "^1.4.0" 184 | "@solana/buffer-layout" "^4.0.1" 185 | agentkeepalive "^4.5.0" 186 | bigint-buffer "^1.1.5" 187 | bn.js "^5.2.1" 188 | borsh "^0.7.0" 189 | bs58 "^4.0.1" 190 | buffer "6.0.3" 191 | fast-stable-stringify "^1.0.0" 192 | jayson "^4.1.0" 193 | node-fetch "^2.7.0" 194 | rpc-websockets "^7.11.0" 195 | superstruct "^0.14.2" 196 | 197 | "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.91.4": 198 | version "1.91.4" 199 | resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.4.tgz#b80295ce72aa125930dfc5b41b4b4e3f85fd87fa" 200 | integrity sha512-zconqecIcBqEF6JiM4xYF865Xc4aas+iWK5qnu7nwKPq9ilRYcn+2GiwpYXqUqqBUe0XCO17w18KO0F8h+QATg== 201 | dependencies: 202 | "@babel/runtime" "^7.23.4" 203 | "@noble/curves" "^1.2.0" 204 | "@noble/hashes" "^1.3.3" 205 | "@solana/buffer-layout" "^4.0.1" 206 | agentkeepalive "^4.5.0" 207 | bigint-buffer "^1.1.5" 208 | bn.js "^5.2.1" 209 | borsh "^0.7.0" 210 | bs58 "^4.0.1" 211 | buffer "6.0.3" 212 | fast-stable-stringify "^1.0.0" 213 | jayson "^4.1.0" 214 | node-fetch "^2.7.0" 215 | rpc-websockets "^7.5.1" 216 | superstruct "^0.14.2" 217 | 218 | "@types/bn.js@^5.1.0": 219 | version "5.1.5" 220 | resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" 221 | integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== 222 | dependencies: 223 | "@types/node" "*" 224 | 225 | "@types/chai@^4.3.0": 226 | version "4.3.14" 227 | resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.14.tgz#ae3055ea2be43c91c9fd700a36d67820026d96e6" 228 | integrity sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w== 229 | 230 | "@types/connect@^3.4.33": 231 | version "3.4.38" 232 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" 233 | integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== 234 | dependencies: 235 | "@types/node" "*" 236 | 237 | "@types/json5@^0.0.29": 238 | version "0.0.29" 239 | resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" 240 | integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== 241 | 242 | "@types/mocha@^9.0.0": 243 | version "9.1.1" 244 | resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" 245 | integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== 246 | 247 | "@types/node@*": 248 | version "20.12.7" 249 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" 250 | integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== 251 | dependencies: 252 | undici-types "~5.26.4" 253 | 254 | "@types/node@^12.12.54": 255 | version "12.20.55" 256 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" 257 | integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== 258 | 259 | "@types/ws@^7.4.4": 260 | version "7.4.7" 261 | resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" 262 | integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== 263 | dependencies: 264 | "@types/node" "*" 265 | 266 | "@ungap/promise-all-settled@1.1.2": 267 | version "1.1.2" 268 | resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" 269 | integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== 270 | 271 | JSONStream@^1.3.5: 272 | version "1.3.5" 273 | resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" 274 | integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== 275 | dependencies: 276 | jsonparse "^1.2.0" 277 | through ">=2.2.7 <3" 278 | 279 | agentkeepalive@^4.5.0: 280 | version "4.5.0" 281 | resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" 282 | integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== 283 | dependencies: 284 | humanize-ms "^1.2.1" 285 | 286 | ansi-colors@4.1.1: 287 | version "4.1.1" 288 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" 289 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== 290 | 291 | ansi-regex@^5.0.1: 292 | version "5.0.1" 293 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 294 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 295 | 296 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 297 | version "4.3.0" 298 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 299 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 300 | dependencies: 301 | color-convert "^2.0.1" 302 | 303 | anymatch@~3.1.2: 304 | version "3.1.3" 305 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" 306 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 307 | dependencies: 308 | normalize-path "^3.0.0" 309 | picomatch "^2.0.4" 310 | 311 | argparse@^2.0.1: 312 | version "2.0.1" 313 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 314 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 315 | 316 | arrify@^1.0.0: 317 | version "1.0.1" 318 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 319 | integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== 320 | 321 | assertion-error@^1.1.0: 322 | version "1.1.0" 323 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 324 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 325 | 326 | balanced-match@^1.0.0: 327 | version "1.0.2" 328 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 329 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 330 | 331 | base-x@^3.0.2: 332 | version "3.0.9" 333 | resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" 334 | integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== 335 | dependencies: 336 | safe-buffer "^5.0.1" 337 | 338 | base64-js@^1.3.1: 339 | version "1.5.1" 340 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 341 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 342 | 343 | bigint-buffer@^1.1.5: 344 | version "1.1.5" 345 | resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" 346 | integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== 347 | dependencies: 348 | bindings "^1.3.0" 349 | 350 | bignumber.js@^9.0.1: 351 | version "9.1.2" 352 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" 353 | integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== 354 | 355 | binary-extensions@^2.0.0: 356 | version "2.3.0" 357 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" 358 | integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== 359 | 360 | bindings@^1.3.0: 361 | version "1.5.0" 362 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 363 | integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 364 | dependencies: 365 | file-uri-to-path "1.0.0" 366 | 367 | bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: 368 | version "5.2.1" 369 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" 370 | integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== 371 | 372 | borsh@^0.7.0: 373 | version "0.7.0" 374 | resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" 375 | integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== 376 | dependencies: 377 | bn.js "^5.2.0" 378 | bs58 "^4.0.0" 379 | text-encoding-utf-8 "^1.0.2" 380 | 381 | brace-expansion@^1.1.7: 382 | version "1.1.11" 383 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 384 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 385 | dependencies: 386 | balanced-match "^1.0.0" 387 | concat-map "0.0.1" 388 | 389 | braces@~3.0.2: 390 | version "3.0.2" 391 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 392 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 393 | dependencies: 394 | fill-range "^7.0.1" 395 | 396 | browser-stdout@1.3.1: 397 | version "1.3.1" 398 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 399 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 400 | 401 | bs58@^4.0.0, bs58@^4.0.1: 402 | version "4.0.1" 403 | resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" 404 | integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== 405 | dependencies: 406 | base-x "^3.0.2" 407 | 408 | buffer-from@^1.0.0, buffer-from@^1.1.0: 409 | version "1.1.2" 410 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 411 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 412 | 413 | buffer-layout@^1.2.0, buffer-layout@^1.2.2: 414 | version "1.2.2" 415 | resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" 416 | integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== 417 | 418 | buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: 419 | version "6.0.3" 420 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" 421 | integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== 422 | dependencies: 423 | base64-js "^1.3.1" 424 | ieee754 "^1.2.1" 425 | 426 | bufferutil@^4.0.1: 427 | version "4.0.8" 428 | resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" 429 | integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== 430 | dependencies: 431 | node-gyp-build "^4.3.0" 432 | 433 | camelcase@^6.0.0, camelcase@^6.3.0: 434 | version "6.3.0" 435 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" 436 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== 437 | 438 | chai@^4.3.4: 439 | version "4.4.1" 440 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" 441 | integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== 442 | dependencies: 443 | assertion-error "^1.1.0" 444 | check-error "^1.0.3" 445 | deep-eql "^4.1.3" 446 | get-func-name "^2.0.2" 447 | loupe "^2.3.6" 448 | pathval "^1.1.1" 449 | type-detect "^4.0.8" 450 | 451 | chalk@^4.1.0: 452 | version "4.1.2" 453 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 454 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 455 | dependencies: 456 | ansi-styles "^4.1.0" 457 | supports-color "^7.1.0" 458 | 459 | chalk@^5.3.0: 460 | version "5.3.0" 461 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" 462 | integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== 463 | 464 | check-error@^1.0.3: 465 | version "1.0.3" 466 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" 467 | integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== 468 | dependencies: 469 | get-func-name "^2.0.2" 470 | 471 | chokidar@3.5.3: 472 | version "3.5.3" 473 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 474 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 475 | dependencies: 476 | anymatch "~3.1.2" 477 | braces "~3.0.2" 478 | glob-parent "~5.1.2" 479 | is-binary-path "~2.1.0" 480 | is-glob "~4.0.1" 481 | normalize-path "~3.0.0" 482 | readdirp "~3.6.0" 483 | optionalDependencies: 484 | fsevents "~2.3.2" 485 | 486 | cliui@^7.0.2: 487 | version "7.0.4" 488 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 489 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 490 | dependencies: 491 | string-width "^4.2.0" 492 | strip-ansi "^6.0.0" 493 | wrap-ansi "^7.0.0" 494 | 495 | color-convert@^2.0.1: 496 | version "2.0.1" 497 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 498 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 499 | dependencies: 500 | color-name "~1.1.4" 501 | 502 | color-name@~1.1.4: 503 | version "1.1.4" 504 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 505 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 506 | 507 | commander@^12.0.0: 508 | version "12.1.0" 509 | resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" 510 | integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== 511 | 512 | commander@^2.20.3: 513 | version "2.20.3" 514 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 515 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 516 | 517 | concat-map@0.0.1: 518 | version "0.0.1" 519 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 520 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 521 | 522 | cross-fetch@^3.1.5: 523 | version "3.1.8" 524 | resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" 525 | integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== 526 | dependencies: 527 | node-fetch "^2.6.12" 528 | 529 | crypto-hash@^1.3.0: 530 | version "1.3.0" 531 | resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" 532 | integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== 533 | 534 | debug@4.3.3: 535 | version "4.3.3" 536 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" 537 | integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== 538 | dependencies: 539 | ms "2.1.2" 540 | 541 | decamelize@^4.0.0: 542 | version "4.0.0" 543 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" 544 | integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== 545 | 546 | deep-eql@^4.1.3: 547 | version "4.1.3" 548 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" 549 | integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== 550 | dependencies: 551 | type-detect "^4.0.0" 552 | 553 | delay@^5.0.0: 554 | version "5.0.0" 555 | resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" 556 | integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== 557 | 558 | diff@5.0.0: 559 | version "5.0.0" 560 | resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" 561 | integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== 562 | 563 | diff@^3.1.0: 564 | version "3.5.0" 565 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 566 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 567 | 568 | dot-case@^3.0.4: 569 | version "3.0.4" 570 | resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" 571 | integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== 572 | dependencies: 573 | no-case "^3.0.4" 574 | tslib "^2.0.3" 575 | 576 | dotenv@^16.4.5: 577 | version "16.4.5" 578 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" 579 | integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== 580 | 581 | emoji-regex@^8.0.0: 582 | version "8.0.0" 583 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 584 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 585 | 586 | es6-promise@^4.0.3: 587 | version "4.2.8" 588 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" 589 | integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== 590 | 591 | es6-promisify@^5.0.0: 592 | version "5.0.0" 593 | resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" 594 | integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== 595 | dependencies: 596 | es6-promise "^4.0.3" 597 | 598 | escalade@^3.1.1: 599 | version "3.1.2" 600 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" 601 | integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== 602 | 603 | escape-string-regexp@4.0.0: 604 | version "4.0.0" 605 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 606 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 607 | 608 | eventemitter3@^4.0.7: 609 | version "4.0.7" 610 | resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" 611 | integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== 612 | 613 | eyes@^0.1.8: 614 | version "0.1.8" 615 | resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" 616 | integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== 617 | 618 | fast-stable-stringify@^1.0.0: 619 | version "1.0.0" 620 | resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" 621 | integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== 622 | 623 | file-uri-to-path@1.0.0: 624 | version "1.0.0" 625 | resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 626 | integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== 627 | 628 | fill-range@^7.0.1: 629 | version "7.0.1" 630 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 631 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 632 | dependencies: 633 | to-regex-range "^5.0.1" 634 | 635 | find-up@5.0.0: 636 | version "5.0.0" 637 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" 638 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 639 | dependencies: 640 | locate-path "^6.0.0" 641 | path-exists "^4.0.0" 642 | 643 | flat@^5.0.2: 644 | version "5.0.2" 645 | resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" 646 | integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== 647 | 648 | fs.realpath@^1.0.0: 649 | version "1.0.0" 650 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 651 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 652 | 653 | fsevents@~2.3.2: 654 | version "2.3.3" 655 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" 656 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== 657 | 658 | get-caller-file@^2.0.5: 659 | version "2.0.5" 660 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 661 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 662 | 663 | get-func-name@^2.0.1, get-func-name@^2.0.2: 664 | version "2.0.2" 665 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" 666 | integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== 667 | 668 | glob-parent@~5.1.2: 669 | version "5.1.2" 670 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 671 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 672 | dependencies: 673 | is-glob "^4.0.1" 674 | 675 | glob@7.2.0: 676 | version "7.2.0" 677 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" 678 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== 679 | dependencies: 680 | fs.realpath "^1.0.0" 681 | inflight "^1.0.4" 682 | inherits "2" 683 | minimatch "^3.0.4" 684 | once "^1.3.0" 685 | path-is-absolute "^1.0.0" 686 | 687 | growl@1.10.5: 688 | version "1.10.5" 689 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 690 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 691 | 692 | has-flag@^4.0.0: 693 | version "4.0.0" 694 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 695 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 696 | 697 | he@1.2.0: 698 | version "1.2.0" 699 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 700 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 701 | 702 | humanize-ms@^1.2.1: 703 | version "1.2.1" 704 | resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" 705 | integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== 706 | dependencies: 707 | ms "^2.0.0" 708 | 709 | ieee754@^1.2.1: 710 | version "1.2.1" 711 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 712 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 713 | 714 | inflight@^1.0.4: 715 | version "1.0.6" 716 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 717 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 718 | dependencies: 719 | once "^1.3.0" 720 | wrappy "1" 721 | 722 | inherits@2: 723 | version "2.0.4" 724 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 725 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 726 | 727 | is-binary-path@~2.1.0: 728 | version "2.1.0" 729 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 730 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 731 | dependencies: 732 | binary-extensions "^2.0.0" 733 | 734 | is-extglob@^2.1.1: 735 | version "2.1.1" 736 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 737 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 738 | 739 | is-fullwidth-code-point@^3.0.0: 740 | version "3.0.0" 741 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 742 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 743 | 744 | is-glob@^4.0.1, is-glob@~4.0.1: 745 | version "4.0.3" 746 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 747 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 748 | dependencies: 749 | is-extglob "^2.1.1" 750 | 751 | is-number@^7.0.0: 752 | version "7.0.0" 753 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 754 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 755 | 756 | is-plain-obj@^2.1.0: 757 | version "2.1.0" 758 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" 759 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== 760 | 761 | is-unicode-supported@^0.1.0: 762 | version "0.1.0" 763 | resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" 764 | integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== 765 | 766 | isexe@^2.0.0: 767 | version "2.0.0" 768 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 769 | integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== 770 | 771 | isomorphic-ws@^4.0.1: 772 | version "4.0.1" 773 | resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" 774 | integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== 775 | 776 | jayson@^4.1.0: 777 | version "4.1.0" 778 | resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.0.tgz#60dc946a85197317f2b1439d672a8b0a99cea2f9" 779 | integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A== 780 | dependencies: 781 | "@types/connect" "^3.4.33" 782 | "@types/node" "^12.12.54" 783 | "@types/ws" "^7.4.4" 784 | JSONStream "^1.3.5" 785 | commander "^2.20.3" 786 | delay "^5.0.0" 787 | es6-promisify "^5.0.0" 788 | eyes "^0.1.8" 789 | isomorphic-ws "^4.0.1" 790 | json-stringify-safe "^5.0.1" 791 | uuid "^8.3.2" 792 | ws "^7.4.5" 793 | 794 | js-yaml@4.1.0: 795 | version "4.1.0" 796 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 797 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 798 | dependencies: 799 | argparse "^2.0.1" 800 | 801 | json-stringify-safe@^5.0.1: 802 | version "5.0.1" 803 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 804 | integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== 805 | 806 | json5@^1.0.2: 807 | version "1.0.2" 808 | resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" 809 | integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== 810 | dependencies: 811 | minimist "^1.2.0" 812 | 813 | jsonparse@^1.2.0: 814 | version "1.3.1" 815 | resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" 816 | integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== 817 | 818 | locate-path@^6.0.0: 819 | version "6.0.0" 820 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" 821 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 822 | dependencies: 823 | p-locate "^5.0.0" 824 | 825 | log-symbols@4.1.0: 826 | version "4.1.0" 827 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" 828 | integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== 829 | dependencies: 830 | chalk "^4.1.0" 831 | is-unicode-supported "^0.1.0" 832 | 833 | loupe@^2.3.6: 834 | version "2.3.7" 835 | resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" 836 | integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== 837 | dependencies: 838 | get-func-name "^2.0.1" 839 | 840 | lower-case@^2.0.2: 841 | version "2.0.2" 842 | resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" 843 | integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== 844 | dependencies: 845 | tslib "^2.0.3" 846 | 847 | make-error@^1.1.1: 848 | version "1.3.6" 849 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 850 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 851 | 852 | minimatch@4.2.1: 853 | version "4.2.1" 854 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" 855 | integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== 856 | dependencies: 857 | brace-expansion "^1.1.7" 858 | 859 | minimatch@^3.0.4: 860 | version "3.1.2" 861 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 862 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 863 | dependencies: 864 | brace-expansion "^1.1.7" 865 | 866 | minimist@^1.2.0, minimist@^1.2.6: 867 | version "1.2.8" 868 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" 869 | integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== 870 | 871 | mkdirp@^0.5.1: 872 | version "0.5.6" 873 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" 874 | integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== 875 | dependencies: 876 | minimist "^1.2.6" 877 | 878 | mocha@^9.0.3: 879 | version "9.2.2" 880 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" 881 | integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== 882 | dependencies: 883 | "@ungap/promise-all-settled" "1.1.2" 884 | ansi-colors "4.1.1" 885 | browser-stdout "1.3.1" 886 | chokidar "3.5.3" 887 | debug "4.3.3" 888 | diff "5.0.0" 889 | escape-string-regexp "4.0.0" 890 | find-up "5.0.0" 891 | glob "7.2.0" 892 | growl "1.10.5" 893 | he "1.2.0" 894 | js-yaml "4.1.0" 895 | log-symbols "4.1.0" 896 | minimatch "4.2.1" 897 | ms "2.1.3" 898 | nanoid "3.3.1" 899 | serialize-javascript "6.0.0" 900 | strip-json-comments "3.1.1" 901 | supports-color "8.1.1" 902 | which "2.0.2" 903 | workerpool "6.2.0" 904 | yargs "16.2.0" 905 | yargs-parser "20.2.4" 906 | yargs-unparser "2.0.0" 907 | 908 | ms@2.1.2: 909 | version "2.1.2" 910 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 911 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 912 | 913 | ms@2.1.3, ms@^2.0.0: 914 | version "2.1.3" 915 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 916 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 917 | 918 | nanoid@3.3.1: 919 | version "3.3.1" 920 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" 921 | integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== 922 | 923 | no-case@^3.0.4: 924 | version "3.0.4" 925 | resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" 926 | integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== 927 | dependencies: 928 | lower-case "^2.0.2" 929 | tslib "^2.0.3" 930 | 931 | node-fetch@^2.6.12, node-fetch@^2.7.0: 932 | version "2.7.0" 933 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" 934 | integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== 935 | dependencies: 936 | whatwg-url "^5.0.0" 937 | 938 | node-gyp-build@^4.3.0: 939 | version "4.8.0" 940 | resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" 941 | integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== 942 | 943 | normalize-path@^3.0.0, normalize-path@~3.0.0: 944 | version "3.0.0" 945 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 946 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 947 | 948 | once@^1.3.0: 949 | version "1.4.0" 950 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 951 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 952 | dependencies: 953 | wrappy "1" 954 | 955 | p-limit@^3.0.2: 956 | version "3.1.0" 957 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" 958 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 959 | dependencies: 960 | yocto-queue "^0.1.0" 961 | 962 | p-locate@^5.0.0: 963 | version "5.0.0" 964 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" 965 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 966 | dependencies: 967 | p-limit "^3.0.2" 968 | 969 | pako@^2.0.3: 970 | version "2.1.0" 971 | resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" 972 | integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== 973 | 974 | path-exists@^4.0.0: 975 | version "4.0.0" 976 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 977 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 978 | 979 | path-is-absolute@^1.0.0: 980 | version "1.0.1" 981 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 982 | integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 983 | 984 | pathval@^1.1.1: 985 | version "1.1.1" 986 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 987 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 988 | 989 | picomatch@^2.0.4, picomatch@^2.2.1: 990 | version "2.3.1" 991 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 992 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 993 | 994 | prettier@^2.6.2: 995 | version "2.8.8" 996 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" 997 | integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== 998 | 999 | randombytes@^2.1.0: 1000 | version "2.1.0" 1001 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 1002 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 1003 | dependencies: 1004 | safe-buffer "^5.1.0" 1005 | 1006 | readdirp@~3.6.0: 1007 | version "3.6.0" 1008 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 1009 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 1010 | dependencies: 1011 | picomatch "^2.2.1" 1012 | 1013 | regenerator-runtime@^0.14.0: 1014 | version "0.14.1" 1015 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" 1016 | integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== 1017 | 1018 | require-directory@^2.1.1: 1019 | version "2.1.1" 1020 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 1021 | integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== 1022 | 1023 | rpc-websockets@^7.11.0: 1024 | version "7.11.0" 1025 | resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.11.0.tgz#05451975963a7d1a4cf36d54e200bfc4402a56d7" 1026 | integrity sha512-IkLYjayPv6Io8C/TdCL5gwgzd1hFz2vmBZrjMw/SPEXo51ETOhnzgS4Qy5GWi2JQN7HKHa66J3+2mv0fgNh/7w== 1027 | dependencies: 1028 | eventemitter3 "^4.0.7" 1029 | uuid "^8.3.2" 1030 | ws "^8.5.0" 1031 | optionalDependencies: 1032 | bufferutil "^4.0.1" 1033 | utf-8-validate "^5.0.2" 1034 | 1035 | rpc-websockets@^7.5.1: 1036 | version "7.9.0" 1037 | resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.9.0.tgz#a3938e16d6f134a3999fdfac422a503731bf8973" 1038 | integrity sha512-DwKewQz1IUA5wfLvgM8wDpPRcr+nWSxuFxx5CbrI2z/MyyZ4nXLM86TvIA+cI1ZAdqC8JIBR1mZR55dzaLU+Hw== 1039 | dependencies: 1040 | "@babel/runtime" "^7.17.2" 1041 | eventemitter3 "^4.0.7" 1042 | uuid "^8.3.2" 1043 | ws "^8.5.0" 1044 | optionalDependencies: 1045 | bufferutil "^4.0.1" 1046 | utf-8-validate "^5.0.2" 1047 | 1048 | safe-buffer@^5.0.1, safe-buffer@^5.1.0: 1049 | version "5.2.1" 1050 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 1051 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 1052 | 1053 | serialize-javascript@6.0.0: 1054 | version "6.0.0" 1055 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" 1056 | integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== 1057 | dependencies: 1058 | randombytes "^2.1.0" 1059 | 1060 | snake-case@^3.0.4: 1061 | version "3.0.4" 1062 | resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" 1063 | integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== 1064 | dependencies: 1065 | dot-case "^3.0.4" 1066 | tslib "^2.0.3" 1067 | 1068 | source-map-support@^0.5.6: 1069 | version "0.5.21" 1070 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 1071 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 1072 | dependencies: 1073 | buffer-from "^1.0.0" 1074 | source-map "^0.6.0" 1075 | 1076 | source-map@^0.6.0: 1077 | version "0.6.1" 1078 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 1079 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 1080 | 1081 | string-width@^4.1.0, string-width@^4.2.0: 1082 | version "4.2.3" 1083 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 1084 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 1085 | dependencies: 1086 | emoji-regex "^8.0.0" 1087 | is-fullwidth-code-point "^3.0.0" 1088 | strip-ansi "^6.0.1" 1089 | 1090 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 1091 | version "6.0.1" 1092 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 1093 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1094 | dependencies: 1095 | ansi-regex "^5.0.1" 1096 | 1097 | strip-bom@^3.0.0: 1098 | version "3.0.0" 1099 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 1100 | integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== 1101 | 1102 | strip-json-comments@3.1.1: 1103 | version "3.1.1" 1104 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 1105 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1106 | 1107 | superstruct@^0.14.2: 1108 | version "0.14.2" 1109 | resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" 1110 | integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== 1111 | 1112 | superstruct@^0.15.4: 1113 | version "0.15.5" 1114 | resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" 1115 | integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== 1116 | 1117 | supports-color@8.1.1: 1118 | version "8.1.1" 1119 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" 1120 | integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== 1121 | dependencies: 1122 | has-flag "^4.0.0" 1123 | 1124 | supports-color@^7.1.0: 1125 | version "7.2.0" 1126 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1127 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1128 | dependencies: 1129 | has-flag "^4.0.0" 1130 | 1131 | text-encoding-utf-8@^1.0.2: 1132 | version "1.0.2" 1133 | resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" 1134 | integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== 1135 | 1136 | "through@>=2.2.7 <3": 1137 | version "2.3.8" 1138 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 1139 | integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== 1140 | 1141 | to-regex-range@^5.0.1: 1142 | version "5.0.1" 1143 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1144 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1145 | dependencies: 1146 | is-number "^7.0.0" 1147 | 1148 | toml@^3.0.0: 1149 | version "3.0.0" 1150 | resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" 1151 | integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== 1152 | 1153 | tr46@~0.0.3: 1154 | version "0.0.3" 1155 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 1156 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 1157 | 1158 | ts-mocha@^10.0.0: 1159 | version "10.0.0" 1160 | resolved "https://registry.yarnpkg.com/ts-mocha/-/ts-mocha-10.0.0.tgz#41a8d099ac90dbbc64b06976c5025ffaebc53cb9" 1161 | integrity sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw== 1162 | dependencies: 1163 | ts-node "7.0.1" 1164 | optionalDependencies: 1165 | tsconfig-paths "^3.5.0" 1166 | 1167 | ts-node@7.0.1: 1168 | version "7.0.1" 1169 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" 1170 | integrity sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw== 1171 | dependencies: 1172 | arrify "^1.0.0" 1173 | buffer-from "^1.1.0" 1174 | diff "^3.1.0" 1175 | make-error "^1.1.1" 1176 | minimist "^1.2.0" 1177 | mkdirp "^0.5.1" 1178 | source-map-support "^0.5.6" 1179 | yn "^2.0.0" 1180 | 1181 | tsconfig-paths@^3.5.0: 1182 | version "3.15.0" 1183 | resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" 1184 | integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== 1185 | dependencies: 1186 | "@types/json5" "^0.0.29" 1187 | json5 "^1.0.2" 1188 | minimist "^1.2.6" 1189 | strip-bom "^3.0.0" 1190 | 1191 | tslib@^2.0.3: 1192 | version "2.6.2" 1193 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" 1194 | integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== 1195 | 1196 | type-detect@^4.0.0, type-detect@^4.0.8: 1197 | version "4.0.8" 1198 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 1199 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 1200 | 1201 | typescript@^4.3.5: 1202 | version "4.9.5" 1203 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" 1204 | integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== 1205 | 1206 | undici-types@~5.26.4: 1207 | version "5.26.5" 1208 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" 1209 | integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== 1210 | 1211 | utf-8-validate@^5.0.2: 1212 | version "5.0.10" 1213 | resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" 1214 | integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== 1215 | dependencies: 1216 | node-gyp-build "^4.3.0" 1217 | 1218 | uuid@^8.3.2: 1219 | version "8.3.2" 1220 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" 1221 | integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== 1222 | 1223 | webidl-conversions@^3.0.0: 1224 | version "3.0.1" 1225 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 1226 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 1227 | 1228 | whatwg-url@^5.0.0: 1229 | version "5.0.0" 1230 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 1231 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 1232 | dependencies: 1233 | tr46 "~0.0.3" 1234 | webidl-conversions "^3.0.0" 1235 | 1236 | which@2.0.2: 1237 | version "2.0.2" 1238 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1239 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1240 | dependencies: 1241 | isexe "^2.0.0" 1242 | 1243 | workerpool@6.2.0: 1244 | version "6.2.0" 1245 | resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" 1246 | integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== 1247 | 1248 | wrap-ansi@^7.0.0: 1249 | version "7.0.0" 1250 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 1251 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 1252 | dependencies: 1253 | ansi-styles "^4.0.0" 1254 | string-width "^4.1.0" 1255 | strip-ansi "^6.0.0" 1256 | 1257 | wrappy@1: 1258 | version "1.0.2" 1259 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1260 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 1261 | 1262 | ws@^7.4.5: 1263 | version "7.5.9" 1264 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" 1265 | integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== 1266 | 1267 | ws@^8.5.0: 1268 | version "8.16.0" 1269 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" 1270 | integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== 1271 | 1272 | y18n@^5.0.5: 1273 | version "5.0.8" 1274 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 1275 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 1276 | 1277 | yargs-parser@20.2.4: 1278 | version "20.2.4" 1279 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" 1280 | integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== 1281 | 1282 | yargs-parser@^20.2.2: 1283 | version "20.2.9" 1284 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" 1285 | integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== 1286 | 1287 | yargs-unparser@2.0.0: 1288 | version "2.0.0" 1289 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" 1290 | integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== 1291 | dependencies: 1292 | camelcase "^6.0.0" 1293 | decamelize "^4.0.0" 1294 | flat "^5.0.2" 1295 | is-plain-obj "^2.1.0" 1296 | 1297 | yargs@16.2.0: 1298 | version "16.2.0" 1299 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" 1300 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== 1301 | dependencies: 1302 | cliui "^7.0.2" 1303 | escalade "^3.1.1" 1304 | get-caller-file "^2.0.5" 1305 | require-directory "^2.1.1" 1306 | string-width "^4.2.0" 1307 | y18n "^5.0.5" 1308 | yargs-parser "^20.2.2" 1309 | 1310 | yn@^2.0.0: 1311 | version "2.0.0" 1312 | resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" 1313 | integrity sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ== 1314 | 1315 | yocto-queue@^0.1.0: 1316 | version "0.1.0" 1317 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 1318 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 1319 | --------------------------------------------------------------------------------