From c31284dd0d268cab602db342774a073bd89831d8 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Fri, 12 Jan 2024 14:13:18 -0500 Subject: [PATCH] Adding ability to fetch Leaf Data from a TX. --- clients/js/src/leafAssetId.ts | 34 ++++++++++- clients/js/test/parseMintV1.test.ts | 95 +++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 clients/js/test/parseMintV1.test.ts diff --git a/clients/js/src/leafAssetId.ts b/clients/js/src/leafAssetId.ts index 7cb3d23a..2e487b80 100644 --- a/clients/js/src/leafAssetId.ts +++ b/clients/js/src/leafAssetId.ts @@ -1,6 +1,6 @@ -import { Context, Pda, PublicKey } from '@metaplex-foundation/umi'; +import { Context, Pda, PublicKey, TransactionSignature } from '@metaplex-foundation/umi'; import { publicKey, string, u64 } from '@metaplex-foundation/umi/serializers'; -import { MPL_BUBBLEGUM_PROGRAM_ID } from './generated'; +import { LeafSchema, MPL_BUBBLEGUM_PROGRAM_ID, getLeafSchemaSerializer } from './generated'; export function findLeafAssetIdPda( context: Pick, @@ -19,3 +19,33 @@ export function findLeafAssetIdPda( u64().serialize(seeds.leafIndex), ]); } + +export async function parseLeafFromMintV1Transaction( + context: Pick, + signature: TransactionSignature +): Promise { + const transaction = await context.rpc.getTransaction(signature); + const innerInstructions = transaction?.meta.innerInstructions; + + if (innerInstructions) { + const leaf = getLeafSchemaSerializer().deserialize(innerInstructions[0].instructions[0].data.slice(8)); + return leaf[0]; + } + + throw new Error('Could not parse leaf from transaction'); +} + +export async function parseLeafFromMintToCollectionV1Transaction( + context: Pick, + signature: TransactionSignature +): Promise { + const transaction = await context.rpc.getTransaction(signature); + const innerInstructions = transaction?.meta.innerInstructions; + + if (innerInstructions) { + const leaf = getLeafSchemaSerializer().deserialize(innerInstructions[0].instructions[1].data.slice(8)); + return leaf[0]; + } + + throw new Error('Could not parse leaf from transaction'); +} \ No newline at end of file diff --git a/clients/js/test/parseMintV1.test.ts b/clients/js/test/parseMintV1.test.ts new file mode 100644 index 00000000..bcf80e4b --- /dev/null +++ b/clients/js/test/parseMintV1.test.ts @@ -0,0 +1,95 @@ +import { + defaultPublicKey, + generateSigner, + none, + percentAmount, +} from '@metaplex-foundation/umi'; +import { createNft } from '@metaplex-foundation/mpl-token-metadata'; +import test from 'ava'; +import { MetadataArgsArgs, fetchMerkleTree, findLeafAssetIdPda, mintToCollectionV1, mintV1, parseLeafFromMintToCollectionV1Transaction, parseLeafFromMintV1Transaction } from '../src'; +import { createTree, createUmi } from './_setup'; + +test('it can parse the leaf from mint instructions', async (t) => { + // Given an empty Bubblegum tree. + const umi = await createUmi(); + const merkleTree = await createTree(umi); + const leafOwner = generateSigner(umi).publicKey; + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.sequenceNumber, 0n); + t.is(merkleTreeAccount.tree.activeIndex, 0n); + t.is(merkleTreeAccount.tree.bufferSize, 1n); + t.is(merkleTreeAccount.tree.rightMostPath.index, 0); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, defaultPublicKey()); + + // When we mint a new NFT from the tree using the following metadata. + const metadata: MetadataArgsArgs = { + name: 'My NFT', + uri: 'https://example.com/my-nft.json', + sellerFeeBasisPoints: 500, // 5% + collection: none(), + creators: [], + }; + + // Test with 10 different leaves to be sure they increment correctly. + for (let nonce = 0; nonce < 10; nonce += 1) { + const { signature } = await mintV1(umi, { leafOwner, merkleTree, metadata }).sendAndConfirm(umi, { confirm: { commitment: 'confirmed' } }); + const leaf = await parseLeafFromMintV1Transaction(umi, signature); + const assetId = findLeafAssetIdPda(umi, { merkleTree, leafIndex: nonce }); + + t.is(leafOwner, leaf.owner); + t.is(Number(leaf.nonce), nonce); + t.is(leaf.id, assetId[0]) + } +}); + +test('it can parse the leaf from mintToCollection instructions)', async (t) => { + // Given an empty Bubblegum tree. + const umi = await createUmi(); + const merkleTree = await createTree(umi); + const leafOwner = generateSigner(umi).publicKey; + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.sequenceNumber, 0n); + t.is(merkleTreeAccount.tree.activeIndex, 0n); + t.is(merkleTreeAccount.tree.bufferSize, 1n); + t.is(merkleTreeAccount.tree.rightMostPath.index, 0); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, defaultPublicKey()); + + // And a Collection NFT. + const collectionMint = generateSigner(umi); + await createNft(umi, { + mint: collectionMint, + name: 'My Collection', + uri: 'https://example.com/my-collection.json', + sellerFeeBasisPoints: percentAmount(5.5), // 5.5% + isCollection: true, + }).sendAndConfirm(umi); + + // When we mint a new NFT from the tree using the following metadata, with collection unverified. + const metadata: MetadataArgsArgs = { + name: 'My NFT', + uri: 'https://example.com/my-nft.json', + sellerFeeBasisPoints: 550, // 5.5% + collection: { + key: collectionMint.publicKey, + verified: false, + }, + creators: [], + }; + + // Test with 10 different leaves to be sure they increment correctly. + for (let nonce = 0; nonce < 10; nonce += 1) { + const { signature } = await mintToCollectionV1(umi, { + leafOwner, + merkleTree, + metadata, + collectionMint: collectionMint.publicKey, + }).sendAndConfirm(umi); + + const leaf = await parseLeafFromMintToCollectionV1Transaction(umi, signature); + const assetId = findLeafAssetIdPda(umi, { merkleTree, leafIndex: nonce }); + + t.is(leafOwner, leaf.owner); + t.is(Number(leaf.nonce), nonce); + t.is(leaf.id, assetId[0]) + } +});