From 375a64becfaf7de2ac8a90a628533d65a465daa4 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Thu, 26 Sep 2024 19:37:37 -0300 Subject: [PATCH 01/29] added merkle tree verification lib --- .cursorrules | 31 +++ Cargo.lock | 10 + libraries/rarible-merkle-verify/Cargo.toml | 12 + libraries/rarible-merkle-verify/src/lib.rs | 23 ++ .../libreplex_editions_controls/Cargo.toml | 5 +- target/types/libreplex_editions.ts | 44 +-- tests/editions_controls.test.ts | 261 ++++++++++++++++++ 7 files changed, 365 insertions(+), 21 deletions(-) create mode 100644 .cursorrules create mode 100644 libraries/rarible-merkle-verify/Cargo.toml create mode 100644 libraries/rarible-merkle-verify/src/lib.rs create mode 100644 tests/editions_controls.test.ts diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..3eaaab40 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,31 @@ +You are an expert Solana blockchain developer with deep knowledge of the Anchor framework and Rust programming language. Your task is to assist with refactoring contracts and SDKs for Rarible on the Solana blockchain. + +When providing assistance: + +1. Prioritize Solana best practices and Anchor idioms in all code suggestions. +2. Ensure all code follows Rust safety and performance guidelines. +3. Consider scalability and gas efficiency in contract designs. +4. Suggest ways to improve code readability and maintainability. +5. Provide explanations for complex Anchor concepts or Solana-specific patterns. +6. When refactoring, maintain or improve existing functionality while enhancing code quality. +7. Be aware of common security vulnerabilities in Solana smart contracts and suggest mitigations. +8. Recommend appropriate Anchor program structures, accounts, and instructions for given tasks. +9. Offer insights on optimizing Solana transaction throughput and minimizing compute units. +10. Suggest relevant Solana and Anchor testing strategies for refactored code. + +Always consider the context of Rarible's NFT marketplace when providing suggestions or solutions. If you're unsure about any Rarible-specific requirements, ask for clarification before proceeding. + +Current Task: +[Contracts] - Add merkle tree for each phase + +Description: when creating a new phase, a merkle tree will be used if the phase is private. we will need to modify the phase struct, and the check phase constraints, that if the phase is private, it will have to check the merkle tree. + +Please provide a detailed step-by-step plan to accomplish this task, including: + +1. Analysis of the current codebase and identification of areas that need modification +2. Proposed changes to the contract structure or existing functions +3. New functions or structs that need to be implemented +4. Any necessary changes to the SDK +5. Considerations for gas efficiency and security +6. Testing strategy for the new functionality +7. Any potential impacts on existing features or integrations diff --git a/Cargo.lock b/Cargo.lock index bd9f1917..a6417738 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2229,6 +2229,7 @@ dependencies = [ "arrayref", "libreplex_editions", "libreplex_shared", + "rarible-merkle-verify", "solana-program", "solana-program-test", "solana-sdk", @@ -3217,6 +3218,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rarible-merkle-verify" +version = "0.0.1" +dependencies = [ + "anchor-lang", + "anchor-spl", + "solana-program", +] + [[package]] name = "rayon" version = "1.10.0" diff --git a/libraries/rarible-merkle-verify/Cargo.toml b/libraries/rarible-merkle-verify/Cargo.toml new file mode 100644 index 00000000..b2db1128 --- /dev/null +++ b/libraries/rarible-merkle-verify/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rarible-merkle-verify" +version = "0.0.1" +edition = "2021" + +[dependencies] +anchor-lang = {version = "~0.30"} +anchor-spl = {version = "~0.30"} +solana-program = {version = "1.17.13"} + +[features] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] diff --git a/libraries/rarible-merkle-verify/src/lib.rs b/libraries/rarible-merkle-verify/src/lib.rs new file mode 100644 index 00000000..1a86fdbc --- /dev/null +++ b/libraries/rarible-merkle-verify/src/lib.rs @@ -0,0 +1,23 @@ +use solana_program::hash::hashv; + +/// modified version of https://github.com/saber-hq/merkle-distributor/blob/ac937d1901033ecb7fa3b0db22f7b39569c8e052/programs/merkle-distributor/src/merkle_proof.rs#L8 +/// This function deals with verification of Merkle trees (hash trees). +/// Direct port of https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/MerkleProof.sol +/// Returns true if a `leaf` can be proved to be a part of a Merkle tree +/// defined by `root`. For this, a `proof` must be provided, containing +/// sibling hashes on the branch from the leaf to the root of the tree. Each +/// pair of leaves and each pair of pre-images are assumed to be sorted. +pub fn verify(proof: Vec<[u8; 32]>, root: [u8; 32], leaf: [u8; 32]) -> bool { + let mut computed_hash = leaf; + for proof_element in proof.into_iter() { + if computed_hash <= proof_element { + // Hash(current computed hash + current element of the proof) + computed_hash = hashv(&[&[1u8], &computed_hash, &proof_element]).to_bytes(); + } else { + // Hash(current element of the proof + current computed hash) + computed_hash = hashv(&[&[1u8], &proof_element, &computed_hash]).to_bytes(); + } + } + // Check if the computed hash (root) is equal to the provided root + computed_hash == root +} diff --git a/programs/libreplex_editions_controls/Cargo.toml b/programs/libreplex_editions_controls/Cargo.toml index d24fa948..eaf2c5fa 100644 --- a/programs/libreplex_editions_controls/Cargo.toml +++ b/programs/libreplex_editions_controls/Cargo.toml @@ -20,13 +20,10 @@ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] [dependencies] anchor-lang = {version = "~0.30", features = ["init-if-needed"]} - anchor-spl = {version = "~0.30"} - - libreplex_editions = {version="*", path = "../libreplex_editions", features =["cpi", "no-entrypoint"]} - +rarible-merkle-verify = { version="*", path = "../../libraries/rarible-merkle-verify" } solana-program = {version = "1.17.13"} arrayref = "0.3.7" diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 950bb662..da432d13 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "Fb9o5V3ZrvSVAfoAZ3P2cRyUCmSPzHdKCf7FzGv42AGD", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", "metadata": { "name": "libreplexEditions", "version": "0.2.1", @@ -694,43 +694,53 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "sizeExceedsMaxSize", - "msg": "Collection size exceeds max size." + "name": "tickerTooLong", + "msg": "Ticker too long" }, { "code": 6001, - "name": "maxSizeBelowCurrentSize", - "msg": "Max size cannot be reduced below current size." + "name": "mintTemplateTooLong", + "msg": "Mint template too long" }, { "code": 6002, - "name": "creatorShareInvalid", - "msg": "Creators shares must add up to 100." + "name": "deploymentTemplateTooLong", + "msg": "Deployment template too long" }, { "code": 6003, - "name": "missingApproveAccount", - "msg": "Missing approve account." + "name": "rootTypeTooLong", + "msg": "Root type too long" }, { "code": 6004, - "name": "expiredApproveAccount", - "msg": "Approve account has expired." + "name": "mintedOut", + "msg": "Minted out" }, { "code": 6005, - "name": "invalidField", - "msg": "Invalid field. You cannot use a public key as a field." + "name": "legacyMigrationsAreMintedOut", + "msg": "Legacy migrations are minted out" }, { "code": 6006, - "name": "creatorAddressInvalid", - "msg": "The Address you provided is invalid. Please provide a valid address." + "name": "missingGlobalTreeDelegate", + "msg": "Global tree delegate is missing" }, { "code": 6007, - "name": "royaltyBasisPointsInvalid", - "msg": "Royalty basis points must be less than or equal to 10000." + "name": "incorrectMintType", + "msg": "Incorrect mint type" + }, + { + "code": 6008, + "name": "invalidMetadata", + "msg": "Invalid Metadata" + }, + { + "code": 6009, + "name": "creatorFeeTooHigh", + "msg": "Creator fee too high" } ], "types": [ diff --git a/tests/editions_controls.test.ts b/tests/editions_controls.test.ts new file mode 100644 index 00000000..012e2fc4 --- /dev/null +++ b/tests/editions_controls.test.ts @@ -0,0 +1,261 @@ +import * as anchor from '@coral-xyz/anchor'; +import { Program } from '@coral-xyz/anchor'; +import { PublicKey, Keypair, SystemProgram } from '@solana/web3.js'; +import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; +import { LibreplexEditionsControls } from '../target/types/libreplex_editions_controls'; +import { LibreplexEditions } from '../target/types/libreplex_editions'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +describe('Editions Controls', () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + const editionsControlsProgram = anchor.workspace + .LibreplexEditionsControls as Program; + console.log( + 'editionsControlsProgram ID:', + editionsControlsProgram.programId.toBase58() + ); + + const editionsProgram = anchor.workspace + .LibreplexEditions as Program; + console.log('editionsProgram ID:', editionsProgram.programId.toBase58()); + + const payer = provider.wallet as anchor.Wallet; + const creator1 = Keypair.generate(); + const creator2 = Keypair.generate(); + const treasury = Keypair.generate(); + + it('should deploy a collection, add a phase, and execute a mint', async () => { + const collectionConfig = { + maxNumberOfTokens: new anchor.BN(100), + symbol: 'TEST', + name: 'Test Collection', + offchainUrl: 'https://example.com/metadata.json', + treasury: treasury.publicKey, + maxMintsPerWallet: new anchor.BN(5), + royaltyBasisPoints: new anchor.BN(1000), + creators: [ + { + address: creator1.publicKey, + share: 50, + }, + // { + // address: creator2.publicKey, + // share: 50, + // }, + ], + extraMeta: [ + { field: 'f1', value: 'v1' }, + // { field: 'f2', value: 'v2' }, + ], + phases: [ + { + priceAmount: new anchor.BN(1000000), // 0.001 SOL + priceToken: new PublicKey( + 'So11111111111111111111111111111111111111112' + ), + startTime: new anchor.BN(Math.floor(Date.now() / 1000)), + maxMintsPerWallet: new anchor.BN(5), + maxMintsTotal: new anchor.BN(50), + endTime: new anchor.BN(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now + }, + ], + }; + + // 1. Deploy a collection + const [editions] = PublicKey.findProgramAddressSync( + [ + Buffer.from('editions_deployment'), + Buffer.from(collectionConfig.symbol), + ], + editionsProgram.programId + ); + + const [editionsControls] = PublicKey.findProgramAddressSync( + [Buffer.from('editions_controls'), editions.toBuffer()], + editionsControlsProgram.programId + ); + + const [hashlist] = PublicKey.findProgramAddressSync( + [Buffer.from('hashlist'), editions.toBuffer()], + editionsProgram.programId + ); + + const groupMint = Keypair.generate(); + const group = Keypair.generate(); + + console.log('Initializing accounts...'); + console.log('Editions address:', editions.toBase58()); + console.log('EditionsControls address:', editionsControls.toBase58()); + console.log('Hashlist address:', hashlist.toBase58()); + console.log('GroupMint address:', groupMint.publicKey.toBase58()); + console.log('Group address:', group.publicKey.toBase58()); + + console.log('initialiseEditionsControls...'); + try { + await editionsControlsProgram.methods + .initialiseEditionsControls({ + maxNumberOfTokens: collectionConfig.maxNumberOfTokens, + symbol: collectionConfig.symbol, + name: collectionConfig.name, + offchainUrl: collectionConfig.offchainUrl, + cosignerProgramId: null, + treasury: collectionConfig.treasury, + maxMintsPerWallet: collectionConfig.maxMintsPerWallet, + royaltyBasisPoints: collectionConfig.royaltyBasisPoints, + creators: collectionConfig.creators, + extraMeta: collectionConfig.extraMeta, + }) + .accountsStrict({ + editionsControls, + editionsDeployment: editions, + hashlist, + payer: payer.publicKey, + creator: payer.publicKey, + groupMint: groupMint.publicKey, + group: group.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + groupExtensionProgram: new PublicKey( + '2TBWcwXdtwQEN8gXoEg6xFtUCYcBRpQaunWTDJwDp5Tx' + ), + }) + .signers([groupMint, group]) + .rpc(); + console.log('initialiseEditionsControls done'); + } catch (error) { + console.error('Error in initialiseEditionsControls:', error); + console.log('Accounts used and their executability:'); + + const accountsToCheck = { + editionsControls, + editionsDeployment: editions, + hashlist, + payer: payer.publicKey, + creator: payer.publicKey, + groupMint: groupMint.publicKey, + group: group.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + groupExtensionProgram: new PublicKey( + '2TBWcwXdtwQEN8gXoEg6xFtUCYcBRpQaunWTDJwDp5Tx' + ), + }; + + for (const [name, pubkey] of Object.entries(accountsToCheck)) { + try { + const accountInfo = await provider.connection.getAccountInfo(pubkey); + console.log(`${name}:`, pubkey.toBase58()); + console.log( + ` Executable: ${ + accountInfo ? accountInfo.executable : 'Account not found' + }` + ); + } catch (err) { + console.log(`${name}:`, pubkey.toBase58()); + console.log(` Error fetching account info: ${err.message}`); + } + } + + throw error; + } + + // // 2. Add a phase + // console.log('addPhase...'); + // await editionsControlsProgram.methods + // .addPhase({ + // priceAmount: new anchor.BN(1000000), // 0.001 SOL + // priceToken: new PublicKey( + // 'So11111111111111111111111111111111111111112' + // ), + // startTime: new anchor.BN(Math.floor(Date.now() / 1000)), + // maxMintsPerWallet: new anchor.BN(5), + // maxMintsTotal: new anchor.BN(50), + // endTime: new anchor.BN(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now + // }) + // .accounts({ + // editionsControls, + // creator: payer.publicKey, + // payer: payer.publicKey, + // systemProgram: SystemProgram.programId, + // tokenProgram: TOKEN_2022_PROGRAM_ID, + // libreplexEditionsProgram: editionsProgram.programId, + // }) + // .rpc(); + // console.log('addPhase done'); + + // // 3. Execute a mint + // const mint = Keypair.generate(); + // const member = Keypair.generate(); + + // const [hashlistMarker] = PublicKey.findProgramAddressSync( + // [ + // Buffer.from('hashlist_marker'), + // editionsDeployment.toBuffer(), + // mint.publicKey.toBuffer(), + // ], + // editionsProgram.programId + // ); + + // const [minterStats] = PublicKey.findProgramAddressSync( + // [ + // Buffer.from('minter_stats'), + // editionsDeployment.toBuffer(), + // payer.publicKey.toBuffer(), + // ], + // editionsControlsProgram.programId + // ); + + // const [minterStatsPhase] = PublicKey.findProgramAddressSync( + // [ + // Buffer.from('minter_stats_phase'), + // editionsDeployment.toBuffer(), + // payer.publicKey.toBuffer(), + // Buffer.from([0]), + // ], + // editionsControlsProgram.programId + // ); + + // console.log('mintWithControls...'); + // await editionsControlsProgram.methods + // .mintWithControls({ + // phaseIndex: 0, + // }) + // .accounts({ + // editionsDeployment, + // editionsControls, + // hashlist, + // hashlistMarker, + // payer: payer.publicKey, + // mint: mint.publicKey, + // member: member.publicKey, + // signer: payer.publicKey, + // minter: payer.publicKey, + // minterStats, + // minterStatsPhase, + // group: group.publicKey, + // groupExtensionProgram: new PublicKey('GExfnHgvdPcg7uQh9vHJYKdNbpGfUzb'), + // tokenAccount: payer.publicKey, // This should be the correct associated token account + // treasury: payer.publicKey, + // systemProgram: SystemProgram.programId, + // tokenProgram: TOKEN_2022_PROGRAM_ID, + // libreplexEditionsProgram: editionsProgram.programId, + // }) + // .signers([mint, member]) + // .rpc(); + // console.log('mintWithControls done'); + + // // Add assertions here to verify the mint was successful + // const editionsControlsAccount = + // await editionsControlsProgram.account.editionsControls.fetch( + // editionsControls + // ); + // expect(editionsControlsAccount.phases[0].currentMints.toNumber()).to.equal( + // 1 + // ); + }); +}); From e7efbb87b2abc25e9ef147c7f57af996b2855ac9 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Thu, 26 Sep 2024 19:56:42 -0300 Subject: [PATCH 02/29] Advances on leaf generation during verification --- .../src/instructions/add_phase.rs | 13 +++++----- .../src/instructions/mint_with_controls.rs | 23 ++++++----------- .../src/logic/check_phase_constraints.rs | 25 ++++++++++++++++++- .../libreplex_editions_controls/src/state.rs | 4 +++ 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/programs/libreplex_editions_controls/src/instructions/add_phase.rs b/programs/libreplex_editions_controls/src/instructions/add_phase.rs index f9e355cd..861f392f 100644 --- a/programs/libreplex_editions_controls/src/instructions/add_phase.rs +++ b/programs/libreplex_editions_controls/src/instructions/add_phase.rs @@ -13,15 +13,16 @@ pub struct InitialisePhaseInput { pub start_time: i64, pub max_mints_per_wallet: u64, pub max_mints_total: u64, - pub end_time: i64 // set to i64::MAX if not supplied + pub end_time: i64, // set to i64::MAX if not supplied + pub is_private: bool, + pub merkle_root: Option<[u8; 32]>, } #[derive(Accounts)] #[instruction(input: InitialisePhaseInput)] pub struct AddPhaseCtx<'info> { - - + #[account(mut, realloc = EditionsControls::get_size(editions_controls.phases.len()+1), realloc::zero = false, @@ -63,10 +64,10 @@ pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Resu end_time: input.end_time, padding: [0; 200], max_mints_total: input.max_mints_total, - current_mints: 0 + current_mints: 0, + is_private: input.is_private, + merkle_root: input.merkle_root, }); - - Ok(()) } diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index 73d04f09..645c3027 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -1,28 +1,22 @@ - - use anchor_lang::{prelude::*, system_program}; use anchor_spl::{ associated_token::AssociatedToken, token_2022 }; - use libreplex_editions::group_extension_program; use libreplex_editions::{program::LibreplexEditions, EditionsDeployment}; use libreplex_editions::cpi::accounts::MintCtx; use crate::{EditionsControls, MinterStats}; - use crate::check_phase_constraints; - - #[derive(AnchorDeserialize, AnchorSerialize, Clone)] pub struct MintInput { - pub phase_index: u32 + pub phase_index: u32, + pub merkle_proof: Option>, } - #[derive(Accounts)] #[instruction(mint_input: MintInput)] @@ -37,7 +31,6 @@ pub struct MintWithControlsCtx<'info> { )] pub editions_controls: Box>, - /// CHECK: Checked via CPI #[account(mut)] pub hashlist: UncheckedAccount<'info>, @@ -75,8 +68,6 @@ pub struct MintWithControlsCtx<'info> { space=MinterStats::SIZE)] pub minter_stats_phase: Box>, - - #[account(mut)] pub mint: Signer<'info>, @@ -102,7 +93,6 @@ pub struct MintWithControlsCtx<'info> { constraint = editions_controls.treasury == treasury.key())] pub treasury: UncheckedAccount<'info>, - /* BOILERPLATE PROGRAM ACCOUNTS */ /// CHECK: Checked in constraint #[account( @@ -124,14 +114,12 @@ pub struct MintWithControlsCtx<'info> { } - - pub fn mint_with_controls(ctx: Context, mint_input: MintInput) -> Result<()> { let libreplex_editions_program = &ctx.accounts.libreplex_editions_program; let editions_deployment = &ctx.accounts.editions_deployment; let editions_controls = &mut ctx.accounts.editions_controls; - + let hashlist = &ctx.accounts.hashlist; let hashlist_marker = &ctx.accounts.hashlist_marker; let payer = &ctx.accounts.payer; @@ -163,7 +151,10 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp &editions_controls.phases[phase_index], minter_stats, minter_stats_phase, - editions_controls); + editions_controls, + mint_input.merkle_proof, + &minter.key() + ); msg!("[mint_count] total:{} phase: {}", minter_stats.mint_count, minter_stats_phase.mint_count); // update this in case it has been initialised diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs index 64ef4069..da687c54 100644 --- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs @@ -9,6 +9,8 @@ pub fn check_phase_constraints( minter_stats: &mut Account, minter_stats_phase: &mut Account, editions_controls: &Account, + merkle_proof: Option>, + minter: &Pubkey, ) { // check let clock = Clock::get().unwrap(); @@ -38,6 +40,27 @@ pub fn check_phase_constraints( if editions_controls.max_mints_per_wallet > 0 && minter_stats.mint_count >= editions_controls.max_mints_per_wallet { panic!("This wallet has exceeded max mints for the deployment") } - + // check private phase constraints @dev on-going development + if phase.is_private { + if let Some(merkle_root) = phase.merkle_root { + if let Some(proof) = merkle_proof { + + // construct leaf, check PhaseTreeNode. + let leaf = hashv(&[ + // minter + // price + // max_claims + ]); + + if !verify(proof, merkle_root, leaf.to_bytes()) { + panic!("Invalid merkle proof"); + } + } else { + panic!("Merkle proof required for private phase"); + } + } else { + panic!("Merkle root not set for private phase"); + } + } } diff --git a/programs/libreplex_editions_controls/src/state.rs b/programs/libreplex_editions_controls/src/state.rs index 03eb87f1..6f8ebdf1 100644 --- a/programs/libreplex_editions_controls/src/state.rs +++ b/programs/libreplex_editions_controls/src/state.rs @@ -12,6 +12,8 @@ pub struct Phase { pub max_mints_total: u64, // set to 0 for unlimited (applied across all the phases) pub end_time: i64, // set to i64::MAX for unlimited pub current_mints: u64, + pub is_private: bool, + pub merkle_root: Option<[u8; 32]>, pub padding: [u8; 200] } @@ -24,6 +26,8 @@ impl Phase { + 8 + 8 + 8 + + 1 // is_private + + 33 // Option<[u8; 32]> + 200; } From 871f1230f4a31087f1a2d73c534c90b255ebc561 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Fri, 27 Sep 2024 18:56:01 -0300 Subject: [PATCH 03/29] Implemented tree node generation and verification --- .../libreplex_editions_controls/src/errors.rs | 3 +- .../src/instructions/mint_with_controls.rs | 6 +- .../src/logic/check_phase_constraints.rs | 36 +- target/types/libreplex_editions.ts | 219 +++- target/types/libreplex_editions_controls.ts | 1097 +++++++++++++++++ 5 files changed, 1347 insertions(+), 14 deletions(-) create mode 100644 target/types/libreplex_editions_controls.ts diff --git a/programs/libreplex_editions_controls/src/errors.rs b/programs/libreplex_editions_controls/src/errors.rs index 53e98476..a13ae94b 100644 --- a/programs/libreplex_editions_controls/src/errors.rs +++ b/programs/libreplex_editions_controls/src/errors.rs @@ -8,7 +8,6 @@ pub enum EditionsError { #[msg("Mint template too long")] MintTemplateTooLong, - #[msg("Deployment template too long")] DeploymentTemplateTooLong, @@ -32,4 +31,4 @@ pub enum EditionsError { #[msg("Creator fee too high")] CreatorFeeTooHigh -} +} \ No newline at end of file diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index 645c3027..ff3df51b 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -15,6 +15,8 @@ use crate::check_phase_constraints; pub struct MintInput { pub phase_index: u32, pub merkle_proof: Option>, + pub allow_list_price: Option, + pub allow_list_max_claims: Option, } #[derive(Accounts)] @@ -153,7 +155,9 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp minter_stats_phase, editions_controls, mint_input.merkle_proof, - &minter.key() + &minter.key, + mint_input.allow_list_price, + mint_input.allow_list_max_claims ); msg!("[mint_count] total:{} phase: {}", minter_stats.mint_count, minter_stats_phase.mint_count); diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs index da687c54..86002f41 100644 --- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs @@ -1,9 +1,18 @@ -use anchor_lang::accounts::account::Account; +use anchor_lang::{ + accounts::account::Account, + prelude::*, + solana_program::hash::hashv, +}; -use anchor_lang::prelude::*; +use rarible_merkle_verify::verify; use crate::{EditionsControls, MinterStats, Phase}; +// We need to discern between leaf and intermediate nodes to prevent trivial second +// pre-image attacks. +// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack +const LEAF_PREFIX: &[u8] = &[0]; + pub fn check_phase_constraints( phase: &Phase, minter_stats: &mut Account, @@ -11,6 +20,8 @@ pub fn check_phase_constraints( editions_controls: &Account, merkle_proof: Option>, minter: &Pubkey, + allow_list_price: Option, + allow_list_max_claims: Option, ) { // check let clock = Clock::get().unwrap(); @@ -45,16 +56,21 @@ pub fn check_phase_constraints( if phase.is_private { if let Some(merkle_root) = phase.merkle_root { if let Some(proof) = merkle_proof { + if let (Some(price), Some(max_claims)) = (allow_list_price, allow_list_max_claims) { + // construct leaf + let leaf = hashv(&[ + &minter.to_bytes(), + &price.to_le_bytes(), + &max_claims.to_le_bytes(), + ]); - // construct leaf, check PhaseTreeNode. - let leaf = hashv(&[ - // minter - // price - // max_claims - ]); + let node = hashv(&[LEAF_PREFIX, &leaf.to_bytes()]); - if !verify(proof, merkle_root, leaf.to_bytes()) { - panic!("Invalid merkle proof"); + if !verify(proof, merkle_root, node.to_bytes()) { + panic!("Invalid merkle proof"); + } + } else { + panic!("Merkle proof required for private phase"); } } else { panic!("Merkle proof required for private phase"); diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index da432d13..413e1c10 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", + "address": "GBh9v74hwXm4pqMYVUndiUDdyA71et7EfqXfcbQTNhnf", "metadata": { "name": "libreplexEditions", "version": "0.2.1", @@ -104,6 +104,94 @@ export type LibreplexEditions = { } ] }, + { + "name": "addPlatformFee", + "docs": [ + "add royalties to mint" + ], + "discriminator": [ + 62, + 94, + 29, + 95, + 254, + 228, + 21, + 18 + ], + "accounts": [ + { + "name": "editionsDeployment", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 101, + 100, + 105, + 116, + 105, + 111, + 110, + 115, + 95, + 100, + 101, + 112, + 108, + 111, + 121, + 109, + 101, + 110, + 116 + ] + }, + { + "kind": "account", + "path": "editions_deployment.symbol", + "account": "editionsDeployment" + } + ] + } + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "groupMint", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": { + "name": "updatePlatformFeeArgs" + } + } + } + ] + }, { "name": "addRoyalties", "docs": [ @@ -474,6 +562,93 @@ export type LibreplexEditions = { ], "args": [] }, + { + "name": "modifyPlatformFee", + "docs": [ + "modify royalties of mint" + ], + "discriminator": [ + 186, + 73, + 229, + 152, + 183, + 174, + 250, + 197 + ], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "editionsDeployment", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 101, + 100, + 105, + 116, + 105, + 111, + 110, + 115, + 95, + 100, + 101, + 112, + 108, + 111, + 121, + 109, + 101, + 110, + 116 + ] + }, + { + "kind": "account", + "path": "editions_deployment.symbol", + "account": "editionsDeployment" + } + ] + } + }, + { + "name": "signer", + "writable": true, + "signer": true + }, + { + "name": "groupMint", + "writable": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + } + ], + "args": [ + { + "name": "args", + "type": { + "defined": { + "name": "updatePlatformFeeArgs" + } + } + } + ] + }, { "name": "modifyRoyalties", "docs": [ @@ -929,6 +1104,22 @@ export type LibreplexEditions = { ] } }, + { + "name": "platformFeeRecipient", + "type": { + "kind": "struct", + "fields": [ + { + "name": "address", + "type": "pubkey" + }, + { + "name": "share", + "type": "u8" + } + ] + } + }, { "name": "removeMetadataArgs", "type": { @@ -945,6 +1136,32 @@ export type LibreplexEditions = { ] } }, + { + "name": "updatePlatformFeeArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "platformFeeValue", + "type": "u64" + }, + { + "name": "recipients", + "type": { + "vec": { + "defined": { + "name": "platformFeeRecipient" + } + } + } + }, + { + "name": "isFeeFlat", + "type": "bool" + } + ] + } + }, { "name": "updateRoyaltiesArgs", "type": { diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts new file mode 100644 index 00000000..a061046e --- /dev/null +++ b/target/types/libreplex_editions_controls.ts @@ -0,0 +1,1097 @@ +/** + * Program IDL in camelCase format in order to be used in JS/TS. + * + * Note that this is only a type helper and is not the actual IDL. The original + * IDL can be found at `target/idl/libreplex_editions_controls.json`. + */ +export type LibreplexEditionsControls = { + "address": "J87ZtbZvSYaBhwwDb4LqCHAh7rAUjkVWiab5AZ7R3TY5", + "metadata": { + "name": "libreplexEditionsControls", + "version": "0.2.1", + "spec": "0.1.0", + "description": "Created with Anchor", + "repository": "https://github.com/Libreplex/libreplex-program-library" + }, + "instructions": [ + { + "name": "addPhase", + "discriminator": [ + 245, + 220, + 147, + 40, + 30, + 207, + 36, + 127 + ], + "accounts": [ + { + "name": "editionsControls", + "writable": true + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "creator", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + }, + { + "name": "libreplexEditionsProgram", + "address": "GBh9v74hwXm4pqMYVUndiUDdyA71et7EfqXfcbQTNhnf" + } + ], + "args": [ + { + "name": "input", + "type": { + "defined": { + "name": "initialisePhaseInput" + } + } + } + ] + }, + { + "name": "initialiseEditionsControls", + "discriminator": [ + 69, + 176, + 133, + 29, + 20, + 49, + 120, + 202 + ], + "accounts": [ + { + "name": "editionsControls", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 101, + 100, + 105, + 116, + 105, + 111, + 110, + 115, + 95, + 99, + 111, + 110, + 116, + 114, + 111, + 108, + 115 + ] + }, + { + "kind": "account", + "path": "editionsDeployment" + } + ] + } + }, + { + "name": "editionsDeployment", + "writable": true + }, + { + "name": "hashlist", + "writable": true + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "creator" + }, + { + "name": "groupMint", + "writable": true, + "signer": true + }, + { + "name": "group", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + }, + { + "name": "groupExtensionProgram", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" + }, + { + "name": "libreplexEditionsProgram", + "address": "GBh9v74hwXm4pqMYVUndiUDdyA71et7EfqXfcbQTNhnf" + } + ], + "args": [ + { + "name": "input", + "type": { + "defined": { + "name": "initialiseControlInput" + } + } + } + ] + }, + { + "name": "mintWithControls", + "discriminator": [ + 167, + 57, + 252, + 220, + 69, + 92, + 231, + 61 + ], + "accounts": [ + { + "name": "editionsDeployment", + "writable": true + }, + { + "name": "editionsControls", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 101, + 100, + 105, + 116, + 105, + 111, + 110, + 115, + 95, + 99, + 111, + 110, + 116, + 114, + 111, + 108, + 115 + ] + }, + { + "kind": "account", + "path": "editionsDeployment" + } + ] + } + }, + { + "name": "hashlist", + "writable": true + }, + { + "name": "hashlistMarker", + "writable": true + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "signer", + "signer": true + }, + { + "name": "minter", + "writable": true + }, + { + "name": "minterStats", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 105, + 110, + 116, + 101, + 114, + 95, + 115, + 116, + 97, + 116, + 115 + ] + }, + { + "kind": "account", + "path": "editionsDeployment" + }, + { + "kind": "account", + "path": "minter" + } + ] + } + }, + { + "name": "minterStatsPhase", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 109, + 105, + 110, + 116, + 101, + 114, + 95, + 115, + 116, + 97, + 116, + 115, + 95, + 112, + 104, + 97, + 115, + 101 + ] + }, + { + "kind": "account", + "path": "editionsDeployment" + }, + { + "kind": "account", + "path": "minter" + }, + { + "kind": "arg", + "path": "mint_input.phase_index" + } + ] + } + }, + { + "name": "mint", + "writable": true, + "signer": true + }, + { + "name": "member", + "writable": true, + "signer": true + }, + { + "name": "group", + "writable": true + }, + { + "name": "groupMint", + "writable": true + }, + { + "name": "tokenAccount", + "writable": true + }, + { + "name": "treasury", + "writable": true + }, + { + "name": "tokenProgram" + }, + { + "name": "associatedTokenProgram", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "groupExtensionProgram", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "libreplexEditionsProgram", + "address": "GBh9v74hwXm4pqMYVUndiUDdyA71et7EfqXfcbQTNhnf" + } + ], + "args": [ + { + "name": "mintInput", + "type": { + "defined": { + "name": "mintInput" + } + } + } + ] + }, + { + "name": "modifyPlatformFee", + "discriminator": [ + 186, + 73, + 229, + 152, + 183, + 174, + 250, + 197 + ], + "accounts": [ + { + "name": "editionsDeployment", + "writable": true + }, + { + "name": "editionsControls", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 101, + 100, + 105, + 116, + 105, + 111, + 110, + 115, + 95, + 99, + 111, + 110, + 116, + 114, + 111, + 108, + 115 + ] + }, + { + "kind": "account", + "path": "editionsDeployment" + } + ] + } + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "creator", + "writable": true, + "signer": true + }, + { + "name": "groupMint", + "writable": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + }, + { + "name": "libreplexEditionsProgram", + "address": "GBh9v74hwXm4pqMYVUndiUDdyA71et7EfqXfcbQTNhnf" + } + ], + "args": [ + { + "name": "input", + "type": { + "defined": { + "name": "updatePlatformFeeArgs" + } + } + } + ] + }, + { + "name": "modifyRoyalties", + "discriminator": [ + 199, + 95, + 20, + 107, + 136, + 161, + 93, + 137 + ], + "accounts": [ + { + "name": "editionsDeployment", + "writable": true + }, + { + "name": "editionsControls", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 101, + 100, + 105, + 116, + 105, + 111, + 110, + 115, + 95, + 99, + 111, + 110, + 116, + 114, + 111, + 108, + 115 + ] + }, + { + "kind": "account", + "path": "editionsDeployment" + } + ] + } + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "creator", + "writable": true, + "signer": true + }, + { + "name": "mint", + "writable": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + }, + { + "name": "tokenProgram", + "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + }, + { + "name": "libreplexEditionsProgram", + "address": "GBh9v74hwXm4pqMYVUndiUDdyA71et7EfqXfcbQTNhnf" + } + ], + "args": [ + { + "name": "input", + "type": { + "defined": { + "name": "updateRoyaltiesArgs" + } + } + } + ] + } + ], + "accounts": [ + { + "name": "editionsControls", + "discriminator": [ + 124, + 32, + 239, + 85, + 118, + 231, + 152, + 156 + ] + }, + { + "name": "editionsDeployment", + "discriminator": [ + 101, + 54, + 68, + 216, + 168, + 131, + 242, + 157 + ] + }, + { + "name": "minterStats", + "discriminator": [ + 138, + 239, + 240, + 226, + 199, + 53, + 170, + 179 + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "tickerTooLong", + "msg": "Ticker too long" + }, + { + "code": 6001, + "name": "mintTemplateTooLong", + "msg": "Mint template too long" + }, + { + "code": 6002, + "name": "deploymentTemplateTooLong", + "msg": "Deployment template too long" + }, + { + "code": 6003, + "name": "rootTypeTooLong", + "msg": "Root type too long" + }, + { + "code": 6004, + "name": "mintedOut", + "msg": "Minted out" + }, + { + "code": 6005, + "name": "legacyMigrationsAreMintedOut", + "msg": "Legacy migrations are minted out" + }, + { + "code": 6006, + "name": "missingGlobalTreeDelegate", + "msg": "Global tree delegate is missing" + }, + { + "code": 6007, + "name": "incorrectMintType", + "msg": "Incorrect mint type" + }, + { + "code": 6008, + "name": "invalidMetadata", + "msg": "Invalid Metadata" + }, + { + "code": 6009, + "name": "creatorFeeTooHigh", + "msg": "Creator fee too high" + } + ], + "types": [ + { + "name": "addMetadataArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field", + "type": "string" + }, + { + "name": "value", + "type": "string" + } + ] + } + }, + { + "name": "creatorWithShare", + "type": { + "kind": "struct", + "fields": [ + { + "name": "address", + "type": "pubkey" + }, + { + "name": "share", + "type": "u8" + } + ] + } + }, + { + "name": "editionsControls", + "type": { + "kind": "struct", + "fields": [ + { + "name": "editionsDeployment", + "type": "pubkey" + }, + { + "name": "creator", + "type": "pubkey" + }, + { + "name": "treasury", + "type": "pubkey" + }, + { + "name": "maxMintsPerWallet", + "type": "u64" + }, + { + "name": "cosignerProgramId", + "type": "pubkey" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 200 + ] + } + }, + { + "name": "phases", + "type": { + "vec": { + "defined": { + "name": "phase" + } + } + } + } + ] + } + }, + { + "name": "editionsDeployment", + "type": { + "kind": "struct", + "fields": [ + { + "name": "creator", + "type": "pubkey" + }, + { + "name": "maxNumberOfTokens", + "type": "u64" + }, + { + "name": "numberOfTokensIssued", + "type": "u64" + }, + { + "name": "cosignerProgramId", + "type": "pubkey" + }, + { + "name": "groupMint", + "type": "pubkey" + }, + { + "name": "group", + "type": "pubkey" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "offchainUrl", + "type": "string" + }, + { + "name": "nameIsTemplate", + "type": "bool" + }, + { + "name": "urlIsTemplate", + "type": "bool" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 98 + ] + } + } + ] + } + }, + { + "name": "initialiseControlInput", + "type": { + "kind": "struct", + "fields": [ + { + "name": "maxMintsPerWallet", + "type": "u64" + }, + { + "name": "treasury", + "type": "pubkey" + }, + { + "name": "maxNumberOfTokens", + "type": "u64" + }, + { + "name": "symbol", + "type": "string" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "offchainUrl", + "type": "string" + }, + { + "name": "cosignerProgramId", + "type": { + "option": "pubkey" + } + }, + { + "name": "royalties", + "type": { + "defined": { + "name": "updateRoyaltiesArgs" + } + } + }, + { + "name": "extraMeta", + "type": { + "vec": { + "defined": { + "name": "addMetadataArgs" + } + } + } + }, + { + "name": "itemBaseUri", + "type": "string" + }, + { + "name": "itemName", + "type": "string" + }, + { + "name": "platformFee", + "type": { + "defined": { + "name": "updatePlatformFeeArgs" + } + } + } + ] + } + }, + { + "name": "initialisePhaseInput", + "type": { + "kind": "struct", + "fields": [ + { + "name": "priceAmount", + "type": "u64" + }, + { + "name": "priceToken", + "type": "pubkey" + }, + { + "name": "startTime", + "type": "i64" + }, + { + "name": "maxMintsPerWallet", + "type": "u64" + }, + { + "name": "maxMintsTotal", + "type": "u64" + }, + { + "name": "endTime", + "type": "i64" + }, + { + "name": "isPrivate", + "type": "bool" + }, + { + "name": "merkleRoot", + "type": { + "option": { + "array": [ + "u8", + 32 + ] + } + } + } + ] + } + }, + { + "name": "mintInput", + "type": { + "kind": "struct", + "fields": [ + { + "name": "phaseIndex", + "type": "u32" + }, + { + "name": "merkleProof", + "type": { + "option": { + "vec": { + "array": [ + "u8", + 32 + ] + } + } + } + }, + { + "name": "allowListPrice", + "type": { + "option": "u64" + } + }, + { + "name": "allowListMaxClaims", + "type": { + "option": "u64" + } + } + ] + } + }, + { + "name": "minterStats", + "type": { + "kind": "struct", + "fields": [ + { + "name": "wallet", + "type": "pubkey" + }, + { + "name": "mintCount", + "type": "u64" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 50 + ] + } + } + ] + } + }, + { + "name": "phase", + "type": { + "kind": "struct", + "fields": [ + { + "name": "priceAmount", + "type": "u64" + }, + { + "name": "priceToken", + "type": "pubkey" + }, + { + "name": "startTime", + "type": "i64" + }, + { + "name": "active", + "type": "bool" + }, + { + "name": "maxMintsPerWallet", + "type": "u64" + }, + { + "name": "maxMintsTotal", + "type": "u64" + }, + { + "name": "endTime", + "type": "i64" + }, + { + "name": "currentMints", + "type": "u64" + }, + { + "name": "isPrivate", + "type": "bool" + }, + { + "name": "merkleRoot", + "type": { + "option": { + "array": [ + "u8", + 32 + ] + } + } + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 200 + ] + } + } + ] + } + }, + { + "name": "platformFeeRecipient", + "type": { + "kind": "struct", + "fields": [ + { + "name": "address", + "type": "pubkey" + }, + { + "name": "share", + "type": "u8" + } + ] + } + }, + { + "name": "updatePlatformFeeArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "platformFeeValue", + "type": "u64" + }, + { + "name": "recipients", + "type": { + "vec": { + "defined": { + "name": "platformFeeRecipient" + } + } + } + }, + { + "name": "isFeeFlat", + "type": "bool" + } + ] + } + }, + { + "name": "updateRoyaltiesArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "royaltyBasisPoints", + "type": "u16" + }, + { + "name": "creators", + "type": { + "vec": { + "defined": { + "name": "creatorWithShare" + } + } + } + } + ] + } + } + ] +}; From 8d0158614426e2e005a2715648b4e0476d5b0390 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Fri, 27 Sep 2024 19:21:56 -0300 Subject: [PATCH 04/29] refactored public and private phase constraints --- .../src/instructions/mint_with_controls.rs | 11 +++-- .../src/logic/check_phase_constraints.rs | 41 ++++++++++------ target/types/libreplex_editions.ts | 47 ++++++++++--------- 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index ff3df51b..7c6aa02d 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -161,7 +161,7 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp ); msg!("[mint_count] total:{} phase: {}", minter_stats.mint_count, minter_stats_phase.mint_count); - // update this in case it has been initialised + // update this in case it has been initialised // idempotent because the account is determined by the PDA minter_stats.wallet = minter.key(); minter_stats.mint_count += 1; @@ -171,7 +171,12 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp editions_controls.phases[phase_index].current_mints += 1; - + let isPrivatePhase = editions_controls.phases[phase_index].is_private; + let final_mint_price = if isPrivatePhase { + mint_input.allow_list_price.unwrap_or(0) + } else { + price_amount + }; // ok, we are gucci. transfer funds to treasury if applicable system_program::transfer( @@ -182,7 +187,7 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp to: treasury.to_account_info(), }, ), - price_amount + final_mint_price )?; // take all the data for platform fee and transfer diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs index 86002f41..1aca90d6 100644 --- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs @@ -8,9 +8,9 @@ use rarible_merkle_verify::verify; use crate::{EditionsControls, MinterStats, Phase}; -// We need to discern between leaf and intermediate nodes to prevent trivial second -// pre-image attacks. -// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack +/// We need to discern between leaf and intermediate nodes to prevent trivial second +/// pre-image attacks. +/// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack const LEAF_PREFIX: &[u8] = &[0]; pub fn check_phase_constraints( @@ -23,7 +23,6 @@ pub fn check_phase_constraints( allow_list_price: Option, allow_list_max_claims: Option, ) { - // check let clock = Clock::get().unwrap(); let current_time = clock.unix_timestamp; @@ -40,32 +39,44 @@ pub fn check_phase_constraints( panic!("Phase already finished") } - if phase.max_mints_per_wallet > 0 && minter_stats_phase.mint_count >= phase.max_mints_per_wallet { - panic!("This wallet has exceeded max mints in the current phase") - } - + /// Checks if the total mints for the phase has been exceeded (phase sold out) + /// @dev dev: notice that if max_mints_total is 0, this constraint is disabled if phase.max_mints_total > 0 && phase.current_mints >= phase.max_mints_total { - panic!("Total mints exceeded in this phase") + panic!("Total mints exceeded in this phase (sold out)") } + /// Checks if the user has exceeded the max mints for the deployment (across all phases!) + /// dev: notice that if max_mints_per_wallet is 0, this constraint is disabled if editions_controls.max_mints_per_wallet > 0 && minter_stats.mint_count >= editions_controls.max_mints_per_wallet { - panic!("This wallet has exceeded max mints for the deployment") + panic!("This wallet has exceeded max mints for the collection across all phases") + } + + /// Check public phase only constraints + if !phase.is_private { + if phase.max_mints_per_wallet > 0 && minter_stats_phase.mint_count >= phase.max_mints_per_wallet { + panic!("This wallet has exceeded max mints in the current phase") + } } - - // check private phase constraints @dev on-going development - if phase.is_private { + else { + /// check private only constraints if let Some(merkle_root) = phase.merkle_root { if let Some(proof) = merkle_proof { if let (Some(price), Some(max_claims)) = (allow_list_price, allow_list_max_claims) { - // construct leaf + + /// 1. check constraints + if max_claims > 0 && minter_stats_phase.mint_count >= max_claims { + panic!("This wallet has exceeded max_claims in the current private phase") + } + + /// 2. construct leaf let leaf = hashv(&[ &minter.to_bytes(), &price.to_le_bytes(), &max_claims.to_le_bytes(), ]); - let node = hashv(&[LEAF_PREFIX, &leaf.to_bytes()]); + /// 3. verify proof if !verify(proof, merkle_root, node.to_bytes()) { panic!("Invalid merkle proof"); } diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 413e1c10..969b3969 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "GBh9v74hwXm4pqMYVUndiUDdyA71et7EfqXfcbQTNhnf", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", "metadata": { "name": "libreplexEditions", "version": "0.2.1", @@ -869,53 +869,58 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "tickerTooLong", - "msg": "Ticker too long" + "name": "sizeExceedsMaxSize", + "msg": "Collection size exceeds max size." }, { "code": 6001, - "name": "mintTemplateTooLong", - "msg": "Mint template too long" + "name": "maxSizeBelowCurrentSize", + "msg": "Max size cannot be reduced below current size." }, { "code": 6002, - "name": "deploymentTemplateTooLong", - "msg": "Deployment template too long" + "name": "creatorShareInvalid", + "msg": "Creators shares must add up to 100." }, { "code": 6003, - "name": "rootTypeTooLong", - "msg": "Root type too long" + "name": "missingApproveAccount", + "msg": "Missing approve account." }, { "code": 6004, - "name": "mintedOut", - "msg": "Minted out" + "name": "expiredApproveAccount", + "msg": "Approve account has expired." }, { "code": 6005, - "name": "legacyMigrationsAreMintedOut", - "msg": "Legacy migrations are minted out" + "name": "invalidField", + "msg": "Invalid field. You cannot use a public key as a field." }, { "code": 6006, - "name": "missingGlobalTreeDelegate", - "msg": "Global tree delegate is missing" + "name": "creatorAddressInvalid", + "msg": "The Address you provided is invalid. Please provide a valid address." }, { "code": 6007, - "name": "incorrectMintType", - "msg": "Incorrect mint type" + "name": "royaltyBasisPointsInvalid", + "msg": "Royalty basis points must be less than or equal to 10000." }, { "code": 6008, - "name": "invalidMetadata", - "msg": "Invalid Metadata" + "name": "platformFeeBasisPointsInvalid", + "msg": "Platform fee basis points must be less than or equal to 10000." }, { "code": 6009, - "name": "creatorFeeTooHigh", - "msg": "Creator fee too high" + "name": "recipientShareInvalid", + "msg": "Recipient shares must add up to 100." + }, + { + "code": 6010, + "name": "reservedField", + "msg": "The provided field is invalid or reserved." } ], "types": [ From 023875bd24813d7373cfcef8ae4baa06e011741f Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 11:27:05 -0300 Subject: [PATCH 05/29] Advances on proof verification --- .../merkle_tree.json | 364 ++++++++++++++++++ .../src/instructions/add_phase.rs | 6 - .../src/instructions/mint_with_controls.rs | 71 ++-- .../src/logic/check_allow_list_constraints.rs | 54 +++ .../src/logic/check_phase_constraints.rs | 58 +-- .../libreplex_editions_controls/src/state.rs | 9 +- 6 files changed, 472 insertions(+), 90 deletions(-) create mode 100644 programs/libreplex_editions_controls/merkle_tree.json create mode 100644 programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs diff --git a/programs/libreplex_editions_controls/merkle_tree.json b/programs/libreplex_editions_controls/merkle_tree.json new file mode 100644 index 00000000..6fc81e5c --- /dev/null +++ b/programs/libreplex_editions_controls/merkle_tree.json @@ -0,0 +1,364 @@ +{ + "merkle_root": [ + 212, + 215, + 40, + 226, + 187, + 234, + 161, + 122, + 12, + 63, + 245, + 247, + 52, + 154, + 111, + 169, + 93, + 188, + 148, + 136, + 219, + 178, + 25, + 235, + 255, + 205, + 61, + 172, + 52, + 98, + 128, + 208 + ], + "max_num_nodes": 3, + "max_total_claim": 42, + "tree_nodes": [ + { + "claimant": [ + 51, + 28, + 157, + 112, + 119, + 55, + 31, + 220, + 151, + 164, + 164, + 163, + 29, + 66, + 125, + 52, + 151, + 192, + 195, + 169, + 101, + 80, + 210, + 208, + 217, + 31, + 126, + 50, + 54, + 248, + 124, + 226 + ], + "claim_price": 100000, + "max_claims": 12, + "proof": [ + [ + 202, + 170, + 250, + 249, + 215, + 254, + 191, + 180, + 143, + 8, + 101, + 61, + 96, + 18, + 111, + 116, + 175, + 51, + 102, + 96, + 79, + 82, + 3, + 104, + 223, + 162, + 98, + 185, + 106, + 181, + 185, + 67 + ], + [ + 86, + 166, + 34, + 11, + 92, + 49, + 8, + 151, + 69, + 151, + 51, + 219, + 47, + 122, + 205, + 201, + 235, + 151, + 122, + 10, + 67, + 192, + 206, + 34, + 7, + 93, + 186, + 154, + 49, + 184, + 131, + 229 + ] + ] + }, + { + "claimant": [ + 107, + 227, + 164, + 147, + 231, + 245, + 48, + 203, + 111, + 88, + 109, + 83, + 116, + 252, + 17, + 150, + 105, + 100, + 7, + 60, + 14, + 20, + 22, + 255, + 69, + 46, + 245, + 33, + 54, + 46, + 33, + 9 + ], + "claim_price": 100000, + "max_claims": 14, + "proof": [ + [ + 130, + 120, + 137, + 185, + 228, + 30, + 169, + 247, + 146, + 10, + 251, + 191, + 243, + 235, + 136, + 83, + 98, + 132, + 140, + 227, + 198, + 218, + 151, + 147, + 226, + 64, + 173, + 251, + 185, + 101, + 237, + 2 + ], + [ + 86, + 166, + 34, + 11, + 92, + 49, + 8, + 151, + 69, + 151, + 51, + 219, + 47, + 122, + 205, + 201, + 235, + 151, + 122, + 10, + 67, + 192, + 206, + 34, + 7, + 93, + 186, + 154, + 49, + 184, + 131, + 229 + ] + ] + }, + { + "claimant": [ + 134, + 175, + 237, + 81, + 136, + 244, + 104, + 181, + 193, + 46, + 207, + 35, + 204, + 52, + 215, + 244, + 165, + 237, + 140, + 209, + 18, + 211, + 7, + 43, + 22, + 156, + 99, + 139, + 118, + 148, + 249, + 249 + ], + "claim_price": 100000, + "max_claims": 16, + "proof": [ + [ + 87, + 78, + 93, + 186, + 146, + 241, + 186, + 182, + 194, + 16, + 220, + 218, + 198, + 40, + 208, + 233, + 122, + 100, + 215, + 43, + 231, + 251, + 30, + 234, + 121, + 4, + 5, + 181, + 36, + 3, + 244, + 44 + ], + [ + 42, + 96, + 10, + 26, + 96, + 112, + 34, + 28, + 8, + 163, + 63, + 156, + 131, + 123, + 182, + 225, + 81, + 189, + 55, + 101, + 184, + 123, + 83, + 199, + 160, + 43, + 156, + 85, + 140, + 8, + 99, + 127 + ] + ] + } + ] +} \ No newline at end of file diff --git a/programs/libreplex_editions_controls/src/instructions/add_phase.rs b/programs/libreplex_editions_controls/src/instructions/add_phase.rs index 861f392f..1eeba069 100644 --- a/programs/libreplex_editions_controls/src/instructions/add_phase.rs +++ b/programs/libreplex_editions_controls/src/instructions/add_phase.rs @@ -2,10 +2,8 @@ use anchor_lang::prelude::*; use libreplex_editions::program::LibreplexEditions; use libreplex_shared::wrapped_sol; - use crate::{EditionsControls, Phase}; - #[derive(AnchorDeserialize, AnchorSerialize, Clone)] pub struct InitialisePhaseInput { pub price_amount: u64, @@ -14,15 +12,12 @@ pub struct InitialisePhaseInput { pub max_mints_per_wallet: u64, pub max_mints_total: u64, pub end_time: i64, // set to i64::MAX if not supplied - pub is_private: bool, pub merkle_root: Option<[u8; 32]>, } - #[derive(Accounts)] #[instruction(input: InitialisePhaseInput)] pub struct AddPhaseCtx<'info> { - #[account(mut, realloc = EditionsControls::get_size(editions_controls.phases.len()+1), realloc::zero = false, @@ -65,7 +60,6 @@ pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Resu padding: [0; 200], max_mints_total: input.max_mints_total, current_mints: 0, - is_private: input.is_private, merkle_root: input.merkle_root, }); diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index 7c6aa02d..129b4fbb 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -3,9 +3,12 @@ use anchor_spl::{ associated_token::AssociatedToken, token_2022 }; -use libreplex_editions::group_extension_program; -use libreplex_editions::{program::LibreplexEditions, EditionsDeployment}; -use libreplex_editions::cpi::accounts::MintCtx; +use libreplex_editions::{ + group_extension_program, + program::LibreplexEditions, + EditionsDeployment, + cpi::accounts::MintCtx +}; use crate::{EditionsControls, MinterStats}; @@ -21,9 +24,7 @@ pub struct MintInput { #[derive(Accounts)] #[instruction(mint_input: MintInput)] - pub struct MintWithControlsCtx<'info> { - #[account(mut)] pub editions_deployment: Box>, @@ -33,8 +34,8 @@ pub struct MintWithControlsCtx<'info> { )] pub editions_controls: Box>, - /// CHECK: Checked via CPI - #[account(mut)] + /// CHECK: Checked via CPI + #[account(mut)] pub hashlist: UncheckedAccount<'info>, /// CHECK: Checked via CPI @@ -113,11 +114,9 @@ pub struct MintWithControlsCtx<'info> { pub system_program: Program<'info, System>, pub libreplex_editions_program: Program<'info, LibreplexEditions> - } pub fn mint_with_controls(ctx: Context, mint_input: MintInput) -> Result<()> { - let libreplex_editions_program = &ctx.accounts.libreplex_editions_program; let editions_deployment = &ctx.accounts.editions_deployment; let editions_controls = &mut ctx.accounts.editions_controls; @@ -138,6 +137,7 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp let minter_stats_phase = &mut ctx.accounts.minter_stats_phase; let group_extension_program = &ctx.accounts.group_extension_program; let member = &ctx.accounts.member; + if mint_input.phase_index >= editions_controls.phases.len() as u32 { if editions_controls.phases.is_empty() { panic!("No phases added. Cannot mint"); @@ -147,37 +147,56 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp } let phase_index = mint_input.phase_index as usize; - let price_amount = editions_controls.phases[phase_index].price_amount; + let current_phase = editions_controls.phases[phase_index]; + /// check phase constraints check_phase_constraints( &editions_controls.phases[phase_index], minter_stats, minter_stats_phase, editions_controls, - mint_input.merkle_proof, - &minter.key, - mint_input.allow_list_price, - mint_input.allow_list_max_claims ); - msg!("[mint_count] total:{} phase: {}", minter_stats.mint_count, minter_stats_phase.mint_count); - // update this in case it has been initialised - // idempotent because the account is determined by the PDA + /// Determine if is a normal mint or an allow list mint + let is_allow_list_mint = mint_input.merkle_proof.is_some(); + + /// If allow list mint, check allow list constraints + /// This check generates the leaf based on (minter, price, max_claims), verifies the proof, and ensures the minter has not exceeded max_claims + if is_allow_list_mint { + check_allow_list_constraints( + &editions_controls.phases[phase_index], + minter, + minter_stats_phase, + mint_input.merkle_proof, + mint_input.allow_list_price, + mint_input.allow_list_max_claims, + ); + } + + /// determine mint price, which is either the allow list price or the phase price, depending on the mint type + let price_amount = if is_allow_list_mint { + mint_input.allow_list_price.unwrap_or(0) + } else { + current_phase.price_amount + }; + + msg!( + "[mint_count] user mints on collection:{}, user mints on phase: {}", + minter_stats.mint_count, minter_stats_phase.mint_count + ); + + /// Increment the minter stats across the collection minter_stats.wallet = minter.key(); minter_stats.mint_count += 1; + /// Increment the minter stats for the current phase minter_stats_phase.wallet = minter.key(); minter_stats_phase.mint_count += 1; - editions_controls.phases[phase_index].current_mints += 1; + /// Increment the current mints for the phase + current_phase.current_mints += 1; - let isPrivatePhase = editions_controls.phases[phase_index].is_private; - let final_mint_price = if isPrivatePhase { - mint_input.allow_list_price.unwrap_or(0) - } else { - price_amount - }; - // ok, we are gucci. transfer funds to treasury if applicable + /// Checks completed, transfer funds to treasury if applicable system_program::transfer( CpiContext::new( @@ -187,7 +206,7 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp to: treasury.to_account_info(), }, ), - final_mint_price + price_amount )?; // take all the data for platform fee and transfer diff --git a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs new file mode 100644 index 00000000..b86ace0c --- /dev/null +++ b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs @@ -0,0 +1,54 @@ +use anchor_lang::{ + accounts::account::Account, + prelude::*, + solana_program::hash::hashv, +}; + +use rarible_merkle_verify::verify; + +use crate::{MinterStats, Phase}; + +/// We need to discern between leaf and intermediate nodes to prevent trivial second +/// pre-image attacks. +/// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack +const LEAF_PREFIX: &[u8] = &[0]; + +pub fn check_phase_constraints( + phase: &Phase, + minter: &Pubkey, + minter_stats_phase: &mut Account, + merkle_proof: Option>, + allow_list_price: Option, + allow_list_max_claims: Option, +) { + if let Some(merkle_root) = phase.merkle_root { + if let Some(proof) = merkle_proof { + if let (Some(phase_list_price), Some(phase_max_claims)) = (allow_list_price, allow_list_max_claims) { + /// 1. check constraints + /// dev: notice that if phase_max_claims is 0, this constraint is disabled + if phase_max_claims > 0 && minter_stats_phase.mint_count >= phase_max_claims { + panic!("This wallet has exceeded max_claims in the current private phase") + } + + /// 2. construct leaf + let leaf = hashv(&[ + &minter.to_bytes(), + &phase_list_price.to_le_bytes(), + &phase_max_claims.to_le_bytes(), + ]); + let node = hashv(&[LEAF_PREFIX, &leaf.to_bytes()]); + + /// 3. verify proof against merkle root + if !verify(proof, merkle_root, node.to_bytes()) { + panic!("Invalid merkle proof"); + } + } else { + panic!("Allow list price and max claims are required for allow list mint"); + } + } else { + panic!("Merkle proof required for allow list mint"); + } + } else { + panic!("Merkle root not set for allow list mint"); + } +} diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs index 1aca90d6..f85372e6 100644 --- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs @@ -4,24 +4,13 @@ use anchor_lang::{ solana_program::hash::hashv, }; -use rarible_merkle_verify::verify; - use crate::{EditionsControls, MinterStats, Phase}; -/// We need to discern between leaf and intermediate nodes to prevent trivial second -/// pre-image attacks. -/// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack -const LEAF_PREFIX: &[u8] = &[0]; - pub fn check_phase_constraints( phase: &Phase, minter_stats: &mut Account, minter_stats_phase: &mut Account, editions_controls: &Account, - merkle_proof: Option>, - minter: &Pubkey, - allow_list_price: Option, - allow_list_max_claims: Option, ) { let clock = Clock::get().unwrap(); let current_time = clock.unix_timestamp; @@ -30,7 +19,6 @@ pub fn check_phase_constraints( panic!("Phase not active") } - msg!("{} {}", phase.start_time, current_time); if phase.start_time > current_time { panic!("Phase not yet started") } @@ -42,52 +30,18 @@ pub fn check_phase_constraints( /// Checks if the total mints for the phase has been exceeded (phase sold out) /// @dev dev: notice that if max_mints_total is 0, this constraint is disabled if phase.max_mints_total > 0 && phase.current_mints >= phase.max_mints_total { - panic!("Total mints exceeded in this phase (sold out)") + panic!("Phase sold out") } /// Checks if the user has exceeded the max mints for the deployment (across all phases!) /// dev: notice that if max_mints_per_wallet is 0, this constraint is disabled if editions_controls.max_mints_per_wallet > 0 && minter_stats.mint_count >= editions_controls.max_mints_per_wallet { - panic!("This wallet has exceeded max mints for the collection across all phases") - } - - /// Check public phase only constraints - if !phase.is_private { - if phase.max_mints_per_wallet > 0 && minter_stats_phase.mint_count >= phase.max_mints_per_wallet { - panic!("This wallet has exceeded max mints in the current phase") - } + panic!("Exceeded wallet max mints for the collection") } - else { - /// check private only constraints - if let Some(merkle_root) = phase.merkle_root { - if let Some(proof) = merkle_proof { - if let (Some(price), Some(max_claims)) = (allow_list_price, allow_list_max_claims) { - /// 1. check constraints - if max_claims > 0 && minter_stats_phase.mint_count >= max_claims { - panic!("This wallet has exceeded max_claims in the current private phase") - } - - /// 2. construct leaf - let leaf = hashv(&[ - &minter.to_bytes(), - &price.to_le_bytes(), - &max_claims.to_le_bytes(), - ]); - let node = hashv(&[LEAF_PREFIX, &leaf.to_bytes()]); - - /// 3. verify proof - if !verify(proof, merkle_root, node.to_bytes()) { - panic!("Invalid merkle proof"); - } - } else { - panic!("Merkle proof required for private phase"); - } - } else { - panic!("Merkle proof required for private phase"); - } - } else { - panic!("Merkle root not set for private phase"); - } + /// Checks if the user has exceeded the max mints for the current phase + /// dev: notice that if max_mints_per_wallet is 0, this constraint is disabled + if phase.max_mints_per_wallet > 0 && minter_stats_phase.mint_count >= phase.max_mints_per_wallet { + panic!("Exceeded wallet max mints in the current phase") } } diff --git a/programs/libreplex_editions_controls/src/state.rs b/programs/libreplex_editions_controls/src/state.rs index 6f8ebdf1..f71d0a07 100644 --- a/programs/libreplex_editions_controls/src/state.rs +++ b/programs/libreplex_editions_controls/src/state.rs @@ -9,10 +9,9 @@ pub struct Phase { pub start_time: i64, // set to any date before now for instant activate pub active: bool, pub max_mints_per_wallet: u64, // set to 0 for unlimited - pub max_mints_total: u64, // set to 0 for unlimited (applied across all the phases) + pub max_mints_total: u64, // set to 0 for unlimited pub end_time: i64, // set to i64::MAX for unlimited pub current_mints: u64, - pub is_private: bool, pub merkle_root: Option<[u8; 32]>, pub padding: [u8; 200] } @@ -26,16 +25,14 @@ impl Phase { + 8 + 8 + 8 - + 1 // is_private + 33 // Option<[u8; 32]> + 200; } - #[account] pub struct MinterStats { pub wallet: Pubkey, - pub mint_count: u64, // set to any date before now for instant activate + pub mint_count: u64, pub padding: [u8; 50] } @@ -50,8 +47,8 @@ pub struct EditionsControls { pub treasury: Pubkey, // mint proceeds go here pub max_mints_per_wallet: u64, // set to 0 for unlimited (applied across all the phases) pub cosigner_program_id: Pubkey, - pub padding: [u8; 200], // in case we need some more stuff in the future pub phases: Vec, + pub padding: [u8; 200], } impl EditionsControls { From 9550021a6714321f9479a04554f6341455763003 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 17:06:00 -0300 Subject: [PATCH 06/29] advances on restructuring constraints --- .../src/instructions/mint_with_controls.rs | 52 +++++++-------- .../src/logic/check_allow_list_constraints.rs | 2 +- .../src/logic/check_phase_constraints.rs | 1 - .../src/logic/mod.rs | 3 + target/types/libreplex_editions.ts | 2 +- target/types/libreplex_editions_controls.ts | 65 ++++++++++++++++--- 6 files changed, 87 insertions(+), 38 deletions(-) diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index 129b4fbb..94f1d321 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -12,7 +12,7 @@ use libreplex_editions::{ use crate::{EditionsControls, MinterStats}; -use crate::check_phase_constraints; +use crate::{check_phase_constraints, check_allow_list_constraints}; #[derive(AnchorDeserialize, AnchorSerialize, Clone)] pub struct MintInput { @@ -34,11 +34,11 @@ pub struct MintWithControlsCtx<'info> { )] pub editions_controls: Box>, - /// CHECK: Checked via CPI + // CHECK: Checked via CPI #[account(mut)] pub hashlist: UncheckedAccount<'info>, - /// CHECK: Checked via CPI + // CHECK: Checked via CPI #[account(mut)] pub hashlist_marker: UncheckedAccount<'info>, @@ -50,11 +50,11 @@ pub struct MintWithControlsCtx<'info> { #[account(constraint = editions_controls.cosigner_program_id == system_program::ID || signer.key() == editions_deployment.creator)] pub signer: Signer<'info>, - /// CHECK: Anybody can sign, anybody can receive the inscription + // CHECK: Anybody can sign, anybody can receive the inscription #[account(mut)] pub minter: UncheckedAccount<'info>, - /// CHECK: Anybody can sign, anybody can receive the inscription + // CHECK: Anybody can sign, anybody can receive the inscription #[account(init_if_needed, payer = payer, seeds=[b"minter_stats", editions_deployment.key().as_ref(), minter.key().as_ref()], @@ -62,7 +62,7 @@ pub struct MintWithControlsCtx<'info> { space=MinterStats::SIZE)] pub minter_stats: Box>, - /// CHECK: Anybody can sign, anybody can receive the inscription + // CHECK: Anybody can sign, anybody can receive the inscription #[account(init_if_needed, payer = payer, seeds=["minter_stats_phase".as_bytes(), editions_deployment.key().as_ref(), minter.key().as_ref() @@ -77,27 +77,27 @@ pub struct MintWithControlsCtx<'info> { #[account(mut)] pub member: Signer<'info>, - /// CHECK: checked in constraint + // CHECK: checked in constraint #[account(mut, constraint = editions_deployment.group == group.key())] pub group: UncheckedAccount<'info>, - /// CHECK: Checked in constraint + // CHECK: Checked in constraint #[account(mut, constraint = editions_deployment.group_mint == group_mint.key())] pub group_mint: UncheckedAccount<'info>, - /// CHECK: passed in via CPI to mpl_token_metadata program + // CHECK: passed in via CPI to mpl_token_metadata program #[account(mut)] pub token_account: UncheckedAccount<'info>, - /// CHECK: Checked in constraint + // CHECK: Checked in constraint #[account(mut, constraint = editions_controls.treasury == treasury.key())] pub treasury: UncheckedAccount<'info>, /* BOILERPLATE PROGRAM ACCOUNTS */ - /// CHECK: Checked in constraint + // CHECK: Checked in constraint #[account( constraint = token_program.key() == token_2022::ID )] @@ -106,7 +106,7 @@ pub struct MintWithControlsCtx<'info> { #[account()] pub associated_token_program: Program<'info, AssociatedToken>, - /// CHECK: address checked + // CHECK: address checked #[account(address = group_extension_program::ID)] pub group_extension_program: AccountInfo<'info>, @@ -147,25 +147,25 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp } let phase_index = mint_input.phase_index as usize; - let current_phase = editions_controls.phases[phase_index]; + let current_phase = &editions_controls.phases[phase_index]; - /// check phase constraints + // check phase constraints check_phase_constraints( - &editions_controls.phases[phase_index], + current_phase, minter_stats, minter_stats_phase, editions_controls, ); - /// Determine if is a normal mint or an allow list mint + // Determine if is a normal mint or an allow list mint let is_allow_list_mint = mint_input.merkle_proof.is_some(); - /// If allow list mint, check allow list constraints - /// This check generates the leaf based on (minter, price, max_claims), verifies the proof, and ensures the minter has not exceeded max_claims + // If allow list mint, check allow list constraints + // This check generates the leaf based on (minter, price, max_claims), verifies the proof, and ensures the minter has not exceeded max_claims if is_allow_list_mint { check_allow_list_constraints( - &editions_controls.phases[phase_index], - minter, + current_phase, + &minter.key(), minter_stats_phase, mint_input.merkle_proof, mint_input.allow_list_price, @@ -173,7 +173,7 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp ); } - /// determine mint price, which is either the allow list price or the phase price, depending on the mint type + // determine mint price, which is either the allow list price or the phase price, depending on the mint type let price_amount = if is_allow_list_mint { mint_input.allow_list_price.unwrap_or(0) } else { @@ -185,18 +185,18 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp minter_stats.mint_count, minter_stats_phase.mint_count ); - /// Increment the minter stats across the collection + // Increment the minter stats across the collection minter_stats.wallet = minter.key(); minter_stats.mint_count += 1; - /// Increment the minter stats for the current phase + // Increment the minter stats for the current phase minter_stats_phase.wallet = minter.key(); minter_stats_phase.mint_count += 1; - /// Increment the current mints for the phase - current_phase.current_mints += 1; + // Increment the current mints for the phase + editions_controls.phases[phase_index].current_mints += 1; - /// Checks completed, transfer funds to treasury if applicable + // Checks completed, transfer funds to treasury if applicable system_program::transfer( CpiContext::new( diff --git a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs index b86ace0c..7e40bc1c 100644 --- a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs @@ -13,7 +13,7 @@ use crate::{MinterStats, Phase}; /// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack const LEAF_PREFIX: &[u8] = &[0]; -pub fn check_phase_constraints( +pub fn check_allow_list_constraints( phase: &Phase, minter: &Pubkey, minter_stats_phase: &mut Account, diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs index f85372e6..6cf03dd7 100644 --- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs @@ -1,7 +1,6 @@ use anchor_lang::{ accounts::account::Account, prelude::*, - solana_program::hash::hashv, }; use crate::{EditionsControls, MinterStats, Phase}; diff --git a/programs/libreplex_editions_controls/src/logic/mod.rs b/programs/libreplex_editions_controls/src/logic/mod.rs index 3ee46df1..749fbc95 100644 --- a/programs/libreplex_editions_controls/src/logic/mod.rs +++ b/programs/libreplex_editions_controls/src/logic/mod.rs @@ -1,3 +1,6 @@ pub mod check_phase_constraints; pub use check_phase_constraints::*; + +pub mod check_allow_list_constraints; +pub use check_allow_list_constraints::*; diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 103f5d5c..969b3969 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", "metadata": { "name": "libreplexEditions", "version": "0.2.1", diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index ca288df5..2723e8c3 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -701,15 +701,6 @@ export type LibreplexEditionsControls = { "name": "cosignerProgramId", "type": "pubkey" }, - { - "name": "padding", - "type": { - "array": [ - "u8", - 200 - ] - } - }, { "name": "phases", "type": { @@ -719,6 +710,15 @@ export type LibreplexEditionsControls = { } } } + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 200 + ] + } } ] } @@ -884,6 +884,17 @@ export type LibreplexEditionsControls = { { "name": "endTime", "type": "i64" + }, + { + "name": "merkleRoot", + "type": { + "option": { + "array": [ + "u8", + 32 + ] + } + } } ] } @@ -896,6 +907,31 @@ export type LibreplexEditionsControls = { { "name": "phaseIndex", "type": "u32" + }, + { + "name": "merkleProof", + "type": { + "option": { + "vec": { + "array": [ + "u8", + 32 + ] + } + } + } + }, + { + "name": "allowListPrice", + "type": { + "option": "u64" + } + }, + { + "name": "allowListMaxClaims", + "type": { + "option": "u64" + } } ] } @@ -962,6 +998,17 @@ export type LibreplexEditionsControls = { "name": "currentMints", "type": "u64" }, + { + "name": "merkleRoot", + "type": { + "option": { + "array": [ + "u8", + 32 + ] + } + } + }, { "name": "padding", "type": { From 7449bff973d49f360bcf7d83291f9b6c117038d5 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 17:08:10 -0300 Subject: [PATCH 07/29] stabilized with incoming changes --- .../src/instructions/initialise.rs | 2 +- programs/libreplex_editions_controls/src/state.rs | 1 - target/types/libreplex_editions_controls.ts | 9 --------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/programs/libreplex_editions_controls/src/instructions/initialise.rs b/programs/libreplex_editions_controls/src/instructions/initialise.rs index 767c8620..0d9852e9 100644 --- a/programs/libreplex_editions_controls/src/instructions/initialise.rs +++ b/programs/libreplex_editions_controls/src/instructions/initialise.rs @@ -120,7 +120,7 @@ pub fn initialise_editions_controls( editions_deployment: editions_deployment.key(), creator: creator.key(), max_mints_per_wallet: input.max_mints_per_wallet, - padding: [0; 136], + padding: [0; 200], cosigner_program_id: input.cosigner_program_id.unwrap_or(system_program::ID), phases: vec![], treasury: input.treasury, diff --git a/programs/libreplex_editions_controls/src/state.rs b/programs/libreplex_editions_controls/src/state.rs index 1c5d3381..92ce5985 100644 --- a/programs/libreplex_editions_controls/src/state.rs +++ b/programs/libreplex_editions_controls/src/state.rs @@ -52,7 +52,6 @@ pub struct EditionsControls { pub cosigner_program_id: Pubkey, pub platform_fee_primary_admin: Pubkey, pub platform_fee_secondary_admin: Pubkey, - pub padding: [u8; 136], // in case we need some more stuff in the future pub phases: Vec, pub padding: [u8; 200], } diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index a95306fd..cac4680c 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -777,15 +777,6 @@ export type LibreplexEditionsControls = { "name": "platformFeeSecondaryAdmin", "type": "pubkey" }, - { - "name": "padding", - "type": { - "array": [ - "u8", - 136 - ] - } - }, { "name": "phases", "type": { From d52d8ef4af57f7ede29ddf30fe91ab3cfd85a1cf Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 18:25:39 -0300 Subject: [PATCH 08/29] chore: struct sizes fix --- .gitignore | 2 + Anchor.toml | 8 +- .../libreplex_default_renderer/src/lib.rs | 2 +- programs/libreplex_editions/src/lib.rs | 2 +- .../merkle_tree.json | 364 ---- .../src/instructions/initialise.rs | 2 +- .../src/instructions/mint_with_controls.rs | 1 - .../libreplex_editions_controls/src/lib.rs | 2 +- .../libreplex_editions_controls/src/state.rs | 19 +- .../libreplex_monoswap_deprecated/src/lib.rs | 2 +- target/types/libreplex_default_renderer.ts | 2 +- target/types/libreplex_editions_controls.ts | 12 +- target/types/libreplex_inscriptions.ts | 1537 ----------------- target/types/libreplex_legacy.ts | 727 -------- target/types/libreplex_monoswap.ts | 2 +- target/types/libreplex_nft.ts | 183 -- 16 files changed, 29 insertions(+), 2838 deletions(-) delete mode 100644 programs/libreplex_editions_controls/merkle_tree.json delete mode 100644 target/types/libreplex_inscriptions.ts delete mode 100644 target/types/libreplex_legacy.ts delete mode 100644 target/types/libreplex_nft.ts diff --git a/.gitignore b/.gitignore index c5362743..30327dc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ **/target/* !**/target/types +.cursorrules + local-scripts .anchor diff --git a/Anchor.toml b/Anchor.toml index dcd129e2..3b203455 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -7,13 +7,13 @@ skip-lint = false [programs.localnet] libreplex_creator = "78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM" libreplex_creator_controls = "G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV" -libreplex_default_renderer = "EfC2horkE4hCaCruMvoDDWxUhcCgVaufHeG3Hkj69eR8" -libreplex_editions = "CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU" -libreplex_editions_controls = "JBoH2n87fFvyJgsty8UN3KBTEWbkv1pH7yfT8kGKSs6y" +libreplex_default_renderer = "HX1px98S4bYCKe5PTrgNpkFqJnaYwSmNeJy6xHe2PE7p" +libreplex_editions = "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" +libreplex_editions_controls = "6L4TGey3VrSMfPHaRZuBB2WpDgVFDgLHJzja3KMSj9py" libreplex_inscriptions = "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" libreplex_legacy_inscribers = "Leg1xVbrpq5gY6mprak3Ud4q4mBwcJi5C9ZruYjWv7n" libreplex_metadata = "LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p" -libreplex_monoswap_deprecated = "8Ktjj9tddWm4QM1ywrxgNRyxkNHnhBCwr9Tq3iGbPDxc" +libreplex_monoswap_deprecated = "HZAjnEyAN7Potf7cht2UArx6PAaa2pDHTRYnzLemetBp" libreplex_nft = "3oR36M1JQ9LHM1xykKTHDvhuYVM5mDAqRmq989zc1Pzi" libreplex_shop = "ListjawGEdhxuAErSyYwcTEGWQswFoi6FScnGG1RKSB" diff --git a/programs/libreplex_default_renderer/src/lib.rs b/programs/libreplex_default_renderer/src/lib.rs index 1a582c24..1b4c5b74 100644 --- a/programs/libreplex_default_renderer/src/lib.rs +++ b/programs/libreplex_default_renderer/src/lib.rs @@ -10,7 +10,7 @@ pub mod instructions; -declare_id!("EfC2horkE4hCaCruMvoDDWxUhcCgVaufHeG3Hkj69eR8"); +declare_id!("HX1px98S4bYCKe5PTrgNpkFqJnaYwSmNeJy6xHe2PE7p"); #[program] diff --git a/programs/libreplex_editions/src/lib.rs b/programs/libreplex_editions/src/lib.rs index d7d31186..4c41361c 100644 --- a/programs/libreplex_editions/src/lib.rs +++ b/programs/libreplex_editions/src/lib.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; pub mod instructions; pub use instructions::*; -declare_id!("CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU"); +declare_id!("AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA"); pub mod errors; pub mod state; diff --git a/programs/libreplex_editions_controls/merkle_tree.json b/programs/libreplex_editions_controls/merkle_tree.json deleted file mode 100644 index 6fc81e5c..00000000 --- a/programs/libreplex_editions_controls/merkle_tree.json +++ /dev/null @@ -1,364 +0,0 @@ -{ - "merkle_root": [ - 212, - 215, - 40, - 226, - 187, - 234, - 161, - 122, - 12, - 63, - 245, - 247, - 52, - 154, - 111, - 169, - 93, - 188, - 148, - 136, - 219, - 178, - 25, - 235, - 255, - 205, - 61, - 172, - 52, - 98, - 128, - 208 - ], - "max_num_nodes": 3, - "max_total_claim": 42, - "tree_nodes": [ - { - "claimant": [ - 51, - 28, - 157, - 112, - 119, - 55, - 31, - 220, - 151, - 164, - 164, - 163, - 29, - 66, - 125, - 52, - 151, - 192, - 195, - 169, - 101, - 80, - 210, - 208, - 217, - 31, - 126, - 50, - 54, - 248, - 124, - 226 - ], - "claim_price": 100000, - "max_claims": 12, - "proof": [ - [ - 202, - 170, - 250, - 249, - 215, - 254, - 191, - 180, - 143, - 8, - 101, - 61, - 96, - 18, - 111, - 116, - 175, - 51, - 102, - 96, - 79, - 82, - 3, - 104, - 223, - 162, - 98, - 185, - 106, - 181, - 185, - 67 - ], - [ - 86, - 166, - 34, - 11, - 92, - 49, - 8, - 151, - 69, - 151, - 51, - 219, - 47, - 122, - 205, - 201, - 235, - 151, - 122, - 10, - 67, - 192, - 206, - 34, - 7, - 93, - 186, - 154, - 49, - 184, - 131, - 229 - ] - ] - }, - { - "claimant": [ - 107, - 227, - 164, - 147, - 231, - 245, - 48, - 203, - 111, - 88, - 109, - 83, - 116, - 252, - 17, - 150, - 105, - 100, - 7, - 60, - 14, - 20, - 22, - 255, - 69, - 46, - 245, - 33, - 54, - 46, - 33, - 9 - ], - "claim_price": 100000, - "max_claims": 14, - "proof": [ - [ - 130, - 120, - 137, - 185, - 228, - 30, - 169, - 247, - 146, - 10, - 251, - 191, - 243, - 235, - 136, - 83, - 98, - 132, - 140, - 227, - 198, - 218, - 151, - 147, - 226, - 64, - 173, - 251, - 185, - 101, - 237, - 2 - ], - [ - 86, - 166, - 34, - 11, - 92, - 49, - 8, - 151, - 69, - 151, - 51, - 219, - 47, - 122, - 205, - 201, - 235, - 151, - 122, - 10, - 67, - 192, - 206, - 34, - 7, - 93, - 186, - 154, - 49, - 184, - 131, - 229 - ] - ] - }, - { - "claimant": [ - 134, - 175, - 237, - 81, - 136, - 244, - 104, - 181, - 193, - 46, - 207, - 35, - 204, - 52, - 215, - 244, - 165, - 237, - 140, - 209, - 18, - 211, - 7, - 43, - 22, - 156, - 99, - 139, - 118, - 148, - 249, - 249 - ], - "claim_price": 100000, - "max_claims": 16, - "proof": [ - [ - 87, - 78, - 93, - 186, - 146, - 241, - 186, - 182, - 194, - 16, - 220, - 218, - 198, - 40, - 208, - 233, - 122, - 100, - 215, - 43, - 231, - 251, - 30, - 234, - 121, - 4, - 5, - 181, - 36, - 3, - 244, - 44 - ], - [ - 42, - 96, - 10, - 26, - 96, - 112, - 34, - 28, - 8, - 163, - 63, - 156, - 131, - 123, - 182, - 225, - 81, - 189, - 55, - 101, - 184, - 123, - 83, - 199, - 160, - 43, - 156, - 85, - 140, - 8, - 99, - 127 - ] - ] - } - ] -} \ No newline at end of file diff --git a/programs/libreplex_editions_controls/src/instructions/initialise.rs b/programs/libreplex_editions_controls/src/instructions/initialise.rs index 0d9852e9..c3427d03 100644 --- a/programs/libreplex_editions_controls/src/instructions/initialise.rs +++ b/programs/libreplex_editions_controls/src/instructions/initialise.rs @@ -120,12 +120,12 @@ pub fn initialise_editions_controls( editions_deployment: editions_deployment.key(), creator: creator.key(), max_mints_per_wallet: input.max_mints_per_wallet, - padding: [0; 200], cosigner_program_id: input.cosigner_program_id.unwrap_or(system_program::ID), phases: vec![], treasury: input.treasury, platform_fee_primary_admin: DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN.parse().unwrap(), platform_fee_secondary_admin: DEFAULT_PLATFORM_FEE_SECONDARY_ADMIN.parse().unwrap(), + padding: [0; 136], }); let editions_deployment_key = editions_deployment.key(); diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index 853b80f2..bf170012 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -197,7 +197,6 @@ pub fn mint_with_controls(ctx: Context, mint_input: MintInp editions_controls.phases[phase_index].current_mints += 1; // Checks completed, transfer funds to treasury if applicable - system_program::transfer( CpiContext::new( system_program.to_account_info(), diff --git a/programs/libreplex_editions_controls/src/lib.rs b/programs/libreplex_editions_controls/src/lib.rs index 77262904..c3148eb6 100644 --- a/programs/libreplex_editions_controls/src/lib.rs +++ b/programs/libreplex_editions_controls/src/lib.rs @@ -5,7 +5,7 @@ pub use logic::*; pub mod instructions; pub use instructions::*; -declare_id!("JBoH2n87fFvyJgsty8UN3KBTEWbkv1pH7yfT8kGKSs6y"); +declare_id!("6L4TGey3VrSMfPHaRZuBB2WpDgVFDgLHJzja3KMSj9py"); pub mod errors; pub mod state; diff --git a/programs/libreplex_editions_controls/src/state.rs b/programs/libreplex_editions_controls/src/state.rs index 92ce5985..742f6905 100644 --- a/programs/libreplex_editions_controls/src/state.rs +++ b/programs/libreplex_editions_controls/src/state.rs @@ -1,7 +1,6 @@ use anchor_lang::prelude::*; use solana_program::pubkey::Pubkey; -// max length: 8 + 8 + 8 + 1 + 8 = 33 #[derive(Clone, AnchorDeserialize, AnchorSerialize)] pub struct Phase { pub price_amount: u64, @@ -13,20 +12,22 @@ pub struct Phase { pub end_time: i64, // set to i64::MAX for unlimited pub current_mints: u64, pub merkle_root: Option<[u8; 32]>, - pub padding: [u8; 200] + pub padding: [u8; 136] } impl Phase { - pub const SIZE: usize = 8 - + 32 + pub const SIZE: usize = + 8 + + 8 + + 32 + 8 + 1 + 8 + 8 + 8 + 8 - + 33 // Option<[u8; 32]> - + 200; + + 33 + + 136; } pub const DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN: &str = "674s1Sap3KVnr8WGrY5KGQ69oTYjjgr1disKJo6GpTYw"; @@ -40,7 +41,7 @@ pub struct MinterStats { } impl MinterStats { - pub const SIZE: usize = 8 + 32 + 8 + 8 + 50; + pub const SIZE: usize = 8 + 32 + 8 + 50; } #[account] @@ -53,11 +54,11 @@ pub struct EditionsControls { pub platform_fee_primary_admin: Pubkey, pub platform_fee_secondary_admin: Pubkey, pub phases: Vec, - pub padding: [u8; 200], + pub padding: [u8; 136], } impl EditionsControls { - pub const INITIAL_SIZE: usize = 8 + 32 + 32 + 32 + 8 + 32 + 200 + 4; + pub const INITIAL_SIZE: usize = 8 + 32 + 32 + 32 + 8 + 32 + 32 + 32 + 4 + 136; pub fn get_size(number_of_phases: usize) -> usize { EditionsControls::INITIAL_SIZE + Phase::SIZE * number_of_phases } diff --git a/programs/libreplex_monoswap_deprecated/src/lib.rs b/programs/libreplex_monoswap_deprecated/src/lib.rs index 85abec9b..2bfa3cfa 100644 --- a/programs/libreplex_monoswap_deprecated/src/lib.rs +++ b/programs/libreplex_monoswap_deprecated/src/lib.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -declare_id!("8Ktjj9tddWm4QM1ywrxgNRyxkNHnhBCwr9Tq3iGbPDxc"); +declare_id!("HZAjnEyAN7Potf7cht2UArx6PAaa2pDHTRYnzLemetBp"); pub mod constants; pub mod errors; diff --git a/target/types/libreplex_default_renderer.ts b/target/types/libreplex_default_renderer.ts index 312568ba..7216c2aa 100644 --- a/target/types/libreplex_default_renderer.ts +++ b/target/types/libreplex_default_renderer.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_default_renderer.json`. */ export type LibreplexDefaultRenderer = { - "address": "EfC2horkE4hCaCruMvoDDWxUhcCgVaufHeG3Hkj69eR8", + "address": "HX1px98S4bYCKe5PTrgNpkFqJnaYwSmNeJy6xHe2PE7p", "metadata": { "name": "libreplexDefaultRenderer", "version": "0.1.2", diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index cac4680c..7675a07b 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions_controls.json`. */ export type LibreplexEditionsControls = { - "address": "JBoH2n87fFvyJgsty8UN3KBTEWbkv1pH7yfT8kGKSs6y", + "address": "6L4TGey3VrSMfPHaRZuBB2WpDgVFDgLHJzja3KMSj9py", "metadata": { "name": "libreplexEditionsControls", "version": "0.2.1", @@ -51,7 +51,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU" + "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" } ], "args": [ @@ -152,7 +152,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU" + "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" } ], "args": [ @@ -357,7 +357,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU" + "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" } ], "args": [ @@ -446,7 +446,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU" + "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" } ], "args": [ @@ -603,7 +603,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "CeGRFA9sFRbfhaUVWj4hi3oDezCD8o56abbdMoeAkYBU" + "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" } ], "args": [ diff --git a/target/types/libreplex_inscriptions.ts b/target/types/libreplex_inscriptions.ts deleted file mode 100644 index 6f19a5af..00000000 --- a/target/types/libreplex_inscriptions.ts +++ /dev/null @@ -1,1537 +0,0 @@ -/** - * Program IDL in camelCase format in order to be used in JS/TS. - * - * Note that this is only a type helper and is not the actual IDL. The original - * IDL can be found at `target/idl/libreplex_inscriptions.json`. - */ -export type LibreplexInscriptions = { - "address": "", - "metadata": { - "name": "libreplexInscriptions", - "version": "0.5.1", - "spec": "0.1.0", - "description": "Inscriptions from LibrePlex", - "repository": "https://github.com/LibrePlex/libreplex-program-library" - }, - "instructions": [ - { - "name": "claimExcessRent", - "discriminator": [ - 106, - 43, - 164, - 6, - 220, - 74, - 225, - 17 - ], - "accounts": [ - { - "name": "authority", - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "inscriptionV3" - }, - { - "name": "inscriptionData", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 100, - 97, - 116, - 97 - ] - }, - { - "kind": "account", - "path": "inscription_v3.root", - "account": "inscriptionV3" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "createGhostRootInscription", - "discriminator": [ - 168, - 13, - 68, - 18, - 130, - 171, - 33, - 121 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "signer", - "writable": true, - "signer": true - }, - { - "name": "inscriptionSummary", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 115, - 117, - 109, - 109, - 97, - 114, - 121 - ] - } - ] - } - }, - { - "name": "inscriptionData", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 100, - 97, - 116, - 97 - ] - }, - { - "kind": "arg", - "path": "inscription_input.root" - } - ] - } - }, - { - "name": "inscriptionV3", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 118, - 51 - ] - }, - { - "kind": "arg", - "path": "inscription_input.root" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "inscriptionInput", - "type": { - "defined": { - "name": "createGhostRootInscriptionInput" - } - } - } - ] - }, - { - "name": "createInscriptionRankPage", - "discriminator": [ - 250, - 76, - 231, - 197, - 254, - 132, - 31, - 136 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "page", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 114, - 97, - 110, - 107 - ] - }, - { - "kind": "arg", - "path": "input.page_index" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "createInscriptionRankInput" - } - } - } - ] - }, - { - "name": "createInscriptionV3", - "discriminator": [ - 168, - 43, - 77, - 230, - 240, - 247, - 161, - 175 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "signer", - "writable": true, - "signer": true - }, - { - "name": "root" - }, - { - "name": "inscriptionSummary", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 115, - 117, - 109, - 109, - 97, - 114, - 121 - ] - } - ] - } - }, - { - "name": "inscriptionData", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 100, - 97, - 116, - 97 - ] - }, - { - "kind": "account", - "path": "root" - } - ] - } - }, - { - "name": "inscriptionV3", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 118, - 51 - ] - }, - { - "kind": "account", - "path": "root" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "inscriptionInput", - "type": { - "defined": { - "name": "createInscriptionInputV3" - } - } - } - ] - }, - { - "name": "makeInscriptionImmutable", - "discriminator": [ - 161, - 66, - 113, - 199, - 96, - 233, - 223, - 172 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "authority", - "signer": true - }, - { - "name": "inscriptionSummary", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 115, - 117, - 109, - 109, - 97, - 114, - 121 - ] - } - ] - } - }, - { - "name": "inscription", - "writable": true - }, - { - "name": "inscription2", - "writable": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "makeInscriptionImmutableV3", - "discriminator": [ - 98, - 71, - 230, - 185, - 46, - 129, - 233, - 48 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "authority", - "signer": true - }, - { - "name": "inscriptionSummary", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 115, - 117, - 109, - 109, - 97, - 114, - 121 - ] - } - ] - } - }, - { - "name": "inscriptionV3", - "writable": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "migrateToV3", - "discriminator": [ - 255, - 126, - 224, - 187, - 202, - 169, - 102, - 214 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "root" - }, - { - "name": "migrator", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 109, - 105, - 103, - 114, - 97, - 116, - 111, - 114 - ] - }, - { - "kind": "account", - "path": "root" - } - ] - } - }, - { - "name": "inscription", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110 - ] - }, - { - "kind": "account", - "path": "root" - } - ] - } - }, - { - "name": "inscription2", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 118, - 51 - ] - }, - { - "kind": "account", - "path": "root" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - }, - { - "name": "resizeInscriptionV3", - "discriminator": [ - 129, - 45, - 209, - 5, - 231, - 155, - 217, - 211 - ], - "accounts": [ - { - "name": "authority", - "writable": true, - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "inscriptionV3", - "writable": true - }, - { - "name": "inscriptionData", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110, - 95, - 100, - 97, - 116, - 97 - ] - }, - { - "kind": "account", - "path": "inscription_v3.root", - "account": "inscriptionV3" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "resizeInscriptionInput" - } - } - } - ] - }, - { - "name": "setValidationHash", - "discriminator": [ - 220, - 65, - 43, - 233, - 137, - 215, - 187, - 229 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "signer", - "writable": true, - "signer": true - }, - { - "name": "inscription", - "writable": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - }, - { - "name": "writeToInscriptionV3", - "discriminator": [ - 118, - 248, - 99, - 188, - 226, - 144, - 151, - 46 - ], - "accounts": [ - { - "name": "authority", - "writable": true, - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "inscriptionV3", - "writable": true - }, - { - "name": "inscriptionData", - "writable": true - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "writeToInscriptionInput" - } - } - } - ] - } - ], - "accounts": [ - { - "name": "inscription", - "discriminator": [ - 100, - 11, - 151, - 42, - 228, - 38, - 69, - 187 - ] - }, - { - "name": "inscriptionData", - "discriminator": [ - 44, - 58, - 66, - 156, - 82, - 136, - 80, - 64 - ] - }, - { - "name": "inscriptionRankPage", - "discriminator": [ - 192, - 133, - 192, - 195, - 42, - 49, - 155, - 198 - ] - }, - { - "name": "inscriptionSummary", - "discriminator": [ - 189, - 189, - 190, - 90, - 73, - 71, - 253, - 107 - ] - }, - { - "name": "inscriptionV3", - "discriminator": [ - 232, - 120, - 205, - 47, - 153, - 239, - 229, - 224 - ] - }, - { - "name": "migrator", - "discriminator": [ - 74, - 71, - 185, - 52, - 75, - 186, - 114, - 78 - ] - } - ], - "types": [ - { - "name": "createGhostRootInscriptionInput", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": { - "option": "pubkey" - } - }, - { - "name": "signerType", - "type": { - "defined": { - "name": "signerType" - } - } - }, - { - "name": "validationHash", - "type": { - "option": "string" - } - }, - { - "name": "root", - "type": "pubkey" - } - ] - } - }, - { - "name": "createInscriptionInputV3", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": { - "option": "pubkey" - } - }, - { - "name": "signerType", - "type": { - "defined": { - "name": "signerType" - } - } - }, - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - } - }, - { - "name": "createInscriptionRankInput", - "type": { - "kind": "struct", - "fields": [ - { - "name": "pageIndex", - "type": "u32" - } - ] - } - }, - { - "name": "encodingType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "none" - }, - { - "name": "base64" - } - ] - } - }, - { - "name": "inscription", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "pubkey" - }, - { - "name": "root", - "type": "pubkey" - }, - { - "name": "mediaType", - "type": { - "defined": { - "name": "mediaType" - } - } - }, - { - "name": "encodingType", - "type": { - "defined": { - "name": "encodingType" - } - } - }, - { - "name": "inscriptionData", - "type": "pubkey" - }, - { - "name": "order", - "type": "u64" - }, - { - "name": "size", - "type": "u32" - }, - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - } - }, - { - "name": "inscriptionData", - "type": { - "kind": "struct", - "fields": [] - } - }, - { - "name": "inscriptionEventCreate", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - }, - { - "name": "data", - "type": { - "defined": { - "name": "inscriptionEventData" - } - } - } - ] - } - }, - { - "name": "inscriptionEventData", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "pubkey" - }, - { - "name": "root", - "type": "pubkey" - }, - { - "name": "mediaType", - "type": { - "defined": { - "name": "mediaType" - } - } - }, - { - "name": "encodingType", - "type": { - "defined": { - "name": "encodingType" - } - } - }, - { - "name": "inscriptionData", - "type": "pubkey" - }, - { - "name": "order", - "type": "u64" - }, - { - "name": "size", - "type": "u32" - }, - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - } - }, - { - "name": "inscriptionEventUpdate", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - }, - { - "name": "data", - "type": { - "defined": { - "name": "inscriptionEventData" - } - } - } - ] - } - }, - { - "name": "inscriptionRankPage", - "type": { - "kind": "struct", - "fields": [ - { - "name": "size", - "type": "u32" - } - ] - } - }, - { - "name": "inscriptionSummary", - "type": { - "kind": "struct", - "fields": [ - { - "name": "inscriptionCountTotal", - "type": "u64" - }, - { - "name": "inscriptionCountImmutables", - "type": "u64" - }, - { - "name": "lastInscription", - "type": "pubkey" - }, - { - "name": "lastInscriber", - "type": "pubkey" - }, - { - "name": "lastInscriptionCreateTime", - "type": "i64" - }, - { - "name": "extension", - "type": { - "defined": { - "name": "summaryExtension" - } - } - } - ] - } - }, - { - "name": "inscriptionV3", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "pubkey" - }, - { - "name": "root", - "type": "pubkey" - }, - { - "name": "inscriptionData", - "type": "pubkey" - }, - { - "name": "order", - "type": "u64" - }, - { - "name": "size", - "type": "u32" - }, - { - "name": "contentType", - "type": "string" - }, - { - "name": "encoding", - "type": "string" - }, - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - } - }, - { - "name": "inscriptionV3EventCreate", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - }, - { - "name": "data", - "type": { - "defined": { - "name": "inscriptionV3EventData" - } - } - } - ] - } - }, - { - "name": "inscriptionV3EventData", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "pubkey" - }, - { - "name": "root", - "type": "pubkey" - }, - { - "name": "contentType", - "type": "string" - }, - { - "name": "encoding", - "type": "string" - }, - { - "name": "inscriptionData", - "type": "pubkey" - }, - { - "name": "order", - "type": "u64" - }, - { - "name": "size", - "type": "u32" - }, - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - } - }, - { - "name": "inscriptionV3EventUpdate", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - }, - { - "name": "data", - "type": { - "defined": { - "name": "inscriptionV3EventData" - } - } - } - ] - } - }, - { - "name": "mediaType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "none" - }, - { - "name": "audio", - "fields": [ - { - "name": "subtype", - "type": "string" - } - ] - }, - { - "name": "application", - "fields": [ - { - "name": "subtype", - "type": "string" - } - ] - }, - { - "name": "image", - "fields": [ - { - "name": "subtype", - "type": "string" - } - ] - }, - { - "name": "video", - "fields": [ - { - "name": "subtype", - "type": "string" - } - ] - }, - { - "name": "text", - "fields": [ - { - "name": "subtype", - "type": "string" - } - ] - }, - { - "name": "custom", - "fields": [ - { - "name": "mediaType", - "type": "string" - } - ] - }, - { - "name": "erc721" - } - ] - } - }, - { - "name": "migrator", - "type": { - "kind": "struct", - "fields": [ - { - "name": "root", - "type": "pubkey" - }, - { - "name": "migrator", - "type": "pubkey" - } - ] - } - }, - { - "name": "resizeInscriptionInput", - "type": { - "kind": "struct", - "fields": [ - { - "name": "change", - "type": "i32" - }, - { - "name": "expectedStartSize", - "type": "u32" - }, - { - "name": "targetSize", - "type": "u32" - } - ] - } - }, - { - "name": "signerType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "root" - }, - { - "name": "legacyMetadataSigner" - }, - { - "name": "fairLaunchGhostRootSigner" - } - ] - } - }, - { - "name": "summaryExtension", - "type": { - "kind": "enum", - "variants": [ - { - "name": "none" - } - ] - } - }, - { - "name": "writeToInscriptionInput", - "type": { - "kind": "struct", - "fields": [ - { - "name": "data", - "type": "bytes" - }, - { - "name": "startPos", - "type": "u32" - }, - { - "name": "mediaType", - "type": { - "option": "string" - } - }, - { - "name": "encodingType", - "type": { - "option": "string" - } - } - ] - } - }, - { - "name": "libreplex_inscriptions::instructions::resize_inscription::InscriptionResizeEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - }, - { - "name": "size", - "type": "u32" - } - ] - } - }, - { - "name": "libreplex_inscriptions::instructions::v3::resize_inscription_v3::InscriptionResizeEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - }, - { - "name": "size", - "type": "u32" - } - ] - } - }, - { - "name": "libreplex_inscriptions::instructions::v3::write_to_inscription_v3::InscriptionWriteEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - } - ] - } - }, - { - "name": "libreplex_inscriptions::instructions::write_to_inscription::InscriptionWriteEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "pubkey" - } - ] - } - } - ] -}; diff --git a/target/types/libreplex_legacy.ts b/target/types/libreplex_legacy.ts deleted file mode 100644 index 826ffc12..00000000 --- a/target/types/libreplex_legacy.ts +++ /dev/null @@ -1,727 +0,0 @@ -/** - * Program IDL in camelCase format in order to be used in JS/TS. - * - * Note that this is only a type helper and is not the actual IDL. The original - * IDL can be found at `target/idl/libreplex_legacy.json`. - */ -export type LibreplexLegacy = { - "address": "", - "metadata": { - "name": "libreplexLegacy", - "version": "0.2.0", - "spec": "0.1.0", - "description": "Created with Anchor", - "repository": "https://github.com/LibrePlex/metadata" - }, - "instructions": [ - { - "name": "claimExcessRentAsUauth", - "discriminator": [ - 98, - 206, - 169, - 44, - 204, - 10, - 47, - 111 - ], - "accounts": [ - { - "name": "authority", - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "mint" - }, - { - "name": "legacyMetadata" - }, - { - "name": "inscriptionV3" - }, - { - "name": "inscriptionData", - "writable": true - }, - { - "name": "legacyInscription", - "docs": [ - "is passed on to inscriptions program as the authority of the INSCRIPTION" - ], - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 108, - 101, - 103, - 97, - 99, - 121, - 95, - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110 - ] - }, - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "inscriptionsProgram", - "address": "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" - } - ], - "args": [] - }, - { - "name": "inscribeLegacyMetadataAsUauthV3", - "discriminator": [ - 106, - 73, - 39, - 184, - 151, - 74, - 183, - 66 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "legacySigner", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "mint" - }, - { - "name": "inscriptionV3", - "writable": true - }, - { - "name": "inscriptionData", - "writable": true - }, - { - "name": "inscriptionSummary", - "writable": true - }, - { - "name": "legacyInscription", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 108, - 101, - 103, - 97, - 99, - 121, - 95, - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110 - ] - }, - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "legacyMetadata" - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "inscriptionsProgram", - "address": "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" - } - ], - "args": [ - { - "name": "validationHash", - "type": "string" - } - ] - }, - { - "name": "makeLegacyInscriptionImmutableV3", - "discriminator": [ - 162, - 51, - 73, - 185, - 59, - 65, - 177, - 64 - ], - "accounts": [ - { - "name": "authority", - "writable": true, - "signer": true - }, - { - "name": "mint" - }, - { - "name": "inscriptionV3", - "writable": true - }, - { - "name": "inscriptionSummary", - "writable": true - }, - { - "name": "legacyMetadata" - }, - { - "name": "legacyInscription", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 108, - 101, - 103, - 97, - 99, - 121, - 95, - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110 - ] - }, - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "inscriptionsProgram", - "address": "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" - } - ], - "args": [] - }, - { - "name": "resizeLegacyInscriptionAsUauthV3", - "discriminator": [ - 69, - 5, - 7, - 55, - 175, - 204, - 134, - 9 - ], - "accounts": [ - { - "name": "authority", - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "mint" - }, - { - "name": "legacyMetadata" - }, - { - "name": "inscriptionV3", - "writable": true - }, - { - "name": "inscriptionData", - "writable": true - }, - { - "name": "legacyInscription", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 108, - 101, - 103, - 97, - 99, - 121, - 95, - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110 - ] - }, - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "inscriptionsProgram", - "address": "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" - } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "resizeLegacyInscriptionInput" - } - } - } - ] - }, - { - "name": "setValidationHash", - "discriminator": [ - 220, - 65, - 43, - 233, - 137, - 215, - 187, - 229 - ], - "accounts": [ - { - "name": "authority", - "writable": true, - "signer": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "mint" - }, - { - "name": "inscription", - "writable": true - }, - { - "name": "legacyMetadata" - }, - { - "name": "legacyInscription", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 108, - 101, - 103, - 97, - 99, - 121, - 95, - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110 - ] - }, - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "inscriptionsProgram", - "address": "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" - } - ], - "args": [ - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - }, - { - "name": "writeToLegacyInscriptionAsUauthV3", - "discriminator": [ - 241, - 255, - 17, - 224, - 137, - 137, - 5, - 221 - ], - "accounts": [ - { - "name": "authority", - "writable": true, - "signer": true - }, - { - "name": "mint" - }, - { - "name": "inscriptionV3", - "writable": true - }, - { - "name": "inscriptionData", - "writable": true - }, - { - "name": "legacyMetadata" - }, - { - "name": "legacyInscription", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 108, - 101, - 103, - 97, - 99, - 121, - 95, - 105, - 110, - 115, - 99, - 114, - 105, - 112, - 116, - 105, - 111, - 110 - ] - }, - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "inscriptionsProgram", - "address": "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" - } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "writeToInscriptionInput" - } - } - } - ] - } - ], - "accounts": [ - { - "name": "inscriptionV3", - "discriminator": [ - 232, - 120, - 205, - 47, - 153, - 239, - 229, - 224 - ] - }, - { - "name": "legacyInscription", - "discriminator": [ - 0, - 39, - 45, - 80, - 158, - 103, - 99, - 146 - ] - } - ], - "types": [ - { - "name": "authorityType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "holder" - }, - { - "name": "updateAuthority" - } - ] - } - }, - { - "name": "inscriptionV3", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "pubkey" - }, - { - "name": "root", - "type": "pubkey" - }, - { - "name": "inscriptionData", - "type": "pubkey" - }, - { - "name": "order", - "type": "u64" - }, - { - "name": "size", - "type": "u32" - }, - { - "name": "contentType", - "type": "string" - }, - { - "name": "encoding", - "type": "string" - }, - { - "name": "validationHash", - "type": { - "option": "string" - } - } - ] - } - }, - { - "name": "legacyInscription", - "type": { - "kind": "struct", - "fields": [ - { - "name": "mint", - "type": "pubkey" - }, - { - "name": "inscription", - "type": "pubkey" - }, - { - "name": "legacyType", - "type": { - "defined": { - "name": "legacyType" - } - } - }, - { - "name": "authorityType", - "type": { - "defined": { - "name": "authorityType" - } - } - } - ] - } - }, - { - "name": "legacyType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "metaplexMint" - } - ] - } - }, - { - "name": "resizeLegacyInscriptionInput", - "type": { - "kind": "struct", - "fields": [ - { - "name": "change", - "type": "i32" - }, - { - "name": "expectedStartSize", - "type": "u32" - }, - { - "name": "targetSize", - "type": "u32" - } - ] - } - }, - { - "name": "writeToInscriptionInput", - "type": { - "kind": "struct", - "fields": [ - { - "name": "data", - "type": "bytes" - }, - { - "name": "startPos", - "type": "u32" - }, - { - "name": "mediaType", - "type": { - "option": "string" - } - }, - { - "name": "encodingType", - "type": { - "option": "string" - } - } - ] - } - } - ] -}; diff --git a/target/types/libreplex_monoswap.ts b/target/types/libreplex_monoswap.ts index 28b3c05e..483a2742 100644 --- a/target/types/libreplex_monoswap.ts +++ b/target/types/libreplex_monoswap.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_monoswap.json`. */ export type LibreplexMonoswap = { - "address": "8Ktjj9tddWm4QM1ywrxgNRyxkNHnhBCwr9Tq3iGbPDxc", + "address": "HZAjnEyAN7Potf7cht2UArx6PAaa2pDHTRYnzLemetBp", "metadata": { "name": "libreplexMonoswap", "version": "0.0.0", diff --git a/target/types/libreplex_nft.ts b/target/types/libreplex_nft.ts deleted file mode 100644 index 44c1ae58..00000000 --- a/target/types/libreplex_nft.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Program IDL in camelCase format in order to be used in JS/TS. - * - * Note that this is only a type helper and is not the actual IDL. The original - * IDL can be found at `target/idl/libreplex_nft.json`. - */ -export type LibreplexNft = { - "address": "3oR36M1JQ9LHM1xykKTHDvhuYVM5mDAqRmq989zc1Pzi", - "metadata": { - "name": "libreplexNft", - "version": "0.10.0", - "spec": "0.1.0", - "description": "Created with Anchor", - "repository": "https://github.com/LibrePlex/metadata" - }, - "instructions": [ - { - "name": "toggleFreeze", - "discriminator": [ - 136, - 3, - 54, - 90, - 252, - 134, - 158, - 172 - ], - "accounts": [ - { - "name": "delegate", - "signer": true - }, - { - "name": "mint" - }, - { - "name": "tokenAccount", - "writable": true - }, - { - "name": "wrappedMint", - "pda": { - "seeds": [ - { - "kind": "account", - "path": "token_account.mint" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" - } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "toggleFreezeInput" - } - } - } - ] - }, - { - "name": "wrap", - "discriminator": [ - 178, - 40, - 10, - 189, - 228, - 129, - 186, - 140 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "authority", - "writable": true, - "signer": true - }, - { - "name": "mint", - "writable": true - }, - { - "name": "wrappedMint", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - }, - { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" - } - ], - "args": [] - } - ], - "accounts": [ - { - "name": "wrappedMint", - "discriminator": [ - 154, - 132, - 105, - 241, - 240, - 244, - 36, - 193 - ] - } - ], - "errors": [ - { - "code": 6000, - "name": "invalidMintAuthority", - "msg": "invalidMintAuthority" - }, - { - "code": 6001, - "name": "mintCannotRepresentNft", - "msg": "mintCannotRepresentNft" - }, - { - "code": 6002, - "name": "invalidMint", - "msg": "invalidMint" - }, - { - "code": 6003, - "name": "invalidTokenAccount", - "msg": "invalidTokenAccount" - } - ], - "types": [ - { - "name": "toggleFreezeInput", - "type": { - "kind": "enum", - "variants": [ - { - "name": "freeze" - }, - { - "name": "unfreeze" - } - ] - } - }, - { - "name": "wrappedMint", - "type": { - "kind": "struct", - "fields": [] - } - } - ] -}; From 454072768a79bebdd6cb93c186f3d34073001ce0 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 18:26:47 -0300 Subject: [PATCH 09/29] chore: struct sizes fix --- .cursorrules | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .cursorrules diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 3eaaab40..00000000 --- a/.cursorrules +++ /dev/null @@ -1,31 +0,0 @@ -You are an expert Solana blockchain developer with deep knowledge of the Anchor framework and Rust programming language. Your task is to assist with refactoring contracts and SDKs for Rarible on the Solana blockchain. - -When providing assistance: - -1. Prioritize Solana best practices and Anchor idioms in all code suggestions. -2. Ensure all code follows Rust safety and performance guidelines. -3. Consider scalability and gas efficiency in contract designs. -4. Suggest ways to improve code readability and maintainability. -5. Provide explanations for complex Anchor concepts or Solana-specific patterns. -6. When refactoring, maintain or improve existing functionality while enhancing code quality. -7. Be aware of common security vulnerabilities in Solana smart contracts and suggest mitigations. -8. Recommend appropriate Anchor program structures, accounts, and instructions for given tasks. -9. Offer insights on optimizing Solana transaction throughput and minimizing compute units. -10. Suggest relevant Solana and Anchor testing strategies for refactored code. - -Always consider the context of Rarible's NFT marketplace when providing suggestions or solutions. If you're unsure about any Rarible-specific requirements, ask for clarification before proceeding. - -Current Task: -[Contracts] - Add merkle tree for each phase - -Description: when creating a new phase, a merkle tree will be used if the phase is private. we will need to modify the phase struct, and the check phase constraints, that if the phase is private, it will have to check the merkle tree. - -Please provide a detailed step-by-step plan to accomplish this task, including: - -1. Analysis of the current codebase and identification of areas that need modification -2. Proposed changes to the contract structure or existing functions -3. New functions or structs that need to be implemented -4. Any necessary changes to the SDK -5. Considerations for gas efficiency and security -6. Testing strategy for the new functionality -7. Any potential impacts on existing features or integrations From 29c08b1a42c3eda17b6bcd89f6b5320bb3b797d3 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 18:32:17 -0300 Subject: [PATCH 10/29] chore: add phase struct input resize --- cursorrules | 31 +++++++++++++ .../src/instructions/add_phase.rs | 2 +- target/types/libreplex_editions.ts | 45 +++++++++---------- target/types/libreplex_editions_controls.ts | 4 +- 4 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 cursorrules diff --git a/cursorrules b/cursorrules new file mode 100644 index 00000000..3eaaab40 --- /dev/null +++ b/cursorrules @@ -0,0 +1,31 @@ +You are an expert Solana blockchain developer with deep knowledge of the Anchor framework and Rust programming language. Your task is to assist with refactoring contracts and SDKs for Rarible on the Solana blockchain. + +When providing assistance: + +1. Prioritize Solana best practices and Anchor idioms in all code suggestions. +2. Ensure all code follows Rust safety and performance guidelines. +3. Consider scalability and gas efficiency in contract designs. +4. Suggest ways to improve code readability and maintainability. +5. Provide explanations for complex Anchor concepts or Solana-specific patterns. +6. When refactoring, maintain or improve existing functionality while enhancing code quality. +7. Be aware of common security vulnerabilities in Solana smart contracts and suggest mitigations. +8. Recommend appropriate Anchor program structures, accounts, and instructions for given tasks. +9. Offer insights on optimizing Solana transaction throughput and minimizing compute units. +10. Suggest relevant Solana and Anchor testing strategies for refactored code. + +Always consider the context of Rarible's NFT marketplace when providing suggestions or solutions. If you're unsure about any Rarible-specific requirements, ask for clarification before proceeding. + +Current Task: +[Contracts] - Add merkle tree for each phase + +Description: when creating a new phase, a merkle tree will be used if the phase is private. we will need to modify the phase struct, and the check phase constraints, that if the phase is private, it will have to check the merkle tree. + +Please provide a detailed step-by-step plan to accomplish this task, including: + +1. Analysis of the current codebase and identification of areas that need modification +2. Proposed changes to the contract structure or existing functions +3. New functions or structs that need to be implemented +4. Any necessary changes to the SDK +5. Considerations for gas efficiency and security +6. Testing strategy for the new functionality +7. Any potential impacts on existing features or integrations diff --git a/programs/libreplex_editions_controls/src/instructions/add_phase.rs b/programs/libreplex_editions_controls/src/instructions/add_phase.rs index 1eeba069..b9acaf30 100644 --- a/programs/libreplex_editions_controls/src/instructions/add_phase.rs +++ b/programs/libreplex_editions_controls/src/instructions/add_phase.rs @@ -57,10 +57,10 @@ pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Resu max_mints_per_wallet: input.max_mints_per_wallet, active: true, // everything starts out as active - end_time: input.end_time, - padding: [0; 200], max_mints_total: input.max_mints_total, current_mints: 0, merkle_root: input.merkle_root, + padding: [0; 136], }); Ok(()) diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 969b3969..576514e5 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -869,58 +869,53 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "sizeExceedsMaxSize", - "msg": "Collection size exceeds max size." + "name": "tickerTooLong", + "msg": "Ticker too long" }, { "code": 6001, - "name": "maxSizeBelowCurrentSize", - "msg": "Max size cannot be reduced below current size." + "name": "mintTemplateTooLong", + "msg": "Mint template too long" }, { "code": 6002, - "name": "creatorShareInvalid", - "msg": "Creators shares must add up to 100." + "name": "deploymentTemplateTooLong", + "msg": "Deployment template too long" }, { "code": 6003, - "name": "missingApproveAccount", - "msg": "Missing approve account." + "name": "rootTypeTooLong", + "msg": "Root type too long" }, { "code": 6004, - "name": "expiredApproveAccount", - "msg": "Approve account has expired." + "name": "mintedOut", + "msg": "Minted out" }, { "code": 6005, - "name": "invalidField", - "msg": "Invalid field. You cannot use a public key as a field." + "name": "legacyMigrationsAreMintedOut", + "msg": "Legacy migrations are minted out" }, { "code": 6006, - "name": "creatorAddressInvalid", - "msg": "The Address you provided is invalid. Please provide a valid address." + "name": "missingGlobalTreeDelegate", + "msg": "Global tree delegate is missing" }, { "code": 6007, - "name": "royaltyBasisPointsInvalid", - "msg": "Royalty basis points must be less than or equal to 10000." + "name": "incorrectMintType", + "msg": "Incorrect mint type" }, { "code": 6008, - "name": "platformFeeBasisPointsInvalid", - "msg": "Platform fee basis points must be less than or equal to 10000." + "name": "invalidMetadata", + "msg": "Invalid Metadata" }, { "code": 6009, - "name": "recipientShareInvalid", - "msg": "Recipient shares must add up to 100." - }, - { - "code": 6010, - "name": "reservedField", - "msg": "The provided field is invalid or reserved." + "name": "creatorFeeTooHigh", + "msg": "Creator fee too high" } ], "types": [ diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index 7675a07b..8dad884c 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -792,7 +792,7 @@ export type LibreplexEditionsControls = { "type": { "array": [ "u8", - 200 + 136 ] } } @@ -1090,7 +1090,7 @@ export type LibreplexEditionsControls = { "type": { "array": [ "u8", - 200 + 136 ] } } From aed164bfa45d6db1d2e995ab0fea567429780ee7 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 18:34:09 -0300 Subject: [PATCH 11/29] chore: add phase struct input resize --- .gitignore | 1 + cursorrules | 31 ------------------------------- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 cursorrules diff --git a/.gitignore b/.gitignore index 30327dc1..7b91373e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .cursorrules + local-scripts .anchor diff --git a/cursorrules b/cursorrules deleted file mode 100644 index 3eaaab40..00000000 --- a/cursorrules +++ /dev/null @@ -1,31 +0,0 @@ -You are an expert Solana blockchain developer with deep knowledge of the Anchor framework and Rust programming language. Your task is to assist with refactoring contracts and SDKs for Rarible on the Solana blockchain. - -When providing assistance: - -1. Prioritize Solana best practices and Anchor idioms in all code suggestions. -2. Ensure all code follows Rust safety and performance guidelines. -3. Consider scalability and gas efficiency in contract designs. -4. Suggest ways to improve code readability and maintainability. -5. Provide explanations for complex Anchor concepts or Solana-specific patterns. -6. When refactoring, maintain or improve existing functionality while enhancing code quality. -7. Be aware of common security vulnerabilities in Solana smart contracts and suggest mitigations. -8. Recommend appropriate Anchor program structures, accounts, and instructions for given tasks. -9. Offer insights on optimizing Solana transaction throughput and minimizing compute units. -10. Suggest relevant Solana and Anchor testing strategies for refactored code. - -Always consider the context of Rarible's NFT marketplace when providing suggestions or solutions. If you're unsure about any Rarible-specific requirements, ask for clarification before proceeding. - -Current Task: -[Contracts] - Add merkle tree for each phase - -Description: when creating a new phase, a merkle tree will be used if the phase is private. we will need to modify the phase struct, and the check phase constraints, that if the phase is private, it will have to check the merkle tree. - -Please provide a detailed step-by-step plan to accomplish this task, including: - -1. Analysis of the current codebase and identification of areas that need modification -2. Proposed changes to the contract structure or existing functions -3. New functions or structs that need to be implemented -4. Any necessary changes to the SDK -5. Considerations for gas efficiency and security -6. Testing strategy for the new functionality -7. Any potential impacts on existing features or integrations From 69e1eda69f08c6051cc6c7b7e7c1189835af5fa1 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 1 Oct 2024 19:04:38 -0300 Subject: [PATCH 12/29] chore: redeployed programs --- target/types/libreplex_editions.ts | 45 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 576514e5..969b3969 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -869,53 +869,58 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "tickerTooLong", - "msg": "Ticker too long" + "name": "sizeExceedsMaxSize", + "msg": "Collection size exceeds max size." }, { "code": 6001, - "name": "mintTemplateTooLong", - "msg": "Mint template too long" + "name": "maxSizeBelowCurrentSize", + "msg": "Max size cannot be reduced below current size." }, { "code": 6002, - "name": "deploymentTemplateTooLong", - "msg": "Deployment template too long" + "name": "creatorShareInvalid", + "msg": "Creators shares must add up to 100." }, { "code": 6003, - "name": "rootTypeTooLong", - "msg": "Root type too long" + "name": "missingApproveAccount", + "msg": "Missing approve account." }, { "code": 6004, - "name": "mintedOut", - "msg": "Minted out" + "name": "expiredApproveAccount", + "msg": "Approve account has expired." }, { "code": 6005, - "name": "legacyMigrationsAreMintedOut", - "msg": "Legacy migrations are minted out" + "name": "invalidField", + "msg": "Invalid field. You cannot use a public key as a field." }, { "code": 6006, - "name": "missingGlobalTreeDelegate", - "msg": "Global tree delegate is missing" + "name": "creatorAddressInvalid", + "msg": "The Address you provided is invalid. Please provide a valid address." }, { "code": 6007, - "name": "incorrectMintType", - "msg": "Incorrect mint type" + "name": "royaltyBasisPointsInvalid", + "msg": "Royalty basis points must be less than or equal to 10000." }, { "code": 6008, - "name": "invalidMetadata", - "msg": "Invalid Metadata" + "name": "platformFeeBasisPointsInvalid", + "msg": "Platform fee basis points must be less than or equal to 10000." }, { "code": 6009, - "name": "creatorFeeTooHigh", - "msg": "Creator fee too high" + "name": "recipientShareInvalid", + "msg": "Recipient shares must add up to 100." + }, + { + "code": 6010, + "name": "reservedField", + "msg": "The provided field is invalid or reserved." } ], "types": [ From ae6bccd2d4410596ee42a72e9d4fef91cd14ac1f Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Wed, 2 Oct 2024 18:21:09 -0300 Subject: [PATCH 13/29] advances on merkle tree cli integration --- Anchor.toml | 8 +- .../libreplex_default_renderer/src/lib.rs | 2 +- programs/libreplex_editions/src/lib.rs | 2 +- .../libreplex_editions_controls/src/lib.rs | 2 +- .../libreplex_monoswap_deprecated/src/lib.rs | 2 +- target/types/libreplex_default_renderer.ts | 2 +- target/types/libreplex_editions.ts | 1290 ++++++++--------- target/types/libreplex_editions_controls.ts | 12 +- target/types/libreplex_monoswap.ts | 2 +- 9 files changed, 595 insertions(+), 727 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 3b203455..96c8403e 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -7,13 +7,13 @@ skip-lint = false [programs.localnet] libreplex_creator = "78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM" libreplex_creator_controls = "G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV" -libreplex_default_renderer = "HX1px98S4bYCKe5PTrgNpkFqJnaYwSmNeJy6xHe2PE7p" -libreplex_editions = "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" -libreplex_editions_controls = "6L4TGey3VrSMfPHaRZuBB2WpDgVFDgLHJzja3KMSj9py" +libreplex_default_renderer = "8MzCUXH5Y52jjorkSu9G6Y69AHBwFoW8GC7MFFhDJRaZ" +libreplex_editions = "2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR" +libreplex_editions_controls = "GoD9GkAZuP9Cc2DUbvv6CNXSz1cG4FMxzRxLoB37WEKy" libreplex_inscriptions = "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" libreplex_legacy_inscribers = "Leg1xVbrpq5gY6mprak3Ud4q4mBwcJi5C9ZruYjWv7n" libreplex_metadata = "LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p" -libreplex_monoswap_deprecated = "HZAjnEyAN7Potf7cht2UArx6PAaa2pDHTRYnzLemetBp" +libreplex_monoswap_deprecated = "3nY7oDsfv8UwRiE6rUE5abb3g3gLZKcmTHbQrsed6SrQ" libreplex_nft = "3oR36M1JQ9LHM1xykKTHDvhuYVM5mDAqRmq989zc1Pzi" libreplex_shop = "ListjawGEdhxuAErSyYwcTEGWQswFoi6FScnGG1RKSB" diff --git a/programs/libreplex_default_renderer/src/lib.rs b/programs/libreplex_default_renderer/src/lib.rs index 1b4c5b74..826344ef 100644 --- a/programs/libreplex_default_renderer/src/lib.rs +++ b/programs/libreplex_default_renderer/src/lib.rs @@ -10,7 +10,7 @@ pub mod instructions; -declare_id!("HX1px98S4bYCKe5PTrgNpkFqJnaYwSmNeJy6xHe2PE7p"); +declare_id!("8MzCUXH5Y52jjorkSu9G6Y69AHBwFoW8GC7MFFhDJRaZ"); #[program] diff --git a/programs/libreplex_editions/src/lib.rs b/programs/libreplex_editions/src/lib.rs index 4c41361c..314da947 100644 --- a/programs/libreplex_editions/src/lib.rs +++ b/programs/libreplex_editions/src/lib.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; pub mod instructions; pub use instructions::*; -declare_id!("AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA"); +declare_id!("2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR"); pub mod errors; pub mod state; diff --git a/programs/libreplex_editions_controls/src/lib.rs b/programs/libreplex_editions_controls/src/lib.rs index c3148eb6..90151af6 100644 --- a/programs/libreplex_editions_controls/src/lib.rs +++ b/programs/libreplex_editions_controls/src/lib.rs @@ -5,7 +5,7 @@ pub use logic::*; pub mod instructions; pub use instructions::*; -declare_id!("6L4TGey3VrSMfPHaRZuBB2WpDgVFDgLHJzja3KMSj9py"); +declare_id!("GoD9GkAZuP9Cc2DUbvv6CNXSz1cG4FMxzRxLoB37WEKy"); pub mod errors; pub mod state; diff --git a/programs/libreplex_monoswap_deprecated/src/lib.rs b/programs/libreplex_monoswap_deprecated/src/lib.rs index 2bfa3cfa..be2cd070 100644 --- a/programs/libreplex_monoswap_deprecated/src/lib.rs +++ b/programs/libreplex_monoswap_deprecated/src/lib.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -declare_id!("HZAjnEyAN7Potf7cht2UArx6PAaa2pDHTRYnzLemetBp"); +declare_id!("3nY7oDsfv8UwRiE6rUE5abb3g3gLZKcmTHbQrsed6SrQ"); pub mod constants; pub mod errors; diff --git a/target/types/libreplex_default_renderer.ts b/target/types/libreplex_default_renderer.ts index 7216c2aa..72a2793c 100644 --- a/target/types/libreplex_default_renderer.ts +++ b/target/types/libreplex_default_renderer.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_default_renderer.json`. */ export type LibreplexDefaultRenderer = { - "address": "HX1px98S4bYCKe5PTrgNpkFqJnaYwSmNeJy6xHe2PE7p", + "address": "8MzCUXH5Y52jjorkSu9G6Y69AHBwFoW8GC7MFFhDJRaZ", "metadata": { "name": "libreplexDefaultRenderer", "version": "0.1.2", diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 969b3969..da583968 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,39 +5,28 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", - "metadata": { - "name": "libreplexEditions", - "version": "0.2.1", - "spec": "0.1.0", - "description": "Created with Anchor", - "repository": "https://github.com/Libreplex/libreplex-program-library" - }, - "instructions": [ + address: '2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR'; + metadata: { + name: 'libreplexEditions'; + version: '0.2.1'; + spec: '0.1.0'; + description: 'Created with Anchor'; + repository: 'https://github.com/Libreplex/libreplex-program-library'; + }; + instructions: [ { - "name": "addMetadata", - "docs": [ - "add additional metadata to mint" - ], - "discriminator": [ - 231, - 195, - 40, - 240, - 67, - 231, - 53, - 136 - ], - "accounts": [ - { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'addMetadata'; + docs: ['add additional metadata to mint']; + discriminator: [231, 195, 40, 240, 67, 231, 53, 136]; + accounts: [ + { + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -57,77 +46,66 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "account", - "path": "editions_deployment.symbol", - "account": "editionsDeployment" + kind: 'account'; + path: 'editions_deployment.symbol'; + account: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "payer", - "writable": true, - "signer": true + name: 'payer'; + writable: true; + signer: true; }, { - "name": "signer", - "writable": true, - "signer": true + name: 'signer'; + writable: true; + signer: true; }, { - "name": "mint", - "writable": true, - "signer": true + name: 'mint'; + writable: true; + signer: true; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; }, { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + name: 'tokenProgram'; + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; } - ], - "args": [ - { - "name": "args", - "type": { - "vec": { - "defined": { - "name": "addMetadataArgs" - } - } - } + ]; + args: [ + { + name: 'args'; + type: { + vec: { + defined: { + name: 'addMetadataArgs'; + }; + }; + }; } - ] + ]; }, { - "name": "addPlatformFee", - "docs": [ - "add royalties to mint" - ], - "discriminator": [ - 62, - 94, - 29, - 95, - 254, - 228, - 21, - 18 - ], - "accounts": [ - { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'addPlatformFee'; + docs: ['add royalties to mint']; + discriminator: [62, 94, 29, 95, 254, 228, 21, 18]; + accounts: [ + { + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -147,75 +125,64 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "account", - "path": "editions_deployment.symbol", - "account": "editionsDeployment" + kind: 'account'; + path: 'editions_deployment.symbol'; + account: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "payer", - "writable": true, - "signer": true + name: 'payer'; + writable: true; + signer: true; }, { - "name": "signer", - "writable": true, - "signer": true + name: 'signer'; + writable: true; + signer: true; }, { - "name": "groupMint", - "writable": true, - "signer": true + name: 'groupMint'; + writable: true; + signer: true; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; }, { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + name: 'tokenProgram'; + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; } - ], - "args": [ - { - "name": "args", - "type": { - "defined": { - "name": "updatePlatformFeeArgs" - } - } + ]; + args: [ + { + name: 'args'; + type: { + defined: { + name: 'updatePlatformFeeArgs'; + }; + }; } - ] + ]; }, { - "name": "addRoyalties", - "docs": [ - "add royalties to mint" - ], - "discriminator": [ - 195, - 251, - 126, - 230, - 187, - 134, - 168, - 210 - ], - "accounts": [ - { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'addRoyalties'; + docs: ['add royalties to mint']; + discriminator: [195, 251, 126, 230, 187, 134, 168, 210]; + accounts: [ + { + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -235,72 +202,63 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "account", - "path": "editions_deployment.symbol", - "account": "editionsDeployment" + kind: 'account'; + path: 'editions_deployment.symbol'; + account: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "payer", - "writable": true, - "signer": true + name: 'payer'; + writable: true; + signer: true; }, { - "name": "signer", - "writable": true, - "signer": true + name: 'signer'; + writable: true; + signer: true; }, { - "name": "mint", - "writable": true, - "signer": true + name: 'mint'; + writable: true; + signer: true; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; }, { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + name: 'tokenProgram'; + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; } - ], - "args": [ - { - "name": "args", - "type": { - "defined": { - "name": "updateRoyaltiesArgs" - } - } + ]; + args: [ + { + name: 'args'; + type: { + defined: { + name: 'updateRoyaltiesArgs'; + }; + }; } - ] + ]; }, { - "name": "initialise", - "discriminator": [ - 162, - 198, - 118, - 235, - 215, - 247, - 25, - 118 - ], - "accounts": [ - { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'initialise'; + discriminator: [162, 198, 118, 235, 215, 247, 25, 118]; + accounts: [ + { + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -320,104 +278,86 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "arg", - "path": "input.symbol" + kind: 'arg'; + path: 'input.symbol'; } - ] - } + ]; + }; }, { - "name": "hashlist", - "writable": true, - "pda": { - "seeds": [ + name: 'hashlist'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 104, - 97, - 115, - 104, - 108, - 105, - 115, - 116 - ] + kind: 'const'; + value: [104, 97, 115, 104, 108, 105, 115, 116]; }, { - "kind": "account", - "path": "editionsDeployment" + kind: 'account'; + path: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "payer", - "writable": true, - "signer": true + name: 'payer'; + writable: true; + signer: true; }, { - "name": "creator", - "writable": true + name: 'creator'; + writable: true; }, { - "name": "groupMint", - "writable": true, - "signer": true + name: 'groupMint'; + writable: true; + signer: true; }, { - "name": "group", - "writable": true, - "signer": true + name: 'group'; + writable: true; + signer: true; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; }, { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + name: 'tokenProgram'; + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; }, { - "name": "groupExtensionProgram", - "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" + name: 'groupExtensionProgram'; + address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'; } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "initialiseInput" - } - } + ]; + args: [ + { + name: 'input'; + type: { + defined: { + name: 'initialiseInput'; + }; + }; } - ] + ]; }, { - "name": "mint", - "discriminator": [ - 51, - 57, - 225, - 47, - 182, - 146, - 137, - 166 - ], - "accounts": [ - { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'mint'; + discriminator: [51, 57, 225, 47, 182, 146, 137, 166]; + accounts: [ + { + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -437,49 +377,40 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "account", - "path": "editions_deployment.symbol", - "account": "editionsDeployment" + kind: 'account'; + path: 'editions_deployment.symbol'; + account: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "hashlist", - "writable": true, - "pda": { - "seeds": [ + name: 'hashlist'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 104, - 97, - 115, - 104, - 108, - 105, - 115, - 116 - ] + kind: 'const'; + value: [104, 97, 115, 104, 108, 105, 115, 116]; }, { - "kind": "account", - "path": "editionsDeployment" + kind: 'account'; + path: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "hashlistMarker", - "writable": true, - "pda": { - "seeds": [ + name: 'hashlistMarker'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 104, 97, 115, @@ -495,102 +426,91 @@ export type LibreplexEditions = { 107, 101, 114 - ] + ]; }, { - "kind": "account", - "path": "editionsDeployment" + kind: 'account'; + path: 'editionsDeployment'; }, { - "kind": "account", - "path": "mint" + kind: 'account'; + path: 'mint'; } - ] - } + ]; + }; }, { - "name": "payer", - "writable": true, - "signer": true + name: 'payer'; + writable: true; + signer: true; }, { - "name": "signer", - "writable": true, - "signer": true + name: 'signer'; + writable: true; + signer: true; }, { - "name": "minter", - "writable": true + name: 'minter'; + writable: true; }, { - "name": "mint", - "writable": true, - "signer": true + name: 'mint'; + writable: true; + signer: true; }, { - "name": "member", - "writable": true, - "signer": true + name: 'member'; + writable: true; + signer: true; }, { - "name": "group", - "writable": true + name: 'group'; + writable: true; }, { - "name": "groupMint", - "writable": true + name: 'groupMint'; + writable: true; }, { - "name": "tokenAccount", - "writable": true + name: 'tokenAccount'; + writable: true; }, { - "name": "tokenProgram" + name: 'tokenProgram'; }, { - "name": "associatedTokenProgram", - "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + name: 'associatedTokenProgram'; + address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; }, { - "name": "groupExtensionProgram", - "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" + name: 'groupExtensionProgram'; + address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; } - ], - "args": [] + ]; + args: []; }, { - "name": "modifyPlatformFee", - "docs": [ - "modify royalties of mint" - ], - "discriminator": [ - 186, - 73, - 229, - 152, - 183, - 174, - 250, - 197 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true + name: 'modifyPlatformFee'; + docs: ['modify royalties of mint']; + discriminator: [186, 73, 229, 152, 183, 174, 250, 197]; + accounts: [ + { + name: 'payer'; + writable: true; + signer: true; }, { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -610,74 +530,63 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "account", - "path": "editions_deployment.symbol", - "account": "editionsDeployment" + kind: 'account'; + path: 'editions_deployment.symbol'; + account: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "signer", - "writable": true, - "signer": true + name: 'signer'; + writable: true; + signer: true; }, { - "name": "groupMint", - "writable": true + name: 'groupMint'; + writable: true; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; }, { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + name: 'tokenProgram'; + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; } - ], - "args": [ - { - "name": "args", - "type": { - "defined": { - "name": "updatePlatformFeeArgs" - } - } + ]; + args: [ + { + name: 'args'; + type: { + defined: { + name: 'updatePlatformFeeArgs'; + }; + }; } - ] + ]; }, { - "name": "modifyRoyalties", - "docs": [ - "modify royalties of mint" - ], - "discriminator": [ - 199, - 95, - 20, - 107, - 136, - 161, - 93, - 137 - ], - "accounts": [ - { - "name": "payer", - "writable": true, - "signer": true + name: 'modifyRoyalties'; + docs: ['modify royalties of mint']; + discriminator: [199, 95, 20, 107, 136, 161, 93, 137]; + accounts: [ + { + name: 'payer'; + writable: true; + signer: true; }, { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -697,69 +606,58 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "account", - "path": "editions_deployment.symbol", - "account": "editionsDeployment" + kind: 'account'; + path: 'editions_deployment.symbol'; + account: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "signer", - "writable": true, - "signer": true + name: 'signer'; + writable: true; + signer: true; }, { - "name": "mint", - "writable": true + name: 'mint'; + writable: true; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; }, { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + name: 'tokenProgram'; + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; } - ], - "args": [ - { - "name": "args", - "type": { - "defined": { - "name": "updateRoyaltiesArgs" - } - } + ]; + args: [ + { + name: 'args'; + type: { + defined: { + name: 'updateRoyaltiesArgs'; + }; + }; } - ] + ]; }, { - "name": "removeMetadata", - "docs": [ - "remove additional metadata to mint" - ], - "discriminator": [ - 81, - 68, - 231, - 49, - 91, - 8, - 111, - 160 - ], - "accounts": [ - { - "name": "editionsDeployment", - "writable": true, - "pda": { - "seeds": [ + name: 'removeMetadata'; + docs: ['remove additional metadata to mint']; + discriminator: [81, 68, 231, 49, 91, 8, 111, 160]; + accounts: [ + { + name: 'editionsDeployment'; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: 'const'; + value: [ 101, 100, 105, @@ -779,415 +677,385 @@ export type LibreplexEditions = { 101, 110, 116 - ] + ]; }, { - "kind": "account", - "path": "editions_deployment.symbol", - "account": "editionsDeployment" + kind: 'account'; + path: 'editions_deployment.symbol'; + account: 'editionsDeployment'; } - ] - } + ]; + }; }, { - "name": "payer", - "writable": true, - "signer": true + name: 'payer'; + writable: true; + signer: true; }, { - "name": "signer", - "writable": true, - "signer": true + name: 'signer'; + writable: true; + signer: true; }, { - "name": "mint" + name: 'mint'; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: 'systemProgram'; + address: '11111111111111111111111111111111'; }, { - "name": "tokenProgram", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + name: 'tokenProgram'; + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'; } - ], - "args": [ - { - "name": "args", - "type": { - "vec": { - "defined": { - "name": "removeMetadataArgs" - } - } - } + ]; + args: [ + { + name: 'args'; + type: { + vec: { + defined: { + name: 'removeMetadataArgs'; + }; + }; + }; } - ] + ]; } - ], - "accounts": [ + ]; + accounts: [ { - "name": "editionsDeployment", - "discriminator": [ - 101, - 54, - 68, - 216, - 168, - 131, - 242, - 157 - ] + name: 'editionsDeployment'; + discriminator: [101, 54, 68, 216, 168, 131, 242, 157]; }, { - "name": "hashlist", - "discriminator": [ - 187, - 203, - 134, - 6, - 43, - 198, - 120, - 186 - ] + name: 'hashlist'; + discriminator: [187, 203, 134, 6, 43, 198, 120, 186]; }, { - "name": "hashlistMarker", - "discriminator": [ - 55, - 46, - 160, - 53, - 239, - 41, - 223, - 50 - ] + name: 'hashlistMarker'; + discriminator: [55, 46, 160, 53, 239, 41, 223, 50]; } - ], - "errors": [ + ]; + errors: [ { - "code": 6000, - "name": "sizeExceedsMaxSize", - "msg": "Collection size exceeds max size." + code: 6000; + name: 'sizeExceedsMaxSize'; + msg: 'Collection size exceeds max size.'; }, { - "code": 6001, - "name": "maxSizeBelowCurrentSize", - "msg": "Max size cannot be reduced below current size." + code: 6001; + name: 'maxSizeBelowCurrentSize'; + msg: 'Max size cannot be reduced below current size.'; }, { - "code": 6002, - "name": "creatorShareInvalid", - "msg": "Creators shares must add up to 100." + code: 6002; + name: 'creatorShareInvalid'; + msg: 'Creators shares must add up to 100.'; }, { - "code": 6003, - "name": "missingApproveAccount", - "msg": "Missing approve account." + code: 6003; + name: 'missingApproveAccount'; + msg: 'Missing approve account.'; }, { - "code": 6004, - "name": "expiredApproveAccount", - "msg": "Approve account has expired." + code: 6004; + name: 'expiredApproveAccount'; + msg: 'Approve account has expired.'; }, { - "code": 6005, - "name": "invalidField", - "msg": "Invalid field. You cannot use a public key as a field." + code: 6005; + name: 'invalidField'; + msg: 'Invalid field. You cannot use a public key as a field.'; }, { - "code": 6006, - "name": "creatorAddressInvalid", - "msg": "The Address you provided is invalid. Please provide a valid address." + code: 6006; + name: 'creatorAddressInvalid'; + msg: 'The Address you provided is invalid. Please provide a valid address.'; }, { - "code": 6007, - "name": "royaltyBasisPointsInvalid", - "msg": "Royalty basis points must be less than or equal to 10000." + code: 6007; + name: 'royaltyBasisPointsInvalid'; + msg: 'Royalty basis points must be less than or equal to 10000.'; }, { - "code": 6008, - "name": "platformFeeBasisPointsInvalid", - "msg": "Platform fee basis points must be less than or equal to 10000." + code: 6008; + name: 'platformFeeBasisPointsInvalid'; + msg: 'Platform fee basis points must be less than or equal to 10000.'; }, { - "code": 6009, - "name": "recipientShareInvalid", - "msg": "Recipient shares must add up to 100." + code: 6009; + name: 'recipientShareInvalid'; + msg: 'Recipient shares must add up to 100.'; }, { - "code": 6010, - "name": "reservedField", - "msg": "The provided field is invalid or reserved." + code: 6010; + name: 'reservedField'; + msg: 'The provided field is invalid or reserved.'; } - ], - "types": [ + ]; + types: [ { - "name": "addMetadataArgs", - "type": { - "kind": "struct", - "fields": [ + name: 'addMetadataArgs'; + type: { + kind: 'struct'; + fields: [ { - "name": "field", - "type": "string" + name: 'field'; + type: 'string'; }, { - "name": "value", - "type": "string" + name: 'value'; + type: 'string'; } - ] - } + ]; + }; }, { - "name": "creatorWithShare", - "type": { - "kind": "struct", - "fields": [ + name: 'creatorWithShare'; + type: { + kind: 'struct'; + fields: [ { - "name": "address", - "type": "pubkey" + name: 'address'; + type: 'pubkey'; }, { - "name": "share", - "type": "u8" + name: 'share'; + type: 'u8'; } - ] - } + ]; + }; }, { - "name": "editionsDeployment", - "type": { - "kind": "struct", - "fields": [ + name: 'editionsDeployment'; + type: { + kind: 'struct'; + fields: [ { - "name": "creator", - "type": "pubkey" + name: 'creator'; + type: 'pubkey'; }, { - "name": "maxNumberOfTokens", - "type": "u64" + name: 'maxNumberOfTokens'; + type: 'u64'; }, { - "name": "numberOfTokensIssued", - "type": "u64" + name: 'numberOfTokensIssued'; + type: 'u64'; }, { - "name": "cosignerProgramId", - "type": "pubkey" + name: 'cosignerProgramId'; + type: 'pubkey'; }, { - "name": "groupMint", - "type": "pubkey" + name: 'groupMint'; + type: 'pubkey'; }, { - "name": "group", - "type": "pubkey" + name: 'group'; + type: 'pubkey'; }, { - "name": "symbol", - "type": "string" + name: 'symbol'; + type: 'string'; }, { - "name": "name", - "type": "string" + name: 'name'; + type: 'string'; }, { - "name": "offchainUrl", - "type": "string" + name: 'offchainUrl'; + type: 'string'; }, { - "name": "nameIsTemplate", - "type": "bool" + name: 'nameIsTemplate'; + type: 'bool'; }, { - "name": "urlIsTemplate", - "type": "bool" + name: 'urlIsTemplate'; + type: 'bool'; }, { - "name": "padding", - "type": { - "array": [ - "u8", - 98 - ] - } + name: 'padding'; + type: { + array: ['u8', 98]; + }; } - ] - } + ]; + }; }, { - "name": "hashlist", - "type": { - "kind": "struct", - "fields": [ + name: 'hashlist'; + type: { + kind: 'struct'; + fields: [ { - "name": "deployment", - "type": "pubkey" + name: 'deployment'; + type: 'pubkey'; }, { - "name": "issues", - "type": { - "vec": { - "defined": { - "name": "mintAndOrder" - } - } - } + name: 'issues'; + type: { + vec: { + defined: { + name: 'mintAndOrder'; + }; + }; + }; } - ] - } + ]; + }; }, { - "name": "hashlistMarker", - "type": { - "kind": "struct", - "fields": [ + name: 'hashlistMarker'; + type: { + kind: 'struct'; + fields: [ { - "name": "editionsDeployment", - "type": "pubkey" + name: 'editionsDeployment'; + type: 'pubkey'; }, { - "name": "mint", - "type": "pubkey" + name: 'mint'; + type: 'pubkey'; } - ] - } + ]; + }; }, { - "name": "initialiseInput", - "type": { - "kind": "struct", - "fields": [ + name: 'initialiseInput'; + type: { + kind: 'struct'; + fields: [ { - "name": "maxNumberOfTokens", - "type": "u64" + name: 'maxNumberOfTokens'; + type: 'u64'; }, { - "name": "symbol", - "type": "string" + name: 'symbol'; + type: 'string'; }, { - "name": "name", - "type": "string" + name: 'name'; + type: 'string'; }, { - "name": "offchainUrl", - "type": "string" + name: 'offchainUrl'; + type: 'string'; }, { - "name": "creatorCosignProgramId", - "type": { - "option": "pubkey" - } + name: 'creatorCosignProgramId'; + type: { + option: 'pubkey'; + }; }, { - "name": "itemBaseUri", - "type": "string" + name: 'itemBaseUri'; + type: 'string'; }, { - "name": "itemName", - "type": "string" + name: 'itemName'; + type: 'string'; } - ] - } + ]; + }; }, { - "name": "mintAndOrder", - "type": { - "kind": "struct", - "fields": [ + name: 'mintAndOrder'; + type: { + kind: 'struct'; + fields: [ { - "name": "mint", - "type": "pubkey" + name: 'mint'; + type: 'pubkey'; }, { - "name": "order", - "type": "u64" + name: 'order'; + type: 'u64'; } - ] - } + ]; + }; }, { - "name": "platformFeeRecipient", - "type": { - "kind": "struct", - "fields": [ + name: 'platformFeeRecipient'; + type: { + kind: 'struct'; + fields: [ { - "name": "address", - "type": "pubkey" + name: 'address'; + type: 'pubkey'; }, { - "name": "share", - "type": "u8" + name: 'share'; + type: 'u8'; } - ] - } + ]; + }; }, { - "name": "removeMetadataArgs", - "type": { - "kind": "struct", - "fields": [ + name: 'removeMetadataArgs'; + type: { + kind: 'struct'; + fields: [ { - "name": "field", - "type": "string" + name: 'field'; + type: 'string'; }, { - "name": "value", - "type": "string" + name: 'value'; + type: 'string'; } - ] - } + ]; + }; }, { - "name": "updatePlatformFeeArgs", - "type": { - "kind": "struct", - "fields": [ + name: 'updatePlatformFeeArgs'; + type: { + kind: 'struct'; + fields: [ { - "name": "platformFeeValue", - "type": "u64" + name: 'platformFeeValue'; + type: 'u64'; }, { - "name": "recipients", - "type": { - "vec": { - "defined": { - "name": "platformFeeRecipient" - } - } - } + name: 'recipients'; + type: { + vec: { + defined: { + name: 'platformFeeRecipient'; + }; + }; + }; }, { - "name": "isFeeFlat", - "type": "bool" + name: 'isFeeFlat'; + type: 'bool'; } - ] - } + ]; + }; }, { - "name": "updateRoyaltiesArgs", - "type": { - "kind": "struct", - "fields": [ + name: 'updateRoyaltiesArgs'; + type: { + kind: 'struct'; + fields: [ { - "name": "royaltyBasisPoints", - "type": "u16" + name: 'royaltyBasisPoints'; + type: 'u16'; }, { - "name": "creators", - "type": { - "vec": { - "defined": { - "name": "creatorWithShare" - } - } - } + name: 'creators'; + type: { + vec: { + defined: { + name: 'creatorWithShare'; + }; + }; + }; } - ] - } + ]; + }; } - ] + ]; }; diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index 8dad884c..f27cd66e 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions_controls.json`. */ export type LibreplexEditionsControls = { - "address": "6L4TGey3VrSMfPHaRZuBB2WpDgVFDgLHJzja3KMSj9py", + "address": "GoD9GkAZuP9Cc2DUbvv6CNXSz1cG4FMxzRxLoB37WEKy", "metadata": { "name": "libreplexEditionsControls", "version": "0.2.1", @@ -51,7 +51,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" + "address": "2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR" } ], "args": [ @@ -152,7 +152,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" + "address": "2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR" } ], "args": [ @@ -357,7 +357,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" + "address": "2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR" } ], "args": [ @@ -446,7 +446,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" + "address": "2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR" } ], "args": [ @@ -603,7 +603,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "AffMEwvVXZgKcSD5aBDeB6H9PByiupYLryt4nw7LUbYA" + "address": "2wxjzcyejdC7jqgJPx3SVGhtroKoyB7ixERtRhKXTBxR" } ], "args": [ diff --git a/target/types/libreplex_monoswap.ts b/target/types/libreplex_monoswap.ts index 483a2742..03b02889 100644 --- a/target/types/libreplex_monoswap.ts +++ b/target/types/libreplex_monoswap.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_monoswap.json`. */ export type LibreplexMonoswap = { - "address": "HZAjnEyAN7Potf7cht2UArx6PAaa2pDHTRYnzLemetBp", + "address": "3nY7oDsfv8UwRiE6rUE5abb3g3gLZKcmTHbQrsed6SrQ", "metadata": { "name": "libreplexMonoswap", "version": "0.0.0", From c9ae76171812be67a9ad3a22c78b9b632e8299ef Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Fri, 4 Oct 2024 15:33:24 -0300 Subject: [PATCH 14/29] chore:lint --- .gitignore | 1 - Anchor.toml | 8 +- .../libreplex_default_renderer/src/lib.rs | 2 +- programs/libreplex_editions/src/lib.rs | 2 +- .../src/instructions/add_phase.rs | 3 +- .../src/instructions/initialise.rs | 9 +- .../src/instructions/mint_with_controls.rs | 6 +- .../src/instructions/mod.rs | 5 - .../update_platform_fee_secondary_admin.rs | 4 - .../libreplex_editions_controls/src/lib.rs | 2 +- .../src/logic/check_allow_list_constraints.rs | 2 - .../src/logic/check_phase_constraints.rs | 3 +- .../src/logic/mod.rs | 1 - .../libreplex_monoswap_deprecated/src/lib.rs | 2 +- target/types/libreplex_default_renderer.ts | 2 +- target/types/libreplex_editions.ts | 2 +- target/types/libreplex_editions_controls.ts | 10 +- target/types/libreplex_monoswap.ts | 2 +- tests/editions_controls.test.ts | 261 ------------------ 19 files changed, 26 insertions(+), 301 deletions(-) delete mode 100644 tests/editions_controls.test.ts diff --git a/.gitignore b/.gitignore index 7b91373e..30327dc1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ .cursorrules - local-scripts .anchor diff --git a/Anchor.toml b/Anchor.toml index 8a91dfd2..4030743c 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -7,13 +7,13 @@ skip-lint = false [programs.localnet] libreplex_creator = "78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM" libreplex_creator_controls = "G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV" -libreplex_default_renderer = "EfC2horkE4hCaCruMvoDDWxUhcCgVaufHeG3Hkj69eR8" -libreplex_editions = "6hRLBM1rexUvd64qaBHQJUz4ufRAFbXZXM12ExWd7kYX" -libreplex_editions_controls = "Ady6poZ59kW6rvbad3hiQpf8vun58RzHtNQ5Zvh7aJk3" +libreplex_default_renderer = "3CkggaHBFWPAghBvpz5QtebciEdh5dGEeN6XWHC2USVf" +libreplex_editions = "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" +libreplex_editions_controls = "4yPHPvJi5ZL6i5S4fvCrgzof2Azs47KNddK8YZs8b5Ra" libreplex_inscriptions = "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" libreplex_legacy_inscribers = "Leg1xVbrpq5gY6mprak3Ud4q4mBwcJi5C9ZruYjWv7n" libreplex_metadata = "LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p" -libreplex_monoswap_deprecated = "3nY7oDsfv8UwRiE6rUE5abb3g3gLZKcmTHbQrsed6SrQ" +libreplex_monoswap_deprecated = "HPhPBUi8eSYkQb5CUUxXdeaVVwbC7xQz3a6jmkLhy9a3" libreplex_nft = "3oR36M1JQ9LHM1xykKTHDvhuYVM5mDAqRmq989zc1Pzi" libreplex_shop = "ListjawGEdhxuAErSyYwcTEGWQswFoi6FScnGG1RKSB" diff --git a/programs/libreplex_default_renderer/src/lib.rs b/programs/libreplex_default_renderer/src/lib.rs index 826344ef..9fa264c2 100644 --- a/programs/libreplex_default_renderer/src/lib.rs +++ b/programs/libreplex_default_renderer/src/lib.rs @@ -10,7 +10,7 @@ pub mod instructions; -declare_id!("8MzCUXH5Y52jjorkSu9G6Y69AHBwFoW8GC7MFFhDJRaZ"); +declare_id!("3CkggaHBFWPAghBvpz5QtebciEdh5dGEeN6XWHC2USVf"); #[program] diff --git a/programs/libreplex_editions/src/lib.rs b/programs/libreplex_editions/src/lib.rs index 928ac089..9ac40a07 100644 --- a/programs/libreplex_editions/src/lib.rs +++ b/programs/libreplex_editions/src/lib.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; pub mod instructions; pub use instructions::*; -declare_id!("6hRLBM1rexUvd64qaBHQJUz4ufRAFbXZXM12ExWd7kYX"); +declare_id!("2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P"); pub mod errors; pub mod state; diff --git a/programs/libreplex_editions_controls/src/instructions/add_phase.rs b/programs/libreplex_editions_controls/src/instructions/add_phase.rs index b9acaf30..1ee9bda9 100644 --- a/programs/libreplex_editions_controls/src/instructions/add_phase.rs +++ b/programs/libreplex_editions_controls/src/instructions/add_phase.rs @@ -11,7 +11,7 @@ pub struct InitialisePhaseInput { pub start_time: i64, pub max_mints_per_wallet: u64, pub max_mints_total: u64, - pub end_time: i64, // set to i64::MAX if not supplied + pub end_time: i64, pub merkle_root: Option<[u8; 32]>, } @@ -44,7 +44,6 @@ pub struct AddPhaseCtx<'info> { } pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Result<()> { - if !input.price_token.eq(&wrapped_sol::ID) { panic!("Only native price currently supported") } diff --git a/programs/libreplex_editions_controls/src/instructions/initialise.rs b/programs/libreplex_editions_controls/src/instructions/initialise.rs index e82c0f1c..43c2737c 100644 --- a/programs/libreplex_editions_controls/src/instructions/initialise.rs +++ b/programs/libreplex_editions_controls/src/instructions/initialise.rs @@ -1,7 +1,9 @@ use anchor_lang::{prelude::*, system_program}; -use libreplex_editions::{cpi::accounts::InitialiseCtx, group_extension_program, program::LibreplexEditions, AddMetadataArgs, CreatorWithShare, InitialiseInput, UpdateRoyaltiesArgs}; -use libreplex_editions::cpi::accounts::AddMetadata; -use libreplex_editions::cpi::accounts::AddRoyalties; +use libreplex_editions::{ + cpi::accounts::InitialiseCtx, AddMetadata, AddRoyalties + program::LibreplexEditions,AddMetadataArgs, CreatorWithShare, InitialiseInput, UpdateRoyaltiesArgs + group_extension_program, +}; use crate::{EditionsControls, PlatformFeeRecipient, UpdatePlatformFeeArgs, DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN, DEFAULT_PLATFORM_FEE_SECONDARY_ADMIN}; use crate::errors::EditionsError; @@ -75,7 +77,6 @@ pub fn initialise_editions_controls( ) -> Result<()> { let libreplex_editions_program = &ctx.accounts.libreplex_editions_program; let editions_controls = &mut ctx.accounts.editions_controls; - let editions_deployment = &ctx.accounts.editions_deployment; let hashlist = &ctx.accounts.hashlist; let payer = &ctx.accounts.payer; diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index ccf59aef..dfc56685 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -17,7 +17,7 @@ use crate::{ check_allow_list_constraints }; -#[derive(AnchorDeserialize, AnchorSerialize, Clone)] +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] pub struct MintInput { pub phase_index: u32, pub merkle_proof: Option>, @@ -30,7 +30,7 @@ pub struct MintInput { pub struct MintWithControlsCtx<'info> { #[account(mut)] pub editions_deployment: Box>, - +c #[account( mut, seeds = [b"editions_controls", editions_deployment.key().as_ref()], @@ -215,7 +215,7 @@ fn validate_phase( return Err(EditionsError::InvalidPhaseIndex.into()); } } - + Ok(()) } diff --git a/programs/libreplex_editions_controls/src/instructions/mod.rs b/programs/libreplex_editions_controls/src/instructions/mod.rs index 818ff67b..cd4b141f 100644 --- a/programs/libreplex_editions_controls/src/instructions/mod.rs +++ b/programs/libreplex_editions_controls/src/instructions/mod.rs @@ -1,8 +1,3 @@ - -/* - initialises a new launch. does not create any - on-chain accounts, mints, token accounts etc -*/ pub mod initialise; pub use initialise::*; diff --git a/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs b/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs index a5e98852..ad6a8be4 100644 --- a/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs +++ b/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs @@ -1,17 +1,13 @@ use anchor_lang::prelude::*; use libreplex_editions::program::LibreplexEditions; use libreplex_shared::wrapped_sol; - - use crate::{EditionsControls, Phase}; - #[derive(AnchorDeserialize, AnchorSerialize, Clone)] pub struct UpdatePlatformFeeSecondaryAdminInput { pub new_admin: Pubkey, } - #[derive(Accounts)] #[instruction(input: UpdatePlatformFeeSecondaryAdminInput)] pub struct UpdatePlatformFeeSecondaryAdminCtx<'info> { diff --git a/programs/libreplex_editions_controls/src/lib.rs b/programs/libreplex_editions_controls/src/lib.rs index d2d3112e..51602459 100644 --- a/programs/libreplex_editions_controls/src/lib.rs +++ b/programs/libreplex_editions_controls/src/lib.rs @@ -5,7 +5,7 @@ pub use logic::*; pub mod instructions; pub use instructions::*; -declare_id!("Ady6poZ59kW6rvbad3hiQpf8vun58RzHtNQ5Zvh7aJk3"); +declare_id!("4yPHPvJi5ZL6i5S4fvCrgzof2Azs47KNddK8YZs8b5Ra"); pub mod errors; pub mod state; diff --git a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs index 7e40bc1c..81495231 100644 --- a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs @@ -3,9 +3,7 @@ use anchor_lang::{ prelude::*, solana_program::hash::hashv, }; - use rarible_merkle_verify::verify; - use crate::{MinterStats, Phase}; /// We need to discern between leaf and intermediate nodes to prevent trivial second diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs index 6cf03dd7..48c2f805 100644 --- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs @@ -2,7 +2,6 @@ use anchor_lang::{ accounts::account::Account, prelude::*, }; - use crate::{EditionsControls, MinterStats, Phase}; pub fn check_phase_constraints( @@ -29,7 +28,7 @@ pub fn check_phase_constraints( /// Checks if the total mints for the phase has been exceeded (phase sold out) /// @dev dev: notice that if max_mints_total is 0, this constraint is disabled if phase.max_mints_total > 0 && phase.current_mints >= phase.max_mints_total { - panic!("Phase sold out") + panic!("Exceeded max mints for this phase") } /// Checks if the user has exceeded the max mints for the deployment (across all phases!) diff --git a/programs/libreplex_editions_controls/src/logic/mod.rs b/programs/libreplex_editions_controls/src/logic/mod.rs index 749fbc95..6b143b6c 100644 --- a/programs/libreplex_editions_controls/src/logic/mod.rs +++ b/programs/libreplex_editions_controls/src/logic/mod.rs @@ -1,4 +1,3 @@ - pub mod check_phase_constraints; pub use check_phase_constraints::*; diff --git a/programs/libreplex_monoswap_deprecated/src/lib.rs b/programs/libreplex_monoswap_deprecated/src/lib.rs index be2cd070..38988bd2 100644 --- a/programs/libreplex_monoswap_deprecated/src/lib.rs +++ b/programs/libreplex_monoswap_deprecated/src/lib.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -declare_id!("3nY7oDsfv8UwRiE6rUE5abb3g3gLZKcmTHbQrsed6SrQ"); +declare_id!("HPhPBUi8eSYkQb5CUUxXdeaVVwbC7xQz3a6jmkLhy9a3"); pub mod constants; pub mod errors; diff --git a/target/types/libreplex_default_renderer.ts b/target/types/libreplex_default_renderer.ts index 72a2793c..51c26e28 100644 --- a/target/types/libreplex_default_renderer.ts +++ b/target/types/libreplex_default_renderer.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_default_renderer.json`. */ export type LibreplexDefaultRenderer = { - "address": "8MzCUXH5Y52jjorkSu9G6Y69AHBwFoW8GC7MFFhDJRaZ", + "address": "3CkggaHBFWPAghBvpz5QtebciEdh5dGEeN6XWHC2USVf", "metadata": { "name": "libreplexDefaultRenderer", "version": "0.1.2", diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 0b51055c..921f9e07 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", + "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P", "metadata": { "name": "libreplexEditions", "version": "0.2.1", diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index 0955140e..0eebe9fe 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions_controls.json`. */ export type LibreplexEditionsControls = { - "address": "Ady6poZ59kW6rvbad3hiQpf8vun58RzHtNQ5Zvh7aJk3", + "address": "4yPHPvJi5ZL6i5S4fvCrgzof2Azs47KNddK8YZs8b5Ra", "metadata": { "name": "libreplexEditionsControls", "version": "0.2.1", @@ -51,7 +51,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "6hRLBM1rexUvd64qaBHQJUz4ufRAFbXZXM12ExWd7kYX" + "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" } ], "args": [ @@ -152,7 +152,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "6hRLBM1rexUvd64qaBHQJUz4ufRAFbXZXM12ExWd7kYX" + "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" } ], "args": [ @@ -365,7 +365,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "6hRLBM1rexUvd64qaBHQJUz4ufRAFbXZXM12ExWd7kYX" + "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" } ], "args": [ @@ -595,7 +595,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "6hRLBM1rexUvd64qaBHQJUz4ufRAFbXZXM12ExWd7kYX" + "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" } ], "args": [ diff --git a/target/types/libreplex_monoswap.ts b/target/types/libreplex_monoswap.ts index 03b02889..e7d11823 100644 --- a/target/types/libreplex_monoswap.ts +++ b/target/types/libreplex_monoswap.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_monoswap.json`. */ export type LibreplexMonoswap = { - "address": "3nY7oDsfv8UwRiE6rUE5abb3g3gLZKcmTHbQrsed6SrQ", + "address": "HPhPBUi8eSYkQb5CUUxXdeaVVwbC7xQz3a6jmkLhy9a3", "metadata": { "name": "libreplexMonoswap", "version": "0.0.0", diff --git a/tests/editions_controls.test.ts b/tests/editions_controls.test.ts deleted file mode 100644 index 012e2fc4..00000000 --- a/tests/editions_controls.test.ts +++ /dev/null @@ -1,261 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import { Program } from '@coral-xyz/anchor'; -import { PublicKey, Keypair, SystemProgram } from '@solana/web3.js'; -import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; -import { LibreplexEditionsControls } from '../target/types/libreplex_editions_controls'; -import { LibreplexEditions } from '../target/types/libreplex_editions'; -import { expect } from 'chai'; -import { describe, it } from 'mocha'; - -describe('Editions Controls', () => { - const provider = anchor.AnchorProvider.env(); - anchor.setProvider(provider); - - const editionsControlsProgram = anchor.workspace - .LibreplexEditionsControls as Program; - console.log( - 'editionsControlsProgram ID:', - editionsControlsProgram.programId.toBase58() - ); - - const editionsProgram = anchor.workspace - .LibreplexEditions as Program; - console.log('editionsProgram ID:', editionsProgram.programId.toBase58()); - - const payer = provider.wallet as anchor.Wallet; - const creator1 = Keypair.generate(); - const creator2 = Keypair.generate(); - const treasury = Keypair.generate(); - - it('should deploy a collection, add a phase, and execute a mint', async () => { - const collectionConfig = { - maxNumberOfTokens: new anchor.BN(100), - symbol: 'TEST', - name: 'Test Collection', - offchainUrl: 'https://example.com/metadata.json', - treasury: treasury.publicKey, - maxMintsPerWallet: new anchor.BN(5), - royaltyBasisPoints: new anchor.BN(1000), - creators: [ - { - address: creator1.publicKey, - share: 50, - }, - // { - // address: creator2.publicKey, - // share: 50, - // }, - ], - extraMeta: [ - { field: 'f1', value: 'v1' }, - // { field: 'f2', value: 'v2' }, - ], - phases: [ - { - priceAmount: new anchor.BN(1000000), // 0.001 SOL - priceToken: new PublicKey( - 'So11111111111111111111111111111111111111112' - ), - startTime: new anchor.BN(Math.floor(Date.now() / 1000)), - maxMintsPerWallet: new anchor.BN(5), - maxMintsTotal: new anchor.BN(50), - endTime: new anchor.BN(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - }, - ], - }; - - // 1. Deploy a collection - const [editions] = PublicKey.findProgramAddressSync( - [ - Buffer.from('editions_deployment'), - Buffer.from(collectionConfig.symbol), - ], - editionsProgram.programId - ); - - const [editionsControls] = PublicKey.findProgramAddressSync( - [Buffer.from('editions_controls'), editions.toBuffer()], - editionsControlsProgram.programId - ); - - const [hashlist] = PublicKey.findProgramAddressSync( - [Buffer.from('hashlist'), editions.toBuffer()], - editionsProgram.programId - ); - - const groupMint = Keypair.generate(); - const group = Keypair.generate(); - - console.log('Initializing accounts...'); - console.log('Editions address:', editions.toBase58()); - console.log('EditionsControls address:', editionsControls.toBase58()); - console.log('Hashlist address:', hashlist.toBase58()); - console.log('GroupMint address:', groupMint.publicKey.toBase58()); - console.log('Group address:', group.publicKey.toBase58()); - - console.log('initialiseEditionsControls...'); - try { - await editionsControlsProgram.methods - .initialiseEditionsControls({ - maxNumberOfTokens: collectionConfig.maxNumberOfTokens, - symbol: collectionConfig.symbol, - name: collectionConfig.name, - offchainUrl: collectionConfig.offchainUrl, - cosignerProgramId: null, - treasury: collectionConfig.treasury, - maxMintsPerWallet: collectionConfig.maxMintsPerWallet, - royaltyBasisPoints: collectionConfig.royaltyBasisPoints, - creators: collectionConfig.creators, - extraMeta: collectionConfig.extraMeta, - }) - .accountsStrict({ - editionsControls, - editionsDeployment: editions, - hashlist, - payer: payer.publicKey, - creator: payer.publicKey, - groupMint: groupMint.publicKey, - group: group.publicKey, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - groupExtensionProgram: new PublicKey( - '2TBWcwXdtwQEN8gXoEg6xFtUCYcBRpQaunWTDJwDp5Tx' - ), - }) - .signers([groupMint, group]) - .rpc(); - console.log('initialiseEditionsControls done'); - } catch (error) { - console.error('Error in initialiseEditionsControls:', error); - console.log('Accounts used and their executability:'); - - const accountsToCheck = { - editionsControls, - editionsDeployment: editions, - hashlist, - payer: payer.publicKey, - creator: payer.publicKey, - groupMint: groupMint.publicKey, - group: group.publicKey, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - groupExtensionProgram: new PublicKey( - '2TBWcwXdtwQEN8gXoEg6xFtUCYcBRpQaunWTDJwDp5Tx' - ), - }; - - for (const [name, pubkey] of Object.entries(accountsToCheck)) { - try { - const accountInfo = await provider.connection.getAccountInfo(pubkey); - console.log(`${name}:`, pubkey.toBase58()); - console.log( - ` Executable: ${ - accountInfo ? accountInfo.executable : 'Account not found' - }` - ); - } catch (err) { - console.log(`${name}:`, pubkey.toBase58()); - console.log(` Error fetching account info: ${err.message}`); - } - } - - throw error; - } - - // // 2. Add a phase - // console.log('addPhase...'); - // await editionsControlsProgram.methods - // .addPhase({ - // priceAmount: new anchor.BN(1000000), // 0.001 SOL - // priceToken: new PublicKey( - // 'So11111111111111111111111111111111111111112' - // ), - // startTime: new anchor.BN(Math.floor(Date.now() / 1000)), - // maxMintsPerWallet: new anchor.BN(5), - // maxMintsTotal: new anchor.BN(50), - // endTime: new anchor.BN(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now - // }) - // .accounts({ - // editionsControls, - // creator: payer.publicKey, - // payer: payer.publicKey, - // systemProgram: SystemProgram.programId, - // tokenProgram: TOKEN_2022_PROGRAM_ID, - // libreplexEditionsProgram: editionsProgram.programId, - // }) - // .rpc(); - // console.log('addPhase done'); - - // // 3. Execute a mint - // const mint = Keypair.generate(); - // const member = Keypair.generate(); - - // const [hashlistMarker] = PublicKey.findProgramAddressSync( - // [ - // Buffer.from('hashlist_marker'), - // editionsDeployment.toBuffer(), - // mint.publicKey.toBuffer(), - // ], - // editionsProgram.programId - // ); - - // const [minterStats] = PublicKey.findProgramAddressSync( - // [ - // Buffer.from('minter_stats'), - // editionsDeployment.toBuffer(), - // payer.publicKey.toBuffer(), - // ], - // editionsControlsProgram.programId - // ); - - // const [minterStatsPhase] = PublicKey.findProgramAddressSync( - // [ - // Buffer.from('minter_stats_phase'), - // editionsDeployment.toBuffer(), - // payer.publicKey.toBuffer(), - // Buffer.from([0]), - // ], - // editionsControlsProgram.programId - // ); - - // console.log('mintWithControls...'); - // await editionsControlsProgram.methods - // .mintWithControls({ - // phaseIndex: 0, - // }) - // .accounts({ - // editionsDeployment, - // editionsControls, - // hashlist, - // hashlistMarker, - // payer: payer.publicKey, - // mint: mint.publicKey, - // member: member.publicKey, - // signer: payer.publicKey, - // minter: payer.publicKey, - // minterStats, - // minterStatsPhase, - // group: group.publicKey, - // groupExtensionProgram: new PublicKey('GExfnHgvdPcg7uQh9vHJYKdNbpGfUzb'), - // tokenAccount: payer.publicKey, // This should be the correct associated token account - // treasury: payer.publicKey, - // systemProgram: SystemProgram.programId, - // tokenProgram: TOKEN_2022_PROGRAM_ID, - // libreplexEditionsProgram: editionsProgram.programId, - // }) - // .signers([mint, member]) - // .rpc(); - // console.log('mintWithControls done'); - - // // Add assertions here to verify the mint was successful - // const editionsControlsAccount = - // await editionsControlsProgram.account.editionsControls.fetch( - // editionsControls - // ); - // expect(editionsControlsAccount.phases[0].currentMints.toNumber()).to.equal( - // 1 - // ); - }); -}); From 03dad55e306e505bb53d7b77573d867a6552920b Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Fri, 4 Oct 2024 15:59:59 -0300 Subject: [PATCH 15/29] chore: improved errors --- programs/libreplex_editions/src/errors.rs | 1 - .../libreplex_editions_controls/src/errors.rs | 36 +++++++++++- .../src/instructions/add_phase.rs | 2 +- .../src/instructions/initialise.rs | 14 ++--- .../src/instructions/mint_with_controls.rs | 24 ++++---- .../src/logic/check_allow_list_constraints.rs | 20 ++++--- .../src/logic/check_phase_constraints.rs | 21 ++++--- .../libreplex_editions_controls/src/state.rs | 6 +- target/types/libreplex_editions.ts | 2 +- target/types/libreplex_editions_controls.ts | 57 ++++++++++++++++++- 10 files changed, 140 insertions(+), 43 deletions(-) diff --git a/programs/libreplex_editions/src/errors.rs b/programs/libreplex_editions/src/errors.rs index 20bafb21..eb204d10 100644 --- a/programs/libreplex_editions/src/errors.rs +++ b/programs/libreplex_editions/src/errors.rs @@ -8,7 +8,6 @@ pub enum EditionsError { #[msg("Mint template too long")] MintTemplateTooLong, - #[msg("Deployment template too long")] DeploymentTemplateTooLong, diff --git a/programs/libreplex_editions_controls/src/errors.rs b/programs/libreplex_editions_controls/src/errors.rs index 383f3760..5dd706bf 100644 --- a/programs/libreplex_editions_controls/src/errors.rs +++ b/programs/libreplex_editions_controls/src/errors.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; #[error_code] -pub enum EditionsError { +pub enum EditionsControlsError { #[msg("Ticker too long")] TickerTooLong, @@ -49,7 +49,41 @@ pub enum EditionsError { #[msg("No phases have been added. Cannot mint.")] NoPhasesAdded, + #[msg("Invalid phase index.")] InvalidPhaseIndex, + #[msg("Merkle root not set for allow list mint")] + MerkleRootNotSet, + + #[msg("Merkle proof required for allow list mint")] + MerkleProofRequired, + + #[msg("Allow list price and max claims are required for allow list mint")] + AllowListPriceAndMaxClaimsRequired, + + #[msg("Invalid merkle proof")] + InvalidMerkleProof, + + #[msg("This wallet has exceeded allow list max_claims in the current phase")] + ExceededAllowListMaxClaims, + + #[msg("Phase not active")] + PhaseNotActive, + + #[msg("Phase not yet started")] + PhaseNotStarted, + + #[msg("Phase already finished")] + PhaseAlreadyFinished, + + #[msg("Exceeded max mints for this phase")] + ExceededMaxMintsForPhase, + + #[msg("Exceeded wallet max mints for this phase")] + ExceededWalletMaxMintsForPhase, + + #[msg("Exceeded wallet max mints for the collection")] + ExceededWalletMaxMintsForCollection, + } diff --git a/programs/libreplex_editions_controls/src/instructions/add_phase.rs b/programs/libreplex_editions_controls/src/instructions/add_phase.rs index 1ee9bda9..8c0c63da 100644 --- a/programs/libreplex_editions_controls/src/instructions/add_phase.rs +++ b/programs/libreplex_editions_controls/src/instructions/add_phase.rs @@ -59,7 +59,7 @@ pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Resu max_mints_total: input.max_mints_total, current_mints: 0, merkle_root: input.merkle_root, - padding: [0; 136], + padding: [0; 200], }); Ok(()) diff --git a/programs/libreplex_editions_controls/src/instructions/initialise.rs b/programs/libreplex_editions_controls/src/instructions/initialise.rs index 43c2737c..548a78e2 100644 --- a/programs/libreplex_editions_controls/src/instructions/initialise.rs +++ b/programs/libreplex_editions_controls/src/instructions/initialise.rs @@ -1,11 +1,9 @@ use anchor_lang::{prelude::*, system_program}; -use libreplex_editions::{ - cpi::accounts::InitialiseCtx, AddMetadata, AddRoyalties - program::LibreplexEditions,AddMetadataArgs, CreatorWithShare, InitialiseInput, UpdateRoyaltiesArgs - group_extension_program, -}; +use libreplex_editions::{cpi::accounts::InitialiseCtx, group_extension_program, program::LibreplexEditions, AddMetadataArgs, CreatorWithShare, InitialiseInput, UpdateRoyaltiesArgs}; +use libreplex_editions::cpi::accounts::AddMetadata; +use libreplex_editions::cpi::accounts::AddRoyalties; use crate::{EditionsControls, PlatformFeeRecipient, UpdatePlatformFeeArgs, DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN, DEFAULT_PLATFORM_FEE_SECONDARY_ADMIN}; -use crate::errors::EditionsError; +use crate::errors::EditionsControlsError; #[derive(AnchorDeserialize, AnchorSerialize, Clone)] pub struct InitialiseControlInput { @@ -119,13 +117,13 @@ pub fn initialise_editions_controls( // Validate that platform_fee has up to 5 recipients let provided_recipients = input.platform_fee.recipients.len(); if provided_recipients > 5 { - return Err(EditionsError::TooManyRecipients.into()); + return Err(EditionsControlsError::TooManyRecipients.into()); } // Ensure that the sum of shares equals 100 let total_shares: u8 = input.platform_fee.recipients.iter().map(|r| r.share).sum(); if total_shares != 100 { - return Err(EditionsError::InvalidFeeShares.into()); + return Err(EditionsControlsError::InvalidFeeShares.into()); } // Initialize an array of 5 PlatformFeeRecipient with default values diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index dfc56685..b3518d6f 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -12,7 +12,7 @@ use libreplex_editions::{ use crate::{ EditionsControls, MinterStats, - errors::EditionsError, + errors::EditionsControlsError, check_phase_constraints, check_allow_list_constraints }; @@ -30,7 +30,7 @@ pub struct MintInput { pub struct MintWithControlsCtx<'info> { #[account(mut)] pub editions_deployment: Box>, -c + #[account( mut, seeds = [b"editions_controls", editions_deployment.key().as_ref()], @@ -210,12 +210,12 @@ fn validate_phase( ) -> Result<()> { if phase_index >= editions_controls.phases.len() as u32 { if editions_controls.phases.is_empty() { - return Err(EditionsError::NoPhasesAdded.into()); + return Err(EditionsControlsError::NoPhasesAdded.into()); } else { - return Err(EditionsError::InvalidPhaseIndex.into()); + return Err(EditionsControlsError::InvalidPhaseIndex.into()); } } - + Ok(()) } @@ -249,7 +249,7 @@ fn process_platform_fees( // Ensure that the sum of shares equals 100 let total_shares: u8 = recipients.iter().map(|r| r.share).sum(); if total_shares != 100 { - return Err(EditionsError::InvalidFeeShares.into()); + return Err(EditionsControlsError::InvalidFeeShares.into()); } let total_fee: u64; @@ -262,12 +262,12 @@ fn process_platform_fees( // Calculate fee as (price_amount * platform_fee_value) / 10,000 (assuming basis points) total_fee = price_amount .checked_mul(editions_controls.platform_fee_value as u64) - .ok_or(EditionsError::FeeCalculationError)? + .ok_or(EditionsControlsError::FeeCalculationError)? .checked_div(10_000) - .ok_or(EditionsError::FeeCalculationError)?; + .ok_or(EditionsControlsError::FeeCalculationError)?; remaining_amount = price_amount.checked_sub(total_fee) - .ok_or(EditionsError::FeeCalculationError)?; + .ok_or(EditionsControlsError::FeeCalculationError)?; } // Distribute fees to recipients @@ -281,14 +281,14 @@ fn process_platform_fees( msg!("check recipients {}: {} vs {}", i, recipient_account.key(), recipient_struct.address.key()); // Ensure that the account matches the expected recipient if recipient_account.key() != recipient_struct.address.key() { - return Err(EditionsError::RecipientMismatch.into()); + return Err(EditionsControlsError::RecipientMismatch.into()); } let recipient_fee = total_fee .checked_mul(recipient_struct.share as u64) - .ok_or(EditionsError::FeeCalculationError)? + .ok_or(EditionsControlsError::FeeCalculationError)? .checked_div(100) - .ok_or(EditionsError::FeeCalculationError)?; + .ok_or(EditionsControlsError::FeeCalculationError)?; // Transfer platform fee to recipient system_program::transfer( diff --git a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs index 81495231..7664c714 100644 --- a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs @@ -4,7 +4,11 @@ use anchor_lang::{ solana_program::hash::hashv, }; use rarible_merkle_verify::verify; -use crate::{MinterStats, Phase}; +use crate::{ + MinterStats, Phase, + errors::{EditionsControlsError}, +}; + /// We need to discern between leaf and intermediate nodes to prevent trivial second /// pre-image attacks. @@ -18,14 +22,14 @@ pub fn check_allow_list_constraints( merkle_proof: Option>, allow_list_price: Option, allow_list_max_claims: Option, -) { +) -> Result<()> { if let Some(merkle_root) = phase.merkle_root { if let Some(proof) = merkle_proof { if let (Some(phase_list_price), Some(phase_max_claims)) = (allow_list_price, allow_list_max_claims) { /// 1. check constraints /// dev: notice that if phase_max_claims is 0, this constraint is disabled if phase_max_claims > 0 && minter_stats_phase.mint_count >= phase_max_claims { - panic!("This wallet has exceeded max_claims in the current private phase") + return Err(EditionsControlsError::ExceededAllowListMaxClaims.into()); } /// 2. construct leaf @@ -38,15 +42,17 @@ pub fn check_allow_list_constraints( /// 3. verify proof against merkle root if !verify(proof, merkle_root, node.to_bytes()) { - panic!("Invalid merkle proof"); + return Err(EditionsControlsError::InvalidMerkleProof.into()); } } else { - panic!("Allow list price and max claims are required for allow list mint"); + return Err(EditionsControlsError::AllowListPriceAndMaxClaimsRequired.into()); } } else { - panic!("Merkle proof required for allow list mint"); + return Err(EditionsControlsError::MerkleProofRequired.into()); } } else { - panic!("Merkle root not set for allow list mint"); + return Err(EditionsControlsError::MerkleRootNotSet.into()); } + + Ok(()) } diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs index 48c2f805..9d208f53 100644 --- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs @@ -2,44 +2,49 @@ use anchor_lang::{ accounts::account::Account, prelude::*, }; -use crate::{EditionsControls, MinterStats, Phase}; +use crate::{ + EditionsControls,MinterStats,Phase, + errors::EditionsControlsError, +}; pub fn check_phase_constraints( phase: &Phase, minter_stats: &mut Account, minter_stats_phase: &mut Account, editions_controls: &Account, -) { +) -> Result<()> { let clock = Clock::get().unwrap(); let current_time = clock.unix_timestamp; if !phase.active { - panic!("Phase not active") + return Err(EditionsControlsError::PhaseNotActive.into()); } if phase.start_time > current_time { - panic!("Phase not yet started") + return Err(EditionsControlsError::PhaseNotStarted.into()); } if phase.end_time <= current_time { - panic!("Phase already finished") + return Err(EditionsControlsError::PhaseAlreadyFinished.into()); } /// Checks if the total mints for the phase has been exceeded (phase sold out) /// @dev dev: notice that if max_mints_total is 0, this constraint is disabled if phase.max_mints_total > 0 && phase.current_mints >= phase.max_mints_total { - panic!("Exceeded max mints for this phase") + return Err(EditionsControlsError::ExceededMaxMintsForPhase.into()); } /// Checks if the user has exceeded the max mints for the deployment (across all phases!) /// dev: notice that if max_mints_per_wallet is 0, this constraint is disabled if editions_controls.max_mints_per_wallet > 0 && minter_stats.mint_count >= editions_controls.max_mints_per_wallet { - panic!("Exceeded wallet max mints for the collection") + return Err(EditionsControlsError::ExceededWalletMaxMintsForCollection.into()); } /// Checks if the user has exceeded the max mints for the current phase /// dev: notice that if max_mints_per_wallet is 0, this constraint is disabled if phase.max_mints_per_wallet > 0 && minter_stats_phase.mint_count >= phase.max_mints_per_wallet { - panic!("Exceeded wallet max mints in the current phase") + return Err(EditionsControlsError::ExceededWalletMaxMintsForPhase.into()); } + + Ok(()) } diff --git a/programs/libreplex_editions_controls/src/state.rs b/programs/libreplex_editions_controls/src/state.rs index c124705f..67e7cdae 100644 --- a/programs/libreplex_editions_controls/src/state.rs +++ b/programs/libreplex_editions_controls/src/state.rs @@ -31,7 +31,7 @@ pub struct Phase { pub end_time: i64, // set to i64::MAX for unlimited pub current_mints: u64, pub merkle_root: Option<[u8; 32]>, - pub padding: [u8; 136] + pub padding: [u8; 200] } impl Phase { @@ -45,7 +45,7 @@ impl Phase { + 8 // end_time + 8 // current_mints + 32 + 1 // merkle_root - + 136; // padding + + 200; // padding } pub const DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN: &str = "674s1Sap3KVnr8WGrY5KGQ69oTYjjgr1disKJo6GpTYw"; @@ -93,7 +93,7 @@ impl EditionsControls { + 8 // platform_fee_value + 1 // is_fee_flat + (PlatformFeeRecipient::SIZE * 5) // platform_fee_recipients (5 * 33 = 165) - + 4 // Vec length for phases + + 4 // Vec length for phases + 200; // padding pub fn get_size(number_of_phases: usize) -> usize { diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 921f9e07..0b51055c 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", "metadata": { "name": "libreplexEditions", "version": "0.2.1", diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index 0eebe9fe..d5a80b99 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -736,6 +736,61 @@ export type LibreplexEditionsControls = { "code": 6016, "name": "invalidPhaseIndex", "msg": "Invalid phase index." + }, + { + "code": 6017, + "name": "merkleRootNotSet", + "msg": "Merkle root not set for allow list mint" + }, + { + "code": 6018, + "name": "merkleProofRequired", + "msg": "Merkle proof required for allow list mint" + }, + { + "code": 6019, + "name": "allowListPriceAndMaxClaimsRequired", + "msg": "Allow list price and max claims are required for allow list mint" + }, + { + "code": 6020, + "name": "invalidMerkleProof", + "msg": "Invalid merkle proof" + }, + { + "code": 6021, + "name": "exceededAllowListMaxClaims", + "msg": "This wallet has exceeded allow list max_claims in the current phase" + }, + { + "code": 6022, + "name": "phaseNotActive", + "msg": "Phase not active" + }, + { + "code": 6023, + "name": "phaseNotStarted", + "msg": "Phase not yet started" + }, + { + "code": 6024, + "name": "phaseAlreadyFinished", + "msg": "Phase already finished" + }, + { + "code": 6025, + "name": "exceededMaxMintsForPhase", + "msg": "Exceeded max mints for this phase" + }, + { + "code": 6026, + "name": "exceededWalletMaxMintsForPhase", + "msg": "Exceeded wallet max mints for this phase" + }, + { + "code": 6027, + "name": "exceededWalletMaxMintsForCollection", + "msg": "Exceeded wallet max mints for the collection" } ], "types": [ @@ -1138,7 +1193,7 @@ export type LibreplexEditionsControls = { "type": { "array": [ "u8", - 136 + 200 ] } } From a6871efb30ad6c4a1f5e1c33f42ec4574419754b Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Fri, 4 Oct 2024 17:36:55 -0300 Subject: [PATCH 16/29] new deployment --- Anchor.toml | 8 ++++---- programs/libreplex_default_renderer/src/lib.rs | 2 +- programs/libreplex_editions/src/lib.rs | 2 +- programs/libreplex_editions_controls/src/lib.rs | 2 +- programs/libreplex_monoswap_deprecated/src/lib.rs | 2 +- target/types/libreplex_default_renderer.ts | 2 +- target/types/libreplex_editions_controls.ts | 10 +++++----- target/types/libreplex_monoswap.ts | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 4030743c..4830bbaf 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -7,13 +7,13 @@ skip-lint = false [programs.localnet] libreplex_creator = "78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM" libreplex_creator_controls = "G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV" -libreplex_default_renderer = "3CkggaHBFWPAghBvpz5QtebciEdh5dGEeN6XWHC2USVf" -libreplex_editions = "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" -libreplex_editions_controls = "4yPHPvJi5ZL6i5S4fvCrgzof2Azs47KNddK8YZs8b5Ra" +libreplex_default_renderer = "7oHj4YFq4JhxVc1BvHqvZma5evSn8hkXwFHPoTQ1PB4f" +libreplex_editions = "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" +libreplex_editions_controls = "8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco" libreplex_inscriptions = "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" libreplex_legacy_inscribers = "Leg1xVbrpq5gY6mprak3Ud4q4mBwcJi5C9ZruYjWv7n" libreplex_metadata = "LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p" -libreplex_monoswap_deprecated = "HPhPBUi8eSYkQb5CUUxXdeaVVwbC7xQz3a6jmkLhy9a3" +libreplex_monoswap_deprecated = "6taGmaEfZx27c25Pk5BwJbDCNNsirVranzWYAeNEhvHi" libreplex_nft = "3oR36M1JQ9LHM1xykKTHDvhuYVM5mDAqRmq989zc1Pzi" libreplex_shop = "ListjawGEdhxuAErSyYwcTEGWQswFoi6FScnGG1RKSB" diff --git a/programs/libreplex_default_renderer/src/lib.rs b/programs/libreplex_default_renderer/src/lib.rs index 9fa264c2..7af9882c 100644 --- a/programs/libreplex_default_renderer/src/lib.rs +++ b/programs/libreplex_default_renderer/src/lib.rs @@ -10,7 +10,7 @@ pub mod instructions; -declare_id!("3CkggaHBFWPAghBvpz5QtebciEdh5dGEeN6XWHC2USVf"); +declare_id!("7oHj4YFq4JhxVc1BvHqvZma5evSn8hkXwFHPoTQ1PB4f"); #[program] diff --git a/programs/libreplex_editions/src/lib.rs b/programs/libreplex_editions/src/lib.rs index 9ac40a07..96097643 100644 --- a/programs/libreplex_editions/src/lib.rs +++ b/programs/libreplex_editions/src/lib.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; pub mod instructions; pub use instructions::*; -declare_id!("2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P"); +declare_id!("GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp"); pub mod errors; pub mod state; diff --git a/programs/libreplex_editions_controls/src/lib.rs b/programs/libreplex_editions_controls/src/lib.rs index 51602459..228f7068 100644 --- a/programs/libreplex_editions_controls/src/lib.rs +++ b/programs/libreplex_editions_controls/src/lib.rs @@ -5,7 +5,7 @@ pub use logic::*; pub mod instructions; pub use instructions::*; -declare_id!("4yPHPvJi5ZL6i5S4fvCrgzof2Azs47KNddK8YZs8b5Ra"); +declare_id!("8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco"); pub mod errors; pub mod state; diff --git a/programs/libreplex_monoswap_deprecated/src/lib.rs b/programs/libreplex_monoswap_deprecated/src/lib.rs index 38988bd2..efc27908 100644 --- a/programs/libreplex_monoswap_deprecated/src/lib.rs +++ b/programs/libreplex_monoswap_deprecated/src/lib.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -declare_id!("HPhPBUi8eSYkQb5CUUxXdeaVVwbC7xQz3a6jmkLhy9a3"); +declare_id!("6taGmaEfZx27c25Pk5BwJbDCNNsirVranzWYAeNEhvHi"); pub mod constants; pub mod errors; diff --git a/target/types/libreplex_default_renderer.ts b/target/types/libreplex_default_renderer.ts index 51c26e28..01c6a51e 100644 --- a/target/types/libreplex_default_renderer.ts +++ b/target/types/libreplex_default_renderer.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_default_renderer.json`. */ export type LibreplexDefaultRenderer = { - "address": "3CkggaHBFWPAghBvpz5QtebciEdh5dGEeN6XWHC2USVf", + "address": "7oHj4YFq4JhxVc1BvHqvZma5evSn8hkXwFHPoTQ1PB4f", "metadata": { "name": "libreplexDefaultRenderer", "version": "0.1.2", diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index d5a80b99..664027b3 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions_controls.json`. */ export type LibreplexEditionsControls = { - "address": "4yPHPvJi5ZL6i5S4fvCrgzof2Azs47KNddK8YZs8b5Ra", + "address": "8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco", "metadata": { "name": "libreplexEditionsControls", "version": "0.2.1", @@ -51,7 +51,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" + "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" } ], "args": [ @@ -152,7 +152,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" + "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" } ], "args": [ @@ -365,7 +365,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" + "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" } ], "args": [ @@ -595,7 +595,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "2BABpp1x8BsgiC2Yu3GFY2zUr6XLJKfG1JkWjV8yor2P" + "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" } ], "args": [ diff --git a/target/types/libreplex_monoswap.ts b/target/types/libreplex_monoswap.ts index e7d11823..46e17f4a 100644 --- a/target/types/libreplex_monoswap.ts +++ b/target/types/libreplex_monoswap.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_monoswap.json`. */ export type LibreplexMonoswap = { - "address": "HPhPBUi8eSYkQb5CUUxXdeaVVwbC7xQz3a6jmkLhy9a3", + "address": "6taGmaEfZx27c25Pk5BwJbDCNNsirVranzWYAeNEhvHi", "metadata": { "name": "libreplexMonoswap", "version": "0.0.0", From aef188f23e3295b9fc9584de7b704b2dc7e67788 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 8 Oct 2024 19:52:02 -0300 Subject: [PATCH 17/29] fixed dependency and max CU per tx issue --- Anchor.toml | 7 +- Cargo.lock | 44 - packages/libreplex-idls/package.json | 23 - packages/libreplex-idls/tsconfig.cjs.json | 11 - packages/libreplex-idls/tsconfig.esm.json | 14 - packages/libreplex-idls/tsconfig.json | 27 - packages/libreplex-idls/tsconfig.types.json | 9 - packages/libreplex-sdk/README.md | 9 - packages/libreplex-sdk/package.json | 31 - packages/libreplex-sdk/src/constants.ts | 6 - .../libreplex-sdk/src/createCollection.ts | 93 -- packages/libreplex-sdk/src/creatorControls.ts | 306 ------- .../libreplex-sdk/src/groupPermissions.ts | 87 -- packages/libreplex-sdk/src/index.ts | 23 - packages/libreplex-sdk/src/mint.ts | 800 ------------------ packages/libreplex-sdk/src/pda.ts | 27 - packages/libreplex-sdk/src/programs.ts | 16 - packages/libreplex-sdk/src/setupCreator.ts | 184 ---- .../libreplex-sdk/src/updateCollection.ts | 30 - packages/libreplex-sdk/src/updateCreator.ts | 50 -- packages/libreplex-sdk/tsconfig.cjs.json | 9 - packages/libreplex-sdk/tsconfig.esm.json | 13 - packages/libreplex-sdk/tsconfig.json | 28 - .../libreplex_default_renderer/Cargo.toml | 31 - .../libreplex_default_renderer/Xargo.toml | 2 - programs/libreplex_default_renderer/c.bmp | Bin 251 -> 0 bytes .../src/instructions/canonical.rs | 56 -- .../src/instructions/input.rs | 27 - .../src/instructions/mod.rs | 5 - .../libreplex_default_renderer/src/lib.rs | 32 - .../libreplex_monoswap_deprecated/Cargo.toml | 33 - .../libreplex_monoswap_deprecated/Xargo.toml | 2 - .../src/constants.rs | 16 - .../src/errors.rs | 36 - .../src/instructions/create_monoswap.rs | 131 --- .../src/instructions/mod.rs | 7 - .../src/instructions/swap.rs | 189 ----- .../libreplex_monoswap_deprecated/src/lib.rs | 33 - .../src/state/mod.rs | 3 - .../src/state/swap_marker.rs | 29 - target/types/libreplex_editions.ts | 52 +- test/editions_controls.test.ts | 285 +++++++ test/utils.ts | 63 ++ tsconfig.json | 28 +- 44 files changed, 387 insertions(+), 2520 deletions(-) delete mode 100644 packages/libreplex-idls/package.json delete mode 100644 packages/libreplex-idls/tsconfig.cjs.json delete mode 100644 packages/libreplex-idls/tsconfig.esm.json delete mode 100644 packages/libreplex-idls/tsconfig.json delete mode 100644 packages/libreplex-idls/tsconfig.types.json delete mode 100644 packages/libreplex-sdk/README.md delete mode 100644 packages/libreplex-sdk/package.json delete mode 100644 packages/libreplex-sdk/src/constants.ts delete mode 100644 packages/libreplex-sdk/src/createCollection.ts delete mode 100644 packages/libreplex-sdk/src/creatorControls.ts delete mode 100644 packages/libreplex-sdk/src/groupPermissions.ts delete mode 100644 packages/libreplex-sdk/src/index.ts delete mode 100644 packages/libreplex-sdk/src/mint.ts delete mode 100644 packages/libreplex-sdk/src/pda.ts delete mode 100644 packages/libreplex-sdk/src/programs.ts delete mode 100644 packages/libreplex-sdk/src/setupCreator.ts delete mode 100644 packages/libreplex-sdk/src/updateCollection.ts delete mode 100644 packages/libreplex-sdk/src/updateCreator.ts delete mode 100644 packages/libreplex-sdk/tsconfig.cjs.json delete mode 100644 packages/libreplex-sdk/tsconfig.esm.json delete mode 100644 packages/libreplex-sdk/tsconfig.json delete mode 100644 programs/libreplex_default_renderer/Cargo.toml delete mode 100644 programs/libreplex_default_renderer/Xargo.toml delete mode 100644 programs/libreplex_default_renderer/c.bmp delete mode 100644 programs/libreplex_default_renderer/src/instructions/canonical.rs delete mode 100644 programs/libreplex_default_renderer/src/instructions/input.rs delete mode 100644 programs/libreplex_default_renderer/src/instructions/mod.rs delete mode 100644 programs/libreplex_default_renderer/src/lib.rs delete mode 100644 programs/libreplex_monoswap_deprecated/Cargo.toml delete mode 100644 programs/libreplex_monoswap_deprecated/Xargo.toml delete mode 100644 programs/libreplex_monoswap_deprecated/src/constants.rs delete mode 100644 programs/libreplex_monoswap_deprecated/src/errors.rs delete mode 100644 programs/libreplex_monoswap_deprecated/src/instructions/create_monoswap.rs delete mode 100644 programs/libreplex_monoswap_deprecated/src/instructions/mod.rs delete mode 100644 programs/libreplex_monoswap_deprecated/src/instructions/swap.rs delete mode 100644 programs/libreplex_monoswap_deprecated/src/lib.rs delete mode 100644 programs/libreplex_monoswap_deprecated/src/state/mod.rs delete mode 100644 programs/libreplex_monoswap_deprecated/src/state/swap_marker.rs create mode 100644 test/editions_controls.test.ts create mode 100644 test/utils.ts diff --git a/Anchor.toml b/Anchor.toml index 4830bbaf..951daf14 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -25,7 +25,7 @@ cluster = "Localnet" wallet = "~/.config/solana/id.json" [scripts] -test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 js-tests/**/*.ts" +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/**/*.ts" [test] startup_wait = 100000 @@ -34,7 +34,7 @@ upgradeable = false [test.validator] bind_address = "0.0.0.0" -url = "https://api.mainnet-beta.solana.com" +url = "https://api.devnet.solana.com" ledger = ".anchor/test-ledger" rpc_port = 8899 @@ -49,3 +49,6 @@ address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" [[test.validator.clone]] address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" + +[[test.validator.clone]] +address = "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a6417738..7551fc2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -698,15 +698,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "bmp" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69985ff4f58085ac696454692d0b646a66ad1f9cc9be294c91dc51bb5df511ae" -dependencies = [ - "byteorder", -] - [[package]] name = "borsh" version = "0.9.3" @@ -2184,19 +2175,6 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" -[[package]] -name = "libreplex_default_renderer" -version = "0.1.2" -dependencies = [ - "anchor-lang", - "assert_matches", - "bmp", - "mocha", - "solana-program", - "solana-program-test", - "solana-sdk", -] - [[package]] name = "libreplex_editions" version = "0.2.1" @@ -2240,22 +2218,6 @@ dependencies = [ "spl-type-length-value 0.3.1", ] -[[package]] -name = "libreplex_monoswap_deprecated" -version = "0.0.0" -dependencies = [ - "anchor-lang", - "anchor-spl", - "assert_matches", - "libreplex_shared", - "mocha", - "mpl-token-metadata", - "solana-program", - "solana-program-test", - "solana-sdk", - "spl-associated-token-account 3.0.2", -] - [[package]] name = "libreplex_shared" version = "0.3.0" @@ -2462,12 +2424,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mocha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0cb102da93ce824d7b0beb84232bf08d90768f1ebb6229be485c0b2e74787f" - [[package]] name = "mockall" version = "0.11.4" diff --git a/packages/libreplex-idls/package.json b/packages/libreplex-idls/package.json deleted file mode 100644 index 63935387..00000000 --- a/packages/libreplex-idls/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@libreplex/idls", - "version": "0.5.2", - "description": "Idls for libreplex programs", - "sideEffects": false, - "files": [ - "lib" - ], - "types": "./lib/types/index.d.ts", - "exports": { - "./lib/esm/*": "./lib/esm/*", - "./lib/cjs/*": "./lib/cjs/*.js", - "./lib/types/*": "./lib/types/*" - }, - "scripts": { - "clean": "npx shx mkdir -p lib && npx shx rm -rf lib", - "package": "npx shx mkdir -p lib/cjs lib/esm lib/types", - "build": "tsc -b ./tsconfig.cjs.json ./tsconfig.esm.json ./tsconfig.types.json" - }, - "keywords": [], - "author": "", - "license": "ISC" -} diff --git a/packages/libreplex-idls/tsconfig.cjs.json b/packages/libreplex-idls/tsconfig.cjs.json deleted file mode 100644 index ff528415..00000000 --- a/packages/libreplex-idls/tsconfig.cjs.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "include": ["../../target/types/**/*"], - "extends": "./tsconfig.json", - "compilerOptions": { - "target": "ES2016", - "module": "CommonJS", - "sourceMap": false, - "outDir": "lib/cjs", - "rootDir": "../../target/types", - } - } \ No newline at end of file diff --git a/packages/libreplex-idls/tsconfig.esm.json b/packages/libreplex-idls/tsconfig.esm.json deleted file mode 100644 index d1625681..00000000 --- a/packages/libreplex-idls/tsconfig.esm.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["../../target/types"], - "compilerOptions": { - "rootDir": "../../target/types", - "outDir": "lib/esm", - "declarationDir": "lib/types", - "target": "ESNext", - "module": "ESNext", - "sourceMap": false, - "declaration": true, - "composite": true - } -} diff --git a/packages/libreplex-idls/tsconfig.json b/packages/libreplex-idls/tsconfig.json deleted file mode 100644 index d01bd73a..00000000 --- a/packages/libreplex-idls/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "include": [], - "compilerOptions": { - "lib": ["ES2021.String"], - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "Node", - "esModuleInterop": true, - "isolatedModules": true, - "noImplicitAny": false, - "noEmitOnError": true, - "resolveJsonModule": true, - "strict": true, - "stripInternal": true, - "skipLibCheck": true, - "noEmit": false, - "allowSyntheticDefaultImports": true, - }, - "references": [ - { - "path": "./tsconfig.esm.json" - }, - { - "path": "./tsconfig.cjs.json" - }, - ] -} diff --git a/packages/libreplex-idls/tsconfig.types.json b/packages/libreplex-idls/tsconfig.types.json deleted file mode 100644 index 0c118a89..00000000 --- a/packages/libreplex-idls/tsconfig.types.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "include": ["../../target/types/**/*"], - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./lib/types", - "declaration": true, - "emitDeclarationOnly": true - } -} diff --git a/packages/libreplex-sdk/README.md b/packages/libreplex-sdk/README.md deleted file mode 100644 index 455182ec..00000000 --- a/packages/libreplex-sdk/README.md +++ /dev/null @@ -1,9 +0,0 @@ -
- - -

Libreplex

- -

- Documentation (stable) -

-
\ No newline at end of file diff --git a/packages/libreplex-sdk/package.json b/packages/libreplex-sdk/package.json deleted file mode 100644 index 096c92d0..00000000 --- a/packages/libreplex-sdk/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@libreplex/sdk", - "version": "0.3.5", - "description": "", - "main": "./lib/cjs/src/index.js", - "module": "./lib/esm/src/index.js", - "types": "./lib/types/src/index.d.ts", - "sideEffects": false, - "files": [ - "lib" - ], - "dependencies": { - "@libreplex/idls": "0.5.2", - "@solana/spl-token": "0.3.8" - }, - "devDependencies": { - "shx": "^0.3.4" - }, - "exports": { - "import": "./lib/esm/src/index.js", - "require": "./lib/cjs/src/index.js", - "types": "./lib/types/src/index.d.ts" - }, - "scripts": { - "clean": "npx shx mkdir -p lib && npx shx rm -rf lib", - "build": "tsc --build" - }, - "keywords": [], - "author": "", - "license": "ISC" -} diff --git a/packages/libreplex-sdk/src/constants.ts b/packages/libreplex-sdk/src/constants.ts deleted file mode 100644 index 9ef6b4a7..00000000 --- a/packages/libreplex-sdk/src/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; - -export const LIBREPLEX_METADATA_PROGRAM_ID = new PublicKey("LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p") -export const LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID = new PublicKey("G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV") -export const LIBREPLEX_CREATOR_PROGRAM_ID = new PublicKey("78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM") -export const LIBREPLEX_NFT_PROGRAM_ID = new PublicKey("9SXDHUdtfvBGT3H2uPCNEkxmWREoqdeS1qdBudLDD6KX") \ No newline at end of file diff --git a/packages/libreplex-sdk/src/createCollection.ts b/packages/libreplex-sdk/src/createCollection.ts deleted file mode 100644 index 13b3e2d9..00000000 --- a/packages/libreplex-sdk/src/createCollection.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Program, BN, Provider } from "@coral-xyz/anchor"; -import { Keypair, PublicKey, SystemProgram } from "@solana/web3.js"; -import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata" -import { getCollectionAddress } from "./pda"; -import { loadMetadataProgram } from "./programs"; - -type AttributeType = { - name: string, - possibleValues: (string | BN | number)[] -} - -export type RoyaltyConfig = { - bps: number, - shares: { - recipient: PublicKey, - share: number, - }[] -} - -type SetupCollectionInput = { - name: string, - symbol: string, - url: string, - royalties: RoyaltyConfig - description: string, - - /** - * The set of possible attributes for metadatas in your collection. - */ - onChainAttributes?: AttributeType[] - - /** - * The set of all addresses that are allowed to sign your collection. - */ - permittedSigners?: PublicKey[] -} - - -export type Connector = { - type: "provider", - provider: Provider, -} | { - type: "program", - metadataProgram: Program, -} - - -export async function setupCollection( - groupInfo: { - connector: Connector, - input: SetupCollectionInput, - collectionAuthority: PublicKey, - groupSeedKp?: Keypair - } - ) { - const { - connector, - input, - collectionAuthority, - groupSeedKp = Keypair.generate() - } = groupInfo - const collection = getCollectionAddress(groupSeedKp.publicKey) - - const metadataProgram = connector.type === "program" ? connector.metadataProgram : await loadMetadataProgram(connector.provider) - - return { - method: metadataProgram.methods.createCollection({ - permittedSigners: input.permittedSigners || [], - attributeTypes: input.onChainAttributes?.map(v => { - return { - permittedValues: v.possibleValues, - continuedFromIndex: null, - continuedAtIndex: null, - deleted: false, - name: v.name, - } - }) || [], - description: input.description, - name: input.name, - symbol: input.symbol, - url: input.url, - royalties: input.royalties - }).accounts({ - authority: collectionAuthority, - seed: groupSeedKp.publicKey, - systemProgram: SystemProgram.programId, - collection, - }), - - collection, - } - } - \ No newline at end of file diff --git a/packages/libreplex-sdk/src/creatorControls.ts b/packages/libreplex-sdk/src/creatorControls.ts deleted file mode 100644 index 441a3725..00000000 --- a/packages/libreplex-sdk/src/creatorControls.ts +++ /dev/null @@ -1,306 +0,0 @@ -import {BN, IdlAccounts, IdlTypes} from "@coral-xyz/anchor" -import { PublicKey } from "@solana/web3.js" -import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls" - -export type CreatorControl = SolPaymentControl | - MintLimitControl | - SplPaymentControl | - AllowListControl | - CustomProgramControl - -export type SolPaymentControl = { - name: "SolPayment", - price: BN, - receiver: PublicKey, -} - -export type MintLimitControl = { - name: "MintLimitControl", - - amount: number, - - // Is this a global limit or does each buy have their own limit - scopedToBuyer: boolean, - - extraSeeds: PublicKey[] -} - - -export type SplPaymentControl = { - name: "SplPayment", - amount: BN, - mint: PublicKey, - recepient: PublicKey, - tokenProgram: PublicKey, -} - -export type AllowListControl = { - label: string, - name: "AllowList", - merkleRoot: number[], -} - - -type CustomProgramAccountMetaKeySeed = { - type: "bytes", - value: Buffer -} | { - type: "mintPlaceHolder", -} | { - type: "receiverPlaceHolder", -} | { - type: "payerPlaceHolder", -} - -type CustomProgramAccountMetaKey = { - type: "key", - value: PublicKey, -} | { - type: "seedDerivation", - programId: PublicKey, - seeds: CustomProgramAccountMetaKeySeed[], -} - -export type CustomProgramControl = { - label: string, - name: "CustomProgram", - programId: PublicKey, - instructionData: Buffer, - remainingAccountsMetas: { - isSigner: boolean, - isWritable: boolean, - key: CustomProgramAccountMetaKey, - }[], -} - -export function anchorToControl(c: IdlAccounts["creatorController"]["phases"][0]["controls"][0]): CreatorControl { - if (c.allowList) { - return { - name: "AllowList", - label: c.allowList[0].label, - merkleRoot: c.allowList[0].root - } - } - - if (c.customProgram) { - return { - name: "CustomProgram", - instructionData: c.customProgram[0].instructionData, - label: c.customProgram[0].label, - programId: c.customProgram[0].programId, - remainingAccountsMetas: c.customProgram[0].remainingAccountMetas.map(m => { - let anchorKey: IdlTypes["CustomProgramAcountMetaKey"] = m.key as any; - - let key: CustomProgramAccountMetaKey; - if (anchorKey.pubkey) { - key = { - type: "key", - value: anchorKey.pubkey[0] - } - } - else if (anchorKey.derivedFromSeeds) { - const anchorSeeds = anchorKey.derivedFromSeeds[0].seeds; - - const seeds: CustomProgramAccountMetaKeySeed[] = [] - for (let anchorSeed of anchorSeeds) { - if (anchorSeed.bytes) { - seeds.push({ - type: "bytes", - value: anchorSeed.bytes[0] - }) - } - else if (anchorSeed.mintPlaceHolder) { - seeds.push({ - type: "mintPlaceHolder", - }) - } - else if (anchorSeed.payerPlaceHolder) { - seeds.push({ - type: "payerPlaceHolder", - }) - } - else if (anchorSeed.receiverPlaceHolder) { - seeds.push({ - type: "receiverPlaceHolder", - }) - } - else { - throw new Error("Unsupported custom program anchor seed.") - } - } - - key = { - type: "seedDerivation", - seeds, - programId: anchorKey.derivedFromSeeds[0].programId, - } - } - else { - throw new Error("Unsupported custom program anchor key.") - } - - - return { - isSigner: m.isSigner, - isWritable: m.isWritable, - key, - } - }), - } - } - - if (c.mintLimit) { - return { - name: "MintLimitControl", - amount: c.mintLimit[0].limit, - extraSeeds: c.mintLimit[0].accountKey, - scopedToBuyer: c.mintLimit[0].scopedToBuyer, - } - } - - if (c.payment) { - const control = c.payment[0]; - - return { - name: "SolPayment", - price: control.amount, - receiver: control.recepient - } - } - - if (c.splPayment) { - const control = c.splPayment[0] - - return { - name: "SplPayment", - amount: control.amount, - mint: control.mint, - recepient: control.recepient, - tokenProgram: control.tokenProgram - } - } - - throw new Error("Tried to convert invalid anchor control") -} - -export function controlToAnchor(c: CreatorControl): IdlTypes["ControlType"] { - switch (c.name) { - case "SolPayment": - return { - payment: { - 0: { - amount: c.price, - recepient: c.receiver, - } - } - } - - case "AllowList": - return { - allowList: { - "0": { - root: c.merkleRoot, - label: c.label, - } - } - } - - case "CustomProgram": - - const remainingAccountMetas: IdlTypes["CustomProgramAccountMeta"][] = [] - - for (let meta of c.remainingAccountsMetas) { - let key: IdlTypes["CustomProgramAcountMetaKey"]; - - switch (meta.key.type) { - case "key": - key = { - pubkey: { - "0": meta.key.value, - } - } - break; - case "seedDerivation": - const seeds: IdlTypes["Seed"][] = [] - - for (const seed of meta.key.seeds) { - switch (seed.type) { - case "bytes": - seeds.push({ - bytes: { - "0": seed.value - } - }) - break; - case "mintPlaceHolder": - seeds.push({ - mintPlaceHolder: {} - }) - break; - case "receiverPlaceHolder": - seeds.push({ - receiverPlaceHolder: {} - }) - break; - case "payerPlaceHolder": - seeds.push({ - payerPlaceHolder: {} - }) - break; - - default: - throw new Error(`Invalid seed ${seed}`) - } - } - - key = { - derivedFromSeeds: { - "0": { - programId: meta.key.programId, - seeds, - } - } - } - break; - default: - throw new Error(`Invalid custom program account meta key type. ${meta.key}`) - } - - remainingAccountMetas.push({ - isSigner: meta.isSigner, - isWritable: meta.isWritable, - key, - }) - } - - return { - customProgram: { - 0: { - ...c, - remainingAccountMetas - } - } - } - - case "MintLimitControl": - return { - mintLimit: { - "0": { - limit: c.amount, - scopedToBuyer: c.scopedToBuyer, - accountKey: c.extraSeeds, - } - } - } - - case "SplPayment": - return { - splPayment: { - "0": c - } - } - - default: - throw new Error("Invalid control") - }; -} \ No newline at end of file diff --git a/packages/libreplex-sdk/src/groupPermissions.ts b/packages/libreplex-sdk/src/groupPermissions.ts deleted file mode 100644 index a8f4b3f7..00000000 --- a/packages/libreplex-sdk/src/groupPermissions.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Program } from "@coral-xyz/anchor" -import { PublicKey, SystemProgram } from "@solana/web3.js" -import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata"; - -import { LIBREPLEX_METADATA_PROGRAM_ID } from "./constants"; -import { Connector } from "./createCollection"; -import { loadMetadataProgram } from "./programs"; - -export enum UserPermission { - Update, - Delete, - AddToGroup, - } - - export function getCollectionWideUserPermissionsAddress(collection: PublicKey, user: PublicKey, program = LIBREPLEX_METADATA_PROGRAM_ID) { - return PublicKey.findProgramAddressSync([Buffer.from("permissions"), user.toBuffer(), collection.toBuffer()], program)[0] - - } - - function convertPermission(p: UserPermission) { - if (p === UserPermission.AddToGroup) { - return { - addToGroup: {} - } - } - - if (p === UserPermission.Delete) { - return { - addToGroup: {} - } - } - - if (p === UserPermission.Update) { - return { - addToGroup: {} - } - } - - throw new Error("Invalid permission enum") - } - - -export async function setUserPermissionsForGroup( - { - connector, - collection, - user, - permissions, - groupUpdateAuthority, - }: { - connector: Connector, - collection: PublicKey, - user: PublicKey, - permissions: UserPermission[], - groupUpdateAuthority: PublicKey, - } - ) { - const permissionsAccountAddress = getCollectionWideUserPermissionsAddress(collection, user) - - const metadataProgram = connector.type === "program" ? connector.metadataProgram : await loadMetadataProgram(connector.provider) - - - const existingPermissionsInfo = await metadataProgram.provider.connection.getAccountInfo(permissionsAccountAddress) - - const anchorPermissions = permissions.map(convertPermission); - - if (!existingPermissionsInfo) { - return metadataProgram.methods.delegateCollectionPermissions({ - permissions: anchorPermissions, - }).accounts({ - collection, - delegatedUser: user, - systemProgram: SystemProgram.programId, - updateAuthority: groupUpdateAuthority, - userPermissions: permissionsAccountAddress - }) - } - - return metadataProgram.methods.updatePermissions({ - permissions: anchorPermissions - }).accounts({ - updateAuthority: groupUpdateAuthority, - user: user, - userPermissions: permissionsAccountAddress - }) - } - \ No newline at end of file diff --git a/packages/libreplex-sdk/src/index.ts b/packages/libreplex-sdk/src/index.ts deleted file mode 100644 index 47a875b2..00000000 --- a/packages/libreplex-sdk/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export {setupCollection} from "./createCollection" -export * from "./constants" -export * from "./pda" -export {setUserPermissionsForGroup, UserPermission} from "./groupPermissions" - -export {setupCreator, setupCreatorWithCustomSalePhases} from "./setupCreator" -export type {Phase} from "./setupCreator" - -export {mintFromCreatorController, mintFromCreatorControllerState} from "./mint" - -export type {CreatorControl, AllowListControl, CustomProgramControl, - MintLimitControl, SolPaymentControl, SplPaymentControl as SplPayment} from "./creatorControls" - -export {anchorToControl, controlToAnchor} from "./creatorControls" - -export {updateCreator} from "./updateCreator" - - -export type {UpdateCreatorInput} from "./updateCreator" - -export {mintSingle, setupLibreplexReadyMint} from "./mint" - -export {updateCollectionAuthority} from "./updateCollection" \ No newline at end of file diff --git a/packages/libreplex-sdk/src/mint.ts b/packages/libreplex-sdk/src/mint.ts deleted file mode 100644 index c12ea3df..00000000 --- a/packages/libreplex-sdk/src/mint.ts +++ /dev/null @@ -1,800 +0,0 @@ -import { - AccountMeta, - Connection, - Keypair, - PublicKey, - SYSVAR_SLOT_HASHES_PUBKEY, - SystemProgram, - Transaction, - TransactionInstruction, -} from '@solana/web3.js'; -import { LibreplexCreator } from '@libreplex/idls/lib/types/libreplex_creator'; -import { LibreplexMetadata } from '@libreplex/idls/lib/types/libreplex_metadata'; -import { LibreplexNft } from '@libreplex/idls/lib/types/libreplex_nft'; -import { LibreplexCreatorControls } from '@libreplex/idls/lib/types/libreplex_creator_controls'; -import { - Program, - AccountClient, - IdlAccounts, - IdlTypes, - Provider, -} from '@coral-xyz/anchor'; -import { - LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID, - LIBREPLEX_CREATOR_PROGRAM_ID, - LIBREPLEX_METADATA_PROGRAM_ID, - LIBREPLEX_NFT_PROGRAM_ID, -} from './constants'; -import { - MINT_SIZE, - TOKEN_2022_PROGRAM_ID, - createInitializeMint2Instruction, - getMinimumBalanceForRentExemptMint, - getAssociatedTokenAddressSync, - createAssociatedTokenAccountInstruction, - createMintToInstruction, -} from '@solana/spl-token'; -import { struct, u8 } from '@solana/buffer-layout'; -import { publicKey } from '@solana/buffer-layout-utils'; -import { getMetadataAddress, getMintWrapperAddress } from './pda'; -import { getCollectionWideUserPermissionsAddress } from './groupPermissions'; - -import { RoyaltyConfig } from './createCollection'; -import { loadMetadataProgram, loadNftProgram } from './programs'; - -type CustomProgramAccountMeta = Omit & { - key: - | { - keyType: 'PublicKey'; - value: PublicKey; - } - | { - keyType: 'PDA'; - seeds: Buffer[]; - deriveFromMint: boolean; - deriveFromBuyer: boolean; - programIdToDeriveFrom: PublicKey; - }; -}; - -export type MintFromCreatorControllerInput = { - creatorControllerProgram: Program; - creatorProgram: Program; - creatorController: PublicKey; - - mintKeyPair?: Keypair; - - // If there are multiple active sale phases, specify the one to mint in. - phaseToMintIn?: string; - - merkleProofsForAllowLists?: { - label: string; - proof: Buffer[]; - }[]; - - addTransferHookToMint?: { - programId: PublicKey; - authority: PublicKey; - }; -}; - -type MintFromCreatorControllerStateInput = { - creator: PublicKey; - targetPhase: IdlAccounts['creatorController']['phases'][0]; - minterNumbers: PublicKey | null; - collection: PublicKey; -} & Omit; - -export async function mintFromCreatorControllerState( - input: MintFromCreatorControllerStateInput -) { - const { - creatorControllerProgram, - creatorController, - merkleProofsForAllowLists, - addTransferHookToMint, - minterNumbers, - targetPhase, - collection, - creator, - } = input; - - let mintKeyPair = input.mintKeyPair || Keypair.generate(); - - const connection = creatorControllerProgram.provider.connection; - const me = creatorControllerProgram.provider.publicKey; - - if (!me) { - throw new Error('Provider not setup. Perhaps your wallet is not connected'); - } - - const args: Buffer[] = []; - const remainingAccounts: AccountMeta[] = []; - - const controls = targetPhase.controls; - - for (const control of controls) { - if (control.payment) { - remainingAccounts.push({ - isSigner: false, - isWritable: true, - pubkey: control.payment[0].recepient, - }); - } else if (control.splPayment) { - remainingAccounts.push({ - isSigner: false, - isWritable: true, - pubkey: control.splPayment[0].recepient, - }); - - remainingAccounts.push({ - isSigner: false, - isWritable: true, - pubkey: getAssociatedTokenAddressSync( - control.splPayment[0].mint, - me, - undefined, - control.splPayment[0].tokenProgram - ), - }); - - remainingAccounts.push({ - isSigner: false, - isWritable: false, - pubkey: control.splPayment[0].tokenProgram, - }); - } else if (control.mintLimit) { - const seeds: Buffer[] = [Buffer.from('mint_limit')]; - - if (control.mintLimit[0].scopedToBuyer) { - seeds.push(me.toBuffer()); - } - - control.mintLimit[0].accountKey.forEach((keyElement) => { - seeds.push(keyElement.toBuffer()); - }); - - const mintLimitAccount = PublicKey.findProgramAddressSync( - seeds, - LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID - )[0]; - - remainingAccounts.push({ - isSigner: false, - isWritable: true, - pubkey: mintLimitAccount, - }); - } else if (control.allowList) { - if (!merkleProofsForAllowLists) { - throw new Error( - 'Must provide merkle proofs when your creator as an allowlist' - ); - } - - const proofEntry = merkleProofsForAllowLists.find( - (mp) => mp.label === control.allowList[0].label - ); - - if (!proofEntry) { - throw new Error( - `Proof entry not found for allowlist: ${control.allowList[0].label}` - ); - } - - args.push(Buffer.concat(proofEntry.proof)); - } else if (control.customProgram) { - const remainingAccountMetas: AccountMeta[] = [ - { - isSigner: false, - isWritable: false, - pubkey: control.customProgram[0].programId, - }, - ]; - - for (const meta of control.customProgram[0].remainingAccountMetas) { - const key: IdlTypes['CustomProgramAcountMetaKey'] = - meta.key as any; - - if (key.pubkey) { - remainingAccountMetas.push({ - ...meta, - pubkey: key.pubkey[0], - }); - } else if (key.derivedFromSeeds) { - const programId = key.derivedFromSeeds[0].programId; - - const seeds: Buffer[] = []; - for (const seed of key.derivedFromSeeds[0].seeds) { - if (seed.bytes) { - seeds.push(seed.bytes[0]); - } else if (seed.mintPlaceHolder) { - seeds.push(mintKeyPair.publicKey.toBuffer()); - } else if (seed.payerPlaceHolder || seed.receiverPlaceHolder) { - seeds.push(me.toBuffer()); - } else { - throw new Error('Invalid seed derivation'); - } - } - - remainingAccountMetas.push({ - ...meta, - pubkey: PublicKey.findProgramAddressSync(seeds, programId)[0], - }); - } else { - throw new Error('Invalid CustomProgramAcountMetaKey'); - } - } - - remainingAccounts.push(...remainingAccountMetas); - } - } - - const metadata = getMetadataAddress(mintKeyPair.publicKey); - const setupMintCtx = await setupLibreplexReadyMint( - connection, - me, - me, - me, - me, - 0, - mintKeyPair, - metadata, - addTransferHookToMint - ); - - return { - method: creatorControllerProgram.methods - .mint({ - chosenPhase: targetPhase.label, - args, - }) - .accounts({ - attributeConfig: null, - creator, - creatorController, - collection, - libreplexCreatorProgram: LIBREPLEX_CREATOR_PROGRAM_ID, - libreplexMetadataProgram: LIBREPLEX_METADATA_PROGRAM_ID, - libreplexNftProgram: LIBREPLEX_NFT_PROGRAM_ID, - mint: mintKeyPair.publicKey, - metadata, - mintAuthority: me, - minterNumbers, - mintWrapper: getMintWrapperAddress(mintKeyPair.publicKey), - payer: me, - receiver: me, - receiverTokenAccount: getAssociatedTokenAddressSync( - mintKeyPair.publicKey, - me, - undefined, - TOKEN_2022_PROGRAM_ID - ), - recentSlothashes: SYSVAR_SLOT_HASHES_PUBKEY, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - collectionPermissions: getCollectionWideUserPermissionsAddress( - collection, - creator - ), - }) - .preInstructions([...setupMintCtx.transaction.instructions]) - .signers([mintKeyPair]) - .remainingAccounts(remainingAccounts), - - mint: mintKeyPair, - }; -} - -export async function mintFromCreatorController( - input: MintFromCreatorControllerInput -) { - const { - creatorControllerProgram, - creatorController, - creatorProgram, - phaseToMintIn, - } = input; - - const controller = - await creatorControllerProgram.account.creatorController.fetchNullable( - creatorController - ); - - if (!controller) { - throw new Error( - `Creator controller at address: ${creatorController.toString()} not found` - ); - } - - const creator = await creatorProgram.account.creator.fetchNullable( - controller.creator - ); - - if (!creator) { - throw new Error( - `Creator at address ${controller.creator?.toString()} not found` - ); - } - - const now = Date.now() / 1000; - - const availableSalePhases = controller.phases; - - const activePhases = availableSalePhases.filter( - (ph) => now > ph.start && (ph.end === null || now < ph.end) - ); - - if (activePhases.length === 0) { - throw new Error('No currently active phases to mint from'); - } - - let targetPhase = activePhases[0]; - - if (activePhases.length > 1) { - if (!phaseToMintIn) { - throw new Error( - 'Must provide a target phase to mint in when multiple are active' - ); - } - - const maybeTargetPhase = activePhases.find( - (ph) => ph.label === phaseToMintIn - ); - - if (!maybeTargetPhase) { - throw new Error( - `Specified phase to mint in ${phaseToMintIn} is not active` - ); - } - - targetPhase = maybeTargetPhase; - } - - return mintFromCreatorControllerState({ - ...input, - targetPhase, - creator: controller.creator, - minterNumbers: creator.minterNumbers, - collection: creator.collection, - }); -} - -export type MintAssetUrl = - | { - type: 'jsonUrl'; - value: string; - } - | { - type: 'imageUrl'; - value: string; - } - | { - type: 'renderedOnChain'; - programId: PublicKey; - description: string | null; - }; - -export type MetadataExtension = { - // Metadata extension data. If you don't want it don't take it. - - licenseUrl?: string; - - /** - * The list of keys that can add their signature to your metadata. - */ - permittedSigners?: PublicKey[]; - - /** - * Only works when part of a group. - * Attribute defintions exist on the group. - * List of pointers to the on chain attributes stored in the group. - */ - onChainAttributes?: number[]; - - royalties?: RoyaltyConfig; -}; - -export type MetadataData = { - name: string; - symbol: string; - assetUrl: MintAssetUrl; - - extension?: MetadataExtension; -}; - -export type MintSingleInput = { - provider: Provider; - - mintData: MetadataData; - - mintToCollection?: { - collection: PublicKey; - checkValidGroup: boolean; - - /** - * If you are not the update auth of the group. - * But have been given permission to add metadatas to it. - * Set this to true. - * - * Defaults to false. - */ - groupDelegate?: boolean; - }; - - mintKp?: Keypair; - - receiver?: PublicKey; - - transferHook?: TransferHookConfig; - - metadataProgram?: Program; - nftProgram?: Program; - - updateAuthority?: PublicKey; -}; - -export async function mintSingle(input: MintSingleInput) { - const { - provider, - metadataProgram = await loadMetadataProgram(provider), - nftProgram = await loadNftProgram(provider), - mintToCollection, - receiver = provider.publicKey, - mintKp = Keypair.generate(), - transferHook, - updateAuthority = provider.publicKey, - mintData, - } = input; - - const me = provider.publicKey; - - if (!me) { - throw new Error( - 'Provider does have a wallet loaded into it. Are you sure your wallet is connected' - ); - } - - if (mintToCollection) { - if (mintToCollection.checkValidGroup) { - const groupData = await metadataProgram.account.collection.fetchNullable( - mintToCollection.collection - ); - - if (!groupData) { - throw new Error('Group does not exist'); - } - - if (groupData.updateAuthority.toString() != me.toString()) { - const groupWideAddress = getCollectionWideUserPermissionsAddress( - mintToCollection.collection, - me - ); - - const permissionsData = - await metadataProgram.account.delegatePermissions.fetchNullable( - groupWideAddress - ); - - const hasDelegatedPermission = !!permissionsData?.permissions.find( - (perm) => !!perm.addToGroup - ); - - if (!permissionsData || !hasDelegatedPermission) { - throw new Error( - 'You do not have permission to add metadata to this group.' - ); - } - - mintToCollection.groupDelegate = true; - } - } - } - - const connection = provider.connection; - - const metadata = getMetadataAddress(mintKp.publicKey); - - const mintCtx = await setupLibreplexReadyMint( - connection, - me, - me, - receiver as PublicKey, - me, - 0, - mintKp, - metadata, - transferHook - ); - - let anchorAssetUrl: IdlTypes['Asset']; - - const { assetUrl, name, symbol } = mintData; - - switch (assetUrl.type) { - case 'jsonUrl': - anchorAssetUrl = { - json: { - url: assetUrl.value, - }, - }; - break; - case 'imageUrl': - anchorAssetUrl = { - image: { - url: assetUrl.value, - description: null, - }, - }; - break; - case 'renderedOnChain': - anchorAssetUrl = { - chainRenderer: { - programId: assetUrl.programId, - }, - }; - break; - - default: - throw new Error('Invalid asset type'); - } - - type ExtensionType = IdlTypes['MetadataExtension']; - let extensions: ExtensionType[] = []; - - if (mintData.extension) { - if (mintData.extension.onChainAttributes) { - extensions.push({ - attributes: { - attributes: Buffer.from(mintData.extension.onChainAttributes ?? []), - }, - }); - } - if (mintData.extension.royalties) { - extensions.push({ - royalties: { - royalties: mintData.extension.royalties || null, - }, - }); - } - - if (mintData.extension.licenseUrl) { - extensions.push({ - license: { - license: { - custom: { - licenseUrl: mintData.extension.licenseUrl, - }, - }, - }, - }); - } - - if (mintData.extension.permittedSigners) { - extensions.push({ - signers: { - signers: mintData.extension.permittedSigners || [], - }, - }); - } - } - console.log('Creating metadata instructions'); - const createMetaData = metadataProgram.methods - .createMetadata({ - asset: anchorAssetUrl, - extensions, - name, - symbol, - updateAuthority: updateAuthority as PublicKey, - }) - .accounts({ - authority: me, - metadata, - payer: me, - mint: mintKp.publicKey, - invokedMigratorProgram: null, - systemProgram: SystemProgram.programId, - }) - .preInstructions(mintCtx.transaction.instructions) - .signers([mintCtx.keypair]); - - console.log('Created metadata instruction'); - - const postIxs: TransactionInstruction[] = [ - await nftProgram.methods - .wrap() - .accounts({ - authority: me, - mint: mintKp.publicKey, - payer: me, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - wrappedMint: getMintWrapperAddress(mintKp.publicKey), - }) - .instruction(), - ]; - - if (mintToCollection) { - const ix = await metadataProgram.methods - .addMetadataToCollection() - .accounts({ - delegatedCollectionWidePermissions: mintToCollection.groupDelegate - ? getCollectionWideUserPermissionsAddress( - mintToCollection.collection, - me - ) - : null, - systemProgram: SystemProgram.programId, - payer: me, - metadata, - metadataAuthority: me, - collectionAuthority: me, - delegatedMetadataSpecificPermissions: null, - collection: mintToCollection.collection, - }) - .instruction(); - - postIxs.push(ix); - } - - return { - method: createMetaData.postInstructions(postIxs), - mint: mintKp, - }; -} - -const MetadataPointerMintSize = 234; -const MintSizeForTranserHookAndPointer = 302; - -interface InitializeMetadataPointerIx { - instruction: 39; - metadataPointerInitIx: 0; - authority: PublicKey; - metadataAddress: PublicKey; -} - -const initializeMetadataPointerInstructionData = - struct([ - u8('instruction') as any, - u8('metadataPointerInitIx'), - publicKey('authority'), - publicKey('metadataAddress'), - ]); - -interface InitializeTransferHookInit { - instruction: 36; - transferHookInstruction: 0; - authority: PublicKey; - transferHookProgramId: PublicKey; -} - -const initializeTransferHookInitInstructionData = - struct([ - u8('instruction') as any, - u8('transferHookInstruction'), - publicKey('authority'), - publicKey('transferHookProgramId'), - ]); - -type TransferHookConfig = { - programId: PublicKey; - authority: PublicKey; -}; - -export async function setupLibreplexReadyMint( - connection: Connection, - payer: PublicKey, - receiver: PublicKey, - mintAuthority: PublicKey, - freezeAuthority: PublicKey | null, - decimals: number, - mintKeypair = Keypair.generate(), - metadata: PublicKey, - transferHook?: TransferHookConfig, - programId = TOKEN_2022_PROGRAM_ID -) { - const mintSize = transferHook - ? MintSizeForTranserHookAndPointer - : MetadataPointerMintSize; - const lamports = await connection.getMinimumBalanceForRentExemption(mintSize); - - const initMetadataPointerExtensionIx = (() => { - const initMetadataPointerIxSpan = Buffer.alloc( - initializeMetadataPointerInstructionData.span - ); - - initializeMetadataPointerInstructionData.encode( - { - instruction: 39, - authority: PublicKey.default, - metadataPointerInitIx: 0, - metadataAddress: metadata, - }, - initMetadataPointerIxSpan - ); - - return new TransactionInstruction({ - keys: [ - { - isSigner: false, - isWritable: true, - pubkey: mintKeypair.publicKey, - }, - ], - programId, - data: initMetadataPointerIxSpan, - }); - })(); - - const preInitMintIxs: TransactionInstruction[] = []; - - if (transferHook) { - const accounts = [ - { pubkey: mintKeypair.publicKey, isSigner: false, isWritable: true }, - ]; - const transferHookIxBuf = Buffer.alloc( - initializeTransferHookInitInstructionData.span - ); - initializeTransferHookInitInstructionData.encode( - { - authority: transferHook.authority, - transferHookProgramId: transferHook.programId, - instruction: 36, - transferHookInstruction: 0, - }, - transferHookIxBuf - ); - - preInitMintIxs.push( - new TransactionInstruction({ - keys: accounts, - programId: TOKEN_2022_PROGRAM_ID, - data: transferHookIxBuf, - }) - ); - } - - const assocTokenAccount = getAssociatedTokenAddressSync( - mintKeypair.publicKey, - receiver, - undefined, - TOKEN_2022_PROGRAM_ID - ); - const transaction = new Transaction().add( - SystemProgram.createAccount({ - fromPubkey: payer, - newAccountPubkey: mintKeypair.publicKey, - space: mintSize, - lamports, - programId, - }), - initMetadataPointerExtensionIx, - ...preInitMintIxs, - createInitializeMint2Instruction( - mintKeypair.publicKey, - decimals, - mintAuthority, - freezeAuthority, - programId - ), - createAssociatedTokenAccountInstruction( - payer, - assocTokenAccount, - receiver, - mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID - ), - createMintToInstruction( - mintKeypair.publicKey, - assocTokenAccount, - mintAuthority, - 1, - undefined, - TOKEN_2022_PROGRAM_ID - ) - ); - - return { - transaction, - keypair: mintKeypair, - }; -} diff --git a/packages/libreplex-sdk/src/pda.ts b/packages/libreplex-sdk/src/pda.ts deleted file mode 100644 index 6a9ffa30..00000000 --- a/packages/libreplex-sdk/src/pda.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { LIBREPLEX_METADATA_PROGRAM_ID, LIBREPLEX_CREATOR_PROGRAM_ID, LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID, LIBREPLEX_NFT_PROGRAM_ID } from "./constants"; - - - -export function getCollectionAddress(collectionSeed: PublicKey, program = LIBREPLEX_METADATA_PROGRAM_ID) { - return PublicKey.findProgramAddressSync([Buffer.from("collection"), collectionSeed.toBuffer()], LIBREPLEX_METADATA_PROGRAM_ID)[0] - - } - -export function getCreatorAddress(seed: PublicKey, program = LIBREPLEX_CREATOR_PROGRAM_ID) { - return PublicKey.findProgramAddressSync([Buffer.from("creator"), seed.toBuffer()], program)[0] - -} - -export function getCreatorControllerAddress(seed: PublicKey, programId: PublicKey = LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID) { - return PublicKey.findProgramAddressSync([seed.toBuffer()], programId)[0] -} - -export function getMetadataAddress(mint: PublicKey, program = LIBREPLEX_METADATA_PROGRAM_ID) { - return PublicKey.findProgramAddressSync([Buffer.from("metadata"), - mint.toBuffer()], program)[0] -} - -export function getMintWrapperAddress(mint: PublicKey, program = LIBREPLEX_NFT_PROGRAM_ID) { - return PublicKey.findProgramAddressSync([mint.toBuffer()], program)[0] -} \ No newline at end of file diff --git a/packages/libreplex-sdk/src/programs.ts b/packages/libreplex-sdk/src/programs.ts deleted file mode 100644 index 45d56967..00000000 --- a/packages/libreplex-sdk/src/programs.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {LibreplexCreator} from "@libreplex/idls/lib/types/libreplex_creator" -import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata" -import {LibreplexNft} from "@libreplex/idls/lib/types/libreplex_nft" -import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls" -import { Program, AccountClient , IdlAccounts, IdlTypes, Provider} from "@coral-xyz/anchor" -import { LIBREPLEX_METADATA_PROGRAM_ID, LIBREPLEX_NFT_PROGRAM_ID } from "./constants" - -export async function loadMetadataProgram(provider: Provider) { - return new Program((await import("@libreplex/idls/lib/cjs/libreplex_metadata")).IDL, - LIBREPLEX_METADATA_PROGRAM_ID, provider) -} - -export async function loadNftProgram(provider: Provider) { - return new Program((await import("@libreplex/idls/lib/cjs/libreplex_nft")).IDL, - LIBREPLEX_NFT_PROGRAM_ID, provider) -} diff --git a/packages/libreplex-sdk/src/setupCreator.ts b/packages/libreplex-sdk/src/setupCreator.ts deleted file mode 100644 index 7499e3a0..00000000 --- a/packages/libreplex-sdk/src/setupCreator.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { BN, Program } from "@coral-xyz/anchor" -import {LibreplexCreator} from "@libreplex/idls/lib/types/libreplex_creator" -import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata" -import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls" - -import { Keypair, PublicKey, SystemProgram, TransactionInstruction } from "@solana/web3.js"; -import {getCreatorAddress, getCreatorControllerAddress} from "./pda" -import {UserPermission, setUserPermissionsForGroup} from "./groupPermissions" -import { CreatorControl, controlToAnchor } from "./creatorControls"; -import { LIBREPLEX_CREATOR_PROGRAM_ID } from "./constants"; - -export type SetupCreatorData = { - description: string, - baseName: string, - ordered: boolean, - supply: number, - symbol: string, - baseUrl: { - type: "json-prefix", - url: string, - } | { - type: "chain-renderer", - programId: PublicKey, - description?: string, - }, -} - -export type SetupCreatorInput = { - mintAuthority: PublicKey, - program: Program, - metadataProgram: Program, - collection: PublicKey, - creatorData: SetupCreatorData, -} - - - -export type Phase = { - start: Date, - end: Date | null, - label: string, - control: CreatorControl[] -} - -export async function setupCreatorWithCustomSalePhases( - input: SetupCreatorInput, - creatorControllerProgram: Program, - salePhases: Phase[], - checkGroupIsValid = true) { - const me = creatorControllerProgram.provider.publicKey - const setupCreatorCtx = await setupCreator(input, checkGroupIsValid) - - const creatorControllerSeed = Keypair.generate() - - const anchorPhases = salePhases.map(p => { - const anchorControls = p.control.map(controlToAnchor) - - return { - start: new BN(Math.floor(p.start.getTime() / 1000)), - end: p.end != null ? new BN(Math.floor(p.end.getTime() / 1000)) : null, - label: p.label, - controls: anchorControls, - } - }); - - const creatorController = getCreatorControllerAddress(creatorControllerSeed.publicKey) - - const controllerCtx = await creatorControllerProgram.methods.initialize({ - seed: creatorControllerSeed.publicKey, - phases: anchorPhases - }).accounts({ - creator: setupCreatorCtx.creator, - creatorController, - libreplexCreatorProgram: LIBREPLEX_CREATOR_PROGRAM_ID, - systemProgram: SystemProgram.programId, - updateAuthority: me, - payer: me, - }).prepare() - - const method = setupCreatorCtx.method.postInstructions([controllerCtx.instruction]) - - return { - method, - creatorController, - creator: setupCreatorCtx.creator, - minterNumbers: setupCreatorCtx.minterNumbers, - } -} - - -export async function setupCreator(input: SetupCreatorInput, checkGroupIsValid = true) { - const {program, collection, creatorData, mintAuthority, metadataProgram} = input; - const {description, baseName, ordered, supply, symbol, baseUrl} = creatorData; - - const me = program.provider.publicKey - - if (checkGroupIsValid) { - const groupAccount = await metadataProgram.account.collection.fetchNullable(collection) - - if (!groupAccount) { - throw new Error("Provided group does not exist") - } - - - if (groupAccount.updateAuthority.toString() !== me?.toString()) { - throw new Error(`You do not have authority over the provided group. - ${groupAccount.updateAuthority.toString()} ${me?.toString()}`) - } - } - - const creatorSeed = Keypair.generate() - const creator = getCreatorAddress(creatorSeed.publicKey); - - const preIx: TransactionInstruction[] = [] - const signers: Keypair[] = [] - let minterNumbers: PublicKey | null = null - - if (!ordered) { - const minterNumbersKp = Keypair.generate() - const minterNumbersSize = 8 + 32 + 4 * supply - const rent = await program.provider.connection.getMinimumBalanceForRentExemption(minterNumbersSize, "confirmed") - - const creatorMinterNumbersIx = SystemProgram.createAccount({ - fromPubkey: program.provider.publicKey as PublicKey, - lamports: rent, - newAccountPubkey: minterNumbersKp.publicKey, - programId: program.programId, - space: minterNumbersSize, - }) - - minterNumbers = minterNumbersKp.publicKey - - preIx.push(creatorMinterNumbersIx) - signers.push(minterNumbersKp) - } - - - let createCreatorMethod = await program.methods.createCreator({ - attributeMappings: null, - collection, - description, - isOrdered: ordered, - maxMints: supply, - mintAuthority, - name: baseName, - seed: creatorSeed.publicKey, - symbol: symbol, - assetUrl: baseUrl.type === "json-prefix" ? { - jsonPrefix: { - url: baseUrl.url, - } - } : { - chainRenderer: { - programId: baseUrl.programId - } - } - }).accounts({ - creator, - minterNumbers, - signer: program.provider.publicKey, - systemProgram: SystemProgram.programId, - }).preInstructions(preIx).signers(signers) - - const delegateToGroupMethod = await (await setUserPermissionsForGroup({ - collection, - groupUpdateAuthority: me as PublicKey, - user: creator, - connector: { - type: "program", - metadataProgram, - }, - permissions: [UserPermission.AddToGroup] - })).prepare() - - createCreatorMethod = createCreatorMethod.postInstructions([delegateToGroupMethod.instruction]) - - - - return { - method: createCreatorMethod, - creator, - minterNumbers - } -} \ No newline at end of file diff --git a/packages/libreplex-sdk/src/updateCollection.ts b/packages/libreplex-sdk/src/updateCollection.ts deleted file mode 100644 index 9c5011bd..00000000 --- a/packages/libreplex-sdk/src/updateCollection.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { PublicKey } from "@solana/web3.js"; -import { Connector } from "./createCollection"; -import { loadMetadataProgram } from "./programs"; - - - -export async function updateCollectionAuthority( - { - connector, - collection, - new_authority - }: { - connector: Connector, - collection: PublicKey, - new_authority: PublicKey, - } - ) { - const metadataProgram = connector.type === "program" ? connector.metadataProgram : await loadMetadataProgram(connector.provider) - - const me = metadataProgram.provider.publicKey; - - if (!me) { - throw new Error("Provider not setup. Perhaps your wallet is not connected"); - } - - return metadataProgram.methods.updateCollectionAuthority(new_authority).accounts({ - collection, - updateAuthority: me, - }) - } diff --git a/packages/libreplex-sdk/src/updateCreator.ts b/packages/libreplex-sdk/src/updateCreator.ts deleted file mode 100644 index 0354cbd6..00000000 --- a/packages/libreplex-sdk/src/updateCreator.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Keypair, PublicKey, SystemProgram, TransactionInstruction } from "@solana/web3.js"; -import { Phase } from "./setupCreator"; -import {controlToAnchor} from "./creatorControls" -import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls" -import { Program, AccountClient , IdlAccounts, IdlTypes} from "@coral-xyz/anchor" - -export type UpdateCreatorInput = { - program: Program, - creatorController: PublicKey, - phases: Phase[], -} - - -export async function updateCreator(input: UpdateCreatorInput) { - const {program, phases, creatorController} = input; - - const me = program.provider.publicKey - - if (!me) { - throw new Error("Missing provider. Are you sure your wallet is connected?") - } - - - - const anchorPhases: IdlTypes["Phase"][] = []; - - for (const ph of phases) { - const controls: IdlTypes["ControlType"][] = [] - - for (const control of ph.control) { - controls.push(controlToAnchor(control)) - } - - anchorPhases.push({ - controls, - end: ph.end ? ph.end.getTime()/1000 : null, - label: ph.label, - start: ph.start.getTime()/1000, - }) - } - - return program.methods.update({ - phases: anchorPhases, - }).accounts({ - systemProgram: SystemProgram.programId, - payer: me, - updateAuthority: me, - creatorController, - }); -} \ No newline at end of file diff --git a/packages/libreplex-sdk/tsconfig.cjs.json b/packages/libreplex-sdk/tsconfig.cjs.json deleted file mode 100644 index d1d9da77..00000000 --- a/packages/libreplex-sdk/tsconfig.cjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "include": ["src"], - "extends": "./tsconfig.json", - "compilerOptions": { - "target": "ES2016", - "module": "CommonJS", - "outDir": "lib/cjs" - } - } \ No newline at end of file diff --git a/packages/libreplex-sdk/tsconfig.esm.json b/packages/libreplex-sdk/tsconfig.esm.json deleted file mode 100644 index 0e27a423..00000000 --- a/packages/libreplex-sdk/tsconfig.esm.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["src"], - "compilerOptions": { - "outDir": "lib/esm", - "declarationDir": "lib/types", - "target": "ESNext", - "module": "ESNext", - "sourceMap": true, - "declaration": true, - "declarationMap": true, - } -} \ No newline at end of file diff --git a/packages/libreplex-sdk/tsconfig.json b/packages/libreplex-sdk/tsconfig.json deleted file mode 100644 index 169a901e..00000000 --- a/packages/libreplex-sdk/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "include": [], - "compilerOptions": { - "lib": ["ES2021.String"], - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "Node", - "esModuleInterop": true, - "isolatedModules": true, - "noImplicitAny": false, - "noEmitOnError": true, - "resolveJsonModule": true, - "strict": true, - "stripInternal": true, - "skipLibCheck": true, - "noEmit": false, - "allowSyntheticDefaultImports": true, - "composite": true - }, - "references": [ - { - "path": "./tsconfig.cjs.json" - }, - { - "path": "./tsconfig.esm.json" - }, - ] -} diff --git a/programs/libreplex_default_renderer/Cargo.toml b/programs/libreplex_default_renderer/Cargo.toml deleted file mode 100644 index 2aa86ef3..00000000 --- a/programs/libreplex_default_renderer/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "libreplex_default_renderer" -version = "0.1.2" -description = "Created with Anchor" -edition = "2021" - -[lib] -crate-type = ["cdylib", "lib"] -name = "libreplex_default_renderer" - -[features] -no-entrypoint = [] -no-idl = [] -no-log-ix-name = [] -cpi = ["no-entrypoint"] -default = [] -idl-build = ["anchor-lang/idl-build"] - -[dependencies] -anchor-lang = {version = "0.30.0", features = ["init-if-needed"]} - -bmp = "0.5.0" - -[dev-dependencies] -mocha = "0.1.1" -assert_matches = "1.5.0" -solana-program-test = "1.17.13" -solana-sdk = "1.17.13" -solana-program = "1.17.13" - - diff --git a/programs/libreplex_default_renderer/Xargo.toml b/programs/libreplex_default_renderer/Xargo.toml deleted file mode 100644 index 475fb71e..00000000 --- a/programs/libreplex_default_renderer/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] diff --git a/programs/libreplex_default_renderer/c.bmp b/programs/libreplex_default_renderer/c.bmp deleted file mode 100644 index 83a787fd94029bf8962a16cdebcf93862b3a6ca3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251 zcmXYrp^n2a5JV?B5*TQ>u+ZpKXcd*5lus}W3|u(yVBkW-gNB8Mg@J{Eg+XoM*>@f3 zt#(JF)jR>5z8-ZpeO3;Y{!Is0H=tDRAOEzw$t@<4vsb`#)ttD^WDrLKSf~ZwQD<-> z0dZoXFh-5=IcW&At;WPT^9$ErK`$>&Gr#Xd6nC{HlcpPem~?${jbl>^1$=C{n3jKJAV!gQMzAW;8iOC diff --git a/programs/libreplex_default_renderer/src/instructions/canonical.rs b/programs/libreplex_default_renderer/src/instructions/canonical.rs deleted file mode 100644 index 66a2ed7c..00000000 --- a/programs/libreplex_default_renderer/src/instructions/canonical.rs +++ /dev/null @@ -1,56 +0,0 @@ -use anchor_lang::prelude::*; - -extern crate bmp; -use bmp::{Image, Pixel}; - - -use super::input::{RenderInput, RenderContext}; - - - -const RED_GEN: u8 = 7; -const GREEN_GEN: u8 = 13; -const BLUE_GEN: u8 = 17; - -const RESOLUTION: u32 = 8; - - -pub fn handler(ctx: Context, - _render_input: RenderInput, -) -> anchor_lang::Result> { - - - - let metadata_key = &ctx.accounts.metadata.key(); - let keyref = metadata_key.as_ref(); - msg!("len: {}", keyref.len()); - - let mut img = Image::new(8, 8); - - - - for x in 0..RESOLUTION { - for y in 0..RESOLUTION { - let red = ((RED_GEN as u32).checked_mul(x+7).unwrap().checked_mul( - keyref[x as usize] as u32 - ).unwrap() % 32)*8; - let green = ((GREEN_GEN as u32).checked_mul(x+13).unwrap().checked_mul( - keyref[y as usize] as u32 - ).unwrap() % 32 )* 8; - let blue = ((BLUE_GEN as u32).checked_mul(x+17).unwrap().checked_mul( - keyref[(x.checked_mul(y).unwrap() % 32) as usize] as u32 - ).unwrap() % 256 ) *8; - img.set_pixel(x, y, Pixel::new(red as u8, green as u8, blue as u8)) - } - } - - - // img.set_pixel(0,0, Pixel::new(255,255,255)); - // img.set_pixel(0,0, Pixel::new(255,255,255)); - // img.set_pixel(0,0, Pixel::new(255,255,255)); - - let mut buf = Vec::new(); - img.to_writer(&mut buf)?; - buf.push(1); - Ok(buf) -} diff --git a/programs/libreplex_default_renderer/src/instructions/input.rs b/programs/libreplex_default_renderer/src/instructions/input.rs deleted file mode 100644 index 9e5d55f8..00000000 --- a/programs/libreplex_default_renderer/src/instructions/input.rs +++ /dev/null @@ -1,27 +0,0 @@ -use anchor_lang::prelude::*; - - - -#[derive(Clone, AnchorDeserialize, AnchorSerialize)] -pub struct RenderInput { - -} - -#[derive(Accounts)] -pub struct RenderContext<'info> { - /// CHECK: Can be empty - pub metadata: UncheckedAccount<'info>, - - /// CHECK: Can be empty - pub mint: UncheckedAccount<'info>, - - /// CHECK: Can be empty - pub group: UncheckedAccount<'info>, - - /// CHECK: Can be empty - #[account(seeds = [mint.key.as_ref()], bump)] - pub render_state: UncheckedAccount<'info>, - - /// CHECK: Can be empty - pub output_account: UncheckedAccount<'info>, -} \ No newline at end of file diff --git a/programs/libreplex_default_renderer/src/instructions/mod.rs b/programs/libreplex_default_renderer/src/instructions/mod.rs deleted file mode 100644 index d7620a5d..00000000 --- a/programs/libreplex_default_renderer/src/instructions/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod canonical; -pub mod input; - -pub use canonical::*; -pub use input::*; diff --git a/programs/libreplex_default_renderer/src/lib.rs b/programs/libreplex_default_renderer/src/lib.rs deleted file mode 100644 index 7af9882c..00000000 --- a/programs/libreplex_default_renderer/src/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -use anchor_lang::prelude::*; -use instructions::*; - - -use anchor_lang::{AnchorDeserialize, AnchorSerialize}; - - - -pub mod instructions; - - - -declare_id!("7oHj4YFq4JhxVc1BvHqvZma5evSn8hkXwFHPoTQ1PB4f"); - - -#[program] -pub mod libreplex_default_renderer { - - - - - - use super::*; - pub fn canonical( - ctx: Context, - render_input: RenderInput - ) -> Result> { - instructions::canonical::handler(ctx, render_input) - } - - -} diff --git a/programs/libreplex_monoswap_deprecated/Cargo.toml b/programs/libreplex_monoswap_deprecated/Cargo.toml deleted file mode 100644 index f168bab3..00000000 --- a/programs/libreplex_monoswap_deprecated/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "libreplex_monoswap_deprecated" -version = "0.0.0" -description = "Created with Anchor" -edition = "2021" -license = "MIT" -repository = "https://github.com/LibrePlex/metadata" - -[lib] -crate-type = ["cdylib", "lib"] -name = "libreplex_monoswap_deprecated" - -[features] -no-entrypoint = [] -no-idl = [] -no-log-ix-name = [] -cpi = ["no-entrypoint"] -default = [] -idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] - -[dependencies] -anchor-lang = {version = "~0.30", features = ["init-if-needed"]} -anchor-spl = {version = "~0.30"} -spl-associated-token-account = "3.0.2" -solana-program = {version = "~1.18"} -libreplex_shared = {version= "0.3.0", features=["no-entrypoint"] } -mpl-token-metadata = { version="~3" } - -[dev-dependencies] -mocha = "0.1.1" -assert_matches = "1.5.0" -solana-program-test = "~1.18" -solana-sdk = "~1.18" diff --git a/programs/libreplex_monoswap_deprecated/Xargo.toml b/programs/libreplex_monoswap_deprecated/Xargo.toml deleted file mode 100644 index 475fb71e..00000000 --- a/programs/libreplex_monoswap_deprecated/Xargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.bpfel-unknown-unknown.dependencies.std] -features = [] diff --git a/programs/libreplex_monoswap_deprecated/src/constants.rs b/programs/libreplex_monoswap_deprecated/src/constants.rs deleted file mode 100644 index 73cc7ad8..00000000 --- a/programs/libreplex_monoswap_deprecated/src/constants.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub const METADATA: &str = "metadata"; -pub const METADATA_EXTENSION: &str = "metadata_extension"; -pub const NFT_OVERRIDE: &str = "nft_override"; -pub const SPL_AUTHORITY: &str = "spl_authority"; -pub const VERIFICATION: &str = "verification"; -pub const COLLECTION: &str = "collection"; -pub const GROUP: &str = "group"; -pub const PERMISSIONS: &str ="permissions"; -pub const OVERRIDE: &str = "override"; -pub const NFT: &str = "nft"; -pub const ADG: &str = "adg"; - - -pub const MAX_NAME_LENGTH: usize = 256; -pub const MAX_SYMBOL_LENGTH: usize = 8; -pub const MAX_URL_LENGTH: usize = 256; diff --git a/programs/libreplex_monoswap_deprecated/src/errors.rs b/programs/libreplex_monoswap_deprecated/src/errors.rs deleted file mode 100644 index c2d19db8..00000000 --- a/programs/libreplex_monoswap_deprecated/src/errors.rs +++ /dev/null @@ -1,36 +0,0 @@ -use anchor_lang::prelude::*; - -#[error_code] -pub enum LegacyInscriptionErrorCode { - - #[msg("Metadata has a bad mint")] - BadMint, - - #[msg("Cannot inscribe a fungible asset")] - CannotInscribeFungible, - - #[msg("Bad authority")] - BadAuthority, - - #[msg("Bad authority for holder inscription")] - BadAuthorityForHolderInscription, - - #[msg("Bad authority for update auth inscription")] - BadAuthorityForUpdateAuthInscription, - - #[msg("Multi Signature threshold must be one to create / edit inscriptions")] - MultiSigThresholdMustBeOne, - - #[msg("Not squads member")] - NotSquadsMember, - - #[msg("Inscription V2 key mismatch")] - Inscription2KeyMismatch, - - #[msg("Inscription V3 key mismatch")] - InscriptionV3KeyMismatch, - - #[msg("Metadata data missmatch")] - DataHashMismatch, - -} diff --git a/programs/libreplex_monoswap_deprecated/src/instructions/create_monoswap.rs b/programs/libreplex_monoswap_deprecated/src/instructions/create_monoswap.rs deleted file mode 100644 index 87e647cc..00000000 --- a/programs/libreplex_monoswap_deprecated/src/instructions/create_monoswap.rs +++ /dev/null @@ -1,131 +0,0 @@ - - -use anchor_lang::prelude::*; -use anchor_spl::{associated_token::AssociatedToken, token::spl_token, token_interface::{Mint,TokenAccount, spl_token_2022}}; -use libreplex_shared::operations::transfer_generic_spl; - -use crate::SwapMarker; - - -#[derive(Clone, AnchorDeserialize, AnchorSerialize)] -pub struct CreateMonoSwapInput { - pub mint_outgoing_amount: u64, - pub mint_incoming_amount: u64 -} - -#[derive(Accounts)] -pub struct CreateMonoSwapCtx<'info> { - - #[account(init, - payer = payer, - space = SwapMarker::SIZE, - seeds = ["swap_marker".as_bytes(), - namespace.key().as_ref(), - mint_outgoing.key().as_ref(), - mint_incoming.key().as_ref()], // always indexed by the incoming mint - bump,)] - pub swap_marker: Account<'info, SwapMarker>, - - #[account(mut)] - pub mint_incoming: InterfaceAccount<'info, Mint>, - - // each mint has to exist - there must be enough - pub mint_outgoing: InterfaceAccount<'info, Mint>, - - // it is the responsibility of each swapper program to create enough - // of the outgoing mint so that the swap can happen. It is deposited - // from this account - #[account(mut, - associated_token::mint = mint_outgoing, - associated_token::authority = mint_outgoing_owner - )] - pub mint_outgoing_token_account_source: InterfaceAccount<'info, TokenAccount>, - - // escrow holders are organised by namespace + incoming mint - - // that way you can get wallet contents to see what swaps are available to you - /// CHECK: Checked in transfer logic - #[account( - seeds = ["swap_escrow".as_bytes(), - namespace.key().as_ref(), - mint_incoming.key().as_ref()], // always indexed by the incoming mint - bump)] - pub escrow_holder: UncheckedAccount<'info>, - - // ... into this escrow account - #[account(init, - payer = payer, - // yes this is mint_outgoing - associated_token::mint = mint_outgoing, - associated_token::authority = escrow_holder - )] - pub mint_outgoing_token_account_escrow: InterfaceAccount<'info, TokenAccount>, - - - #[account(mut)] - pub payer: Signer<'info>, - - // leave this here for integrations - #[account(mut)] - pub mint_outgoing_owner: Signer<'info>, - - // any account that can sign this. this is useful for grouping swaps - pub namespace: Signer<'info>, - - /// CHECK: Checked in constraint - #[account( - constraint = token_program.key() == spl_token::ID || token_program.key() == spl_token_2022::ID - )] - pub token_program: UncheckedAccount<'info>, - - pub associated_token_program: Program<'info, AssociatedToken>, - - #[account()] - pub system_program: Program<'info, System>, - -} - -pub fn create_swap(ctx: Context, input: CreateMonoSwapInput) -> Result<()> { - - let swap_marker = &mut ctx.accounts.swap_marker; - let mint_outgoing = &mut ctx.accounts.mint_outgoing; - - - swap_marker.namespace = ctx.accounts.namespace.key(); - swap_marker.mint_incoming = ctx.accounts.mint_incoming.key(); - swap_marker.mint_outgoing = mint_outgoing.key(); - swap_marker.mint_incoming_amount = input.mint_incoming_amount; - swap_marker.mint_outgoing_amount = input.mint_outgoing_amount; - - swap_marker.used = false; - - // transfer the outgoing mint into escrow - - let token_program = &ctx.accounts.token_program; - let mint_outgoing_token_account_source = &ctx.accounts.mint_outgoing_token_account_source; - let mint_outgoing_token_account_escrow = &ctx.accounts.mint_outgoing_token_account_escrow; - let associated_token_program = &ctx.accounts.associated_token_program; - let system_program = &ctx.accounts.system_program; - let escrow_holder = &ctx.accounts.escrow_holder; - let payer = &ctx.accounts.payer; - - transfer_generic_spl( - &token_program.to_account_info(), - &mint_outgoing_token_account_source.to_account_info(), - &mint_outgoing_token_account_escrow.to_account_info(), - &payer.to_account_info(), - &mint_outgoing.to_account_info(), - // swap marker outgoing owns this to start with. - // when swapping, this ATA will be emptied - // and a new mint will come in - &escrow_holder.to_account_info(), - &associated_token_program.to_account_info(), - &system_program.to_account_info(), - None, // payer signs - &payer.to_account_info(), - mint_outgoing.decimals, - input.mint_outgoing_amount, - &[] - )?; - - - Ok(()) -} diff --git a/programs/libreplex_monoswap_deprecated/src/instructions/mod.rs b/programs/libreplex_monoswap_deprecated/src/instructions/mod.rs deleted file mode 100644 index d350e338..00000000 --- a/programs/libreplex_monoswap_deprecated/src/instructions/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ - -pub mod create_monoswap; -pub use create_monoswap::*; - - -pub mod swap; -pub use swap::*; \ No newline at end of file diff --git a/programs/libreplex_monoswap_deprecated/src/instructions/swap.rs b/programs/libreplex_monoswap_deprecated/src/instructions/swap.rs deleted file mode 100644 index f0bb38f4..00000000 --- a/programs/libreplex_monoswap_deprecated/src/instructions/swap.rs +++ /dev/null @@ -1,189 +0,0 @@ -use anchor_lang::prelude::*; -use anchor_spl::{ - associated_token::AssociatedToken, - token::Token, - token_interface::{Mint, Token2022, TokenAccount}, -}; -use libreplex_shared::operations::transfer_generic_spl; - -use crate::SwapMarker; - -// the swap endpoint is symmetrical. -// it can be used to swap back and forth -#[derive(Accounts)] -pub struct SwapCtx<'info> { - #[account(mut, - close = payer, - constraint = mint_incoming.key() == swap_marker.mint_incoming, - constraint = mint_outgoing.key() == swap_marker.mint_outgoing, - seeds = [ - "swap_marker".as_bytes(), - swap_marker.namespace.as_ref(), - mint_outgoing.key().as_ref(), - mint_incoming.key().as_ref()], - bump,)] - pub swap_marker: Account<'info, SwapMarker>, - - /// swapping always creates a symmetrical swap marker that enables a swap back - #[account(init, - payer = payer, - space = SwapMarker::SIZE, - seeds = ["swap_marker".as_bytes(), - swap_marker.namespace.as_ref(), - mint_incoming.key().as_ref(), - mint_outgoing.key().as_ref()], // always indexed by the incoming mint - bump,)] - pub swap_marker_reverse: Account<'info, SwapMarker>, - - // each mint has to exist. for now we restrict incoming mints to NFTS - // to make sure that each of these marker pairs can only be hit once - // unless the swap is reversed and then called again - #[account()] - pub mint_incoming: InterfaceAccount<'info, Mint>, - - #[account()] - pub mint_outgoing: InterfaceAccount<'info, Mint>, - - // it is the responsibility of each swapper program to create enough - // of the outgoing mint so that the swap can happen. It is deposited - // from this account - #[account(mut, - associated_token::mint = mint_incoming, - associated_token::authority = payer - )] - pub mint_incoming_token_account_source: InterfaceAccount<'info, TokenAccount>, - - /// CHECK: Check in pda derivation - #[account( - seeds = ["swap_escrow".as_bytes(), - swap_marker.namespace.as_ref(), - mint_incoming.key().as_ref()], // always indexed by the incoming mint - bump)] - pub escrow_holder: UncheckedAccount<'info>, - - /// CHECK: Check in pda derivation - #[account( - seeds = ["swap_escrow".as_bytes(), - swap_marker.namespace.as_ref(), - mint_outgoing.key().as_ref()], // always indexed by the incoming mint - bump)] - pub escrow_holder_reverse: UncheckedAccount<'info>, - - // ... into this escrow account - /// CHECK: Checked in transfer logic - #[account(mut)] - pub mint_incoming_token_account_target: UncheckedAccount<'info>, - - - // it is the responsibility of each swapper program to create enough - // of the outgoing mint so that the swap can happen. It is deposited - // from this account - #[account(mut, - associated_token::mint = mint_outgoing, - associated_token::authority = escrow_holder - )] - pub mint_outgoing_token_account_source: InterfaceAccount<'info, TokenAccount>, - - // ... into this escrow account - /// CHECK: Checked in transfer logic - #[account(mut)] - pub mint_outgoing_token_account_target: UncheckedAccount<'info>, - - #[account(mut)] - pub payer: Signer<'info>, - - #[account()] - pub token_program: Program<'info, Token>, - - #[account()] - pub token_program_2022: Program<'info, Token2022>, - - pub associated_token_program: Program<'info, AssociatedToken>, - - - #[account()] - pub system_program: Program<'info, System>, -} - -pub fn swap(ctx: Context) -> Result<()> { - - - let swap_marker_reverse = &mut ctx.accounts.swap_marker_reverse; - let mint_incoming = &mut ctx.accounts.mint_incoming; - let mint_outgoing = &mut ctx.accounts.mint_outgoing; - let escrow_holder_reverse = &ctx.accounts.escrow_holder_reverse; - let swap_marker = &ctx.accounts.swap_marker; - - swap_marker_reverse.set_inner(SwapMarker { - namespace: swap_marker.namespace.key(), - mint_incoming: mint_outgoing.key(), - mint_outgoing: mint_incoming.key(), - mint_incoming_amount: swap_marker.mint_outgoing_amount, - mint_outgoing_amount: swap_marker.mint_incoming_amount, - used: true }); - - // transfer the outgoing mint into escrow - - let token_program = &ctx.accounts.token_program; - let mint_outgoing_token_account_source = &ctx.accounts.mint_outgoing_token_account_source; - let mint_outgoing_token_account_target = &ctx.accounts.mint_outgoing_token_account_target; - - let mint_incoming_token_account_source = &ctx.accounts.mint_incoming_token_account_source; - let mint_incoming_token_account_target = &ctx.accounts.mint_incoming_token_account_target; - - let associated_token_program = &ctx.accounts.associated_token_program; - let system_program = &ctx.accounts.system_program; - let escrow_holder = &ctx.accounts.escrow_holder; - - let mint_incoming_key = mint_incoming.key(); - let authority_seeds = &[ - "swap_escrow".as_bytes(), - swap_marker.namespace.as_ref(), - mint_incoming_key.as_ref(), - &[ctx.bumps.escrow_holder], - ]; - - - let payer = &ctx.accounts.payer; - - // outgoing is going to the payer - transfer_generic_spl( - &token_program.to_account_info(), - &mint_outgoing_token_account_source.to_account_info(), - &mint_outgoing_token_account_target.to_account_info(), - &escrow_holder.to_account_info(), - &mint_outgoing.to_account_info(), - // swap marker outgoing owns this to start with. - // when swapping, this ATA will be emptied - // and a new mint will come in - &payer.to_account_info(), - &associated_token_program.to_account_info(), - &system_program.to_account_info(), - Some(&[authority_seeds]), // payer signs - &payer.to_account_info(), - mint_outgoing.decimals, - swap_marker.mint_outgoing_amount, - &[] - )?; - - transfer_generic_spl( - &token_program.to_account_info(), - &mint_incoming_token_account_source.to_account_info(), - &mint_incoming_token_account_target.to_account_info(), - &payer.to_account_info(), - &mint_incoming.to_account_info(), - // swap marker outgoing owns this to start with. - // when swapping, this ATA will be emptied - // and a new mint will come in - &escrow_holder_reverse.to_account_info(), - &associated_token_program.to_account_info(), - &system_program.to_account_info(), - None, // payer signs - &payer.to_account_info(), - mint_incoming.decimals, - swap_marker.mint_incoming_amount, - &[] - )?; - - - Ok(()) -} diff --git a/programs/libreplex_monoswap_deprecated/src/lib.rs b/programs/libreplex_monoswap_deprecated/src/lib.rs deleted file mode 100644 index efc27908..00000000 --- a/programs/libreplex_monoswap_deprecated/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -use anchor_lang::prelude::*; - -declare_id!("6taGmaEfZx27c25Pk5BwJbDCNNsirVranzWYAeNEhvHi"); - -pub mod constants; -pub mod errors; -pub mod instructions; -pub mod state; -pub use errors::*; - -pub use constants::*; -pub use state::*; -pub use instructions::*; - -#[program] -pub mod libreplex_monoswap { - use super::*; - - pub fn create_monoswap( - ctx: Context, - input: CreateMonoSwapInput, - ) -> Result<()> { - instructions::create_monoswap::create_swap(ctx, input) - } - - - pub fn swap( - ctx: Context, - ) -> Result<()> { - instructions::swap::swap(ctx) - } - -} diff --git a/programs/libreplex_monoswap_deprecated/src/state/mod.rs b/programs/libreplex_monoswap_deprecated/src/state/mod.rs deleted file mode 100644 index ffd9f08b..00000000 --- a/programs/libreplex_monoswap_deprecated/src/state/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ - -pub use swap_marker::*; -pub mod swap_marker; \ No newline at end of file diff --git a/programs/libreplex_monoswap_deprecated/src/state/swap_marker.rs b/programs/libreplex_monoswap_deprecated/src/state/swap_marker.rs deleted file mode 100644 index 9fe00b68..00000000 --- a/programs/libreplex_monoswap_deprecated/src/state/swap_marker.rs +++ /dev/null @@ -1,29 +0,0 @@ -use anchor_lang::prelude::*; -use anchor_lang::{AnchorDeserialize, AnchorSerialize}; - -// two of these are made when a mint is introduced -// to the system. allowing for two-way swaps -// PDA is always ["allowed_swap_marker", , ] -// they can never be deleted so the swap links are permanent -#[account] -pub struct SwapMarker { - // namespace can be anything that the caller can sign - pub namespace: Pubkey, - // allows slicing and dicing by incoming mint - pub mint_incoming: Pubkey, - // allows slicing and dicing by outgoing mint - pub mint_outgoing: Pubkey, - pub mint_incoming_amount: u64, - pub mint_outgoing_amount: u64, - // an unused marker can be closed. - // after it has been used, it can not be - // closed to avoid a situation where a - // holder gets trapped into a crappy token - // and cannot go back - pub used: bool -} - -impl SwapMarker { - pub const SIZE: usize = 8 + 32 + 32 + 32 + 8 + 8 + 1; -} - diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 0b51055c..b8117fab 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", + "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp", "metadata": { "name": "libreplexEditions", "version": "0.2.1", @@ -694,63 +694,53 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "sizeExceedsMaxSize", - "msg": "Collection size exceeds max size." + "name": "tickerTooLong", + "msg": "Ticker too long" }, { "code": 6001, - "name": "maxSizeBelowCurrentSize", - "msg": "Max size cannot be reduced below current size." + "name": "mintTemplateTooLong", + "msg": "Mint template too long" }, { "code": 6002, - "name": "creatorShareInvalid", - "msg": "Creators shares must add up to 100." + "name": "deploymentTemplateTooLong", + "msg": "Deployment template too long" }, { "code": 6003, - "name": "missingApproveAccount", - "msg": "Missing approve account." + "name": "rootTypeTooLong", + "msg": "Root type too long" }, { "code": 6004, - "name": "expiredApproveAccount", - "msg": "Approve account has expired." + "name": "mintedOut", + "msg": "Minted out" }, { "code": 6005, - "name": "invalidField", - "msg": "Invalid field. You cannot use a public key as a field." + "name": "legacyMigrationsAreMintedOut", + "msg": "Legacy migrations are minted out" }, { "code": 6006, - "name": "creatorAddressInvalid", - "msg": "The Address you provided is invalid. Please provide a valid address." + "name": "missingGlobalTreeDelegate", + "msg": "Global tree delegate is missing" }, { "code": 6007, - "name": "royaltyBasisPointsInvalid", - "msg": "Royalty basis points must be less than or equal to 10000." + "name": "incorrectMintType", + "msg": "Incorrect mint type" }, { "code": 6008, - "name": "platformFeeBasisPointsInvalid", - "msg": "Platform fee basis points must be less than or equal to 10000." + "name": "invalidMetadata", + "msg": "Invalid Metadata" }, { "code": 6009, - "name": "recipientShareInvalid", - "msg": "Recipient shares must add up to 100." - }, - { - "code": 6010, - "name": "reservedField", - "msg": "The provided field is invalid or reserved." - }, - { - "code": 6011, - "name": "invalidNumberOfRecipients", - "msg": "Invalid number of platform fee recipients. Exactly 5 recipients are required." + "name": "creatorFeeTooHigh", + "msg": "Creator fee too high" } ], "types": [ diff --git a/test/editions_controls.test.ts b/test/editions_controls.test.ts new file mode 100644 index 00000000..b0dc7a6b --- /dev/null +++ b/test/editions_controls.test.ts @@ -0,0 +1,285 @@ +import * as anchor from '@coral-xyz/anchor'; +import { Program } from '@coral-xyz/anchor'; +import { + PublicKey, + Keypair, + SystemProgram, + ComputeBudgetProgram, +} from '@solana/web3.js'; +import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; +import { LibreplexEditionsControls } from '../../eclipse-program-library/target/types/libreplex_editions_controls'; +import { LibreplexEditions } from '../../eclipse-program-library/target/types/libreplex_editions'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { decodeEditions, getCluster } from './utils'; +import { Transaction } from '@solana/web3.js'; +import { decodeEditionsControls } from './utils'; + +describe('Editions Controls Test Suite', () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + const editionsControlsProgram = anchor.workspace + .LibreplexEditionsControls as Program; + console.log( + 'editionsControlsProgram ID:', + editionsControlsProgram.programId.toBase58() + ); + + const editionsProgram = anchor.workspace + .LibreplexEditions as Program; + console.log('editionsProgram ID:', editionsProgram.programId.toBase58()); + + const payer = (provider.wallet as anchor.Wallet).payer; + const creator1 = Keypair.generate(); + const creator2 = Keypair.generate(); + const treasury = Keypair.generate(); + const platformFeeAdmin = Keypair.generate(); + + const collectionConfig = { + maxNumberOfTokens: new anchor.BN(1150), + symbol: 'COOLX55', + name: 'Collection name with meta, platform fee and royalties', + offchainUrl: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/0', + treasury: treasury.publicKey, + maxMintsPerWallet: new anchor.BN(100), + royalties: { + royaltyBasisPoints: new anchor.BN(1000), + creators: [ + { + address: creator1.publicKey, + share: 50, + }, + { + address: creator2.publicKey, + share: 50, + }, + ], + }, + platformFee: { + platformFeeValue: new anchor.BN(500000), + recipients: [ + { + address: platformFeeAdmin.publicKey, + share: 100, + }, + ], + isFeeFlat: true, + }, + extraMeta: [ + { field: 'field1', value: 'value1' }, + { field: 'field2', value: 'value2' }, + { field: 'field3', value: 'value3' }, + { field: 'field4', value: 'value4' }, + ], + itemBaseUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/{}', + itemName: 'Item T8 V4 #{}', + cosignerProgramId: null, + }; + + before(async () => { + const cluster = await getCluster(provider.connection); + console.log('Cluster:', cluster); + }); + + it('should deploy a collection, add a phase, and execute a mint', async () => { + // Modify compute units for the transaction + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 800000, + }); + + const [editionsPda] = PublicKey.findProgramAddressSync( + [ + Buffer.from('editions_deployment'), + Buffer.from(collectionConfig.symbol), + ], + editionsProgram.programId + ); + + const [editionsControlsPda] = PublicKey.findProgramAddressSync( + [Buffer.from('editions_controls'), editionsPda.toBuffer()], + editionsControlsProgram.programId + ); + const [hashlistPda] = PublicKey.findProgramAddressSync( + [Buffer.from('hashlist'), editionsPda.toBuffer()], + editionsProgram.programId + ); + + const groupMint = Keypair.generate(); + const group = Keypair.generate(); + + console.log('Collection config: ', collectionConfig); + + console.log('\nDeploying via initialiseEditionsControls...\n'); + try { + const initialiseIx = await editionsControlsProgram.methods + .initialiseEditionsControls({ + maxMintsPerWallet: collectionConfig.maxMintsPerWallet, + treasury: collectionConfig.treasury, + maxNumberOfTokens: collectionConfig.maxNumberOfTokens, + symbol: collectionConfig.symbol, + name: collectionConfig.name, + offchainUrl: collectionConfig.offchainUrl, + cosignerProgramId: collectionConfig.cosignerProgramId, + royalties: collectionConfig.royalties, + platformFee: collectionConfig.platformFee, + extraMeta: collectionConfig.extraMeta, + itemBaseUri: collectionConfig.itemBaseUri, + itemName: collectionConfig.itemName, + }) + .accountsStrict({ + editionsControls: editionsControlsPda, + editionsDeployment: editionsPda, + hashlist: hashlistPda, + payer: payer.publicKey, + creator: payer.publicKey, + groupMint: groupMint.publicKey, + group: group.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + groupExtensionProgram: new PublicKey( + '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V' + ), + }) + .instruction(); + + const transaction = new Transaction() + .add(modifyComputeUnits) + .add(initialiseIx); + + const txSignature = await provider.sendAndConfirm(transaction, [ + groupMint, + group, + payer, + ]); + + console.log('Transaction signature:', txSignature); + + console.log('\ninitialiseEditionsControls done!\n'); + // log deployed addresses + console.log('Editions address:', editionsPda.toBase58()); + console.log('EditionsControls address:', editionsControlsPda.toBase58()); + console.log('Hashlist address:', hashlistPda.toBase58()); + console.log('GroupMint address:', groupMint.publicKey.toBase58()); + console.log('Group address:', group.publicKey.toBase58()); + + console.log('\nFetching and displaying deployed collection state...\n'); + + // Fetch and decode the Editions deployment + const editionsAccountInfo = await provider.connection.getAccountInfo( + editionsPda + ); + if (!editionsAccountInfo) { + throw new Error('Editions account not found'); + } + + const editionsDeployment = decodeEditions(editionsProgram)( + editionsAccountInfo.data, + editionsPda + ); + + console.log('Editions Deployment:'); + console.log({ + creator: editionsDeployment.item.creator.toBase58(), + groupMint: editionsDeployment.item.groupMint.toBase58(), + maxNumberOfTokens: editionsDeployment.item.maxNumberOfTokens.toString(), + name: editionsDeployment.item.name, + tokensMinted: editionsDeployment.item.numberOfTokensIssued.toString(), + offchainUrl: editionsDeployment.item.offchainUrl, + symbol: editionsDeployment.item.symbol, + nameIsTemplate: editionsDeployment.item.nameIsTemplate, + urlIsTemplate: editionsDeployment.item.urlIsTemplate, + }); + + // Fetch and check the EditionsControls account + const controlsAccountData = await provider.connection.getAccountInfo( + editionsControlsPda + ); + + if (!controlsAccountData || !controlsAccountData.data) { + console.log('Core editions deployment - no controls specified'); + } else { + const editionsControlsObj = decodeEditionsControls( + editionsControlsProgram + )(controlsAccountData.data, editionsControlsPda); + + console.log({ + editionsControls: { + address: editionsControlsPda.toBase58(), + coreDeployment: + editionsControlsObj.item.editionsDeployment.toBase58(), + creator: editionsControlsObj.item.creator.toBase58(), + treasury: editionsControlsObj.item.treasury.toBase58(), + maxMintsPerWallet: Number( + editionsControlsObj.item.maxMintsPerWallet + ), + }, + phases: editionsControlsObj.item.phases.map((item, idx) => ({ + phaseIndex: idx, + currentMints: Number(item.currentMints), + maxMintsPerWallet: Number(item.maxMintsPerWallet), + maxMintsTotal: Number(item.maxMintsTotal), + startTime: Number(item.startTime), + endTime: Number(item.endTime), + priceAmount: Number(item.priceAmount), + priceToken: item.priceToken ? item.priceToken.toBase58() : null, + merkleRoot: item.merkleRoot + ? JSON.stringify(item.merkleRoot) + : null, + })), + }); + + // Add assertions to verify the state + expect(editionsControlsObj.item.editionsDeployment.toBase58()).to.equal( + editionsPda.toBase58() + ); + expect(editionsControlsObj.item.creator.toBase58()).to.equal( + payer.publicKey.toBase58() + ); + expect(editionsControlsObj.item.treasury.toBase58()).to.equal( + collectionConfig.treasury.toBase58() + ); + expect(Number(editionsControlsObj.item.maxMintsPerWallet)).to.equal( + Number(collectionConfig.maxMintsPerWallet) + ); + + // Verify Editions deployment + // expect(editionsDeployment.item.creator.toBase58()).to.equal( + // payer.publicKey.toBase58() + // ); + // expect(editionsDeployment.item.maxNumberOfTokens.toString()).to.equal( + // collectionConfig.maxNumberOfTokens.toString() + // ); + // expect(editionsDeployment.item.name).to.equal(collectionConfig.name); + // expect(editionsDeployment.item.symbol).to.equal( + // collectionConfig.symbol + // ); + // expect(editionsDeployment.item.offchainUrl).to.equal( + // collectionConfig.offchainUrl + // ); + + // Add more assertions as needed for phases, if any are expected to be present at this point + } + + // 2. Add a phase (if needed) + // 3. Execute a mint (if needed) + + // These sections can be implemented similarly to the previous version, + // but make sure to update any parameters that have changed in the new CLI version. + } catch (error) { + console.error('Error in initialiseEditionsControls:', error); + throw error; + } + + // Add assertions to verify the initialization was successful + + // Add more assertions as needed + + // 2. Add a phase (if needed) + // 3. Execute a mint (if needed) + + // These sections can be implemented similarly to the previous version, + // but make sure to update any parameters that have changed in the new CLI version. + }); +}); diff --git a/test/utils.ts b/test/utils.ts new file mode 100644 index 00000000..c83c48a0 --- /dev/null +++ b/test/utils.ts @@ -0,0 +1,63 @@ +import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'; +import { LibreplexEditions } from '../target/types/libreplex_editions'; +import { IdlAccounts, IdlTypes } from '@coral-xyz/anchor'; +import { BorshCoder, Program } from '@coral-xyz/anchor'; +import { LibreplexEditionsControls } from '../target/types/libreplex_editions_controls'; + +export type EditionsDeployment = + IdlAccounts['editionsDeployment']; + +export async function getCluster(connection: Connection): Promise { + // Get the genesis hash + const genesisHash = await connection.getGenesisHash(); + + // Compare the genesis hash with known cluster genesis hashes + switch (genesisHash) { + case '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d': + return 'mainnet-beta'; + case 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG': + return 'testnet'; + case '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY': + return 'devnet'; + default: + // If it doesn't match any known cluster, it's likely localhost + return 'localhost'; + } +} + +export const decodeEditions = + (program: Program) => + (buffer: Buffer | undefined, pubkey: PublicKey) => { + const coder = new BorshCoder(program.idl); + const liquidity = buffer + ? coder.accounts.decode('editionsDeployment', buffer) + : null; + + return { + item: liquidity, + pubkey, + }; + }; + +export type EditionsControls = + IdlAccounts['editionsControls']; + +export const getBase64FromDatabytes = (dataBytes: Buffer, dataType: string) => { + console.log({ dataBytes }); + const base = dataBytes.toString('base64'); + return `data:${dataType};base64,${base}`; +}; + +export const decodeEditionsControls = + (program: Program) => + (buffer: Buffer | undefined, pubkey: PublicKey) => { + const coder = new BorshCoder(program.idl); + const liquidity = buffer + ? coder.accounts.decode('editionsControls', buffer) + : null; + + return { + item: liquidity, + pubkey, + }; + }; diff --git a/tsconfig.json b/tsconfig.json index a58c9f14..713f4570 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,16 @@ { "compilerOptions": { - "types": ["mocha", "chai"], - "typeRoots": ["./node_modules/@types"], - "lib": ["es2015"], - "module": "commonjs", - "target": "es6", - "esModuleInterop": true - }, - "include": [], - "exclude": ["node_modules"], - "references": [ - { - "path": "./packages/libreplex-idls" - }, - - ], + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true, + "baseUrl": ".", + "paths": { + "*": ["node_modules/*"] + } + }, + "include": ["test/**/*"], + "exclude": ["node_modules"] } From a85dada6c3f3af80b461fbe6c6cfc2eb19a119b1 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Wed, 9 Oct 2024 17:36:19 -0300 Subject: [PATCH 18/29] Advances on test suite up to executing claim --- constants.ts | 6 + programs/libreplex_editions/Cargo.toml | 1 - .../src/instructions/initialise.rs | 48 +- .../src/instructions/mint.rs | 18 +- programs/libreplex_editions/src/state.rs | 25 +- .../src/instructions/initialise.rs | 12 +- target/types/libreplex_editions.ts | 841 ++++++++++++- target/types/libreplex_editions_controls.ts | 1101 ++++++++++++++++- test/data/merkle_tree.json | 188 +++ test/data/minter1.json | 6 + test/data/minter2.json | 6 + test/editions_controls.test.ts | 689 ++++++++--- test/utils.ts | 175 ++- 13 files changed, 2825 insertions(+), 291 deletions(-) create mode 100644 constants.ts create mode 100644 test/data/merkle_tree.json create mode 100644 test/data/minter1.json create mode 100644 test/data/minter2.json diff --git a/constants.ts b/constants.ts new file mode 100644 index 00000000..003ad932 --- /dev/null +++ b/constants.ts @@ -0,0 +1,6 @@ +export const EDITIONS_PROGRAM_ID = + 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp'; +export const EDITIONS_CONTROLS_PROGRAM_ID = + '8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco'; +export const TOKEN_GROUP_EXTENSION_PROGRAM_ID = + '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'; diff --git a/programs/libreplex_editions/Cargo.toml b/programs/libreplex_editions/Cargo.toml index 53ddcbe1..1f9fe037 100644 --- a/programs/libreplex_editions/Cargo.toml +++ b/programs/libreplex_editions/Cargo.toml @@ -19,7 +19,6 @@ default = [] idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] - [dependencies] anchor-lang = {version = "~0.30", features = ["init-if-needed"]} anchor-spl = {version = "~0.30"} diff --git a/programs/libreplex_editions/src/instructions/initialise.rs b/programs/libreplex_editions/src/instructions/initialise.rs index 1c026d8d..40075eb3 100644 --- a/programs/libreplex_editions/src/instructions/initialise.rs +++ b/programs/libreplex_editions/src/instructions/initialise.rs @@ -1,4 +1,4 @@ -use crate::{group_extension_program, utils::update_account_lamports_to_minimum_balance, EditionsDeployment, Hashlist, NAME_LIMIT, OFFCHAIN_URL_LIMIT, SYMBOL_LIMIT}; +use crate::{group_extension_program, utils::update_account_lamports_to_minimum_balance, EditionsDeployment, Hashlist, NAME_LIMIT, URI_LIMIT, SYMBOL_LIMIT}; use anchor_lang::prelude::*; use libreplex_shared::{create_token_2022_and_metadata, MintAccounts2022, TokenGroupInput}; use solana_program::system_program; @@ -7,19 +7,19 @@ use spl_token_metadata_interface::state::TokenMetadata; #[derive(AnchorDeserialize, AnchorSerialize, Clone)] pub struct InitialiseInput { - pub max_number_of_tokens: u64, // this is the max *number* of tokens pub symbol: String, + pub collection_name: String, + pub collection_uri: String, + pub max_number_of_tokens: u64, // this is the max *number* of tokens + pub creator_cosign_program_id: Option, // add curlies if you want this to be created dynamically. For example - // hippo #{} -> turns into hippo #0, hippo #1, etc + // ipfs://pippo/{} -> turns into ipfs://pippo/1, ipfs://pippo/2, etc // without curlies the url is the same for all mints - pub name: String, + pub item_base_uri: String, // add curlies if you want this to be created dynamically. For example - // ipfs://pippo/{} -> turns into ipfs://pippo/1, ipfs://pippo/2, etc + // hippo #{} -> turns into hippo #0, hippo #1, etc // without curlies the url is the same for all mints - pub offchain_url: String, - pub creator_cosign_program_id: Option, - pub item_base_uri: String, - pub item_name: String, + pub item_base_name: String, } @@ -63,22 +63,21 @@ pub struct InitialiseCtx<'info> { pub fn initialise(ctx: Context, input: InitialiseInput) -> Result<()> { - if input.offchain_url.len() > OFFCHAIN_URL_LIMIT { - panic!("Offchain url too long"); - } if input.symbol.len() > SYMBOL_LIMIT { panic!("Symbol too long"); } - if input.name.len() > NAME_LIMIT { + if input.collection_name.len() > NAME_LIMIT { panic!("Name too long"); } + if input.collection_uri.len() > URI_LIMIT { + panic!("Offchain url too long"); + } let group_mint = &ctx.accounts.group_mint; let group = &ctx.accounts.group; - - let url_is_template = match input.item_base_uri.matches("{}").count() { + let item_uri_is_template = match input.item_base_uri.matches("{}").count() { 0 => false, 1 => true, _ => { @@ -86,8 +85,7 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result } }; - - let name_is_template = match input.item_name.matches("{}").count() { + let item_name_is_template = match input.item_base_name.matches("{}").count() { 0 => false, 1 => true, _ => { @@ -95,7 +93,6 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result } }; - ctx.accounts.editions_deployment.set_inner(EditionsDeployment { creator: ctx.accounts.creator.key(), max_number_of_tokens: input.max_number_of_tokens, @@ -107,14 +104,15 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result _ => system_program::ID }, symbol: input.symbol, - name: input.item_name, - url_is_template, - name_is_template, - offchain_url: input.item_base_uri, + collection_name: input.collection_name, + collection_uri: input.collection_uri, + item_base_name: input.item_base_name, + item_base_uri: input.item_base_uri, + item_name_is_template, + item_uri_is_template, padding: [0; 98], }); - let editions_deployment = &ctx.accounts.editions_deployment; let payer = &ctx.accounts.payer; let group_mint = &ctx.accounts.group_mint; @@ -141,9 +139,9 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result }, 0, Some(TokenMetadata { - name: input.name.clone(), + name: editions_deployment.collection_name.clone(), symbol: editions_deployment.symbol.clone(), - uri: input.offchain_url.clone(), + uri: editions_deployment.collection_uri.clone(), update_authority, mint: group_mint.key(), additional_metadata: vec![], // Leave this empty for now, diff --git a/programs/libreplex_editions/src/instructions/mint.rs b/programs/libreplex_editions/src/instructions/mint.rs index 2fba7e67..7e20c806 100644 --- a/programs/libreplex_editions/src/instructions/mint.rs +++ b/programs/libreplex_editions/src/instructions/mint.rs @@ -85,7 +85,6 @@ pub struct MintCtx<'info> { #[account()] pub system_program: Program<'info, System>, - } pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()> { @@ -120,7 +119,6 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<() return Err(EditionsError::MintedOut.into()); } - let update_authority = OptionalNonZeroPubkey::try_from(Some(editions_deployment.key())).expect("Bad update auth"); @@ -130,14 +128,14 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<() &[ctx.bumps.editions_deployment], ]; - let name = match editions_deployment.name_is_template { - true => editions_deployment.name.format(&[editions_deployment.number_of_tokens_issued + 1]), - false => editions_deployment.name.clone() + let item_name = match editions_deployment.item_name_is_template { + true => editions_deployment.item_base_name.format(&[editions_deployment.number_of_tokens_issued + 1]), + false => editions_deployment.item_base_name.clone() }; - let url = match editions_deployment.url_is_template { - true => editions_deployment.offchain_url.format(&[editions_deployment.number_of_tokens_issued + 1]), - false => editions_deployment.offchain_url.clone() + let item_url = match editions_deployment.item_uri_is_template { + true => editions_deployment.item_base_uri.format(&[editions_deployment.number_of_tokens_issued + 1]), + false => editions_deployment.item_base_uri.clone() }; // msg!("Create token 2022 w/ metadata"); create_token_2022_and_metadata( @@ -150,9 +148,9 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<() }, 0, Some(TokenMetadata { - name, + name: item_name, symbol: editions_deployment.symbol.clone(), - uri: url, + uri: item_url, update_authority, mint: mint.key(), additional_metadata: vec![], diff --git a/programs/libreplex_editions/src/state.rs b/programs/libreplex_editions/src/state.rs index 49e07fbb..b4cd0045 100644 --- a/programs/libreplex_editions/src/state.rs +++ b/programs/libreplex_editions/src/state.rs @@ -1,9 +1,9 @@ use anchor_lang::prelude::*; use solana_program::pubkey::Pubkey; -pub const NAME_LIMIT: usize = 400; pub const SYMBOL_LIMIT: usize = 100; -pub const OFFCHAIN_URL_LIMIT: usize = 1200; +pub const NAME_LIMIT: usize = 400; +pub const URI_LIMIT: usize = 1200; pub const META_LIST_ACCOUNT_SEED: &[u8] = b"extra-account-metas"; pub const APPROVE_ACCOUNT_SEED: &[u8] = b"approve-account"; pub const ROYALTY_BASIS_POINTS_FIELD: &str = "royalty_basis_points"; @@ -22,6 +22,7 @@ pub struct EditionsDeployment { pub creator: Pubkey, // set to 0 for unlimited pub max_number_of_tokens: u64, + pub number_of_tokens_issued: u64, // set to system account for no cosign @@ -35,20 +36,24 @@ pub struct EditionsDeployment { pub symbol: String, #[max_len(NAME_LIMIT)] - pub name: String, + pub collection_name: String, - #[max_len(OFFCHAIN_URL_LIMIT)] - pub offchain_url: String, // pub padding: Vec - - pub name_is_template: bool, + #[max_len(URI_LIMIT)] + pub collection_uri: String, + + #[max_len(NAME_LIMIT)] + pub item_base_name: String, - pub url_is_template: bool, + #[max_len(URI_LIMIT)] + pub item_base_uri: String, + + pub item_name_is_template: bool, + + pub item_uri_is_template: bool, pub padding: [u8; 98] - } - // slightly more extended #[account] pub struct HashlistMarker { diff --git a/programs/libreplex_editions_controls/src/instructions/initialise.rs b/programs/libreplex_editions_controls/src/instructions/initialise.rs index 548a78e2..949ea466 100644 --- a/programs/libreplex_editions_controls/src/instructions/initialise.rs +++ b/programs/libreplex_editions_controls/src/instructions/initialise.rs @@ -11,13 +11,13 @@ pub struct InitialiseControlInput { pub treasury: Pubkey, pub max_number_of_tokens: u64, pub symbol: String, - pub name: String, - pub offchain_url: String, + pub collection_name: String, + pub collection_uri: String, pub cosigner_program_id: Option, pub royalties: UpdateRoyaltiesArgs, pub extra_meta: Vec, pub item_base_uri: String, - pub item_name: String, + pub item_base_name: String, pub platform_fee: UpdatePlatformFeeArgs } @@ -88,10 +88,10 @@ pub fn initialise_editions_controls( let core_input = InitialiseInput { max_number_of_tokens: input.max_number_of_tokens, symbol: input.symbol, - name: input.name, - offchain_url: input.offchain_url, + collection_name: input.collection_name, + collection_uri: input.collection_uri, creator_cosign_program_id: Some(crate::ID), - item_name: input.item_name, + item_base_name: input.item_base_name, item_base_uri: input.item_base_uri }; diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index b8117fab..65a70114 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", "metadata": { "name": "libreplexEditions", "version": "0.2.1", @@ -694,53 +694,63 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "tickerTooLong", - "msg": "Ticker too long" + "name": "sizeExceedsMaxSize", + "msg": "Collection size exceeds max size." }, { "code": 6001, - "name": "mintTemplateTooLong", - "msg": "Mint template too long" + "name": "maxSizeBelowCurrentSize", + "msg": "Max size cannot be reduced below current size." }, { "code": 6002, - "name": "deploymentTemplateTooLong", - "msg": "Deployment template too long" + "name": "creatorShareInvalid", + "msg": "Creators shares must add up to 100." }, { "code": 6003, - "name": "rootTypeTooLong", - "msg": "Root type too long" + "name": "missingApproveAccount", + "msg": "Missing approve account." }, { "code": 6004, - "name": "mintedOut", - "msg": "Minted out" + "name": "expiredApproveAccount", + "msg": "Approve account has expired." }, { "code": 6005, - "name": "legacyMigrationsAreMintedOut", - "msg": "Legacy migrations are minted out" + "name": "invalidField", + "msg": "Invalid field. You cannot use a public key as a field." }, { "code": 6006, - "name": "missingGlobalTreeDelegate", - "msg": "Global tree delegate is missing" + "name": "creatorAddressInvalid", + "msg": "The Address you provided is invalid. Please provide a valid address." }, { "code": 6007, - "name": "incorrectMintType", - "msg": "Incorrect mint type" + "name": "royaltyBasisPointsInvalid", + "msg": "Royalty basis points must be less than or equal to 10000." }, { "code": 6008, - "name": "invalidMetadata", - "msg": "Invalid Metadata" + "name": "platformFeeBasisPointsInvalid", + "msg": "Platform fee basis points must be less than or equal to 10000." }, { "code": 6009, - "name": "creatorFeeTooHigh", - "msg": "Creator fee too high" + "name": "recipientShareInvalid", + "msg": "Recipient shares must add up to 100." + }, + { + "code": 6010, + "name": "reservedField", + "msg": "The provided field is invalid or reserved." + }, + { + "code": 6011, + "name": "invalidNumberOfRecipients", + "msg": "Invalid number of platform fee recipients. Exactly 5 recipients are required." } ], "types": [ @@ -810,19 +820,27 @@ export type LibreplexEditions = { "type": "string" }, { - "name": "name", + "name": "collectionName", "type": "string" }, { - "name": "offchainUrl", + "name": "collectionUri", + "type": "string" + }, + { + "name": "itemBaseName", + "type": "string" + }, + { + "name": "itemBaseUri", "type": "string" }, { - "name": "nameIsTemplate", + "name": "itemNameIsTemplate", "type": "bool" }, { - "name": "urlIsTemplate", + "name": "itemUriIsTemplate", "type": "bool" }, { @@ -880,22 +898,22 @@ export type LibreplexEditions = { "type": { "kind": "struct", "fields": [ - { - "name": "maxNumberOfTokens", - "type": "u64" - }, { "name": "symbol", "type": "string" }, { - "name": "name", + "name": "collectionName", "type": "string" }, { - "name": "offchainUrl", + "name": "collectionUri", "type": "string" }, + { + "name": "maxNumberOfTokens", + "type": "u64" + }, { "name": "creatorCosignProgramId", "type": { @@ -907,7 +925,7 @@ export type LibreplexEditions = { "type": "string" }, { - "name": "itemName", + "name": "itemBaseName", "type": "string" } ] @@ -969,3 +987,762 @@ export type LibreplexEditions = { } ] }; + +export const IDL: LibreplexEditions = { + address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', + metadata: { + name: 'libreplexEditions', + version: '0.2.1', + spec: '0.1.0', + description: 'Created with Anchor', + repository: 'https://github.com/Libreplex/libreplex-program-library', + }, + instructions: [ + { + name: 'addMetadata', + docs: ['add additional metadata to mint'], + discriminator: [231, 195, 40, 240, 67, 231, 53, 136], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, + 108, 111, 121, 109, 101, 110, 116, + ], + }, + { + kind: 'account', + path: 'editions_deployment.symbol', + account: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'signer', + writable: true, + signer: true, + }, + { + name: 'mint', + writable: true, + signer: true, + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + ], + args: [ + { + name: 'args', + type: { + vec: { + defined: { + name: 'addMetadataArgs', + }, + }, + }, + }, + ], + }, + { + name: 'addRoyalties', + docs: ['add royalties to mint'], + discriminator: [195, 251, 126, 230, 187, 134, 168, 210], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, + 108, 111, 121, 109, 101, 110, 116, + ], + }, + { + kind: 'account', + path: 'editions_deployment.symbol', + account: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'signer', + writable: true, + signer: true, + }, + { + name: 'mint', + writable: true, + signer: true, + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + ], + args: [ + { + name: 'args', + type: { + defined: { + name: 'updateRoyaltiesArgs', + }, + }, + }, + ], + }, + { + name: 'initialise', + discriminator: [162, 198, 118, 235, 215, 247, 25, 118], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, + 108, 111, 121, 109, 101, 110, 116, + ], + }, + { + kind: 'arg', + path: 'input.symbol', + }, + ], + }, + }, + { + name: 'hashlist', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [104, 97, 115, 104, 108, 105, 115, 116], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'creator', + writable: true, + }, + { + name: 'groupMint', + writable: true, + signer: true, + }, + { + name: 'group', + writable: true, + signer: true, + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + { + name: 'groupExtensionProgram', + address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', + }, + ], + args: [ + { + name: 'input', + type: { + defined: { + name: 'initialiseInput', + }, + }, + }, + ], + }, + { + name: 'mint', + discriminator: [51, 57, 225, 47, 182, 146, 137, 166], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, + 108, 111, 121, 109, 101, 110, 116, + ], + }, + { + kind: 'account', + path: 'editions_deployment.symbol', + account: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'hashlist', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [104, 97, 115, 104, 108, 105, 115, 116], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'hashlistMarker', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 104, 97, 115, 104, 108, 105, 115, 116, 95, 109, 97, 114, 107, + 101, 114, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + { + kind: 'account', + path: 'mint', + }, + ], + }, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'signer', + writable: true, + signer: true, + }, + { + name: 'minter', + writable: true, + }, + { + name: 'mint', + writable: true, + signer: true, + }, + { + name: 'member', + writable: true, + signer: true, + }, + { + name: 'group', + writable: true, + }, + { + name: 'groupMint', + writable: true, + }, + { + name: 'tokenAccount', + writable: true, + }, + { + name: 'tokenProgram', + }, + { + name: 'associatedTokenProgram', + address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', + }, + { + name: 'groupExtensionProgram', + address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + ], + args: [], + }, + { + name: 'modifyRoyalties', + docs: ['modify royalties of mint'], + discriminator: [199, 95, 20, 107, 136, 161, 93, 137], + accounts: [ + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'editionsDeployment', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, + 108, 111, 121, 109, 101, 110, 116, + ], + }, + { + kind: 'account', + path: 'editions_deployment.symbol', + account: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'signer', + writable: true, + signer: true, + }, + { + name: 'mint', + writable: true, + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + ], + args: [ + { + name: 'args', + type: { + defined: { + name: 'updateRoyaltiesArgs', + }, + }, + }, + ], + }, + { + name: 'removeMetadata', + docs: ['remove additional metadata to mint'], + discriminator: [81, 68, 231, 49, 91, 8, 111, 160], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, + 108, 111, 121, 109, 101, 110, 116, + ], + }, + { + kind: 'account', + path: 'editions_deployment.symbol', + account: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'signer', + writable: true, + signer: true, + }, + { + name: 'mint', + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + ], + args: [ + { + name: 'args', + type: { + vec: { + defined: { + name: 'removeMetadataArgs', + }, + }, + }, + }, + ], + }, + ], + accounts: [ + { + name: 'editionsDeployment', + discriminator: [101, 54, 68, 216, 168, 131, 242, 157], + }, + { + name: 'hashlist', + discriminator: [187, 203, 134, 6, 43, 198, 120, 186], + }, + { + name: 'hashlistMarker', + discriminator: [55, 46, 160, 53, 239, 41, 223, 50], + }, + ], + errors: [ + { + code: 6000, + name: 'sizeExceedsMaxSize', + msg: 'Collection size exceeds max size.', + }, + { + code: 6001, + name: 'maxSizeBelowCurrentSize', + msg: 'Max size cannot be reduced below current size.', + }, + { + code: 6002, + name: 'creatorShareInvalid', + msg: 'Creators shares must add up to 100.', + }, + { + code: 6003, + name: 'missingApproveAccount', + msg: 'Missing approve account.', + }, + { + code: 6004, + name: 'expiredApproveAccount', + msg: 'Approve account has expired.', + }, + { + code: 6005, + name: 'invalidField', + msg: 'Invalid field. You cannot use a public key as a field.', + }, + { + code: 6006, + name: 'creatorAddressInvalid', + msg: 'The Address you provided is invalid. Please provide a valid address.', + }, + { + code: 6007, + name: 'royaltyBasisPointsInvalid', + msg: 'Royalty basis points must be less than or equal to 10000.', + }, + { + code: 6008, + name: 'platformFeeBasisPointsInvalid', + msg: 'Platform fee basis points must be less than or equal to 10000.', + }, + { + code: 6009, + name: 'recipientShareInvalid', + msg: 'Recipient shares must add up to 100.', + }, + { + code: 6010, + name: 'reservedField', + msg: 'The provided field is invalid or reserved.', + }, + { + code: 6011, + name: 'invalidNumberOfRecipients', + msg: 'Invalid number of platform fee recipients. Exactly 5 recipients are required.', + }, + ], + types: [ + { + name: 'addMetadataArgs', + type: { + kind: 'struct', + fields: [ + { + name: 'field', + type: 'string', + }, + { + name: 'value', + type: 'string', + }, + ], + }, + }, + { + name: 'creatorWithShare', + type: { + kind: 'struct', + fields: [ + { + name: 'address', + type: 'pubkey', + }, + { + name: 'share', + type: 'u8', + }, + ], + }, + }, + { + name: 'editionsDeployment', + type: { + kind: 'struct', + fields: [ + { + name: 'creator', + type: 'pubkey', + }, + { + name: 'maxNumberOfTokens', + type: 'u64', + }, + { + name: 'numberOfTokensIssued', + type: 'u64', + }, + { + name: 'cosignerProgramId', + type: 'pubkey', + }, + { + name: 'groupMint', + type: 'pubkey', + }, + { + name: 'group', + type: 'pubkey', + }, + { + name: 'symbol', + type: 'string', + }, + { + name: 'collectionName', + type: 'string', + }, + { + name: 'collectionUri', + type: 'string', + }, + { + name: 'itemBaseName', + type: 'string', + }, + { + name: 'itemBaseUri', + type: 'string', + }, + { + name: 'itemNameIsTemplate', + type: 'bool', + }, + { + name: 'itemUriIsTemplate', + type: 'bool', + }, + { + name: 'padding', + type: { + array: ['u8', 98], + }, + }, + ], + }, + }, + { + name: 'hashlist', + type: { + kind: 'struct', + fields: [ + { + name: 'deployment', + type: 'pubkey', + }, + { + name: 'issues', + type: { + vec: { + defined: { + name: 'mintAndOrder', + }, + }, + }, + }, + ], + }, + }, + { + name: 'hashlistMarker', + type: { + kind: 'struct', + fields: [ + { + name: 'editionsDeployment', + type: 'pubkey', + }, + { + name: 'mint', + type: 'pubkey', + }, + ], + }, + }, + { + name: 'initialiseInput', + type: { + kind: 'struct', + fields: [ + { + name: 'symbol', + type: 'string', + }, + { + name: 'collectionName', + type: 'string', + }, + { + name: 'collectionUri', + type: 'string', + }, + { + name: 'maxNumberOfTokens', + type: 'u64', + }, + { + name: 'creatorCosignProgramId', + type: { + option: 'pubkey', + }, + }, + { + name: 'itemBaseUri', + type: 'string', + }, + { + name: 'itemBaseName', + type: 'string', + }, + ], + }, + }, + { + name: 'mintAndOrder', + type: { + kind: 'struct', + fields: [ + { + name: 'mint', + type: 'pubkey', + }, + { + name: 'order', + type: 'u64', + }, + ], + }, + }, + { + name: 'removeMetadataArgs', + type: { + kind: 'struct', + fields: [ + { + name: 'field', + type: 'string', + }, + { + name: 'value', + type: 'string', + }, + ], + }, + }, + { + name: 'updateRoyaltiesArgs', + type: { + kind: 'struct', + fields: [ + { + name: 'royaltyBasisPoints', + type: 'u16', + }, + { + name: 'creators', + type: { + vec: { + defined: { + name: 'creatorWithShare', + }, + }, + }, + }, + ], + }, + }, + ], +}; diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index 664027b3..0c53a8ad 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -936,19 +936,27 @@ export type LibreplexEditionsControls = { "type": "string" }, { - "name": "name", + "name": "collectionName", "type": "string" }, { - "name": "offchainUrl", + "name": "collectionUri", "type": "string" }, { - "name": "nameIsTemplate", + "name": "itemBaseName", + "type": "string" + }, + { + "name": "itemBaseUri", + "type": "string" + }, + { + "name": "itemNameIsTemplate", "type": "bool" }, { - "name": "urlIsTemplate", + "name": "itemUriIsTemplate", "type": "bool" }, { @@ -985,11 +993,11 @@ export type LibreplexEditionsControls = { "type": "string" }, { - "name": "name", + "name": "collectionName", "type": "string" }, { - "name": "offchainUrl", + "name": "collectionUri", "type": "string" }, { @@ -1021,7 +1029,7 @@ export type LibreplexEditionsControls = { "type": "string" }, { - "name": "itemName", + "name": "itemBaseName", "type": "string" }, { @@ -1278,3 +1286,1082 @@ export type LibreplexEditionsControls = { } ] }; + +export const IDL: LibreplexEditionsControls = { + address: '8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco', + metadata: { + name: 'libreplexEditionsControls', + version: '0.2.1', + spec: '0.1.0', + description: 'Created with Anchor', + repository: 'https://github.com/Libreplex/libreplex-program-library', + }, + instructions: [ + { + name: 'addPhase', + discriminator: [245, 220, 147, 40, 30, 207, 36, 127], + accounts: [ + { + name: 'editionsControls', + writable: true, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'creator', + writable: true, + signer: true, + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + { + name: 'libreplexEditionsProgram', + address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', + }, + ], + args: [ + { + name: 'input', + type: { + defined: { + name: 'initialisePhaseInput', + }, + }, + }, + ], + }, + { + name: 'initialiseEditionsControls', + discriminator: [69, 176, 133, 29, 20, 49, 120, 202], + accounts: [ + { + name: 'editionsControls', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, + 114, 111, 108, 115, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'editionsDeployment', + writable: true, + }, + { + name: 'hashlist', + writable: true, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'creator', + }, + { + name: 'groupMint', + writable: true, + signer: true, + }, + { + name: 'group', + writable: true, + signer: true, + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + { + name: 'groupExtensionProgram', + address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', + }, + { + name: 'libreplexEditionsProgram', + address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', + }, + ], + args: [ + { + name: 'input', + type: { + defined: { + name: 'initialiseControlInput', + }, + }, + }, + ], + }, + { + name: 'mintWithControls', + discriminator: [167, 57, 252, 220, 69, 92, 231, 61], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + }, + { + name: 'editionsControls', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, + 114, 111, 108, 115, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'hashlist', + writable: true, + }, + { + name: 'hashlistMarker', + writable: true, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'signer', + docs: [ + 'When deployment.require_creator_cosign is true, this must be equal to the creator', + 'of the deployment; otherwise, can be any signer account', + ], + signer: true, + }, + { + name: 'minter', + writable: true, + }, + { + name: 'minterStats', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 109, 105, 110, 116, 101, 114, 95, 115, 116, 97, 116, 115, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + { + kind: 'account', + path: 'minter', + }, + ], + }, + }, + { + name: 'minterStatsPhase', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 109, 105, 110, 116, 101, 114, 95, 115, 116, 97, 116, 115, 95, + 112, 104, 97, 115, 101, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + { + kind: 'account', + path: 'minter', + }, + { + kind: 'arg', + path: 'mint_input.phase_index', + }, + ], + }, + }, + { + name: 'mint', + writable: true, + signer: true, + }, + { + name: 'member', + writable: true, + signer: true, + }, + { + name: 'group', + writable: true, + }, + { + name: 'groupMint', + writable: true, + }, + { + name: 'platformFeeRecipient1', + writable: true, + }, + { + name: 'tokenAccount', + writable: true, + }, + { + name: 'treasury', + writable: true, + }, + { + name: 'tokenProgram', + }, + { + name: 'associatedTokenProgram', + address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', + }, + { + name: 'groupExtensionProgram', + address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'libreplexEditionsProgram', + address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', + }, + ], + args: [ + { + name: 'mintInput', + type: { + defined: { + name: 'mintInput', + }, + }, + }, + ], + }, + { + name: 'modifyPlatformFee', + discriminator: [186, 73, 229, 152, 183, 174, 250, 197], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + }, + { + name: 'editionsControls', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, + 114, 111, 108, 115, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'creator', + writable: true, + signer: true, + }, + ], + args: [ + { + name: 'input', + type: { + defined: { + name: 'updatePlatformFeeArgs', + }, + }, + }, + ], + }, + { + name: 'modifyPlatformSecondaryAdmin', + discriminator: [128, 153, 231, 143, 156, 220, 161, 147], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + }, + { + name: 'editionsControls', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, + 114, 111, 108, 115, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'creator', + writable: true, + signer: true, + }, + ], + args: [ + { + name: 'input', + type: { + defined: { + name: 'updatePlatformFeeSecondaryAdminInput', + }, + }, + }, + ], + }, + { + name: 'modifyRoyalties', + discriminator: [199, 95, 20, 107, 136, 161, 93, 137], + accounts: [ + { + name: 'editionsDeployment', + writable: true, + }, + { + name: 'editionsControls', + writable: true, + pda: { + seeds: [ + { + kind: 'const', + value: [ + 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, + 114, 111, 108, 115, + ], + }, + { + kind: 'account', + path: 'editionsDeployment', + }, + ], + }, + }, + { + name: 'payer', + writable: true, + signer: true, + }, + { + name: 'creator', + writable: true, + signer: true, + }, + { + name: 'mint', + writable: true, + }, + { + name: 'systemProgram', + address: '11111111111111111111111111111111', + }, + { + name: 'tokenProgram', + address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', + }, + { + name: 'libreplexEditionsProgram', + address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', + }, + ], + args: [ + { + name: 'input', + type: { + defined: { + name: 'updateRoyaltiesArgs', + }, + }, + }, + ], + }, + ], + accounts: [ + { + name: 'editionsControls', + discriminator: [124, 32, 239, 85, 118, 231, 152, 156], + }, + { + name: 'editionsDeployment', + discriminator: [101, 54, 68, 216, 168, 131, 242, 157], + }, + { + name: 'minterStats', + discriminator: [138, 239, 240, 226, 199, 53, 170, 179], + }, + ], + errors: [ + { + code: 6000, + name: 'tickerTooLong', + msg: 'Ticker too long', + }, + { + code: 6001, + name: 'mintTemplateTooLong', + msg: 'Mint template too long', + }, + { + code: 6002, + name: 'deploymentTemplateTooLong', + msg: 'Deployment template too long', + }, + { + code: 6003, + name: 'rootTypeTooLong', + msg: 'Root type too long', + }, + { + code: 6004, + name: 'mintedOut', + msg: 'Minted out', + }, + { + code: 6005, + name: 'legacyMigrationsAreMintedOut', + msg: 'Legacy migrations are minted out', + }, + { + code: 6006, + name: 'missingGlobalTreeDelegate', + msg: 'Global tree delegate is missing', + }, + { + code: 6007, + name: 'incorrectMintType', + msg: 'Incorrect mint type', + }, + { + code: 6008, + name: 'invalidMetadata', + msg: 'Invalid Metadata', + }, + { + code: 6009, + name: 'creatorFeeTooHigh', + msg: 'Creator fee too high', + }, + { + code: 6010, + name: 'feeCalculationError', + msg: 'Platform fee calculation failed.', + }, + { + code: 6011, + name: 'feeExceedsPrice', + msg: 'Total fee exceeds the price amount.', + }, + { + code: 6012, + name: 'invalidFeeShares', + msg: 'Total fee shares must equal 100.', + }, + { + code: 6013, + name: 'tooManyRecipients', + msg: 'Too many platform fee recipients. Maximum allowed is 5.', + }, + { + code: 6014, + name: 'recipientMismatch', + msg: 'Recipient account does not match the expected address.', + }, + { + code: 6015, + name: 'noPhasesAdded', + msg: 'No phases have been added. Cannot mint.', + }, + { + code: 6016, + name: 'invalidPhaseIndex', + msg: 'Invalid phase index.', + }, + { + code: 6017, + name: 'merkleRootNotSet', + msg: 'Merkle root not set for allow list mint', + }, + { + code: 6018, + name: 'merkleProofRequired', + msg: 'Merkle proof required for allow list mint', + }, + { + code: 6019, + name: 'allowListPriceAndMaxClaimsRequired', + msg: 'Allow list price and max claims are required for allow list mint', + }, + { + code: 6020, + name: 'invalidMerkleProof', + msg: 'Invalid merkle proof', + }, + { + code: 6021, + name: 'exceededAllowListMaxClaims', + msg: 'This wallet has exceeded allow list max_claims in the current phase', + }, + { + code: 6022, + name: 'phaseNotActive', + msg: 'Phase not active', + }, + { + code: 6023, + name: 'phaseNotStarted', + msg: 'Phase not yet started', + }, + { + code: 6024, + name: 'phaseAlreadyFinished', + msg: 'Phase already finished', + }, + { + code: 6025, + name: 'exceededMaxMintsForPhase', + msg: 'Exceeded max mints for this phase', + }, + { + code: 6026, + name: 'exceededWalletMaxMintsForPhase', + msg: 'Exceeded wallet max mints for this phase', + }, + { + code: 6027, + name: 'exceededWalletMaxMintsForCollection', + msg: 'Exceeded wallet max mints for the collection', + }, + ], + types: [ + { + name: 'addMetadataArgs', + type: { + kind: 'struct', + fields: [ + { + name: 'field', + type: 'string', + }, + { + name: 'value', + type: 'string', + }, + ], + }, + }, + { + name: 'creatorWithShare', + type: { + kind: 'struct', + fields: [ + { + name: 'address', + type: 'pubkey', + }, + { + name: 'share', + type: 'u8', + }, + ], + }, + }, + { + name: 'editionsControls', + type: { + kind: 'struct', + fields: [ + { + name: 'editionsDeployment', + type: 'pubkey', + }, + { + name: 'creator', + type: 'pubkey', + }, + { + name: 'treasury', + type: 'pubkey', + }, + { + name: 'maxMintsPerWallet', + type: 'u64', + }, + { + name: 'cosignerProgramId', + type: 'pubkey', + }, + { + name: 'platformFeePrimaryAdmin', + type: 'pubkey', + }, + { + name: 'platformFeeSecondaryAdmin', + type: 'pubkey', + }, + { + name: 'platformFeeValue', + type: 'u64', + }, + { + name: 'isFeeFlat', + type: 'bool', + }, + { + name: 'platformFeeRecipients', + type: { + array: [ + { + defined: { + name: 'platformFeeRecipient', + }, + }, + 5, + ], + }, + }, + { + name: 'phases', + type: { + vec: { + defined: { + name: 'phase', + }, + }, + }, + }, + { + name: 'padding', + type: { + array: ['u8', 200], + }, + }, + ], + }, + }, + { + name: 'editionsDeployment', + type: { + kind: 'struct', + fields: [ + { + name: 'creator', + type: 'pubkey', + }, + { + name: 'maxNumberOfTokens', + type: 'u64', + }, + { + name: 'numberOfTokensIssued', + type: 'u64', + }, + { + name: 'cosignerProgramId', + type: 'pubkey', + }, + { + name: 'groupMint', + type: 'pubkey', + }, + { + name: 'group', + type: 'pubkey', + }, + { + name: 'symbol', + type: 'string', + }, + { + name: 'collectionName', + type: 'string', + }, + { + name: 'collectionUri', + type: 'string', + }, + { + name: 'itemBaseName', + type: 'string', + }, + { + name: 'itemBaseUri', + type: 'string', + }, + { + name: 'itemNameIsTemplate', + type: 'bool', + }, + { + name: 'itemUriIsTemplate', + type: 'bool', + }, + { + name: 'padding', + type: { + array: ['u8', 98], + }, + }, + ], + }, + }, + { + name: 'initialiseControlInput', + type: { + kind: 'struct', + fields: [ + { + name: 'maxMintsPerWallet', + type: 'u64', + }, + { + name: 'treasury', + type: 'pubkey', + }, + { + name: 'maxNumberOfTokens', + type: 'u64', + }, + { + name: 'symbol', + type: 'string', + }, + { + name: 'collectionName', + type: 'string', + }, + { + name: 'collectionUri', + type: 'string', + }, + { + name: 'cosignerProgramId', + type: { + option: 'pubkey', + }, + }, + { + name: 'royalties', + type: { + defined: { + name: 'updateRoyaltiesArgs', + }, + }, + }, + { + name: 'extraMeta', + type: { + vec: { + defined: { + name: 'addMetadataArgs', + }, + }, + }, + }, + { + name: 'itemBaseUri', + type: 'string', + }, + { + name: 'itemBaseName', + type: 'string', + }, + { + name: 'platformFee', + type: { + defined: { + name: 'updatePlatformFeeArgs', + }, + }, + }, + ], + }, + }, + { + name: 'initialisePhaseInput', + type: { + kind: 'struct', + fields: [ + { + name: 'priceAmount', + type: 'u64', + }, + { + name: 'priceToken', + type: 'pubkey', + }, + { + name: 'startTime', + type: 'i64', + }, + { + name: 'maxMintsPerWallet', + type: 'u64', + }, + { + name: 'maxMintsTotal', + type: 'u64', + }, + { + name: 'endTime', + type: 'i64', + }, + { + name: 'merkleRoot', + type: { + option: { + array: ['u8', 32], + }, + }, + }, + ], + }, + }, + { + name: 'mintInput', + type: { + kind: 'struct', + fields: [ + { + name: 'phaseIndex', + type: 'u32', + }, + { + name: 'merkleProof', + type: { + option: { + vec: { + array: ['u8', 32], + }, + }, + }, + }, + { + name: 'allowListPrice', + type: { + option: 'u64', + }, + }, + { + name: 'allowListMaxClaims', + type: { + option: 'u64', + }, + }, + ], + }, + }, + { + name: 'minterStats', + type: { + kind: 'struct', + fields: [ + { + name: 'wallet', + type: 'pubkey', + }, + { + name: 'mintCount', + type: 'u64', + }, + { + name: 'padding', + type: { + array: ['u8', 50], + }, + }, + ], + }, + }, + { + name: 'phase', + type: { + kind: 'struct', + fields: [ + { + name: 'priceAmount', + type: 'u64', + }, + { + name: 'priceToken', + type: 'pubkey', + }, + { + name: 'startTime', + type: 'i64', + }, + { + name: 'active', + type: 'bool', + }, + { + name: 'maxMintsPerWallet', + type: 'u64', + }, + { + name: 'maxMintsTotal', + type: 'u64', + }, + { + name: 'endTime', + type: 'i64', + }, + { + name: 'currentMints', + type: 'u64', + }, + { + name: 'merkleRoot', + type: { + option: { + array: ['u8', 32], + }, + }, + }, + { + name: 'padding', + type: { + array: ['u8', 200], + }, + }, + ], + }, + }, + { + name: 'platformFeeRecipient', + type: { + kind: 'struct', + fields: [ + { + name: 'address', + type: 'pubkey', + }, + { + name: 'share', + type: 'u8', + }, + ], + }, + }, + { + name: 'updatePlatformFeeArgs', + type: { + kind: 'struct', + fields: [ + { + name: 'platformFeeValue', + type: 'u64', + }, + { + name: 'recipients', + type: { + vec: { + defined: { + name: 'platformFeeRecipient', + }, + }, + }, + }, + { + name: 'isFeeFlat', + type: 'bool', + }, + ], + }, + }, + { + name: 'updatePlatformFeeSecondaryAdminInput', + type: { + kind: 'struct', + fields: [ + { + name: 'newAdmin', + type: 'pubkey', + }, + ], + }, + }, + { + name: 'updateRoyaltiesArgs', + type: { + kind: 'struct', + fields: [ + { + name: 'royaltyBasisPoints', + type: 'u16', + }, + { + name: 'creators', + type: { + vec: { + defined: { + name: 'creatorWithShare', + }, + }, + }, + }, + ], + }, + }, + ], +}; \ No newline at end of file diff --git a/test/data/merkle_tree.json b/test/data/merkle_tree.json new file mode 100644 index 00000000..f13c217f --- /dev/null +++ b/test/data/merkle_tree.json @@ -0,0 +1,188 @@ +{ + "merkle_root": [ + 125, + 184, + 194, + 116, + 52, + 36, + 65, + 219, + 171, + 135, + 154, + 27, + 188, + 122, + 207, + 204, + 111, + 70, + 66, + 115, + 161, + 228, + 44, + 84, + 67, + 97, + 29, + 70, + 253, + 69, + 11, + 245 + ], + "max_num_nodes": 2, + "max_total_claim": 6, + "tree_nodes": [ + { + "claimant": [ + 104, + 164, + 83, + 51, + 23, + 177, + 193, + 29, + 252, + 241, + 86, + 132, + 173, + 155, + 114, + 131, + 130, + 73, + 27, + 101, + 233, + 95, + 12, + 45, + 107, + 255, + 120, + 26, + 121, + 221, + 120, + 54 + ], + "claim_price": 500000, + "max_claims": 3, + "proof": [ + [ + 64, + 131, + 242, + 169, + 206, + 112, + 155, + 119, + 81, + 214, + 17, + 137, + 174, + 140, + 208, + 220, + 141, + 177, + 213, + 131, + 127, + 104, + 181, + 15, + 121, + 228, + 87, + 25, + 232, + 172, + 235, + 168 + ] + ] + }, + { + "claimant": [ + 186, + 39, + 133, + 92, + 39, + 241, + 42, + 161, + 180, + 15, + 92, + 18, + 15, + 101, + 248, + 80, + 238, + 254, + 220, + 231, + 1, + 14, + 231, + 145, + 170, + 49, + 163, + 111, + 239, + 112, + 135, + 6 + ], + "claim_price": 500000, + "max_claims": 3, + "proof": [ + [ + 86, + 37, + 15, + 136, + 192, + 159, + 125, + 244, + 163, + 213, + 251, + 242, + 217, + 215, + 159, + 249, + 93, + 166, + 82, + 38, + 187, + 58, + 199, + 64, + 161, + 50, + 122, + 122, + 17, + 125, + 63, + 188 + ] + ] + } + ] +} \ No newline at end of file diff --git a/test/data/minter1.json b/test/data/minter1.json new file mode 100644 index 00000000..018778a6 --- /dev/null +++ b/test/data/minter1.json @@ -0,0 +1,6 @@ +[ + 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, 108, + 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, 218, 104, + 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, 114, 131, 130, 73, + 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221, 120, 54 +] diff --git a/test/data/minter2.json b/test/data/minter2.json new file mode 100644 index 00000000..6abe9d9b --- /dev/null +++ b/test/data/minter2.json @@ -0,0 +1,6 @@ +[ + 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, 114, 46, + 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, 220, 186, 39, + 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80, 238, 254, 220, + 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6 +] diff --git a/test/editions_controls.test.ts b/test/editions_controls.test.ts index b0dc7a6b..bff713f7 100644 --- a/test/editions_controls.test.ts +++ b/test/editions_controls.test.ts @@ -5,90 +5,155 @@ import { Keypair, SystemProgram, ComputeBudgetProgram, + LAMPORTS_PER_SOL, } from '@solana/web3.js'; -import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; -import { LibreplexEditionsControls } from '../../eclipse-program-library/target/types/libreplex_editions_controls'; -import { LibreplexEditions } from '../../eclipse-program-library/target/types/libreplex_editions'; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + getAssociatedTokenAddressSync, + TOKEN_2022_PROGRAM_ID, +} from '@solana/spl-token'; +import { + LibreplexEditionsControls, + IDL as EditionsControlsIDL, +} from '../../eclipse-program-library/target/types/libreplex_editions_controls'; +import { + LibreplexEditions, + IDL as EditionsIDL, +} from '../../eclipse-program-library/target/types/libreplex_editions'; import { expect } from 'chai'; import { describe, it } from 'mocha'; -import { decodeEditions, getCluster } from './utils'; +import { + getCluster, + getEditions, + getEditionsControls, + logEditions, + logEditionsControls, + createErrorHandler, +} from './utils'; import { Transaction } from '@solana/web3.js'; -import { decodeEditionsControls } from './utils'; +import { + EDITIONS_CONTROLS_PROGRAM_ID, + EDITIONS_PROGRAM_ID, + TOKEN_GROUP_EXTENSION_PROGRAM_ID, +} from '../constants'; +import { toBufferLE } from 'bigint-buffer'; describe('Editions Controls Test Suite', () => { const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); - const editionsControlsProgram = anchor.workspace - .LibreplexEditionsControls as Program; - console.log( - 'editionsControlsProgram ID:', - editionsControlsProgram.programId.toBase58() - ); - - const editionsProgram = anchor.workspace - .LibreplexEditions as Program; - console.log('editionsProgram ID:', editionsProgram.programId.toBase58()); - - const payer = (provider.wallet as anchor.Wallet).payer; - const creator1 = Keypair.generate(); - const creator2 = Keypair.generate(); - const treasury = Keypair.generate(); - const platformFeeAdmin = Keypair.generate(); - - const collectionConfig = { - maxNumberOfTokens: new anchor.BN(1150), - symbol: 'COOLX55', - name: 'Collection name with meta, platform fee and royalties', - offchainUrl: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/0', - treasury: treasury.publicKey, - maxMintsPerWallet: new anchor.BN(100), + const handleError = createErrorHandler(EditionsIDL, EditionsControlsIDL); + + let editionsControlsProgram: Program; + let editionsProgram: Program; + + let editionsPda: PublicKey; + let editionsControlsPda: PublicKey; + let hashlistPda: PublicKey; + + let payer: Keypair; + let creator1: Keypair; + let creator2: Keypair; + let treasury: Keypair; + let platformFeeAdmin: Keypair; + let groupMint: Keypair; + let group: Keypair; + + let minter1: Keypair; + let minter2: Keypair; + + let collectionConfig: { + symbol: string; + maxMintsPerWallet: anchor.BN; + maxNumberOfTokens: anchor.BN; + collectionName: string; + collectionUri: string; royalties: { - royaltyBasisPoints: new anchor.BN(1000), - creators: [ - { - address: creator1.publicKey, - share: 50, - }, - { - address: creator2.publicKey, - share: 50, - }, - ], - }, + royaltyBasisPoints: anchor.BN; + creators: { address: PublicKey; share: number }[]; + }; platformFee: { - platformFeeValue: new anchor.BN(500000), - recipients: [ - { - address: platformFeeAdmin.publicKey, - share: 100, - }, - ], - isFeeFlat: true, - }, - extraMeta: [ - { field: 'field1', value: 'value1' }, - { field: 'field2', value: 'value2' }, - { field: 'field3', value: 'value3' }, - { field: 'field4', value: 'value4' }, - ], - itemBaseUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/{}', - itemName: 'Item T8 V4 #{}', - cosignerProgramId: null, + platformFeeValue: anchor.BN; + recipients: { address: PublicKey; share: number }[]; + isFeeFlat: boolean; + }; + extraMeta: { field: string; value: string }[]; + itemBaseUri: string; + itemBaseName: string; + treasury: PublicKey; + cosignerProgramId: PublicKey | null; + }; + + let allowListConfig: { + merkleRoot: Buffer; + list: { + address: PublicKey; + price: anchor.BN; + max_claims: anchor.BN; + proof: Buffer[]; + }[]; }; before(async () => { const cluster = await getCluster(provider.connection); console.log('Cluster:', cluster); - }); - it('should deploy a collection, add a phase, and execute a mint', async () => { - // Modify compute units for the transaction - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 800000, - }); + editionsControlsProgram = anchor.workspace + .LibreplexEditionsControls as Program; + editionsProgram = anchor.workspace + .LibreplexEditions as Program; + + payer = (provider.wallet as anchor.Wallet).payer; + creator1 = Keypair.generate(); + creator2 = Keypair.generate(); + treasury = Keypair.generate(); + platformFeeAdmin = Keypair.generate(); + groupMint = Keypair.generate(); + group = Keypair.generate(); + + collectionConfig = { + maxNumberOfTokens: new anchor.BN(1150), + symbol: 'COOLX55', + collectionName: 'Collection name with meta, platform fee and royalties', + collectionUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/0', + treasury: treasury.publicKey, + maxMintsPerWallet: new anchor.BN(100), + royalties: { + royaltyBasisPoints: new anchor.BN(1000), + creators: [ + { + address: creator1.publicKey, + share: 50, + }, + { + address: creator2.publicKey, + share: 50, + }, + ], + }, + platformFee: { + platformFeeValue: new anchor.BN(500000), + recipients: [ + { + address: platformFeeAdmin.publicKey, + share: 100, + }, + ], + isFeeFlat: true, + }, + extraMeta: [ + { field: 'field1', value: 'value1' }, + { field: 'field2', value: 'value2' }, + { field: 'field3', value: 'value3' }, + { field: 'field4', value: 'value4' }, + ], + itemBaseUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/{}', + itemBaseName: 'Item T8 V4 #{}', + cosignerProgramId: null, + }; + console.log('Collection config: ', collectionConfig); - const [editionsPda] = PublicKey.findProgramAddressSync( + [editionsPda] = PublicKey.findProgramAddressSync( [ Buffer.from('editions_deployment'), Buffer.from(collectionConfig.symbol), @@ -96,19 +161,22 @@ describe('Editions Controls Test Suite', () => { editionsProgram.programId ); - const [editionsControlsPda] = PublicKey.findProgramAddressSync( + [editionsControlsPda] = PublicKey.findProgramAddressSync( [Buffer.from('editions_controls'), editionsPda.toBuffer()], editionsControlsProgram.programId ); - const [hashlistPda] = PublicKey.findProgramAddressSync( + + [hashlistPda] = PublicKey.findProgramAddressSync( [Buffer.from('hashlist'), editionsPda.toBuffer()], editionsProgram.programId ); + }); - const groupMint = Keypair.generate(); - const group = Keypair.generate(); - - console.log('Collection config: ', collectionConfig); + it('should deploy a collection, add a phase, and execute a mint', async () => { + // Modify compute units for the transaction + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 800000, + }); console.log('\nDeploying via initialiseEditionsControls...\n'); try { @@ -118,14 +186,14 @@ describe('Editions Controls Test Suite', () => { treasury: collectionConfig.treasury, maxNumberOfTokens: collectionConfig.maxNumberOfTokens, symbol: collectionConfig.symbol, - name: collectionConfig.name, - offchainUrl: collectionConfig.offchainUrl, + collectionName: collectionConfig.collectionName, + collectionUri: collectionConfig.collectionUri, cosignerProgramId: collectionConfig.cosignerProgramId, royalties: collectionConfig.royalties, platformFee: collectionConfig.platformFee, extraMeta: collectionConfig.extraMeta, itemBaseUri: collectionConfig.itemBaseUri, - itemName: collectionConfig.itemName, + itemBaseName: collectionConfig.itemBaseName, }) .accountsStrict({ editionsControls: editionsControlsPda, @@ -154,132 +222,385 @@ describe('Editions Controls Test Suite', () => { payer, ]); - console.log('Transaction signature:', txSignature); - + console.log('\nTransaction signature:', txSignature, '\n'); console.log('\ninitialiseEditionsControls done!\n'); - // log deployed addresses - console.log('Editions address:', editionsPda.toBase58()); - console.log('EditionsControls address:', editionsControlsPda.toBase58()); - console.log('Hashlist address:', hashlistPda.toBase58()); - console.log('GroupMint address:', groupMint.publicKey.toBase58()); - console.log('Group address:', group.publicKey.toBase58()); console.log('\nFetching and displaying deployed collection state...\n'); - - // Fetch and decode the Editions deployment - const editionsAccountInfo = await provider.connection.getAccountInfo( - editionsPda + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram ); - if (!editionsAccountInfo) { - throw new Error('Editions account not found'); - } + logEditions(editionsDecoded); - const editionsDeployment = decodeEditions(editionsProgram)( - editionsAccountInfo.data, - editionsPda + // Fetch and check the EditionsControls account + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram ); + logEditionsControls(editionsControlsDecoded); - console.log('Editions Deployment:'); - console.log({ - creator: editionsDeployment.item.creator.toBase58(), - groupMint: editionsDeployment.item.groupMint.toBase58(), - maxNumberOfTokens: editionsDeployment.item.maxNumberOfTokens.toString(), - name: editionsDeployment.item.name, - tokensMinted: editionsDeployment.item.numberOfTokensIssued.toString(), - offchainUrl: editionsDeployment.item.offchainUrl, - symbol: editionsDeployment.item.symbol, - nameIsTemplate: editionsDeployment.item.nameIsTemplate, - urlIsTemplate: editionsDeployment.item.urlIsTemplate, - }); + // Verify Editions deployment + expect(editionsDecoded.data.symbol).to.equal(collectionConfig.symbol); + expect(editionsDecoded.data.creator.toBase58()).to.equal( + editionsControlsPda.toBase58() + ); + expect(editionsDecoded.data.maxNumberOfTokens.toString()).to.equal( + collectionConfig.maxNumberOfTokens.toString() + ); + expect(editionsDecoded.data.collectionName).to.equal( + collectionConfig.collectionName + ); + expect(editionsDecoded.data.collectionUri).to.equal( + collectionConfig.collectionUri + ); + expect(editionsDecoded.data.itemBaseName).to.equal( + collectionConfig.itemBaseName + ); + expect(editionsDecoded.data.itemBaseUri).to.equal( + collectionConfig.itemBaseUri + ); - // Fetch and check the EditionsControls account - const controlsAccountData = await provider.connection.getAccountInfo( - editionsControlsPda + // Verify EditionsControls deployment + expect( + editionsControlsDecoded.data.editionsDeployment.toBase58() + ).to.equal(editionsPda.toBase58()); + expect(editionsControlsDecoded.data.creator.toBase58()).to.equal( + payer.publicKey.toBase58() + ); + expect(editionsControlsDecoded.data.treasury.toBase58()).to.equal( + collectionConfig.treasury.toBase58() ); + expect(Number(editionsControlsDecoded.data.maxMintsPerWallet)).to.equal( + Number(collectionConfig.maxMintsPerWallet) + ); + expect(editionsControlsDecoded.data.phases.length).to.equal(0); - if (!controlsAccountData || !controlsAccountData.data) { - console.log('Core editions deployment - no controls specified'); - } else { - const editionsControlsObj = decodeEditionsControls( - editionsControlsProgram - )(controlsAccountData.data, editionsControlsPda); - - console.log({ - editionsControls: { - address: editionsControlsPda.toBase58(), - coreDeployment: - editionsControlsObj.item.editionsDeployment.toBase58(), - creator: editionsControlsObj.item.creator.toBase58(), - treasury: editionsControlsObj.item.treasury.toBase58(), - maxMintsPerWallet: Number( - editionsControlsObj.item.maxMintsPerWallet - ), - }, - phases: editionsControlsObj.item.phases.map((item, idx) => ({ - phaseIndex: idx, - currentMints: Number(item.currentMints), - maxMintsPerWallet: Number(item.maxMintsPerWallet), - maxMintsTotal: Number(item.maxMintsTotal), - startTime: Number(item.startTime), - endTime: Number(item.endTime), - priceAmount: Number(item.priceAmount), - priceToken: item.priceToken ? item.priceToken.toBase58() : null, - merkleRoot: item.merkleRoot - ? JSON.stringify(item.merkleRoot) - : null, - })), - }); - - // Add assertions to verify the state - expect(editionsControlsObj.item.editionsDeployment.toBase58()).to.equal( - editionsPda.toBase58() - ); - expect(editionsControlsObj.item.creator.toBase58()).to.equal( - payer.publicKey.toBase58() - ); - expect(editionsControlsObj.item.treasury.toBase58()).to.equal( - collectionConfig.treasury.toBase58() - ); - expect(Number(editionsControlsObj.item.maxMintsPerWallet)).to.equal( - Number(collectionConfig.maxMintsPerWallet) - ); - - // Verify Editions deployment - // expect(editionsDeployment.item.creator.toBase58()).to.equal( - // payer.publicKey.toBase58() - // ); - // expect(editionsDeployment.item.maxNumberOfTokens.toString()).to.equal( - // collectionConfig.maxNumberOfTokens.toString() - // ); - // expect(editionsDeployment.item.name).to.equal(collectionConfig.name); - // expect(editionsDeployment.item.symbol).to.equal( - // collectionConfig.symbol - // ); - // expect(editionsDeployment.item.offchainUrl).to.equal( - // collectionConfig.offchainUrl - // ); - - // Add more assertions as needed for phases, if any are expected to be present at this point - } - - // 2. Add a phase (if needed) - // 3. Execute a mint (if needed) - - // These sections can be implemented similarly to the previous version, - // but make sure to update any parameters that have changed in the new CLI version. + // Add more assertions as needed } catch (error) { console.error('Error in initialiseEditionsControls:', error); throw error; } + }); - // Add assertions to verify the initialization was successful + // 2. Add a phase + it('Should add a phase without allowlist', async () => { + const phaseConfig = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(1000), + priceAmount: new anchor.BN(10000000), // 0.01 SOL + startTime: new anchor.BN(new Date().getTime() / 1000), + endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + merkleRoot: null, + }; + + const phaseIx = await editionsControlsProgram.methods + .addPhase(phaseConfig) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + const txSignature = await provider.sendAndConfirm(transaction, [payer]); + + console.log('\nTransaction signature:', txSignature, '\n'); + console.log('\naddPhase done!\n'); + + // get state + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + logEditions(editionsDecoded); + + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + logEditionsControls(editionsControlsDecoded); + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(1); + expect( + editionsControlsDecoded.data.phases[0].maxMintsPerWallet.toString() + ).to.equal(phaseConfig.maxMintsPerWallet.toString()); + expect( + editionsControlsDecoded.data.phases[0].maxMintsTotal.toString() + ).to.equal(phaseConfig.maxMintsTotal.toString()); + }); + + // ADD PHASE WITH ALLOWLIST + before(async () => { + console.log('Creating minters...'); + minter1 = Keypair.fromSecretKey( + new Uint8Array([ + 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, + 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, + 218, 104, 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, + 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, + 221, 120, 54, + ]) + ); + console.log('minter1: ', minter1.publicKey.toBase58()); + minter2 = Keypair.fromSecretKey( + new Uint8Array([ + 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, + 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, + 220, 186, 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, + 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, + 135, 6, + ]) + ); + console.log('minter2: ', minter2.publicKey.toBase58()); + + console.log('Creating allowlist...'); + allowListConfig = { + merkleRoot: Buffer.from([ + 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, + 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, + 245, + ]), + list: [ + { + address: minter1.publicKey, + price: new anchor.BN(500000), // 0.005 SOL + max_claims: new anchor.BN(3), + proof: [ + Buffer.from([ + 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, + 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, + 232, 172, 235, 168, + ]), + ], + }, + { + address: minter2.publicKey, + price: new anchor.BN(500000), // 0.005 SOL + max_claims: new anchor.BN(3), + proof: [ + Buffer.from([ + 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, + 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, + 17, 125, 63, 188, + ]), + ], + }, + ], + }; + console.log('Allowlist created'); + }); + + it('Should add a phase with allowlist', async () => { + const phaseConfig = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(1000), + priceAmount: new anchor.BN(10000000), // 0.01 SOL + startTime: new anchor.BN(new Date().getTime() / 1000), + endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + merkleRoot: allowListConfig.merkleRoot, + }; + + const phaseIx = await editionsControlsProgram.methods + .addPhase(phaseConfig) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + const txSignature = await provider.sendAndConfirm(transaction, [payer]); + + console.log('\nTransaction signature:', txSignature, '\n'); + console.log('\naddPhase done!\n'); + + // get state + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + logEditions(editionsDecoded); + + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + logEditionsControls(editionsControlsDecoded); + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(2); + expect( + editionsControlsDecoded.data.phases[1].maxMintsPerWallet.toString() + ).to.equal(phaseConfig.maxMintsPerWallet.toString()); + expect( + editionsControlsDecoded.data.phases[1].maxMintsTotal.toString() + ).to.equal(phaseConfig.maxMintsTotal.toString()); + }); + + // mint on first allow list + it('Should mint on first phase without allowlist', async () => { + // Airdrop SOL to minter1 + const airdropSignature = await provider.connection.requestAirdrop( + minter1.publicKey, + 10 * LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction(airdropSignature); + + // Airdrop SOL to treasury + const treasuryAirdropSignature = await provider.connection.requestAirdrop( + collectionConfig.treasury, + 10 * LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction(treasuryAirdropSignature); + + let minterBalance = await provider.connection.getBalance(minter1.publicKey); + console.log('minterBalance: ', minterBalance / LAMPORTS_PER_SOL); + + let payerBalance = await provider.connection.getBalance(payer.publicKey); + console.log('payerBalance: ', payerBalance / LAMPORTS_PER_SOL); + + let treasuryBalance = await provider.connection.getBalance( + collectionConfig.treasury + ); + console.log('treasuryBalance: ', treasuryBalance / LAMPORTS_PER_SOL); - // Add more assertions as needed + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 850000, + }); + + const mintConfig = { + phaseIndex: 0, + merkleProof: null, + allowListPrice: null, + allowListMint: null, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const tokenAccount = getAssociatedTokenAddressSync( + mint.publicKey, + minter1.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + + const [hashlistMarker] = PublicKey.findProgramAddressSync( + [ + Buffer.from('hashlist_marker'), + editionsPda.toBuffer(), + mint.publicKey.toBuffer(), + ], + new PublicKey(EDITIONS_PROGRAM_ID) + ); - // 2. Add a phase (if needed) - // 3. Execute a mint (if needed) + const [minterStats] = PublicKey.findProgramAddressSync( + [ + Buffer.from('minter_stats'), + editionsPda.toBuffer(), + minter1.publicKey.toBuffer(), + ], + new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) + ); + + const [minterStatsPhase] = PublicKey.findProgramAddressSync( + [ + Buffer.from('minter_stats_phase'), + editionsPda.toBuffer(), + minter1.publicKey.toBuffer(), + toBufferLE(BigInt(0), 4), + ], + new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) + ); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker, + payer: minter1.publicKey, + mint: mint.publicKey, + member: member.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats, + minterStatsPhase, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + groupExtensionProgram: new PublicKey(TOKEN_GROUP_EXTENSION_PROGRAM_ID), + tokenAccount, + treasury: collectionConfig.treasury, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }) + .instruction(); + + const transaction = new Transaction().add(modifyComputeUnits).add(mintIx); + transaction.recentBlockhash = ( + await provider.connection.getLatestBlockhash() + ).blockhash; + transaction.feePayer = minter1.publicKey; + transaction.sign(minter1, mint, member); + const rawTransaction = transaction.serialize(); + + try { + const txSignature = await provider.connection.sendRawTransaction( + rawTransaction, + { + skipPreflight: false, + preflightCommitment: 'confirmed', + } + ); + console.log('\nTransaction signature:', txSignature, '\n'); + console.log('\nmintWithControls done!\n'); + } catch (error) { + handleError(error); + console.error('Error in mintWithControls:', error); + throw error; + } + + // get state + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + logEditions(editionsDecoded); + + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + logEditionsControls(editionsControlsDecoded); - // These sections can be implemented similarly to the previous version, - // but make sure to update any parameters that have changed in the new CLI version. + // verify state + expect( + editionsControlsDecoded.data.phases[0].currentMints.toString() + ).to.equal(new anchor.BN(1).toString()); }); }); diff --git a/test/utils.ts b/test/utils.ts index c83c48a0..99d908e4 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,9 +1,13 @@ -import { Connection, clusterApiUrl, PublicKey } from '@solana/web3.js'; +import { + Connection, + clusterApiUrl, + PublicKey, + LAMPORTS_PER_SOL, +} from '@solana/web3.js'; import { LibreplexEditions } from '../target/types/libreplex_editions'; -import { IdlAccounts, IdlTypes } from '@coral-xyz/anchor'; +import { Idl, IdlAccounts, IdlTypes, AnchorError } from '@coral-xyz/anchor'; import { BorshCoder, Program } from '@coral-xyz/anchor'; import { LibreplexEditionsControls } from '../target/types/libreplex_editions_controls'; - export type EditionsDeployment = IdlAccounts['editionsDeployment']; @@ -25,39 +29,178 @@ export async function getCluster(connection: Connection): Promise { } } +export type EditionsControls = + IdlAccounts['editionsControls']; + +export const getBase64FromDatabytes = (dataBytes: Buffer, dataType: string) => { + console.log({ dataBytes }); + const base = dataBytes.toString('base64'); + return `data:${dataType};base64,${base}`; +}; + export const decodeEditions = (program: Program) => (buffer: Buffer | undefined, pubkey: PublicKey) => { const coder = new BorshCoder(program.idl); - const liquidity = buffer + const data = buffer ? coder.accounts.decode('editionsDeployment', buffer) : null; return { - item: liquidity, + data, pubkey, }; }; -export type EditionsControls = - IdlAccounts['editionsControls']; - -export const getBase64FromDatabytes = (dataBytes: Buffer, dataType: string) => { - console.log({ dataBytes }); - const base = dataBytes.toString('base64'); - return `data:${dataType};base64,${base}`; -}; - export const decodeEditionsControls = (program: Program) => (buffer: Buffer | undefined, pubkey: PublicKey) => { const coder = new BorshCoder(program.idl); - const liquidity = buffer + const data = buffer ? coder.accounts.decode('editionsControls', buffer) : null; return { - item: liquidity, + data, pubkey, }; }; + +export const getEditions = async ( + connection: Connection, + editionsPda: PublicKey, + editionsProgram: Program +) => { + const editionsAccountInfo = await connection.getAccountInfo(editionsPda); + if (!editionsAccountInfo) { + throw new Error('Editions account not found'); + } + const editionsDecoded = decodeEditions(editionsProgram)( + editionsAccountInfo.data, + editionsPda + ); + return editionsDecoded; +}; + +export const getEditionsControls = async ( + connection: Connection, + editionsControlsPda: PublicKey, + editionsControlsProgram: Program +) => { + const editionsControlsAccountInfo = await connection.getAccountInfo( + editionsControlsPda + ); + if (!editionsControlsAccountInfo) { + throw new Error( + 'EditionsControls account not found. The collection was not initialized with controls.' + ); + } + const editionsControlsDecoded = decodeEditionsControls( + editionsControlsProgram + )(editionsControlsAccountInfo.data, editionsControlsPda); + return editionsControlsDecoded; +}; + +export const logEditions = (editionsDecoded: { + data: EditionsDeployment; + pubkey: PublicKey; +}) => { + console.log({ + Editions: { + symbol: editionsDecoded.data.symbol, + creator: editionsDecoded.data.creator.toBase58(), + groupMint: editionsDecoded.data.groupMint.toBase58(), + maxNumberOfTokens: editionsDecoded.data.maxNumberOfTokens.toString(), + cosignerProgramId: editionsDecoded.data.cosignerProgramId + ? editionsDecoded.data.cosignerProgramId.toBase58() + : null, + collectionName: editionsDecoded.data.collectionName, + collectionUri: editionsDecoded.data.collectionUri, + tokensMinted: editionsDecoded.data.numberOfTokensIssued.toString(), + itemBaseName: editionsDecoded.data.itemBaseName, + itemBaseUri: editionsDecoded.data.itemBaseUri, + itemNameIsTemplate: editionsDecoded.data.itemNameIsTemplate, + itemUriIsTemplate: editionsDecoded.data.itemUriIsTemplate, + }, + }); +}; + +export const logEditionsControls = (editionsControlsDecoded: { + data: EditionsControls; + pubkey: PublicKey; +}) => { + console.log({ + EditionsControls: { + address: editionsControlsDecoded.pubkey.toBase58(), + coreDeployment: + editionsControlsDecoded.data.editionsDeployment.toBase58(), + creator: editionsControlsDecoded.data.creator.toBase58(), + treasury: editionsControlsDecoded.data.treasury.toBase58(), + maxMintsPerWallet: Number(editionsControlsDecoded.data.maxMintsPerWallet), + }, + phases: editionsControlsDecoded.data.phases.map((item, idx) => ({ + phaseIndex: idx, + currentMints: Number(item.currentMints), + maxMintsPerWallet: Number(item.maxMintsPerWallet), + maxMintsTotal: Number(item.maxMintsTotal), + startTime: Number(item.startTime), + endTime: Number(item.endTime), + priceAmount: Number(item.priceAmount), + priceToken: item.priceToken ? item.priceToken.toBase58() : null, + merkleRoot: item.merkleRoot ? JSON.stringify(item.merkleRoot) : null, + })), + }); +}; + +export async function ensureAccountHasSol( + connection: Connection, + account: PublicKey, + minBalance: number +) { + let balance = await connection.getBalance(account); + console.log( + `Initial balance of ${account.toBase58()}: ${ + balance / LAMPORTS_PER_SOL + } SOL` + ); + + if (balance < minBalance) { + const airdropAmount = minBalance - balance; + const signature = await connection.requestAirdrop(account, airdropAmount); + await connection.confirmTransaction(signature, 'confirmed'); + balance = await connection.getBalance(account); + console.log(`New balance after airdrop: ${balance / LAMPORTS_PER_SOL} SOL`); + } +} + +export function createErrorHandler(...idls: Idl[]) { + return function handleError(error: unknown): void { + console.error('Error occurred:', error); + + if (error instanceof AnchorError) { + const errorCode = error.error.errorCode.number; + for (const idl of idls) { + const errorMessage = getErrorMessage(errorCode, idl); + if (errorMessage !== `Unknown error: ${errorCode}`) { + console.error('Error code:', errorCode); + console.error('Error message:', errorMessage); + console.error('Error name:', error.error.errorCode.code); + console.error('Program ID:', error.program.programId.toBase58()); + return; + } + } + console.error(`Unknown error code: ${errorCode}`); + } else if (error instanceof Error) { + console.error('Error message:', error.message); + console.error('Stack trace:', error.stack); + } else { + console.error('Unexpected error:', error); + } + } +} + +function getErrorMessage(code: number, idl: Idl): string { + const idlErrors = idl.errors ?? []; + const error = idlErrors.find((e) => e.code === code); + return error ? error.msg : `Unknown error: ${code}`; +} From 71b5cc4bc09b3431530ae85972244111c972f7ec Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Thu, 10 Oct 2024 15:37:32 -0300 Subject: [PATCH 19/29] Added token metadata pulling & verification --- Cargo.toml | 2 +- package.json | 5 +- .../src/instructions/initialise.rs | 6 +- .../src/instructions/mint.rs | 2 +- programs/libreplex_editions/src/state.rs | 6 - target/types/libreplex_default_renderer.ts | 75 -- target/types/libreplex_editions.ts | 819 +------------ target/types/libreplex_editions_controls.ts | 1087 ----------------- target/types/libreplex_monoswap.ts | 467 ------- test/editions_controls.test.ts | 52 +- test/utils.ts | 155 +-- yarn.lock | 261 +++- 12 files changed, 317 insertions(+), 2620 deletions(-) delete mode 100644 target/types/libreplex_default_renderer.ts delete mode 100644 target/types/libreplex_monoswap.ts diff --git a/Cargo.toml b/Cargo.toml index 436fcebf..eaaf8cf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ codegen-units = 1 [profile.release.build-override] opt-level = 3 incremental = false -codegen-units = 1 +codegen-units = 1 \ No newline at end of file diff --git a/package.json b/package.json index 10aeb93e..2ccfc4df 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,9 @@ }, "dependencies": { "@coral-xyz/anchor": "0.30.0", - "@solana/spl-token": "^0.3.8", - "@solana/web3.js": "^1.73.0" + "@solana/spl-token": "^0.4.8", + "@solana/web3.js": "^1.73.0", + "@solana/spl-token-metadata": "^0.1.5" }, "devDependencies": { "@types/chai": "^4.3.5", diff --git a/programs/libreplex_editions/src/instructions/initialise.rs b/programs/libreplex_editions/src/instructions/initialise.rs index 40075eb3..b462a6d9 100644 --- a/programs/libreplex_editions/src/instructions/initialise.rs +++ b/programs/libreplex_editions/src/instructions/initialise.rs @@ -104,8 +104,6 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result _ => system_program::ID }, symbol: input.symbol, - collection_name: input.collection_name, - collection_uri: input.collection_uri, item_base_name: input.item_base_name, item_base_uri: input.item_base_uri, item_name_is_template, @@ -139,9 +137,9 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result }, 0, Some(TokenMetadata { - name: editions_deployment.collection_name.clone(), + name: input.collection_name.clone(), symbol: editions_deployment.symbol.clone(), - uri: editions_deployment.collection_uri.clone(), + uri: input.collection_uri.clone(), update_authority, mint: group_mint.key(), additional_metadata: vec![], // Leave this empty for now, diff --git a/programs/libreplex_editions/src/instructions/mint.rs b/programs/libreplex_editions/src/instructions/mint.rs index 7e20c806..30e667fe 100644 --- a/programs/libreplex_editions/src/instructions/mint.rs +++ b/programs/libreplex_editions/src/instructions/mint.rs @@ -137,7 +137,7 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<() true => editions_deployment.item_base_uri.format(&[editions_deployment.number_of_tokens_issued + 1]), false => editions_deployment.item_base_uri.clone() }; - // msg!("Create token 2022 w/ metadata"); + create_token_2022_and_metadata( MintAccounts2022 { authority: editions_deployment.to_account_info(), diff --git a/programs/libreplex_editions/src/state.rs b/programs/libreplex_editions/src/state.rs index b4cd0045..ed537ffd 100644 --- a/programs/libreplex_editions/src/state.rs +++ b/programs/libreplex_editions/src/state.rs @@ -35,12 +35,6 @@ pub struct EditionsDeployment { #[max_len(SYMBOL_LIMIT)] pub symbol: String, - #[max_len(NAME_LIMIT)] - pub collection_name: String, - - #[max_len(URI_LIMIT)] - pub collection_uri: String, - #[max_len(NAME_LIMIT)] pub item_base_name: String, diff --git a/target/types/libreplex_default_renderer.ts b/target/types/libreplex_default_renderer.ts deleted file mode 100644 index 01c6a51e..00000000 --- a/target/types/libreplex_default_renderer.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Program IDL in camelCase format in order to be used in JS/TS. - * - * Note that this is only a type helper and is not the actual IDL. The original - * IDL can be found at `target/idl/libreplex_default_renderer.json`. - */ -export type LibreplexDefaultRenderer = { - "address": "7oHj4YFq4JhxVc1BvHqvZma5evSn8hkXwFHPoTQ1PB4f", - "metadata": { - "name": "libreplexDefaultRenderer", - "version": "0.1.2", - "spec": "0.1.0", - "description": "Created with Anchor" - }, - "instructions": [ - { - "name": "canonical", - "discriminator": [ - 233, - 11, - 68, - 244, - 108, - 63, - 142, - 79 - ], - "accounts": [ - { - "name": "metadata" - }, - { - "name": "mint" - }, - { - "name": "group" - }, - { - "name": "renderState", - "pda": { - "seeds": [ - { - "kind": "account", - "path": "mint" - } - ] - } - }, - { - "name": "outputAccount" - } - ], - "args": [ - { - "name": "renderInput", - "type": { - "defined": { - "name": "renderInput" - } - } - } - ], - "returns": "bytes" - } - ], - "types": [ - { - "name": "renderInput", - "type": { - "kind": "struct", - "fields": [] - } - } - ] -}; diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 65a70114..f6869a02 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", + "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp", "metadata": { "name": "libreplexEditions", "version": "0.2.1", @@ -694,63 +694,53 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "sizeExceedsMaxSize", - "msg": "Collection size exceeds max size." + "name": "tickerTooLong", + "msg": "Ticker too long" }, { "code": 6001, - "name": "maxSizeBelowCurrentSize", - "msg": "Max size cannot be reduced below current size." + "name": "mintTemplateTooLong", + "msg": "Mint template too long" }, { "code": 6002, - "name": "creatorShareInvalid", - "msg": "Creators shares must add up to 100." + "name": "deploymentTemplateTooLong", + "msg": "Deployment template too long" }, { "code": 6003, - "name": "missingApproveAccount", - "msg": "Missing approve account." + "name": "rootTypeTooLong", + "msg": "Root type too long" }, { "code": 6004, - "name": "expiredApproveAccount", - "msg": "Approve account has expired." + "name": "mintedOut", + "msg": "Minted out" }, { "code": 6005, - "name": "invalidField", - "msg": "Invalid field. You cannot use a public key as a field." + "name": "legacyMigrationsAreMintedOut", + "msg": "Legacy migrations are minted out" }, { "code": 6006, - "name": "creatorAddressInvalid", - "msg": "The Address you provided is invalid. Please provide a valid address." + "name": "missingGlobalTreeDelegate", + "msg": "Global tree delegate is missing" }, { "code": 6007, - "name": "royaltyBasisPointsInvalid", - "msg": "Royalty basis points must be less than or equal to 10000." + "name": "incorrectMintType", + "msg": "Incorrect mint type" }, { "code": 6008, - "name": "platformFeeBasisPointsInvalid", - "msg": "Platform fee basis points must be less than or equal to 10000." + "name": "invalidMetadata", + "msg": "Invalid Metadata" }, { "code": 6009, - "name": "recipientShareInvalid", - "msg": "Recipient shares must add up to 100." - }, - { - "code": 6010, - "name": "reservedField", - "msg": "The provided field is invalid or reserved." - }, - { - "code": 6011, - "name": "invalidNumberOfRecipients", - "msg": "Invalid number of platform fee recipients. Exactly 5 recipients are required." + "name": "creatorFeeTooHigh", + "msg": "Creator fee too high" } ], "types": [ @@ -819,14 +809,6 @@ export type LibreplexEditions = { "name": "symbol", "type": "string" }, - { - "name": "collectionName", - "type": "string" - }, - { - "name": "collectionUri", - "type": "string" - }, { "name": "itemBaseName", "type": "string" @@ -987,762 +969,3 @@ export type LibreplexEditions = { } ] }; - -export const IDL: LibreplexEditions = { - address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', - metadata: { - name: 'libreplexEditions', - version: '0.2.1', - spec: '0.1.0', - description: 'Created with Anchor', - repository: 'https://github.com/Libreplex/libreplex-program-library', - }, - instructions: [ - { - name: 'addMetadata', - docs: ['add additional metadata to mint'], - discriminator: [231, 195, 40, 240, 67, 231, 53, 136], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, - 108, 111, 121, 109, 101, 110, 116, - ], - }, - { - kind: 'account', - path: 'editions_deployment.symbol', - account: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'signer', - writable: true, - signer: true, - }, - { - name: 'mint', - writable: true, - signer: true, - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - ], - args: [ - { - name: 'args', - type: { - vec: { - defined: { - name: 'addMetadataArgs', - }, - }, - }, - }, - ], - }, - { - name: 'addRoyalties', - docs: ['add royalties to mint'], - discriminator: [195, 251, 126, 230, 187, 134, 168, 210], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, - 108, 111, 121, 109, 101, 110, 116, - ], - }, - { - kind: 'account', - path: 'editions_deployment.symbol', - account: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'signer', - writable: true, - signer: true, - }, - { - name: 'mint', - writable: true, - signer: true, - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - ], - args: [ - { - name: 'args', - type: { - defined: { - name: 'updateRoyaltiesArgs', - }, - }, - }, - ], - }, - { - name: 'initialise', - discriminator: [162, 198, 118, 235, 215, 247, 25, 118], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, - 108, 111, 121, 109, 101, 110, 116, - ], - }, - { - kind: 'arg', - path: 'input.symbol', - }, - ], - }, - }, - { - name: 'hashlist', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [104, 97, 115, 104, 108, 105, 115, 116], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'creator', - writable: true, - }, - { - name: 'groupMint', - writable: true, - signer: true, - }, - { - name: 'group', - writable: true, - signer: true, - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - { - name: 'groupExtensionProgram', - address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', - }, - ], - args: [ - { - name: 'input', - type: { - defined: { - name: 'initialiseInput', - }, - }, - }, - ], - }, - { - name: 'mint', - discriminator: [51, 57, 225, 47, 182, 146, 137, 166], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, - 108, 111, 121, 109, 101, 110, 116, - ], - }, - { - kind: 'account', - path: 'editions_deployment.symbol', - account: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'hashlist', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [104, 97, 115, 104, 108, 105, 115, 116], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'hashlistMarker', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 104, 97, 115, 104, 108, 105, 115, 116, 95, 109, 97, 114, 107, - 101, 114, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - { - kind: 'account', - path: 'mint', - }, - ], - }, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'signer', - writable: true, - signer: true, - }, - { - name: 'minter', - writable: true, - }, - { - name: 'mint', - writable: true, - signer: true, - }, - { - name: 'member', - writable: true, - signer: true, - }, - { - name: 'group', - writable: true, - }, - { - name: 'groupMint', - writable: true, - }, - { - name: 'tokenAccount', - writable: true, - }, - { - name: 'tokenProgram', - }, - { - name: 'associatedTokenProgram', - address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', - }, - { - name: 'groupExtensionProgram', - address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - ], - args: [], - }, - { - name: 'modifyRoyalties', - docs: ['modify royalties of mint'], - discriminator: [199, 95, 20, 107, 136, 161, 93, 137], - accounts: [ - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'editionsDeployment', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, - 108, 111, 121, 109, 101, 110, 116, - ], - }, - { - kind: 'account', - path: 'editions_deployment.symbol', - account: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'signer', - writable: true, - signer: true, - }, - { - name: 'mint', - writable: true, - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - ], - args: [ - { - name: 'args', - type: { - defined: { - name: 'updateRoyaltiesArgs', - }, - }, - }, - ], - }, - { - name: 'removeMetadata', - docs: ['remove additional metadata to mint'], - discriminator: [81, 68, 231, 49, 91, 8, 111, 160], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 100, 101, 112, - 108, 111, 121, 109, 101, 110, 116, - ], - }, - { - kind: 'account', - path: 'editions_deployment.symbol', - account: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'signer', - writable: true, - signer: true, - }, - { - name: 'mint', - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - ], - args: [ - { - name: 'args', - type: { - vec: { - defined: { - name: 'removeMetadataArgs', - }, - }, - }, - }, - ], - }, - ], - accounts: [ - { - name: 'editionsDeployment', - discriminator: [101, 54, 68, 216, 168, 131, 242, 157], - }, - { - name: 'hashlist', - discriminator: [187, 203, 134, 6, 43, 198, 120, 186], - }, - { - name: 'hashlistMarker', - discriminator: [55, 46, 160, 53, 239, 41, 223, 50], - }, - ], - errors: [ - { - code: 6000, - name: 'sizeExceedsMaxSize', - msg: 'Collection size exceeds max size.', - }, - { - code: 6001, - name: 'maxSizeBelowCurrentSize', - msg: 'Max size cannot be reduced below current size.', - }, - { - code: 6002, - name: 'creatorShareInvalid', - msg: 'Creators shares must add up to 100.', - }, - { - code: 6003, - name: 'missingApproveAccount', - msg: 'Missing approve account.', - }, - { - code: 6004, - name: 'expiredApproveAccount', - msg: 'Approve account has expired.', - }, - { - code: 6005, - name: 'invalidField', - msg: 'Invalid field. You cannot use a public key as a field.', - }, - { - code: 6006, - name: 'creatorAddressInvalid', - msg: 'The Address you provided is invalid. Please provide a valid address.', - }, - { - code: 6007, - name: 'royaltyBasisPointsInvalid', - msg: 'Royalty basis points must be less than or equal to 10000.', - }, - { - code: 6008, - name: 'platformFeeBasisPointsInvalid', - msg: 'Platform fee basis points must be less than or equal to 10000.', - }, - { - code: 6009, - name: 'recipientShareInvalid', - msg: 'Recipient shares must add up to 100.', - }, - { - code: 6010, - name: 'reservedField', - msg: 'The provided field is invalid or reserved.', - }, - { - code: 6011, - name: 'invalidNumberOfRecipients', - msg: 'Invalid number of platform fee recipients. Exactly 5 recipients are required.', - }, - ], - types: [ - { - name: 'addMetadataArgs', - type: { - kind: 'struct', - fields: [ - { - name: 'field', - type: 'string', - }, - { - name: 'value', - type: 'string', - }, - ], - }, - }, - { - name: 'creatorWithShare', - type: { - kind: 'struct', - fields: [ - { - name: 'address', - type: 'pubkey', - }, - { - name: 'share', - type: 'u8', - }, - ], - }, - }, - { - name: 'editionsDeployment', - type: { - kind: 'struct', - fields: [ - { - name: 'creator', - type: 'pubkey', - }, - { - name: 'maxNumberOfTokens', - type: 'u64', - }, - { - name: 'numberOfTokensIssued', - type: 'u64', - }, - { - name: 'cosignerProgramId', - type: 'pubkey', - }, - { - name: 'groupMint', - type: 'pubkey', - }, - { - name: 'group', - type: 'pubkey', - }, - { - name: 'symbol', - type: 'string', - }, - { - name: 'collectionName', - type: 'string', - }, - { - name: 'collectionUri', - type: 'string', - }, - { - name: 'itemBaseName', - type: 'string', - }, - { - name: 'itemBaseUri', - type: 'string', - }, - { - name: 'itemNameIsTemplate', - type: 'bool', - }, - { - name: 'itemUriIsTemplate', - type: 'bool', - }, - { - name: 'padding', - type: { - array: ['u8', 98], - }, - }, - ], - }, - }, - { - name: 'hashlist', - type: { - kind: 'struct', - fields: [ - { - name: 'deployment', - type: 'pubkey', - }, - { - name: 'issues', - type: { - vec: { - defined: { - name: 'mintAndOrder', - }, - }, - }, - }, - ], - }, - }, - { - name: 'hashlistMarker', - type: { - kind: 'struct', - fields: [ - { - name: 'editionsDeployment', - type: 'pubkey', - }, - { - name: 'mint', - type: 'pubkey', - }, - ], - }, - }, - { - name: 'initialiseInput', - type: { - kind: 'struct', - fields: [ - { - name: 'symbol', - type: 'string', - }, - { - name: 'collectionName', - type: 'string', - }, - { - name: 'collectionUri', - type: 'string', - }, - { - name: 'maxNumberOfTokens', - type: 'u64', - }, - { - name: 'creatorCosignProgramId', - type: { - option: 'pubkey', - }, - }, - { - name: 'itemBaseUri', - type: 'string', - }, - { - name: 'itemBaseName', - type: 'string', - }, - ], - }, - }, - { - name: 'mintAndOrder', - type: { - kind: 'struct', - fields: [ - { - name: 'mint', - type: 'pubkey', - }, - { - name: 'order', - type: 'u64', - }, - ], - }, - }, - { - name: 'removeMetadataArgs', - type: { - kind: 'struct', - fields: [ - { - name: 'field', - type: 'string', - }, - { - name: 'value', - type: 'string', - }, - ], - }, - }, - { - name: 'updateRoyaltiesArgs', - type: { - kind: 'struct', - fields: [ - { - name: 'royaltyBasisPoints', - type: 'u16', - }, - { - name: 'creators', - type: { - vec: { - defined: { - name: 'creatorWithShare', - }, - }, - }, - }, - ], - }, - }, - ], -}; diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index 0c53a8ad..fece7a7d 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -935,14 +935,6 @@ export type LibreplexEditionsControls = { "name": "symbol", "type": "string" }, - { - "name": "collectionName", - "type": "string" - }, - { - "name": "collectionUri", - "type": "string" - }, { "name": "itemBaseName", "type": "string" @@ -1286,1082 +1278,3 @@ export type LibreplexEditionsControls = { } ] }; - -export const IDL: LibreplexEditionsControls = { - address: '8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco', - metadata: { - name: 'libreplexEditionsControls', - version: '0.2.1', - spec: '0.1.0', - description: 'Created with Anchor', - repository: 'https://github.com/Libreplex/libreplex-program-library', - }, - instructions: [ - { - name: 'addPhase', - discriminator: [245, 220, 147, 40, 30, 207, 36, 127], - accounts: [ - { - name: 'editionsControls', - writable: true, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'creator', - writable: true, - signer: true, - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - { - name: 'libreplexEditionsProgram', - address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', - }, - ], - args: [ - { - name: 'input', - type: { - defined: { - name: 'initialisePhaseInput', - }, - }, - }, - ], - }, - { - name: 'initialiseEditionsControls', - discriminator: [69, 176, 133, 29, 20, 49, 120, 202], - accounts: [ - { - name: 'editionsControls', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, - 114, 111, 108, 115, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'editionsDeployment', - writable: true, - }, - { - name: 'hashlist', - writable: true, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'creator', - }, - { - name: 'groupMint', - writable: true, - signer: true, - }, - { - name: 'group', - writable: true, - signer: true, - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - { - name: 'groupExtensionProgram', - address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', - }, - { - name: 'libreplexEditionsProgram', - address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', - }, - ], - args: [ - { - name: 'input', - type: { - defined: { - name: 'initialiseControlInput', - }, - }, - }, - ], - }, - { - name: 'mintWithControls', - discriminator: [167, 57, 252, 220, 69, 92, 231, 61], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - }, - { - name: 'editionsControls', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, - 114, 111, 108, 115, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'hashlist', - writable: true, - }, - { - name: 'hashlistMarker', - writable: true, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'signer', - docs: [ - 'When deployment.require_creator_cosign is true, this must be equal to the creator', - 'of the deployment; otherwise, can be any signer account', - ], - signer: true, - }, - { - name: 'minter', - writable: true, - }, - { - name: 'minterStats', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 109, 105, 110, 116, 101, 114, 95, 115, 116, 97, 116, 115, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - { - kind: 'account', - path: 'minter', - }, - ], - }, - }, - { - name: 'minterStatsPhase', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 109, 105, 110, 116, 101, 114, 95, 115, 116, 97, 116, 115, 95, - 112, 104, 97, 115, 101, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - { - kind: 'account', - path: 'minter', - }, - { - kind: 'arg', - path: 'mint_input.phase_index', - }, - ], - }, - }, - { - name: 'mint', - writable: true, - signer: true, - }, - { - name: 'member', - writable: true, - signer: true, - }, - { - name: 'group', - writable: true, - }, - { - name: 'groupMint', - writable: true, - }, - { - name: 'platformFeeRecipient1', - writable: true, - }, - { - name: 'tokenAccount', - writable: true, - }, - { - name: 'treasury', - writable: true, - }, - { - name: 'tokenProgram', - }, - { - name: 'associatedTokenProgram', - address: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', - }, - { - name: 'groupExtensionProgram', - address: '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V', - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'libreplexEditionsProgram', - address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', - }, - ], - args: [ - { - name: 'mintInput', - type: { - defined: { - name: 'mintInput', - }, - }, - }, - ], - }, - { - name: 'modifyPlatformFee', - discriminator: [186, 73, 229, 152, 183, 174, 250, 197], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - }, - { - name: 'editionsControls', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, - 114, 111, 108, 115, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'creator', - writable: true, - signer: true, - }, - ], - args: [ - { - name: 'input', - type: { - defined: { - name: 'updatePlatformFeeArgs', - }, - }, - }, - ], - }, - { - name: 'modifyPlatformSecondaryAdmin', - discriminator: [128, 153, 231, 143, 156, 220, 161, 147], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - }, - { - name: 'editionsControls', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, - 114, 111, 108, 115, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'creator', - writable: true, - signer: true, - }, - ], - args: [ - { - name: 'input', - type: { - defined: { - name: 'updatePlatformFeeSecondaryAdminInput', - }, - }, - }, - ], - }, - { - name: 'modifyRoyalties', - discriminator: [199, 95, 20, 107, 136, 161, 93, 137], - accounts: [ - { - name: 'editionsDeployment', - writable: true, - }, - { - name: 'editionsControls', - writable: true, - pda: { - seeds: [ - { - kind: 'const', - value: [ - 101, 100, 105, 116, 105, 111, 110, 115, 95, 99, 111, 110, 116, - 114, 111, 108, 115, - ], - }, - { - kind: 'account', - path: 'editionsDeployment', - }, - ], - }, - }, - { - name: 'payer', - writable: true, - signer: true, - }, - { - name: 'creator', - writable: true, - signer: true, - }, - { - name: 'mint', - writable: true, - }, - { - name: 'systemProgram', - address: '11111111111111111111111111111111', - }, - { - name: 'tokenProgram', - address: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb', - }, - { - name: 'libreplexEditionsProgram', - address: 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp', - }, - ], - args: [ - { - name: 'input', - type: { - defined: { - name: 'updateRoyaltiesArgs', - }, - }, - }, - ], - }, - ], - accounts: [ - { - name: 'editionsControls', - discriminator: [124, 32, 239, 85, 118, 231, 152, 156], - }, - { - name: 'editionsDeployment', - discriminator: [101, 54, 68, 216, 168, 131, 242, 157], - }, - { - name: 'minterStats', - discriminator: [138, 239, 240, 226, 199, 53, 170, 179], - }, - ], - errors: [ - { - code: 6000, - name: 'tickerTooLong', - msg: 'Ticker too long', - }, - { - code: 6001, - name: 'mintTemplateTooLong', - msg: 'Mint template too long', - }, - { - code: 6002, - name: 'deploymentTemplateTooLong', - msg: 'Deployment template too long', - }, - { - code: 6003, - name: 'rootTypeTooLong', - msg: 'Root type too long', - }, - { - code: 6004, - name: 'mintedOut', - msg: 'Minted out', - }, - { - code: 6005, - name: 'legacyMigrationsAreMintedOut', - msg: 'Legacy migrations are minted out', - }, - { - code: 6006, - name: 'missingGlobalTreeDelegate', - msg: 'Global tree delegate is missing', - }, - { - code: 6007, - name: 'incorrectMintType', - msg: 'Incorrect mint type', - }, - { - code: 6008, - name: 'invalidMetadata', - msg: 'Invalid Metadata', - }, - { - code: 6009, - name: 'creatorFeeTooHigh', - msg: 'Creator fee too high', - }, - { - code: 6010, - name: 'feeCalculationError', - msg: 'Platform fee calculation failed.', - }, - { - code: 6011, - name: 'feeExceedsPrice', - msg: 'Total fee exceeds the price amount.', - }, - { - code: 6012, - name: 'invalidFeeShares', - msg: 'Total fee shares must equal 100.', - }, - { - code: 6013, - name: 'tooManyRecipients', - msg: 'Too many platform fee recipients. Maximum allowed is 5.', - }, - { - code: 6014, - name: 'recipientMismatch', - msg: 'Recipient account does not match the expected address.', - }, - { - code: 6015, - name: 'noPhasesAdded', - msg: 'No phases have been added. Cannot mint.', - }, - { - code: 6016, - name: 'invalidPhaseIndex', - msg: 'Invalid phase index.', - }, - { - code: 6017, - name: 'merkleRootNotSet', - msg: 'Merkle root not set for allow list mint', - }, - { - code: 6018, - name: 'merkleProofRequired', - msg: 'Merkle proof required for allow list mint', - }, - { - code: 6019, - name: 'allowListPriceAndMaxClaimsRequired', - msg: 'Allow list price and max claims are required for allow list mint', - }, - { - code: 6020, - name: 'invalidMerkleProof', - msg: 'Invalid merkle proof', - }, - { - code: 6021, - name: 'exceededAllowListMaxClaims', - msg: 'This wallet has exceeded allow list max_claims in the current phase', - }, - { - code: 6022, - name: 'phaseNotActive', - msg: 'Phase not active', - }, - { - code: 6023, - name: 'phaseNotStarted', - msg: 'Phase not yet started', - }, - { - code: 6024, - name: 'phaseAlreadyFinished', - msg: 'Phase already finished', - }, - { - code: 6025, - name: 'exceededMaxMintsForPhase', - msg: 'Exceeded max mints for this phase', - }, - { - code: 6026, - name: 'exceededWalletMaxMintsForPhase', - msg: 'Exceeded wallet max mints for this phase', - }, - { - code: 6027, - name: 'exceededWalletMaxMintsForCollection', - msg: 'Exceeded wallet max mints for the collection', - }, - ], - types: [ - { - name: 'addMetadataArgs', - type: { - kind: 'struct', - fields: [ - { - name: 'field', - type: 'string', - }, - { - name: 'value', - type: 'string', - }, - ], - }, - }, - { - name: 'creatorWithShare', - type: { - kind: 'struct', - fields: [ - { - name: 'address', - type: 'pubkey', - }, - { - name: 'share', - type: 'u8', - }, - ], - }, - }, - { - name: 'editionsControls', - type: { - kind: 'struct', - fields: [ - { - name: 'editionsDeployment', - type: 'pubkey', - }, - { - name: 'creator', - type: 'pubkey', - }, - { - name: 'treasury', - type: 'pubkey', - }, - { - name: 'maxMintsPerWallet', - type: 'u64', - }, - { - name: 'cosignerProgramId', - type: 'pubkey', - }, - { - name: 'platformFeePrimaryAdmin', - type: 'pubkey', - }, - { - name: 'platformFeeSecondaryAdmin', - type: 'pubkey', - }, - { - name: 'platformFeeValue', - type: 'u64', - }, - { - name: 'isFeeFlat', - type: 'bool', - }, - { - name: 'platformFeeRecipients', - type: { - array: [ - { - defined: { - name: 'platformFeeRecipient', - }, - }, - 5, - ], - }, - }, - { - name: 'phases', - type: { - vec: { - defined: { - name: 'phase', - }, - }, - }, - }, - { - name: 'padding', - type: { - array: ['u8', 200], - }, - }, - ], - }, - }, - { - name: 'editionsDeployment', - type: { - kind: 'struct', - fields: [ - { - name: 'creator', - type: 'pubkey', - }, - { - name: 'maxNumberOfTokens', - type: 'u64', - }, - { - name: 'numberOfTokensIssued', - type: 'u64', - }, - { - name: 'cosignerProgramId', - type: 'pubkey', - }, - { - name: 'groupMint', - type: 'pubkey', - }, - { - name: 'group', - type: 'pubkey', - }, - { - name: 'symbol', - type: 'string', - }, - { - name: 'collectionName', - type: 'string', - }, - { - name: 'collectionUri', - type: 'string', - }, - { - name: 'itemBaseName', - type: 'string', - }, - { - name: 'itemBaseUri', - type: 'string', - }, - { - name: 'itemNameIsTemplate', - type: 'bool', - }, - { - name: 'itemUriIsTemplate', - type: 'bool', - }, - { - name: 'padding', - type: { - array: ['u8', 98], - }, - }, - ], - }, - }, - { - name: 'initialiseControlInput', - type: { - kind: 'struct', - fields: [ - { - name: 'maxMintsPerWallet', - type: 'u64', - }, - { - name: 'treasury', - type: 'pubkey', - }, - { - name: 'maxNumberOfTokens', - type: 'u64', - }, - { - name: 'symbol', - type: 'string', - }, - { - name: 'collectionName', - type: 'string', - }, - { - name: 'collectionUri', - type: 'string', - }, - { - name: 'cosignerProgramId', - type: { - option: 'pubkey', - }, - }, - { - name: 'royalties', - type: { - defined: { - name: 'updateRoyaltiesArgs', - }, - }, - }, - { - name: 'extraMeta', - type: { - vec: { - defined: { - name: 'addMetadataArgs', - }, - }, - }, - }, - { - name: 'itemBaseUri', - type: 'string', - }, - { - name: 'itemBaseName', - type: 'string', - }, - { - name: 'platformFee', - type: { - defined: { - name: 'updatePlatformFeeArgs', - }, - }, - }, - ], - }, - }, - { - name: 'initialisePhaseInput', - type: { - kind: 'struct', - fields: [ - { - name: 'priceAmount', - type: 'u64', - }, - { - name: 'priceToken', - type: 'pubkey', - }, - { - name: 'startTime', - type: 'i64', - }, - { - name: 'maxMintsPerWallet', - type: 'u64', - }, - { - name: 'maxMintsTotal', - type: 'u64', - }, - { - name: 'endTime', - type: 'i64', - }, - { - name: 'merkleRoot', - type: { - option: { - array: ['u8', 32], - }, - }, - }, - ], - }, - }, - { - name: 'mintInput', - type: { - kind: 'struct', - fields: [ - { - name: 'phaseIndex', - type: 'u32', - }, - { - name: 'merkleProof', - type: { - option: { - vec: { - array: ['u8', 32], - }, - }, - }, - }, - { - name: 'allowListPrice', - type: { - option: 'u64', - }, - }, - { - name: 'allowListMaxClaims', - type: { - option: 'u64', - }, - }, - ], - }, - }, - { - name: 'minterStats', - type: { - kind: 'struct', - fields: [ - { - name: 'wallet', - type: 'pubkey', - }, - { - name: 'mintCount', - type: 'u64', - }, - { - name: 'padding', - type: { - array: ['u8', 50], - }, - }, - ], - }, - }, - { - name: 'phase', - type: { - kind: 'struct', - fields: [ - { - name: 'priceAmount', - type: 'u64', - }, - { - name: 'priceToken', - type: 'pubkey', - }, - { - name: 'startTime', - type: 'i64', - }, - { - name: 'active', - type: 'bool', - }, - { - name: 'maxMintsPerWallet', - type: 'u64', - }, - { - name: 'maxMintsTotal', - type: 'u64', - }, - { - name: 'endTime', - type: 'i64', - }, - { - name: 'currentMints', - type: 'u64', - }, - { - name: 'merkleRoot', - type: { - option: { - array: ['u8', 32], - }, - }, - }, - { - name: 'padding', - type: { - array: ['u8', 200], - }, - }, - ], - }, - }, - { - name: 'platformFeeRecipient', - type: { - kind: 'struct', - fields: [ - { - name: 'address', - type: 'pubkey', - }, - { - name: 'share', - type: 'u8', - }, - ], - }, - }, - { - name: 'updatePlatformFeeArgs', - type: { - kind: 'struct', - fields: [ - { - name: 'platformFeeValue', - type: 'u64', - }, - { - name: 'recipients', - type: { - vec: { - defined: { - name: 'platformFeeRecipient', - }, - }, - }, - }, - { - name: 'isFeeFlat', - type: 'bool', - }, - ], - }, - }, - { - name: 'updatePlatformFeeSecondaryAdminInput', - type: { - kind: 'struct', - fields: [ - { - name: 'newAdmin', - type: 'pubkey', - }, - ], - }, - }, - { - name: 'updateRoyaltiesArgs', - type: { - kind: 'struct', - fields: [ - { - name: 'royaltyBasisPoints', - type: 'u16', - }, - { - name: 'creators', - type: { - vec: { - defined: { - name: 'creatorWithShare', - }, - }, - }, - }, - ], - }, - }, - ], -}; \ No newline at end of file diff --git a/target/types/libreplex_monoswap.ts b/target/types/libreplex_monoswap.ts deleted file mode 100644 index 46e17f4a..00000000 --- a/target/types/libreplex_monoswap.ts +++ /dev/null @@ -1,467 +0,0 @@ -/** - * Program IDL in camelCase format in order to be used in JS/TS. - * - * Note that this is only a type helper and is not the actual IDL. The original - * IDL can be found at `target/idl/libreplex_monoswap.json`. - */ -export type LibreplexMonoswap = { - "address": "6taGmaEfZx27c25Pk5BwJbDCNNsirVranzWYAeNEhvHi", - "metadata": { - "name": "libreplexMonoswap", - "version": "0.0.0", - "spec": "0.1.0", - "description": "Created with Anchor", - "repository": "https://github.com/LibrePlex/metadata" - }, - "instructions": [ - { - "name": "createMonoswap", - "discriminator": [ - 98, - 245, - 160, - 178, - 252, - 5, - 159, - 225 - ], - "accounts": [ - { - "name": "swapMarker", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 119, - 97, - 112, - 95, - 109, - 97, - 114, - 107, - 101, - 114 - ] - }, - { - "kind": "account", - "path": "namespace" - }, - { - "kind": "account", - "path": "mintOutgoing" - }, - { - "kind": "account", - "path": "mintIncoming" - } - ] - } - }, - { - "name": "mintIncoming", - "writable": true - }, - { - "name": "mintOutgoing" - }, - { - "name": "mintOutgoingTokenAccountSource", - "writable": true - }, - { - "name": "escrowHolder", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 119, - 97, - 112, - 95, - 101, - 115, - 99, - 114, - 111, - 119 - ] - }, - { - "kind": "account", - "path": "namespace" - }, - { - "kind": "account", - "path": "mintIncoming" - } - ] - } - }, - { - "name": "mintOutgoingTokenAccountEscrow", - "writable": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "mintOutgoingOwner", - "writable": true, - "signer": true - }, - { - "name": "namespace", - "signer": true - }, - { - "name": "tokenProgram" - }, - { - "name": "associatedTokenProgram", - "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [ - { - "name": "input", - "type": { - "defined": { - "name": "createMonoSwapInput" - } - } - } - ] - }, - { - "name": "swap", - "discriminator": [ - 248, - 198, - 158, - 145, - 225, - 117, - 135, - 200 - ], - "accounts": [ - { - "name": "swapMarker", - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 119, - 97, - 112, - 95, - 109, - 97, - 114, - 107, - 101, - 114 - ] - }, - { - "kind": "account", - "path": "swap_marker.namespace", - "account": "swapMarker" - }, - { - "kind": "account", - "path": "mintOutgoing" - }, - { - "kind": "account", - "path": "mintIncoming" - } - ] - } - }, - { - "name": "swapMarkerReverse", - "docs": [ - "swapping always creates a symmetrical swap marker that enables a swap back" - ], - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 119, - 97, - 112, - 95, - 109, - 97, - 114, - 107, - 101, - 114 - ] - }, - { - "kind": "account", - "path": "swap_marker.namespace", - "account": "swapMarker" - }, - { - "kind": "account", - "path": "mintIncoming" - }, - { - "kind": "account", - "path": "mintOutgoing" - } - ] - } - }, - { - "name": "mintIncoming" - }, - { - "name": "mintOutgoing" - }, - { - "name": "mintIncomingTokenAccountSource", - "writable": true - }, - { - "name": "escrowHolder", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 119, - 97, - 112, - 95, - 101, - 115, - 99, - 114, - 111, - 119 - ] - }, - { - "kind": "account", - "path": "swap_marker.namespace", - "account": "swapMarker" - }, - { - "kind": "account", - "path": "mintIncoming" - } - ] - } - }, - { - "name": "escrowHolderReverse", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 115, - 119, - 97, - 112, - 95, - 101, - 115, - 99, - 114, - 111, - 119 - ] - }, - { - "kind": "account", - "path": "swap_marker.namespace", - "account": "swapMarker" - }, - { - "kind": "account", - "path": "mintOutgoing" - } - ] - } - }, - { - "name": "mintIncomingTokenAccountTarget", - "writable": true - }, - { - "name": "mintOutgoingTokenAccountSource", - "writable": true - }, - { - "name": "mintOutgoingTokenAccountTarget", - "writable": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "tokenProgram", - "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - }, - { - "name": "tokenProgram2022", - "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" - }, - { - "name": "associatedTokenProgram", - "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" - }, - { - "name": "systemProgram", - "address": "11111111111111111111111111111111" - } - ], - "args": [] - } - ], - "accounts": [ - { - "name": "swapMarker", - "discriminator": [ - 186, - 7, - 231, - 231, - 117, - 67, - 107, - 191 - ] - } - ], - "errors": [ - { - "code": 6000, - "name": "badMint", - "msg": "Metadata has a bad mint" - }, - { - "code": 6001, - "name": "cannotInscribeFungible", - "msg": "Cannot inscribe a fungible asset" - }, - { - "code": 6002, - "name": "badAuthority", - "msg": "Bad authority" - }, - { - "code": 6003, - "name": "badAuthorityForHolderInscription", - "msg": "Bad authority for holder inscription" - }, - { - "code": 6004, - "name": "badAuthorityForUpdateAuthInscription", - "msg": "Bad authority for update auth inscription" - }, - { - "code": 6005, - "name": "multiSigThresholdMustBeOne", - "msg": "Multi Signature threshold must be one to create / edit inscriptions" - }, - { - "code": 6006, - "name": "notSquadsMember", - "msg": "Not squads member" - }, - { - "code": 6007, - "name": "inscription2KeyMismatch", - "msg": "Inscription V2 key mismatch" - }, - { - "code": 6008, - "name": "inscriptionV3KeyMismatch", - "msg": "Inscription V3 key mismatch" - }, - { - "code": 6009, - "name": "dataHashMismatch", - "msg": "Metadata data missmatch" - } - ], - "types": [ - { - "name": "createMonoSwapInput", - "type": { - "kind": "struct", - "fields": [ - { - "name": "mintOutgoingAmount", - "type": "u64" - }, - { - "name": "mintIncomingAmount", - "type": "u64" - } - ] - } - }, - { - "name": "swapMarker", - "type": { - "kind": "struct", - "fields": [ - { - "name": "namespace", - "type": "pubkey" - }, - { - "name": "mintIncoming", - "type": "pubkey" - }, - { - "name": "mintOutgoing", - "type": "pubkey" - }, - { - "name": "mintIncomingAmount", - "type": "u64" - }, - { - "name": "mintOutgoingAmount", - "type": "u64" - }, - { - "name": "used", - "type": "bool" - } - ] - } - } - ] -}; diff --git a/test/editions_controls.test.ts b/test/editions_controls.test.ts index bff713f7..a716e9c1 100644 --- a/test/editions_controls.test.ts +++ b/test/editions_controls.test.ts @@ -11,15 +11,10 @@ import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID, + getTokenMetadata, } from '@solana/spl-token'; -import { - LibreplexEditionsControls, - IDL as EditionsControlsIDL, -} from '../../eclipse-program-library/target/types/libreplex_editions_controls'; -import { - LibreplexEditions, - IDL as EditionsIDL, -} from '../../eclipse-program-library/target/types/libreplex_editions'; +import { LibreplexEditionsControls } from '../../eclipse-program-library/target/types/libreplex_editions_controls'; +import { LibreplexEditions } from '../../eclipse-program-library/target/types/libreplex_editions'; import { expect } from 'chai'; import { describe, it } from 'mocha'; import { @@ -28,7 +23,8 @@ import { getEditionsControls, logEditions, logEditionsControls, - createErrorHandler, + logTokenMetadata, + parseMetadata, } from './utils'; import { Transaction } from '@solana/web3.js'; import { @@ -42,8 +38,6 @@ describe('Editions Controls Test Suite', () => { const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); - const handleError = createErrorHandler(EditionsIDL, EditionsControlsIDL); - let editionsControlsProgram: Program; let editionsProgram: Program; @@ -241,6 +235,12 @@ describe('Editions Controls Test Suite', () => { ); logEditionsControls(editionsControlsDecoded); + const metadata = await getTokenMetadata( + provider.connection, + groupMint.publicKey + ); + logTokenMetadata(metadata); + // Verify Editions deployment expect(editionsDecoded.data.symbol).to.equal(collectionConfig.symbol); expect(editionsDecoded.data.creator.toBase58()).to.equal( @@ -249,12 +249,6 @@ describe('Editions Controls Test Suite', () => { expect(editionsDecoded.data.maxNumberOfTokens.toString()).to.equal( collectionConfig.maxNumberOfTokens.toString() ); - expect(editionsDecoded.data.collectionName).to.equal( - collectionConfig.collectionName - ); - expect(editionsDecoded.data.collectionUri).to.equal( - collectionConfig.collectionUri - ); expect(editionsDecoded.data.itemBaseName).to.equal( collectionConfig.itemBaseName ); @@ -277,6 +271,17 @@ describe('Editions Controls Test Suite', () => { ); expect(editionsControlsDecoded.data.phases.length).to.equal(0); + // Verify metadata + const parsedMetadata = parseMetadata(metadata.additionalMetadata); + expect(metadata.name).to.equal(collectionConfig.collectionName); + expect(metadata.uri).to.equal(collectionConfig.collectionUri); + expect(metadata.mint.toBase58()).to.equal(groupMint.publicKey.toBase58()); + // Verify that every key in extraMeta is present in metadata.additionalMetadata + collectionConfig.extraMeta.forEach((meta) => { + expect(parsedMetadata).to.have.property(meta.field); + expect(parsedMetadata[meta.field]).to.equal(meta.value); + }); + // Add more assertions as needed } catch (error) { console.error('Error in initialiseEditionsControls:', error); @@ -330,6 +335,12 @@ describe('Editions Controls Test Suite', () => { ); logEditionsControls(editionsControlsDecoded); + const metadata = await getTokenMetadata( + provider.connection, + groupMint.publicKey + ); + logTokenMetadata(metadata); + // verify state expect(editionsControlsDecoded.data.phases.length).to.equal(1); expect( @@ -446,6 +457,12 @@ describe('Editions Controls Test Suite', () => { ); logEditionsControls(editionsControlsDecoded); + const metadata = await getTokenMetadata( + provider.connection, + groupMint.publicKey + ); + logTokenMetadata(metadata); + // verify state expect(editionsControlsDecoded.data.phases.length).to.equal(2); expect( @@ -578,7 +595,6 @@ describe('Editions Controls Test Suite', () => { console.log('\nTransaction signature:', txSignature, '\n'); console.log('\nmintWithControls done!\n'); } catch (error) { - handleError(error); console.error('Error in mintWithControls:', error); throw error; } diff --git a/test/utils.ts b/test/utils.ts index 99d908e4..6d2efe43 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,16 +1,16 @@ -import { - Connection, - clusterApiUrl, - PublicKey, - LAMPORTS_PER_SOL, -} from '@solana/web3.js'; +import { Connection, PublicKey } from '@solana/web3.js'; import { LibreplexEditions } from '../target/types/libreplex_editions'; -import { Idl, IdlAccounts, IdlTypes, AnchorError } from '@coral-xyz/anchor'; +import { IdlAccounts } from '@coral-xyz/anchor'; import { BorshCoder, Program } from '@coral-xyz/anchor'; import { LibreplexEditionsControls } from '../target/types/libreplex_editions_controls'; +import { TokenMetadata } from '@solana/spl-token-metadata'; + export type EditionsDeployment = IdlAccounts['editionsDeployment']; +export type EditionsControls = + IdlAccounts['editionsControls']; + export async function getCluster(connection: Connection): Promise { // Get the genesis hash const genesisHash = await connection.getGenesisHash(); @@ -29,9 +29,6 @@ export async function getCluster(connection: Connection): Promise { } } -export type EditionsControls = - IdlAccounts['editionsControls']; - export const getBase64FromDatabytes = (dataBytes: Buffer, dataType: string) => { console.log({ dataBytes }); const base = dataBytes.toString('base64'); @@ -52,20 +49,6 @@ export const decodeEditions = }; }; -export const decodeEditionsControls = - (program: Program) => - (buffer: Buffer | undefined, pubkey: PublicKey) => { - const coder = new BorshCoder(program.idl); - const data = buffer - ? coder.accounts.decode('editionsControls', buffer) - : null; - - return { - data, - pubkey, - }; - }; - export const getEditions = async ( connection: Connection, editionsPda: PublicKey, @@ -82,25 +65,6 @@ export const getEditions = async ( return editionsDecoded; }; -export const getEditionsControls = async ( - connection: Connection, - editionsControlsPda: PublicKey, - editionsControlsProgram: Program -) => { - const editionsControlsAccountInfo = await connection.getAccountInfo( - editionsControlsPda - ); - if (!editionsControlsAccountInfo) { - throw new Error( - 'EditionsControls account not found. The collection was not initialized with controls.' - ); - } - const editionsControlsDecoded = decodeEditionsControls( - editionsControlsProgram - )(editionsControlsAccountInfo.data, editionsControlsPda); - return editionsControlsDecoded; -}; - export const logEditions = (editionsDecoded: { data: EditionsDeployment; pubkey: PublicKey; @@ -114,8 +78,6 @@ export const logEditions = (editionsDecoded: { cosignerProgramId: editionsDecoded.data.cosignerProgramId ? editionsDecoded.data.cosignerProgramId.toBase58() : null, - collectionName: editionsDecoded.data.collectionName, - collectionUri: editionsDecoded.data.collectionUri, tokensMinted: editionsDecoded.data.numberOfTokensIssued.toString(), itemBaseName: editionsDecoded.data.itemBaseName, itemBaseUri: editionsDecoded.data.itemBaseUri, @@ -125,6 +87,39 @@ export const logEditions = (editionsDecoded: { }); }; +export const decodeEditionsControls = + (program: Program) => + (buffer: Buffer | undefined, pubkey: PublicKey) => { + const coder = new BorshCoder(program.idl); + const data = buffer + ? coder.accounts.decode('editionsControls', buffer) + : null; + + return { + data, + pubkey, + }; + }; + +export const getEditionsControls = async ( + connection: Connection, + editionsControlsPda: PublicKey, + editionsControlsProgram: Program +) => { + const editionsControlsAccountInfo = await connection.getAccountInfo( + editionsControlsPda + ); + if (!editionsControlsAccountInfo) { + throw new Error( + 'EditionsControls account not found. The collection was not initialized with controls.' + ); + } + const editionsControlsDecoded = decodeEditionsControls( + editionsControlsProgram + )(editionsControlsAccountInfo.data, editionsControlsPda); + return editionsControlsDecoded; +}; + export const logEditionsControls = (editionsControlsDecoded: { data: EditionsControls; pubkey: PublicKey; @@ -152,55 +147,25 @@ export const logEditionsControls = (editionsControlsDecoded: { }); }; -export async function ensureAccountHasSol( - connection: Connection, - account: PublicKey, - minBalance: number -) { - let balance = await connection.getBalance(account); - console.log( - `Initial balance of ${account.toBase58()}: ${ - balance / LAMPORTS_PER_SOL - } SOL` - ); - - if (balance < minBalance) { - const airdropAmount = minBalance - balance; - const signature = await connection.requestAirdrop(account, airdropAmount); - await connection.confirmTransaction(signature, 'confirmed'); - balance = await connection.getBalance(account); - console.log(`New balance after airdrop: ${balance / LAMPORTS_PER_SOL} SOL`); - } -} - -export function createErrorHandler(...idls: Idl[]) { - return function handleError(error: unknown): void { - console.error('Error occurred:', error); +export const logTokenMetadata = (metadata: TokenMetadata) => { + console.log({ + TokenMetadata: { + name: metadata.name, + symbol: metadata.symbol, + uri: metadata.uri, + updateAuthority: metadata.updateAuthority.toBase58(), + mint: metadata.mint.toBase58(), + additionalMetadata: parseMetadata(metadata.additionalMetadata), + }, + }); +}; - if (error instanceof AnchorError) { - const errorCode = error.error.errorCode.number; - for (const idl of idls) { - const errorMessage = getErrorMessage(errorCode, idl); - if (errorMessage !== `Unknown error: ${errorCode}`) { - console.error('Error code:', errorCode); - console.error('Error message:', errorMessage); - console.error('Error name:', error.error.errorCode.code); - console.error('Program ID:', error.program.programId.toBase58()); - return; - } - } - console.error(`Unknown error code: ${errorCode}`); - } else if (error instanceof Error) { - console.error('Error message:', error.message); - console.error('Stack trace:', error.stack); - } else { - console.error('Unexpected error:', error); - } +export const parseMetadata = ( + rawMetadata: (readonly [string, string])[] +): Record => { + const metadata: Record = {}; + for (const [key, value] of rawMetadata) { + metadata[key] = value; } -} - -function getErrorMessage(code: number, idl: Idl): string { - const idlErrors = idl.errors ?? []; - const error = idlErrors.find((e) => e.code === code); - return error ? error.msg : `Unknown error: ${code}`; -} + return metadata; +}; diff --git a/yarn.lock b/yarn.lock index 2f896775..1b4c1879 100644 --- a/yarn.lock +++ b/yarn.lock @@ -510,13 +510,164 @@ dependencies: buffer "~6.0.3" -"@solana/spl-token@0.3.8", "@solana/spl-token@^0.3.8": - version "0.3.8" - resolved "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.8.tgz" - integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg== +"@solana/codecs-core@2.0.0-preview.4": + version "2.0.0-preview.4" + resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-preview.4.tgz#770826105f2f884110a21662573e7a2014654324" + integrity sha512-A0VVuDDA5kNKZUinOqHxJQK32aKTucaVbvn31YenGzHX1gPqq+SOnFwgaEY6pq4XEopSmaK16w938ZQS8IvCnw== + dependencies: + "@solana/errors" "2.0.0-preview.4" + +"@solana/codecs-core@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz#1a2d76b9c7b9e7b7aeb3bd78be81c2ba21e3ce22" + integrity sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ== + dependencies: + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-data-structures@2.0.0-preview.4": + version "2.0.0-preview.4" + resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-preview.4.tgz#f8a2470982a9792334737ea64000ccbdff287247" + integrity sha512-nt2k2eTeyzlI/ccutPcG36M/J8NAYfxBPI9h/nQjgJ+M+IgOKi31JV8StDDlG/1XvY0zyqugV3I0r3KAbZRJpA== + dependencies: + "@solana/codecs-core" "2.0.0-preview.4" + "@solana/codecs-numbers" "2.0.0-preview.4" + "@solana/errors" "2.0.0-preview.4" + +"@solana/codecs-data-structures@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz#d47b2363d99fb3d643f5677c97d64a812982b888" + integrity sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-numbers@2.0.0-preview.4": + version "2.0.0-preview.4" + resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-preview.4.tgz#6a53b456bb7866f252d8c032c81a92651e150f66" + integrity sha512-Q061rLtMadsO7uxpguT+Z7G4UHnjQ6moVIxAQxR58nLxDPCC7MB1Pk106/Z7NDhDLHTcd18uO6DZ7ajHZEn2XQ== + dependencies: + "@solana/codecs-core" "2.0.0-preview.4" + "@solana/errors" "2.0.0-preview.4" + +"@solana/codecs-numbers@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz#f34978ddf7ea4016af3aaed5f7577c1d9869a614" + integrity sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-strings@2.0.0-preview.4": + version "2.0.0-preview.4" + resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-preview.4.tgz#4d06bb722a55a5d04598d362021bfab4bd446760" + integrity sha512-YDbsQePRWm+xnrfS64losSGRg8Wb76cjK1K6qfR8LPmdwIC3787x9uW5/E4icl/k+9nwgbIRXZ65lpF+ucZUnw== + dependencies: + "@solana/codecs-core" "2.0.0-preview.4" + "@solana/codecs-numbers" "2.0.0-preview.4" + "@solana/errors" "2.0.0-preview.4" + +"@solana/codecs-strings@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz#e1d9167075b8c5b0b60849f8add69c0f24307018" + integrity sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs@2.0.0-preview.4": + version "2.0.0-preview.4" + resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-preview.4.tgz#a1923cc78a6f64ebe656c7ec6335eb6b70405b22" + integrity sha512-gLMupqI4i+G4uPi2SGF/Tc1aXcviZF2ybC81x7Q/fARamNSgNOCUUoSCg9nWu1Gid6+UhA7LH80sWI8XjKaRog== + dependencies: + "@solana/codecs-core" "2.0.0-preview.4" + "@solana/codecs-data-structures" "2.0.0-preview.4" + "@solana/codecs-numbers" "2.0.0-preview.4" + "@solana/codecs-strings" "2.0.0-preview.4" + "@solana/options" "2.0.0-preview.4" + +"@solana/codecs@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-rc.1.tgz#146dc5db58bd3c28e04b4c805e6096c2d2a0a875" + integrity sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/options" "2.0.0-rc.1" + +"@solana/errors@2.0.0-preview.4": + version "2.0.0-preview.4" + resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-preview.4.tgz#056ba76b6dd900dafa70117311bec3aef0f5250b" + integrity sha512-kadtlbRv2LCWr8A9V22On15Us7Nn8BvqNaOB4hXsTB3O0fU40D1ru2l+cReqLcRPij4znqlRzW9Xi0m6J5DIhA== + dependencies: + chalk "^5.3.0" + commander "^12.1.0" + +"@solana/errors@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-rc.1.tgz#3882120886eab98a37a595b85f81558861b29d62" + integrity sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ== + dependencies: + chalk "^5.3.0" + commander "^12.1.0" + +"@solana/options@2.0.0-preview.4": + version "2.0.0-preview.4" + resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-preview.4.tgz#212d35d1da87c7efb13de4d3569ad9eb070f013d" + integrity sha512-tv2O/Frxql/wSe3jbzi5nVicIWIus/BftH+5ZR+r9r3FO0/htEllZS5Q9XdbmSboHu+St87584JXeDx3xm4jaA== + dependencies: + "@solana/codecs-core" "2.0.0-preview.4" + "@solana/codecs-data-structures" "2.0.0-preview.4" + "@solana/codecs-numbers" "2.0.0-preview.4" + "@solana/codecs-strings" "2.0.0-preview.4" + "@solana/errors" "2.0.0-preview.4" + +"@solana/options@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-rc.1.tgz#06924ba316dc85791fc46726a51403144a85fc4d" + integrity sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/spl-token-group@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@solana/spl-token-group/-/spl-token-group-0.0.5.tgz#f955dcca782031c85e862b2b46878d1bb02db6c2" + integrity sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ== + dependencies: + "@solana/codecs" "2.0.0-preview.4" + "@solana/spl-type-length-value" "0.1.0" + +"@solana/spl-token-metadata@^0.1.3", "@solana/spl-token-metadata@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.5.tgz#91616470d6862ec6b762e6cfcf882b8a8a24b1e8" + integrity sha512-DSBlo7vjuLe/xvNn75OKKndDBkFxlqjLdWlq6rf40StnrhRn7TDntHGLZpry1cf3uzQFShqeLROGNPAJwvkPnA== + dependencies: + "@solana/codecs" "2.0.0-rc.1" + "@solana/spl-type-length-value" "0.1.0" + +"@solana/spl-token@^0.4.8": + version "0.4.8" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.8.tgz#a84e4131af957fa9fbd2727e5fc45dfbf9083586" + integrity sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA== dependencies: "@solana/buffer-layout" "^4.0.0" "@solana/buffer-layout-utils" "^0.2.0" + "@solana/spl-token-group" "^0.0.5" + "@solana/spl-token-metadata" "^0.1.3" + buffer "^6.0.3" + +"@solana/spl-type-length-value@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@solana/spl-type-length-value/-/spl-type-length-value-0.1.0.tgz#b5930cf6c6d8f50c7ff2a70463728a4637a2f26b" + integrity sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA== + dependencies: buffer "^6.0.3" "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.73.0": @@ -1059,6 +1210,11 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + chardet@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" @@ -1202,6 +1358,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + commander@^2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" @@ -1934,18 +2095,6 @@ glob@^10.2.2: minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-scurry "^1.10.1" -glob@^7.0.0: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.1.3, glob@^7.1.4: version "7.1.7" resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" @@ -2221,11 +2370,6 @@ inquirer@^8.2.4: through "^2.3.6" wrap-ansi "^6.0.1" -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== - ip@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz" @@ -2257,13 +2401,6 @@ is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.8.1: dependencies: has "^1.0.3" -is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" @@ -2897,7 +3034,7 @@ minimatch@4.2.1: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2934,7 +3071,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -3824,13 +3961,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - redent@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" @@ -3866,15 +3996,6 @@ resolve-from@^4.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.1.6: - version "1.22.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" - integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - resolve@^1.10.0: version "1.22.2" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz" @@ -4011,23 +4132,6 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shelljs@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -shx@^0.3.4: - version "0.3.4" - resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02" - integrity sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g== - dependencies: - minimist "^1.2.3" - shelljs "^0.8.5" - signal-exit@3.0.7, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" @@ -4163,7 +4267,16 @@ ssri@^9.0.1: dependencies: minipass "^3.1.1" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4195,7 +4308,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4598,7 +4718,7 @@ workerpool@6.2.0: resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz" integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -4616,6 +4736,15 @@ wrap-ansi@^6.0.1: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" From 8a628449fa97bbbebb1002e809e25b9fff3f84de Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Thu, 10 Oct 2024 17:48:53 -0300 Subject: [PATCH 20/29] Implemented minting state test, minter stats pulling --- Anchor.toml | 6 +- constants.ts | 4 +- programs/libreplex_editions/src/lib.rs | 2 +- .../libreplex_editions_controls/src/lib.rs | 2 +- target/types/libreplex_editions.ts | 52 +++--- target/types/libreplex_editions_controls.ts | 10 +- test/editions_controls.test.ts | 156 ++++++++++-------- test/utils.ts | 60 +++++++ 8 files changed, 186 insertions(+), 106 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 951daf14..55040af6 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -8,8 +8,8 @@ skip-lint = false libreplex_creator = "78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM" libreplex_creator_controls = "G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV" libreplex_default_renderer = "7oHj4YFq4JhxVc1BvHqvZma5evSn8hkXwFHPoTQ1PB4f" -libreplex_editions = "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" -libreplex_editions_controls = "8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco" +libreplex_editions = "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD" +libreplex_editions_controls = "CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN" libreplex_inscriptions = "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" libreplex_legacy_inscribers = "Leg1xVbrpq5gY6mprak3Ud4q4mBwcJi5C9ZruYjWv7n" libreplex_metadata = "LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p" @@ -51,4 +51,4 @@ address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" [[test.validator.clone]] -address = "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" \ No newline at end of file +address = "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" diff --git a/constants.ts b/constants.ts index 003ad932..c2229207 100644 --- a/constants.ts +++ b/constants.ts @@ -1,6 +1,6 @@ export const EDITIONS_PROGRAM_ID = - 'GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp'; + 'GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD'; export const EDITIONS_CONTROLS_PROGRAM_ID = - '8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco'; + 'CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN'; export const TOKEN_GROUP_EXTENSION_PROGRAM_ID = '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'; diff --git a/programs/libreplex_editions/src/lib.rs b/programs/libreplex_editions/src/lib.rs index 96097643..6864fcdc 100644 --- a/programs/libreplex_editions/src/lib.rs +++ b/programs/libreplex_editions/src/lib.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; pub mod instructions; pub use instructions::*; -declare_id!("GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp"); +declare_id!("GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD"); pub mod errors; pub mod state; diff --git a/programs/libreplex_editions_controls/src/lib.rs b/programs/libreplex_editions_controls/src/lib.rs index 228f7068..c8dd0375 100644 --- a/programs/libreplex_editions_controls/src/lib.rs +++ b/programs/libreplex_editions_controls/src/lib.rs @@ -5,7 +5,7 @@ pub use logic::*; pub mod instructions; pub use instructions::*; -declare_id!("8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco"); +declare_id!("CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN"); pub mod errors; pub mod state; diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index f6869a02..669cdb5a 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp", + "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD", "metadata": { "name": "libreplexEditions", "version": "0.2.1", @@ -694,53 +694,63 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "tickerTooLong", - "msg": "Ticker too long" + "name": "sizeExceedsMaxSize", + "msg": "Collection size exceeds max size." }, { "code": 6001, - "name": "mintTemplateTooLong", - "msg": "Mint template too long" + "name": "maxSizeBelowCurrentSize", + "msg": "Max size cannot be reduced below current size." }, { "code": 6002, - "name": "deploymentTemplateTooLong", - "msg": "Deployment template too long" + "name": "creatorShareInvalid", + "msg": "Creators shares must add up to 100." }, { "code": 6003, - "name": "rootTypeTooLong", - "msg": "Root type too long" + "name": "missingApproveAccount", + "msg": "Missing approve account." }, { "code": 6004, - "name": "mintedOut", - "msg": "Minted out" + "name": "expiredApproveAccount", + "msg": "Approve account has expired." }, { "code": 6005, - "name": "legacyMigrationsAreMintedOut", - "msg": "Legacy migrations are minted out" + "name": "invalidField", + "msg": "Invalid field. You cannot use a public key as a field." }, { "code": 6006, - "name": "missingGlobalTreeDelegate", - "msg": "Global tree delegate is missing" + "name": "creatorAddressInvalid", + "msg": "The Address you provided is invalid. Please provide a valid address." }, { "code": 6007, - "name": "incorrectMintType", - "msg": "Incorrect mint type" + "name": "royaltyBasisPointsInvalid", + "msg": "Royalty basis points must be less than or equal to 10000." }, { "code": 6008, - "name": "invalidMetadata", - "msg": "Invalid Metadata" + "name": "platformFeeBasisPointsInvalid", + "msg": "Platform fee basis points must be less than or equal to 10000." }, { "code": 6009, - "name": "creatorFeeTooHigh", - "msg": "Creator fee too high" + "name": "recipientShareInvalid", + "msg": "Recipient shares must add up to 100." + }, + { + "code": 6010, + "name": "reservedField", + "msg": "The provided field is invalid or reserved." + }, + { + "code": 6011, + "name": "invalidNumberOfRecipients", + "msg": "Invalid number of platform fee recipients. Exactly 5 recipients are required." } ], "types": [ diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index fece7a7d..27d591c0 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions_controls.json`. */ export type LibreplexEditionsControls = { - "address": "8FEBY2DomLgXkBU8Le4eYRUSTxTh2AeSubvmVV6dxnco", + "address": "CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN", "metadata": { "name": "libreplexEditionsControls", "version": "0.2.1", @@ -51,7 +51,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" + "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD" } ], "args": [ @@ -152,7 +152,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" + "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD" } ], "args": [ @@ -365,7 +365,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" + "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD" } ], "args": [ @@ -595,7 +595,7 @@ export type LibreplexEditionsControls = { }, { "name": "libreplexEditionsProgram", - "address": "GWvPUJ7j4XzT1AJnHn7UkmJbodRkvCay2yLwVQcN2YEp" + "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD" } ], "args": [ diff --git a/test/editions_controls.test.ts b/test/editions_controls.test.ts index a716e9c1..2583aba3 100644 --- a/test/editions_controls.test.ts +++ b/test/editions_controls.test.ts @@ -21,8 +21,11 @@ import { getCluster, getEditions, getEditionsControls, + getMinterStats, logEditions, logEditionsControls, + logMinterStats, + logMinterStatsPhase, logTokenMetadata, parseMetadata, } from './utils'; @@ -34,6 +37,8 @@ import { } from '../constants'; import { toBufferLE } from 'bigint-buffer'; +const VERBOSE_LOGGING = true; + describe('Editions Controls Test Suite', () => { const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); @@ -89,8 +94,10 @@ describe('Editions Controls Test Suite', () => { }; before(async () => { - const cluster = await getCluster(provider.connection); - console.log('Cluster:', cluster); + if (VERBOSE_LOGGING) { + const cluster = await getCluster(provider.connection); + console.log('Cluster:', cluster); + } editionsControlsProgram = anchor.workspace .LibreplexEditionsControls as Program; @@ -145,7 +152,10 @@ describe('Editions Controls Test Suite', () => { itemBaseName: 'Item T8 V4 #{}', cosignerProgramId: null, }; - console.log('Collection config: ', collectionConfig); + + if (VERBOSE_LOGGING) { + console.log('Collection config: ', collectionConfig); + } [editionsPda] = PublicKey.findProgramAddressSync( [ @@ -172,7 +182,6 @@ describe('Editions Controls Test Suite', () => { units: 800000, }); - console.log('\nDeploying via initialiseEditionsControls...\n'); try { const initialiseIx = await editionsControlsProgram.methods .initialiseEditionsControls({ @@ -210,36 +219,29 @@ describe('Editions Controls Test Suite', () => { .add(modifyComputeUnits) .add(initialiseIx); - const txSignature = await provider.sendAndConfirm(transaction, [ - groupMint, - group, - payer, - ]); + await provider.sendAndConfirm(transaction, [groupMint, group, payer]); - console.log('\nTransaction signature:', txSignature, '\n'); - console.log('\ninitialiseEditionsControls done!\n'); - - console.log('\nFetching and displaying deployed collection state...\n'); + // Fetch updated state const editionsDecoded = await getEditions( provider.connection, editionsPda, editionsProgram ); - logEditions(editionsDecoded); - - // Fetch and check the EditionsControls account const editionsControlsDecoded = await getEditionsControls( provider.connection, editionsControlsPda, editionsControlsProgram ); - logEditionsControls(editionsControlsDecoded); - const metadata = await getTokenMetadata( provider.connection, groupMint.publicKey ); - logTokenMetadata(metadata); + + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logTokenMetadata(metadata); + } // Verify Editions deployment expect(editionsDecoded.data.symbol).to.equal(collectionConfig.symbol); @@ -315,10 +317,7 @@ describe('Editions Controls Test Suite', () => { .instruction(); const transaction = new Transaction().add(phaseIx); - const txSignature = await provider.sendAndConfirm(transaction, [payer]); - - console.log('\nTransaction signature:', txSignature, '\n'); - console.log('\naddPhase done!\n'); + await provider.sendAndConfirm(transaction, [payer]); // get state const editionsDecoded = await getEditions( @@ -326,20 +325,15 @@ describe('Editions Controls Test Suite', () => { editionsPda, editionsProgram ); - logEditions(editionsDecoded); - const editionsControlsDecoded = await getEditionsControls( provider.connection, editionsControlsPda, editionsControlsProgram ); - logEditionsControls(editionsControlsDecoded); - - const metadata = await getTokenMetadata( - provider.connection, - groupMint.publicKey - ); - logTokenMetadata(metadata); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + } // verify state expect(editionsControlsDecoded.data.phases.length).to.equal(1); @@ -351,9 +345,8 @@ describe('Editions Controls Test Suite', () => { ).to.equal(phaseConfig.maxMintsTotal.toString()); }); - // ADD PHASE WITH ALLOWLIST + // Generate allowlist variables before(async () => { - console.log('Creating minters...'); minter1 = Keypair.fromSecretKey( new Uint8Array([ 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, @@ -363,7 +356,6 @@ describe('Editions Controls Test Suite', () => { 221, 120, 54, ]) ); - console.log('minter1: ', minter1.publicKey.toBase58()); minter2 = Keypair.fromSecretKey( new Uint8Array([ 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, @@ -373,9 +365,6 @@ describe('Editions Controls Test Suite', () => { 135, 6, ]) ); - console.log('minter2: ', minter2.publicKey.toBase58()); - - console.log('Creating allowlist...'); allowListConfig = { merkleRoot: Buffer.from([ 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, @@ -409,7 +398,6 @@ describe('Editions Controls Test Suite', () => { }, ], }; - console.log('Allowlist created'); }); it('Should add a phase with allowlist', async () => { @@ -437,31 +425,22 @@ describe('Editions Controls Test Suite', () => { .instruction(); const transaction = new Transaction().add(phaseIx); - const txSignature = await provider.sendAndConfirm(transaction, [payer]); + await provider.sendAndConfirm(transaction, [payer]); - console.log('\nTransaction signature:', txSignature, '\n'); - console.log('\naddPhase done!\n'); - - // get state const editionsDecoded = await getEditions( provider.connection, editionsPda, editionsProgram ); - logEditions(editionsDecoded); - const editionsControlsDecoded = await getEditionsControls( provider.connection, editionsControlsPda, editionsControlsProgram ); - logEditionsControls(editionsControlsDecoded); - - const metadata = await getTokenMetadata( - provider.connection, - groupMint.publicKey - ); - logTokenMetadata(metadata); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + } // verify state expect(editionsControlsDecoded.data.phases.length).to.equal(2); @@ -473,33 +452,33 @@ describe('Editions Controls Test Suite', () => { ).to.equal(phaseConfig.maxMintsTotal.toString()); }); - // mint on first allow list - it('Should mint on first phase without allowlist', async () => { + before(async () => { // Airdrop SOL to minter1 const airdropSignature = await provider.connection.requestAirdrop( minter1.publicKey, - 10 * LAMPORTS_PER_SOL + 1 * LAMPORTS_PER_SOL ); await provider.connection.confirmTransaction(airdropSignature); // Airdrop SOL to treasury const treasuryAirdropSignature = await provider.connection.requestAirdrop( collectionConfig.treasury, - 10 * LAMPORTS_PER_SOL + 1 * LAMPORTS_PER_SOL ); await provider.connection.confirmTransaction(treasuryAirdropSignature); - let minterBalance = await provider.connection.getBalance(minter1.publicKey); - console.log('minterBalance: ', minterBalance / LAMPORTS_PER_SOL); - - let payerBalance = await provider.connection.getBalance(payer.publicKey); - console.log('payerBalance: ', payerBalance / LAMPORTS_PER_SOL); - - let treasuryBalance = await provider.connection.getBalance( - collectionConfig.treasury + // Airdrop SOL to platformFeeRecipient + const platformFeeRecipientAirdropSignature = + await provider.connection.requestAirdrop( + platformFeeAdmin.publicKey, + 1 * LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction( + platformFeeRecipientAirdropSignature ); - console.log('treasuryBalance: ', treasuryBalance / LAMPORTS_PER_SOL); + }); + it('Should mint on first phase without allowlist', async () => { const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ units: 850000, }); @@ -508,7 +487,7 @@ describe('Editions Controls Test Suite', () => { phaseIndex: 0, merkleProof: null, allowListPrice: null, - allowListMint: null, + allowListMaxClaims: null, }; const mint = Keypair.generate(); @@ -585,17 +564,26 @@ describe('Editions Controls Test Suite', () => { const rawTransaction = transaction.serialize(); try { - const txSignature = await provider.connection.sendRawTransaction( + const signature = await provider.connection.sendRawTransaction( rawTransaction, { skipPreflight: false, preflightCommitment: 'confirmed', } ); - console.log('\nTransaction signature:', txSignature, '\n'); + await provider.connection.confirmTransaction(signature); + console.log('\nmintWithControls done!\n'); } catch (error) { - console.error('Error in mintWithControls:', error); + if (error.logs) { + console.error('Full error logs:'); + error.logs.forEach((log, index) => { + console.error(`${index + 1}: ${log}`); + }); + } else { + console.error(error); + } + throw error; } @@ -605,18 +593,40 @@ describe('Editions Controls Test Suite', () => { editionsPda, editionsProgram ); - logEditions(editionsDecoded); - const editionsControlsDecoded = await getEditionsControls( provider.connection, editionsControlsPda, editionsControlsProgram ); - logEditionsControls(editionsControlsDecoded); + const minterStatsDecoded = await getMinterStats( + provider.connection, + minterStats, + editionsControlsProgram + ); + const minterStatsPhaseDecoded = await getMinterStats( + provider.connection, + minterStatsPhase, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logMinterStats(minterStatsDecoded); + logMinterStatsPhase(minterStatsPhaseDecoded); + } // verify state expect( editionsControlsDecoded.data.phases[0].currentMints.toString() ).to.equal(new anchor.BN(1).toString()); + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( + new anchor.BN(1).toString() + ); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal( + new anchor.BN(1).toString() + ); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal( + new anchor.BN(1).toString() + ); }); }); diff --git a/test/utils.ts b/test/utils.ts index 6d2efe43..20974cc1 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -11,6 +11,8 @@ export type EditionsDeployment = export type EditionsControls = IdlAccounts['editionsControls']; +export type MinterStats = IdlAccounts['minterStats']; + export async function getCluster(connection: Connection): Promise { // Get the genesis hash const genesisHash = await connection.getGenesisHash(); @@ -169,3 +171,61 @@ export const parseMetadata = ( } return metadata; }; + +export const decodeMinterStats = + (program: Program) => + (buffer: Buffer | undefined, pubkey: PublicKey) => { + const coder = new BorshCoder(program.idl); + const data = buffer + ? coder.accounts.decode('minterStats', buffer) + : null; + + return { + data, + pubkey, + }; + }; + +export const getMinterStats = async ( + connection: Connection, + minterStatsPda: PublicKey, + editionsControlsProgram: Program +) => { + const minterStatsAccountInfo = await connection.getAccountInfo( + minterStatsPda + ); + if (!minterStatsAccountInfo) { + throw new Error('MinterStats account not found'); + } + const minterStatsDecoded = decodeMinterStats(editionsControlsProgram)( + minterStatsAccountInfo.data, + minterStatsPda + ); + return minterStatsDecoded; +}; + +export const logMinterStats = (minterStatsDecoded: { + data: MinterStats; + pubkey: PublicKey; +}) => { + console.log({ + MinterStats: { + address: minterStatsDecoded.pubkey.toBase58(), + wallet: minterStatsDecoded.data.wallet.toBase58(), + mintCount: minterStatsDecoded.data.mintCount.toString(), + }, + }); +}; + +export const logMinterStatsPhase = (minterStatsDecoded: { + data: MinterStats; + pubkey: PublicKey; +}) => { + console.log({ + MinterStatsPhase: { + address: minterStatsDecoded.pubkey.toBase58(), + wallet: minterStatsDecoded.data.wallet.toBase58(), + mintCount: minterStatsDecoded.data.mintCount.toString(), + }, + }); +}; From a93b95732b25ce790fdcf461c867394b460599b2 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Thu, 10 Oct 2024 18:02:20 -0300 Subject: [PATCH 21/29] Add test for minting with allowlist, including verification --- test/editions_controls.test.ts | 149 +++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/test/editions_controls.test.ts b/test/editions_controls.test.ts index 2583aba3..a02bff5f 100644 --- a/test/editions_controls.test.ts +++ b/test/editions_controls.test.ts @@ -629,4 +629,153 @@ describe('Editions Controls Test Suite', () => { new anchor.BN(1).toString() ); }); + + it('Should mint on second phase with allowlist', async () => { + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 850000, + }); + + const mintConfig = { + phaseIndex: 1, // Use the second phase (index 1) + merkleProof: allowListConfig.list[0].proof, // Use the proof for minter1 + allowListPrice: allowListConfig.list[0].price, + allowListMaxClaims: allowListConfig.list[0].max_claims, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const tokenAccount = getAssociatedTokenAddressSync( + mint.publicKey, + minter1.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + + const [hashlistMarker] = PublicKey.findProgramAddressSync( + [ + Buffer.from('hashlist_marker'), + editionsPda.toBuffer(), + mint.publicKey.toBuffer(), + ], + new PublicKey(EDITIONS_PROGRAM_ID) + ); + + const [minterStats] = PublicKey.findProgramAddressSync( + [ + Buffer.from('minter_stats'), + editionsPda.toBuffer(), + minter1.publicKey.toBuffer(), + ], + new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) + ); + + const [minterStatsPhase] = PublicKey.findProgramAddressSync( + [ + Buffer.from('minter_stats_phase'), + editionsPda.toBuffer(), + minter1.publicKey.toBuffer(), + toBufferLE(BigInt(1), 4), // Use phase index 1 + ], + new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) + ); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker, + payer: minter1.publicKey, + mint: mint.publicKey, + member: member.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats, + minterStatsPhase, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + groupExtensionProgram: new PublicKey(TOKEN_GROUP_EXTENSION_PROGRAM_ID), + tokenAccount, + treasury: collectionConfig.treasury, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }) + .instruction(); + + const transaction = new Transaction().add(modifyComputeUnits).add(mintIx); + transaction.recentBlockhash = ( + await provider.connection.getLatestBlockhash() + ).blockhash; + transaction.feePayer = minter1.publicKey; + transaction.sign(minter1, mint, member); + const rawTransaction = transaction.serialize(); + + try { + const signature = await provider.connection.sendRawTransaction( + rawTransaction, + { + skipPreflight: false, + preflightCommitment: 'confirmed', + } + ); + // wait for transaction to be confirmed + await provider.connection.confirmTransaction(signature); + + console.log('\nmintWithControls on allowlist phase done!\n'); + + // Fetch updated state + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + const minterStatsDecoded = await getMinterStats( + provider.connection, + minterStats, + editionsControlsProgram + ); + const minterStatsPhaseDecoded = await getMinterStats( + provider.connection, + minterStatsPhase, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logMinterStats(minterStatsDecoded); + logMinterStatsPhase(minterStatsPhaseDecoded); + } + + // Verify state + expect( + editionsControlsDecoded.data.phases[1].currentMints.toString() + ).to.equal('1'); + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( + '2' + ); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + + console.log('Allowlist mint verified successfully'); + } catch (error) { + console.error('Error in allowlist minting:', error); + if (error.logs) { + console.error('Full error logs:'); + error.logs.forEach((log, index) => { + console.error(`${index + 1}: ${log}`); + }); + } + throw error; + } + }); }); From fd0360bedcbaa4ec5877b9560709e3e6f867e1db Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Fri, 11 Oct 2024 15:42:25 -0300 Subject: [PATCH 22/29] advances on testing suite structure, patched check_allow_list_contraints.rs bug --- Anchor.toml | 3 +- .../src/instructions/mint_with_controls.rs | 8 +- .../src/logic/check_allow_list_constraints.rs | 7 +- target/types/libreplex_editions.ts | 2 +- test/data/merkle_tree.json | 175 +-- test/editions_controls.test.ts | 781 ------------- test/tests/editions_controls.test.ts | 1013 +++++++++++++++++ test/{utils.ts => utils/getters.ts} | 77 +- test/utils/pdas.ts | 76 ++ test/utils/types.ts | 34 + test/utils/utils.ts | 26 + 11 files changed, 1214 insertions(+), 988 deletions(-) delete mode 100644 test/editions_controls.test.ts create mode 100644 test/tests/editions_controls.test.ts rename test/{utils.ts => utils/getters.ts} (84%) create mode 100644 test/utils/pdas.ts create mode 100644 test/utils/types.ts create mode 100644 test/utils/utils.ts diff --git a/Anchor.toml b/Anchor.toml index 55040af6..3fec3ad9 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -25,7 +25,7 @@ cluster = "Localnet" wallet = "~/.config/solana/id.json" [scripts] -test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/**/*.ts" +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/tests/**/*.ts -- --verbose" [test] startup_wait = 100000 @@ -37,6 +37,7 @@ bind_address = "0.0.0.0" url = "https://api.devnet.solana.com" ledger = ".anchor/test-ledger" rpc_port = 8899 +logs = "all" [[test.validator.clone]] address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index b3518d6f..826bfe3e 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -144,6 +144,7 @@ pub fn mint_with_controls( ctx: Context, mint_input: MintInput, ) -> Result<()> { + msg!("program: executing mint with controls"); let editions_controls = &mut ctx.accounts.editions_controls; let minter_stats = &mut ctx.accounts.minter_stats; let minter_stats_phase = &mut ctx.accounts.minter_stats_phase; @@ -158,13 +159,14 @@ pub fn mint_with_controls( minter_stats, minter_stats_phase, editions_controls, - ); + )?; // Get the default/standard price amount for the phase let mut price_amount = editions_controls.phases[mint_input.phase_index as usize].price_amount; // Determine if is a normal mint or an allow list mint and perform the appropriate constraints if mint_input.merkle_proof.is_some() { + msg!("program: executing allow list mint"); // Check allow list constraints check_allow_list_constraints( &editions_controls.phases[mint_input.phase_index as usize], @@ -173,7 +175,8 @@ pub fn mint_with_controls( mint_input.merkle_proof, mint_input.allow_list_price, mint_input.allow_list_max_claims, - ); + )?; + msg!("program: allow list constraints passed"); // Override the price amount with the allow list price price_amount = mint_input.allow_list_price.unwrap_or(0); } @@ -278,7 +281,6 @@ fn process_platform_fees( let recipient_account = &ctx.accounts.platform_fee_recipient_1; - msg!("check recipients {}: {} vs {}", i, recipient_account.key(), recipient_struct.address.key()); // Ensure that the account matches the expected recipient if recipient_account.key() != recipient_struct.address.key() { return Err(EditionsControlsError::RecipientMismatch.into()); diff --git a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs index 7664c714..88cc5a91 100644 --- a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs @@ -28,10 +28,15 @@ pub fn check_allow_list_constraints( if let (Some(phase_list_price), Some(phase_max_claims)) = (allow_list_price, allow_list_max_claims) { /// 1. check constraints /// dev: notice that if phase_max_claims is 0, this constraint is disabled + msg!("check_allow_list_constraints: phase_max_claims: {}", phase_max_claims); + msg!("check_allow_list_constraints: mint_count: {}", minter_stats_phase.mint_count); + if phase_max_claims > 0 && minter_stats_phase.mint_count >= phase_max_claims { return Err(EditionsControlsError::ExceededAllowListMaxClaims.into()); } - + + msg!("check_allow_list_constraints: passed phase_max_claims"); + /// 2. construct leaf let leaf = hashv(&[ &minter.to_bytes(), diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index 669cdb5a..c40adcad 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -5,7 +5,7 @@ * IDL can be found at `target/idl/libreplex_editions.json`. */ export type LibreplexEditions = { - "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD", + "address": "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V", "metadata": { "name": "libreplexEditions", "version": "0.2.1", diff --git a/test/data/merkle_tree.json b/test/data/merkle_tree.json index f13c217f..2121a02e 100644 --- a/test/data/merkle_tree.json +++ b/test/data/merkle_tree.json @@ -1,188 +1,41 @@ { "merkle_root": [ - 125, - 184, - 194, - 116, - 52, - 36, - 65, - 219, - 171, - 135, - 154, - 27, - 188, - 122, - 207, - 204, - 111, - 70, - 66, - 115, - 161, - 228, - 44, - 84, - 67, - 97, - 29, - 70, - 253, - 69, - 11, - 245 + 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, 204, + 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, 245 ], "max_num_nodes": 2, "max_total_claim": 6, "tree_nodes": [ { "claimant": [ - 104, - 164, - 83, - 51, - 23, - 177, - 193, - 29, - 252, - 241, - 86, - 132, - 173, - 155, - 114, - 131, - 130, - 73, - 27, - 101, - 233, - 95, - 12, - 45, - 107, - 255, - 120, - 26, - 121, - 221, - 120, - 54 + 104, 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, 114, + 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221, + 120, 54 ], "claim_price": 500000, "max_claims": 3, "proof": [ [ - 64, - 131, - 242, - 169, - 206, - 112, - 155, - 119, - 81, - 214, - 17, - 137, - 174, - 140, - 208, - 220, - 141, - 177, - 213, - 131, - 127, - 104, - 181, - 15, - 121, - 228, - 87, - 25, - 232, - 172, - 235, - 168 + 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, + 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, + 232, 172, 235, 168 ] ] }, { "claimant": [ - 186, - 39, - 133, - 92, - 39, - 241, - 42, - 161, - 180, - 15, - 92, - 18, - 15, - 101, - 248, - 80, - 238, - 254, - 220, - 231, - 1, - 14, - 231, - 145, - 170, - 49, - 163, - 111, - 239, - 112, - 135, - 6 + 186, 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80, + 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6 ], "claim_price": 500000, "max_claims": 3, "proof": [ [ - 86, - 37, - 15, - 136, - 192, - 159, - 125, - 244, - 163, - 213, - 251, - 242, - 217, - 215, - 159, - 249, - 93, - 166, - 82, - 38, - 187, - 58, - 199, - 64, - 161, - 50, - 122, - 122, - 17, - 125, - 63, - 188 + 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, + 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, 17, + 125, 63, 188 ] ] } ] -} \ No newline at end of file +} diff --git a/test/editions_controls.test.ts b/test/editions_controls.test.ts deleted file mode 100644 index a02bff5f..00000000 --- a/test/editions_controls.test.ts +++ /dev/null @@ -1,781 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import { Program } from '@coral-xyz/anchor'; -import { - PublicKey, - Keypair, - SystemProgram, - ComputeBudgetProgram, - LAMPORTS_PER_SOL, -} from '@solana/web3.js'; -import { - ASSOCIATED_TOKEN_PROGRAM_ID, - getAssociatedTokenAddressSync, - TOKEN_2022_PROGRAM_ID, - getTokenMetadata, -} from '@solana/spl-token'; -import { LibreplexEditionsControls } from '../../eclipse-program-library/target/types/libreplex_editions_controls'; -import { LibreplexEditions } from '../../eclipse-program-library/target/types/libreplex_editions'; -import { expect } from 'chai'; -import { describe, it } from 'mocha'; -import { - getCluster, - getEditions, - getEditionsControls, - getMinterStats, - logEditions, - logEditionsControls, - logMinterStats, - logMinterStatsPhase, - logTokenMetadata, - parseMetadata, -} from './utils'; -import { Transaction } from '@solana/web3.js'; -import { - EDITIONS_CONTROLS_PROGRAM_ID, - EDITIONS_PROGRAM_ID, - TOKEN_GROUP_EXTENSION_PROGRAM_ID, -} from '../constants'; -import { toBufferLE } from 'bigint-buffer'; - -const VERBOSE_LOGGING = true; - -describe('Editions Controls Test Suite', () => { - const provider = anchor.AnchorProvider.env(); - anchor.setProvider(provider); - - let editionsControlsProgram: Program; - let editionsProgram: Program; - - let editionsPda: PublicKey; - let editionsControlsPda: PublicKey; - let hashlistPda: PublicKey; - - let payer: Keypair; - let creator1: Keypair; - let creator2: Keypair; - let treasury: Keypair; - let platformFeeAdmin: Keypair; - let groupMint: Keypair; - let group: Keypair; - - let minter1: Keypair; - let minter2: Keypair; - - let collectionConfig: { - symbol: string; - maxMintsPerWallet: anchor.BN; - maxNumberOfTokens: anchor.BN; - collectionName: string; - collectionUri: string; - royalties: { - royaltyBasisPoints: anchor.BN; - creators: { address: PublicKey; share: number }[]; - }; - platformFee: { - platformFeeValue: anchor.BN; - recipients: { address: PublicKey; share: number }[]; - isFeeFlat: boolean; - }; - extraMeta: { field: string; value: string }[]; - itemBaseUri: string; - itemBaseName: string; - treasury: PublicKey; - cosignerProgramId: PublicKey | null; - }; - - let allowListConfig: { - merkleRoot: Buffer; - list: { - address: PublicKey; - price: anchor.BN; - max_claims: anchor.BN; - proof: Buffer[]; - }[]; - }; - - before(async () => { - if (VERBOSE_LOGGING) { - const cluster = await getCluster(provider.connection); - console.log('Cluster:', cluster); - } - - editionsControlsProgram = anchor.workspace - .LibreplexEditionsControls as Program; - editionsProgram = anchor.workspace - .LibreplexEditions as Program; - - payer = (provider.wallet as anchor.Wallet).payer; - creator1 = Keypair.generate(); - creator2 = Keypair.generate(); - treasury = Keypair.generate(); - platformFeeAdmin = Keypair.generate(); - groupMint = Keypair.generate(); - group = Keypair.generate(); - - collectionConfig = { - maxNumberOfTokens: new anchor.BN(1150), - symbol: 'COOLX55', - collectionName: 'Collection name with meta, platform fee and royalties', - collectionUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/0', - treasury: treasury.publicKey, - maxMintsPerWallet: new anchor.BN(100), - royalties: { - royaltyBasisPoints: new anchor.BN(1000), - creators: [ - { - address: creator1.publicKey, - share: 50, - }, - { - address: creator2.publicKey, - share: 50, - }, - ], - }, - platformFee: { - platformFeeValue: new anchor.BN(500000), - recipients: [ - { - address: platformFeeAdmin.publicKey, - share: 100, - }, - ], - isFeeFlat: true, - }, - extraMeta: [ - { field: 'field1', value: 'value1' }, - { field: 'field2', value: 'value2' }, - { field: 'field3', value: 'value3' }, - { field: 'field4', value: 'value4' }, - ], - itemBaseUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/{}', - itemBaseName: 'Item T8 V4 #{}', - cosignerProgramId: null, - }; - - if (VERBOSE_LOGGING) { - console.log('Collection config: ', collectionConfig); - } - - [editionsPda] = PublicKey.findProgramAddressSync( - [ - Buffer.from('editions_deployment'), - Buffer.from(collectionConfig.symbol), - ], - editionsProgram.programId - ); - - [editionsControlsPda] = PublicKey.findProgramAddressSync( - [Buffer.from('editions_controls'), editionsPda.toBuffer()], - editionsControlsProgram.programId - ); - - [hashlistPda] = PublicKey.findProgramAddressSync( - [Buffer.from('hashlist'), editionsPda.toBuffer()], - editionsProgram.programId - ); - }); - - it('should deploy a collection, add a phase, and execute a mint', async () => { - // Modify compute units for the transaction - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 800000, - }); - - try { - const initialiseIx = await editionsControlsProgram.methods - .initialiseEditionsControls({ - maxMintsPerWallet: collectionConfig.maxMintsPerWallet, - treasury: collectionConfig.treasury, - maxNumberOfTokens: collectionConfig.maxNumberOfTokens, - symbol: collectionConfig.symbol, - collectionName: collectionConfig.collectionName, - collectionUri: collectionConfig.collectionUri, - cosignerProgramId: collectionConfig.cosignerProgramId, - royalties: collectionConfig.royalties, - platformFee: collectionConfig.platformFee, - extraMeta: collectionConfig.extraMeta, - itemBaseUri: collectionConfig.itemBaseUri, - itemBaseName: collectionConfig.itemBaseName, - }) - .accountsStrict({ - editionsControls: editionsControlsPda, - editionsDeployment: editionsPda, - hashlist: hashlistPda, - payer: payer.publicKey, - creator: payer.publicKey, - groupMint: groupMint.publicKey, - group: group.publicKey, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - groupExtensionProgram: new PublicKey( - '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V' - ), - }) - .instruction(); - - const transaction = new Transaction() - .add(modifyComputeUnits) - .add(initialiseIx); - - await provider.sendAndConfirm(transaction, [groupMint, group, payer]); - - // Fetch updated state - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const metadata = await getTokenMetadata( - provider.connection, - groupMint.publicKey - ); - - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - logTokenMetadata(metadata); - } - - // Verify Editions deployment - expect(editionsDecoded.data.symbol).to.equal(collectionConfig.symbol); - expect(editionsDecoded.data.creator.toBase58()).to.equal( - editionsControlsPda.toBase58() - ); - expect(editionsDecoded.data.maxNumberOfTokens.toString()).to.equal( - collectionConfig.maxNumberOfTokens.toString() - ); - expect(editionsDecoded.data.itemBaseName).to.equal( - collectionConfig.itemBaseName - ); - expect(editionsDecoded.data.itemBaseUri).to.equal( - collectionConfig.itemBaseUri - ); - - // Verify EditionsControls deployment - expect( - editionsControlsDecoded.data.editionsDeployment.toBase58() - ).to.equal(editionsPda.toBase58()); - expect(editionsControlsDecoded.data.creator.toBase58()).to.equal( - payer.publicKey.toBase58() - ); - expect(editionsControlsDecoded.data.treasury.toBase58()).to.equal( - collectionConfig.treasury.toBase58() - ); - expect(Number(editionsControlsDecoded.data.maxMintsPerWallet)).to.equal( - Number(collectionConfig.maxMintsPerWallet) - ); - expect(editionsControlsDecoded.data.phases.length).to.equal(0); - - // Verify metadata - const parsedMetadata = parseMetadata(metadata.additionalMetadata); - expect(metadata.name).to.equal(collectionConfig.collectionName); - expect(metadata.uri).to.equal(collectionConfig.collectionUri); - expect(metadata.mint.toBase58()).to.equal(groupMint.publicKey.toBase58()); - // Verify that every key in extraMeta is present in metadata.additionalMetadata - collectionConfig.extraMeta.forEach((meta) => { - expect(parsedMetadata).to.have.property(meta.field); - expect(parsedMetadata[meta.field]).to.equal(meta.value); - }); - - // Add more assertions as needed - } catch (error) { - console.error('Error in initialiseEditionsControls:', error); - throw error; - } - }); - - // 2. Add a phase - it('Should add a phase without allowlist', async () => { - const phaseConfig = { - maxMintsPerWallet: new anchor.BN(100), - maxMintsTotal: new anchor.BN(1000), - priceAmount: new anchor.BN(10000000), // 0.01 SOL - startTime: new anchor.BN(new Date().getTime() / 1000), - endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now - priceToken: new PublicKey('So11111111111111111111111111111111111111112'), - merkleRoot: null, - }; - - const phaseIx = await editionsControlsProgram.methods - .addPhase(phaseConfig) - .accountsStrict({ - editionsControls: editionsControlsPda, - creator: payer.publicKey, - payer: payer.publicKey, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - }) - .signers([]) - .instruction(); - - const transaction = new Transaction().add(phaseIx); - await provider.sendAndConfirm(transaction, [payer]); - - // get state - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - } - - // verify state - expect(editionsControlsDecoded.data.phases.length).to.equal(1); - expect( - editionsControlsDecoded.data.phases[0].maxMintsPerWallet.toString() - ).to.equal(phaseConfig.maxMintsPerWallet.toString()); - expect( - editionsControlsDecoded.data.phases[0].maxMintsTotal.toString() - ).to.equal(phaseConfig.maxMintsTotal.toString()); - }); - - // Generate allowlist variables - before(async () => { - minter1 = Keypair.fromSecretKey( - new Uint8Array([ - 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, - 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, - 218, 104, 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, - 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, - 221, 120, 54, - ]) - ); - minter2 = Keypair.fromSecretKey( - new Uint8Array([ - 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, - 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, - 220, 186, 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, - 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, - 135, 6, - ]) - ); - allowListConfig = { - merkleRoot: Buffer.from([ - 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, - 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, - 245, - ]), - list: [ - { - address: minter1.publicKey, - price: new anchor.BN(500000), // 0.005 SOL - max_claims: new anchor.BN(3), - proof: [ - Buffer.from([ - 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, - 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, - 232, 172, 235, 168, - ]), - ], - }, - { - address: minter2.publicKey, - price: new anchor.BN(500000), // 0.005 SOL - max_claims: new anchor.BN(3), - proof: [ - Buffer.from([ - 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, - 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, - 17, 125, 63, 188, - ]), - ], - }, - ], - }; - }); - - it('Should add a phase with allowlist', async () => { - const phaseConfig = { - maxMintsPerWallet: new anchor.BN(100), - maxMintsTotal: new anchor.BN(1000), - priceAmount: new anchor.BN(10000000), // 0.01 SOL - startTime: new anchor.BN(new Date().getTime() / 1000), - endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now - priceToken: new PublicKey('So11111111111111111111111111111111111111112'), - merkleRoot: allowListConfig.merkleRoot, - }; - - const phaseIx = await editionsControlsProgram.methods - .addPhase(phaseConfig) - .accountsStrict({ - editionsControls: editionsControlsPda, - creator: payer.publicKey, - payer: payer.publicKey, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - }) - .signers([]) - .instruction(); - - const transaction = new Transaction().add(phaseIx); - await provider.sendAndConfirm(transaction, [payer]); - - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - } - - // verify state - expect(editionsControlsDecoded.data.phases.length).to.equal(2); - expect( - editionsControlsDecoded.data.phases[1].maxMintsPerWallet.toString() - ).to.equal(phaseConfig.maxMintsPerWallet.toString()); - expect( - editionsControlsDecoded.data.phases[1].maxMintsTotal.toString() - ).to.equal(phaseConfig.maxMintsTotal.toString()); - }); - - before(async () => { - // Airdrop SOL to minter1 - const airdropSignature = await provider.connection.requestAirdrop( - minter1.publicKey, - 1 * LAMPORTS_PER_SOL - ); - await provider.connection.confirmTransaction(airdropSignature); - - // Airdrop SOL to treasury - const treasuryAirdropSignature = await provider.connection.requestAirdrop( - collectionConfig.treasury, - 1 * LAMPORTS_PER_SOL - ); - await provider.connection.confirmTransaction(treasuryAirdropSignature); - - // Airdrop SOL to platformFeeRecipient - const platformFeeRecipientAirdropSignature = - await provider.connection.requestAirdrop( - platformFeeAdmin.publicKey, - 1 * LAMPORTS_PER_SOL - ); - await provider.connection.confirmTransaction( - platformFeeRecipientAirdropSignature - ); - }); - - it('Should mint on first phase without allowlist', async () => { - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 850000, - }); - - const mintConfig = { - phaseIndex: 0, - merkleProof: null, - allowListPrice: null, - allowListMaxClaims: null, - }; - - const mint = Keypair.generate(); - const member = Keypair.generate(); - - const tokenAccount = getAssociatedTokenAddressSync( - mint.publicKey, - minter1.publicKey, - false, - TOKEN_2022_PROGRAM_ID - ); - - const [hashlistMarker] = PublicKey.findProgramAddressSync( - [ - Buffer.from('hashlist_marker'), - editionsPda.toBuffer(), - mint.publicKey.toBuffer(), - ], - new PublicKey(EDITIONS_PROGRAM_ID) - ); - - const [minterStats] = PublicKey.findProgramAddressSync( - [ - Buffer.from('minter_stats'), - editionsPda.toBuffer(), - minter1.publicKey.toBuffer(), - ], - new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) - ); - - const [minterStatsPhase] = PublicKey.findProgramAddressSync( - [ - Buffer.from('minter_stats_phase'), - editionsPda.toBuffer(), - minter1.publicKey.toBuffer(), - toBufferLE(BigInt(0), 4), - ], - new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) - ); - - const mintIx = await editionsControlsProgram.methods - .mintWithControls(mintConfig) - .accountsStrict({ - editionsDeployment: editionsPda, - editionsControls: editionsControlsPda, - hashlist: hashlistPda, - hashlistMarker, - payer: minter1.publicKey, - mint: mint.publicKey, - member: member.publicKey, - signer: minter1.publicKey, - minter: minter1.publicKey, - minterStats, - minterStatsPhase, - group: group.publicKey, - groupMint: groupMint.publicKey, - platformFeeRecipient1: platformFeeAdmin.publicKey, - groupExtensionProgram: new PublicKey(TOKEN_GROUP_EXTENSION_PROGRAM_ID), - tokenAccount, - treasury: collectionConfig.treasury, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, - }) - .instruction(); - - const transaction = new Transaction().add(modifyComputeUnits).add(mintIx); - transaction.recentBlockhash = ( - await provider.connection.getLatestBlockhash() - ).blockhash; - transaction.feePayer = minter1.publicKey; - transaction.sign(minter1, mint, member); - const rawTransaction = transaction.serialize(); - - try { - const signature = await provider.connection.sendRawTransaction( - rawTransaction, - { - skipPreflight: false, - preflightCommitment: 'confirmed', - } - ); - await provider.connection.confirmTransaction(signature); - - console.log('\nmintWithControls done!\n'); - } catch (error) { - if (error.logs) { - console.error('Full error logs:'); - error.logs.forEach((log, index) => { - console.error(`${index + 1}: ${log}`); - }); - } else { - console.error(error); - } - - throw error; - } - - // get state - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const minterStatsDecoded = await getMinterStats( - provider.connection, - minterStats, - editionsControlsProgram - ); - const minterStatsPhaseDecoded = await getMinterStats( - provider.connection, - minterStatsPhase, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - logMinterStats(minterStatsDecoded); - logMinterStatsPhase(minterStatsPhaseDecoded); - } - - // verify state - expect( - editionsControlsDecoded.data.phases[0].currentMints.toString() - ).to.equal(new anchor.BN(1).toString()); - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( - new anchor.BN(1).toString() - ); - expect(minterStatsDecoded.data.mintCount.toString()).to.equal( - new anchor.BN(1).toString() - ); - expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal( - new anchor.BN(1).toString() - ); - }); - - it('Should mint on second phase with allowlist', async () => { - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 850000, - }); - - const mintConfig = { - phaseIndex: 1, // Use the second phase (index 1) - merkleProof: allowListConfig.list[0].proof, // Use the proof for minter1 - allowListPrice: allowListConfig.list[0].price, - allowListMaxClaims: allowListConfig.list[0].max_claims, - }; - - const mint = Keypair.generate(); - const member = Keypair.generate(); - - const tokenAccount = getAssociatedTokenAddressSync( - mint.publicKey, - minter1.publicKey, - false, - TOKEN_2022_PROGRAM_ID - ); - - const [hashlistMarker] = PublicKey.findProgramAddressSync( - [ - Buffer.from('hashlist_marker'), - editionsPda.toBuffer(), - mint.publicKey.toBuffer(), - ], - new PublicKey(EDITIONS_PROGRAM_ID) - ); - - const [minterStats] = PublicKey.findProgramAddressSync( - [ - Buffer.from('minter_stats'), - editionsPda.toBuffer(), - minter1.publicKey.toBuffer(), - ], - new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) - ); - - const [minterStatsPhase] = PublicKey.findProgramAddressSync( - [ - Buffer.from('minter_stats_phase'), - editionsPda.toBuffer(), - minter1.publicKey.toBuffer(), - toBufferLE(BigInt(1), 4), // Use phase index 1 - ], - new PublicKey(EDITIONS_CONTROLS_PROGRAM_ID) - ); - - const mintIx = await editionsControlsProgram.methods - .mintWithControls(mintConfig) - .accountsStrict({ - editionsDeployment: editionsPda, - editionsControls: editionsControlsPda, - hashlist: hashlistPda, - hashlistMarker, - payer: minter1.publicKey, - mint: mint.publicKey, - member: member.publicKey, - signer: minter1.publicKey, - minter: minter1.publicKey, - minterStats, - minterStatsPhase, - group: group.publicKey, - groupMint: groupMint.publicKey, - platformFeeRecipient1: platformFeeAdmin.publicKey, - groupExtensionProgram: new PublicKey(TOKEN_GROUP_EXTENSION_PROGRAM_ID), - tokenAccount, - treasury: collectionConfig.treasury, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, - }) - .instruction(); - - const transaction = new Transaction().add(modifyComputeUnits).add(mintIx); - transaction.recentBlockhash = ( - await provider.connection.getLatestBlockhash() - ).blockhash; - transaction.feePayer = minter1.publicKey; - transaction.sign(minter1, mint, member); - const rawTransaction = transaction.serialize(); - - try { - const signature = await provider.connection.sendRawTransaction( - rawTransaction, - { - skipPreflight: false, - preflightCommitment: 'confirmed', - } - ); - // wait for transaction to be confirmed - await provider.connection.confirmTransaction(signature); - - console.log('\nmintWithControls on allowlist phase done!\n'); - - // Fetch updated state - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const minterStatsDecoded = await getMinterStats( - provider.connection, - minterStats, - editionsControlsProgram - ); - const minterStatsPhaseDecoded = await getMinterStats( - provider.connection, - minterStatsPhase, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - logMinterStats(minterStatsDecoded); - logMinterStatsPhase(minterStatsPhaseDecoded); - } - - // Verify state - expect( - editionsControlsDecoded.data.phases[1].currentMints.toString() - ).to.equal('1'); - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( - '2' - ); - expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2'); - expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); - - console.log('Allowlist mint verified successfully'); - } catch (error) { - console.error('Error in allowlist minting:', error); - if (error.logs) { - console.error('Full error logs:'); - error.logs.forEach((log, index) => { - console.error(`${index + 1}: ${log}`); - }); - } - throw error; - } - }); -}); diff --git a/test/tests/editions_controls.test.ts b/test/tests/editions_controls.test.ts new file mode 100644 index 00000000..8fc037a8 --- /dev/null +++ b/test/tests/editions_controls.test.ts @@ -0,0 +1,1013 @@ +import * as anchor from '@coral-xyz/anchor'; +import { Program } from '@coral-xyz/anchor'; +import { + PublicKey, + Keypair, + SystemProgram, + ComputeBudgetProgram, + LAMPORTS_PER_SOL, +} from '@solana/web3.js'; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + getAssociatedTokenAddressSync, + TOKEN_2022_PROGRAM_ID, +} from '@solana/spl-token'; +import { LibreplexEditionsControls } from '../../target/types/libreplex_editions_controls'; +import { LibreplexEditions } from '../../target/types/libreplex_editions'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { getCluster } from '../utils/utils'; +import { + getEditions, + getEditionsControls, + getMinterStats, + getTokenMetadata, + logEditions, + logEditionsControls, + logMinterStats, + logMinterStatsPhase, + logTokenMetadata, +} from '../utils/getters'; +import { Transaction } from '@solana/web3.js'; +// devnote: try to make tests don't rely on hard addresses but on dynamic runtime ids. +import { TOKEN_GROUP_EXTENSION_PROGRAM_ID } from '../../constants'; +import { + getEditionsPda, + getEditionsControlsPda, + getHashlistPda, + getHashlistMarkerPda, + getMinterStatsPda, + getMinterStatsPhasePda, +} from '../utils/pdas'; +import { CollectionConfig, AllowListConfig } from '../utils/types'; + +const VERBOSE_LOGGING = false; + +describe('Editions Controls Test Suite', () => { + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + let editionsControlsProgram: Program; + let editionsProgram: Program; + + let editionsPda: PublicKey; + let editionsControlsPda: PublicKey; + let hashlistPda: PublicKey; + + let payer: Keypair; + let creator1: Keypair; + let creator2: Keypair; + let treasury: Keypair; + let platformFeeAdmin: Keypair; + let groupMint: Keypair; + let group: Keypair; + + let minter1: Keypair; + let minter2: Keypair; + + let collectionConfig: CollectionConfig; + let allowListConfig: AllowListConfig; + + // Generate dynamic keypairs and collection config + before(async () => { + if (VERBOSE_LOGGING) { + const cluster = await getCluster(provider.connection); + console.log('Cluster:', cluster); + } + + editionsControlsProgram = anchor.workspace + .LibreplexEditionsControls as Program; + editionsProgram = anchor.workspace + .LibreplexEditions as Program; + + payer = (provider.wallet as anchor.Wallet).payer; + creator1 = Keypair.generate(); + creator2 = Keypair.generate(); + treasury = Keypair.generate(); + platformFeeAdmin = Keypair.generate(); + groupMint = Keypair.generate(); + group = Keypair.generate(); + + collectionConfig = { + maxNumberOfTokens: new anchor.BN(1150), + symbol: 'COOLX55', + collectionName: 'Collection name with meta, platform fee and royalties', + collectionUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/0', + treasury: treasury.publicKey, + maxMintsPerWallet: new anchor.BN(100), + royalties: { + royaltyBasisPoints: new anchor.BN(1000), + creators: [ + { + address: creator1.publicKey, + share: 50, + }, + { + address: creator2.publicKey, + share: 50, + }, + ], + }, + platformFee: { + platformFeeValue: new anchor.BN(500000), + recipients: [ + { + address: platformFeeAdmin.publicKey, + share: 100, + }, + ], + isFeeFlat: true, + }, + extraMeta: [ + { field: 'field1', value: 'value1' }, + { field: 'field2', value: 'value2' }, + { field: 'field3', value: 'value3' }, + { field: 'field4', value: 'value4' }, + ], + itemBaseUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/{}', + itemBaseName: 'Item T8 V4 #{}', + cosignerProgramId: null, + }; + if (VERBOSE_LOGGING) { + console.log('Collection config: ', collectionConfig); + } + + editionsPda = getEditionsPda( + collectionConfig.symbol, + editionsProgram.programId + ); + editionsControlsPda = getEditionsControlsPda( + editionsPda, + editionsControlsProgram.programId + ); + hashlistPda = getHashlistPda(editionsPda, editionsProgram.programId); + }); + + // Generate allowlist variables + before(async () => { + minter1 = Keypair.fromSecretKey( + new Uint8Array([ + 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, + 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, + 218, 104, 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, + 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, + 221, 120, 54, + ]) + ); + minter2 = Keypair.fromSecretKey( + new Uint8Array([ + 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, + 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, + 220, 186, 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, + 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, + 135, 6, + ]) + ); + allowListConfig = { + merkleRoot: Buffer.from([ + 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, + 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, + 245, + ]), + list: [ + { + address: minter1.publicKey, + price: new anchor.BN(500000), // 0.005 SOL + max_claims: new anchor.BN(3), + proof: [ + Buffer.from([ + 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, + 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, + 232, 172, 235, 168, + ]), + ], + }, + { + address: minter2.publicKey, + price: new anchor.BN(500000), // 0.005 SOL + max_claims: new anchor.BN(3), + proof: [ + Buffer.from([ + 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, + 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, + 17, 125, 63, 188, + ]), + ], + }, + ], + }; + }); + + // Perform needed airdrops to minter1, treasury and platformFeeRecipient + before(async () => { + // Airdrop SOL to minter1 + const airdropSignature = await provider.connection.requestAirdrop( + minter1.publicKey, + 1 * LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction(airdropSignature); + + // Airdrop SOL to minter2 + const minter2AirdropSignature = await provider.connection.requestAirdrop( + minter2.publicKey, + 10 * LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction(minter2AirdropSignature); + + // Airdrop SOL to treasury + const treasuryAirdropSignature = await provider.connection.requestAirdrop( + collectionConfig.treasury, + 1 * LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction(treasuryAirdropSignature); + + // Airdrop SOL to platformFeeRecipient + const platformFeeRecipientAirdropSignature = + await provider.connection.requestAirdrop( + platformFeeAdmin.publicKey, + 1 * LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction( + platformFeeRecipientAirdropSignature + ); + }); + + describe('Deploying', () => { + it('Should deploy a collection', async () => { + // Modify compute units for the transaction + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 800000, + }); + + try { + const initialiseIx = await editionsControlsProgram.methods + .initialiseEditionsControls({ + maxMintsPerWallet: collectionConfig.maxMintsPerWallet, + treasury: collectionConfig.treasury, + maxNumberOfTokens: collectionConfig.maxNumberOfTokens, + symbol: collectionConfig.symbol, + collectionName: collectionConfig.collectionName, + collectionUri: collectionConfig.collectionUri, + cosignerProgramId: collectionConfig.cosignerProgramId, + royalties: collectionConfig.royalties, + platformFee: collectionConfig.platformFee, + extraMeta: collectionConfig.extraMeta, + itemBaseUri: collectionConfig.itemBaseUri, + itemBaseName: collectionConfig.itemBaseName, + }) + .accountsStrict({ + editionsControls: editionsControlsPda, + editionsDeployment: editionsPda, + hashlist: hashlistPda, + payer: payer.publicKey, + creator: payer.publicKey, + groupMint: groupMint.publicKey, + group: group.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + groupExtensionProgram: new PublicKey( + '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V' + ), + }) + .instruction(); + + const transaction = new Transaction() + .add(modifyComputeUnits) + .add(initialiseIx); + + await provider.sendAndConfirm(transaction, [groupMint, group, payer]); + + // Fetch updated state + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + const metadata = await getTokenMetadata( + provider.connection, + groupMint.publicKey + ); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logTokenMetadata(metadata); + } + + // Verify Editions deployment + expect(editionsDecoded.data.symbol).to.equal(collectionConfig.symbol); + expect(editionsDecoded.data.creator.toBase58()).to.equal( + editionsControlsPda.toBase58() + ); + expect(editionsDecoded.data.maxNumberOfTokens.toString()).to.equal( + collectionConfig.maxNumberOfTokens.toString() + ); + expect(editionsDecoded.data.itemBaseName).to.equal( + collectionConfig.itemBaseName + ); + expect(editionsDecoded.data.itemBaseUri).to.equal( + collectionConfig.itemBaseUri + ); + + // Verify EditionsControls deployment + expect( + editionsControlsDecoded.data.editionsDeployment.toBase58() + ).to.equal(editionsPda.toBase58()); + expect(editionsControlsDecoded.data.creator.toBase58()).to.equal( + payer.publicKey.toBase58() + ); + expect(editionsControlsDecoded.data.treasury.toBase58()).to.equal( + collectionConfig.treasury.toBase58() + ); + expect(Number(editionsControlsDecoded.data.maxMintsPerWallet)).to.equal( + Number(collectionConfig.maxMintsPerWallet) + ); + expect(editionsControlsDecoded.data.phases.length).to.equal(0); + + // Verify metadata + expect(metadata.name).to.equal(collectionConfig.collectionName); + expect(metadata.uri).to.equal(collectionConfig.collectionUri); + expect(metadata.mint.toBase58()).to.equal( + groupMint.publicKey.toBase58() + ); + // Verify that every key in extraMeta is present in metadata.additionalMetadata + collectionConfig.extraMeta.forEach((meta) => { + expect(metadata.additionalMetadata).to.have.property(meta.field); + expect(metadata.additionalMetadata[meta.field]).to.equal(meta.value); + }); + + // Add more assertions as needed + } catch (error) { + console.error('Error in initialiseEditionsControls:', error); + throw error; + } + }); + }); + + describe('Adding phases', () => { + it('Should add a phase without allowlist', async () => { + const phaseConfig = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(1000), + priceAmount: new anchor.BN(10000000), // 0.01 SOL + startTime: new anchor.BN(new Date().getTime() / 1000), + endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now + priceToken: new PublicKey( + 'So11111111111111111111111111111111111111112' + ), + merkleRoot: null, + }; + + const phaseIx = await editionsControlsProgram.methods + .addPhase(phaseConfig) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + await provider.sendAndConfirm(transaction, [payer]); + + // get state + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditionsControls(editionsControlsDecoded); + } + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(1); + expect( + editionsControlsDecoded.data.phases[0].maxMintsPerWallet.toString() + ).to.equal(phaseConfig.maxMintsPerWallet.toString()); + expect( + editionsControlsDecoded.data.phases[0].maxMintsTotal.toString() + ).to.equal(phaseConfig.maxMintsTotal.toString()); + }); + + it('Should add a phase with allowlist', async () => { + const phaseConfig = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(1000), + priceAmount: new anchor.BN(10000000), // 0.01 SOL + startTime: new anchor.BN(new Date().getTime() / 1000), + endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now + priceToken: new PublicKey( + 'So11111111111111111111111111111111111111112' + ), + merkleRoot: allowListConfig.merkleRoot, + }; + const phaseIx = await editionsControlsProgram.methods + .addPhase(phaseConfig) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + await provider.sendAndConfirm(transaction, [payer]); + + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditionsControls(editionsControlsDecoded); + } + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(2); + expect( + editionsControlsDecoded.data.phases[1].maxMintsPerWallet.toString() + ).to.equal(phaseConfig.maxMintsPerWallet.toString()); + expect( + editionsControlsDecoded.data.phases[1].maxMintsTotal.toString() + ).to.equal(phaseConfig.maxMintsTotal.toString()); + }); + }); + + describe('Minting', () => { + describe('Minting without allowlist', () => { + it('Should mint on first phase without allowlist', async () => { + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 850000, + }); + + const mintConfig = { + phaseIndex: 0, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const tokenAccount = getAssociatedTokenAddressSync( + mint.publicKey, + minter1.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + const hashlistMarker = getHashlistMarkerPda( + editionsPda, + mint.publicKey, + editionsProgram.programId + ); + const minterStats = getMinterStatsPda( + editionsPda, + minter1.publicKey, + editionsControlsProgram.programId + ); + const minterStatsPhase = getMinterStatsPhasePda( + editionsPda, + minter1.publicKey, + 0, + editionsControlsProgram.programId + ); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker, + payer: minter1.publicKey, + mint: mint.publicKey, + member: member.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats, + minterStatsPhase, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + groupExtensionProgram: new PublicKey( + TOKEN_GROUP_EXTENSION_PROGRAM_ID + ), + tokenAccount, + treasury: collectionConfig.treasury, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }) + .instruction(); + + const transaction = new Transaction() + .add(modifyComputeUnits) + .add(mintIx); + transaction.recentBlockhash = ( + await provider.connection.getLatestBlockhash() + ).blockhash; + transaction.feePayer = minter1.publicKey; + transaction.sign(minter1, mint, member); + const rawTransaction = transaction.serialize(); + + try { + const signature = await provider.connection.sendRawTransaction( + rawTransaction + ); + await provider.connection.confirmTransaction(signature); + } catch (error) { + console.error('Error in mintWithControls:', error); + throw error; + } + + // get state + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + const minterStatsDecoded = await getMinterStats( + provider.connection, + minterStats, + editionsControlsProgram + ); + const minterStatsPhaseDecoded = await getMinterStats( + provider.connection, + minterStatsPhase, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logMinterStats(minterStatsDecoded); + logMinterStatsPhase(minterStatsPhaseDecoded); + } + + // verify collection state + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( + new anchor.BN(1).toString() + ); + expect( + editionsControlsDecoded.data.phases[0].currentMints.toString() + ).to.equal(new anchor.BN(1).toString()); + // verify minter state + expect(minterStatsDecoded.data.mintCount.toString()).to.equal( + new anchor.BN(1).toString() + ); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal( + new anchor.BN(1).toString() + ); + }); + }); + + describe('Minting with allowlist', () => { + it('Should mint on second phase with allowlist', async () => { + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 850000, + }); + + const mintConfig = { + phaseIndex: 1, // Use the second phase (index 1) + merkleProof: allowListConfig.list[0].proof, // Use the proof for minter1 + allowListPrice: allowListConfig.list[0].price, + allowListMaxClaims: allowListConfig.list[0].max_claims, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const tokenAccount = getAssociatedTokenAddressSync( + mint.publicKey, + minter1.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + const hashlistMarker = getHashlistMarkerPda( + editionsPda, + mint.publicKey, + editionsProgram.programId + ); + const minterStats = getMinterStatsPda( + editionsPda, + minter1.publicKey, + editionsControlsProgram.programId + ); + const minterStatsPhase = getMinterStatsPhasePda( + editionsPda, + minter1.publicKey, + 1, + editionsControlsProgram.programId + ); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker, + payer: minter1.publicKey, + mint: mint.publicKey, + member: member.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats, + minterStatsPhase, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + groupExtensionProgram: new PublicKey( + TOKEN_GROUP_EXTENSION_PROGRAM_ID + ), + tokenAccount, + treasury: collectionConfig.treasury, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }) + .instruction(); + + const transaction = new Transaction() + .add(modifyComputeUnits) + .add(mintIx); + transaction.recentBlockhash = ( + await provider.connection.getLatestBlockhash() + ).blockhash; + transaction.feePayer = minter1.publicKey; + transaction.sign(minter1, mint, member); + const rawTransaction = transaction.serialize(); + + try { + const signature = await provider.connection.sendRawTransaction( + rawTransaction + ); + // wait for transaction to be confirmed + await provider.connection.confirmTransaction(signature); + + // Fetch updated state + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + const minterStatsDecoded = await getMinterStats( + provider.connection, + minterStats, + editionsControlsProgram + ); + const minterStatsPhaseDecoded = await getMinterStats( + provider.connection, + minterStatsPhase, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logMinterStats(minterStatsDecoded); + logMinterStatsPhase(minterStatsPhaseDecoded); + } + + // Verify state + expect( + editionsControlsDecoded.data.phases[1].currentMints.toString() + ).to.equal('1'); + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( + '2' + ); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal( + '1' + ); + } catch (error) { + console.error('Error in allowlist minting:', error); + if (error.logs) { + console.error('Full error logs:'); + error.logs.forEach((log, index) => { + console.error(`${index + 1}: ${log}`); + }); + } + throw error; + } + }); + + it('Should fail to mint with allowlist if proof is incorrect', async () => { + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 850000, + }); + + // Use an incorrect proof (e.g., the proof for minter2 instead of minter1) + const incorrectMinterProof = allowListConfig.list[1].proof; + + const mintConfig = { + phaseIndex: 1, // Use the second phase (index 1) + merkleProof: incorrectMinterProof, + allowListPrice: allowListConfig.list[0].price, + allowListMaxClaims: allowListConfig.list[0].max_claims, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const tokenAccount = getAssociatedTokenAddressSync( + mint.publicKey, + minter1.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + const hashlistMarker = getHashlistMarkerPda( + editionsPda, + mint.publicKey, + editionsProgram.programId + ); + const minterStats = getMinterStatsPda( + editionsPda, + minter1.publicKey, + editionsControlsProgram.programId + ); + const minterStatsPhase = getMinterStatsPhasePda( + editionsPda, + minter1.publicKey, + 1, + editionsControlsProgram.programId + ); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker, + payer: minter1.publicKey, + mint: mint.publicKey, + member: member.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats, + minterStatsPhase, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + groupExtensionProgram: new PublicKey( + TOKEN_GROUP_EXTENSION_PROGRAM_ID + ), + tokenAccount, + treasury: collectionConfig.treasury, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }) + .instruction(); + + const transaction = new Transaction() + .add(modifyComputeUnits) + .add(mintIx); + transaction.recentBlockhash = ( + await provider.connection.getLatestBlockhash() + ).blockhash; + transaction.feePayer = minter1.publicKey; + transaction.sign(minter1, mint, member); + const rawTransaction = transaction.serialize(); + + try { + const signature = await provider.connection.sendRawTransaction( + rawTransaction + ); + // wait for transaction to be confirmed + await provider.connection.confirmTransaction(signature); + } catch (error) { + // We expect an error to be thrown + expect(JSON.stringify(error)).to.include('Invalid merkle proof.'); // Replace with the actual error code for invalid Merkle proof + } + + // Verify that no mint actually occurred + const editionsDecoded = await getEditions( + provider.connection, + editionsPda, + editionsProgram + ); + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + const minterStatsDecoded = await getMinterStats( + provider.connection, + minterStats, + editionsControlsProgram + ); + const minterStatsPhaseDecoded = await getMinterStats( + provider.connection, + minterStatsPhase, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logMinterStats(minterStatsDecoded); + logMinterStatsPhase(minterStatsPhaseDecoded); + } + + // Verify state + expect( + editionsControlsDecoded.data.phases[1].currentMints.toString() + ).to.equal('1'); // Should still be 1 from the previous successful mint + }); + + it('Should fail to mint with allowlist if allowlist max_claims is reached', async () => { + const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 850000, + }); + + const mintConfig = { + phaseIndex: 1, + merkleProof: allowListConfig.list[1].proof, // Use the proof for minter2 + allowListPrice: allowListConfig.list[1].price, + allowListMaxClaims: allowListConfig.list[1].max_claims, + }; + + // Function to create and send a mint transaction + const createAndSendMintTransaction = async (): Promise => { + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const tokenAccount = getAssociatedTokenAddressSync( + mint.publicKey, + minter2.publicKey, + false, + TOKEN_2022_PROGRAM_ID + ); + const hashlistMarker = getHashlistMarkerPda( + editionsPda, + mint.publicKey, + editionsProgram.programId + ); + const minterStats = getMinterStatsPda( + editionsPda, + minter2.publicKey, + editionsControlsProgram.programId + ); + const minterStatsPhase = getMinterStatsPhasePda( + editionsPda, + minter2.publicKey, + 1, + editionsControlsProgram.programId + ); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker, + payer: minter2.publicKey, + mint: mint.publicKey, + member: member.publicKey, + signer: minter2.publicKey, + minter: minter2.publicKey, + minterStats, + minterStatsPhase, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + groupExtensionProgram: new PublicKey( + TOKEN_GROUP_EXTENSION_PROGRAM_ID + ), + tokenAccount, + treasury: collectionConfig.treasury, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }) + .instruction(); + + const transaction = new Transaction() + .add(modifyComputeUnits) + .add(mintIx); + transaction.recentBlockhash = ( + await provider.connection.getLatestBlockhash() + ).blockhash; + transaction.feePayer = minter2.publicKey; + transaction.sign(minter2, mint, member); + const rawTransaction = transaction.serialize(); + + const signature = await provider.connection.sendRawTransaction( + rawTransaction + ); + await provider.connection.confirmTransaction(signature); + return signature; + }; + + // Perform 3 successful mints + for (let i = 0; i < 3; i++) { + await createAndSendMintTransaction(); + } + + const minterStats = getMinterStatsPda( + editionsPda, + minter2.publicKey, + editionsControlsProgram.programId + ); + const minterStatsPhase = getMinterStatsPhasePda( + editionsPda, + minter2.publicKey, + 1, + editionsControlsProgram.programId + ); + + // Verify the state after the 3 mints + const editionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + const minterStatsDecoded = await getMinterStats( + provider.connection, + minterStats, + editionsControlsProgram + ); + const minterStatsPhaseDecoded = await getMinterStats( + provider.connection, + minterStatsPhase, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditionsControls(editionsControlsDecoded); + logMinterStats(minterStatsDecoded); + logMinterStatsPhase(minterStatsPhaseDecoded); + } + + // verify state + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('3'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('3'); + + // Attempt the 4th mint, which should fail + try { + const signature = await createAndSendMintTransaction(); + } catch (error) { + expect(JSON.stringify(error)).to.include( + 'This wallet has exceeded allow list max_claims in the current phase.' + ); + } + + // Verify the state hasn't changed after the failed mint + const finalEditionsControlsDecoded = await getEditionsControls( + provider.connection, + editionsControlsPda, + editionsControlsProgram + ); + const finalMinterStatsDecoded = await getMinterStats( + provider.connection, + minterStats, + editionsControlsProgram + ); + const finalMinterStatsPhaseDecoded = await getMinterStats( + provider.connection, + minterStatsPhase, + editionsControlsProgram + ); + if (VERBOSE_LOGGING) { + logEditionsControls(finalEditionsControlsDecoded); + logMinterStats(finalMinterStatsDecoded); + logMinterStatsPhase(finalMinterStatsPhaseDecoded); + } + + // User mints should still be 3 + expect(finalMinterStatsDecoded.data.mintCount.toString()).to.equal('3'); + expect(finalMinterStatsPhaseDecoded.data.mintCount.toString()).to.equal( + '3' + ); + }); + }); + }); +}); diff --git a/test/utils.ts b/test/utils/getters.ts similarity index 84% rename from test/utils.ts rename to test/utils/getters.ts index 20974cc1..22d3effe 100644 --- a/test/utils.ts +++ b/test/utils/getters.ts @@ -1,9 +1,10 @@ +import { TokenMetadata } from '@solana/spl-token-metadata'; +import { BorshCoder, Program } from '@coral-xyz/anchor'; import { Connection, PublicKey } from '@solana/web3.js'; -import { LibreplexEditions } from '../target/types/libreplex_editions'; +import { LibreplexEditionsControls } from '../../target/types/libreplex_editions_controls'; +import { LibreplexEditions } from '../../target/types/libreplex_editions'; import { IdlAccounts } from '@coral-xyz/anchor'; -import { BorshCoder, Program } from '@coral-xyz/anchor'; -import { LibreplexEditionsControls } from '../target/types/libreplex_editions_controls'; -import { TokenMetadata } from '@solana/spl-token-metadata'; +import { getTokenMetadata as getSplTokenMetadata } from '@solana/spl-token'; export type EditionsDeployment = IdlAccounts['editionsDeployment']; @@ -13,30 +14,6 @@ export type EditionsControls = export type MinterStats = IdlAccounts['minterStats']; -export async function getCluster(connection: Connection): Promise { - // Get the genesis hash - const genesisHash = await connection.getGenesisHash(); - - // Compare the genesis hash with known cluster genesis hashes - switch (genesisHash) { - case '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d': - return 'mainnet-beta'; - case 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG': - return 'testnet'; - case '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY': - return 'devnet'; - default: - // If it doesn't match any known cluster, it's likely localhost - return 'localhost'; - } -} - -export const getBase64FromDatabytes = (dataBytes: Buffer, dataType: string) => { - console.log({ dataBytes }); - const base = dataBytes.toString('base64'); - return `data:${dataType};base64,${base}`; -}; - export const decodeEditions = (program: Program) => (buffer: Buffer | undefined, pubkey: PublicKey) => { @@ -149,7 +126,37 @@ export const logEditionsControls = (editionsControlsDecoded: { }); }; -export const logTokenMetadata = (metadata: TokenMetadata) => { +export const parseMetadata = ( + rawMetadata: (readonly [string, string])[] +): Record => { + const metadata: Record = {}; + for (const [key, value] of rawMetadata) { + metadata[key] = value; + } + return metadata; +}; + +export const getTokenMetadata = async ( + connection: Connection, + mint: PublicKey +) => { + const rawMetadata = await getSplTokenMetadata(connection, mint); + const additionalMetadata = rawMetadata.additionalMetadata; + const parsedMetadata = parseMetadata(additionalMetadata); + return { + ...rawMetadata, + additionalMetadata: parsedMetadata, + }; +}; + +export const logTokenMetadata = (metadata: { + name: string; + symbol: string; + uri: string; + updateAuthority?: PublicKey; + mint: PublicKey; + additionalMetadata: Record; +}) => { console.log({ TokenMetadata: { name: metadata.name, @@ -157,21 +164,11 @@ export const logTokenMetadata = (metadata: TokenMetadata) => { uri: metadata.uri, updateAuthority: metadata.updateAuthority.toBase58(), mint: metadata.mint.toBase58(), - additionalMetadata: parseMetadata(metadata.additionalMetadata), + additionalMetadata: metadata.additionalMetadata, }, }); }; -export const parseMetadata = ( - rawMetadata: (readonly [string, string])[] -): Record => { - const metadata: Record = {}; - for (const [key, value] of rawMetadata) { - metadata[key] = value; - } - return metadata; -}; - export const decodeMinterStats = (program: Program) => (buffer: Buffer | undefined, pubkey: PublicKey) => { diff --git a/test/utils/pdas.ts b/test/utils/pdas.ts new file mode 100644 index 00000000..2382772e --- /dev/null +++ b/test/utils/pdas.ts @@ -0,0 +1,76 @@ +import { PublicKey } from '@solana/web3.js'; +import { toBufferLE } from 'bigint-buffer'; + +export const getEditionsPda = ( + symbol: string, + editionsProgramId: PublicKey +) => { + return PublicKey.findProgramAddressSync( + [Buffer.from('editions_deployment'), Buffer.from(symbol)], + editionsProgramId + )[0]; +}; + +export const getEditionsControlsPda = ( + editionsDeployment: PublicKey, + editionsControlsProgramId: PublicKey +) => { + return PublicKey.findProgramAddressSync( + [Buffer.from('editions_controls'), editionsDeployment.toBuffer()], + editionsControlsProgramId + )[0]; +}; + +export const getHashlistPda = ( + deployment: PublicKey, + editionsProgramId: PublicKey +) => { + return PublicKey.findProgramAddressSync( + [Buffer.from('hashlist'), deployment.toBuffer()], + editionsProgramId + )[0]; +}; + +export const getHashlistMarkerPda = ( + editionsDeployment: PublicKey, + mint: PublicKey, + editionsProgramId: PublicKey +) => { + return PublicKey.findProgramAddressSync( + [ + Buffer.from('hashlist_marker'), + editionsDeployment.toBuffer(), + mint.toBuffer(), + ], + editionsProgramId + )[0]; +}; + +export const getMinterStatsPda = ( + deployment: PublicKey, + minter: PublicKey, + editionsControlsProgramId: PublicKey +) => { + return PublicKey.findProgramAddressSync( + [Buffer.from('minter_stats'), deployment.toBuffer(), minter.toBuffer()], + editionsControlsProgramId + )[0]; +}; + +export const getMinterStatsPhasePda = ( + deployment: PublicKey, + minter: PublicKey, + phaseIndex: number, + editionsControlsProgramId: PublicKey +) => { + // console.log({minter: minter.toBase58(), phaseIndex}) + return PublicKey.findProgramAddressSync( + [ + Buffer.from('minter_stats_phase'), + deployment.toBuffer(), + minter.toBuffer(), + toBufferLE(BigInt(phaseIndex), 4), + ], + editionsControlsProgramId + )[0]; +}; diff --git a/test/utils/types.ts b/test/utils/types.ts new file mode 100644 index 00000000..a0a3feab --- /dev/null +++ b/test/utils/types.ts @@ -0,0 +1,34 @@ +import * as anchor from '@coral-xyz/anchor'; +import { PublicKey } from '@solana/web3.js'; + +export interface CollectionConfig { + symbol: string; + maxMintsPerWallet: anchor.BN; + maxNumberOfTokens: anchor.BN; + collectionName: string; + collectionUri: string; + royalties: { + royaltyBasisPoints: anchor.BN; + creators: { address: PublicKey; share: number }[]; + }; + platformFee: { + platformFeeValue: anchor.BN; + recipients: { address: PublicKey; share: number }[]; + isFeeFlat: boolean; + }; + extraMeta: { field: string; value: string }[]; + itemBaseUri: string; + itemBaseName: string; + treasury: PublicKey; + cosignerProgramId: PublicKey | null; +} + +export interface AllowListConfig { + merkleRoot: Buffer; + list: { + address: PublicKey; + price: anchor.BN; + max_claims: anchor.BN; + proof: Buffer[]; + }[]; +} diff --git a/test/utils/utils.ts b/test/utils/utils.ts new file mode 100644 index 00000000..2625c89e --- /dev/null +++ b/test/utils/utils.ts @@ -0,0 +1,26 @@ +import { Connection } from '@solana/web3.js'; + +/// Verify this works +export async function getCluster(connection: Connection): Promise { + // Get the genesis hash + const genesisHash = await connection.getGenesisHash(); + + // Compare the genesis hash with known cluster genesis hashes + switch (genesisHash) { + case '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d': + return 'mainnet-beta'; + case 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG': + return 'testnet'; + case '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY': + return 'devnet'; + default: + // If it doesn't match any known cluster, it's likely localhost + return 'localhost'; + } +} + +export const getBase64FromDatabytes = (dataBytes: Buffer, dataType: string) => { + console.log({ dataBytes }); + const base = dataBytes.toString('base64'); + return `data:${dataType};base64,${base}`; +}; From 3746626e201f3edfe6c66b4816288ff235098fd1 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Fri, 11 Oct 2024 17:03:36 -0300 Subject: [PATCH 23/29] restructure divided on different nature phases --- .prettierrc.js | 10 +- Anchor.toml | 9 - docs/book.toml | 9 - .../20231130-fairlaunch-base-architecture.pdf | Bin 63153 -> 0 bytes docs/src/SUMMARY.md | 6 - docs/src/chapter_1.md | 95 -- docs/src/creator_machine.md | 3 - docs/src/data_model.md | 17 - docs/src/programs.md | 10 - js-tests/creator.ts | 176 ---- js-tests/mint.ts | 144 --- package.json | 38 +- .../libreplex_editions_controls/src/errors.rs | 3 + .../src/instructions/add_phase.rs | 2 + .../src/instructions/mint_with_controls.rs | 8 +- .../libreplex_editions_controls/src/state.rs | 2 + target/types/libreplex_editions.ts | 50 +- target/types/libreplex_editions_controls.ts | 33 +- test/tests/editions_controls.test.ts | 968 +++++++----------- test/utils/getters.ts | 2 +- test/utils/types.ts | 11 + test/utils/utils.ts | 7 - 22 files changed, 439 insertions(+), 1164 deletions(-) delete mode 100644 docs/book.toml delete mode 100644 docs/pdf/20231130-fairlaunch-base-architecture.pdf delete mode 100644 docs/src/SUMMARY.md delete mode 100644 docs/src/chapter_1.md delete mode 100644 docs/src/creator_machine.md delete mode 100644 docs/src/data_model.md delete mode 100644 docs/src/programs.md delete mode 100644 js-tests/creator.ts delete mode 100644 js-tests/mint.ts diff --git a/.prettierrc.js b/.prettierrc.js index 57b18d55..13ee303a 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,6 +1,8 @@ module.exports = { - trailingComma: 'es5', - tabWidth: 2, - semi: true, - singleQuote: true, + trailingComma: 'es5', + tabWidth: 2, + printWidth: 160, + + semi: true, + singleQuote: true, }; diff --git a/Anchor.toml b/Anchor.toml index 3fec3ad9..872be242 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -5,17 +5,8 @@ resolution = true skip-lint = false [programs.localnet] -libreplex_creator = "78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM" -libreplex_creator_controls = "G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV" -libreplex_default_renderer = "7oHj4YFq4JhxVc1BvHqvZma5evSn8hkXwFHPoTQ1PB4f" libreplex_editions = "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD" libreplex_editions_controls = "CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN" -libreplex_inscriptions = "GAc6iGWe4D8ZDT3xqJXysn3wfARrpz9eLUC1jquvd3Q1" -libreplex_legacy_inscribers = "Leg1xVbrpq5gY6mprak3Ud4q4mBwcJi5C9ZruYjWv7n" -libreplex_metadata = "LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p" -libreplex_monoswap_deprecated = "6taGmaEfZx27c25Pk5BwJbDCNNsirVranzWYAeNEhvHi" -libreplex_nft = "3oR36M1JQ9LHM1xykKTHDvhuYVM5mDAqRmq989zc1Pzi" -libreplex_shop = "ListjawGEdhxuAErSyYwcTEGWQswFoi6FScnGG1RKSB" [registry] url = "https://api.apr.dev" diff --git a/docs/book.toml b/docs/book.toml deleted file mode 100644 index 62fad5f9..00000000 --- a/docs/book.toml +++ /dev/null @@ -1,9 +0,0 @@ -[book] -authors = ["FallingHazard"] -language = "en" -multilingual = false -src = "src" -title = "Libreplex Docs" - -[output.html] -default-theme = "rust" \ No newline at end of file diff --git a/docs/pdf/20231130-fairlaunch-base-architecture.pdf b/docs/pdf/20231130-fairlaunch-base-architecture.pdf deleted file mode 100644 index b5003328660faa65d9d081a2a2420cfce2f58475..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63153 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 E4=E)*d;kCd diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md deleted file mode 100644 index bcba8905..00000000 --- a/docs/src/SUMMARY.md +++ /dev/null @@ -1,6 +0,0 @@ -# Summary - -- [Quickstart](./chapter_1.md) -- [Programs](./programs.md) -- [Data Model](./data_model.md) -- [Creator Machine](./creator_machine.md) diff --git a/docs/src/chapter_1.md b/docs/src/chapter_1.md deleted file mode 100644 index e23e9800..00000000 --- a/docs/src/chapter_1.md +++ /dev/null @@ -1,95 +0,0 @@ -# Quickstart - - -Install the [SDK](https://www.npmjs.com/package/@libreplex/sdk) - - -``` -import {mintSingle} from "@libreplex/sdk" -import * as anchor from "@coral-xyz/anchor"; - -const provider = anchor.AnchorProvider.env() - -const {method, mint} = (await mintSingle({ - provider, - mintData: { - assetUrl: { - type: "jsonUrl", - value: "COOL.com" - }, - name: "COOL", - symbol: "COOL", - } -})) - -await method.rpc() - -``` - -## Creating a collection/group. - - - -``` -import {mintSingle} from "@libreplex/sdk" -import * as anchor from "@coral-xyz/anchor"; - -const provider = anchor.AnchorProvider.env() - -const {method, group} = await setupGroup({ - connector: { - type: "provider", - provider, - }, - groupAuthority: me, - input: { - description: "A very cool group", - name: "COOLIO", - symbol: "GRP", - url: "COOL.com", - royalties: { - bps: 0, - shares: [{ - recipient: me, - share: 100, - }], - }, - permittedSigners: [], - onChainAttributes: [], - }, - }) - -await method.rpc() - -``` - -## Minting to a group. - -``` -import {mintSingle} from "@libreplex/sdk" -import * as anchor from "@coral-xyz/anchor"; - -const provider = anchor.AnchorProvider.env() - - -const group = "....Some Public Key..." - -const {method, mint} = (await mintSingle({ - provider, - mintData: { - assetUrl: { - type: "jsonUrl", - value: "COOL.com" - }, - name: "COOL", - symbol: "COOL", - }, - mintToGroup: { - group, - checkValidGroup: false, - } -})) - -await method.rpc() - -``` diff --git a/docs/src/creator_machine.md b/docs/src/creator_machine.md deleted file mode 100644 index 2adec254..00000000 --- a/docs/src/creator_machine.md +++ /dev/null @@ -1,3 +0,0 @@ -# Creator Machine - -Work in progress... \ No newline at end of file diff --git a/docs/src/data_model.md b/docs/src/data_model.md deleted file mode 100644 index 9bc591f4..00000000 --- a/docs/src/data_model.md +++ /dev/null @@ -1,17 +0,0 @@ -# Data Model - -Libreplex provides Metadata for [Token-2022](https://spl.solana.com/token-2022) mints. - -## Metadata structure - -| Field Name | Type | Extra Info | -|:-------------------------------------------|:--------------------------------------------------------------------------|--------------------------------------------------------------------------| -| Mint | [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) | -| Update Authority | [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) | -| Creator | [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) | Cannot be changed -| Is Mutable | Bool | -| Group ( Collection ) | Optional [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) | -| Name | String | -| Symbol | String | -| Asset | | -| Extension | | \ No newline at end of file diff --git a/docs/src/programs.md b/docs/src/programs.md deleted file mode 100644 index 603a46e8..00000000 --- a/docs/src/programs.md +++ /dev/null @@ -1,10 +0,0 @@ -# Programs - -| Name | Address | -|:-------------------------------------------|:--------------------------------------------------------------------------| -| Metadata | LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p | -| Creator | 78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM | -| Creator Controls | G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV | -| Shop | ListjawGEdhxuAErSyYwcTEGWQswFoi6FScnGG1RKSB | -| Inscriptions | inscokhJarcjaEs59QbQ7hYjrKz25LEPRfCbP8EmdUp | -| NFT | 9SXDHUdtfvBGT3H2uPCNEkxmWREoqdeS1qdBudLDD6KX | diff --git a/js-tests/creator.ts b/js-tests/creator.ts deleted file mode 100644 index 459b2040..00000000 --- a/js-tests/creator.ts +++ /dev/null @@ -1,176 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import { Program } from '@coral-xyz/anchor'; -import { - Keypair, - PublicKey, - SYSVAR_EPOCH_SCHEDULE_PUBKEY -} from '@solana/web3.js'; -import { LibreplexCreator } from '../target/types/libreplex_creator'; -import { LibreplexCreatorControls } from '../target/types/libreplex_creator_controls'; -import { LibreplexMetadata } from '../target/types/libreplex_metadata'; -import { LibreplexNft } from '../target/types/libreplex_nft'; - -import { - LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID, - mintFromCreatorController, - setupCreatorWithCustomSalePhases, - setupCollection -} from '@libreplex/sdk'; -import { sha256 } from 'js-sha256'; - -describe('libreplex creator', () => { - const provider = anchor.AnchorProvider.env(); - anchor.setProvider(provider); - - const program = anchor.workspace - .LibreplexCreator as Program; - const metadataProgram = anchor.workspace - .LibreplexMetadata as Program; - const nftProgram = anchor.workspace.LibreplexNft as Program; - const controllerProgram = anchor.workspace - .LibreplexCreatorControls as Program; - - console.log(Object.keys(anchor.workspace)); - - const authority = anchor.getProvider().publicKey; - - it('has minted', async () => { - const groupSeed = Keypair.generate(); - - console.log('Setting up group'); - const grpSetupCtx = await setupCollection({ - connector: { - type: 'provider', - provider, - }, - groupSeedKp: groupSeed, - groupAuthority: program.provider.publicKey as PublicKey, - input: { - description: 'A very cool group', - name: 'COOL GROUP', - symbol: 'COOL', - url: 'COOL.com/group', - royalties: { - bps: 100, - shares: [ - { - recipient: program.provider.publicKey as PublicKey, - share: 100, - }, - ], - }, - }, - }); - - const collection = grpSetupCtx.collection; - await grpSetupCtx.method.rpc({ - skipPreflight: false, - }); - - const startTime = new Date(); - startTime.setDate(startTime.getDate() - 1); - - const pingDiscrim = Buffer.from(sha256.digest('global:ping')).slice(0, 8); - - console.log('Setting up controller'); - const creatorControllerCtx = await setupCreatorWithCustomSalePhases( - { - collection, - metadataProgram, - mintAuthority: program.provider.publicKey as PublicKey, - program, - creatorData: { - baseName: 'COOL #', - baseUrl: { - type: 'json-prefix', - url: 'COOL.com/', - }, - description: 'The coolest metadatas', - ordered: false, - symbol: 'COOL', - supply: 2000, - }, - }, - controllerProgram, - [ - { - end: null, - start: startTime, - label: 'Public', - /* No controls anyone can mint and it's free*/ - control: [ - { - name: 'CustomProgram', - instructionData: pingDiscrim, - label: 'Ping', - programId: LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID, - remainingAccountsMetas: [ - { - isSigner: false, - isWritable: true, - key: { - type: 'key', - value: Keypair.generate().publicKey, - }, - }, - { - isSigner: false, - isWritable: true, - key: { - type: 'seedDerivation', - programId: SYSVAR_EPOCH_SCHEDULE_PUBKEY, - seeds: [ - { - type: 'mintPlaceHolder', - }, - ], - }, - }, - ], - }, - ], - }, - ] - ); - - await creatorControllerCtx.method.rpc(); - - const { creator, minterNumbers, creatorController } = creatorControllerCtx; - - // const controllerData = await controllerProgram.account.creatorController.fetch(creatorController) - - console.log('Creator initialised'); - - { - // Set some dummy values for transfer hook. - const mintMethod = await mintFromCreatorController({ - addTransferHookToMint: { - authority: program.provider.publicKey as PublicKey, - programId: program.provider.publicKey as PublicKey, - }, - creatorController: creatorControllerCtx.creatorController, - creatorControllerProgram: controllerProgram, - creatorProgram: program, - }); - - const txId = await mintMethod.method.rpc({ - skipPreflight: true, - }); - - console.log(txId); - } - - { - // Mint without transfer hook - const mintMethod = await mintFromCreatorController({ - creatorController: creatorControllerCtx.creatorController, - creatorControllerProgram: controllerProgram, - creatorProgram: program, - }); - - await mintMethod.method.rpc({ - skipPreflight: true, - }); - } - }); -}); diff --git a/js-tests/mint.ts b/js-tests/mint.ts deleted file mode 100644 index c8a06eae..00000000 --- a/js-tests/mint.ts +++ /dev/null @@ -1,144 +0,0 @@ -import * as anchor from "@coral-xyz/anchor"; -import { BN, Program } from "@coral-xyz/anchor"; -import { AnchorProvider, getProvider } from "@coral-xyz/anchor"; -import {mintSingle, setupCollection, setUserPermissionsForGroup, UserPermission, updateCollectionAuthority} from "@libreplex/sdk" -import { Keypair } from "@solana/web3.js"; - - -describe("mint", () => { - const provider = anchor.AnchorProvider.env() - anchor.setProvider(provider); - - it("has minted", async () => { - const mintCtx = (await mintSingle({ - provider, - mintData: { - assetUrl: { - type: "jsonUrl", - value: "COOL.com" - }, - name: "COOL", - symbol: "COOL", - } - })) - - await mintCtx.method.rpc() - }) - - it ("has minted to a collection", async () => { - const me = provider.publicKey - - const grpCtx = await setupCollection({ - connector: { - type: "provider", - provider, - }, - groupAuthority: me, - input: { - description: "A very cool group", - name: "COOLIO", - symbol: "GRP", - url: "COOL.com", - royalties: { - bps: 0, - shares: [{ - recipient: me, - share: 100, - }], - }, - permittedSigners: [], - onChainAttributes: [], - }, - }) - - await grpCtx.method.rpc() - - const collection = grpCtx.collection - - const mintCtx = (await mintSingle({ - provider, - mintData: { - assetUrl: { - type: "jsonUrl", - value: "COOL.com" - }, - name: "COOL", - symbol: "COOL", - }, - mintToCollection: { - collection, - checkValidGroup: true, - } - })) - - await mintCtx.method.rpc() - }) - - it ("has minted to a collection where I am not the authority", async () => { - const me = provider.publicKey - - const grpCtx = await setupCollection({ - connector: { - type: "provider", - provider, - }, - groupAuthority: me, - input: { - description: "A very cool group", - name: "COOLIO", - symbol: "GRP", - url: "COOL.com", - royalties: { - bps: 0, - shares: [], - }, - permittedSigners: [], - onChainAttributes: [], - }, - }) - - await grpCtx.method.rpc(); - - const collection = grpCtx.collection; - - await (await setUserPermissionsForGroup({ - connector: { - type: "provider", - provider, - }, - collection, - user: me, - groupUpdateAuthority: me, - permissions: [UserPermission.AddToGroup] - })).rpc() - - await (await updateCollectionAuthority({ - collection, - new_authority: Keypair.generate().publicKey, - connector: { - type: "provider", - provider, - }, - })).rpc() - - const mintCtx = (await mintSingle({ - provider, - mintData: { - assetUrl: { - type: "jsonUrl", - value: "COOL.com" - }, - name: "COOL", - symbol: "COOL", - }, - mintToCollection: { - collection, - checkValidGroup: true, - } - })) - - await mintCtx.method.rpc() - }) - - -}) \ No newline at end of file diff --git a/package.json b/package.json index 2ccfc4df..6c4addcf 100644 --- a/package.json +++ b/package.json @@ -8,46 +8,20 @@ "build-interfaces": "cd packages/libreplex-idls && yarn package && yarn build && cd .. && cd libreplex-sdk && yarn build && cd ../..", "build": "lerna run clean && lerna run build", "libre-cli": "ts-node src/cli/index.ts", - "libreplex_nifty_hybrid:deploy:devnet": "anchor deploy -p libreplex_nifty_hybrid --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_monoswap:deploy:devnet": "anchor deploy -p libreplex_monoswap --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_monoswap:deploy:mainnet": "anchor deploy -p libreplex_monoswap --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_pipelines:deploy:devnet": "anchor deploy -p libreplex_pipelines --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_pipelines:deploy:mainnet": "anchor deploy -p libreplex_pipelines --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_editions:deploy:devnet": "anchor deploy -p libreplex_editions --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_editions_controls:deploy:devnet": "anchor deploy -p libreplex_editions_controls --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/id.json", - "libreplex_editions:deploy:mainnet": "anchor deploy -p libreplex_editions --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_editions:deploy:eclipse": "anchor deploy -p libreplex_editions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_editions:deploy:eclipse-dev": "anchor deploy -p libreplex_editions --provider.cluster https://staging-rpc.dev.eclipsenetwork.xyz --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_liquidity:deploy:devnet": "anchor deploy -p libreplex_liquidity --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_liquidity:deploy:mainnet": "anchor build -p libreplex_liquidity&& anchor deploy -p libreplex_liquidity --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_inscriptions:deploy:eclipse-testnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_inscriptions:deploy:localnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster http://localhost:8899 --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_metadata:deploy:localnet": "anchor build -p libreplex_metadata && anchor deploy -p libreplex_metadata --provider.cluster http://localhost:8899 --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_creator:deploy:devnet": "anchor build -p libreplex_creator && anchor deploy -p libreplex_creator --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_creator_controls:deploy:devnet": "anchor build -p libreplex_creator_controls && anchor deploy -p libreplex_creator_controls --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_inscriptions:deploy:devnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_metadata:deploy:devnet": "anchor build -p libreplex_metadata && anchor deploy -p libreplex_metadata --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_shop:deploy:devnet": "anchor build -p libreplex_shop && anchor deploy -p libreplex_shop --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_legacy:deploy:devnet": "anchor build -p libreplex_legacy && anchor deploy -p libreplex_legacy --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_legacy:deploy:mainnet": "anchor build -p libreplex_legacy && anchor deploy -p libreplex_legacy --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_metadata:deploy:mainnet": "anchor build -p libreplex_metadata && anchor deploy -p libreplex_metadata --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_creator:deploy:mainnet": "anchor build -p libreplex_creator && anchor deploy -p libreplex_creator --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_inscriptions:deploy:mainnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_creator_controls:deploy:mainnet": "anchor build -p libreplex_creator_controls && anchor deploy -p libreplex_creator_controls --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", - "libreplex_shop:deploy:mainnet": "anchor build -p libreplex_creator_controls && anchor deploy -p libreplex_creator_controls --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json", + "lint": "next lint", "libreplex_editions:deploy:eclipse-dev2": "anchor deploy -p libreplex_editions --provider.cluster https://staging-rpc.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json", "libreplex_editions_controls:deploy:eclipse-dev2": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://staging-rpc.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json", + + "libreplex_editions:deploy:eclipse-testnet": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json", + "libreplex_editions_controls:deploy:eclipse-testnet": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json", "libreplex_editions:deploy:solana-dev-net": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/id.json", "libreplex_editions_controls:solana-dev-net": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/id.json", "libreplex_editions:deploy:solana-test-net": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://api.testnet.solana.com --provider.wallet ~/.config/solana/id.json", - "libreplex_editions_controls:solana-test-net": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://api.testnet.solana.com --provider.wallet ~/.config/solana/id.json", - - "libreplex_editions:deploy:eclipse-testnet": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json", - "libreplex_editions_controls:deploy:eclipse-testnet": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json", - "lint": "next lint" + "libreplex_editions_controls:solana-test-net": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://api.testnet.solana.com --provider.wallet ~/.config/solana/id.json" + }, "dependencies": { "@coral-xyz/anchor": "0.30.0", diff --git a/programs/libreplex_editions_controls/src/errors.rs b/programs/libreplex_editions_controls/src/errors.rs index 5dd706bf..6536f7a0 100644 --- a/programs/libreplex_editions_controls/src/errors.rs +++ b/programs/libreplex_editions_controls/src/errors.rs @@ -53,6 +53,9 @@ pub enum EditionsControlsError { #[msg("Invalid phase index.")] InvalidPhaseIndex, + #[msg("Private phase but no merkle proof provided")] + PrivatePhaseNoProof, + #[msg("Merkle root not set for allow list mint")] MerkleRootNotSet, diff --git a/programs/libreplex_editions_controls/src/instructions/add_phase.rs b/programs/libreplex_editions_controls/src/instructions/add_phase.rs index 8c0c63da..54569e81 100644 --- a/programs/libreplex_editions_controls/src/instructions/add_phase.rs +++ b/programs/libreplex_editions_controls/src/instructions/add_phase.rs @@ -12,6 +12,7 @@ pub struct InitialisePhaseInput { pub max_mints_per_wallet: u64, pub max_mints_total: u64, pub end_time: i64, + pub is_private: bool, pub merkle_root: Option<[u8; 32]>, } @@ -58,6 +59,7 @@ pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Resu end_time: input.end_time, max_mints_total: input.max_mints_total, current_mints: 0, + is_private: input.is_private, merkle_root: input.merkle_root, padding: [0; 200], }); diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index 826bfe3e..08ad846f 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -165,6 +165,7 @@ pub fn mint_with_controls( let mut price_amount = editions_controls.phases[mint_input.phase_index as usize].price_amount; // Determine if is a normal mint or an allow list mint and perform the appropriate constraints + // depending on if a merkle proof was provided if mint_input.merkle_proof.is_some() { msg!("program: executing allow list mint"); // Check allow list constraints @@ -179,7 +180,12 @@ pub fn mint_with_controls( msg!("program: allow list constraints passed"); // Override the price amount with the allow list price price_amount = mint_input.allow_list_price.unwrap_or(0); - } + } else { + // if the phase is private, and the merkle proof was not provided, throw error + if editions_controls.phases[mint_input.phase_index as usize].is_private { + return Err(EditionsControlsError::PrivatePhaseNoProof.into()); + } + } // Update minter and phase states update_minter_and_phase_stats( diff --git a/programs/libreplex_editions_controls/src/state.rs b/programs/libreplex_editions_controls/src/state.rs index 67e7cdae..81591369 100644 --- a/programs/libreplex_editions_controls/src/state.rs +++ b/programs/libreplex_editions_controls/src/state.rs @@ -30,6 +30,7 @@ pub struct Phase { pub max_mints_total: u64, // set to 0 for unlimited pub end_time: i64, // set to i64::MAX for unlimited pub current_mints: u64, + pub is_private: bool, pub merkle_root: Option<[u8; 32]>, pub padding: [u8; 200] } @@ -44,6 +45,7 @@ impl Phase { + 8 // max_mints_total + 8 // end_time + 8 // current_mints + + 1 // is_private + 32 + 1 // merkle_root + 200; // padding } diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts index c40adcad..8a0f7049 100644 --- a/target/types/libreplex_editions.ts +++ b/target/types/libreplex_editions.ts @@ -694,63 +694,53 @@ export type LibreplexEditions = { "errors": [ { "code": 6000, - "name": "sizeExceedsMaxSize", - "msg": "Collection size exceeds max size." + "name": "tickerTooLong", + "msg": "Ticker too long" }, { "code": 6001, - "name": "maxSizeBelowCurrentSize", - "msg": "Max size cannot be reduced below current size." + "name": "mintTemplateTooLong", + "msg": "Mint template too long" }, { "code": 6002, - "name": "creatorShareInvalid", - "msg": "Creators shares must add up to 100." + "name": "deploymentTemplateTooLong", + "msg": "Deployment template too long" }, { "code": 6003, - "name": "missingApproveAccount", - "msg": "Missing approve account." + "name": "rootTypeTooLong", + "msg": "Root type too long" }, { "code": 6004, - "name": "expiredApproveAccount", - "msg": "Approve account has expired." + "name": "mintedOut", + "msg": "Minted out" }, { "code": 6005, - "name": "invalidField", - "msg": "Invalid field. You cannot use a public key as a field." + "name": "legacyMigrationsAreMintedOut", + "msg": "Legacy migrations are minted out" }, { "code": 6006, - "name": "creatorAddressInvalid", - "msg": "The Address you provided is invalid. Please provide a valid address." + "name": "missingGlobalTreeDelegate", + "msg": "Global tree delegate is missing" }, { "code": 6007, - "name": "royaltyBasisPointsInvalid", - "msg": "Royalty basis points must be less than or equal to 10000." + "name": "incorrectMintType", + "msg": "Incorrect mint type" }, { "code": 6008, - "name": "platformFeeBasisPointsInvalid", - "msg": "Platform fee basis points must be less than or equal to 10000." + "name": "invalidMetadata", + "msg": "Invalid Metadata" }, { "code": 6009, - "name": "recipientShareInvalid", - "msg": "Recipient shares must add up to 100." - }, - { - "code": 6010, - "name": "reservedField", - "msg": "The provided field is invalid or reserved." - }, - { - "code": 6011, - "name": "invalidNumberOfRecipients", - "msg": "Invalid number of platform fee recipients. Exactly 5 recipients are required." + "name": "creatorFeeTooHigh", + "msg": "Creator fee too high" } ], "types": [ diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts index 27d591c0..758cdd30 100644 --- a/target/types/libreplex_editions_controls.ts +++ b/target/types/libreplex_editions_controls.ts @@ -739,56 +739,61 @@ export type LibreplexEditionsControls = { }, { "code": 6017, + "name": "privatePhaseNoProof", + "msg": "Private phase but no merkle proof provided" + }, + { + "code": 6018, "name": "merkleRootNotSet", "msg": "Merkle root not set for allow list mint" }, { - "code": 6018, + "code": 6019, "name": "merkleProofRequired", "msg": "Merkle proof required for allow list mint" }, { - "code": 6019, + "code": 6020, "name": "allowListPriceAndMaxClaimsRequired", "msg": "Allow list price and max claims are required for allow list mint" }, { - "code": 6020, + "code": 6021, "name": "invalidMerkleProof", "msg": "Invalid merkle proof" }, { - "code": 6021, + "code": 6022, "name": "exceededAllowListMaxClaims", "msg": "This wallet has exceeded allow list max_claims in the current phase" }, { - "code": 6022, + "code": 6023, "name": "phaseNotActive", "msg": "Phase not active" }, { - "code": 6023, + "code": 6024, "name": "phaseNotStarted", "msg": "Phase not yet started" }, { - "code": 6024, + "code": 6025, "name": "phaseAlreadyFinished", "msg": "Phase already finished" }, { - "code": 6025, + "code": 6026, "name": "exceededMaxMintsForPhase", "msg": "Exceeded max mints for this phase" }, { - "code": 6026, + "code": 6027, "name": "exceededWalletMaxMintsForPhase", "msg": "Exceeded wallet max mints for this phase" }, { - "code": 6027, + "code": 6028, "name": "exceededWalletMaxMintsForCollection", "msg": "Exceeded wallet max mints for the collection" } @@ -1064,6 +1069,10 @@ export type LibreplexEditionsControls = { "name": "endTime", "type": "i64" }, + { + "name": "isPrivate", + "type": "bool" + }, { "name": "merkleRoot", "type": { @@ -1177,6 +1186,10 @@ export type LibreplexEditionsControls = { "name": "currentMints", "type": "u64" }, + { + "name": "isPrivate", + "type": "bool" + }, { "name": "merkleRoot", "type": { diff --git a/test/tests/editions_controls.test.ts b/test/tests/editions_controls.test.ts index 8fc037a8..a63e7499 100644 --- a/test/tests/editions_controls.test.ts +++ b/test/tests/editions_controls.test.ts @@ -1,17 +1,7 @@ import * as anchor from '@coral-xyz/anchor'; import { Program } from '@coral-xyz/anchor'; -import { - PublicKey, - Keypair, - SystemProgram, - ComputeBudgetProgram, - LAMPORTS_PER_SOL, -} from '@solana/web3.js'; -import { - ASSOCIATED_TOKEN_PROGRAM_ID, - getAssociatedTokenAddressSync, - TOKEN_2022_PROGRAM_ID, -} from '@solana/spl-token'; +import { PublicKey, Keypair, SystemProgram, ComputeBudgetProgram, LAMPORTS_PER_SOL } from '@solana/web3.js'; +import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token'; import { LibreplexEditionsControls } from '../../target/types/libreplex_editions_controls'; import { LibreplexEditions } from '../../target/types/libreplex_editions'; import { expect } from 'chai'; @@ -31,22 +21,20 @@ import { import { Transaction } from '@solana/web3.js'; // devnote: try to make tests don't rely on hard addresses but on dynamic runtime ids. import { TOKEN_GROUP_EXTENSION_PROGRAM_ID } from '../../constants'; -import { - getEditionsPda, - getEditionsControlsPda, - getHashlistPda, - getHashlistMarkerPda, - getMinterStatsPda, - getMinterStatsPhasePda, -} from '../utils/pdas'; -import { CollectionConfig, AllowListConfig } from '../utils/types'; +import { getEditionsPda, getEditionsControlsPda, getHashlistPda, getHashlistMarkerPda, getMinterStatsPda, getMinterStatsPhasePda } from '../utils/pdas'; +import { CollectionConfig, AllowListConfig, PhaseConfig } from '../utils/types'; -const VERBOSE_LOGGING = false; +const VERBOSE_LOGGING = true; describe('Editions Controls Test Suite', () => { const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); + // Modify compute units + const modifiedComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ + units: 800000, + }); + let editionsControlsProgram: Program; let editionsProgram: Program; @@ -75,10 +63,8 @@ describe('Editions Controls Test Suite', () => { console.log('Cluster:', cluster); } - editionsControlsProgram = anchor.workspace - .LibreplexEditionsControls as Program; - editionsProgram = anchor.workspace - .LibreplexEditions as Program; + editionsControlsProgram = anchor.workspace.LibreplexEditionsControls as Program; + editionsProgram = anchor.workspace.LibreplexEditions as Program; payer = (provider.wallet as anchor.Wallet).payer; creator1 = Keypair.generate(); @@ -132,14 +118,8 @@ describe('Editions Controls Test Suite', () => { console.log('Collection config: ', collectionConfig); } - editionsPda = getEditionsPda( - collectionConfig.symbol, - editionsProgram.programId - ); - editionsControlsPda = getEditionsControlsPda( - editionsPda, - editionsControlsProgram.programId - ); + editionsPda = getEditionsPda(collectionConfig.symbol, editionsProgram.programId); + editionsControlsPda = getEditionsControlsPda(editionsPda, editionsControlsProgram.programId); hashlistPda = getHashlistPda(editionsPda, editionsProgram.programId); }); @@ -147,39 +127,25 @@ describe('Editions Controls Test Suite', () => { before(async () => { minter1 = Keypair.fromSecretKey( new Uint8Array([ - 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, - 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, - 218, 104, 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, - 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, - 221, 120, 54, + 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, 218, 104, 164, 83, 51, 23, + 177, 193, 29, 252, 241, 86, 132, 173, 155, 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221, 120, 54, ]) ); minter2 = Keypair.fromSecretKey( new Uint8Array([ - 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, - 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, - 220, 186, 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, - 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, - 135, 6, + 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, 220, 186, 39, 133, 92, 39, + 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6, ]) ); allowListConfig = { - merkleRoot: Buffer.from([ - 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, - 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, - 245, - ]), + merkleRoot: Buffer.from([125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, 245]), list: [ { address: minter1.publicKey, price: new anchor.BN(500000), // 0.005 SOL max_claims: new anchor.BN(3), proof: [ - Buffer.from([ - 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, - 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, - 232, 172, 235, 168, - ]), + Buffer.from([64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, 232, 172, 235, 168]), ], }, { @@ -187,11 +153,7 @@ describe('Editions Controls Test Suite', () => { price: new anchor.BN(500000), // 0.005 SOL max_claims: new anchor.BN(3), proof: [ - Buffer.from([ - 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, - 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, - 17, 125, 63, 188, - ]), + Buffer.from([86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, 17, 125, 63, 188]), ], }, ], @@ -201,35 +163,20 @@ describe('Editions Controls Test Suite', () => { // Perform needed airdrops to minter1, treasury and platformFeeRecipient before(async () => { // Airdrop SOL to minter1 - const airdropSignature = await provider.connection.requestAirdrop( - minter1.publicKey, - 1 * LAMPORTS_PER_SOL - ); + const airdropSignature = await provider.connection.requestAirdrop(minter1.publicKey, 1 * LAMPORTS_PER_SOL); await provider.connection.confirmTransaction(airdropSignature); // Airdrop SOL to minter2 - const minter2AirdropSignature = await provider.connection.requestAirdrop( - minter2.publicKey, - 10 * LAMPORTS_PER_SOL - ); + const minter2AirdropSignature = await provider.connection.requestAirdrop(minter2.publicKey, 10 * LAMPORTS_PER_SOL); await provider.connection.confirmTransaction(minter2AirdropSignature); // Airdrop SOL to treasury - const treasuryAirdropSignature = await provider.connection.requestAirdrop( - collectionConfig.treasury, - 1 * LAMPORTS_PER_SOL - ); + const treasuryAirdropSignature = await provider.connection.requestAirdrop(collectionConfig.treasury, 1 * LAMPORTS_PER_SOL); await provider.connection.confirmTransaction(treasuryAirdropSignature); // Airdrop SOL to platformFeeRecipient - const platformFeeRecipientAirdropSignature = - await provider.connection.requestAirdrop( - platformFeeAdmin.publicKey, - 1 * LAMPORTS_PER_SOL - ); - await provider.connection.confirmTransaction( - platformFeeRecipientAirdropSignature - ); + const platformFeeRecipientAirdropSignature = await provider.connection.requestAirdrop(platformFeeAdmin.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(platformFeeRecipientAirdropSignature); }); describe('Deploying', () => { @@ -266,33 +213,18 @@ describe('Editions Controls Test Suite', () => { systemProgram: SystemProgram.programId, tokenProgram: TOKEN_2022_PROGRAM_ID, libreplexEditionsProgram: editionsProgram.programId, - groupExtensionProgram: new PublicKey( - '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V' - ), + groupExtensionProgram: new PublicKey('5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'), }) .instruction(); - const transaction = new Transaction() - .add(modifyComputeUnits) - .add(initialiseIx); + const transaction = new Transaction().add(modifyComputeUnits).add(initialiseIx); await provider.sendAndConfirm(transaction, [groupMint, group, payer]); // Fetch updated state - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const metadata = await getTokenMetadata( - provider.connection, - groupMint.publicKey - ); + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const metadata = await getTokenMetadata(provider.connection, groupMint.publicKey); if (VERBOSE_LOGGING) { logEditions(editionsDecoded); logEditionsControls(editionsControlsDecoded); @@ -301,40 +233,22 @@ describe('Editions Controls Test Suite', () => { // Verify Editions deployment expect(editionsDecoded.data.symbol).to.equal(collectionConfig.symbol); - expect(editionsDecoded.data.creator.toBase58()).to.equal( - editionsControlsPda.toBase58() - ); - expect(editionsDecoded.data.maxNumberOfTokens.toString()).to.equal( - collectionConfig.maxNumberOfTokens.toString() - ); - expect(editionsDecoded.data.itemBaseName).to.equal( - collectionConfig.itemBaseName - ); - expect(editionsDecoded.data.itemBaseUri).to.equal( - collectionConfig.itemBaseUri - ); + expect(editionsDecoded.data.creator.toBase58()).to.equal(editionsControlsPda.toBase58()); + expect(editionsDecoded.data.maxNumberOfTokens.toString()).to.equal(collectionConfig.maxNumberOfTokens.toString()); + expect(editionsDecoded.data.itemBaseName).to.equal(collectionConfig.itemBaseName); + expect(editionsDecoded.data.itemBaseUri).to.equal(collectionConfig.itemBaseUri); // Verify EditionsControls deployment - expect( - editionsControlsDecoded.data.editionsDeployment.toBase58() - ).to.equal(editionsPda.toBase58()); - expect(editionsControlsDecoded.data.creator.toBase58()).to.equal( - payer.publicKey.toBase58() - ); - expect(editionsControlsDecoded.data.treasury.toBase58()).to.equal( - collectionConfig.treasury.toBase58() - ); - expect(Number(editionsControlsDecoded.data.maxMintsPerWallet)).to.equal( - Number(collectionConfig.maxMintsPerWallet) - ); + expect(editionsControlsDecoded.data.editionsDeployment.toBase58()).to.equal(editionsPda.toBase58()); + expect(editionsControlsDecoded.data.creator.toBase58()).to.equal(payer.publicKey.toBase58()); + expect(editionsControlsDecoded.data.treasury.toBase58()).to.equal(collectionConfig.treasury.toBase58()); + expect(Number(editionsControlsDecoded.data.maxMintsPerWallet)).to.equal(Number(collectionConfig.maxMintsPerWallet)); expect(editionsControlsDecoded.data.phases.length).to.equal(0); // Verify metadata expect(metadata.name).to.equal(collectionConfig.collectionName); expect(metadata.uri).to.equal(collectionConfig.collectionUri); - expect(metadata.mint.toBase58()).to.equal( - groupMint.publicKey.toBase58() - ); + expect(metadata.mint.toBase58()).to.equal(groupMint.publicKey.toBase58()); // Verify that every key in extraMeta is present in metadata.additionalMetadata collectionConfig.extraMeta.forEach((meta) => { expect(metadata.additionalMetadata).to.have.property(meta.field); @@ -350,19 +264,17 @@ describe('Editions Controls Test Suite', () => { }); describe('Adding phases', () => { - it('Should add a phase without allowlist', async () => { - const phaseConfig = { + it('Should add a private phase with allowlist. [Phase Index 0]: Open: Not Available, Allowlist: 0.05 SOL', async () => { + const phaseConfig: PhaseConfig = { maxMintsPerWallet: new anchor.BN(100), maxMintsTotal: new anchor.BN(1000), - priceAmount: new anchor.BN(10000000), // 0.01 SOL + priceAmount: new anchor.BN(0), // Not openly buyable, only available to allowlist startTime: new anchor.BN(new Date().getTime() / 1000), endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now - priceToken: new PublicKey( - 'So11111111111111111111111111111111111111112' - ), - merkleRoot: null, + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + isPrivate: true, + merkleRoot: allowListConfig.merkleRoot, }; - const phaseIx = await editionsControlsProgram.methods .addPhase(phaseConfig) .accountsStrict({ @@ -379,38 +291,27 @@ describe('Editions Controls Test Suite', () => { const transaction = new Transaction().add(phaseIx); await provider.sendAndConfirm(transaction, [payer]); - // get state - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); if (VERBOSE_LOGGING) { logEditionsControls(editionsControlsDecoded); } // verify state expect(editionsControlsDecoded.data.phases.length).to.equal(1); - expect( - editionsControlsDecoded.data.phases[0].maxMintsPerWallet.toString() - ).to.equal(phaseConfig.maxMintsPerWallet.toString()); - expect( - editionsControlsDecoded.data.phases[0].maxMintsTotal.toString() - ).to.equal(phaseConfig.maxMintsTotal.toString()); }); - it('Should add a phase with allowlist', async () => { - const phaseConfig = { + it('Should add a public phase with allowlist. [Phase Index 1]: Open: O.1 SOL, Allowlist: 0.05 SOL', async () => { + const phaseConfig: PhaseConfig = { maxMintsPerWallet: new anchor.BN(100), maxMintsTotal: new anchor.BN(1000), - priceAmount: new anchor.BN(10000000), // 0.01 SOL + priceAmount: new anchor.BN(1000000), // 0.1 SOL startTime: new anchor.BN(new Date().getTime() / 1000), endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now - priceToken: new PublicKey( - 'So11111111111111111111111111111111111111112' - ), + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + isPrivate: false, merkleRoot: allowListConfig.merkleRoot, }; + const phaseIx = await editionsControlsProgram.methods .addPhase(phaseConfig) .accountsStrict({ @@ -427,33 +328,121 @@ describe('Editions Controls Test Suite', () => { const transaction = new Transaction().add(phaseIx); await provider.sendAndConfirm(transaction, [payer]); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); + // get state + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); if (VERBOSE_LOGGING) { logEditionsControls(editionsControlsDecoded); } // verify state expect(editionsControlsDecoded.data.phases.length).to.equal(2); - expect( - editionsControlsDecoded.data.phases[1].maxMintsPerWallet.toString() - ).to.equal(phaseConfig.maxMintsPerWallet.toString()); - expect( - editionsControlsDecoded.data.phases[1].maxMintsTotal.toString() - ).to.equal(phaseConfig.maxMintsTotal.toString()); + }); + + it('Should add a public phase without allowlist. [Phas Index 2]: Open: 0.05 SOL, Allowlist: Not Available', async () => { + const phaseConfig: PhaseConfig = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(1000), + priceAmount: new anchor.BN(500000), // 0.05 SOL + startTime: new anchor.BN(new Date().getTime() / 1000), + endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + isPrivate: false, + merkleRoot: null, + }; + + const phaseIx = await editionsControlsProgram.methods + .addPhase(phaseConfig) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + await provider.sendAndConfirm(transaction, [payer]); + + // get state + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + if (VERBOSE_LOGGING) { + logEditionsControls(editionsControlsDecoded); + } + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(3); }); }); describe('Minting', () => { - describe('Minting without allowlist', () => { - it('Should mint on first phase without allowlist', async () => { - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 850000, - }); + describe('Minting on a private phase with allowlist [Phase Index 0]', () => { + it('Should be able to mint with valid allowlist proof', async () => { + const mintConfig = { + phaseIndex: 0, + merkleProof: allowListConfig.list[0].proof, + allowListPrice: allowListConfig.list[0].price, + allowListMaxClaims: allowListConfig.list[0].max_claims, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 0, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter1.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter1, mint, member]); + } catch (error) { + console.error('Error in mintWithControls:', error); + throw error; + } + + // Verify state after minting + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); + + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('1'); + expect(editionsControlsDecoded.data.phases[0].currentMints.toString()).to.equal('1'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + }); + it('Should fail to mint without allowlist proof', async () => { const mintConfig = { phaseIndex: 0, merkleProof: null, @@ -464,28 +453,10 @@ describe('Editions Controls Test Suite', () => { const mint = Keypair.generate(); const member = Keypair.generate(); - const tokenAccount = getAssociatedTokenAddressSync( - mint.publicKey, - minter1.publicKey, - false, - TOKEN_2022_PROGRAM_ID - ); - const hashlistMarker = getHashlistMarkerPda( - editionsPda, - mint.publicKey, - editionsProgram.programId - ); - const minterStats = getMinterStatsPda( - editionsPda, - minter1.publicKey, - editionsControlsProgram.programId - ); - const minterStatsPhase = getMinterStatsPhasePda( - editionsPda, - minter1.publicKey, - 0, - editionsControlsProgram.programId - ); + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 0, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); const mintIx = await editionsControlsProgram.methods .mintWithControls(mintConfig) @@ -493,132 +464,116 @@ describe('Editions Controls Test Suite', () => { editionsDeployment: editionsPda, editionsControls: editionsControlsPda, hashlist: hashlistPda, - hashlistMarker, + hashlistMarker: hashlistMarkerPda, payer: minter1.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, mint: mint.publicKey, member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter1, mint, member]); + } catch (error) { + const errorString = JSON.stringify(error); + expect(errorString).to.include('Private phase but no merkle proof provided'); + } + }); + }); + + describe('Minting on a public phase with allowlist [Phase Index 1]', () => { + it('Should be able to mint with allowlist proof at discounted price', async () => { + const mintConfig = { + phaseIndex: 1, + merkleProof: allowListConfig.list[0].proof, + allowListPrice: allowListConfig.list[0].price, + allowListMaxClaims: allowListConfig.list[0].max_claims, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter1.publicKey, signer: minter1.publicKey, minter: minter1.publicKey, - minterStats, - minterStatsPhase, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, group: group.publicKey, groupMint: groupMint.publicKey, platformFeeRecipient1: platformFeeAdmin.publicKey, - groupExtensionProgram: new PublicKey( - TOKEN_GROUP_EXTENSION_PROGRAM_ID - ), - tokenAccount, - treasury: collectionConfig.treasury, - systemProgram: SystemProgram.programId, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, }) .instruction(); - const transaction = new Transaction() - .add(modifyComputeUnits) - .add(mintIx); - transaction.recentBlockhash = ( - await provider.connection.getLatestBlockhash() - ).blockhash; - transaction.feePayer = minter1.publicKey; - transaction.sign(minter1, mint, member); - const rawTransaction = transaction.serialize(); - + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); try { - const signature = await provider.connection.sendRawTransaction( - rawTransaction - ); - await provider.connection.confirmTransaction(signature); + await provider.sendAndConfirm(transaction, [minter1, mint, member]); } catch (error) { console.error('Error in mintWithControls:', error); throw error; } - // get state - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const minterStatsDecoded = await getMinterStats( - provider.connection, - minterStats, - editionsControlsProgram - ); - const minterStatsPhaseDecoded = await getMinterStats( - provider.connection, - minterStatsPhase, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - logMinterStats(minterStatsDecoded); - logMinterStatsPhase(minterStatsPhaseDecoded); - } + // Verify state after minting + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); - // verify collection state - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( - new anchor.BN(1).toString() - ); - expect( - editionsControlsDecoded.data.phases[0].currentMints.toString() - ).to.equal(new anchor.BN(1).toString()); - // verify minter state - expect(minterStatsDecoded.data.mintCount.toString()).to.equal( - new anchor.BN(1).toString() - ); - expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal( - new anchor.BN(1).toString() - ); + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('2'); // one was already minted on phase 0 + expect(editionsControlsDecoded.data.phases[1].currentMints.toString()).to.equal('1'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2'); // user already minted on phase 0 + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); }); - }); - - describe('Minting with allowlist', () => { - it('Should mint on second phase with allowlist', async () => { - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 850000, - }); + it('Should be able to mint without allowlist proof at full price', async () => { const mintConfig = { - phaseIndex: 1, // Use the second phase (index 1) - merkleProof: allowListConfig.list[0].proof, // Use the proof for minter1 - allowListPrice: allowListConfig.list[0].price, - allowListMaxClaims: allowListConfig.list[0].max_claims, + phaseIndex: 1, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, }; const mint = Keypair.generate(); const member = Keypair.generate(); - const tokenAccount = getAssociatedTokenAddressSync( - mint.publicKey, - minter1.publicKey, - false, - TOKEN_2022_PROGRAM_ID - ); - const hashlistMarker = getHashlistMarkerPda( - editionsPda, - mint.publicKey, - editionsProgram.programId - ); - const minterStats = getMinterStatsPda( - editionsPda, - minter1.publicKey, - editionsControlsProgram.programId - ); - const minterStatsPhase = getMinterStatsPhasePda( - editionsPda, - minter1.publicKey, - 1, - editionsControlsProgram.programId - ); + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); const mintIx = await editionsControlsProgram.methods .mintWithControls(mintConfig) @@ -626,137 +581,64 @@ describe('Editions Controls Test Suite', () => { editionsDeployment: editionsPda, editionsControls: editionsControlsPda, hashlist: hashlistPda, - hashlistMarker, + hashlistMarker: hashlistMarkerPda, payer: minter1.publicKey, - mint: mint.publicKey, - member: member.publicKey, signer: minter1.publicKey, minter: minter1.publicKey, - minterStats, - minterStatsPhase, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, group: group.publicKey, groupMint: groupMint.publicKey, platformFeeRecipient1: platformFeeAdmin.publicKey, - groupExtensionProgram: new PublicKey( - TOKEN_GROUP_EXTENSION_PROGRAM_ID - ), - tokenAccount, - treasury: collectionConfig.treasury, - systemProgram: SystemProgram.programId, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, }) .instruction(); - const transaction = new Transaction() - .add(modifyComputeUnits) - .add(mintIx); - transaction.recentBlockhash = ( - await provider.connection.getLatestBlockhash() - ).blockhash; - transaction.feePayer = minter1.publicKey; - transaction.sign(minter1, mint, member); - const rawTransaction = transaction.serialize(); - + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); try { - const signature = await provider.connection.sendRawTransaction( - rawTransaction - ); - // wait for transaction to be confirmed - await provider.connection.confirmTransaction(signature); - - // Fetch updated state - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const minterStatsDecoded = await getMinterStats( - provider.connection, - minterStats, - editionsControlsProgram - ); - const minterStatsPhaseDecoded = await getMinterStats( - provider.connection, - minterStatsPhase, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - logMinterStats(minterStatsDecoded); - logMinterStatsPhase(minterStatsPhaseDecoded); - } - - // Verify state - expect( - editionsControlsDecoded.data.phases[1].currentMints.toString() - ).to.equal('1'); - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal( - '2' - ); - expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2'); - expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal( - '1' - ); + await provider.sendAndConfirm(transaction, [minter1, mint, member]); } catch (error) { - console.error('Error in allowlist minting:', error); - if (error.logs) { - console.error('Full error logs:'); - error.logs.forEach((log, index) => { - console.error(`${index + 1}: ${log}`); - }); - } + console.error('Error in mintWithControls:', error); throw error; } - }); - it('Should fail to mint with allowlist if proof is incorrect', async () => { - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 850000, - }); + // Verify state after minting + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); - // Use an incorrect proof (e.g., the proof for minter2 instead of minter1) - const incorrectMinterProof = allowListConfig.list[1].proof; + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('3'); + expect(editionsControlsDecoded.data.phases[1].currentMints.toString()).to.equal('2'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('3'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('2'); + }); + }); + describe('Minting on a public phase without allowlist [Phase Index 2]', () => { + it('Should be able to mint without any allowlist data', async () => { const mintConfig = { - phaseIndex: 1, // Use the second phase (index 1) - merkleProof: incorrectMinterProof, - allowListPrice: allowListConfig.list[0].price, - allowListMaxClaims: allowListConfig.list[0].max_claims, + phaseIndex: 2, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, }; const mint = Keypair.generate(); const member = Keypair.generate(); - const tokenAccount = getAssociatedTokenAddressSync( - mint.publicKey, - minter1.publicKey, - false, - TOKEN_2022_PROGRAM_ID - ); - const hashlistMarker = getHashlistMarkerPda( - editionsPda, - mint.publicKey, - editionsProgram.programId - ); - const minterStats = getMinterStatsPda( - editionsPda, - minter1.publicKey, - editionsControlsProgram.programId - ); - const minterStatsPhase = getMinterStatsPhasePda( - editionsPda, - minter1.publicKey, - 1, - editionsControlsProgram.programId - ); + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 2, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); const mintIx = await editionsControlsProgram.methods .mintWithControls(mintConfig) @@ -764,250 +646,116 @@ describe('Editions Controls Test Suite', () => { editionsDeployment: editionsPda, editionsControls: editionsControlsPda, hashlist: hashlistPda, - hashlistMarker, + hashlistMarker: hashlistMarkerPda, payer: minter1.publicKey, - mint: mint.publicKey, - member: member.publicKey, signer: minter1.publicKey, minter: minter1.publicKey, - minterStats, - minterStatsPhase, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, group: group.publicKey, groupMint: groupMint.publicKey, platformFeeRecipient1: platformFeeAdmin.publicKey, - groupExtensionProgram: new PublicKey( - TOKEN_GROUP_EXTENSION_PROGRAM_ID - ), - tokenAccount, - treasury: collectionConfig.treasury, - systemProgram: SystemProgram.programId, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, }) .instruction(); - const transaction = new Transaction() - .add(modifyComputeUnits) - .add(mintIx); - transaction.recentBlockhash = ( - await provider.connection.getLatestBlockhash() - ).blockhash; - transaction.feePayer = minter1.publicKey; - transaction.sign(minter1, mint, member); - const rawTransaction = transaction.serialize(); - + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); try { - const signature = await provider.connection.sendRawTransaction( - rawTransaction - ); - // wait for transaction to be confirmed - await provider.connection.confirmTransaction(signature); + await provider.sendAndConfirm(transaction, [minter1, mint, member]); } catch (error) { - // We expect an error to be thrown - expect(JSON.stringify(error)).to.include('Invalid merkle proof.'); // Replace with the actual error code for invalid Merkle proof + console.error('Error in mintWithControls:', error); + throw error; } - // Verify that no mint actually occurred - const editionsDecoded = await getEditions( - provider.connection, - editionsPda, - editionsProgram - ); - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const minterStatsDecoded = await getMinterStats( - provider.connection, - minterStats, - editionsControlsProgram - ); - const minterStatsPhaseDecoded = await getMinterStats( - provider.connection, - minterStatsPhase, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditions(editionsDecoded); - logEditionsControls(editionsControlsDecoded); - logMinterStats(minterStatsDecoded); - logMinterStatsPhase(minterStatsPhaseDecoded); - } + // Verify state after minting + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); - // Verify state - expect( - editionsControlsDecoded.data.phases[1].currentMints.toString() - ).to.equal('1'); // Should still be 1 from the previous successful mint + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('4'); + expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('4'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); }); - it('Should fail to mint with allowlist if allowlist max_claims is reached', async () => { - const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ - units: 850000, - }); - + it('Should not be able to mint with allowlist', async () => { const mintConfig = { - phaseIndex: 1, - merkleProof: allowListConfig.list[1].proof, // Use the proof for minter2 - allowListPrice: allowListConfig.list[1].price, - allowListMaxClaims: allowListConfig.list[1].max_claims, - }; - - // Function to create and send a mint transaction - const createAndSendMintTransaction = async (): Promise => { - const mint = Keypair.generate(); - const member = Keypair.generate(); - - const tokenAccount = getAssociatedTokenAddressSync( - mint.publicKey, - minter2.publicKey, - false, - TOKEN_2022_PROGRAM_ID - ); - const hashlistMarker = getHashlistMarkerPda( - editionsPda, - mint.publicKey, - editionsProgram.programId - ); - const minterStats = getMinterStatsPda( - editionsPda, - minter2.publicKey, - editionsControlsProgram.programId - ); - const minterStatsPhase = getMinterStatsPhasePda( - editionsPda, - minter2.publicKey, - 1, - editionsControlsProgram.programId - ); - - const mintIx = await editionsControlsProgram.methods - .mintWithControls(mintConfig) - .accountsStrict({ - editionsDeployment: editionsPda, - editionsControls: editionsControlsPda, - hashlist: hashlistPda, - hashlistMarker, - payer: minter2.publicKey, - mint: mint.publicKey, - member: member.publicKey, - signer: minter2.publicKey, - minter: minter2.publicKey, - minterStats, - minterStatsPhase, - group: group.publicKey, - groupMint: groupMint.publicKey, - platformFeeRecipient1: platformFeeAdmin.publicKey, - groupExtensionProgram: new PublicKey( - TOKEN_GROUP_EXTENSION_PROGRAM_ID - ), - tokenAccount, - treasury: collectionConfig.treasury, - systemProgram: SystemProgram.programId, - tokenProgram: TOKEN_2022_PROGRAM_ID, - libreplexEditionsProgram: editionsProgram.programId, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, - }) - .instruction(); - - const transaction = new Transaction() - .add(modifyComputeUnits) - .add(mintIx); - transaction.recentBlockhash = ( - await provider.connection.getLatestBlockhash() - ).blockhash; - transaction.feePayer = minter2.publicKey; - transaction.sign(minter2, mint, member); - const rawTransaction = transaction.serialize(); - - const signature = await provider.connection.sendRawTransaction( - rawTransaction - ); - await provider.connection.confirmTransaction(signature); - return signature; + phaseIndex: 2, + merkleProof: allowListConfig.list[0].proof, + allowListPrice: allowListConfig.list[0].price, + allowListMaxClaims: allowListConfig.list[0].max_claims, }; - // Perform 3 successful mints - for (let i = 0; i < 3; i++) { - await createAndSendMintTransaction(); - } + const mint = Keypair.generate(); + const member = Keypair.generate(); - const minterStats = getMinterStatsPda( - editionsPda, - minter2.publicKey, - editionsControlsProgram.programId - ); - const minterStatsPhase = getMinterStatsPhasePda( - editionsPda, - minter2.publicKey, - 1, - editionsControlsProgram.programId - ); - - // Verify the state after the 3 mints - const editionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const minterStatsDecoded = await getMinterStats( - provider.connection, - minterStats, - editionsControlsProgram - ); - const minterStatsPhaseDecoded = await getMinterStats( - provider.connection, - minterStatsPhase, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditionsControls(editionsControlsDecoded); - logMinterStats(minterStatsDecoded); - logMinterStatsPhase(minterStatsPhaseDecoded); - } + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 2, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); - // verify state - expect(minterStatsDecoded.data.mintCount.toString()).to.equal('3'); - expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('3'); + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter1.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); - // Attempt the 4th mint, which should fail + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); try { - const signature = await createAndSendMintTransaction(); + await provider.sendAndConfirm(transaction, [minter1, mint, member]); } catch (error) { - expect(JSON.stringify(error)).to.include( - 'This wallet has exceeded allow list max_claims in the current phase.' - ); + const errorString = JSON.stringify(error); + expect(errorString).to.include('Merkle root not set for allow list mint'); } + // get state + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); + + expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('4'); + expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('4'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + }); + }); - // Verify the state hasn't changed after the failed mint - const finalEditionsControlsDecoded = await getEditionsControls( - provider.connection, - editionsControlsPda, - editionsControlsProgram - ); - const finalMinterStatsDecoded = await getMinterStats( - provider.connection, - minterStats, - editionsControlsProgram - ); - const finalMinterStatsPhaseDecoded = await getMinterStats( - provider.connection, - minterStatsPhase, - editionsControlsProgram - ); - if (VERBOSE_LOGGING) { - logEditionsControls(finalEditionsControlsDecoded); - logMinterStats(finalMinterStatsDecoded); - logMinterStatsPhase(finalMinterStatsPhaseDecoded); - } + describe.skip('Minting constraints', () => { + it('Should fail to mint when exceeding allowlist max claims', async () => {}); - // User mints should still be 3 - expect(finalMinterStatsDecoded.data.mintCount.toString()).to.equal('3'); - expect(finalMinterStatsPhaseDecoded.data.mintCount.toString()).to.equal( - '3' - ); - }); + it('Should fail to mint when exceeding phase max mints', async () => {}); + + it('Should fail to mint when exceeding total max mints', async () => {}); }); }); }); diff --git a/test/utils/getters.ts b/test/utils/getters.ts index 22d3effe..96f73f63 100644 --- a/test/utils/getters.ts +++ b/test/utils/getters.ts @@ -1,4 +1,3 @@ -import { TokenMetadata } from '@solana/spl-token-metadata'; import { BorshCoder, Program } from '@coral-xyz/anchor'; import { Connection, PublicKey } from '@solana/web3.js'; import { LibreplexEditionsControls } from '../../target/types/libreplex_editions_controls'; @@ -121,6 +120,7 @@ export const logEditionsControls = (editionsControlsDecoded: { endTime: Number(item.endTime), priceAmount: Number(item.priceAmount), priceToken: item.priceToken ? item.priceToken.toBase58() : null, + isPrivate: item.isPrivate, merkleRoot: item.merkleRoot ? JSON.stringify(item.merkleRoot) : null, })), }); diff --git a/test/utils/types.ts b/test/utils/types.ts index a0a3feab..4b0c2d8f 100644 --- a/test/utils/types.ts +++ b/test/utils/types.ts @@ -32,3 +32,14 @@ export interface AllowListConfig { proof: Buffer[]; }[]; } + +export interface PhaseConfig { + maxMintsPerWallet: anchor.BN; + maxMintsTotal: anchor.BN; + priceAmount: anchor.BN; + startTime: anchor.BN; + endTime: anchor.BN; + priceToken: PublicKey; + isPrivate: boolean; + merkleRoot: Buffer | null; +} diff --git a/test/utils/utils.ts b/test/utils/utils.ts index 2625c89e..c919e55d 100644 --- a/test/utils/utils.ts +++ b/test/utils/utils.ts @@ -1,6 +1,5 @@ import { Connection } from '@solana/web3.js'; -/// Verify this works export async function getCluster(connection: Connection): Promise { // Get the genesis hash const genesisHash = await connection.getGenesisHash(); @@ -18,9 +17,3 @@ export async function getCluster(connection: Connection): Promise { return 'localhost'; } } - -export const getBase64FromDatabytes = (dataBytes: Buffer, dataType: string) => { - console.log({ dataBytes }); - const base = dataBytes.toString('base64'); - return `data:${dataType};base64,${base}`; -}; From 6458f5b1ef00e8c8ccce07700ff94a6d55293717 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Mon, 14 Oct 2024 18:57:54 -0300 Subject: [PATCH 24/29] Covered more testing cases --- .mocharc.json | 3 + Anchor.toml | 2 +- test/tests/editions_controls.test.ts | 991 ++++++++++++++++++++++----- test/utils/utils.ts | 18 +- 4 files changed, 850 insertions(+), 164 deletions(-) create mode 100644 .mocharc.json diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 00000000..21a5f6d9 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,3 @@ +{ + "timeout": 1000000 +} diff --git a/Anchor.toml b/Anchor.toml index 872be242..19aea685 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -16,7 +16,7 @@ cluster = "Localnet" wallet = "~/.config/solana/id.json" [scripts] -test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/tests/**/*.ts -- --verbose" +test = "yarn run ts-mocha -p ./tsconfig.json --timeout 1000000 test/tests/**/*.ts -- --verbose" [test] startup_wait = 100000 diff --git a/test/tests/editions_controls.test.ts b/test/tests/editions_controls.test.ts index a63e7499..903fc022 100644 --- a/test/tests/editions_controls.test.ts +++ b/test/tests/editions_controls.test.ts @@ -6,7 +6,7 @@ import { LibreplexEditionsControls } from '../../target/types/libreplex_editions import { LibreplexEditions } from '../../target/types/libreplex_editions'; import { expect } from 'chai'; import { describe, it } from 'mocha'; -import { getCluster } from '../utils/utils'; +import { estimateTransactionFee, getCluster } from '../utils/utils'; import { getEditions, getEditionsControls, @@ -24,7 +24,7 @@ import { TOKEN_GROUP_EXTENSION_PROGRAM_ID } from '../../constants'; import { getEditionsPda, getEditionsControlsPda, getHashlistPda, getHashlistMarkerPda, getMinterStatsPda, getMinterStatsPhasePda } from '../utils/pdas'; import { CollectionConfig, AllowListConfig, PhaseConfig } from '../utils/types'; -const VERBOSE_LOGGING = true; +const VERBOSE_LOGGING = false; describe('Editions Controls Test Suite', () => { const provider = anchor.AnchorProvider.env(); @@ -50,12 +50,20 @@ describe('Editions Controls Test Suite', () => { let groupMint: Keypair; let group: Keypair; - let minter1: Keypair; - let minter2: Keypair; + let minter0: Keypair; // in allowlist + let minter1: Keypair; // in allowlist + let minter2: Keypair; // not in allowlist let collectionConfig: CollectionConfig; let allowListConfig: AllowListConfig; + let phase0Config: PhaseConfig; + let phase1Config: PhaseConfig; + let phase2Config: PhaseConfig; + let phase3Config: PhaseConfig; + let phase4Config: PhaseConfig; + let phase5Config: PhaseConfig; + // Generate dynamic keypairs and collection config before(async () => { if (VERBOSE_LOGGING) { @@ -75,7 +83,7 @@ describe('Editions Controls Test Suite', () => { group = Keypair.generate(); collectionConfig = { - maxNumberOfTokens: new anchor.BN(1150), + maxNumberOfTokens: new anchor.BN(20), symbol: 'COOLX55', collectionName: 'Collection name with meta, platform fee and royalties', collectionUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/0', @@ -95,7 +103,7 @@ describe('Editions Controls Test Suite', () => { ], }, platformFee: { - platformFeeValue: new anchor.BN(500000), + platformFeeValue: new anchor.BN(50000), recipients: [ { address: platformFeeAdmin.publicKey, @@ -125,49 +133,62 @@ describe('Editions Controls Test Suite', () => { // Generate allowlist variables before(async () => { - minter1 = Keypair.fromSecretKey( + minter0 = Keypair.fromSecretKey( new Uint8Array([ - 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, 218, 104, 164, 83, 51, 23, - 177, 193, 29, 252, 241, 86, 132, 173, 155, 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221, 120, 54, + 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, 218, 104, + 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221, 120, 54, ]) ); - minter2 = Keypair.fromSecretKey( + minter1 = Keypair.fromSecretKey( new Uint8Array([ - 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, 220, 186, 39, 133, 92, 39, - 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6, + 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, 220, 186, + 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6, ]) ); + minter2 = Keypair.generate(); allowListConfig = { - merkleRoot: Buffer.from([125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, 245]), + merkleRoot: Buffer.from([ + 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, 245, + ]), list: [ { - address: minter1.publicKey, + address: minter0.publicKey, price: new anchor.BN(500000), // 0.005 SOL max_claims: new anchor.BN(3), proof: [ - Buffer.from([64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, 232, 172, 235, 168]), + Buffer.from([ + 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, 232, 172, + 235, 168, + ]), ], }, { - address: minter2.publicKey, + address: minter1.publicKey, price: new anchor.BN(500000), // 0.005 SOL max_claims: new anchor.BN(3), proof: [ - Buffer.from([86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, 17, 125, 63, 188]), + Buffer.from([ + 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, 17, 125, 63, + 188, + ]), ], }, ], }; }); - // Perform needed airdrops to minter1, treasury and platformFeeRecipient + // Perform needed airdrops to minter0, treasury and platformFeeRecipient before(async () => { + // Airdrop SOL to minter0 + const minter0AirdropSignature = await provider.connection.requestAirdrop(minter0.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(minter0AirdropSignature); + // Airdrop SOL to minter1 - const airdropSignature = await provider.connection.requestAirdrop(minter1.publicKey, 1 * LAMPORTS_PER_SOL); - await provider.connection.confirmTransaction(airdropSignature); + const minter1AirdropSignature = await provider.connection.requestAirdrop(minter1.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(minter1AirdropSignature); // Airdrop SOL to minter2 - const minter2AirdropSignature = await provider.connection.requestAirdrop(minter2.publicKey, 10 * LAMPORTS_PER_SOL); + const minter2AirdropSignature = await provider.connection.requestAirdrop(minter2.publicKey, 1 * LAMPORTS_PER_SOL); await provider.connection.confirmTransaction(minter2AirdropSignature); // Airdrop SOL to treasury @@ -265,7 +286,7 @@ describe('Editions Controls Test Suite', () => { describe('Adding phases', () => { it('Should add a private phase with allowlist. [Phase Index 0]: Open: Not Available, Allowlist: 0.05 SOL', async () => { - const phaseConfig: PhaseConfig = { + phase0Config = { maxMintsPerWallet: new anchor.BN(100), maxMintsTotal: new anchor.BN(1000), priceAmount: new anchor.BN(0), // Not openly buyable, only available to allowlist @@ -276,7 +297,7 @@ describe('Editions Controls Test Suite', () => { merkleRoot: allowListConfig.merkleRoot, }; const phaseIx = await editionsControlsProgram.methods - .addPhase(phaseConfig) + .addPhase(phase0Config) .accountsStrict({ editionsControls: editionsControlsPda, creator: payer.publicKey, @@ -301,9 +322,9 @@ describe('Editions Controls Test Suite', () => { }); it('Should add a public phase with allowlist. [Phase Index 1]: Open: O.1 SOL, Allowlist: 0.05 SOL', async () => { - const phaseConfig: PhaseConfig = { - maxMintsPerWallet: new anchor.BN(100), - maxMintsTotal: new anchor.BN(1000), + phase1Config = { + maxMintsPerWallet: new anchor.BN(5), + maxMintsTotal: new anchor.BN(10), priceAmount: new anchor.BN(1000000), // 0.1 SOL startTime: new anchor.BN(new Date().getTime() / 1000), endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now @@ -313,7 +334,7 @@ describe('Editions Controls Test Suite', () => { }; const phaseIx = await editionsControlsProgram.methods - .addPhase(phaseConfig) + .addPhase(phase1Config) .accountsStrict({ editionsControls: editionsControlsPda, creator: payer.publicKey, @@ -338,8 +359,8 @@ describe('Editions Controls Test Suite', () => { expect(editionsControlsDecoded.data.phases.length).to.equal(2); }); - it('Should add a public phase without allowlist. [Phas Index 2]: Open: 0.05 SOL, Allowlist: Not Available', async () => { - const phaseConfig: PhaseConfig = { + it('Should add a public phase without allowlist. [Phase Index 2]: Open: 0.05 SOL, Allowlist: Not Available', async () => { + phase2Config = { maxMintsPerWallet: new anchor.BN(100), maxMintsTotal: new anchor.BN(1000), priceAmount: new anchor.BN(500000), // 0.05 SOL @@ -351,7 +372,7 @@ describe('Editions Controls Test Suite', () => { }; const phaseIx = await editionsControlsProgram.methods - .addPhase(phaseConfig) + .addPhase(phase2Config) .accountsStrict({ editionsControls: editionsControlsPda, creator: payer.publicKey, @@ -375,10 +396,124 @@ describe('Editions Controls Test Suite', () => { // verify state expect(editionsControlsDecoded.data.phases.length).to.equal(3); }); + + it('Should add a public phase without max supply, up to collection max. [Phase Index 3]', async () => { + phase3Config = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(0), // unlimited supply + priceAmount: new anchor.BN(500000), // 0.05 SOL + startTime: new anchor.BN(new Date().getTime() / 1000), + endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + isPrivate: false, + merkleRoot: null, + }; + + const phaseIx = await editionsControlsProgram.methods + .addPhase(phase3Config) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + await provider.sendAndConfirm(transaction, [payer]); + + // get state + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + if (VERBOSE_LOGGING) { + logEditionsControls(editionsControlsDecoded); + } + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(4); + }); + + it('Should add a phase that starts in the future. [Phase Index 4]', async () => { + phase4Config = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(1000), + priceAmount: new anchor.BN(500000), // 0.05 SOL + startTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now + endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24 * 7), // 1 week from now + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + isPrivate: false, + merkleRoot: null, + }; + + const phaseIx = await editionsControlsProgram.methods + .addPhase(phase4Config) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + await provider.sendAndConfirm(transaction, [payer]); + + // get state + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + if (VERBOSE_LOGGING) { + logEditionsControls(editionsControlsDecoded); + } + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(5); + }); + + it('Should add a phase that has already ended. [Phase Index 5]', async () => { + phase5Config = { + maxMintsPerWallet: new anchor.BN(100), + maxMintsTotal: new anchor.BN(1000), + priceAmount: new anchor.BN(500000), // 0.05 SOL + startTime: new anchor.BN(new Date().getTime() / 1000 - 60 * 60 * 24 * 7), // 1 week ago + endTime: new anchor.BN(new Date().getTime() / 1000 - 60 * 60 * 24), // 1 day associatedTokenProgram + priceToken: new PublicKey('So11111111111111111111111111111111111111112'), + isPrivate: false, + merkleRoot: null, + }; + + const phaseIx = await editionsControlsProgram.methods + .addPhase(phase5Config) + .accountsStrict({ + editionsControls: editionsControlsPda, + creator: payer.publicKey, + payer: payer.publicKey, + systemProgram: SystemProgram.programId, + tokenProgram: TOKEN_2022_PROGRAM_ID, + libreplexEditionsProgram: editionsProgram.programId, + }) + .signers([]) + .instruction(); + + const transaction = new Transaction().add(phaseIx); + await provider.sendAndConfirm(transaction, [payer]); + + // get state + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + if (VERBOSE_LOGGING) { + logEditionsControls(editionsControlsDecoded); + } + + // verify state + expect(editionsControlsDecoded.data.phases.length).to.equal(6); + }); }); describe('Minting', () => { - describe('Minting on a private phase with allowlist [Phase Index 0]', () => { + describe('Minting on a private allowlist-only phase [Phase Index 0]', () => { it('Should be able to mint with valid allowlist proof', async () => { const mintConfig = { phaseIndex: 0, @@ -391,9 +526,9 @@ describe('Editions Controls Test Suite', () => { const member = Keypair.generate(); const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); - const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); - const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 0, editionsControlsProgram.programId); - const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + const minterStatsPda = getMinterStatsPda(editionsPda, minter0.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter0.publicKey, false, TOKEN_2022_PROGRAM_ID); const mintIx = await editionsControlsProgram.methods .mintWithControls(mintConfig) @@ -402,9 +537,9 @@ describe('Editions Controls Test Suite', () => { editionsControls: editionsControlsPda, hashlist: hashlistPda, hashlistMarker: hashlistMarkerPda, - payer: minter1.publicKey, - signer: minter1.publicKey, - minter: minter1.publicKey, + payer: minter0.publicKey, + signer: minter0.publicKey, + minter: minter0.publicKey, minterStats: minterStatsPda, minterStatsPhase: minterStatsPhasePda, mint: mint.publicKey, @@ -421,10 +556,20 @@ describe('Editions Controls Test Suite', () => { libreplexEditionsProgram: editionsProgram.programId, }) .instruction(); - const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + + const minter0BalanceBefore = await provider.connection.getBalance(minter0.publicKey); + const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey); + + // Estimate transaction fee + const estimatedFee = await estimateTransactionFee(provider.connection, transaction, [minter0, mint, member]); + if (VERBOSE_LOGGING) { + console.log(`Estimated transaction fee: ${estimatedFee} lamports (${estimatedFee / LAMPORTS_PER_SOL} SOL)`); + } + try { - await provider.sendAndConfirm(transaction, [minter1, mint, member]); + await provider.sendAndConfirm(transaction, [minter0, mint, member]); } catch (error) { console.error('Error in mintWithControls:', error); throw error; @@ -436,13 +581,42 @@ describe('Editions Controls Test Suite', () => { const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); + // Pull new balances + const minter0BalanceAfter = await provider.connection.getBalance(minter0.publicKey); + const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey); + + // Verify that the token was minted expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('1'); expect(editionsControlsDecoded.data.phases[0].currentMints.toString()).to.equal('1'); expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1'); expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + + // When the fee is flat, is paid by the minter. + const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue; + const expectedTreasuryIncome = mintConfig.allowListPrice; + + if (VERBOSE_LOGGING) { + console.log('balances before', { + minter0: minter0BalanceBefore / LAMPORTS_PER_SOL, + treasury: treasuryBalanceBefore / LAMPORTS_PER_SOL, + platformFeeRecipient: platformFeeRecipientBalanceBefore / LAMPORTS_PER_SOL, + }); + + console.log('balances after', { + minter0: minter0BalanceAfter / LAMPORTS_PER_SOL, + treasury: treasuryBalanceAfter / LAMPORTS_PER_SOL, + platformFeeRecipient: platformFeeRecipientBalanceAfter / LAMPORTS_PER_SOL, + }); + } + + // Verify that the treasury received the correct amount of SOL (0.05 SOL from allowlist price) + expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber()); + // Verify that the platform fee recipient received the correct amount of royalties (0.05 SOL from collectionConfig platform fee) + expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber()); }); - it('Should fail to mint without allowlist proof', async () => { + it('Should not be able to mint without allowlist proof (private phase)', async () => { const mintConfig = { phaseIndex: 0, merkleProof: null, @@ -454,9 +628,9 @@ describe('Editions Controls Test Suite', () => { const member = Keypair.generate(); const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); - const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); - const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 0, editionsControlsProgram.programId); - const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + const minterStatsPda = getMinterStatsPda(editionsPda, minter0.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter0.publicKey, false, TOKEN_2022_PROGRAM_ID); const mintIx = await editionsControlsProgram.methods .mintWithControls(mintConfig) @@ -465,9 +639,9 @@ describe('Editions Controls Test Suite', () => { editionsControls: editionsControlsPda, hashlist: hashlistPda, hashlistMarker: hashlistMarkerPda, - payer: minter1.publicKey, - signer: minter1.publicKey, - minter: minter1.publicKey, + payer: minter0.publicKey, + signer: minter0.publicKey, + minter: minter0.publicKey, minterStats: minterStatsPda, minterStatsPhase: minterStatsPhasePda, mint: mint.publicKey, @@ -487,23 +661,100 @@ describe('Editions Controls Test Suite', () => { const transaction = new Transaction().add(mintIx); try { - await provider.sendAndConfirm(transaction, [minter1, mint, member]); + await provider.sendAndConfirm(transaction, [minter0, mint, member]); } catch (error) { const errorString = JSON.stringify(error); expect(errorString).to.include('Private phase but no merkle proof provided'); } }); - }); - describe('Minting on a public phase with allowlist [Phase Index 1]', () => { - it('Should be able to mint with allowlist proof at discounted price', async () => { + it('Should fail to mint when exceeding allowlist max claims', async () => { const mintConfig = { - phaseIndex: 1, + phaseIndex: 0, merkleProof: allowListConfig.list[0].proof, allowListPrice: allowListConfig.list[0].price, allowListMaxClaims: allowListConfig.list[0].max_claims, }; + // mint twice, then the third mint should fail because the max claims for the allowlist is 3 + const mintWithControls = async () => { + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter0.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter0.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter0.publicKey, + signer: minter0.publicKey, + minter: minter0.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter0, mint, member]); + } catch (error) { + throw error; + } + }; + + for (let i = 0; i < 2; i++) { + try { + await mintWithControls(); + } catch (error) { + throw error; + } + } + + try { + await mintWithControls(); + } catch (error) { + const errorString = JSON.stringify(error); + expect(errorString).to.include('This wallet has exceeded allow list max_claims in the current phase.'); + } + + // expect the user to have minted three total items on the phase + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); + + expect(editionsControlsDecoded.data.phases[0].currentMints.toString()).to.equal('3'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('3'); + }); + }); + + describe('Minting on a public phase with optional allowlist [Phase Index 1]', () => { + it('Should be able to mint with allowlist proof at discounted price', async () => { + const mintConfig = { + phaseIndex: 1, + merkleProof: allowListConfig.list[1].proof, + allowListPrice: allowListConfig.list[1].price, + allowListMaxClaims: allowListConfig.list[1].max_claims, + }; + const mint = Keypair.generate(); const member = Keypair.generate(); @@ -512,6 +763,10 @@ describe('Editions Controls Test Suite', () => { const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId); const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + // pull before balances + const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey); + const mintIx = await editionsControlsProgram.methods .mintWithControls(mintConfig) .accountsStrict({ @@ -553,10 +808,20 @@ describe('Editions Controls Test Suite', () => { const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('2'); // one was already minted on phase 0 + // Verify mint expect(editionsControlsDecoded.data.phases[1].currentMints.toString()).to.equal('1'); - expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2'); // user already minted on phase 0 + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1'); // user already minted on phase 0 expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + + // Verify protocol fees & treasury income + const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue; + const expectedTreasuryIncome = mintConfig.allowListPrice; + + const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey); + + expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber()); + expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber()); }); it('Should be able to mint without allowlist proof at full price', async () => { @@ -575,6 +840,10 @@ describe('Editions Controls Test Suite', () => { const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId); const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + // pull before balances + const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey); + const mintIx = await editionsControlsProgram.methods .mintWithControls(mintConfig) .accountsStrict({ @@ -616,146 +885,544 @@ describe('Editions Controls Test Suite', () => { const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('3'); expect(editionsControlsDecoded.data.phases[1].currentMints.toString()).to.equal('2'); - expect(minterStatsDecoded.data.mintCount.toString()).to.equal('3'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2'); expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('2'); + + // Verify protocol fees & treasury income + const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue; + const expectedTreasuryIncome = phase1Config.priceAmount; + + const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey); + + expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber()); + expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber()); }); - }); - describe('Minting on a public phase without allowlist [Phase Index 2]', () => { - it('Should be able to mint without any allowlist data', async () => { + // Max wallet mints on Phase 1 is 5 + it('Should fail to mint when exceeding phase max per wallet', async () => { const mintConfig = { - phaseIndex: 2, + phaseIndex: 1, merkleProof: null, allowListPrice: null, allowListMaxClaims: null, }; - const mint = Keypair.generate(); - const member = Keypair.generate(); - - const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); - const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); - const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 2, editionsControlsProgram.programId); - const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + const mintWithControls = async () => { + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter1.publicKey, + signer: minter1.publicKey, + minter: minter1.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter1, mint, member]); + } catch (error) { + throw error; + } + }; - const mintIx = await editionsControlsProgram.methods - .mintWithControls(mintConfig) - .accountsStrict({ - editionsDeployment: editionsPda, - editionsControls: editionsControlsPda, - hashlist: hashlistPda, - hashlistMarker: hashlistMarkerPda, - payer: minter1.publicKey, - signer: minter1.publicKey, - minter: minter1.publicKey, - minterStats: minterStatsPda, - minterStatsPhase: minterStatsPhasePda, - mint: mint.publicKey, - member: member.publicKey, - group: group.publicKey, - groupMint: groupMint.publicKey, - platformFeeRecipient1: platformFeeAdmin.publicKey, - tokenAccount: associatedTokenAddressSync, - treasury: treasury.publicKey, - tokenProgram: TOKEN_2022_PROGRAM_ID, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, - groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, - systemProgram: SystemProgram.programId, - libreplexEditionsProgram: editionsProgram.programId, - }) - .instruction(); + for (let i = 0; i < 3; i++) { + try { + await mintWithControls(); + } catch (error) { + throw error; + } + } - const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + // 5'th mint should fail try { - await provider.sendAndConfirm(transaction, [minter1, mint, member]); + await mintWithControls(); + throw new Error('Mint should fail'); } catch (error) { - console.error('Error in mintWithControls:', error); - throw error; + const errorString = JSON.stringify(error); + expect(errorString).to.include('Exceeded wallet max mints for this phase.'); } - - // Verify state after minting - const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); - const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); - const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); - const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); - - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('4'); - expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1'); - expect(minterStatsDecoded.data.mintCount.toString()).to.equal('4'); - expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); }); - it('Should not be able to mint with allowlist', async () => { - const mintConfig = { - phaseIndex: 2, - merkleProof: allowListConfig.list[0].proof, - allowListPrice: allowListConfig.list[0].price, - allowListMaxClaims: allowListConfig.list[0].max_claims, + // Max total mints on Phase 1 is 10 + it('Should fail to mint when exceeding phase max mints', async () => { + // phase mint count is 5, perform 5 mints more with random wallets that should succeed, then the 11th mint should fail + const mintWithControls = async (minter: Keypair, mint: Keypair, member: Keypair) => { + const mintConfig = { + phaseIndex: 1, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, + }; + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 1, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter.publicKey, + signer: minter.publicKey, + minter: minter.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter, mint, member]); + } catch (error) { + throw error; + } }; + if (VERBOSE_LOGGING) { + console.log('Performing 5 mints with random wallets, takes a while..'); + } + const mintPromises = []; + for (let i = 0; i < 5; i++) { + const minter = Keypair.generate(); + const mint = Keypair.generate(); + const member = Keypair.generate(); - const mint = Keypair.generate(); - const member = Keypair.generate(); + // airdrop minter + const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(airdropTx, 'confirmed'); - const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); - const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId); - const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 2, editionsControlsProgram.programId); - const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID); + mintPromises.push(mintWithControls(minter, mint, member)); + } - const mintIx = await editionsControlsProgram.methods - .mintWithControls(mintConfig) - .accountsStrict({ - editionsDeployment: editionsPda, - editionsControls: editionsControlsPda, - hashlist: hashlistPda, - hashlistMarker: hashlistMarkerPda, - payer: minter1.publicKey, - signer: minter1.publicKey, - minter: minter1.publicKey, - minterStats: minterStatsPda, - minterStatsPhase: minterStatsPhasePda, - mint: mint.publicKey, - member: member.publicKey, - group: group.publicKey, - groupMint: groupMint.publicKey, - platformFeeRecipient1: platformFeeAdmin.publicKey, - tokenAccount: associatedTokenAddressSync, - treasury: treasury.publicKey, - tokenProgram: TOKEN_2022_PROGRAM_ID, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, - groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, - systemProgram: SystemProgram.programId, - libreplexEditionsProgram: editionsProgram.programId, - }) - .instruction(); + try { + await Promise.all(mintPromises); + } catch (error) { + console.error('Error in parallel minting:', error); + throw error; + } - const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + // 11st mint should fail try { - await provider.sendAndConfirm(transaction, [minter1, mint, member]); + const minter = Keypair.generate(); + const mint = Keypair.generate(); + const member = Keypair.generate(); + + // airdrop minter + const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(airdropTx, 'confirmed'); + + await mintWithControls(minter, mint, member); + throw new Error('Mint should fail'); } catch (error) { const errorString = JSON.stringify(error); - expect(errorString).to.include('Merkle root not set for allow list mint'); + expect(errorString).to.include('Exceeded max mints for this phase.'); } - // get state - const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); - const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); - const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); - const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); + }); - expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('4'); - expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1'); - expect(minterStatsDecoded.data.mintCount.toString()).to.equal('4'); - expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + describe('Minting on a public phase without allowlist [Phase Index 2]', () => { + it('Should be able to mint without any allowlist data (open mint)', async () => { + const mintConfig = { + phaseIndex: 2, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter2.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter2.publicKey, 2, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter2.publicKey, false, TOKEN_2022_PROGRAM_ID); + + // pull before balances + const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter2.publicKey, + signer: minter2.publicKey, + minter: minter2.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter2, mint, member]); + } catch (error) { + console.error('Error in mintWithControls:', error); + throw error; + } + + // Verify state after minting + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); + + expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + + // Verify protocol fees & treasury income + const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue; + const expectedTreasuryIncome = phase3Config.priceAmount; + + const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey); + const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey); + + expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber()); + expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber()); + }); + + it('Should not be able to mint with allowlist (phase does not have allowlist)', async () => { + const mintConfig = { + phaseIndex: 2, + merkleProof: allowListConfig.list[0].proof, + allowListPrice: allowListConfig.list[0].price, + allowListMaxClaims: allowListConfig.list[0].max_claims, + }; + + const mint = Keypair.generate(); + const member = Keypair.generate(); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter2.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter2.publicKey, 2, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter2.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter2.publicKey, + signer: minter2.publicKey, + minter: minter2.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter2, mint, member]); + } catch (error) { + const errorString = JSON.stringify(error); + expect(errorString).to.include('Merkle root not set for allow list mint'); + } + // get state + const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram); + const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram); + const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram); + const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram); + + expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1'); + expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1'); + expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1'); + + // log final state + if (VERBOSE_LOGGING) { + logEditions(editionsDecoded); + logEditionsControls(editionsControlsDecoded); + logMinterStats(minterStatsDecoded); + logMinterStatsPhase(minterStatsPhaseDecoded); + } + }); }); - }); - describe.skip('Minting constraints', () => { - it('Should fail to mint when exceeding allowlist max claims', async () => {}); + describe('Minting on a public phase without allowlist and unlimited supply up to collection max [Phase Index 3]', () => { + // should fail to mint if it exceeds the max mints for the entire collection, max mints for the collection is 20. + it('Should fail to mint when exceeding collection max mints', async () => { + if (VERBOSE_LOGGING) { + const decodedEditions = await getEditions(provider.connection, editionsPda, editionsProgram); + logEditions(decodedEditions); + } + + // current mint count is 14, perform 6 mints with random wallets, then the 21st mint should fail + const mintWithControls = async (minter: Keypair, mint: Keypair, member: Keypair) => { + const mintConfig = { + phaseIndex: 3, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, + }; + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 3, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter.publicKey, + signer: minter.publicKey, + minter: minter.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter, mint, member]); + } catch (error) { + throw error; + } + }; + if (VERBOSE_LOGGING) { + console.log('Performing 6 mints with random wallets, takes a while..'); + } + const mintPromises = []; + for (let i = 0; i < 6; i++) { + const minter = Keypair.generate(); + const mint = Keypair.generate(); + const member = Keypair.generate(); + + // airdrop minter + const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(airdropTx, 'confirmed'); + + mintPromises.push(mintWithControls(minter, mint, member)); + } + + try { + await Promise.all(mintPromises); + } catch (error) { + console.error('Error in parallel minting:', error); + throw error; + } + if (VERBOSE_LOGGING) { + console.log('Performing 21th mint with random wallet..'); + } + try { + const minter = Keypair.generate(); + const mint = Keypair.generate(); + const member = Keypair.generate(); + + // airdrop minter + const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(airdropTx, 'confirmed'); + + await mintWithControls(minter, mint, member); + throw new Error('Mint should fail'); + } catch (error) { + const errorString = JSON.stringify(error); + expect(errorString).to.include('Minted out.'); + } + }); + }); - it('Should fail to mint when exceeding phase max mints', async () => {}); + describe('Attempting to mint on invalid phases', () => { + it('Should fail to mint on a phase that has not started yet', async () => { + const mintConfig = { + phaseIndex: 4, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, + }; + + const minter = Keypair.generate(); + const mint = Keypair.generate(); + const member = Keypair.generate(); + + // airdrop minter + const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(airdropTx, 'confirmed'); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 4, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter.publicKey, + signer: minter.publicKey, + minter: minter.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter, mint, member]); + } catch (error) { + const errorString = JSON.stringify(error); + expect(errorString).to.include('Phase not yet started'); + } + }); - it('Should fail to mint when exceeding total max mints', async () => {}); + it('Should fail to mint on a phase that has already ended', async () => { + const mintConfig = { + phaseIndex: 5, + merkleProof: null, + allowListPrice: null, + allowListMaxClaims: null, + }; + + const minter = Keypair.generate(); + const mint = Keypair.generate(); + const member = Keypair.generate(); + + // airdrop minter + const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL); + await provider.connection.confirmTransaction(airdropTx, 'confirmed'); + + const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId); + const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId); + const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 5, editionsControlsProgram.programId); + const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID); + + const mintIx = await editionsControlsProgram.methods + .mintWithControls(mintConfig) + .accountsStrict({ + editionsDeployment: editionsPda, + editionsControls: editionsControlsPda, + hashlist: hashlistPda, + hashlistMarker: hashlistMarkerPda, + payer: minter.publicKey, + signer: minter.publicKey, + minter: minter.publicKey, + minterStats: minterStatsPda, + minterStatsPhase: minterStatsPhasePda, + mint: mint.publicKey, + member: member.publicKey, + group: group.publicKey, + groupMint: groupMint.publicKey, + platformFeeRecipient1: platformFeeAdmin.publicKey, + tokenAccount: associatedTokenAddressSync, + treasury: treasury.publicKey, + tokenProgram: TOKEN_2022_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID, + systemProgram: SystemProgram.programId, + libreplexEditionsProgram: editionsProgram.programId, + }) + .instruction(); + + const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx); + try { + await provider.sendAndConfirm(transaction, [minter, mint, member]); + } catch (error) { + const errorString = JSON.stringify(error); + expect(errorString).to.include('Phase already finished'); + } + }); + }); }); }); }); diff --git a/test/utils/utils.ts b/test/utils/utils.ts index c919e55d..b34f0570 100644 --- a/test/utils/utils.ts +++ b/test/utils/utils.ts @@ -1,4 +1,4 @@ -import { Connection } from '@solana/web3.js'; +import { Connection, Keypair, Transaction } from '@solana/web3.js'; export async function getCluster(connection: Connection): Promise { // Get the genesis hash @@ -17,3 +17,19 @@ export async function getCluster(connection: Connection): Promise { return 'localhost'; } } + + +export async function estimateTransactionFee(connection: Connection, transaction: Transaction, signers: Keypair[]): Promise { + const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.sign(...signers); + + const message = transaction.compileMessage(); + const fees = await connection.getFeeForMessage(message); + + if (fees === null) { + throw new Error('Failed to estimate fee'); + } + + return fees.value; +} \ No newline at end of file From 37aa3d3710947e8041bcdf8221adcbebd1ba0c74 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 15 Oct 2024 11:57:30 -0300 Subject: [PATCH 25/29] Added instruction to run tests --- Anchor.toml | 8 ++-- README.md | 115 +++++++++++++++++++++++++++++---------------------- constants.ts | 10 ++--- 3 files changed, 73 insertions(+), 60 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 1a5efe33..9f8745de 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -5,15 +5,16 @@ resolution = true skip-lint = false [programs.localnet] -libreplex_editions = "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq" -libreplex_editions_controls = "5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs" +libreplex_editions = "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD" +libreplex_editions_controls = "CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN" [registry] url = "https://api.apr.dev" [provider] cluster = "Localnet" -wallet = "usb://ledger" +wallet = "~/.config/solana/id.json" +#wallet = "usb://ledger" [scripts] test = "yarn run ts-mocha -p ./tsconfig.json --timeout 1000000 test/tests/**/*.ts -- --verbose" @@ -28,7 +29,6 @@ bind_address = "0.0.0.0" url = "https://api.devnet.solana.com" ledger = ".anchor/test-ledger" rpc_port = 8899 -logs = "all" [[test.validator.clone]] address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" diff --git a/README.md b/README.md index 7af55bf1..1dc6f546 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,66 @@ -
- +# Eclipse Program Library Testing Guide + +This guide provides step-by-step instructions for setting up and running tests for the Eclipse Program Library. + +## Prerequisites + +- [Rust](https://www.rust-lang.org/tools/install) +- [Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools) +- [Anchor](https://www.anchor-lang.com/docs/installation) +- [Node.js and npm](https://nodejs.org/en/download/) +- [Yarn](https://classic.yarnpkg.com/en/docs/install) + +## Setup and Testing Steps + +1. Clone the repository: + ``` + git clone https://github.com/rarible/eclipse-program-library.git + cd eclipse-program-library + ``` + +2. Install dependencies: + ``` + yarn install + ``` + +3. Sync program IDs: + - Open `Anchor.toml` and `constants.ts` + - Ensure that the program IDs in both files match + - Example: + ```toml + # In Anchor.toml + [programs.localnet] + libreplex_editions = "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq" + libreplex_editions_controls = "5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs" + ``` + ```typescript + // In constants.ts + export const LIBREPLEX_EDITIONS_PROGRAM_ID = new PublicKey("587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq"); + export const LIBREPLEX_EDITIONS_CONTROLS_PROGRAM_ID = new PublicKey("5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs"); + ``` + +4. Run tests: + - To build and run tests: + ``` + anchor test + ``` + - To skip building and only run tests (faster for repeated testing): + ``` + anchor test --skip-build + ``` + +## Notes + +- If you encounter wallet-related errors, ensure your `Anchor.toml` is configured to use your local keypair: + ```toml + [provider] + cluster = "localnet" + wallet = "~/.config/solana/id.json" + ``` + +- For any issues or questions, please open an issue in the GitHub repository. + +## Contributing + +We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) file for details on how to contribute to this project. -

Libreplex

- -

- Documentation (stable) -

-
- - - -The mission of Libreplex is to provide a community-driven, open license protocol to the Solana SPL Token and NFT community. The protocol must meet the following criteria: - -1) Distributed deployment keys - -To ensure that no single entity can unilaterally make changes that impact or jeopardise the integrity of the applications that depend on the protocol. - -2) Open license held by a Trust / Foundation - -The licensing must ensure that any applications utilising the protocol can do so knowing that the nature of the protocol remains constant, to minimise uncertainty and maximise transparency. - -3) Guaranteed fees-free for life - -The fee-free nature of the protocol ensures that even though applications built on top of the protocol may introduce fees, the protocol itself will never do so. This establishes a level playing field to all and enforces predictability and transparency. - - 4) Open source - -The source of the protocol will be made available on github or similar. After initial launch, any changes will be subject to 30-day vetting and a community vote. - -INSTRUCTIONS: - -Install dependencies - -``` -yarn -``` - -Build - -``` -anchor build -``` - -To run unit tests (cargo): - -`cargo test` - -To run unit tests (cargo, for a single program): - -`cargo test libreplex_metadata` diff --git a/constants.ts b/constants.ts index c2229207..e612cfe6 100644 --- a/constants.ts +++ b/constants.ts @@ -1,6 +1,4 @@ -export const EDITIONS_PROGRAM_ID = - 'GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD'; -export const EDITIONS_CONTROLS_PROGRAM_ID = - 'CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN'; -export const TOKEN_GROUP_EXTENSION_PROGRAM_ID = - '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'; +// @dev: sync with Anchor.toml to ensure correct program ids are used on tests +export const EDITIONS_PROGRAM_ID = 'GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD'; +export const EDITIONS_CONTROLS_PROGRAM_ID = 'CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN'; +export const TOKEN_GROUP_EXTENSION_PROGRAM_ID = '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'; From 9de8a5caa36fcc4a36f17fbeaef3370e59089fa4 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 15 Oct 2024 12:56:57 -0300 Subject: [PATCH 26/29] lint pr --- .../src/instructions/add_to_hashlist.rs | 15 +++++---------- .../src/instructions/initialise.rs | 3 --- .../libreplex_editions/src/instructions/mint.rs | 6 +----- .../src/logic/add_to_hashlist.rs | 14 +++++--------- .../src/instructions/mint_with_controls.rs | 7 +------ .../src/instructions/update_platform_fee.rs | 4 ---- .../src/instructions/update_royalties.rs | 4 ++-- .../src/logic/check_allow_list_constraints.rs | 6 ------ 8 files changed, 14 insertions(+), 45 deletions(-) diff --git a/programs/libreplex_editions/src/instructions/add_to_hashlist.rs b/programs/libreplex_editions/src/instructions/add_to_hashlist.rs index c3e2dd3d..3ca58b9e 100644 --- a/programs/libreplex_editions/src/instructions/add_to_hashlist.rs +++ b/programs/libreplex_editions/src/instructions/add_to_hashlist.rs @@ -4,8 +4,6 @@ use solana_program::program::invoke; use solana_program::pubkey::Pubkey; use solana_program::system_instruction; - - pub fn add_to_hashlist<'a>( new_number_of_mints: u32, hashlist: &mut UncheckedAccount<'a>, @@ -13,9 +11,8 @@ pub fn add_to_hashlist<'a>( system_program: &Program<'a, System>, mint: &Pubkey, deployment: &Pubkey, - order_number: u64) -> Result<()> { - - msg!("add_to_hashlist called"); + order_number: u64 +) -> Result<()> { let new_size = 8 + 32 + 4 + (new_number_of_mints) * (32 + 8); let rent = Rent::get()?; let new_minimum_balance = rent.minimum_balance(new_size as usize); @@ -32,9 +29,9 @@ pub fn add_to_hashlist<'a>( } hashlist.realloc(new_size as usize, false)?; let hashlist_account_info = hashlist.to_account_info(); - + let mut hashlist_data = hashlist_account_info.data.borrow_mut(); - + hashlist_data[40..44].copy_from_slice(&new_number_of_mints.to_le_bytes()); let mint_start_pos:usize = (44+(new_number_of_mints-1)*40) as usize; hashlist_data[ @@ -43,13 +40,11 @@ pub fn add_to_hashlist<'a>( hashlist_data[ mint_start_pos + 32..mint_start_pos + 40 ].copy_from_slice(&order_number.to_le_bytes()); - + emit!(HashlistEvent { mint: mint.key(), deployment: deployment.key() }); - Ok(()) - } diff --git a/programs/libreplex_editions/src/instructions/initialise.rs b/programs/libreplex_editions/src/instructions/initialise.rs index b462a6d9..38327675 100644 --- a/programs/libreplex_editions/src/instructions/initialise.rs +++ b/programs/libreplex_editions/src/instructions/initialise.rs @@ -22,7 +22,6 @@ pub struct InitialiseInput { pub item_base_name: String, } - #[derive(Accounts)] #[instruction(input: InitialiseInput)] pub struct InitialiseCtx<'info> { @@ -61,7 +60,6 @@ pub struct InitialiseCtx<'info> { pub group_extension_program: AccountInfo<'info>, } - pub fn initialise(ctx: Context, input: InitialiseInput) -> Result<()> { if input.symbol.len() > SYMBOL_LIMIT { panic!("Symbol too long"); @@ -126,7 +124,6 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result &[ctx.bumps.editions_deployment], ]; - // msg!("Create token 2022 w/ metadata"); create_token_2022_and_metadata( MintAccounts2022 { authority: editions_deployment.to_account_info(), diff --git a/programs/libreplex_editions/src/instructions/mint.rs b/programs/libreplex_editions/src/instructions/mint.rs index 30e667fe..04246bbc 100644 --- a/programs/libreplex_editions/src/instructions/mint.rs +++ b/programs/libreplex_editions/src/instructions/mint.rs @@ -165,7 +165,6 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<() Some(group_extension_program.key()), )?; - // msg!("Minting 2022"); mint_non_fungible_2022_logic( &mint.to_account_info(), minter_token_account, @@ -189,10 +188,8 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<() )?; // Retrieve metadata from the group mint - msg!("Mint: get meta start"); let meta = get_mint_metadata(&mut group_mint.to_account_info())?; let additional_meta = meta.additional_metadata; - msg!("Mint: get meta end: meta len {}", additional_meta.len()); // Process each additional metadata key-value pair, excluding platform fee metadata for additional_metadatum in additional_meta { @@ -220,12 +217,11 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<() } // Transfer minimum rent to the mint account - msg!("Mint: transfer minimum rent to mint account"); update_account_lamports_to_minimum_balance( ctx.accounts.mint.to_account_info(), ctx.accounts.payer.to_account_info(), ctx.accounts.system_program.to_account_info(), )?; - msg!("Mint: done"); + Ok(()) } diff --git a/programs/libreplex_editions/src/logic/add_to_hashlist.rs b/programs/libreplex_editions/src/logic/add_to_hashlist.rs index 0b718f19..af337c87 100644 --- a/programs/libreplex_editions/src/logic/add_to_hashlist.rs +++ b/programs/libreplex_editions/src/logic/add_to_hashlist.rs @@ -4,17 +4,14 @@ use solana_program::program::invoke; use solana_program::pubkey::Pubkey; use solana_program::system_instruction; - - pub fn add_to_hashlist<'a>( new_number_of_mints: u32, hashlist: &mut UncheckedAccount<'a>, payer: &Signer<'a>, system_program: &Program<'a, System>, mint: &Pubkey, - order_number: u64) -> Result<()> { - - msg!("add_to_hashlist called"); + order_number: u64 +) -> Result<()> { let new_size = 8 + 32 + 4 + (new_number_of_mints) * (32 + 8); let rent = Rent::get()?; let new_minimum_balance = rent.minimum_balance(new_size as usize); @@ -31,9 +28,9 @@ pub fn add_to_hashlist<'a>( } hashlist.realloc(new_size as usize, false)?; let hashlist_account_info = hashlist.to_account_info(); - + let mut hashlist_data = hashlist_account_info.data.borrow_mut(); - + hashlist_data[40..44].copy_from_slice(&new_number_of_mints.to_le_bytes()); let mint_start_pos:usize = (44+(new_number_of_mints-1)*40) as usize; hashlist_data[ @@ -42,7 +39,6 @@ pub fn add_to_hashlist<'a>( hashlist_data[ mint_start_pos + 32..mint_start_pos + 40 ].copy_from_slice(&order_number.to_le_bytes()); - - Ok(()) + Ok(()) } diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs index 08ad846f..bbebf44e 100644 --- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs +++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs @@ -144,7 +144,6 @@ pub fn mint_with_controls( ctx: Context, mint_input: MintInput, ) -> Result<()> { - msg!("program: executing mint with controls"); let editions_controls = &mut ctx.accounts.editions_controls; let minter_stats = &mut ctx.accounts.minter_stats; let minter_stats_phase = &mut ctx.accounts.minter_stats_phase; @@ -164,11 +163,8 @@ pub fn mint_with_controls( // Get the default/standard price amount for the phase let mut price_amount = editions_controls.phases[mint_input.phase_index as usize].price_amount; - // Determine if is a normal mint or an allow list mint and perform the appropriate constraints - // depending on if a merkle proof was provided + // Check if it's a normal mint or an allow list mint based on the presence of a merkle proof if mint_input.merkle_proof.is_some() { - msg!("program: executing allow list mint"); - // Check allow list constraints check_allow_list_constraints( &editions_controls.phases[mint_input.phase_index as usize], &minter.key(), @@ -177,7 +173,6 @@ pub fn mint_with_controls( mint_input.allow_list_price, mint_input.allow_list_max_claims, )?; - msg!("program: allow list constraints passed"); // Override the price amount with the allow list price price_amount = mint_input.allow_list_price.unwrap_or(0); } else { diff --git a/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs b/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs index b7f0dc5d..8ea925f3 100644 --- a/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs +++ b/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs @@ -32,7 +32,6 @@ pub fn update_platform_fee(ctx: Context, platform_fee_inpu let platform_fee_value = platform_fee_input.platform_fee_value; let is_fee_flat = platform_fee_input.is_fee_flat; - msg!("libreplex_editions::cpi::modify_platform_ffe start"); let editions_controls = &mut ctx.accounts.editions_controls; // Initialize an array of 5 PlatformFeeRecipient with default values @@ -59,7 +58,6 @@ pub fn update_platform_fee(ctx: Context, platform_fee_inpu }, ]; - msg!("libreplex_editions_controls:: update editions_controls"); // Populate the array with provided recipients for (i, recipient) in platform_fee_input.recipients.iter().enumerate() { recipients_array[i] = recipient.clone(); @@ -68,7 +66,5 @@ pub fn update_platform_fee(ctx: Context, platform_fee_inpu editions_controls.is_fee_flat = is_fee_flat; editions_controls.platform_fee_recipients = recipients_array; - msg!("libreplex_editions::cpi::modify_platform_fee done"); - Ok(()) } diff --git a/programs/libreplex_editions_controls/src/instructions/update_royalties.rs b/programs/libreplex_editions_controls/src/instructions/update_royalties.rs index 3793e8a9..3916431f 100644 --- a/programs/libreplex_editions_controls/src/instructions/update_royalties.rs +++ b/programs/libreplex_editions_controls/src/instructions/update_royalties.rs @@ -58,7 +58,7 @@ pub fn update_royalties(ctx: Context, royalties_input: Updat editions_deployment_key.as_ref(), &[ctx.bumps.editions_controls], ]; - msg!("libreplex_editions::cpi::modify_royalties start"); + libreplex_editions::cpi::modify_royalties( CpiContext::new_with_signer( libreplex_editions_program.to_account_info(), @@ -72,6 +72,6 @@ pub fn update_royalties(ctx: Context, royalties_input: Updat }, &[seeds] ), royalties_input)?; - msg!("libreplex_editions::cpi::modify_royalties done"); + Ok(()) } diff --git a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs index 88cc5a91..280a5eb5 100644 --- a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs +++ b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs @@ -9,7 +9,6 @@ use crate::{ errors::{EditionsControlsError}, }; - /// We need to discern between leaf and intermediate nodes to prevent trivial second /// pre-image attacks. /// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack @@ -28,15 +27,10 @@ pub fn check_allow_list_constraints( if let (Some(phase_list_price), Some(phase_max_claims)) = (allow_list_price, allow_list_max_claims) { /// 1. check constraints /// dev: notice that if phase_max_claims is 0, this constraint is disabled - msg!("check_allow_list_constraints: phase_max_claims: {}", phase_max_claims); - msg!("check_allow_list_constraints: mint_count: {}", minter_stats_phase.mint_count); - if phase_max_claims > 0 && minter_stats_phase.mint_count >= phase_max_claims { return Err(EditionsControlsError::ExceededAllowListMaxClaims.into()); } - msg!("check_allow_list_constraints: passed phase_max_claims"); - /// 2. construct leaf let leaf = hashv(&[ &minter.to_bytes(), From 4d04dd94f6849af32013b77f406f144efffe6972 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 15 Oct 2024 12:57:27 -0300 Subject: [PATCH 27/29] lint pr --- test/utils/pdas.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/utils/pdas.ts b/test/utils/pdas.ts index 2382772e..0af25bdc 100644 --- a/test/utils/pdas.ts +++ b/test/utils/pdas.ts @@ -63,7 +63,6 @@ export const getMinterStatsPhasePda = ( phaseIndex: number, editionsControlsProgramId: PublicKey ) => { - // console.log({minter: minter.toBase58(), phaseIndex}) return PublicKey.findProgramAddressSync( [ Buffer.from('minter_stats_phase'), From 2d56669b83d34dcb8596dee827ec2384c96f74b2 Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 15 Oct 2024 13:00:20 -0300 Subject: [PATCH 28/29] rollback anchor config to solana mainnet --- Anchor.toml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 9f8745de..5a8a72e5 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -13,11 +13,12 @@ url = "https://api.apr.dev" [provider] cluster = "Localnet" -wallet = "~/.config/solana/id.json" -#wallet = "usb://ledger" +# testing wallet +# wallet = "~/.config/solana/id.json" +wallet = "usb://ledger" [scripts] -test = "yarn run ts-mocha -p ./tsconfig.json --timeout 1000000 test/tests/**/*.ts -- --verbose" +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/tests/**/*.ts" [test] startup_wait = 100000 @@ -26,7 +27,7 @@ upgradeable = false [test.validator] bind_address = "0.0.0.0" -url = "https://api.devnet.solana.com" +url = "https://api.mainnet-beta.solana.com" ledger = ".anchor/test-ledger" rpc_port = 8899 @@ -42,5 +43,6 @@ address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" [[test.validator.clone]] address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" +# Token Extension Group [[test.validator.clone]] address = "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" From afc2b560b6269311c0260079521da2d0978ef40b Mon Sep 17 00:00:00 2001 From: gonzaotc Date: Tue, 15 Oct 2024 13:05:02 -0300 Subject: [PATCH 29/29] added config for testing on anchor.toml --- Anchor.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 5a8a72e5..2fc8db5a 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -13,7 +13,7 @@ url = "https://api.apr.dev" [provider] cluster = "Localnet" -# testing wallet +### @dev testing wallet # wallet = "~/.config/solana/id.json" wallet = "usb://ledger" @@ -27,6 +27,8 @@ upgradeable = false [test.validator] bind_address = "0.0.0.0" +### @dev: Utilize the testnet cluster for running the testsuite +# url = "https://api.testnet.solana.com" url = "https://api.mainnet-beta.solana.com" ledger = ".anchor/test-ledger" rpc_port = 8899 @@ -43,6 +45,6 @@ address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK" [[test.validator.clone]] address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" -# Token Extension Group [[test.validator.clone]] address = "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V" +