From a26e9e6622268a117d12691c052ca200ea286755 Mon Sep 17 00:00:00 2001 From: leantOnSol Date: Wed, 19 Feb 2025 00:15:35 +0100 Subject: [PATCH] add pNFT prep helper + cleanup --- common-helpers/.eslintrc.cjs | 1 + common-helpers/package.json | 2 +- common-helpers/src/DAS/helius_queries.ts | 1 - common-helpers/src/compression/merkleTree.ts | 1 - common-helpers/src/constants.ts | 5 ++ .../src/metadata/codecs/metadataDecoder.ts | 20 ++++---- common-helpers/src/metadata/metadata.ts | 48 +++++++++++++++++-- .../src/transactions/simulateInstructions.ts | 42 +++++++++++++--- 8 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 common-helpers/src/constants.ts diff --git a/common-helpers/.eslintrc.cjs b/common-helpers/.eslintrc.cjs index c39f51a..4b795f3 100644 --- a/common-helpers/.eslintrc.cjs +++ b/common-helpers/.eslintrc.cjs @@ -3,6 +3,7 @@ module.exports = { rules: { '@typescript-eslint/ban-types': 'off', '@typescript-eslint/sort-type-constituents': 'off', + '@typescript-eslint/no-explicit-any': 'off', 'prefer-destructuring': 'off', 'simple-import-sort/imports': 'off', 'sort-keys-fix/sort-keys-fix': 'off', diff --git a/common-helpers/package.json b/common-helpers/package.json index 0adae5e..1b20c8c 100644 --- a/common-helpers/package.json +++ b/common-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@tensor-foundation/common-helpers", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "description": "Common helper functions for Tensor SDKs", "sideEffects": false, "module": "./dist/src/index.mjs", diff --git a/common-helpers/src/DAS/helius_queries.ts b/common-helpers/src/DAS/helius_queries.ts index e4185d3..5a16681 100644 --- a/common-helpers/src/DAS/helius_queries.ts +++ b/common-helpers/src/DAS/helius_queries.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { DAS } from 'helius-sdk'; export async function retrieveProofFields( diff --git a/common-helpers/src/compression/merkleTree.ts b/common-helpers/src/compression/merkleTree.ts index 6c12906..a2a2ca8 100644 --- a/common-helpers/src/compression/merkleTree.ts +++ b/common-helpers/src/compression/merkleTree.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Address, Rpc, SolanaRpcApi } from '@solana/web3.js'; import { getConcurrentMerkleTreeDecoderFactory } from './codecs/merkleTreeDecoderFactories'; import { getConcurrentMerkleTreeHeaderDecoder } from './codecs/merkleTreeHeaderDecoder'; diff --git a/common-helpers/src/constants.ts b/common-helpers/src/constants.ts new file mode 100644 index 0000000..e020b56 --- /dev/null +++ b/common-helpers/src/constants.ts @@ -0,0 +1,5 @@ +import { address } from '@solana/web3.js'; + +export const MPL_TOKEN_METADATA_PROGRAM_ID = address( + 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s' +); diff --git a/common-helpers/src/metadata/codecs/metadataDecoder.ts b/common-helpers/src/metadata/codecs/metadataDecoder.ts index 5bd4253..ee21148 100644 --- a/common-helpers/src/metadata/codecs/metadataDecoder.ts +++ b/common-helpers/src/metadata/codecs/metadataDecoder.ts @@ -48,7 +48,7 @@ enum Key { EditionMarkerV2, } -type MetadataData = { +export type MetadataData = { name: string; symbol: string; uri: string; @@ -56,19 +56,19 @@ type MetadataData = { creators: TCreator[] | null; }; -type ProgrammableConfigRecord = { +export type ProgrammableConfigRecord = { ruleSet: Address | null; }; -type ProgrammableConfig = ProgrammableConfigRecord & { +export type ProgrammableConfig = ProgrammableConfigRecord & { __kind: 'V1'; }; -type CollectionDetails = +export type CollectionDetails = | { __kind: 'V1'; size: bigint } | { __kind: 'V2'; padding: Array }; -type Metadata = { +export type Metadata = { key: Key; updateAuthority: Address; mint: Address; @@ -103,26 +103,26 @@ export function getMetadataDecoder(): Decoder { ]); } -function getCollectionDetailsDecoder(): Decoder { +export function getCollectionDetailsDecoder(): Decoder { return getDiscriminatedUnionDecoder([ ['V1', getStructDecoder([['size', getU64Decoder()]])], ['V2', getStructDecoder([['padding', getArrayDecoder(getU8Decoder())]])], ]); } -function getProgrammableConfigDecoder(): Decoder { +export function getProgrammableConfigDecoder(): Decoder { return getDiscriminatedUnionDecoder([ ['V1', getProgrammableConfigRecordDecoder()], ]); } -function getProgrammableConfigRecordDecoder(): Decoder { +export function getProgrammableConfigRecordDecoder(): Decoder { return getStructDecoder([ ['ruleSet', getNullableDecoder(getAddressDecoder())], ]); } -function getMetadataDataDecoder(): Decoder { +export function getMetadataDataDecoder(): Decoder { return getStructDecoder([ ['name', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())], ['symbol', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())], @@ -132,7 +132,7 @@ function getMetadataDataDecoder(): Decoder { ]); } -function getTCreatorDecoder(): Decoder { +export function getTCreatorDecoder(): Decoder { return getStructDecoder([ ['address', getAddressDecoder()], ['verified', getBooleanDecoder()], diff --git a/common-helpers/src/metadata/metadata.ts b/common-helpers/src/metadata/metadata.ts index 0374503..2701228 100644 --- a/common-helpers/src/metadata/metadata.ts +++ b/common-helpers/src/metadata/metadata.ts @@ -1,10 +1,17 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Address, Rpc, SolanaRpcApi } from '@solana/web3.js'; -import { getMetadataDecoder } from './codecs/metadataDecoder'; +import { + Address, + GetAccountInfoApi, + getAddressEncoder, + getProgramDerivedAddress, + getUtf8Encoder, + Rpc, +} from '@solana/web3.js'; +import { getMetadataDecoder, Metadata } from './codecs/metadataDecoder'; +import { MPL_TOKEN_METADATA_PROGRAM_ID } from '../constants'; // fetches ruleset given metadataPda export async function getRulesetFromMetadataPda( - rpc: Rpc, + rpc: Rpc, metadataPda: Address ) { const decodedMetadataData = await fetchMetadata(rpc, metadataPda); @@ -13,7 +20,7 @@ export async function getRulesetFromMetadataPda( // fetches and deserializes data stored on given metadataPda into Metadata export async function fetchMetadata( - rpc: Rpc, + rpc: Rpc, metadataPda: Address ): Promise { const metadataData = await rpc @@ -23,3 +30,34 @@ export async function fetchMetadata( const metadataDataBytes = Buffer.from(metadataData, 'base64'); return getMetadataDecoder().decode(metadataDataBytes); } + +export type PrepPnftMetadataResult = { + metadataPda: Address; + metadata: Metadata; + ruleset: Address | undefined; +}; + +// fetches metadata and ruleset for a given mint +export async function prepPnftMetadata( + rpc: Rpc, + mint: Address +) { + const [metadataPda] = await findMetadataPda(mint); + const metadata: Metadata = await fetchMetadata(rpc, metadataPda); + const ruleset = metadata.programmableConfig?.ruleSet ?? undefined; + return { metadataPda, metadata, ruleset }; +} + +export async function findMetadataPda( + mint: Address, + programId: Address = MPL_TOKEN_METADATA_PROGRAM_ID +) { + return await getProgramDerivedAddress({ + programAddress: programId, + seeds: [ + getUtf8Encoder().encode('metadata'), + getAddressEncoder().encode(programId), + getAddressEncoder().encode(mint), + ], + }); +} diff --git a/common-helpers/src/transactions/simulateInstructions.ts b/common-helpers/src/transactions/simulateInstructions.ts index eb2e2a1..0f1e5b6 100644 --- a/common-helpers/src/transactions/simulateInstructions.ts +++ b/common-helpers/src/transactions/simulateInstructions.ts @@ -1,9 +1,11 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { + GetLatestBlockhashApi, IInstruction, KeyPairSigner, Rpc, - SolanaRpcApi, + SendTransactionApi, + Signature, + SimulateTransactionApi, appendTransactionMessageInstruction, compileTransaction, createTransactionMessage, @@ -11,13 +13,14 @@ import { pipe, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, + signTransaction, } from '@solana/web3.js'; export async function simulateTxWithIxs( - rpc: Rpc, + rpc: Rpc, ixs: IInstruction[], signer: KeyPairSigner -): Promise { +): Promise> { const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); const simPipe = pipe( createTransactionMessage({ version: 0 }), @@ -30,12 +33,39 @@ export async function simulateTxWithIxs( (tx) => compileTransaction(tx), (tx) => getBase64EncodedWireTransaction(tx) ); - const simulationResponse = await rpc + return await rpc .simulateTransaction(simPipe, { encoding: 'base64', sigVerify: false, replaceRecentBlockhash: true, }) .send(); - console.log(simulationResponse); +} + +export async function sendTxWithIxs( + rpc: Rpc, + ixs: IInstruction[], + signer: KeyPairSigner +): Promise { + const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); + const txBytes = await pipe( + createTransactionMessage({ version: 0 }), + // maps each instruction to an lambda expression that looks like: (tx) => appendTransactionInstruction(instruction, tx), + ...(ixs.map( + (ix) => (tx: any) => appendTransactionMessageInstruction(ix, tx) + ) as []), + (tx) => setTransactionMessageFeePayer(signer.address, tx), + (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), + (tx) => compileTransaction(tx), + async (tx) => + await signTransaction([signer.keyPair], tx).then((tx) => + getBase64EncodedWireTransaction(tx) + ) + ); + + return await rpc + .sendTransaction(txBytes, { + encoding: 'base64', + }) + .send(); }