From 45ca918aea3e1b0775a9426bf20ccfb56f092d1b Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Wed, 13 Nov 2024 17:15:55 +0530 Subject: [PATCH] Define IDL instructions for non transferable mint extension (#38) * Added non transferable mint intstruction in idl * Added non transferable mint intstruction in idl * Fixed test * Formatted files * Merged main and regenerated files after removing second discriminator --- .../js/src/generated/instructions/index.ts | 1 + .../initializeNonTransferableMint.ts | 155 ++++++++++++++++++ .../js/src/generated/programs/token2022.ts | 8 + .../getInitializeInstructionsForExtensions.ts | 3 + .../initializeNonTransferableMint.test.ts | 52 ++++++ program/idl.json | 44 +++++ 6 files changed, 263 insertions(+) create mode 100644 clients/js/src/generated/instructions/initializeNonTransferableMint.ts create mode 100644 clients/js/test/extensions/nonTransfer/initializeNonTransferableMint.test.ts diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 5c638e3..5e9bcdc 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -48,6 +48,7 @@ export * from './initializeMint2'; export * from './initializeMintCloseAuthority'; export * from './initializeMultisig'; export * from './initializeMultisig2'; +export * from './initializeNonTransferableMint'; export * from './initializeTokenGroup'; export * from './initializeTokenGroupMember'; export * from './initializeTokenMetadata'; diff --git a/clients/js/src/generated/instructions/initializeNonTransferableMint.ts b/clients/js/src/generated/instructions/initializeNonTransferableMint.ts new file mode 100644 index 0000000..dca03e5 --- /dev/null +++ b/clients/js/src/generated/instructions/initializeNonTransferableMint.ts @@ -0,0 +1,155 @@ +/** + * 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, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + type WritableAccount, +} from '@solana/web3.js'; +import { TOKEN_2022_PROGRAM_ADDRESS } from '../programs'; +import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; + +export const INITIALIZE_NON_TRANSFERABLE_MINT_DISCRIMINATOR = 32; + +export function getInitializeNonTransferableMintDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_NON_TRANSFERABLE_MINT_DISCRIMINATOR); +} + +export type InitializeNonTransferableMintInstruction< + 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 InitializeNonTransferableMintInstructionData = { + discriminator: number; +}; + +export type InitializeNonTransferableMintInstructionDataArgs = {}; + +export function getInitializeNonTransferableMintInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([['discriminator', getU8Encoder()]]), + (value) => ({ + ...value, + discriminator: INITIALIZE_NON_TRANSFERABLE_MINT_DISCRIMINATOR, + }) + ); +} + +export function getInitializeNonTransferableMintInstructionDataDecoder(): Decoder { + return getStructDecoder([['discriminator', getU8Decoder()]]); +} + +export function getInitializeNonTransferableMintInstructionDataCodec(): Codec< + InitializeNonTransferableMintInstructionDataArgs, + InitializeNonTransferableMintInstructionData +> { + return combineCodec( + getInitializeNonTransferableMintInstructionDataEncoder(), + getInitializeNonTransferableMintInstructionDataDecoder() + ); +} + +export type InitializeNonTransferableMintInput< + TAccountMint extends string = string, +> = { + /** The mint account to initialize. */ + mint: Address; +}; + +export function getInitializeNonTransferableMintInstruction< + TAccountMint extends string, + TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS, +>( + input: InitializeNonTransferableMintInput, + config?: { programAddress?: TProgramAddress } +): InitializeNonTransferableMintInstruction { + // 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 + >; + + const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); + const instruction = { + accounts: [getAccountMeta(accounts.mint)], + programAddress, + data: getInitializeNonTransferableMintInstructionDataEncoder().encode({}), + } as InitializeNonTransferableMintInstruction; + + return instruction; +} + +export type ParsedInitializeNonTransferableMintInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + /** The mint account to initialize. */ + mint: TAccountMetas[0]; + }; + data: InitializeNonTransferableMintInstructionData; +}; + +export function parseInitializeNonTransferableMintInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedInitializeNonTransferableMintInstruction { + 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: getInitializeNonTransferableMintInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/programs/token2022.ts b/clients/js/src/generated/programs/token2022.ts index 736b74d..44e34ed 100644 --- a/clients/js/src/generated/programs/token2022.ts +++ b/clients/js/src/generated/programs/token2022.ts @@ -53,6 +53,7 @@ import { type ParsedInitializeMintInstruction, type ParsedInitializeMultisig2Instruction, type ParsedInitializeMultisigInstruction, + type ParsedInitializeNonTransferableMintInstruction, type ParsedInitializeTokenGroupInstruction, type ParsedInitializeTokenGroupMemberInstruction, type ParsedInitializeTokenMetadataInstruction, @@ -162,6 +163,7 @@ export enum Token2022Instruction { Reallocate, EnableMemoTransfers, DisableMemoTransfers, + InitializeNonTransferableMint, EnableCpiGuard, DisableCpiGuard, InitializeMetadataPointer, @@ -410,6 +412,9 @@ export function identifyToken2022Instruction( ) { return Token2022Instruction.DisableMemoTransfers; } + if (containsBytes(data, getU8Encoder().encode(32), 0)) { + return Token2022Instruction.InitializeNonTransferableMint; + } if ( containsBytes(data, getU8Encoder().encode(34), 0) && containsBytes(data, getU8Encoder().encode(0), 1) @@ -692,6 +697,9 @@ export type ParsedToken2022Instruction< | ({ instructionType: Token2022Instruction.DisableMemoTransfers; } & ParsedDisableMemoTransfersInstruction) + | ({ + instructionType: Token2022Instruction.InitializeNonTransferableMint; + } & ParsedInitializeNonTransferableMintInstruction) | ({ instructionType: Token2022Instruction.EnableCpiGuard; } & ParsedEnableCpiGuardInstruction) diff --git a/clients/js/src/getInitializeInstructionsForExtensions.ts b/clients/js/src/getInitializeInstructionsForExtensions.ts index 59ba409..eac75d4 100644 --- a/clients/js/src/getInitializeInstructionsForExtensions.ts +++ b/clients/js/src/getInitializeInstructionsForExtensions.ts @@ -20,6 +20,7 @@ import { getInitializeTokenGroupInstruction, getInitializeTokenMetadataInstruction, getInitializeTransferFeeConfigInstruction, + getInitializeNonTransferableMintInstruction, } from './generated'; /** @@ -82,6 +83,8 @@ export function getPreInitializeInstructionsForMintExtensions( memberAddress: extension.memberAddress, }), ]; + case 'NonTransferable': + return getInitializeNonTransferableMintInstruction({ mint }); default: return []; } diff --git a/clients/js/test/extensions/nonTransfer/initializeNonTransferableMint.test.ts b/clients/js/test/extensions/nonTransfer/initializeNonTransferableMint.test.ts new file mode 100644 index 0000000..9d48ea7 --- /dev/null +++ b/clients/js/test/extensions/nonTransfer/initializeNonTransferableMint.test.ts @@ -0,0 +1,52 @@ +import { Account, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + Mint, + extension, + fetchMint, + getInitializeNonTransferableMintInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + generateKeyPairSignerWithSol, + getCreateMintInstructions, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it initializes a non-transferable mint', async (t) => { + // Given an authority and a mint account. + const client = createDefaultSolanaClient(); + const [authority, mint] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + ]); + + // When we create and initialize a mint account as non-transferable + const [createMintInstruction, initMintInstruction] = + await getCreateMintInstructions({ + authority: authority.address, + client, + extensions: [extension('NonTransferable', {})], + mint, + payer: authority, + }); + + await sendAndConfirmInstructions(client, authority, [ + createMintInstruction, + getInitializeNonTransferableMintInstruction({ + mint: mint.address, + }), + initMintInstruction, + ]); + + // Then we expect the mint to be initialized with the non-transferable extension + const mintAccount = await fetchMint(client.rpc, mint.address); + t.like(mintAccount, >{ + address: mint.address, + data: { + mintAuthority: some(authority.address), + isInitialized: true, + extensions: some([extension('NonTransferable', {})]), + }, + }); +}); diff --git a/program/idl.json b/program/idl.json index f18ecfc..43613d0 100644 --- a/program/idl.json +++ b/program/idl.json @@ -5345,6 +5345,50 @@ } ] }, + { + "kind": "instructionNode", + "name": "initializeNonTransferableMint", + "docs": [ + "Initialize the non transferable extension for the given mint account", + "", + "Fails if the account has already been initialized, so must be called before `InitializeMint`." + ], + "optionalAccountStrategy": "programId", + "accounts": [ + { + "kind": "instructionAccountNode", + "name": "mint", + "isWritable": true, + "isSigner": false, + "isOptional": false, + "docs": ["The mint account to initialize."] + } + ], + "arguments": [ + { + "kind": "instructionArgumentNode", + "name": "discriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 32 + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + } + ] + }, { "kind": "instructionNode", "name": "enableCpiGuard",