From 3919069d80f34be914cb8143b9ff3fcbe05818a7 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Thu, 10 Oct 2024 15:48:56 +0100 Subject: [PATCH 1/2] Add instructions --- .../js/src/generated/instructions/index.ts | 2 + .../instructions/initializeMetadataPointer.ts | 222 +++++++++++++++ .../instructions/updateMetadataPointer.ts | 258 ++++++++++++++++++ .../js/src/generated/programs/token2022.ts | 24 +- program/idl.json | 187 +++++++++++++ 5 files changed, 692 insertions(+), 1 deletion(-) create mode 100644 clients/js/src/generated/instructions/initializeMetadataPointer.ts create mode 100644 clients/js/src/generated/instructions/updateMetadataPointer.ts diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 18cf032..c6cf1c0 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -37,6 +37,7 @@ export * from './initializeAccount3'; export * from './initializeConfidentialTransferMint'; export * from './initializeDefaultAccountState'; export * from './initializeImmutableOwner'; +export * from './initializeMetadataPointer'; export * from './initializeMint'; export * from './initializeMint2'; export * from './initializeMintCloseAuthority'; @@ -58,5 +59,6 @@ export * from './transferCheckedWithFee'; export * from './uiAmountToAmount'; export * from './updateConfidentialTransferMint'; export * from './updateDefaultAccountState'; +export * from './updateMetadataPointer'; export * from './withdrawWithheldTokensFromAccounts'; export * from './withdrawWithheldTokensFromMint'; diff --git a/clients/js/src/generated/instructions/initializeMetadataPointer.ts b/clients/js/src/generated/instructions/initializeMetadataPointer.ts new file mode 100644 index 0000000..f12f97d --- /dev/null +++ b/clients/js/src/generated/instructions/initializeMetadataPointer.ts @@ -0,0 +1,222 @@ +/** + * This code was AUTOGENERATED using the codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + combineCodec, + getAddressDecoder, + getAddressEncoder, + getOptionDecoder, + getOptionEncoder, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type Option, + type OptionOrNullable, + type WritableAccount, +} from '@solana/web3.js'; +import { TOKEN_2022_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const INITIALIZE_METADATA_POINTER_DISCRIMINATOR = 39; + +export function getInitializeMetadataPointerDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_METADATA_POINTER_DISCRIMINATOR); +} + +export const INITIALIZE_METADATA_POINTER_METADATA_POINTER_DISCRIMINATOR = 0; + +export function getInitializeMetadataPointerMetadataPointerDiscriminatorBytes() { + return getU8Encoder().encode( + INITIALIZE_METADATA_POINTER_METADATA_POINTER_DISCRIMINATOR + ); +} + +export type InitializeMetadataPointerInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountMint extends string + ? WritableAccount + : TAccountMint, + ...TRemainingAccounts, + ] + >; + +export type InitializeMetadataPointerInstructionData = { + discriminator: number; + metadataPointerDiscriminator: number; + /** The public key for the account that can update the metadata address. */ + authority: Option
; + /** The account address that holds the metadata. */ + metadataAddress: Option
; +}; + +export type InitializeMetadataPointerInstructionDataArgs = { + /** The public key for the account that can update the metadata address. */ + authority: OptionOrNullable
; + /** The account address that holds the metadata. */ + metadataAddress: OptionOrNullable
; +}; + +export function getInitializeMetadataPointerInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['metadataPointerDiscriminator', getU8Encoder()], + [ + 'authority', + getOptionEncoder(getAddressEncoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + [ + 'metadataAddress', + getOptionEncoder(getAddressEncoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]), + (value) => ({ + ...value, + discriminator: INITIALIZE_METADATA_POINTER_DISCRIMINATOR, + metadataPointerDiscriminator: + INITIALIZE_METADATA_POINTER_METADATA_POINTER_DISCRIMINATOR, + }) + ); +} + +export function getInitializeMetadataPointerInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['metadataPointerDiscriminator', getU8Decoder()], + [ + 'authority', + getOptionDecoder(getAddressDecoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + [ + 'metadataAddress', + getOptionDecoder(getAddressDecoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]); +} + +export function getInitializeMetadataPointerInstructionDataCodec(): Codec< + InitializeMetadataPointerInstructionDataArgs, + InitializeMetadataPointerInstructionData +> { + return combineCodec( + getInitializeMetadataPointerInstructionDataEncoder(), + getInitializeMetadataPointerInstructionDataDecoder() + ); +} + +export type InitializeMetadataPointerInput< + TAccountMint extends string = string, +> = { + /** The mint to initialize. */ + mint: Address; + authority: InitializeMetadataPointerInstructionDataArgs['authority']; + metadataAddress: InitializeMetadataPointerInstructionDataArgs['metadataAddress']; +}; + +export function getInitializeMetadataPointerInstruction< + TAccountMint extends string, + TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS, +>( + input: InitializeMetadataPointerInput, + config?: { programAddress?: TProgramAddress } +): InitializeMetadataPointerInstruction { + // Program address. + const programAddress = config?.programAddress ?? TOKEN_2022_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + mint: { value: input.mint ?? null, isWritable: true }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [getAccountMeta(accounts.mint)], + programAddress, + data: getInitializeMetadataPointerInstructionDataEncoder().encode( + args as InitializeMetadataPointerInstructionDataArgs + ), + } as InitializeMetadataPointerInstruction; + + return instruction; +} + +export type ParsedInitializeMetadataPointerInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + /** The mint to initialize. */ + mint: TAccountMetas[0]; + }; + data: InitializeMetadataPointerInstructionData; +}; + +export function parseInitializeMetadataPointerInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedInitializeMetadataPointerInstruction { + if (instruction.accounts.length < 1) { + // TODO: Coded error. + throw new Error('Not enough accounts'); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = instruction.accounts![accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + mint: getNextAccount(), + }, + data: getInitializeMetadataPointerInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/instructions/updateMetadataPointer.ts b/clients/js/src/generated/instructions/updateMetadataPointer.ts new file mode 100644 index 0000000..69a5fa2 --- /dev/null +++ b/clients/js/src/generated/instructions/updateMetadataPointer.ts @@ -0,0 +1,258 @@ +/** + * This code was AUTOGENERATED using the codama library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun codama to update it. + * + * @see https://github.com/codama-idl/codama + */ + +import { + AccountRole, + combineCodec, + getAddressDecoder, + getAddressEncoder, + getOptionDecoder, + getOptionEncoder, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type Option, + type OptionOrNullable, + type ReadonlyAccount, + type ReadonlySignerAccount, + type TransactionSigner, + type WritableAccount, +} from '@solana/web3.js'; +import { TOKEN_2022_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const UPDATE_METADATA_POINTER_DISCRIMINATOR = 39; + +export function getUpdateMetadataPointerDiscriminatorBytes() { + return getU8Encoder().encode(UPDATE_METADATA_POINTER_DISCRIMINATOR); +} + +export const UPDATE_METADATA_POINTER_METADATA_POINTER_DISCRIMINATOR = 1; + +export function getUpdateMetadataPointerMetadataPointerDiscriminatorBytes() { + return getU8Encoder().encode( + UPDATE_METADATA_POINTER_METADATA_POINTER_DISCRIMINATOR + ); +} + +export type UpdateMetadataPointerInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint extends string | IAccountMeta = string, + TAccountMetadataPointerAuthority extends + | string + | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountMint extends string + ? WritableAccount + : TAccountMint, + TAccountMetadataPointerAuthority extends string + ? ReadonlyAccount + : TAccountMetadataPointerAuthority, + ...TRemainingAccounts, + ] + >; + +export type UpdateMetadataPointerInstructionData = { + discriminator: number; + metadataPointerDiscriminator: number; + /** The new account address that holds the metadata. */ + metadataAddress: Option
; +}; + +export type UpdateMetadataPointerInstructionDataArgs = { + /** The new account address that holds the metadata. */ + metadataAddress: OptionOrNullable
; +}; + +export function getUpdateMetadataPointerInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['metadataPointerDiscriminator', getU8Encoder()], + [ + 'metadataAddress', + getOptionEncoder(getAddressEncoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]), + (value) => ({ + ...value, + discriminator: UPDATE_METADATA_POINTER_DISCRIMINATOR, + metadataPointerDiscriminator: + UPDATE_METADATA_POINTER_METADATA_POINTER_DISCRIMINATOR, + }) + ); +} + +export function getUpdateMetadataPointerInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['metadataPointerDiscriminator', getU8Decoder()], + [ + 'metadataAddress', + getOptionDecoder(getAddressDecoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]); +} + +export function getUpdateMetadataPointerInstructionDataCodec(): Codec< + UpdateMetadataPointerInstructionDataArgs, + UpdateMetadataPointerInstructionData +> { + return combineCodec( + getUpdateMetadataPointerInstructionDataEncoder(), + getUpdateMetadataPointerInstructionDataDecoder() + ); +} + +export type UpdateMetadataPointerInput< + TAccountMint extends string = string, + TAccountMetadataPointerAuthority extends string = string, +> = { + /** The mint to initialize. */ + mint: Address; + /** The account's owner or its multisignature account. */ + metadataPointerAuthority: + | Address + | TransactionSigner; + metadataAddress: UpdateMetadataPointerInstructionDataArgs['metadataAddress']; + multiSigners?: Array; +}; + +export function getUpdateMetadataPointerInstruction< + TAccountMint extends string, + TAccountMetadataPointerAuthority extends string, + TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS, +>( + input: UpdateMetadataPointerInput< + TAccountMint, + TAccountMetadataPointerAuthority + >, + config?: { programAddress?: TProgramAddress } +): UpdateMetadataPointerInstruction< + TProgramAddress, + TAccountMint, + (typeof input)['metadataPointerAuthority'] extends TransactionSigner + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountMetadataPointerAuthority +> { + // Program address. + const programAddress = config?.programAddress ?? TOKEN_2022_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + mint: { value: input.mint ?? null, isWritable: true }, + metadataPointerAuthority: { + value: input.metadataPointerAuthority ?? null, + isWritable: false, + }, + }; + const accounts = originalAccounts as Record< + keyof typeof originalAccounts, + ResolvedAccount + >; + + // Original args. + const args = { ...input }; + + // Remaining accounts. + const remainingAccounts: IAccountMeta[] = (args.multiSigners ?? []).map( + (signer) => ({ + address: signer.address, + role: AccountRole.READONLY_SIGNER, + signer, + }) + ); + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [ + getAccountMeta(accounts.mint), + getAccountMeta(accounts.metadataPointerAuthority), + ...remainingAccounts, + ], + programAddress, + data: getUpdateMetadataPointerInstructionDataEncoder().encode( + args as UpdateMetadataPointerInstructionDataArgs + ), + } as UpdateMetadataPointerInstruction< + TProgramAddress, + TAccountMint, + (typeof input)['metadataPointerAuthority'] extends TransactionSigner + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountMetadataPointerAuthority + >; + + return instruction; +} + +export type ParsedUpdateMetadataPointerInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + /** The mint to initialize. */ + mint: TAccountMetas[0]; + /** The account's owner or its multisignature account. */ + metadataPointerAuthority: TAccountMetas[1]; + }; + data: UpdateMetadataPointerInstructionData; +}; + +export function parseUpdateMetadataPointerInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedUpdateMetadataPointerInstruction { + if (instruction.accounts.length < 2) { + // TODO: Coded error. + throw new Error('Not enough accounts'); + } + let accountIndex = 0; + const getNextAccount = () => { + const accountMeta = instruction.accounts![accountIndex]!; + accountIndex += 1; + return accountMeta; + }; + return { + programAddress: instruction.programAddress, + accounts: { + mint: getNextAccount(), + metadataPointerAuthority: getNextAccount(), + }, + data: getUpdateMetadataPointerInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/programs/token2022.ts b/clients/js/src/generated/programs/token2022.ts index 698ca67..222bcba 100644 --- a/clients/js/src/generated/programs/token2022.ts +++ b/clients/js/src/generated/programs/token2022.ts @@ -42,6 +42,7 @@ import { type ParsedInitializeConfidentialTransferMintInstruction, type ParsedInitializeDefaultAccountStateInstruction, type ParsedInitializeImmutableOwnerInstruction, + type ParsedInitializeMetadataPointerInstruction, type ParsedInitializeMint2Instruction, type ParsedInitializeMintCloseAuthorityInstruction, type ParsedInitializeMintInstruction, @@ -62,6 +63,7 @@ import { type ParsedUiAmountToAmountInstruction, type ParsedUpdateConfidentialTransferMintInstruction, type ParsedUpdateDefaultAccountStateInstruction, + type ParsedUpdateMetadataPointerInstruction, type ParsedWithdrawWithheldTokensFromAccountsInstruction, type ParsedWithdrawWithheldTokensFromMintInstruction, } from '../instructions'; @@ -145,6 +147,8 @@ export enum Token2022Instruction { Reallocate, EnableMemoTransfers, DisableMemoTransfers, + InitializeMetadataPointer, + UpdateMetadataPointer, } export function identifyToken2022Instruction( @@ -376,6 +380,18 @@ export function identifyToken2022Instruction( ) { return Token2022Instruction.DisableMemoTransfers; } + if ( + containsBytes(data, getU8Encoder().encode(39), 0) && + containsBytes(data, getU8Encoder().encode(0), 1) + ) { + return Token2022Instruction.InitializeMetadataPointer; + } + if ( + containsBytes(data, getU8Encoder().encode(39), 0) && + containsBytes(data, getU8Encoder().encode(1), 1) + ) { + return Token2022Instruction.UpdateMetadataPointer; + } throw new Error( 'The provided instruction could not be identified as a token-2022 instruction.' ); @@ -536,4 +552,10 @@ export type ParsedToken2022Instruction< } & ParsedEnableMemoTransfersInstruction) | ({ instructionType: Token2022Instruction.DisableMemoTransfers; - } & ParsedDisableMemoTransfersInstruction); + } & ParsedDisableMemoTransfersInstruction) + | ({ + instructionType: Token2022Instruction.InitializeMetadataPointer; + } & ParsedInitializeMetadataPointerInstruction) + | ({ + instructionType: Token2022Instruction.UpdateMetadataPointer; + } & ParsedUpdateMetadataPointerInstruction); diff --git a/program/idl.json b/program/idl.json index e05bfe3..d45e5bd 100644 --- a/program/idl.json +++ b/program/idl.json @@ -5344,6 +5344,193 @@ "offset": 1 } ] + }, + { + "kind": "instructionNode", + "name": "initializeMetadataPointer", + "docs": [ + "Initialize a new mint with a metadata pointer", + "", + "Fails if the mint has already been initialized, so must be called before", + "`InitializeMint`.", + "", + "The mint must have exactly enough space allocated for the base mint (82", + "bytes), plus 83 bytes of padding, 1 byte reserved for the account type,", + "then space required for this extension, plus any others." + ], + "optionalAccountStrategy": "programId", + "accounts": [ + { + "kind": "instructionAccountNode", + "name": "mint", + "isWritable": true, + "isSigner": false, + "isOptional": false, + "docs": ["The mint to initialize."] + } + ], + "arguments": [ + { + "kind": "instructionArgumentNode", + "name": "discriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 39 + } + }, + { + "kind": "instructionArgumentNode", + "name": "metadataPointerDiscriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 0 + } + }, + { + "kind": "instructionArgumentNode", + "name": "authority", + "docs": [ + "The public key for the account that can update the metadata address." + ], + "type": { + "kind": "zeroableOptionTypeNode", + "item": { + "kind": "publicKeyTypeNode" + } + } + }, + { + "kind": "instructionArgumentNode", + "name": "metadataAddress", + "docs": ["The account address that holds the metadata."], + "type": { + "kind": "zeroableOptionTypeNode", + "item": { + "kind": "publicKeyTypeNode" + } + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + }, + { + "kind": "fieldDiscriminatorNode", + "name": "metadataPointerDiscriminator", + "offset": 1 + } + ] + }, + { + "kind": "instructionNode", + "name": "updateMetadataPointer", + "docs": [ + "Update the metadata pointer address. Only supported for mints that", + "include the `MetadataPointer` extension." + ], + "optionalAccountStrategy": "programId", + "accounts": [ + { + "kind": "instructionAccountNode", + "name": "mint", + "isWritable": true, + "isSigner": false, + "isOptional": false, + "docs": ["The mint to initialize."] + }, + { + "kind": "instructionAccountNode", + "name": "metadataPointerAuthority", + "isWritable": false, + "isSigner": "either", + "isOptional": false, + "docs": ["The account's owner or its multisignature account."] + } + ], + "arguments": [ + { + "kind": "instructionArgumentNode", + "name": "discriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 39 + } + }, + { + "kind": "instructionArgumentNode", + "name": "metadataPointerDiscriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 1 + } + }, + { + "kind": "instructionArgumentNode", + "name": "metadataAddress", + "docs": ["The new account address that holds the metadata."], + "type": { + "kind": "zeroableOptionTypeNode", + "item": { + "kind": "publicKeyTypeNode" + } + } + } + ], + "remainingAccounts": [ + { + "kind": "instructionRemainingAccountsNode", + "isOptional": true, + "isSigner": true, + "docs": [], + "value": { + "kind": "argumentValueNode", + "name": "multiSigners" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + }, + { + "kind": "fieldDiscriminatorNode", + "name": "metadataPointerDiscriminator", + "offset": 1 + } + ] } ], "definedTypes": [ From c5269f749519a4a0a766d479e614c8cfc71cc40a Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Thu, 10 Oct 2024 16:04:42 +0100 Subject: [PATCH 2/2] Add tests --- .../getInitializeInstructionsForExtensions.ts | 9 +++ .../initializeMetadataPointer.test.ts | 60 ++++++++++++++++++ .../updateMetadataPointer.test.ts | 61 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 clients/js/test/extensions/metadataPointer/initializeMetadataPointer.test.ts create mode 100644 clients/js/test/extensions/metadataPointer/updateMetadataPointer.test.ts diff --git a/clients/js/src/getInitializeInstructionsForExtensions.ts b/clients/js/src/getInitializeInstructionsForExtensions.ts index 961c5bb..21fd045 100644 --- a/clients/js/src/getInitializeInstructionsForExtensions.ts +++ b/clients/js/src/getInitializeInstructionsForExtensions.ts @@ -5,6 +5,7 @@ import { getEnableMemoTransfersInstruction, getInitializeConfidentialTransferMintInstruction, getInitializeDefaultAccountStateInstruction, + getInitializeMetadataPointerInstruction, getInitializeTransferFeeConfigInstruction, } from './generated'; @@ -44,6 +45,14 @@ export function getPreInitializeInstructionsForMintExtensions( maximumFee: extension.newerTransferFee.maximumFee, }), ]; + case 'MetadataPointer': + return [ + getInitializeMetadataPointerInstruction({ + mint, + authority: extension.authority, + metadataAddress: extension.metadataAddress, + }), + ]; default: return []; } diff --git a/clients/js/test/extensions/metadataPointer/initializeMetadataPointer.test.ts b/clients/js/test/extensions/metadataPointer/initializeMetadataPointer.test.ts new file mode 100644 index 0000000..c5e5e20 --- /dev/null +++ b/clients/js/test/extensions/metadataPointer/initializeMetadataPointer.test.ts @@ -0,0 +1,60 @@ +import { Account, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + Mint, + extension, + fetchMint, + getInitializeMetadataPointerInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + generateKeyPairSignerWithSol, + getCreateMintInstructions, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it initializes a mint account with a metadata pointer extension', async (t) => { + // Given some signer accounts. + const client = createDefaultSolanaClient(); + const [authority, mint, metadata, metadataPointerAuthority] = + await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + generateKeyPairSigner(), + generateKeyPairSigner(), + ]); + + // And a metadata pointer extension. + const metadataPointerExtension = extension('MetadataPointer', { + authority: some(metadataPointerAuthority.address), + metadataAddress: some(metadata.address), + }); + + // When we create and initialize a mint account with this extension. + const [createMintInstruction, initMintInstruction] = + await getCreateMintInstructions({ + authority: authority.address, + client, + extensions: [metadataPointerExtension], + mint, + payer: authority, + }); + await sendAndConfirmInstructions(client, authority, [ + createMintInstruction, + getInitializeMetadataPointerInstruction({ + mint: mint.address, + authority: metadataPointerExtension.authority, + metadataAddress: metadataPointerExtension.metadataAddress, + }), + initMintInstruction, + ]); + + // Then we expect the mint account to exist and have the following extension. + const mintAccount = await fetchMint(client.rpc, mint.address); + t.like(mintAccount, >{ + address: mint.address, + data: { + extensions: some([metadataPointerExtension]), + }, + }); +}); diff --git a/clients/js/test/extensions/metadataPointer/updateMetadataPointer.test.ts b/clients/js/test/extensions/metadataPointer/updateMetadataPointer.test.ts new file mode 100644 index 0000000..28df5ee --- /dev/null +++ b/clients/js/test/extensions/metadataPointer/updateMetadataPointer.test.ts @@ -0,0 +1,61 @@ +import { Account, address, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + Mint, + extension, + fetchMint, + getUpdateMetadataPointerInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + createMint, + generateKeyPairSignerWithSol, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it updates the metadata pointer extension on a mint account', async (t) => { + // Given some signer accounts. + const client = createDefaultSolanaClient(); + const [authority, metadataPointerAuthority] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + ]); + const oldMetadata = address('8dtp4b6tB8EhLpSG1jgg4swSQtUKRst2f7rJYSwE2Me3'); + const newMetadata = address('88F35KbnWKPeMnKFJDxZVjvEWmGms1FxW6wP52VABCVt'); + + // And a mint account initialized with a metadata pointer extension. + const mint = await createMint({ + authority: authority.address, + client, + extensions: [ + extension('MetadataPointer', { + authority: metadataPointerAuthority.address, + metadataAddress: oldMetadata, + }), + ], + payer: authority, + }); + + // When we update the metadata pointer on the mint account. + await sendAndConfirmInstructions(client, authority, [ + getUpdateMetadataPointerInstruction({ + mint, + metadataPointerAuthority, + metadataAddress: newMetadata, + }), + ]); + + // Then we expect the mint account to have the following updated data. + const mintAccount = await fetchMint(client.rpc, mint); + t.like(mintAccount, >{ + address: mint, + data: { + extensions: some([ + extension('MetadataPointer', { + authority: some(metadataPointerAuthority.address), + metadataAddress: some(newMetadata), + }), + ]), + }, + }); +});