diff --git a/Anchor.toml b/Anchor.toml index fc8bcce..a1e6719 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -39,6 +39,15 @@ address = "CZ1rQoAHSqWBoAEfqGsiLhgbM59dDrCWk3rnG5FXaoRV" # libreplex royalty enf [[test.validator.clone]] address = "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d" # metaplex core program +[[test.validator.clone]] +address = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY" # bubblegum + +[[test.validator.clone]] +address = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV" # Noop logger for bubblegum + +[[test.validator.clone]] +address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" # compression program + [[test.validator.account]] address = "9V5HWD1ap6mCDMhBoXU5SVcZZn9ihqJtoMQZsw5MTnoD" # example payment proxy filename = './tests/deps/proxy.json' diff --git a/Cargo.lock b/Cargo.lock index 83ca789..750e800 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1151,6 +1151,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "kaigan" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dd100976df9dd59d0c3fecf6f9ad3f161a087374d1b2a77ebb4ad8920f11bb" +dependencies = [ + "borsh 0.10.3", +] + [[package]] name = "keccak" version = "0.1.5" @@ -1313,6 +1322,7 @@ dependencies = [ "anchor-spl", "community-managed-token", "m2_interface", + "mpl-bubblegum", "mpl-core", "mpl-token-metadata 4.1.1", "open_creator_protocol", @@ -1345,6 +1355,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "mpl-bubblegum" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9eff5ae5cafd1acdf7e7c93359da1eec91dcaede318470d9f68b78e8b7469f4" +dependencies = [ + "borsh 0.10.3", + "kaigan", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + [[package]] name = "mpl-core" version = "0.7.0" diff --git a/package.json b/package.json index 6c62133..e099fcc 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dependencies": { "@magiceden-oss/open_creator_protocol": "^0.3.2", "@metaplex-foundation/js": "^0.19.4", + "@metaplex-foundation/mpl-bubblegum": "^4.2.1", "@metaplex-foundation/mpl-core": "^0.4.7", "@metaplex-foundation/mpl-token-auth-rules": "^2.0.0", "@metaplex-foundation/mpl-token-metadata": "^3.1.2", @@ -14,6 +15,7 @@ "@metaplex-foundation/umi-bundle-tests": "^0.8.2", "@metaplex-foundation/umi-web3js-adapters": "^0.8.2", "@project-serum/anchor": "^0.26.0", + "@solana/spl-account-compression": "0.1.8", "@solana/spl-token": "^0.4.1", "@solana/spl-token-group": "^0.0.1", "@solana/web3.js": "^1.65.0", @@ -22,15 +24,16 @@ }, "devDependencies": { "@magiceden-oss/mmm": "file:sdk", + "@metaplex-foundation/digital-asset-standard-api": "^1.0.4", "@metaplex-foundation/mpl-migration-validator": "^0.4.1", "@msgpack/msgpack": "^2.8.0", "@types/jest": "29.0.0", "chai": "^4.3.4", "eslint-config-standard-with-typescript": "^21.0.1", "eslint-plugin-prettier": "^4.0.0", - "prettier": "^2.3.2", - "typescript": "^4.4.2", "jest": "29.0.0", - "ts-jest": "^29.0.0" + "prettier": "^2.3.2", + "ts-jest": "^29.0.0", + "typescript": "^4.4.2" } } diff --git a/programs/mmm/Cargo.toml b/programs/mmm/Cargo.toml index 60ab09d..388dd77 100644 --- a/programs/mmm/Cargo.toml +++ b/programs/mmm/Cargo.toml @@ -31,3 +31,4 @@ spl-associated-token-account = { version = "2.2.0", features = [ spl-token-2022 = {version = "1.0.0", features = ["no-entrypoint"] } m2_interface = { path = "../m2_interface" } mpl-core = "0.7.0" +mpl-bubblegum = "1.4.0" diff --git a/programs/mmm/src/errors.rs b/programs/mmm/src/errors.rs index 40d4031..5787a9f 100644 --- a/programs/mmm/src/errors.rs +++ b/programs/mmm/src/errors.rs @@ -72,4 +72,8 @@ pub enum MMMErrorCode { InvalidTokenExtension, // 0x1791 #[msg("Unsupported asset plugin")] UnsupportedAssetPlugin, // 0x1792 + #[msg("Invalid cnft metadata")] + InvalidCnftMetadata, // 0x1795 + #[msg("Invalid cnft metadata args")] + InvalidCnftMetadataArgs, // 0x1796 } diff --git a/programs/mmm/src/instructions/cnft/metadata_args.rs b/programs/mmm/src/instructions/cnft/metadata_args.rs new file mode 100644 index 0000000..38ded03 --- /dev/null +++ b/programs/mmm/src/instructions/cnft/metadata_args.rs @@ -0,0 +1,63 @@ +use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; + +// Below types are copied from mpl bubblegum crate so +// IDL will automatically include them +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +pub enum TokenStandard { + NonFungible, + FungibleAsset, + Fungible, + NonFungibleEdition, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Eq, PartialEq)] +pub struct Collection { + pub verified: bool, + pub key: Pubkey, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +pub enum UseMethod { + Burn, + Multiple, + Single, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Eq, PartialEq)] +pub struct Uses { + pub use_method: UseMethod, + pub remaining: u64, + pub total: u64, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +pub enum TokenProgramVersion { + Original, + Token2022, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Eq, PartialEq)] +pub struct Creator { + pub address: Pubkey, + pub verified: bool, + /// The percentage share. + /// + /// The value is a percentage, not basis points. + pub share: u8, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct MetadataArgs { + pub name: String, + pub symbol: String, // Changed from Option to String + pub uri: String, + pub seller_fee_basis_points: u16, + pub primary_sale_happened: bool, // Changed from Option to bool + pub is_mutable: bool, // Changed from Option to bool + pub edition_nonce: Option, + pub token_standard: Option, // Changed from Option to Option + pub collection: Option, + pub uses: Option, + pub token_program_version: TokenProgramVersion, // Assuming TokenProgramVersion is a simple u8 + pub creators: Vec, +} diff --git a/programs/mmm/src/instructions/cnft/mod.rs b/programs/mmm/src/instructions/cnft/mod.rs new file mode 100644 index 0000000..ba9e952 --- /dev/null +++ b/programs/mmm/src/instructions/cnft/mod.rs @@ -0,0 +1,5 @@ +pub mod metadata_args; +pub mod sol_cnft_fulfill_buy; + +pub use metadata_args::*; +pub use sol_cnft_fulfill_buy::*; diff --git a/programs/mmm/src/instructions/cnft/sol_cnft_fulfill_buy.rs b/programs/mmm/src/instructions/cnft/sol_cnft_fulfill_buy.rs new file mode 100644 index 0000000..70b09b3 --- /dev/null +++ b/programs/mmm/src/instructions/cnft/sol_cnft_fulfill_buy.rs @@ -0,0 +1,395 @@ +use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; +use mpl_bubblegum::utils::get_asset_id; +use solana_program::pubkey; + +use crate::{ + constants::*, + errors::MMMErrorCode, + index_ra, + state::{BubblegumProgram, Pool, SellState}, + util::{ + assert_valid_fees_bp, check_allowlists_for_cnft, check_remaining_accounts_for_m2, + get_buyside_seller_receives, get_lp_fee_bp, get_sol_fee, get_sol_lp_fee, + get_sol_total_price_and_next_price, hash_creators_from_metadata_args, hash_metadata, + log_pool, pay_creator_fees_in_sol_cnft, transfer_compressed_nft, try_close_escrow, + try_close_pool, try_close_sell_state, withdraw_m2, + }, + verify_referral::verify_referral, +}; + +use super::MetadataArgs; + +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct SolCnftFulfillBuyArgs { + // === cNFT transfer args === // + asset_id: Pubkey, + // The Merkle root for the tree. Can be retrieved from off-chain data store. + root: [u8; 32], + // A nonce ("number used once") value used to make the Merkle tree leaves unique. + // This is the value of num_minted for the tree stored in the TreeConfig account at the time the NFT was minted. + // The unique value for each asset can be retrieved from off-chain data store. + nonce: u64, + // The index of the leaf in the merkle tree. Can be retrieved from off-chain store. + index: u32, + + // === Contract args === // + pub min_payment_amount: u64, + pub maker_fee_bp: i16, // will be checked by cosigner + pub taker_fee_bp: i16, // will be checked by cosigner + + // Metadata args for cnft hash + // Reference: https://developers.metaplex.com/bubblegum/hashed-nft-data + pub metadata_args: MetadataArgs, +} + +#[derive(Accounts)] +#[instruction(args:SolCnftFulfillBuyArgs)] +pub struct SolCnftFulfillBuy<'info> { + #[account(mut)] + pub payer: Signer<'info>, + /// CHECK: we will check the owner field that matches the pool owner + #[account(mut)] + pub owner: UncheckedAccount<'info>, + #[account(constraint = owner.key() != cosigner.key() @ MMMErrorCode::InvalidCosigner)] + pub cosigner: Signer<'info>, + #[account( + mut, + constraint = verify_referral(&pool, &referral) @ MMMErrorCode::InvalidReferral, + )] + /// CHECK: use verify_referral to check the referral account + pub referral: UncheckedAccount<'info>, + #[account( + mut, + seeds = [POOL_PREFIX.as_bytes(), owner.key().as_ref(), pool.uuid.as_ref()], + has_one = owner @ MMMErrorCode::InvalidOwner, + has_one = cosigner @ MMMErrorCode::InvalidCosigner, + constraint = pool.payment_mint.eq(&Pubkey::default()) @ MMMErrorCode::InvalidPaymentMint, + constraint = pool.expiry == 0 || pool.expiry > Clock::get().unwrap().unix_timestamp @ MMMErrorCode::Expired, + bump + )] + pub pool: Box>, + /// CHECK: it's a pda, and the private key is owned by the seeds + #[account( + mut, + seeds = [BUYSIDE_SOL_ESCROW_ACCOUNT_PREFIX.as_bytes(), pool.key().as_ref()], + bump, + )] + pub buyside_sol_escrow_account: UncheckedAccount<'info>, + + // ==== cNFT transfer args ==== // + #[account( + mut, + seeds = [merkle_tree.key().as_ref()], + seeds::program = bubblegum_program.key(), + bump, + )] + /// CHECK: This account is neither written to nor read from. + pub tree_authority: UncheckedAccount<'info>, + + // The account that contains the Merkle tree, initialized by create_tree. + /// CHECK: This account is modified in the downstream Bubblegum program + #[account(mut)] + merkle_tree: UncheckedAccount<'info>, + /// CHECK: Used by bubblegum for logging (CPI) + #[account(address = pubkey!("noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"))] + log_wrapper: UncheckedAccount<'info>, + + bubblegum_program: Program<'info, BubblegumProgram>, + + /// CHECK: The Solana Program Library spl-account-compression program ID. + #[account(address = pubkey!("cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK") +)] + compression_program: UncheckedAccount<'info>, + + #[account( + init_if_needed, + payer = payer, + seeds = [ + SELL_STATE_PREFIX.as_bytes(), + pool.key().as_ref(), + args.asset_id.as_ref(), + ], + space = SellState::LEN, + bump + )] + pub sell_state: Account<'info, SellState>, + pub system_program: Program<'info, System>, + // Remaining accounts + // Branch: using shared escrow accounts + // 0: m2_program + // 1: shared_escrow_account + // 2-N: creator accounts + //. N+: proof accounts + // Branch: not using shared escrow accounts + // 0-N: creator accounts + //. N+: proof accounts +} + +pub fn handler<'info>( + ctx: Context<'_, '_, '_, 'info, SolCnftFulfillBuy<'info>>, + args: SolCnftFulfillBuyArgs, +) -> Result<()> { + // let payer = &ctx.accounts.payer; + let owner = &ctx.accounts.owner; + let pool = &mut ctx.accounts.pool; + let buyside_sol_escrow_account = &ctx.accounts.buyside_sol_escrow_account; + let sell_state = &mut ctx.accounts.sell_state; + let payer = &ctx.accounts.payer; + let referral: &UncheckedAccount<'info> = &ctx.accounts.referral; + let merkle_tree = &ctx.accounts.merkle_tree; + let pool_key = pool.key(); + let buyside_sol_escrow_account_seeds: &[&[&[u8]]] = &[&[ + BUYSIDE_SOL_ESCROW_ACCOUNT_PREFIX.as_bytes(), + pool_key.as_ref(), + &[ctx.bumps.buyside_sol_escrow_account], + ]]; + let system_program = &ctx.accounts.system_program; + // Remaining accounts are 1. (Optional) creator addresses and 2. Merkle proof path. + let creator_length = args.metadata_args.creators.len(); + let remaining_accounts = ctx.remaining_accounts; + + // 0. Verify allowlist + if let Some(ref collection) = args.metadata_args.collection { + let _ = check_allowlists_for_cnft(&pool.allowlists, collection.clone())?; + } else { + return Err(MMMErrorCode::InvalidCnftMetadata.into()); + } + + // 1. Cacluate amount and fees + let (total_price, next_price) = get_sol_total_price_and_next_price(pool, 1, true)?; + let metadata_royalty_bp = args.metadata_args.seller_fee_basis_points; + let seller_receives = { + let lp_fee_bp = get_lp_fee_bp(pool, buyside_sol_escrow_account.lamports()); + get_buyside_seller_receives( + total_price, + lp_fee_bp, + metadata_royalty_bp, + pool.buyside_creator_royalty_bp, + ) + }?; + + let lp_fee = get_sol_lp_fee(pool, buyside_sol_escrow_account.lamports(), seller_receives)?; + assert_valid_fees_bp(args.maker_fee_bp, args.taker_fee_bp)?; + let maker_fee = get_sol_fee(seller_receives, args.maker_fee_bp)?; + let taker_fee = get_sol_fee(seller_receives, args.taker_fee_bp)?; + let referral_fee = u64::try_from( + maker_fee + .checked_add(taker_fee) + .ok_or(MMMErrorCode::NumericOverflow)?, + ) + .map_err(|_| MMMErrorCode::NumericOverflow)?; + + // 2. Get creator accounts, verify creators + let (creator_accounts, proof_path) = if pool.using_shared_escrow() { + check_remaining_accounts_for_m2(remaining_accounts, &pool.owner.key())?; + + let amount: u64 = (total_price as i64 + maker_fee) as u64; + withdraw_m2( + pool, + ctx.bumps.pool, + buyside_sol_escrow_account, + index_ra!(remaining_accounts, 1), + system_program, + index_ra!(remaining_accounts, 0), + pool.owner, + amount, + )?; + pool.shared_escrow_count = pool + .shared_escrow_count + .checked_sub(1) + .ok_or(MMMErrorCode::NumericOverflow)?; + + remaining_accounts[2..].split_at(creator_length + 2) + } else { + remaining_accounts.split_at(creator_length) + }; + + let creator_hash = + hash_creators_from_metadata_args(creator_accounts.iter(), &args.metadata_args)?; + + // 3. Transfer CNFT to buyer (pool or owner) + let data_hash = hash_metadata(&args.metadata_args)?; + let asset_mint = get_asset_id(&merkle_tree.key(), args.nonce); + if asset_mint != args.asset_id { + return Err(MMMErrorCode::InvalidCnftMetadataArgs.into()); + } + // reinvest fulfill buy is just a placeholder for now if we want to enable double sided + // pool for cnft in the the future. + if pool.reinvest_fulfill_buy { + if pool.using_shared_escrow() { + return Err(MMMErrorCode::InvalidAccountState.into()); + } + transfer_compressed_nft( + &ctx.accounts.tree_authority.to_account_info(), + &pool.to_account_info(), + &ctx.accounts.payer.to_account_info(), + &ctx.accounts.owner.to_account_info(), + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + &ctx.accounts.system_program, // Pass as Program without calling to_account_info() + proof_path, + ctx.accounts.bubblegum_program.key(), + args.root, + data_hash, + creator_hash, + args.nonce, + args.index, + None, // signer passed through from ctx + )?; + pool.sellside_asset_amount = pool + .sellside_asset_amount + .checked_add(1) + .ok_or(MMMErrorCode::NumericOverflow)?; + sell_state.pool = pool.key(); + sell_state.pool_owner = owner.key(); + sell_state.asset_mint = asset_mint.key(); + sell_state.cosigner_annotation = pool.cosigner_annotation; + sell_state.asset_amount = sell_state + .asset_amount + .checked_add(1) + .ok_or(MMMErrorCode::NumericOverflow)?; + } else { + transfer_compressed_nft( + &ctx.accounts.tree_authority.to_account_info(), + &ctx.accounts.payer.to_account_info(), + &ctx.accounts.payer.to_account_info(), + &ctx.accounts.owner.to_account_info(), + &ctx.accounts.merkle_tree, + &ctx.accounts.log_wrapper, + &ctx.accounts.compression_program, + &ctx.accounts.system_program, // Pass as Program without calling to_account_info() + proof_path, + ctx.accounts.bubblegum_program.key(), + args.root, + data_hash, + creator_hash, + args.nonce, + args.index, + None, // signer passed through from ctx + )?; + } + + // 4. Pool owner as buyer pay royalties to creators + let royalty_paid = pay_creator_fees_in_sol_cnft( + pool.buyside_creator_royalty_bp, + seller_receives, + &args.metadata_args, + creator_accounts, + buyside_sol_escrow_account.to_account_info(), + buyside_sol_escrow_account_seeds, + system_program.to_account_info(), + )?; + + // 5. Seller pay buyer, prevent frontrun by pool config changes + let payment_amount = total_price + .checked_sub(lp_fee) + .ok_or(MMMErrorCode::NumericOverflow)? + .checked_sub(taker_fee as u64) + .ok_or(MMMErrorCode::NumericOverflow)? + .checked_sub(royalty_paid) + .ok_or(MMMErrorCode::NumericOverflow)?; + if payment_amount < args.min_payment_amount { + return Err(MMMErrorCode::InvalidRequestedPrice.into()); + } + + anchor_lang::solana_program::program::invoke_signed( + &anchor_lang::solana_program::system_instruction::transfer( + buyside_sol_escrow_account.key, + &payer.key, + payment_amount, + ), + &[ + buyside_sol_escrow_account.to_account_info(), + payer.to_account_info(), + system_program.to_account_info(), + ], + buyside_sol_escrow_account_seeds, + )?; + + // 6. Pay lp fee + if lp_fee > 0 { + anchor_lang::solana_program::program::invoke_signed( + &anchor_lang::solana_program::system_instruction::transfer( + buyside_sol_escrow_account.key, + owner.key, + lp_fee, + ), + &[ + buyside_sol_escrow_account.to_account_info(), + owner.to_account_info(), + system_program.to_account_info(), + ], + buyside_sol_escrow_account_seeds, + )?; + } + + // 7. Pay referral fee + if referral_fee > 0 { + anchor_lang::solana_program::program::invoke_signed( + &anchor_lang::solana_program::system_instruction::transfer( + buyside_sol_escrow_account.key, + referral.key, + referral_fee, + ), + &[ + buyside_sol_escrow_account.to_account_info(), + referral.to_account_info(), + system_program.to_account_info(), + ], + buyside_sol_escrow_account_seeds, + )?; + } + + // 8. try close accounts + try_close_escrow( + &buyside_sol_escrow_account.to_account_info(), + pool, + system_program, + buyside_sol_escrow_account_seeds, + )?; + try_close_sell_state(sell_state, payer.to_account_info())?; + + // 9. Return the remaining per pool escrow balance to the shared escrow account + if pool.using_shared_escrow() { + let min_rent = Rent::get()?.minimum_balance(0); + let shared_escrow_account = index_ra!(remaining_accounts, 1).to_account_info(); + if shared_escrow_account.lamports() + buyside_sol_escrow_account.lamports() > min_rent + && buyside_sol_escrow_account.lamports() > 0 + { + anchor_lang::solana_program::program::invoke_signed( + &anchor_lang::solana_program::system_instruction::transfer( + buyside_sol_escrow_account.key, + shared_escrow_account.key, + buyside_sol_escrow_account.lamports(), + ), + &[ + buyside_sol_escrow_account.to_account_info(), + shared_escrow_account, + system_program.to_account_info(), + ], + buyside_sol_escrow_account_seeds, + )?; + } else { + try_close_escrow( + buyside_sol_escrow_account, + pool, + system_program, + buyside_sol_escrow_account_seeds, + )?; + } + } + + // 10. update pool state and log + pool.lp_fee_earned = pool + .lp_fee_earned + .checked_add(lp_fee) + .ok_or(MMMErrorCode::NumericOverflow)?; + pool.spot_price = next_price; + pool.buyside_payment_amount = buyside_sol_escrow_account.lamports(); + + log_pool("post_sol_cnft_fulfill_buy", pool)?; + try_close_pool(pool, owner.to_account_info())?; + + Ok(()) +} diff --git a/programs/mmm/src/instructions/mod.rs b/programs/mmm/src/instructions/mod.rs index a12cc04..753895e 100644 --- a/programs/mmm/src/instructions/mod.rs +++ b/programs/mmm/src/instructions/mod.rs @@ -1,6 +1,7 @@ #![allow(missing_docs)] pub mod admin; +pub mod cnft; pub mod ext_vanilla; pub mod mip1; pub mod mpl_core_asset; @@ -8,6 +9,7 @@ pub mod ocp; pub mod vanilla; pub use admin::*; +pub use cnft::*; pub use ext_vanilla::*; pub use mip1::*; pub use mpl_core_asset::*; diff --git a/programs/mmm/src/lib.rs b/programs/mmm/src/lib.rs index f750238..4731b40 100644 --- a/programs/mmm/src/lib.rs +++ b/programs/mmm/src/lib.rs @@ -181,4 +181,11 @@ pub mod mmm { ) -> Result<()> { instructions::sol_mpl_core_fulfill_buy::handler(ctx, args) } + + pub fn cnft_fulfill_buy<'info>( + ctx: Context<'_, '_, '_, 'info, SolCnftFulfillBuy<'info>>, + args: SolCnftFulfillBuyArgs, + ) -> Result<()> { + instructions::sol_cnft_fulfill_buy::handler(ctx, args) + } } diff --git a/programs/mmm/src/state.rs b/programs/mmm/src/state.rs index 18e901a..fbb248d 100644 --- a/programs/mmm/src/state.rs +++ b/programs/mmm/src/state.rs @@ -1,4 +1,7 @@ +use std::ops::Deref; + use anchor_lang::{prelude::*, AnchorDeserialize, AnchorSerialize}; +use mpl_bubblegum::accounts::TreeConfig; use crate::constants::*; @@ -134,3 +137,40 @@ impl SellState { 32 + // [u8; 32] 200; // padding } + +// Wrapper structs to replace the Anchor program types until the Metaplex libs have +// better Anchor support. +pub struct BubblegumProgram; + +impl Id for BubblegumProgram { + fn id() -> Pubkey { + mpl_bubblegum::ID + } +} + +#[derive(Clone)] +pub struct TreeConfigAnchor(pub TreeConfig); + +impl AccountDeserialize for TreeConfigAnchor { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result { + Ok(Self(TreeConfig::from_bytes(buf)?)) + } +} + +impl anchor_lang::Owner for TreeConfigAnchor { + fn owner() -> Pubkey { + // pub use spl_token::ID is used at the top of the file + mpl_bubblegum::ID + } +} + +// No-op since we can't write data to a foreign program's account. +impl AccountSerialize for TreeConfigAnchor {} + +impl Deref for TreeConfigAnchor { + type Target = TreeConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/programs/mmm/src/util.rs b/programs/mmm/src/util.rs index 2603928..ce950a1 100644 --- a/programs/mmm/src/util.rs +++ b/programs/mmm/src/util.rs @@ -7,20 +7,21 @@ use crate::{ errors::MMMErrorCode, get_creators_from_royalties, state::*, - IndexableAsset, + Collection, IndexableAsset, MetadataArgs, }; use anchor_lang::{prelude::*, solana_program::log::sol_log_data}; use anchor_spl::token_interface::Mint; use m2_interface::{ withdraw_by_mmm_ix_with_program_id, WithdrawByMMMArgs, WithdrawByMmmIxArgs, WithdrawByMmmKeys, }; +use mpl_bubblegum::hash::hash_creators; use mpl_core::types::{Royalties, UpdateAuthority}; use mpl_token_metadata::{ accounts::{MasterEdition, Metadata}, types::{Creator, TokenStandard}, }; use open_creator_protocol::state::Policy; -use solana_program::program::invoke_signed; +use solana_program::{keccak, program::invoke_signed}; use spl_token_2022::{ extension::{ group_member_pointer::GroupMemberPointer, metadata_pointer::MetadataPointer, @@ -30,7 +31,7 @@ use spl_token_2022::{ }; use spl_token_group_interface::state::TokenGroupMember; use spl_token_metadata_interface::state::TokenMetadata; -use std::{convert::TryFrom, str::FromStr}; +use std::{convert::TryFrom, slice::Iter, str::FromStr}; #[macro_export] macro_rules! index_ra { @@ -156,6 +157,30 @@ pub fn check_allowlists_for_mint( Err(MMMErrorCode::InvalidAllowLists.into()) } +pub fn check_allowlists_for_cnft(allowlists: &[Allowlist], collection: Collection) -> Result<()> { + // Check mcc for cnft. + for allowlist_val in allowlists.iter() { + match allowlist_val.kind { + ALLOWLIST_KIND_EMPTY => {} + ALLOWLIST_KIND_ANY => { + // any is a special case, we don't need to check anything else + return Ok(()); + } + ALLOWLIST_KIND_MCC => { + if collection.key == allowlist_val.value && collection.verified { + return Ok(()); + } + } + _ => { + return Err(MMMErrorCode::InvalidAllowLists.into()); + } + } + } + + // at the end, we didn't find a match, thus return err + Err(MMMErrorCode::InvalidAllowLists.into()) +} + pub fn check_curve(curve_type: u8, curve_delta: u64) -> Result<()> { // So far we only allow linear and exponential curves // 0: linear @@ -597,6 +622,83 @@ pub fn pay_creator_fees_in_sol<'info>( Ok(total_royalty) } +#[allow(clippy::too_many_arguments)] +pub fn pay_creator_fees_in_sol_cnft<'info>( + buyside_creator_royalty_bp: u16, + total_price: u64, + metadata_args: &MetadataArgs, + creator_accounts: &[AccountInfo<'info>], + payer: AccountInfo<'info>, + payer_seeds: &[&[&[u8]]], + system_program: AccountInfo<'info>, +) -> Result { + // Calculate the total royalty to be paid + let royalty = ((total_price as u128) + .checked_mul(metadata_args.seller_fee_basis_points as u128) + .ok_or(MMMErrorCode::NumericOverflow)? + .checked_div(10000) + .ok_or(MMMErrorCode::NumericOverflow)? + .checked_mul(buyside_creator_royalty_bp as u128) + .ok_or(MMMErrorCode::NumericOverflow)? + .checked_div(10000) + .ok_or(MMMErrorCode::NumericOverflow)?) as u64; + + if royalty == 0 { + return Ok(0); + } + + if payer.lamports() < royalty { + return Err(MMMErrorCode::NotEnoughBalance.into()); + } + + let min_rent = Rent::get()?.minimum_balance(0); + let mut total_royalty: u64 = 0; + + let creator_accounts_iter = &mut creator_accounts.iter(); + for (index, creator) in metadata_args.creators.iter().enumerate() { + let creator_fee = if index == metadata_args.creators.len() - 1 { + royalty + .checked_sub(total_royalty) + .ok_or(MMMErrorCode::NumericOverflow)? + } else { + (royalty as u128) + .checked_mul(creator.share as u128) + .ok_or(MMMErrorCode::NumericOverflow)? + .checked_div(100) + .ok_or(MMMErrorCode::NumericOverflow)? as u64 + }; + let current_creator_info = next_account_info(creator_accounts_iter)?; + if creator.address.ne(current_creator_info.key) { + return Err(MMMErrorCode::InvalidCreatorAddress.into()); + } + let current_creator_lamports = current_creator_info.lamports(); + if creator_fee > 0 + && current_creator_lamports + .checked_add(creator_fee) + .ok_or(MMMErrorCode::NumericOverflow)? + > min_rent + { + anchor_lang::solana_program::program::invoke_signed( + &anchor_lang::solana_program::system_instruction::transfer( + payer.key, + current_creator_info.key, + creator_fee, + ), + &[ + payer.to_account_info(), + current_creator_info.to_account_info(), + system_program.to_account_info(), + ], + payer_seeds, + )?; + total_royalty = total_royalty + .checked_add(creator_fee) + .ok_or(MMMErrorCode::NumericOverflow)?; + } + } + Ok(total_royalty) +} + pub fn log_pool(prefix: &str, pool: &Pool) -> Result<()> { msg!(prefix); sol_log_data(&[&pool.try_to_vec()?]); @@ -1124,6 +1226,152 @@ pub fn create_core_metadata_core(royalties: &Royalties) -> MplCoreMetadata { } } +// TODO: use the transfer cpi builder by mpl bubblegum +#[allow(clippy::too_many_arguments)] +pub fn transfer_compressed_nft<'info>( + tree_authority: &AccountInfo<'info>, + leaf_owner: &AccountInfo<'info>, + leaf_delegate: &AccountInfo<'info>, + new_leaf_owner: &AccountInfo<'info>, + merkle_tree: &AccountInfo<'info>, + log_wrapper: &AccountInfo<'info>, + compression_program: &AccountInfo<'info>, + system_program: &Program<'info, System>, + proof_path: &[AccountInfo<'info>], + bubblegum_program_key: Pubkey, + root: [u8; 32], + data_hash: [u8; 32], + creator_hash: [u8; 32], + nonce: u64, + index: u32, + signer_seeds: Option<&[&[u8]]>, +) -> Result<()> { + // proof_path are the accounts that make up the required proof + let proof_path_len = proof_path.len(); + let mut accounts = Vec::with_capacity( + 8 // space for the 8 AccountMetas that are always included (below) + + proof_path_len, + ); + accounts.extend(vec![ + AccountMeta::new_readonly(tree_authority.key(), false), + AccountMeta::new_readonly(leaf_owner.key(), true), + AccountMeta::new_readonly(leaf_delegate.key(), false), + AccountMeta::new_readonly(new_leaf_owner.key(), false), + AccountMeta::new(merkle_tree.key(), false), + AccountMeta::new_readonly(log_wrapper.key(), false), + AccountMeta::new_readonly(compression_program.key(), false), + AccountMeta::new_readonly(system_program.key(), false), + ]); + + let transfer_discriminator: [u8; 8] = [163, 52, 200, 231, 140, 3, 69, 186]; + + let mut data = Vec::with_capacity( + 8 // The length of transfer_discriminator, + + root.len() + + data_hash.len() + + creator_hash.len() + + 8 // The length of the nonce + + 8, // The length of the index + ); + data.extend(transfer_discriminator); + data.extend(root); + data.extend(data_hash); + data.extend(creator_hash); + data.extend(nonce.to_le_bytes()); + data.extend(index.to_le_bytes()); + + let mut account_infos = Vec::with_capacity( + 8 // space for the 8 AccountInfos that are always included (below) + + proof_path_len, + ); + account_infos.extend(vec![ + tree_authority.to_account_info(), + leaf_owner.to_account_info(), + leaf_delegate.to_account_info(), + new_leaf_owner.to_account_info(), + merkle_tree.to_account_info(), + log_wrapper.to_account_info(), + compression_program.to_account_info(), + system_program.to_account_info(), + ]); + + // Add "accounts" (hashes) that make up the merkle proof from the remaining accounts. + for acc in proof_path.iter() { + accounts.push(AccountMeta::new_readonly(acc.key(), false)); + account_infos.push(acc.to_account_info()); + } + + let instruction = solana_program::instruction::Instruction { + program_id: bubblegum_program_key, + accounts, + data, + }; + + match signer_seeds { + Some(seeds) => { + let seeds_array: &[&[&[u8]]] = &[seeds]; + solana_program::program::invoke_signed(&instruction, &account_infos[..], seeds_array) + } + None => solana_program::program::invoke(&instruction, &account_infos[..]), + }?; + Ok(()) +} + +/// Computes the hash of the metadata. +/// +/// The hash is computed as the keccak256 hash of the metadata bytes, which is +/// then hashed with the `seller_fee_basis_points`. +pub fn hash_metadata(metadata: &MetadataArgs) -> Result<[u8; 32]> { + let hash = keccak::hashv(&[metadata.try_to_vec()?.as_slice()]); + // Calculate new data hash. + Ok(keccak::hashv(&[ + &hash.to_bytes(), + &metadata.seller_fee_basis_points.to_le_bytes(), + ]) + .to_bytes()) +} + +pub fn hash_creators_from_metadata_args( + creator_accounts: Iter, + metadata_args: &MetadataArgs, +) -> Result<[u8; 32]> { + let creator_shares = metadata_args + .creators + .iter() + .map(|c| c.share as u16) + .collect::>(); + + let creator_verified = metadata_args + .creators + .iter() + .map(|c| c.verified) + .collect::>(); + // Check that all input arrays/vectors are of the same length + if creator_accounts.len() != creator_shares.len() + || creator_accounts.len() != creator_verified.len() + { + return Err(MMMErrorCode::InvalidCreatorAddress.into()); + } + + // Convert input data to a vector of Creator structs + let creators: Vec = creator_accounts + .zip(creator_shares.iter()) + .zip(creator_verified.iter()) + .map( + |((account, &share), &verified)| mpl_bubblegum::types::Creator { + address: *account.key, + verified, + share: share as u8, // Assuming the share is never more than 255. If it can be, this needs additional checks. + }, + ) + .collect(); + + // Compute the hash from the Creator vector + let computed_hash = hash_creators(&creators); + + Ok(computed_hash) +} + #[cfg(test)] mod tests { use anchor_spl::token_2022; diff --git a/sdk/src/cnft.ts b/sdk/src/cnft.ts new file mode 100644 index 0000000..5260bce --- /dev/null +++ b/sdk/src/cnft.ts @@ -0,0 +1,117 @@ +import { + MPL_BUBBLEGUM_PROGRAM_ID, + TokenProgramVersion, + TokenStandard, + UseMethod, +} from '@metaplex-foundation/mpl-bubblegum'; +import { AccountMeta, PublicKey } from '@solana/web3.js'; +import { PREFIXES } from './constants'; +import { BN } from '@project-serum/anchor'; +import { Creator } from 'old-mpl-token-metadata'; + +export interface CNFT { + nftIndex: number; + proofs: PublicKey[]; +} + +export interface BubblegumTreeRef { + merkleTree: PublicKey; + // The Merkle root for the tree. Can be retrieved from off-chain data store. + root: PublicKey; + // The Keccak256 hash of the NFTs existing metadata (without the verified flag for the creator changed). + // The metadata is retrieved from off-chain data store + // Hash(Hash(metadataArgs), seller_fee_basis_points) + dataHash: PublicKey; + // The Keccak256 hash of the NFTs existing creators array (without the verified flag for the creator changed). + // The creators array is retrieved from off-chain data store. + creatorHash: PublicKey; + // The Keccak256 hash of the NFT metadata: + // Hash(metadataArgs) + metadataHash: number[]; + // A nonce ("number used once") value used to make the Merkle tree leaves unique. + // This is the value of num_minted for the tree stored in the TreeConfig account at the time the NFT was minted. + // The unique value for each asset can be retrieved from off-chain data store. + nonce: number; +} + +export interface BubblegumNftArgs { + tree: BubblegumTreeRef; + nft: CNFT; +} + +export function getBubblegumAuthorityPDA( + merkleRollPubKey: PublicKey, +): [PublicKey, number] { + return PublicKey.findProgramAddressSync( + [merkleRollPubKey.toBuffer()], + new PublicKey(MPL_BUBBLEGUM_PROGRAM_ID), + ); +} + +export function getByteArray(key: PublicKey): Array { + return Array.from(key.toBuffer()); +} + +// get "proof path" from asset proof, these are the accounts that need to be passed to the program as remaining accounts +// may also be empty if tree is small enough, and canopy depth is large enough +export function getProofPath( + proofs: PublicKey[], + canopyDepth?: number, +): AccountMeta[] { + return proofs + .map((pubkey: PublicKey) => ({ + pubkey, + isSigner: false, + isWritable: false, + })) + .slice(0, proofs.length - (!!canopyDepth ? canopyDepth : 0)); +} + +export interface CreatorRoyaltyConfig { + creators: Creator[]; + sellerFeeBasisPoints: number; +} + +// Function to convert a simple enum value to the IDL structure +export function convertToDecodeTokenStandardEnum(tokenStandard: TokenStandard) { + TokenStandard; + switch (tokenStandard) { + case TokenStandard.NonFungible: + return { nonFungible: {} }; + case TokenStandard.FungibleAsset: + return { fungibleAsset: {} }; + case TokenStandard.Fungible: + return { fungible: {} }; + case TokenStandard.NonFungibleEdition: + return { nonfungibleEdition: {} }; + default: + throw new Error('Unknown TokenStandard value'); + } +} + +// Function to convert UseMethod to the DecodeEnum format +export function convertToDecodeUseMethodEnum(useMethod: UseMethod) { + switch (useMethod) { + case UseMethod.Burn: + return { burn: {} }; + case UseMethod.Multiple: + return { multiple: {} }; + case UseMethod.Single: + return { single: {} }; + default: + throw new Error('Unknown UseMethod value'); + } +} + +export function convertToDecodeTokenProgramVersion( + tokenProgramVersion: TokenProgramVersion, +) { + switch (tokenProgramVersion) { + case TokenProgramVersion.Original: + return { original: {} }; + case TokenProgramVersion.Token2022: + return { token2022: {} }; + default: + throw new Error('Unknown TokenProgramVersion value'); + } +} diff --git a/sdk/src/idl/mmm.ts b/sdk/src/idl/mmm.ts index ae6d289..59e1407 100644 --- a/sdk/src/idl/mmm.ts +++ b/sdk/src/idl/mmm.ts @@ -2155,6 +2155,84 @@ export type Mmm = { } } ] + }, + { + "name": "cnftFulfillBuy", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "owner", + "isMut": true, + "isSigner": false + }, + { + "name": "cosigner", + "isMut": false, + "isSigner": true + }, + { + "name": "referral", + "isMut": true, + "isSigner": false + }, + { + "name": "pool", + "isMut": true, + "isSigner": false + }, + { + "name": "buysideSolEscrowAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "treeAuthority", + "isMut": true, + "isSigner": false + }, + { + "name": "merkleTree", + "isMut": true, + "isSigner": false + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false + }, + { + "name": "bubblegumProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "compressionProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "sellState", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": "SolCnftFulfillBuyArgs" + } + } + ] } ], "accounts": [ @@ -2453,6 +2531,192 @@ export type Mmm = { ] } }, + { + "name": "Collection", + "type": { + "kind": "struct", + "fields": [ + { + "name": "verified", + "type": "bool" + }, + { + "name": "key", + "type": "publicKey" + } + ] + } + }, + { + "name": "Uses", + "type": { + "kind": "struct", + "fields": [ + { + "name": "useMethod", + "type": { + "defined": "UseMethod" + } + }, + { + "name": "remaining", + "type": "u64" + }, + { + "name": "total", + "type": "u64" + } + ] + } + }, + { + "name": "Creator", + "type": { + "kind": "struct", + "fields": [ + { + "name": "address", + "type": "publicKey" + }, + { + "name": "verified", + "type": "bool" + }, + { + "name": "share", + "docs": [ + "The percentage share.", + "", + "The value is a percentage, not basis points." + ], + "type": "u8" + } + ] + } + }, + { + "name": "MetadataArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "sellerFeeBasisPoints", + "type": "u16" + }, + { + "name": "primarySaleHappened", + "type": "bool" + }, + { + "name": "isMutable", + "type": "bool" + }, + { + "name": "editionNonce", + "type": { + "option": "u8" + } + }, + { + "name": "tokenStandard", + "type": { + "option": { + "defined": "TokenStandard" + } + } + }, + { + "name": "collection", + "type": { + "option": { + "defined": "Collection" + } + } + }, + { + "name": "uses", + "type": { + "option": { + "defined": "Uses" + } + } + }, + { + "name": "tokenProgramVersion", + "type": { + "defined": "TokenProgramVersion" + } + }, + { + "name": "creators", + "type": { + "vec": { + "defined": "Creator" + } + } + } + ] + } + }, + { + "name": "SolCnftFulfillBuyArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "assetId", + "type": "publicKey" + }, + { + "name": "root", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "nonce", + "type": "u64" + }, + { + "name": "index", + "type": "u32" + }, + { + "name": "minPaymentAmount", + "type": "u64" + }, + { + "name": "makerFeeBp", + "type": "i16" + }, + { + "name": "takerFeeBp", + "type": "i16" + }, + { + "name": "metadataArgs", + "type": { + "defined": "MetadataArgs" + } + } + ] + } + }, { "name": "SolMip1FulfillSellArgs", "type": { @@ -2754,6 +3018,57 @@ export type Mmm = { } ] } + }, + { + "name": "TokenStandard", + "type": { + "kind": "enum", + "variants": [ + { + "name": "NonFungible" + }, + { + "name": "FungibleAsset" + }, + { + "name": "Fungible" + }, + { + "name": "NonFungibleEdition" + } + ] + } + }, + { + "name": "UseMethod", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Burn" + }, + { + "name": "Multiple" + }, + { + "name": "Single" + } + ] + } + }, + { + "name": "TokenProgramVersion", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Original" + }, + { + "name": "Token2022" + } + ] + } } ], "errors": [ @@ -2931,6 +3246,16 @@ export type Mmm = { "code": 6034, "name": "UnsupportedAssetPlugin", "msg": "Unsupported asset plugin" + }, + { + "code": 6035, + "name": "InvalidCnftMetadata", + "msg": "Invalid cnft metadata" + }, + { + "code": 6036, + "name": "InvalidCnftMetadataArgs", + "msg": "Invalid cnft metadata args" } ] }; @@ -5092,6 +5417,84 @@ export const IDL: Mmm = { } } ] + }, + { + "name": "cnftFulfillBuy", + "accounts": [ + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "owner", + "isMut": true, + "isSigner": false + }, + { + "name": "cosigner", + "isMut": false, + "isSigner": true + }, + { + "name": "referral", + "isMut": true, + "isSigner": false + }, + { + "name": "pool", + "isMut": true, + "isSigner": false + }, + { + "name": "buysideSolEscrowAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "treeAuthority", + "isMut": true, + "isSigner": false + }, + { + "name": "merkleTree", + "isMut": true, + "isSigner": false + }, + { + "name": "logWrapper", + "isMut": false, + "isSigner": false + }, + { + "name": "bubblegumProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "compressionProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "sellState", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": "SolCnftFulfillBuyArgs" + } + } + ] } ], "accounts": [ @@ -5390,6 +5793,192 @@ export const IDL: Mmm = { ] } }, + { + "name": "Collection", + "type": { + "kind": "struct", + "fields": [ + { + "name": "verified", + "type": "bool" + }, + { + "name": "key", + "type": "publicKey" + } + ] + } + }, + { + "name": "Uses", + "type": { + "kind": "struct", + "fields": [ + { + "name": "useMethod", + "type": { + "defined": "UseMethod" + } + }, + { + "name": "remaining", + "type": "u64" + }, + { + "name": "total", + "type": "u64" + } + ] + } + }, + { + "name": "Creator", + "type": { + "kind": "struct", + "fields": [ + { + "name": "address", + "type": "publicKey" + }, + { + "name": "verified", + "type": "bool" + }, + { + "name": "share", + "docs": [ + "The percentage share.", + "", + "The value is a percentage, not basis points." + ], + "type": "u8" + } + ] + } + }, + { + "name": "MetadataArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "name", + "type": "string" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "uri", + "type": "string" + }, + { + "name": "sellerFeeBasisPoints", + "type": "u16" + }, + { + "name": "primarySaleHappened", + "type": "bool" + }, + { + "name": "isMutable", + "type": "bool" + }, + { + "name": "editionNonce", + "type": { + "option": "u8" + } + }, + { + "name": "tokenStandard", + "type": { + "option": { + "defined": "TokenStandard" + } + } + }, + { + "name": "collection", + "type": { + "option": { + "defined": "Collection" + } + } + }, + { + "name": "uses", + "type": { + "option": { + "defined": "Uses" + } + } + }, + { + "name": "tokenProgramVersion", + "type": { + "defined": "TokenProgramVersion" + } + }, + { + "name": "creators", + "type": { + "vec": { + "defined": "Creator" + } + } + } + ] + } + }, + { + "name": "SolCnftFulfillBuyArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "assetId", + "type": "publicKey" + }, + { + "name": "root", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "nonce", + "type": "u64" + }, + { + "name": "index", + "type": "u32" + }, + { + "name": "minPaymentAmount", + "type": "u64" + }, + { + "name": "makerFeeBp", + "type": "i16" + }, + { + "name": "takerFeeBp", + "type": "i16" + }, + { + "name": "metadataArgs", + "type": { + "defined": "MetadataArgs" + } + } + ] + } + }, { "name": "SolMip1FulfillSellArgs", "type": { @@ -5691,6 +6280,57 @@ export const IDL: Mmm = { } ] } + }, + { + "name": "TokenStandard", + "type": { + "kind": "enum", + "variants": [ + { + "name": "NonFungible" + }, + { + "name": "FungibleAsset" + }, + { + "name": "Fungible" + }, + { + "name": "NonFungibleEdition" + } + ] + } + }, + { + "name": "UseMethod", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Burn" + }, + { + "name": "Multiple" + }, + { + "name": "Single" + } + ] + } + }, + { + "name": "TokenProgramVersion", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Original" + }, + { + "name": "Token2022" + } + ] + } } ], "errors": [ @@ -5868,6 +6508,16 @@ export const IDL: Mmm = { "code": 6034, "name": "UnsupportedAssetPlugin", "msg": "Unsupported asset plugin" + }, + { + "code": 6035, + "name": "InvalidCnftMetadata", + "msg": "Invalid cnft metadata" + }, + { + "code": 6036, + "name": "InvalidCnftMetadataArgs", + "msg": "Invalid cnft metadata args" } ] }; diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 4a0c3d5..b0f15c3 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -5,3 +5,4 @@ export * from './mmmClient'; export * from './pda'; export * from './price'; export * from './transferHookProvider'; +export * from './cnft'; diff --git a/sdk/src/mmmClient.ts b/sdk/src/mmmClient.ts index 83020a0..fee18d8 100644 --- a/sdk/src/mmmClient.ts +++ b/sdk/src/mmmClient.ts @@ -305,7 +305,8 @@ export class MMMClient { | ReturnType | ReturnType | ReturnType - | ReturnType; + | ReturnType + | ReturnType; const mintOrCoreAsset = await this.conn.getAccountInfo(assetMint); let { key: buysideSolEscrowAccount } = getMMMBuysideSolEscrowPDA( diff --git a/tests/mmm-cnft.spec.ts b/tests/mmm-cnft.spec.ts new file mode 100644 index 0000000..80ff88e --- /dev/null +++ b/tests/mmm-cnft.spec.ts @@ -0,0 +1,794 @@ +import * as anchor from '@project-serum/anchor'; +import { isSome, publicKey, sol, Umi } from '@metaplex-foundation/umi'; +import { + airdrop, + assertIsBetween, + createPool, + createUmi, + DEFAULT_TEST_SETUP_TREE_PARAMS, + getCreatorRoyaltiesArgs, + getEmptyAllowLists, + getPubKey, + PRICE_ERROR_RANGE, + setupTree, + SIGNATURE_FEE_LAMPORTS, + verifyOwnership, +} from './utils'; +import { + AllowlistKind, + convertToDecodeTokenProgramVersion, + convertToDecodeTokenStandardEnum, + convertToDecodeUseMethodEnum, + getBubblegumAuthorityPDA, + getByteArray, + getM2BuyerSharedEscrow, + getMMMBuysideSolEscrowPDA, + getMMMSellStatePDA, + getProofPath, + getSolFulfillBuyPrices, + IDL, + Mmm, + MMMProgramID, +} from '../sdk/src'; +import { + AccountMeta, + Keypair, + LAMPORTS_PER_SOL, + PublicKey, + SendTransactionError, + SystemProgram, +} from '@solana/web3.js'; +import { + findLeafAssetIdPda, + getMetadataArgsSerializer, + MetadataArgs, + MPL_BUBBLEGUM_PROGRAM_ID, + SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + SPL_NOOP_PROGRAM_ID, +} from '@metaplex-foundation/mpl-bubblegum'; +import { BN } from '@project-serum/anchor'; +import { ConcurrentMerkleTreeAccount } from '@solana/spl-account-compression'; +import { assert } from 'chai'; + +async function createCNftCollectionOffer( + program: anchor.Program, + poolArgs: Parameters[1], + sharedEscrow?: boolean, + sharedEscrowCount?: number, +) { + const poolData = await createPool(program, { + ...poolArgs, + reinvestFulfillBuy: false, + buysideCreatorRoyaltyBp: 10_000, + }); + + const poolKey = poolData.poolKey; + const { key: buysideSolEscrowAccount } = getMMMBuysideSolEscrowPDA( + program.programId, + poolData.poolKey, + ); + + await program.methods + .solDepositBuy({ paymentAmount: new anchor.BN(10 * LAMPORTS_PER_SOL) }) + .accountsStrict({ + owner: poolArgs.owner, + cosigner: poolArgs.cosigner?.publicKey ?? poolArgs.owner, + pool: poolKey, + buysideSolEscrowAccount, + systemProgram: SystemProgram.programId, + }) + .signers([...(poolArgs.cosigner ? [poolArgs.cosigner] : [])]) + .rpc({ skipPreflight: true }); + + if (sharedEscrow) { + const sharedEscrowAccount = getM2BuyerSharedEscrow(poolArgs.owner).key; + await program.methods + .setSharedEscrow({ + sharedEscrowCount: new anchor.BN(sharedEscrowCount || 2), + }) + .accountsStrict({ + owner: poolArgs.owner, + cosigner: poolArgs.cosigner?.publicKey ?? poolArgs.owner, + pool: poolKey, + sharedEscrowAccount, + }) + .signers([...(poolArgs.cosigner ? [poolArgs.cosigner] : [])]) + .rpc(); + } + + return { + buysideSolEscrowAccount, + poolData, + }; +} + +describe('cnft tests', () => { + const endpoint = 'http://localhost:8899'; + const buyer = new anchor.Wallet(Keypair.generate()); + const seller = new anchor.Wallet(Keypair.generate()); + const connection = new anchor.web3.Connection(endpoint, 'confirmed'); + let provider = new anchor.AnchorProvider(connection, buyer, { + commitment: 'confirmed', + }); + + let umi: Umi; + const program = new anchor.Program( + IDL, + MMMProgramID, + provider, + ) as anchor.Program; + const cosigner = Keypair.generate(); + + beforeAll(async () => { + umi = await createUmi(endpoint, sol(3)); + airdrop(connection, buyer.publicKey, 100); + airdrop(connection, seller.publicKey, 100); + airdrop(connection, cosigner.publicKey, 100); + }); + + it('cnft fulfill buy - happy path', async () => { + // 1. Create a tree. + const { + merkleTree, + sellerProof, //already truncated + leafIndex, + metadata, + getBubblegumTreeRef, + getCnftRef, + nft, + creatorRoyalties, + collectionKey, + } = await setupTree( + umi, + publicKey(seller.publicKey), + DEFAULT_TEST_SETUP_TREE_PARAMS, + ); + + // 2. Create an offer. + const { buysideSolEscrowAccount, poolData } = + await createCNftCollectionOffer(program, { + owner: new PublicKey(buyer.publicKey), + cosigner, + allowlists: [ + { + kind: AllowlistKind.mcc, + value: collectionKey, + }, + ...getEmptyAllowLists(5), + ], + }); + + const [treeAuthority, _] = getBubblegumAuthorityPDA( + new PublicKey(nft.tree.merkleTree), + ); + + const [assetId, bump] = findLeafAssetIdPda(umi, { + merkleTree, + leafIndex, + }); + + const { key: sellState } = getMMMSellStatePDA( + program.programId, + poolData.poolKey, + new PublicKey(assetId), + ); + + const spotPrice = 1; + const expectedBuyPrices = getSolFulfillBuyPrices({ + totalPriceLamports: spotPrice * LAMPORTS_PER_SOL, + lpFeeBp: 0, + takerFeeBp: 100, + metadataRoyaltyBp: 500, + buysideCreatorRoyaltyBp: 10_000, + makerFeeBp: 0, + }); + + const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( + connection, + nft.tree.merkleTree, + ); + + const proofPath: AccountMeta[] = getProofPath( + nft.nft.fullProof, + treeAccount.getCanopyDepth(), + ); + + const { + accounts: creatorAccounts, + creatorShares, + creatorVerified, + sellerFeeBasisPoints, + } = getCreatorRoyaltiesArgs(creatorRoyalties); + + // get balances before fulfill buy + const [ + buyerBefore, + sellerBefore, + buyerSolEscrowAccountBalanceBefore, + creator1Before, + creator2Before, + ] = await Promise.all([ + connection.getBalance(buyer.publicKey), + connection.getBalance(seller.publicKey), + connection.getBalance(buysideSolEscrowAccount), + connection.getBalance(creatorAccounts[0].pubkey), + connection.getBalance(creatorAccounts[1].pubkey), + ]); + + try { + const metadataSerializer = getMetadataArgsSerializer(); + const metadataArgs: MetadataArgs = metadataSerializer.deserialize( + metadataSerializer.serialize(metadata), + )[0]; + + const fulfillBuyTxnSig = await program.methods + .cnftFulfillBuy({ + assetId: new PublicKey(assetId), + root: getByteArray(nft.tree.root), + nonce: new BN(nft.tree.nonce), + index: nft.nft.nftIndex, + minPaymentAmount: new BN(expectedBuyPrices.sellerReceives), + makerFeeBp: 0, + takerFeeBp: 100, + metadataArgs: { + name: metadataArgs.name, + symbol: metadataArgs.symbol, + uri: metadataArgs.uri, + sellerFeeBasisPoints: metadataArgs.sellerFeeBasisPoints, + primarySaleHappened: metadataArgs.primarySaleHappened, + isMutable: metadataArgs.isMutable, + editionNonce: isSome(metadataArgs.editionNonce) + ? metadataArgs.editionNonce.value + : null, + tokenStandard: isSome(metadataArgs.tokenStandard) + ? convertToDecodeTokenStandardEnum( + metadataArgs.tokenStandard.value, + ) + : null, + collection: isSome(metadataArgs.collection) + ? { + verified: metadataArgs.collection.value.verified, + key: new PublicKey(metadataArgs.collection.value.key), + } + : null, // Ensure it's a struct or null + uses: isSome(metadataArgs.uses) + ? { + useMethod: convertToDecodeUseMethodEnum( + metadataArgs.uses.value.useMethod, + ), + remaining: metadataArgs.uses.value.remaining, + total: metadataArgs.uses.value.total, + } + : null, + tokenProgramVersion: convertToDecodeTokenProgramVersion( + metadataArgs.tokenProgramVersion, + ), + creators: metadataArgs.creators.map((c) => ({ + address: new PublicKey(c.address), + verified: c.verified, + share: c.share, + })), + }, + }) + .accountsStrict({ + payer: new PublicKey(seller.publicKey), + owner: buyer.publicKey, + cosigner: cosigner.publicKey, + referral: poolData.referral.publicKey, + pool: poolData.poolKey, + buysideSolEscrowAccount, + treeAuthority, + merkleTree: nft.tree.merkleTree, + logWrapper: SPL_NOOP_PROGRAM_ID, + bubblegumProgram: MPL_BUBBLEGUM_PROGRAM_ID, + compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + sellState, + systemProgram: SystemProgram.programId, + }) + .remainingAccounts([...creatorAccounts, ...proofPath]) + .signers([cosigner, seller.payer]) + // note: skipPreflight causes some weird error. + // so just surround in this try-catch to get the logs + .rpc(/* { skipPreflight: true } */); + } catch (e) { + if (e instanceof SendTransactionError) { + const err = e as SendTransactionError; + console.log( + `err.logs: ${JSON.stringify( + await err.getLogs(provider.connection), + null, + 2, + )}`, + ); + } + throw e; + } + + // Verify that buyer now owns the cNFT. + await verifyOwnership( + umi, + merkleTree, + publicKey(buyer.publicKey), + leafIndex, + metadata, + [], + ); + + // Get balances after fulfill buy + const [ + buyerAfter, + sellerAfter, + buyerSolEscrowAccountBalanceAfter, + creator1After, + creator2After, + ] = await Promise.all([ + connection.getBalance(buyer.publicKey), + connection.getBalance(seller.publicKey), + connection.getBalance(buysideSolEscrowAccount), + connection.getBalance(creatorAccounts[0].pubkey), + connection.getBalance(creatorAccounts[1].pubkey), + ]); + + const expectedTxFees = SIGNATURE_FEE_LAMPORTS * 3; // cosigner + seller + payer (due to provider is under buyer) + + assert.equal(buyerBefore, buyerAfter + expectedTxFees); + + assert.equal( + buyerSolEscrowAccountBalanceBefore, + buyerSolEscrowAccountBalanceAfter + spotPrice * LAMPORTS_PER_SOL, + ); + + assert.equal( + sellerAfter, + sellerBefore + + spotPrice * LAMPORTS_PER_SOL - + expectedBuyPrices.takerFeePaid.toNumber() - + expectedBuyPrices.royaltyPaid.toNumber(), + ); + + assertIsBetween( + creator1After, + creator1Before + + (expectedBuyPrices.royaltyPaid.toNumber() * + metadata.creators[0].share) / + 100, + PRICE_ERROR_RANGE, + ); + + assertIsBetween( + creator2After, + creator2Before + + (expectedBuyPrices.royaltyPaid.toNumber() * + metadata.creators[1].share) / + 100, + PRICE_ERROR_RANGE, + ); + }); + + it('cnft fulfill buy - incorrect collection fail allowlist check', async () => { + // 1. Create a tree. + const { + merkleTree, + sellerProof, //already truncated + leafIndex, + metadata, + getBubblegumTreeRef, + getCnftRef, + nft, + creatorRoyalties, + collectionKey, + } = await setupTree( + umi, + publicKey(seller.publicKey), + DEFAULT_TEST_SETUP_TREE_PARAMS, + ); + + // 2. Create an offer. + const { buysideSolEscrowAccount, poolData } = + await createCNftCollectionOffer(program, { + owner: new PublicKey(buyer.publicKey), + cosigner, + allowlists: [ + { + kind: AllowlistKind.mcc, + value: collectionKey, + }, + ...getEmptyAllowLists(5), + ], + }); + + const [treeAuthority, _] = getBubblegumAuthorityPDA( + new PublicKey(nft.tree.merkleTree), + ); + + const [assetId, bump] = findLeafAssetIdPda(umi, { + merkleTree, + leafIndex, + }); + + const { key: sellState } = getMMMSellStatePDA( + program.programId, + poolData.poolKey, + new PublicKey(assetId), + ); + + const spotPrice = 1; + const expectedBuyPrices = getSolFulfillBuyPrices({ + totalPriceLamports: spotPrice * LAMPORTS_PER_SOL, + lpFeeBp: 0, + takerFeeBp: 100, + metadataRoyaltyBp: 500, + buysideCreatorRoyaltyBp: 10_000, + makerFeeBp: 0, + }); + + const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( + connection, + nft.tree.merkleTree, + ); + + const proofPath: AccountMeta[] = getProofPath( + nft.nft.fullProof, + treeAccount.getCanopyDepth(), + ); + + const { + accounts: creatorAccounts, + creatorShares, + creatorVerified, + sellerFeeBasisPoints, + } = getCreatorRoyaltiesArgs(creatorRoyalties); + + // get balances before fulfill buy + const [ + buyerBefore, + sellerBefore, + buyerSolEscrowAccountBalanceBefore, + creator1Before, + creator2Before, + ] = await Promise.all([ + connection.getBalance(buyer.publicKey), + connection.getBalance(seller.publicKey), + connection.getBalance(buysideSolEscrowAccount), + connection.getBalance(creatorAccounts[0].pubkey), + connection.getBalance(creatorAccounts[1].pubkey), + ]); + + try { + const metadataSerializer = getMetadataArgsSerializer(); + const metadataArgs: MetadataArgs = metadataSerializer.deserialize( + metadataSerializer.serialize(metadata), + )[0]; + + const fulfillBuyTxnSig = await program.methods + .cnftFulfillBuy({ + assetId: new PublicKey(assetId), + root: getByteArray(nft.tree.root), + nonce: new BN(nft.tree.nonce), + index: nft.nft.nftIndex, + minPaymentAmount: new BN(expectedBuyPrices.sellerReceives), + makerFeeBp: 0, + takerFeeBp: 100, + metadataArgs: { + name: metadataArgs.name, + symbol: metadataArgs.symbol, + uri: metadataArgs.uri, + sellerFeeBasisPoints: metadataArgs.sellerFeeBasisPoints, + primarySaleHappened: metadataArgs.primarySaleHappened, + isMutable: metadataArgs.isMutable, + editionNonce: isSome(metadataArgs.editionNonce) + ? metadataArgs.editionNonce.value + : null, + tokenStandard: isSome(metadataArgs.tokenStandard) + ? convertToDecodeTokenStandardEnum( + metadataArgs.tokenStandard.value, + ) + : null, + collection: isSome(metadataArgs.collection) + ? { + verified: metadataArgs.collection.value.verified, + key: SystemProgram.programId, // !!!! WRONG COLLECTION + } + : null, // Ensure it's a struct or null + uses: isSome(metadataArgs.uses) + ? { + useMethod: convertToDecodeUseMethodEnum( + metadataArgs.uses.value.useMethod, + ), + remaining: metadataArgs.uses.value.remaining, + total: metadataArgs.uses.value.total, + } + : null, + tokenProgramVersion: convertToDecodeTokenProgramVersion( + metadataArgs.tokenProgramVersion, + ), + creators: metadataArgs.creators.map((c) => ({ + address: new PublicKey(c.address), + verified: c.verified, + share: c.share, + })), + }, + }) + .accountsStrict({ + payer: new PublicKey(seller.publicKey), + owner: buyer.publicKey, + cosigner: cosigner.publicKey, + referral: poolData.referral.publicKey, + pool: poolData.poolKey, + buysideSolEscrowAccount, + treeAuthority, + merkleTree: nft.tree.merkleTree, + logWrapper: SPL_NOOP_PROGRAM_ID, + bubblegumProgram: MPL_BUBBLEGUM_PROGRAM_ID, + compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + sellState, + systemProgram: SystemProgram.programId, + }) + .remainingAccounts([...creatorAccounts, ...proofPath]) + .signers([cosigner, seller.payer]) + // note: skipPreflight causes some weird error. + // so just surround in this try-catch to get the logs + .rpc(/* { skipPreflight: true } */); + } catch (e) { + expect(e).toBeInstanceOf(anchor.AnchorError); + const err = e as anchor.AnchorError; + + assert.strictEqual( + err.message, + 'AnchorError occurred. Error Code: InvalidAllowLists. Error Number: 6001. Error Message: invalid allowlists.', + ); + } + + // Verify that seller still owns the cNFT. + await verifyOwnership( + umi, + merkleTree, + publicKey(seller.publicKey), + leafIndex, + metadata, + [], + ); + + // Get balances after fulfill buy + const [ + buyerAfter, + sellerAfter, + buyerSolEscrowAccountBalanceAfter, + creator1After, + creator2After, + ] = await Promise.all([ + connection.getBalance(buyer.publicKey), + connection.getBalance(seller.publicKey), + connection.getBalance(buysideSolEscrowAccount), + connection.getBalance(creatorAccounts[0].pubkey), + connection.getBalance(creatorAccounts[1].pubkey), + ]); + + assert.equal(buyerBefore, buyerAfter); + + assert.equal( + buyerSolEscrowAccountBalanceBefore, + buyerSolEscrowAccountBalanceAfter, + ); + + assert.equal(sellerAfter, sellerBefore); + + assert.equal(creator1After, creator1Before); + + assert.equal(creator2After, creator2Before); + }); + + it('cnft fulfill buy - incorrect royalty fail bubblegum check', async () => { + // 1. Create a tree. + const { + merkleTree, + sellerProof, //already truncated + leafIndex, + metadata, + getBubblegumTreeRef, + getCnftRef, + nft, + creatorRoyalties, + collectionKey, + } = await setupTree( + umi, + publicKey(seller.publicKey), + DEFAULT_TEST_SETUP_TREE_PARAMS, + ); + + // 2. Create an offer. + const { buysideSolEscrowAccount, poolData } = + await createCNftCollectionOffer(program, { + owner: new PublicKey(buyer.publicKey), + cosigner, + allowlists: [ + { + kind: AllowlistKind.mcc, + value: collectionKey, + }, + ...getEmptyAllowLists(5), + ], + }); + + const [treeAuthority, _] = getBubblegumAuthorityPDA( + new PublicKey(nft.tree.merkleTree), + ); + + const [assetId, bump] = findLeafAssetIdPda(umi, { + merkleTree, + leafIndex, + }); + + const { key: sellState } = getMMMSellStatePDA( + program.programId, + poolData.poolKey, + new PublicKey(assetId), + ); + + const spotPrice = 1; + const expectedBuyPrices = getSolFulfillBuyPrices({ + totalPriceLamports: spotPrice * LAMPORTS_PER_SOL, + lpFeeBp: 0, + takerFeeBp: 100, + metadataRoyaltyBp: 500, + buysideCreatorRoyaltyBp: 10_000, + makerFeeBp: 0, + }); + + const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( + connection, + nft.tree.merkleTree, + ); + + const proofPath: AccountMeta[] = getProofPath( + nft.nft.fullProof, + treeAccount.getCanopyDepth(), + ); + + const { + accounts: creatorAccounts, + creatorShares, + creatorVerified, + sellerFeeBasisPoints, + } = getCreatorRoyaltiesArgs(creatorRoyalties); + + // get balances before fulfill buy + const [ + buyerBefore, + sellerBefore, + buyerSolEscrowAccountBalanceBefore, + creator1Before, + creator2Before, + ] = await Promise.all([ + connection.getBalance(buyer.publicKey), + connection.getBalance(seller.publicKey), + connection.getBalance(buysideSolEscrowAccount), + connection.getBalance(creatorAccounts[0].pubkey), + connection.getBalance(creatorAccounts[1].pubkey), + ]); + + try { + const metadataSerializer = getMetadataArgsSerializer(); + const metadataArgs: MetadataArgs = metadataSerializer.deserialize( + metadataSerializer.serialize(metadata), + )[0]; + + const fulfillBuyTxnSig = await program.methods + .cnftFulfillBuy({ + assetId: new PublicKey(assetId), + root: getByteArray(nft.tree.root), + nonce: new BN(nft.tree.nonce), + index: nft.nft.nftIndex, + minPaymentAmount: new BN(expectedBuyPrices.sellerReceives), + makerFeeBp: 0, + takerFeeBp: 100, + metadataArgs: { + name: metadataArgs.name, + symbol: metadataArgs.symbol, + uri: metadataArgs.uri, + sellerFeeBasisPoints: 0, // !!!! WRONG ROYALTY + primarySaleHappened: metadataArgs.primarySaleHappened, + isMutable: metadataArgs.isMutable, + editionNonce: isSome(metadataArgs.editionNonce) + ? metadataArgs.editionNonce.value + : null, + tokenStandard: isSome(metadataArgs.tokenStandard) + ? convertToDecodeTokenStandardEnum( + metadataArgs.tokenStandard.value, + ) + : null, + collection: isSome(metadataArgs.collection) + ? { + verified: metadataArgs.collection.value.verified, + key: new PublicKey(metadataArgs.collection.value.key), + } + : null, // Ensure it's a struct or null + uses: isSome(metadataArgs.uses) + ? { + useMethod: convertToDecodeUseMethodEnum( + metadataArgs.uses.value.useMethod, + ), + remaining: metadataArgs.uses.value.remaining, + total: metadataArgs.uses.value.total, + } + : null, + tokenProgramVersion: convertToDecodeTokenProgramVersion( + metadataArgs.tokenProgramVersion, + ), + creators: metadataArgs.creators.map((c) => ({ + address: new PublicKey(c.address), + verified: c.verified, + share: c.share, + })), + }, + }) + .accountsStrict({ + payer: new PublicKey(seller.publicKey), + owner: buyer.publicKey, + cosigner: cosigner.publicKey, + referral: poolData.referral.publicKey, + pool: poolData.poolKey, + buysideSolEscrowAccount, + treeAuthority, + merkleTree: nft.tree.merkleTree, + logWrapper: SPL_NOOP_PROGRAM_ID, + bubblegumProgram: MPL_BUBBLEGUM_PROGRAM_ID, + compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, + sellState, + systemProgram: SystemProgram.programId, + }) + .remainingAccounts([...creatorAccounts, ...proofPath]) + .signers([cosigner, seller.payer]) + // note: skipPreflight causes some weird error. + // so just surround in this try-catch to get the logs + .rpc(/* skipPreflight: true } */); + } catch (e) { + expect(e).toBeInstanceOf(SendTransactionError); + const err = e as SendTransactionError; + + assert.include( + err.message, + 'Invalid root recomputed from proof', + 'Error message should contain the expected substring', + ); + } + + // Verify that seller still owns the cNFT. + await verifyOwnership( + umi, + merkleTree, + publicKey(seller.publicKey), + leafIndex, + metadata, + [], + ); + + // Get balances after fulfill buy + const [ + buyerAfter, + sellerAfter, + buyerSolEscrowAccountBalanceAfter, + creator1After, + creator2After, + ] = await Promise.all([ + connection.getBalance(buyer.publicKey), + connection.getBalance(seller.publicKey), + connection.getBalance(buysideSolEscrowAccount), + connection.getBalance(creatorAccounts[0].pubkey), + connection.getBalance(creatorAccounts[1].pubkey), + ]); + + assert.equal(buyerBefore, buyerAfter); + + assert.equal( + buyerSolEscrowAccountBalanceBefore, + buyerSolEscrowAccountBalanceAfter, + ); + + assert.equal(sellerAfter, sellerBefore); + + assert.equal(creator1After, creator1Before); + + assert.equal(creator2After, creator2Before); + }); +}); diff --git a/tests/mmm-ocp.spec.ts b/tests/mmm-ocp.spec.ts index c9b2714..7d57480 100644 --- a/tests/mmm-ocp.spec.ts +++ b/tests/mmm-ocp.spec.ts @@ -42,7 +42,7 @@ import { PROGRAM_ID as OCP_PROGRAM_ID, } from '@magiceden-oss/open_creator_protocol'; -describe('mmm-ocp', () => { +describe.skip('mmm-ocp', () => { const { connection } = anchor.AnchorProvider.env(); const wallet = new anchor.Wallet(Keypair.generate()); const provider = new anchor.AnchorProvider(connection, wallet, { diff --git a/tests/utils/cnft.ts b/tests/utils/cnft.ts new file mode 100644 index 0000000..4e0e629 --- /dev/null +++ b/tests/utils/cnft.ts @@ -0,0 +1,422 @@ +import { + Creator, + MetadataArgsArgs, + mplBubblegum, + createTree as baseCreateTree, + mintV1 as baseMintV1, + fetchMerkleTree, + findLeafAssetIdPda, + hashLeaf, + hashMetadataCreators, + verifyCreator, + getCurrentRoot, + hashMetadataData, + hash, + getMetadataArgsSerializer, + getMerkleProof, + verifyLeaf, + MerkleTree, + mintToCollectionV1, +} from '@metaplex-foundation/mpl-bubblegum'; +import { + createNft, + mplTokenMetadata, +} from '@metaplex-foundation/mpl-token-metadata'; +import { + Context, + generateSigner, + isOption, + isSome, + KeypairSigner, + none, + Pda, + percentAmount, + PublicKey, + publicKey, + sol, + SolAmount, + Umi, + PublicKey as UmiPublicKey, +} from '@metaplex-foundation/umi'; +import { createUmi as baseCreateUmi } from '@metaplex-foundation/umi-bundle-tests'; +import { BubblegumTreeRef, CNFT, CreatorRoyaltyConfig } from '../../sdk/src'; +import { + AccountMeta, + Connection, + PublicKey as Web3PubKey, +} from '@solana/web3.js'; +import { dasApi } from '@metaplex-foundation/digital-asset-standard-api'; + +export const ME_TREASURY = new Web3PubKey( + 'rFqFJ9g7TGBD8Ed7TPDnvGKZ5pWLPDyxLcvcH2eRCtt', +); + +export const createUmi = async (endpoint?: string, airdropAmount?: SolAmount) => + (await baseCreateUmi(endpoint, { commitment: 'confirmed' }, airdropAmount)) + .use(mplTokenMetadata()) + .use(mplBubblegum()) + .use(dasApi()); + +export const createTree = async ( + context: Context, + input: Partial[1]> = {}, +): Promise => { + const merkleTree = generateSigner(context); + const builder = await baseCreateTree(context, { + merkleTree, + maxDepth: input.maxDepth ?? 14, + maxBufferSize: input.maxBufferSize ?? 64, + canopyDepth: input.canopyDepth, + }); + await builder.sendAndConfirm(context); + return merkleTree.publicKey; +}; + +export async function getCreatorPair(umi: Umi): Promise { + const creator1 = generateSigner(umi); + const creator2 = generateSigner(umi); + await umi.rpc.airdrop(creator1.publicKey, sol(1)); + await umi.rpc.airdrop(creator2.publicKey, sol(1)); + return [creator1, creator2]; +} + +export async function initUnverifiedCreatorsArray( + creators: KeypairSigner[], +): Promise { + return [ + { + address: creators[0].publicKey, + verified: false, + share: 60, + }, + { + address: creators[1].publicKey, + verified: false, + share: 40, + }, + ]; +} + +export const mint = async ( + context: Context, + input: Omit[1], 'metadata' | 'leafOwner'> & { + leafIndex?: number | bigint; + metadata?: Partial[1]['metadata']>; + leafOwner?: PublicKey; + creators?: Parameters[1]['metadata']['creators']; + collection?: Parameters[1]['metadata']['collection']; + }, +): Promise<{ + metadata: MetadataArgsArgs; + assetId: Pda; + leaf: PublicKey; + leafIndex: number; + creatorsHash: PublicKey; +}> => { + const merkleTree = publicKey(input.merkleTree, false); + const leafOwner = input.leafOwner ?? context.identity.publicKey; + const leafIndex = Number( + input.leafIndex ?? + (await fetchMerkleTree(context, merkleTree)).tree.activeIndex, + ); + const leafCreators = input.creators ?? []; + const collection = input.collection + ? isOption(input.collection) + ? isSome(input.collection) + ? input.collection.value + : undefined + : input.collection + : undefined; + + const metadata: MetadataArgsArgs = { + name: 'My NFT', + uri: 'https://example.com/my-nft.json', + sellerFeeBasisPoints: 500, // 5% + collection: collection + ? { + key: collection.key, + verified: collection.verified, + } + : none(), + creators: leafCreators, + ...input.metadata, + }; + + if (collection) { + await mintToCollectionV1(context, { + ...input, + leafOwner, + collectionMint: collection.key, + metadata, + }).sendAndConfirm(context); + } else { + await baseMintV1(context, { + ...input, + metadata, + leafOwner, + }).sendAndConfirm(context); + } + + return { + metadata, + assetId: findLeafAssetIdPda(context, { merkleTree, leafIndex }), + leafIndex, + leaf: publicKey( + hashLeaf(context, { + merkleTree, + owner: publicKey(leafOwner, false), + delegate: publicKey(input.leafDelegate ?? leafOwner, false), + leafIndex, + metadata, + }), + ), + creatorsHash: publicKey(hashMetadataCreators(leafCreators)), + }; +}; + +export function hashMetadataArgsArgs(metadata: MetadataArgsArgs): Uint8Array { + return hash(getMetadataArgsSerializer().serialize(metadata)); +} + +export function bufferToArray(buffer: Buffer): number[] { + const nums: number[] = []; + for (let i = 0; i < buffer.length; i++) { + nums.push(buffer[i]); + } + return nums; +} + +export function getPubKey(umiKey: UmiPublicKey) { + return new Web3PubKey(umiKey.toString()); +} + +/** + * Verifies that the expectedOwner owns the leaf at the given leafIndex. + * @param umi + * @param merkleTree + * @param expectedOwner + * @param leafIndex + * @param metadata current metadata of the leaf + * @param preMints + * @returns the **truncated** proof used for verification. + */ +export async function verifyOwnership( + umi: Umi, + merkleTree: UmiPublicKey, + expectedOwner: UmiPublicKey, + leafIndex: number, + metadata: MetadataArgsArgs, + preMints: { leaf: UmiPublicKey }[], +): Promise<{ currentProof: UmiPublicKey[] }> { + const escrowedLeaf = hashLeaf(umi, { + merkleTree, + owner: expectedOwner, + leafIndex, + metadata, + }); + const merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + + const currentProof = getTruncatedMerkleProof( + getCanopyDepth(merkleTreeAccount), + [...preMints.map((m) => m.leaf), publicKey(escrowedLeaf)], + merkleTreeAccount.treeHeader.maxDepth, + publicKey(escrowedLeaf), + ); + + const { result } = await verifyLeaf(umi, { + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + leaf: escrowedLeaf, + index: leafIndex, + proof: currentProof, + }).sendAndConfirm(umi); + + return { currentProof }; +} + +export const DEFAULT_TEST_SETUP_TREE_PARAMS = { + maxDepth: 14, + maxBufferSize: 64, + canopyDepth: 9, +}; + +export async function setupTree( + umi: Umi, + seller: PublicKey, + treeParams: { + maxDepth: number; + maxBufferSize: number; + canopyDepth: number; + }, +) { + const merkleTree = await createTree(umi, { + maxDepth: treeParams.maxDepth, + maxBufferSize: treeParams.maxBufferSize, + canopyDepth: treeParams.canopyDepth, + }); + + const creatorSigners = await getCreatorPair(umi); + const unverifiedCreators = await initUnverifiedCreatorsArray(creatorSigners); + + const collectionMint = generateSigner(umi); + await createNft(umi, { + mint: collectionMint, + name: 'My Collection', + uri: 'https://example.com/my-collection.json', + sellerFeeBasisPoints: percentAmount(5), // % + isCollection: true, + }).sendAndConfirm(umi); + + const { metadata, leaf, leafIndex, creatorsHash, assetId } = await mint(umi, { + merkleTree, + leafOwner: seller, + creators: unverifiedCreators, + collection: { + key: publicKey(collectionMint), + verified: true, + }, + }); + + const verifyCreatorProofTruncated = getTruncatedMerkleProof( + treeParams.canopyDepth, + [leaf], + treeParams.maxDepth, + leaf, + ); + // Verify creator A + + await verifyCreator(umi, { + leafOwner: seller, + creator: creatorSigners[0], + merkleTree, + root: getCurrentRoot((await fetchMerkleTree(umi, merkleTree)).tree), + nonce: leafIndex, + index: leafIndex, + metadata, + proof: verifyCreatorProofTruncated, + }).sendAndConfirm(umi); + + const updatedMetadata = { + ...metadata, + creators: [ + { address: creatorSigners[0].publicKey, verified: true, share: 60 }, + { address: creatorSigners[1].publicKey, verified: false, share: 40 }, + ], + }; + const leafDataPostVerification = hashLeaf(umi, { + merkleTree, + owner: seller, + leafIndex, + metadata: updatedMetadata, + }); + const updatedLeaf = publicKey(leafDataPostVerification); + // Make sure that the leaf is updated with the verifie creator. + const merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + expect(merkleTreeAccount.tree.rightMostPath.leaf).toEqual(updatedLeaf); + + const getBubblegumTreeRef = async () => ({ + merkleTree: getPubKey(merkleTree), + root: new Web3PubKey( + getCurrentRoot((await fetchMerkleTree(umi, merkleTree)).tree), + ), + dataHash: new Web3PubKey(hashMetadataData(updatedMetadata)), + metadataHash: bufferToArray( + Buffer.from(hashMetadataArgsArgs(updatedMetadata)), + ), + creatorHash: new Web3PubKey(hashMetadataCreators(updatedMetadata.creators)), + nonce: leafIndex, + }); + + const getCnftRef = (proof: UmiPublicKey[]) => ({ + nftIndex: leafIndex, + fullProof: proof.map(getPubKey), + }); + + const fullProof = getMerkleProof( + [updatedLeaf], + treeParams.maxDepth, + updatedLeaf, + ); + + // Verify that seller owns the cNFT. + const { currentProof: sellerProof } = await verifyOwnership( + umi, + merkleTree, + seller, + leafIndex, + updatedMetadata, + [], + ); + + return { + merkleTree, + leaf, + sellerProof, + leafIndex, + metadata: updatedMetadata, + creatorsHash, + creators: updatedMetadata.creators, + getBubblegumTreeRef, + getCnftRef, + nft: { + tree: await getBubblegumTreeRef(), + nft: getCnftRef(fullProof), + }, + creatorRoyalties: { + creators: updatedMetadata.creators.map((c) => ({ + ...c, + address: getPubKey(c.address), + })), + sellerFeeBasisPoints: 500, // 5% royalty + }, + collectionKey: new Web3PubKey(collectionMint.publicKey), + }; +} + +export function getCreatorRoyaltiesArgs( + royaltySelection: CreatorRoyaltyConfig, +): { + accounts: AccountMeta[]; + creatorShares: number[]; + creatorVerified: boolean[]; + sellerFeeBasisPoints: number; +} { + const creatorShares: number[] = []; + const creatorVerified: boolean[] = []; + const accounts: AccountMeta[] = royaltySelection.creators.map((creator) => { + creatorShares.push(creator.share); + creatorVerified.push(creator.verified); + return { + pubkey: creator.address, + isSigner: false, + isWritable: true, // so that we can pay creator fees + }; + }); + + return { + accounts, + creatorShares, + creatorVerified, + sellerFeeBasisPoints: royaltySelection.sellerFeeBasisPoints, + }; +} + +export function truncateMerkleProof(proof: PublicKey[], canopyDepth: number) { + return proof.slice(0, canopyDepth === 0 ? undefined : -canopyDepth); +} + +export function getTruncatedMerkleProof( + canopyDepth: number, + leaves: PublicKey[], + maxDepth: number, + leaf: PublicKey, + index?: number | undefined, +) { + const proof = getMerkleProof(leaves, maxDepth, leaf, index); + return truncateMerkleProof(proof, canopyDepth); +} + +// Utility method to calculate the canopy depth from a metaplex MerkleTree type +export function getCanopyDepth(merkleTreeAccount: MerkleTree) { + return Math.log2(merkleTreeAccount.canopy.length + 2) - 1; +} diff --git a/tests/utils/index.ts b/tests/utils/index.ts index 2db438e..60aafc7 100644 --- a/tests/utils/index.ts +++ b/tests/utils/index.ts @@ -5,3 +5,4 @@ export * from './mmm'; export * from './nfts'; export * from './ocp'; export * from './mpl_core'; +export * from './cnft'; diff --git a/yarn.lock b/yarn.lock index 81a1a4b..8e8f07d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1066,6 +1066,13 @@ resolved "https://registry.yarnpkg.com/@metaplex-foundation/cusper/-/cusper-0.0.2.tgz#dc2032a452d6c269e25f016aa4dd63600e2af975" integrity sha512-S9RulC2fFCFOQraz61bij+5YCHhSO9llJegK8c8Y6731fSi6snUSQJdCUqYS8AIgR0TKbQvdvgSyIIdbDFZbBA== +"@metaplex-foundation/digital-asset-standard-api@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/digital-asset-standard-api/-/digital-asset-standard-api-1.0.4.tgz#a321ed36e8aa5fc988faea8e38288fdc8690dca7" + integrity sha512-YSYyMnIoKNykDZTXsSCeiIOJ7NT5Ke2pzghXDsinRwHvwIZWv+zY5kJQBvTglAzYlt/GaI+noAhUZXXmSbp07A== + dependencies: + package.json "^2.0.1" + "@metaplex-foundation/js@^0.16.1": version "0.16.1" resolved "https://registry.yarnpkg.com/@metaplex-foundation/js/-/js-0.16.1.tgz#02bec9357c48ca1d07c941534ce38c369a9bb301" @@ -1177,6 +1184,17 @@ bn.js "^5.2.0" js-sha3 "^0.8.0" +"@metaplex-foundation/mpl-bubblegum@^4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-bubblegum/-/mpl-bubblegum-4.2.1.tgz#248969b2a7e8d892113f4e2fc53608b4117cb67f" + integrity sha512-r9kHrVmkzJApbXwd7cmJyO0mAV3qsJaTjv5ks6PUT1Bzjj9QCvlJYg2UYQJLUTcrY5TjE9wXLpwUqNgllXH/Cw== + dependencies: + "@metaplex-foundation/digital-asset-standard-api" "^1.0.4" + "@metaplex-foundation/mpl-token-metadata" "3.2.1" + "@metaplex-foundation/mpl-toolbox" "^0.9.0" + "@noble/hashes" "^1.3.1" + merkletreejs "^0.3.9" + "@metaplex-foundation/mpl-candy-guard@^0.3.0": version "0.3.2" resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-candy-guard/-/mpl-candy-guard-0.3.2.tgz#426e89793676b42e9bbb5e523303fba36ccd5281" @@ -1271,6 +1289,13 @@ "@solana/web3.js" "^1.66.2" bn.js "^5.2.1" +"@metaplex-foundation/mpl-token-metadata@3.2.1", "@metaplex-foundation/mpl-token-metadata@^3.1.2": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-token-metadata/-/mpl-token-metadata-3.2.1.tgz#d424e378a1ee441a6431d2641d66873118d6dc67" + integrity sha512-26W1NhQwDWmLOg/pBRYut7x/vEs/5kFS2sWVEY5/X0f2jJOLhnd4NaZQcq+5u+XZsXvm1jq2AtrRGPNK43oqWQ== + dependencies: + "@metaplex-foundation/mpl-toolbox" "^0.9.4" + "@metaplex-foundation/mpl-token-metadata@^2.11.0", "@metaplex-foundation/mpl-token-metadata@^2.2.2", "@metaplex-foundation/mpl-token-metadata@^2.5.1", "@metaplex-foundation/mpl-token-metadata@^2.5.2", "@metaplex-foundation/mpl-token-metadata@^2.8.6": version "2.13.0" resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-token-metadata/-/mpl-token-metadata-2.13.0.tgz#ea498190ad4ed1d4c0b8218a72d03bd17a883d11" @@ -1284,14 +1309,7 @@ bn.js "^5.2.0" debug "^4.3.4" -"@metaplex-foundation/mpl-token-metadata@^3.1.2": - version "3.2.1" - resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-token-metadata/-/mpl-token-metadata-3.2.1.tgz#d424e378a1ee441a6431d2641d66873118d6dc67" - integrity sha512-26W1NhQwDWmLOg/pBRYut7x/vEs/5kFS2sWVEY5/X0f2jJOLhnd4NaZQcq+5u+XZsXvm1jq2AtrRGPNK43oqWQ== - dependencies: - "@metaplex-foundation/mpl-toolbox" "^0.9.4" - -"@metaplex-foundation/mpl-toolbox@^0.9.4": +"@metaplex-foundation/mpl-toolbox@^0.9.0", "@metaplex-foundation/mpl-toolbox@^0.9.4": version "0.9.4" resolved "https://registry.yarnpkg.com/@metaplex-foundation/mpl-toolbox/-/mpl-toolbox-0.9.4.tgz#2211b2f726b1e5745c03908d26fd8ee580838b6f" integrity sha512-fd6JxfoLbj/MM8FG2x91KYVy1U6AjBQw4qjt7+Da3trzQaWnSaYHDcYRG/53xqfvZ9qofY1T2t53GXPlD87lnQ== @@ -1466,7 +1484,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@1.5.0", "@noble/hashes@^1.1.3", "@noble/hashes@^1.4.0": +"@noble/hashes@1.5.0", "@noble/hashes@^1.1.3", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== @@ -1822,6 +1840,18 @@ "@solana/codecs-strings" "2.0.0-rc.1" "@solana/errors" "2.0.0-rc.1" +"@solana/spl-account-compression@0.1.8": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@solana/spl-account-compression/-/spl-account-compression-0.1.8.tgz#0c1fd052befddd90c2e8704b0b685761799d4bae" + integrity sha512-vsvsx358pVFPtyNd8zIZy0lezR0NuvOykQ29Zq+8oto+kHfTXMGXXQ1tKHUYke6XkINIWLFVg/jDi+1D9RYaqQ== + dependencies: + "@metaplex-foundation/beet" "^0.7.1" + "@metaplex-foundation/beet-solana" "^0.4.0" + bn.js "^5.2.1" + borsh "^0.7.0" + js-sha3 "^0.8.0" + typescript-collections "^1.3.3" + "@solana/spl-account-compression@^0.1.4", "@solana/spl-account-compression@^0.1.8": version "0.1.10" resolved "https://registry.yarnpkg.com/@solana/spl-account-compression/-/spl-account-compression-0.1.10.tgz#b3135ce89349d6090832b3b1d89095badd57e969" @@ -2160,6 +2190,13 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +abs@^1.2.1: + version "1.3.14" + resolved "https://registry.yarnpkg.com/abs/-/abs-1.3.14.tgz#7b078d5d0735082d5bfb23d45c2d9f440a5c2222" + integrity sha512-PrS26IzwKLWwuURpiKl8wRmJ2KdR/azaVrLEBWG/TALwT20Y7qjtYp1qcMLHA4206hBHY5phv3w4pjf9NPv4Vw== + dependencies: + ul "^5.0.0" + aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" @@ -2699,6 +2736,11 @@ capability@^0.2.5: resolved "https://registry.yarnpkg.com/capability/-/capability-0.2.5.tgz#51ad87353f1936ffd77f2f21c74633a4dea88801" integrity sha512-rsJZYVCgXd08sPqwmaIqjAd5SUTfonV0z/gDJ8D6cN8wQphky1kkAYEqQ+hmDxTw7UihvBfjUVUSY+DBEe44jg== +capture-stack-trace@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz#1c43f6b059d4249e7f3f8724f15f048b927d3a8a" + integrity sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w== + chai@^4.3.4: version "4.5.0" resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" @@ -2876,6 +2918,18 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +create-error-class@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + integrity sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw== + dependencies: + capture-stack-trace "^1.0.0" + create-hash@^1.1.0, create-hash@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -2938,6 +2992,11 @@ crypto-js@^3.1.9-1: resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" integrity sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q== +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + csv-generate@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-4.4.1.tgz#729781ace8d1b92f6bfb407d1ab9548728c55681" @@ -2989,6 +3048,11 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -3001,6 +3065,13 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +deffy@^2.2.1, deffy@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/deffy/-/deffy-2.2.4.tgz#53c1b5f59b58a58150b1c9de5529229875c4cc17" + integrity sha512-pLc9lsbsWjr6RxmJ2OLyvm+9l4j1yK69h+TML/gUit/t3vTijpkNGh8LioaJYTGO7F25m6HZndADcUOo2PsiUg== + dependencies: + typpy "^2.0.0" + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -3069,6 +3140,13 @@ dotenv@10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +duplexer2@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== + dependencies: + readable-stream "^2.0.2" + duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -3122,7 +3200,14 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -error-ex@^1.3.1: +err@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/err/-/err-1.1.1.tgz#eb928e2e11a316648f782833d0f97258ba43c2f8" + integrity sha512-N97Ybd2jJHVQ+Ft3Q5+C2gM3kgygkdeQmEqbN2z15UTVyyEsIwLA1VK39O1DHEJhXbwIFcJLqm6iARNhFANcQA== + dependencies: + typpy "^2.2.0" + +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== @@ -3296,6 +3381,14 @@ eventemitter3@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== +exec-limiter@^3.0.0: + version "3.2.13" + resolved "https://registry.yarnpkg.com/exec-limiter/-/exec-limiter-3.2.13.tgz#5f30f5990b9b10908512394b9b997ed066a574d0" + integrity sha512-86Ri699bwiHZVBzTzNj8gspqAhCPchg70zPVWIh3qzUOA1pUMcb272Em3LPk8AE0mS95B9yMJhtqF8vFJAn0dA== + dependencies: + limit-it "^3.0.0" + typpy "^2.1.0" + execa@5.1.1, execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -3477,6 +3570,13 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +function.name@^1.0.3: + version "1.0.13" + resolved "https://registry.yarnpkg.com/function.name/-/function.name-1.0.13.tgz#eef045abc4b5ff4e3e9d001a53ce14e090c971c6" + integrity sha512-mVrqdoy5npWZyoXl4DxCeuVF6delDcQjVS9aPdvLYlBxtMTZDR2B5GVEQEoM1jJyspCqg3C0v4ABkLE7tp9xFA== + dependencies: + noop6 "^1.0.1" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3513,6 +3613,43 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +git-package-json@^1.4.0: + version "1.4.10" + resolved "https://registry.yarnpkg.com/git-package-json/-/git-package-json-1.4.10.tgz#d85f10d23c371295229dbebc94d5194b0fefda59" + integrity sha512-DRAcvbzd2SxGK7w8OgYfvKqhFliT5keX0lmSmVdgScgf1kkl5tbbo7Pam6uYoCa1liOiipKxQZG8quCtGWl/fA== + dependencies: + deffy "^2.2.1" + err "^1.1.1" + gry "^5.0.0" + normalize-package-data "^2.3.5" + oargv "^3.4.1" + one-by-one "^3.1.0" + r-json "^1.2.1" + r-package-json "^1.0.0" + tmp "0.0.28" + +git-source@^1.1.0: + version "1.1.10" + resolved "https://registry.yarnpkg.com/git-source/-/git-source-1.1.10.tgz#8da8b8a3819f8557dbabffca44805612ad466a6c" + integrity sha512-XZZ7ZgnLL35oLgM/xjnLYgtlKlxJG0FohC1kWDvGkU7s1VKGXK0pFF/g1itQEwQ3D+uTQzBnzPi8XbqOv7Wc1Q== + dependencies: + git-url-parse "^5.0.1" + +git-up@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-1.2.1.tgz#264480a006b1d84261ac1fe09a3a5169c57ea19d" + integrity sha512-SRVN3rOLACva8imc7BFrB6ts5iISWKH1/h/1Z+JZYoUI7UVQM7gQqk4M2yxUENbq2jUUT09NEND5xwP1i7Ktlw== + dependencies: + is-ssh "^1.0.0" + parse-url "^1.0.0" + +git-url-parse@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-5.0.1.tgz#fe3d79c6746ae05048cfa508c81e79dddbba3843" + integrity sha512-4uSiOgrryNEMBX+gTWogenYRUh2j1D+95STTSEF2RCTgLkfJikl8c7BGr0Bn274hwuxTsbS2/FQ5pVS9FoXegQ== + dependencies: + git-up "^1.0.0" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3556,11 +3693,42 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +got@^5.0.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" + integrity sha512-1qd54GLxvVgzuidFmw9ze9umxS3rzhdBH6Wt6BTYrTQUXTN01vGGYXwzLzYLowNx8HBH3/c7kRyvx90fh13i7Q== + dependencies: + create-error-class "^3.0.1" + duplexer2 "^0.1.4" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + node-status-codes "^1.0.0" + object-assign "^4.0.1" + parse-json "^2.1.0" + pinkie-promise "^2.0.0" + read-all-stream "^3.0.0" + readable-stream "^2.0.5" + timed-out "^3.0.0" + unzip-response "^1.0.2" + url-parse-lax "^1.0.0" + graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +gry@^5.0.0: + version "5.0.8" + resolved "https://registry.yarnpkg.com/gry/-/gry-5.0.8.tgz#73c0d246fba4ce6e7924779670088a7d67222e7a" + integrity sha512-meq9ZjYVpLzZh3ojhTg7IMad9grGsx6rUUKHLqPnhLXzJkRQvEL2U3tQpS5/WentYTtHtxkT3Ew/mb10D6F6/g== + dependencies: + abs "^1.2.1" + exec-limiter "^3.0.0" + one-by-one "^3.0.0" + ul "^5.0.0" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -3633,6 +3801,11 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -3699,11 +3872,16 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inquirer@^8.2.0: version "8.2.6" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" @@ -3802,6 +3980,28 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + integrity sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw== + +is-retry-allowed@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" + integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== + +is-ssh@^1.0.0, is-ssh@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" + integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== + dependencies: + protocols "^2.0.1" + +is-stream@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -3819,6 +4019,11 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3882,6 +4087,11 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterate-object@^1.1.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/iterate-object/-/iterate-object-1.3.4.tgz#fa50b1d9e58e340a7dd6b4c98c8a5e182e790096" + integrity sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw== + jake@^10.8.5: version "10.9.2" resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" @@ -4363,6 +4573,13 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +limit-it@^3.0.0: + version "3.2.10" + resolved "https://registry.yarnpkg.com/limit-it/-/limit-it-3.2.10.tgz#a0e12007c9e7aeb46296309bca39bd7646d82887" + integrity sha512-T0NK99pHnkimldr1WUqvbGV1oWDku/xC9J/OqzJFsV1jeOS6Bwl8W7vkeQIBqwiON9dTALws+rX/XPMQqWerDQ== + dependencies: + typpy "^2.0.0" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -4417,6 +4634,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -4478,6 +4700,17 @@ merkletreejs@^0.2.32: treeify "^1.1.0" web3-utils "^1.3.4" +merkletreejs@^0.3.9: + version "0.3.11" + resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.3.11.tgz#e0de05c3ca1fd368de05a12cb8efb954ef6fc04f" + integrity sha512-LJKTl4iVNTndhL+3Uz/tfkjD0klIWsHlUzgtuNnNrsf7bAlXR30m+xYB7lHr5Z/l6e/yAIsr26Dabx6Buo4VGQ== + dependencies: + bignumber.js "^9.0.1" + buffer-reverse "^1.0.1" + crypto-js "^4.2.0" + treeify "^1.1.0" + web3-utils "^1.3.4" + micro-ftch@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" @@ -4537,7 +4770,7 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.7: +minimist@^1.2.0, minimist@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -4646,6 +4879,26 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +node-status-codes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" + integrity sha512-1cBMgRxdMWE8KeWCqk2RIOrvUb0XCwYfEsY5/y2NlXyq4Y/RumnOZvTj4Nbr77+Vb2C+kyBoRTdkNOS8L3d/aQ== + +noop6@^1.0.1: + version "1.0.9" + resolved "https://registry.yarnpkg.com/noop6/-/noop6-1.0.9.tgz#8749944c15c09f2cd2d562ac24f5a8341762a950" + integrity sha512-DB3Hwyd89dPr5HqEPg3YHjzvwh/mCqizC1zZ8vyofqc+TQRyPDnT4wgXXbLGF4z9YAzwwTLi8pNLhGqcbSjgkA== + +normalize-package-data@^2.3.5: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -4673,6 +4926,26 @@ o3@^1.0.3: dependencies: capability "^0.2.5" +oargv@^3.4.1: + version "3.4.10" + resolved "https://registry.yarnpkg.com/oargv/-/oargv-3.4.10.tgz#facf418534c1d5238a6998246419faa4c930cfe5" + integrity sha512-SXaMANv9sr7S/dP0vj0+Ybipa47UE1ntTWQ2rpPRhC6Bsvfl+Jg03Xif7jfL0sWKOYWK8oPjcZ5eJ82t8AP/8g== + dependencies: + iterate-object "^1.1.0" + ul "^5.0.0" + +obj-def@^1.0.0: + version "1.0.9" + resolved "https://registry.yarnpkg.com/obj-def/-/obj-def-1.0.9.tgz#2e63708e91e425f11e60928db1d2d8549f6a95fa" + integrity sha512-bQ4ya3VYD6FAA1+s6mEhaURRHSmw4+sKaXE6UyXZ1XDYc5D+c7look25dFdydmLd18epUegh398gdDkMUZI9xg== + dependencies: + deffy "^2.2.2" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + object-is@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" @@ -4716,6 +4989,14 @@ once@^1.3.0, once@^1.4.0: dependencies: wrappy "1" +one-by-one@^3.0.0, one-by-one@^3.1.0: + version "3.2.8" + resolved "https://registry.yarnpkg.com/one-by-one/-/one-by-one-3.2.8.tgz#9254afa210e816bbb298e2addf4c62927d8f5e6d" + integrity sha512-HR/pSzZdm46Xqj58K+Bu64kMbSTw8/u77AwWvV+rprO/OsuR++pPlkUJn+SmwqBGRgHKwSKQ974V3uls7crIeQ== + dependencies: + obj-def "^1.0.0" + sliced "^1.0.1" + onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" @@ -4738,7 +5019,7 @@ ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" -os-tmpdir@~1.0.2: +os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== @@ -4769,11 +5050,44 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-path@^1.0.0: + version "1.0.9" + resolved "https://registry.yarnpkg.com/package-json-path/-/package-json-path-1.0.9.tgz#6caa627239b7b7fdccd4dba1026d86634397efde" + integrity sha512-uNu7f6Ef7tQHZRnkyVnCtzdSYVN9uBtge/sG7wzcUaawFWkPYUq67iXxRGrQSg/q0tzxIB8jSyIYUKjG2Jn//A== + dependencies: + abs "^1.2.1" + +package-json@^2.3.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" + integrity sha512-PRg65iXMTt/uK8Rfh5zvzkUbfAPitF17YaCY+IbHsYgksiLvtzWWTUildHth3mVaZ7871OJ7gtP4LBRBlmAdXg== + dependencies: + got "^5.0.0" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + +package.json@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/package.json/-/package.json-2.0.1.tgz#f886059d2a49ed076e64883695d73b2b46d21d6d" + integrity sha512-pSxZ6XR5yEawRN2ekxx9IKgPN5uNAYco7MCPxtBEWMKO3UKWa1X2CtQMzMgloeGj2g2o6cue3Sb5iPkByIJqlw== + dependencies: + git-package-json "^1.4.0" + git-source "^1.1.0" + package-json "^2.3.1" + pako@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== +parse-json@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== + dependencies: + error-ex "^1.2.0" + parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -4784,6 +5098,14 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-url@^1.0.0: + version "1.3.11" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-1.3.11.tgz#57c15428ab8a892b1f43869645c711d0e144b554" + integrity sha512-1wj9nkgH/5EboDxLwaTMGJh3oH3f+Gue+aGdh631oCqoSBpokzmMmOldvOeBPtB8GJBYJbaF93KPzlkU+Y1ksg== + dependencies: + is-ssh "^1.3.0" + protocols "^1.4.0" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -4842,6 +5164,18 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== + pirates@^4.0.4: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" @@ -4859,6 +5193,11 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -4880,6 +5219,11 @@ pretty-format@^29.0.0, pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -4893,6 +5237,16 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +protocols@^1.4.0: + version "1.4.8" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" + integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== + +protocols@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" + integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== + ps-tree@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd" @@ -4910,6 +5264,21 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +r-json@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/r-json/-/r-json-1.3.0.tgz#a0b9df3455183a0d8c2de733b047839e462381c9" + integrity sha512-xesd+RHCpymPCYd9DvDvUr1w1IieSChkqYF1EpuAYrvCfLXji9NP36DvyYZJZZB5soVDvZ0WUtBoZaU1g5Yt9A== + dependencies: + w-json "1.3.10" + +r-package-json@^1.0.0: + version "1.0.9" + resolved "https://registry.yarnpkg.com/r-package-json/-/r-package-json-1.0.9.tgz#36549f0d2eafd1e998ee36c7828b0b1b7315d840" + integrity sha512-G4Vpf1KImWmmPFGdtWQTU0L9zk0SjqEC4qs/jE7AQ+Ylmr5kizMzGeC4wnHp5+ijPqNN+2ZPpvyjVNdN1CDVcg== + dependencies: + package-json-path "^1.0.0" + r-json "^1.2.1" + randombytes@^2.0.1, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -4917,11 +5286,42 @@ randombytes@^2.0.1, randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +rc@^1.0.1, rc@^1.1.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +read-all-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" + integrity sha512-DI1drPHbmBcUDWrJ7ull/F2Qb8HkwBncVx8/RpKYFSIACYaVRQReISYPdZz/mt1y1+qMCOrfReTopERmaxtP6w== + dependencies: + pinkie-promise "^2.0.0" + readable-stream "^2.0.0" + +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -4936,6 +5336,21 @@ regenerator-runtime@^0.14.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== +registry-auth-token@^3.0.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" + integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + integrity sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA== + dependencies: + rc "^1.0.1" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -4958,7 +5373,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.20.0: +resolve@^1.10.0, resolve@^1.20.0: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5033,6 +5448,11 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.0, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -5052,6 +5472,11 @@ secp256k1@^4.0.2: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" +"semver@2 || 3 || 4 || 5", semver@^5.1.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -5114,6 +5539,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +sliced@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" + integrity sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA== + snake-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" @@ -5135,6 +5565,32 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.20" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz#e44ed19ed318dd1e5888f93325cee800f0f51b89" + integrity sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw== + split@0.3: version "0.3.3" resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" @@ -5217,6 +5673,13 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -5246,6 +5709,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + superstruct@^0.15.4: version "0.15.5" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" @@ -5306,6 +5774,11 @@ through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3, through@~2.3.1: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +timed-out@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" + integrity sha512-3RB4qgvPkxF/FGPnrzaWLhW1rxNK2sdH0mFjbhxkfTR6QXvcM3EtYm9L44UrhODZrZ+yhDXeMncLqi8QXn2MJg== + tmp-promise@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" @@ -5313,6 +5786,13 @@ tmp-promise@^3.0.2: dependencies: tmp "^0.2.0" +tmp@0.0.28: + version "0.0.28" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120" + integrity sha512-c2mmfiBmND6SOVxzogm1oda0OJ1HZVIk/5n26N59dDTh80MUeavpiCls4PGAdkX1PFkKokLpcf7prSjCeXLsJg== + dependencies: + os-tmpdir "~1.0.1" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -5429,16 +5909,36 @@ typescript@^4.4.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typpy@^2.0.0, typpy@^2.1.0, typpy@^2.2.0, typpy@^2.3.4: + version "2.3.13" + resolved "https://registry.yarnpkg.com/typpy/-/typpy-2.3.13.tgz#7e16a3aa83d7eecdfbd5ee615b9ffd785887ee7e" + integrity sha512-vOxIcQz9sxHi+rT09SJ5aDgVgrPppQjwnnayTrMye1ODaU8gIZTDM19t9TxmEElbMihx2Nq/0/b/MtyKfayRqA== + dependencies: + function.name "^1.0.3" + u3@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/u3/-/u3-0.1.1.tgz#5f52044f42ee76cd8de33148829e14528494b73b" integrity sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w== +ul@^5.0.0: + version "5.2.15" + resolved "https://registry.yarnpkg.com/ul/-/ul-5.2.15.tgz#426425355ae15df2d5d09b351aade26ed06dd9ed" + integrity sha512-svLEUy8xSCip5IWnsRa0UOg+2zP0Wsj4qlbjTmX6GJSmvKMHADBuHOm1dpNkWqWPIGuVSqzUkV3Cris5JrlTRQ== + dependencies: + deffy "^2.2.2" + typpy "^2.3.4" + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +unzip-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" + integrity sha512-pwCcjjhEcpW45JZIySExBHYv5Y9EeL2OIGEfrSKp2dMUFGFv4CpvZkwJbVge8OvGH2BNNtJBx67DuKuJhf+N5Q== + update-browserslist-db@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" @@ -5447,6 +5947,13 @@ update-browserslist-db@^1.1.0: escalade "^3.1.2" picocolors "^1.0.1" +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA== + dependencies: + prepend-http "^1.0.1" + utf-8-validate@^5.0.2: version "5.0.10" resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" @@ -5459,7 +5966,7 @@ utf8@3.0.0: resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== -util-deprecate@^1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -5489,11 +5996,24 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + vlq@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/vlq/-/vlq-2.0.4.tgz#6057b85729245b9829e3cc7755f95b228d4fe041" integrity sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA== +w-json@1.3.10: + version "1.3.10" + resolved "https://registry.yarnpkg.com/w-json/-/w-json-1.3.10.tgz#ac448a19ca22376e2753a684b52369c7b1e83313" + integrity sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw== + wait-on@7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.0.1.tgz#5cff9f8427e94f4deacbc2762e6b0a489b19eae9"