Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common-helpers/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion common-helpers/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 0 additions & 1 deletion common-helpers/src/DAS/helius_queries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { DAS } from 'helius-sdk';

export async function retrieveProofFields(
Expand Down
1 change: 0 additions & 1 deletion common-helpers/src/compression/merkleTree.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
5 changes: 5 additions & 0 deletions common-helpers/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { address } from '@solana/web3.js';

export const MPL_TOKEN_METADATA_PROGRAM_ID = address(
'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opposed to adding this here if necessary, but we already do have most token program IDs here. Might make sense to move these into common-helpers instead of having them in test-helpers tho?

Copy link
Contributor Author

@leantOnSol leantOnSol Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeaaa - the overall toolkit structure is quite messy, I think we could move a lot from test-helpers over to common-helpers (if it's not test specific), but I think that's a bigger task

20 changes: 10 additions & 10 deletions common-helpers/src/metadata/codecs/metadataDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,27 @@ enum Key {
EditionMarkerV2,
}

type MetadataData = {
export type MetadataData = {
name: string;
symbol: string;
uri: string;
sellerFeeBasisPoints: number;
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<number> };

type Metadata = {
export type Metadata = {
key: Key;
updateAuthority: Address;
mint: Address;
Expand Down Expand Up @@ -103,26 +103,26 @@ export function getMetadataDecoder(): Decoder<Metadata> {
]);
}

function getCollectionDetailsDecoder(): Decoder<CollectionDetails> {
export function getCollectionDetailsDecoder(): Decoder<CollectionDetails> {
return getDiscriminatedUnionDecoder([
['V1', getStructDecoder([['size', getU64Decoder()]])],
['V2', getStructDecoder([['padding', getArrayDecoder(getU8Decoder())]])],
]);
}

function getProgrammableConfigDecoder(): Decoder<ProgrammableConfig> {
export function getProgrammableConfigDecoder(): Decoder<ProgrammableConfig> {
return getDiscriminatedUnionDecoder([
['V1', getProgrammableConfigRecordDecoder()],
]);
}

function getProgrammableConfigRecordDecoder(): Decoder<ProgrammableConfigRecord> {
export function getProgrammableConfigRecordDecoder(): Decoder<ProgrammableConfigRecord> {
return getStructDecoder([
['ruleSet', getNullableDecoder(getAddressDecoder())],
]);
}

function getMetadataDataDecoder(): Decoder<MetadataData> {
export function getMetadataDataDecoder(): Decoder<MetadataData> {
return getStructDecoder([
['name', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
['symbol', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
Expand All @@ -132,7 +132,7 @@ function getMetadataDataDecoder(): Decoder<MetadataData> {
]);
}

function getTCreatorDecoder(): Decoder<TCreator> {
export function getTCreatorDecoder(): Decoder<TCreator> {
return getStructDecoder([
['address', getAddressDecoder()],
['verified', getBooleanDecoder()],
Expand Down
48 changes: 43 additions & 5 deletions common-helpers/src/metadata/metadata.ts
Original file line number Diff line number Diff line change
@@ -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<SolanaRpcApi>,
rpc: Rpc<GetAccountInfoApi>,
metadataPda: Address
) {
const decodedMetadataData = await fetchMetadata(rpc, metadataPda);
Expand All @@ -13,7 +20,7 @@ export async function getRulesetFromMetadataPda(

// fetches and deserializes data stored on given metadataPda into Metadata
export async function fetchMetadata(
rpc: Rpc<SolanaRpcApi>,
rpc: Rpc<GetAccountInfoApi>,
metadataPda: Address
): Promise<any> {
const metadataData = await rpc
Expand All @@ -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<GetAccountInfoApi>,
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),
],
});
}
42 changes: 36 additions & 6 deletions common-helpers/src/transactions/simulateInstructions.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
GetLatestBlockhashApi,
IInstruction,
KeyPairSigner,
Rpc,
SolanaRpcApi,
SendTransactionApi,
Signature,
SimulateTransactionApi,
appendTransactionMessageInstruction,
compileTransaction,
createTransactionMessage,
getBase64EncodedWireTransaction,
pipe,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
signTransaction,
} from '@solana/web3.js';

export async function simulateTxWithIxs(
rpc: Rpc<SolanaRpcApi>,
rpc: Rpc<SimulateTransactionApi & GetLatestBlockhashApi>,
ixs: IInstruction[],
signer: KeyPairSigner
): Promise<void> {
): Promise<ReturnType<SimulateTransactionApi['simulateTransaction']>> {
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const simPipe = pipe(
createTransactionMessage({ version: 0 }),
Expand All @@ -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<SendTransactionApi & GetLatestBlockhashApi>,
ixs: IInstruction[],
signer: KeyPairSigner
): Promise<Signature> {
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();
}
Loading