import { AnchorProvider, BN, Program, Wallet } from '@coral-xyz/anchor'; import { ComputeBudgetProgram, Connection, Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'; import { getAssociatedTokenAddress, TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { PROGRAM_ID as METADATA_PROGRAM_ID } from '@metaplex-foundation/mpl-token-metadata'; import * as base58 from 'bs58'; import * as nacl from 'tweetnacl'; import { MysteryBoxSolana, IDL } from './types/mystery_box_solana'; const MYSTERY_BOX_CONFIG_ACCOUNT_PREFIX: string = 'MYSTERY_BOX_CONFIG'; const COLLECTION_CONFIG_ACCOUNT_PREFIX: string = 'COLLECTION_CONFIG'; const USER_CONFIG_ACCOUNT_PREFIX: string = 'USER_CONFIG'; export class MysteryBoxLib { program: Program; connection: Connection; constructor(programId: PublicKey, connection: Connection, wallet: Wallet) { this.connection = connection; const provider = new AnchorProvider(connection, wallet, AnchorProvider.defaultOptions()); this.program = new Program(IDL, programId, provider); } signTransaction(tx: Transaction, secretKey: string): Transaction { const keypair: Keypair = Keypair.fromSecretKey(base58.decode(secretKey)); const signature = nacl.sign.detached(tx.serializeMessage(), keypair.secretKey); tx.addSignature(keypair.publicKey, Buffer.from(signature)); return tx; } addSignatureInTransaction(tx: Transaction, signerAddress: PublicKey, signature: Buffer): Transaction { tx.addSignature(signerAddress, signature); return tx; } async addFeePayerAndRecentBlockHashInTransaction(tx: Transaction, feePayer: PublicKey): Promise { tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 1400_000 })); tx.feePayer = feePayer; tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash; return tx; } async isPdaAddressInitialize(pdaAddress: PublicKey): Promise { const pdaAccountInfo = await this.connection.getAccountInfo(pdaAddress); return pdaAccountInfo != null; } async getMysteryBoxConfigPdaAndBump(): Promise<[PublicKey, number]> { return PublicKey.findProgramAddressSync([Buffer.from(MYSTERY_BOX_CONFIG_ACCOUNT_PREFIX)], this.program.programId); } async getCollectionConfigPdaAndBump(collectionConfigName: string): Promise<[PublicKey, number]> { return PublicKey.findProgramAddressSync([Buffer.from(COLLECTION_CONFIG_ACCOUNT_PREFIX), Buffer.from(collectionConfigName)], this.program.programId); } async getCollectionMintPdaAndBump(collectionConfigName: string, collectionConfigPda: PublicKey): Promise<[PublicKey, number]> { return PublicKey.findProgramAddressSync( [Buffer.from(COLLECTION_CONFIG_ACCOUNT_PREFIX), Buffer.from(collectionConfigName), collectionConfigPda.toBuffer()], this.program.programId ); } async getUserConfigPdaAndBump(collectionConfigName: string, collectionConfigPda: PublicKey, user: PublicKey): Promise<[PublicKey, number]> { return PublicKey.findProgramAddressSync( [Buffer.from(USER_CONFIG_ACCOUNT_PREFIX), Buffer.from(collectionConfigName), collectionConfigPda.toBuffer(), user.toBuffer()], this.program.programId ); } async getItemMintPdaAndBump(collectionConfigName: string, collectionConfigPda: PublicKey, tokenId: string): Promise<[PublicKey, number]> { return PublicKey.findProgramAddressSync( [Buffer.from(USER_CONFIG_ACCOUNT_PREFIX), Buffer.from(collectionConfigName), collectionConfigPda.toBuffer(), Buffer.from(tokenId)], this.program.programId ); } async getMetadataAccountPda(mintAccountPda: PublicKey, mplProgramId: PublicKey = METADATA_PROGRAM_ID): Promise<[PublicKey, number]> { return PublicKey.findProgramAddressSync([Buffer.from('metadata'), mplProgramId.toBuffer(), mintAccountPda.toBuffer()], mplProgramId); } async getMasterEditionAccountPda(mintAccountPda: PublicKey, mplProgramId: PublicKey = METADATA_PROGRAM_ID): Promise<[PublicKey, number]> { return PublicKey.findProgramAddressSync( [Buffer.from('metadata'), mplProgramId.toBuffer(), mintAccountPda.toBuffer(), Buffer.from('edition')], mplProgramId ); } async createInitializeMysteryBocConfigTransaction( payer: PublicKey, mainSigningAuthority: PublicKey, systemProgram: PublicKey = SystemProgram.programId, rent: PublicKey = SYSVAR_RENT_PUBKEY ): Promise { const [mysteryBoxConfigPDA] = await this.getMysteryBoxConfigPdaAndBump(); return this.program.methods .initializeMysteryBoxConfig() .accounts({ payer: payer, mainSigningAuthority: mainSigningAuthority, mysteryBoxConfig: mysteryBoxConfigPDA, systemProgram: systemProgram, rent: rent, }) .transaction(); } async createInitializeCollectionConfigTransaction( payer: PublicKey, mainSigningAuthority: PublicKey, signingAuthority: PublicKey, collectionConfigName: string, assetName: string, assetSymbol: string, assetMetadataBaseUrl: string, assetMetadataBaseUrlSuffix: string, tokenPrice: number, price: number, totalTokenCount: number, maxTokenCount: number, collectionName: string, collectionSymbol: string, collectionUrl: string, paymentTokenMintAccount: PublicKey, paymentReceiver: PublicKey, paymentTokenProgram: PublicKey, collectionTokenProgram: PublicKey, associatedTokenProgram: PublicKey = ASSOCIATED_TOKEN_PROGRAM_ID, mplProgramId: PublicKey = METADATA_PROGRAM_ID, systemProgram: PublicKey = SystemProgram.programId, rent: PublicKey = SYSVAR_RENT_PUBKEY ): Promise { const [mysteryBoxConfigPDA, mysteryBoxConfigBump] = await this.getMysteryBoxConfigPdaAndBump(); const [collectionConfigPDA] = await this.getCollectionConfigPdaAndBump(collectionConfigName); const [collectionMintPDA] = await this.getCollectionMintPdaAndBump(collectionConfigName, collectionConfigPDA); const collectionTokenPDA = await getAssociatedTokenAddress(collectionMintPDA, collectionConfigPDA, true, collectionTokenProgram); const [collectionMetadataPDA] = await this.getMetadataAccountPda(collectionMintPDA, mplProgramId); const [collectionMasterPDA] = await this.getMasterEditionAccountPda(collectionMintPDA, mplProgramId); const paymentReceiverTokenAccount = await getAssociatedTokenAddress(paymentTokenMintAccount, paymentReceiver, false, paymentTokenProgram); return this.program.methods .initializeCollectionConfig( mysteryBoxConfigBump, collectionConfigName, assetName, assetSymbol, assetMetadataBaseUrl, assetMetadataBaseUrlSuffix, new BN(tokenPrice), new BN(price), new BN(totalTokenCount), new BN(maxTokenCount), collectionName, collectionSymbol, collectionUrl ) .accounts({ payer: payer, mainSigningAuthority: mainSigningAuthority, signingAuthority: signingAuthority, mysteryBoxConfig: mysteryBoxConfigPDA, paymentTokenMintAccount: paymentTokenMintAccount, paymentReceiver: paymentReceiver, paymentReceiverTokenAccount: paymentReceiverTokenAccount, collectionConfig: collectionConfigPDA, collectionMintAccount: collectionMintPDA, collectionTokenAccount: collectionTokenPDA, collectionMetadata: collectionMetadataPDA, collectionMasterEdition: collectionMasterPDA, collectionTokenProgram: collectionTokenProgram, associatedTokenProgram: associatedTokenProgram, paymentTokenProgram: paymentTokenProgram, systemProgram: systemProgram, rent: rent, tokenMetadataProgram: mplProgramId, }) .transaction(); } async createAirdropTransaction( payer: PublicKey, signingAuthority: PublicKey, collectionConfigName: string, user: PublicKey, collectionTokenProgram: PublicKey, associatedTokenProgram: PublicKey = ASSOCIATED_TOKEN_PROGRAM_ID, mplProgramId: PublicKey = METADATA_PROGRAM_ID, systemProgram: PublicKey = SystemProgram.programId, rent: PublicKey = SYSVAR_RENT_PUBKEY ): Promise { const [collectionConfigPDA, collectionConfigBump] = await this.getCollectionConfigPdaAndBump(collectionConfigName); const [collectionMintPDA, collectionMintBump] = await this.getCollectionMintPdaAndBump(collectionConfigName, collectionConfigPDA); const [collectionMetadataPDA] = await this.getMetadataAccountPda(collectionMintPDA, mplProgramId); const [collectionMasterPDA] = await this.getMasterEditionAccountPda(collectionMintPDA, mplProgramId); const collectionConfigData = await this.program.account.collectionConfigAccount.fetch(collectionConfigPDA.toBase58()); const currentTokenId = collectionConfigData.currentTokenCount.toNumber(); const tokenId = (currentTokenId + 1).toString(); const [userConfigPda] = await this.getUserConfigPdaAndBump(collectionConfigName, collectionConfigPDA, user); const [itemMintPda] = await this.getItemMintPdaAndBump(collectionConfigName, collectionConfigPDA, tokenId); const [itemMetadataPDA] = await this.getMetadataAccountPda(itemMintPda, mplProgramId); const [itemMasterPDA] = await this.getMasterEditionAccountPda(itemMintPda, mplProgramId); const userItemTokenAccount = await getAssociatedTokenAddress(itemMintPda, user, true, collectionTokenProgram); return this.program.methods .airdrop(collectionConfigName, collectionConfigBump, collectionMintBump, tokenId) .accounts({ payer: payer, signingAuthority: signingAuthority, user: user, userConfig: userConfigPda, collectionConfig: collectionConfigPDA, collectionMintAccount: collectionMintPDA, collectionMetadata: collectionMetadataPDA, collectionMasterEdition: collectionMasterPDA, itemMintAccount: itemMintPda, itemMetadata: itemMetadataPDA, itemMasterEdition: itemMasterPDA, userItemTokenAccount: userItemTokenAccount, collectionTokenProgram: collectionTokenProgram, tokenMetadataProgram: mplProgramId, associatedTokenProgram: associatedTokenProgram, systemProgram: systemProgram, rent: rent, }) .transaction(); } async createMintTransaction( payer: PublicKey, user: PublicKey, collectionConfigName: string, collectionTokenProgram: PublicKey, associatedTokenProgram: PublicKey = ASSOCIATED_TOKEN_PROGRAM_ID, mplProgramId: PublicKey = METADATA_PROGRAM_ID, systemProgram: PublicKey = SystemProgram.programId, rent: PublicKey = SYSVAR_RENT_PUBKEY ): Promise { const [collectionConfigPDA, collectionConfigBump] = await this.getCollectionConfigPdaAndBump(collectionConfigName); const [collectionMintPDA, collectionMintBump] = await this.getCollectionMintPdaAndBump(collectionConfigName, collectionConfigPDA); const [collectionMetadataPDA] = await this.getMetadataAccountPda(collectionMintPDA, mplProgramId); const [collectionMasterPDA] = await this.getMasterEditionAccountPda(collectionMintPDA, mplProgramId); const collectionConfigData = await this.program.account.collectionConfigAccount.fetch(collectionConfigPDA.toBase58()); const currentTokenId = collectionConfigData.currentTokenCount.toNumber(); const tokenId = (currentTokenId + 1).toString(); const paymentReceiver = collectionConfigData.paymentReceiver; const [userConfigPda] = await this.getUserConfigPdaAndBump(collectionConfigName, collectionConfigPDA, user); const [itemMintPda] = await this.getItemMintPdaAndBump(collectionConfigName, collectionConfigPDA, tokenId); const [itemMetadataPDA] = await this.getMetadataAccountPda(itemMintPda, mplProgramId); const [itemMasterPDA] = await this.getMasterEditionAccountPda(itemMintPda, mplProgramId); const userItemTokenAccount = await getAssociatedTokenAddress(itemMintPda, user, true, collectionTokenProgram); return this.program.methods .mint(collectionConfigName, collectionConfigBump, collectionMintBump, tokenId) .accounts({ payer: payer, user: user, paymentReceiver: paymentReceiver, userConfig: userConfigPda, collectionConfig: collectionConfigPDA, collectionMintAccount: collectionMintPDA, collectionMetadata: collectionMetadataPDA, collectionMasterEdition: collectionMasterPDA, itemMintAccount: itemMintPda, itemMetadata: itemMetadataPDA, itemMasterEdition: itemMasterPDA, userItemTokenAccount: userItemTokenAccount, collectionTokenProgram: collectionTokenProgram, tokenMetadataProgram: mplProgramId, associatedTokenProgram: associatedTokenProgram, systemProgram: systemProgram, rent: rent, }) .transaction(); } async createMintWithTokenTransaction( payer: PublicKey, user: PublicKey, collectionConfigName: string, collectionTokenProgram: PublicKey, paymentTokenProgram: PublicKey, associatedTokenProgram: PublicKey = ASSOCIATED_TOKEN_PROGRAM_ID, mplProgramId: PublicKey = METADATA_PROGRAM_ID, systemProgram: PublicKey = SystemProgram.programId, rent: PublicKey = SYSVAR_RENT_PUBKEY ): Promise { const [collectionConfigPDA, collectionConfigBump] = await this.getCollectionConfigPdaAndBump(collectionConfigName); const [collectionMintPDA, collectionMintBump] = await this.getCollectionMintPdaAndBump(collectionConfigName, collectionConfigPDA); const [collectionMetadataPDA] = await this.getMetadataAccountPda(collectionMintPDA, mplProgramId); const [collectionMasterPDA] = await this.getMasterEditionAccountPda(collectionMintPDA, mplProgramId); const collectionConfigData = await this.program.account.collectionConfigAccount.fetch(collectionConfigPDA.toBase58()); const currentTokenId = collectionConfigData.currentTokenCount.toNumber(); const tokenId = (currentTokenId + 1).toString(); const paymentReceiver = collectionConfigData.paymentReceiver; const paymentTokenMintAccount = collectionConfigData.paymentTokenMintAccount; const paymentReceiverTokenAccount = await getAssociatedTokenAddress(paymentTokenMintAccount, paymentReceiver, false, paymentTokenProgram); const userTokenAccount = await getAssociatedTokenAddress(paymentTokenMintAccount, user, false, paymentTokenProgram); const [userConfigPda] = await this.getUserConfigPdaAndBump(collectionConfigName, collectionConfigPDA, user); const [itemMintPda] = await this.getItemMintPdaAndBump(collectionConfigName, collectionConfigPDA, tokenId); const [itemMetadataPDA] = await this.getMetadataAccountPda(itemMintPda, mplProgramId); const [itemMasterPDA] = await this.getMasterEditionAccountPda(itemMintPda, mplProgramId); const userItemTokenAccount = await getAssociatedTokenAddress(itemMintPda, user, true, collectionTokenProgram); return this.program.methods .mintWithToken(collectionConfigName, collectionConfigBump, collectionMintBump, tokenId) .accounts({ payer: payer, user: user, userTokenAccount: userTokenAccount, paymentTokenMintAccount: paymentTokenMintAccount, paymentReceiver: paymentReceiver, paymentReceiverTokenAccount: paymentReceiverTokenAccount, userConfig: userConfigPda, collectionConfig: collectionConfigPDA, collectionMintAccount: collectionMintPDA, collectionMetadata: collectionMetadataPDA, collectionMasterEdition: collectionMasterPDA, itemMintAccount: itemMintPda, itemMetadata: itemMetadataPDA, itemMasterEdition: itemMasterPDA, userItemTokenAccount: userItemTokenAccount, collectionTokenProgram: collectionTokenProgram, paymentTokenProgram: paymentTokenProgram, tokenMetadataProgram: mplProgramId, associatedTokenProgram: associatedTokenProgram, systemProgram: systemProgram, rent: rent, }) .transaction(); } async createUpdateCollectionConfigTransaction( payer: PublicKey, signingAuthority: PublicKey, collectionConfigName: string, tokenPrice: number, price: number, maxTokenCount: number, paymentReceiver: PublicKey, paymentTokenMintAccount: PublicKey, isEnable: boolean, isAirdropEnable: boolean, isMintEnable: boolean, isMintWithTokenEnable: boolean, paymentTokenProgram: PublicKey, associatedTokenProgram: PublicKey = ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: PublicKey = SystemProgram.programId, rent: PublicKey = SYSVAR_RENT_PUBKEY ): Promise { const [collectionConfigPDA, collectionConfigBump] = await this.getCollectionConfigPdaAndBump(collectionConfigName); const paymentReceiverTokenAccount = await getAssociatedTokenAddress(paymentTokenMintAccount, paymentReceiver, false, paymentTokenProgram); return this.program.methods .updateCollectionConfig( collectionConfigName, collectionConfigBump, new BN(tokenPrice), new BN(price), new BN(maxTokenCount), isEnable, isAirdropEnable, isMintEnable, isMintWithTokenEnable ) .accounts({ payer: payer, signingAuthority: signingAuthority, paymentTokenMintAccount: paymentTokenMintAccount, paymentReceiver: paymentReceiver, paymentReceiverTokenAccount: paymentReceiverTokenAccount, collectionConfig: collectionConfigPDA, paymentTokenProgram: paymentTokenProgram, associatedTokenProgram: associatedTokenProgram, systemProgram: systemProgram, rent: rent, }) .transaction(); } }