From 0c8b0b82e2e0dbfbee58f9635bc17548f151ea0e Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Wed, 13 Nov 2024 23:31:24 +0530 Subject: [PATCH] Define IDL for initialize permanent delegate instruction (#44) * Added permanent delegate instruction * Added permanent delegate instruction --- .../js/src/generated/instructions/index.ts | 1 + .../initializePermanentDelegate.ts | 174 ++++++++++++++++++ .../js/src/generated/programs/token2022.ts | 8 + .../getInitializeInstructionsForExtensions.ts | 6 + .../initializePermanentDelegate.test.ts | 61 ++++++ program/idl.json | 66 ++++++- 6 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 clients/js/src/generated/instructions/initializePermanentDelegate.ts create mode 100644 clients/js/test/extensions/permanentDelegate/initializePermanentDelegate.test.ts diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 4a80b4a..ccbf600 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -49,6 +49,7 @@ export * from './initializeMintCloseAuthority'; export * from './initializeMultisig'; export * from './initializeMultisig2'; export * from './initializeNonTransferableMint'; +export * from './initializePermanentDelegate'; export * from './initializeTokenGroup'; export * from './initializeTokenGroupMember'; export * from './initializeTokenMetadata'; diff --git a/clients/js/src/generated/instructions/initializePermanentDelegate.ts b/clients/js/src/generated/instructions/initializePermanentDelegate.ts new file mode 100644 index 0000000..265cb73 --- /dev/null +++ b/clients/js/src/generated/instructions/initializePermanentDelegate.ts @@ -0,0 +1,174 @@ +/** + * 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, + 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_PERMANENT_DELEGATE_DISCRIMINATOR = 35; + +export function getInitializePermanentDelegateDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_PERMANENT_DELEGATE_DISCRIMINATOR); +} + +export type InitializePermanentDelegateInstruction< + 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 InitializePermanentDelegateInstructionData = { + discriminator: number; + /** Authority that may sign for `Transfer`s and `Burn`s on any account */ + delegate: Address; +}; + +export type InitializePermanentDelegateInstructionDataArgs = { + /** Authority that may sign for `Transfer`s and `Burn`s on any account */ + delegate: Address; +}; + +export function getInitializePermanentDelegateInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['delegate', getAddressEncoder()], + ]), + (value) => ({ + ...value, + discriminator: INITIALIZE_PERMANENT_DELEGATE_DISCRIMINATOR, + }) + ); +} + +export function getInitializePermanentDelegateInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['delegate', getAddressDecoder()], + ]); +} + +export function getInitializePermanentDelegateInstructionDataCodec(): Codec< + InitializePermanentDelegateInstructionDataArgs, + InitializePermanentDelegateInstructionData +> { + return combineCodec( + getInitializePermanentDelegateInstructionDataEncoder(), + getInitializePermanentDelegateInstructionDataDecoder() + ); +} + +export type InitializePermanentDelegateInput< + TAccountMint extends string = string, +> = { + /** The mint to initialize. */ + mint: Address; + delegate: InitializePermanentDelegateInstructionDataArgs['delegate']; +}; + +export function getInitializePermanentDelegateInstruction< + TAccountMint extends string, + TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS, +>( + input: InitializePermanentDelegateInput, + config?: { programAddress?: TProgramAddress } +): InitializePermanentDelegateInstruction { + // 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: getInitializePermanentDelegateInstructionDataEncoder().encode( + args as InitializePermanentDelegateInstructionDataArgs + ), + } as InitializePermanentDelegateInstruction; + + return instruction; +} + +export type ParsedInitializePermanentDelegateInstruction< + 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: InitializePermanentDelegateInstructionData; +}; + +export function parseInitializePermanentDelegateInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedInitializePermanentDelegateInstruction { + 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: getInitializePermanentDelegateInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/programs/token2022.ts b/clients/js/src/generated/programs/token2022.ts index 8b16e3b..093b4b3 100644 --- a/clients/js/src/generated/programs/token2022.ts +++ b/clients/js/src/generated/programs/token2022.ts @@ -54,6 +54,7 @@ import { type ParsedInitializeMultisig2Instruction, type ParsedInitializeMultisigInstruction, type ParsedInitializeNonTransferableMintInstruction, + type ParsedInitializePermanentDelegateInstruction, type ParsedInitializeTokenGroupInstruction, type ParsedInitializeTokenGroupMemberInstruction, type ParsedInitializeTokenMetadataInstruction, @@ -168,6 +169,7 @@ export enum Token2022Instruction { InitializeNonTransferableMint, EnableCpiGuard, DisableCpiGuard, + InitializePermanentDelegate, InitializeTransferHook, UpdateTransferHook, InitializeMetadataPointer, @@ -431,6 +433,9 @@ export function identifyToken2022Instruction( ) { return Token2022Instruction.DisableCpiGuard; } + if (containsBytes(data, getU8Encoder().encode(35), 0)) { + return Token2022Instruction.InitializePermanentDelegate; + } if ( containsBytes(data, getU8Encoder().encode(36), 0) && containsBytes(data, getU8Encoder().encode(0), 1) @@ -722,6 +727,9 @@ export type ParsedToken2022Instruction< | ({ instructionType: Token2022Instruction.DisableCpiGuard; } & ParsedDisableCpiGuardInstruction) + | ({ + instructionType: Token2022Instruction.InitializePermanentDelegate; + } & ParsedInitializePermanentDelegateInstruction) | ({ instructionType: Token2022Instruction.InitializeTransferHook; } & ParsedInitializeTransferHookInstruction) diff --git a/clients/js/src/getInitializeInstructionsForExtensions.ts b/clients/js/src/getInitializeInstructionsForExtensions.ts index f9a8fae..4791e4a 100644 --- a/clients/js/src/getInitializeInstructionsForExtensions.ts +++ b/clients/js/src/getInitializeInstructionsForExtensions.ts @@ -22,6 +22,7 @@ import { getInitializeTransferFeeConfigInstruction, getInitializeNonTransferableMintInstruction, getInitializeTransferHookInstruction, + getInitializePermanentDelegateInstruction, } from './generated'; /** @@ -94,6 +95,11 @@ export function getPreInitializeInstructionsForMintExtensions( programId: extension.programId, }), ]; + case 'PermanentDelegate': + return getInitializePermanentDelegateInstruction({ + mint, + delegate: extension.delegate, + }); default: return []; } diff --git a/clients/js/test/extensions/permanentDelegate/initializePermanentDelegate.test.ts b/clients/js/test/extensions/permanentDelegate/initializePermanentDelegate.test.ts new file mode 100644 index 0000000..4bc6dae --- /dev/null +++ b/clients/js/test/extensions/permanentDelegate/initializePermanentDelegate.test.ts @@ -0,0 +1,61 @@ +import { Account, address, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + Mint, + extension, + fetchMint, + getInitializePermanentDelegateInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + generateKeyPairSignerWithSol, + getCreateMintInstructions, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it initializes a mint with permanent delegate', async (t) => { + // Given some signer accounts + const client = createDefaultSolanaClient(); + const [authority, mint] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + ]); + + // And a permanent delegate extension + const permanentDelegate = address( + '6sPR6MzvjMMP5LSZzEtTe4ZBVX9rhBmtM1dmfFtkNTbW' + ); + const permanentDelegateExtension = extension('PermanentDelegate', { + delegate: permanentDelegate, + }); + + // When we create and initialize a mint account with this extension + const [createMintInstruction, initMintInstruction] = + await getCreateMintInstructions({ + authority: authority.address, + client, + extensions: [permanentDelegateExtension], + mint, + payer: authority, + }); + + await sendAndConfirmInstructions(client, authority, [ + createMintInstruction, + getInitializePermanentDelegateInstruction({ + mint: mint.address, + delegate: permanentDelegate, + }), + initMintInstruction, + ]); + + // Then we expect the mint account to exist with the permanent delegate + const mintAccount = await fetchMint(client.rpc, mint.address); + t.like(mintAccount, >{ + address: mint.address, + data: { + mintAuthority: some(authority.address), + isInitialized: true, + extensions: some([permanentDelegateExtension]), + }, + }); +}); diff --git a/program/idl.json b/program/idl.json index 8789869..18c000b 100644 --- a/program/idl.json +++ b/program/idl.json @@ -5565,6 +5565,64 @@ } ] }, + { + "kind": "instructionNode", + "name": "initializePermanentDelegate", + "docs": [ + "Initialize the permanent delegate on a new mint.", + "", + "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": 35 + } + }, + { + "kind": "instructionArgumentNode", + "name": "delegate", + "docs": [ + "Authority that may sign for `Transfer`s and `Burn`s on any account" + ], + "type": { + "kind": "publicKeyTypeNode" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + } + ] + }, { "kind": "instructionNode", "name": "initializeTransferHook", @@ -5622,7 +5680,9 @@ { "kind": "instructionArgumentNode", "name": "authority", - "docs": ["The public key for the account that can update the program id"], + "docs": [ + "The public key for the account that can update the program id" + ], "type": { "kind": "zeroableOptionTypeNode", "item": { @@ -5678,7 +5738,7 @@ "docs": ["The mint."] }, { - "kind": "instructionAccountNode", + "kind": "instructionAccountNode", "name": "authority", "isWritable": false, "isSigner": "either", @@ -5749,7 +5809,7 @@ }, { "kind": "fieldDiscriminatorNode", - "name": "transferHookDiscriminator", + "name": "transferHookDiscriminator", "offset": 1 } ]