123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- import {
- Keypair,
- PublicKey,
- Connection,
- Transaction,
- sendAndConfirmTransaction,
- TransactionInstruction,
- } from "@solana/web3.js";
- import {
- createAccount,
- createMint,
- mintTo,
- TOKEN_PROGRAM_ID,
- } from "@solana/spl-token";
- import {
- SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
- createAllocTreeIx,
- ValidDepthSizePair,
- SPL_NOOP_PROGRAM_ID,
- } from "@solana/spl-account-compression";
- import {
- PROGRAM_ID as BUBBLEGUM_PROGRAM_ID,
- MetadataArgs,
- computeCreatorHash,
- computeDataHash,
- createCreateTreeInstruction,
- createMintToCollectionV1Instruction,
- } from "@metaplex-foundation/mpl-bubblegum";
- import {
- PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID,
- CreateMetadataAccountArgsV3,
- createCreateMetadataAccountV3Instruction,
- createCreateMasterEditionV3Instruction,
- createSetCollectionSizeInstruction,
- } from "@metaplex-foundation/mpl-token-metadata";
- // import local helper functions
- import { explorerURL, extractSignatureFromFailedTransaction } from "./helpers";
- /*
- Helper function to create a merkle tree on chain, including allocating
- all the space required to store all the nodes
- */
- export async function createTree(
- connection: Connection,
- payer: Keypair,
- treeKeypair: Keypair,
- maxDepthSizePair: ValidDepthSizePair,
- canopyDepth: number = 0
- ) {
- console.log("Creating a new Merkle tree...");
- console.log("treeAddress:", treeKeypair.publicKey.toBase58());
- // derive the tree's authority (PDA), owned by Bubblegum
- const [treeAuthority, _bump] = PublicKey.findProgramAddressSync(
- [treeKeypair.publicKey.toBuffer()],
- BUBBLEGUM_PROGRAM_ID
- );
- console.log("treeAuthority:", treeAuthority.toBase58());
- // allocate the tree's account on chain with the `space`
- // NOTE: this will compute the space needed to store the tree on chain (and the lamports required to store it)
- const allocTreeIx = await createAllocTreeIx(
- connection,
- treeKeypair.publicKey,
- payer.publicKey,
- maxDepthSizePair,
- canopyDepth
- );
- // create the instruction to actually create the tree
- const createTreeIx = createCreateTreeInstruction(
- {
- payer: payer.publicKey,
- treeCreator: payer.publicKey,
- treeAuthority,
- merkleTree: treeKeypair.publicKey,
- compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
- // NOTE: this is used for some on chain logging
- logWrapper: SPL_NOOP_PROGRAM_ID,
- },
- {
- maxBufferSize: maxDepthSizePair.maxBufferSize,
- maxDepth: maxDepthSizePair.maxDepth,
- public: false,
- },
- BUBBLEGUM_PROGRAM_ID
- );
- try {
- // create and send the transaction to initialize the tree
- const tx = new Transaction().add(allocTreeIx).add(createTreeIx);
- tx.feePayer = payer.publicKey;
- // send the transaction
- const txSignature = await sendAndConfirmTransaction(
- connection,
- tx,
- // ensuring the `treeKeypair` PDA and the `payer` are BOTH signers
- [treeKeypair, payer],
- {
- commitment: "confirmed",
- skipPreflight: true,
- }
- );
- console.log("\nMerkle tree created successfully!");
- console.log(explorerURL({ txSignature }));
- // return useful info
- return { treeAuthority, treeAddress: treeKeypair.publicKey };
- } catch (err: any) {
- console.error("\nFailed to create merkle tree:", err);
- // log a block explorer link for the failed transaction
- await extractSignatureFromFailedTransaction(connection, err);
- throw err;
- }
- }
- /**
- * Create an NFT collection on-chain, using the regular Metaplex standards
- * with the `payer` as the authority
- */
- export async function createCollection(
- connection: Connection,
- payer: Keypair,
- metadataV3: CreateMetadataAccountArgsV3
- ) {
- // create and initialize the SPL token mint
- console.log("Creating the collection's mint...");
- const mint = await createMint(
- connection,
- payer,
- // mint authority
- payer.publicKey,
- // freeze authority
- payer.publicKey,
- // decimals - use `0` for NFTs since they are non-fungible
- 0
- );
- console.log("Mint address:", mint.toBase58());
- // create the token account
- console.log("Creating a token account...");
- const tokenAccount = await createAccount(
- connection,
- payer,
- mint,
- payer.publicKey
- // undefined, undefined,
- );
- console.log("Token account:", tokenAccount.toBase58());
- // mint 1 token ()
- console.log("Minting 1 token for the collection...");
- const mintSig = await mintTo(
- connection,
- payer,
- mint,
- tokenAccount,
- payer,
- // mint exactly 1 token
- 1,
- // no `multiSigners`
- [],
- undefined,
- TOKEN_PROGRAM_ID
- );
- // console.log(explorerURL({ txSignature: mintSig }));
- // derive the PDA for the metadata account
- const [metadataAccount, _bump] = PublicKey.findProgramAddressSync(
- [
- Buffer.from("metadata", "utf8"),
- TOKEN_METADATA_PROGRAM_ID.toBuffer(),
- mint.toBuffer(),
- ],
- TOKEN_METADATA_PROGRAM_ID
- );
- console.log("Metadata account:", metadataAccount.toBase58());
- // create an instruction to create the metadata account
- const createMetadataIx = createCreateMetadataAccountV3Instruction(
- {
- metadata: metadataAccount,
- mint: mint,
- mintAuthority: payer.publicKey,
- payer: payer.publicKey,
- updateAuthority: payer.publicKey,
- },
- {
- createMetadataAccountArgsV3: metadataV3,
- }
- );
- // derive the PDA for the metadata account
- const [masterEditionAccount, _bump2] = PublicKey.findProgramAddressSync(
- [
- Buffer.from("metadata", "utf8"),
- TOKEN_METADATA_PROGRAM_ID.toBuffer(),
- mint.toBuffer(),
- Buffer.from("edition", "utf8"),
- ],
- TOKEN_METADATA_PROGRAM_ID
- );
- console.log("Master edition account:", masterEditionAccount.toBase58());
- // create an instruction to create the metadata account
- const createMasterEditionIx = createCreateMasterEditionV3Instruction(
- {
- edition: masterEditionAccount,
- mint: mint,
- mintAuthority: payer.publicKey,
- payer: payer.publicKey,
- updateAuthority: payer.publicKey,
- metadata: metadataAccount,
- },
- {
- createMasterEditionArgs: {
- maxSupply: 0,
- },
- }
- );
- // create the collection size instruction
- const collectionSizeIX = createSetCollectionSizeInstruction(
- {
- collectionMetadata: metadataAccount,
- collectionAuthority: payer.publicKey,
- collectionMint: mint,
- },
- {
- setCollectionSizeArgs: { size: 50 },
- }
- );
- try {
- // construct the transaction with our instructions, making the `payer` the `feePayer`
- const tx = new Transaction()
- .add(createMetadataIx)
- .add(createMasterEditionIx)
- .add(collectionSizeIX);
- tx.feePayer = payer.publicKey;
- // send the transaction to the cluster
- const txSignature = await sendAndConfirmTransaction(
- connection,
- tx,
- [payer],
- {
- commitment: "confirmed",
- skipPreflight: true,
- }
- );
- console.log("\nCollection successfully created!");
- console.log(explorerURL({ txSignature }));
- } catch (err) {
- console.error("\nFailed to create collection:", err);
- // log a block explorer link for the failed transaction
- await extractSignatureFromFailedTransaction(connection, err);
- throw err;
- }
- // return all the accounts
- return { mint, tokenAccount, metadataAccount, masterEditionAccount };
- }
- /**
- * Mint a single compressed NFTs to any address
- */
- export async function mintCompressedNFT(
- connection: Connection,
- payer: Keypair,
- treeAddress: PublicKey,
- collectionMint: PublicKey,
- collectionMetadata: PublicKey,
- collectionMasterEditionAccount: PublicKey,
- compressedNFTMetadata: MetadataArgs,
- receiverAddress?: PublicKey
- ) {
- // derive the tree's authority (PDA), owned by Bubblegum
- const [treeAuthority, _bump] = PublicKey.findProgramAddressSync(
- [treeAddress.toBuffer()],
- BUBBLEGUM_PROGRAM_ID
- );
- // derive a PDA (owned by Bubblegum) to act as the signer of the compressed minting
- const [bubblegumSigner, _bump2] = PublicKey.findProgramAddressSync(
- // `collection_cpi` is a custom prefix required by the Bubblegum program
- [Buffer.from("collection_cpi", "utf8")],
- BUBBLEGUM_PROGRAM_ID
- );
- // create an array of instruction, to mint multiple compressed NFTs at once
- const mintIxs: TransactionInstruction[] = [];
- /**
- * correctly format the metadata args for the nft to mint
- * ---
- * note: minting an nft into a collection (via `createMintToCollectionV1Instruction`)
- * will auto verify the collection. But, the `collection.verified` value inside the
- * `metadataArgs` must be set to `false` in order for the instruction to succeed
- */
- const metadataArgs = Object.assign(compressedNFTMetadata, {
- collection: { key: collectionMint, verified: false },
- });
- /**
- * compute the data and creator hash for display in the console
- *
- * note: this is not required to do in order to mint new compressed nfts
- * (since it is performed on chain via the Bubblegum program)
- * this is only for demonstration
- */
- const computedDataHash = new PublicKey(
- computeDataHash(metadataArgs)
- ).toBase58();
- const computedCreatorHash = new PublicKey(
- computeCreatorHash(metadataArgs.creators)
- ).toBase58();
- console.log("computedDataHash:", computedDataHash);
- console.log("computedCreatorHash:", computedCreatorHash);
- /*
- Add a single mint to collection instruction
- ---
- But you could all multiple in the same transaction, as long as your
- transaction is still within the byte size limits
- */
- mintIxs.push(
- createMintToCollectionV1Instruction(
- {
- payer: payer.publicKey,
- merkleTree: treeAddress,
- treeAuthority,
- treeDelegate: payer.publicKey,
- // set the receiver of the NFT
- leafOwner: receiverAddress || payer.publicKey,
- // set a delegated authority over this NFT
- leafDelegate: payer.publicKey,
- /*
- You can set any delegate address at mint, otherwise should
- normally be the same as `leafOwner`
- NOTE: the delegate will be auto cleared upon NFT transfer
- ---
- in this case, we are setting the payer as the delegate
- */
- // collection details
- collectionAuthority: payer.publicKey,
- collectionAuthorityRecordPda: BUBBLEGUM_PROGRAM_ID,
- collectionMint: collectionMint,
- collectionMetadata: collectionMetadata,
- editionAccount: collectionMasterEditionAccount,
- // other accounts
- compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
- logWrapper: SPL_NOOP_PROGRAM_ID,
- bubblegumSigner: bubblegumSigner,
- tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
- },
- {
- metadataArgs,
- }
- )
- );
- try {
- // construct the transaction with our instructions, making the `payer` the `feePayer`
- const tx = new Transaction().add(...mintIxs);
- tx.feePayer = payer.publicKey;
- // send the transaction to the cluster
- const txSignature = await sendAndConfirmTransaction(
- connection,
- tx,
- [payer],
- {
- commitment: "confirmed",
- skipPreflight: true,
- }
- );
- console.log("\nSuccessfully minted the compressed NFT!");
- console.log(explorerURL({ txSignature }));
- return txSignature;
- } catch (err) {
- console.error("\nFailed to mint compressed NFT:", err);
- // log a block explorer link for the failed transaction
- await extractSignatureFromFailedTransaction(connection, err);
- throw err;
- }
- }
|