From 7dd1902b56bbef53f760a29370a1c43198ca7de0 Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Wed, 13 Nov 2024 18:27:40 +0530 Subject: [PATCH 1/2] Added permanent delegate instruction --- .../js/src/generated/instructions/index.ts | 1 + .../initializePermanentDelegate.ts | 174 ++++++++++++++++++ .../js/src/generated/programs/token2022.ts | 8 + program/idl.json | 58 ++++++ 4 files changed, 241 insertions(+) create mode 100644 clients/js/src/generated/instructions/initializePermanentDelegate.ts diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 5e9bcdc..6ec154f 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 44e34ed..56755b0 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, @@ -166,6 +167,7 @@ export enum Token2022Instruction { InitializeNonTransferableMint, EnableCpiGuard, DisableCpiGuard, + InitializePermanentDelegate, InitializeMetadataPointer, UpdateMetadataPointer, InitializeGroupPointer, @@ -427,6 +429,9 @@ export function identifyToken2022Instruction( ) { return Token2022Instruction.DisableCpiGuard; } + if (containsBytes(data, getU8Encoder().encode(35), 0)) { + return Token2022Instruction.InitializePermanentDelegate; + } if ( containsBytes(data, getU8Encoder().encode(39), 0) && containsBytes(data, getU8Encoder().encode(0), 1) @@ -706,6 +711,9 @@ export type ParsedToken2022Instruction< | ({ instructionType: Token2022Instruction.DisableCpiGuard; } & ParsedDisableCpiGuardInstruction) + | ({ + instructionType: Token2022Instruction.InitializePermanentDelegate; + } & ParsedInitializePermanentDelegateInstruction) | ({ instructionType: Token2022Instruction.InitializeMetadataPointer; } & ParsedInitializeMetadataPointerInstruction) diff --git a/program/idl.json b/program/idl.json index 43613d0..3ddd881 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": "initializeMetadataPointer", From f411d47029443a0189505a1c4e87d124ebd8dd1a Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Wed, 13 Nov 2024 19:34:24 +0530 Subject: [PATCH 2/2] Added permanent delegate instruction --- .../getInitializeInstructionsForExtensions.ts | 6 +++ .../initializePermanentDelegate.test.ts | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 clients/js/test/extensions/permanentDelegate/initializePermanentDelegate.test.ts diff --git a/clients/js/src/getInitializeInstructionsForExtensions.ts b/clients/js/src/getInitializeInstructionsForExtensions.ts index eac75d4..8c8b978 100644 --- a/clients/js/src/getInitializeInstructionsForExtensions.ts +++ b/clients/js/src/getInitializeInstructionsForExtensions.ts @@ -21,6 +21,7 @@ import { getInitializeTokenMetadataInstruction, getInitializeTransferFeeConfigInstruction, getInitializeNonTransferableMintInstruction, + getInitializePermanentDelegateInstruction, } from './generated'; /** @@ -85,6 +86,11 @@ export function getPreInitializeInstructionsForMintExtensions( ]; case 'NonTransferable': return getInitializeNonTransferableMintInstruction({ mint }); + 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..74728a1 --- /dev/null +++ b/clients/js/test/extensions/permanentDelegate/initializePermanentDelegate.test.ts @@ -0,0 +1,41 @@ +import { Account, address, some } from '@solana/web3.js'; +import test from 'ava'; +import { Mint, extension, fetchMint } from '../../../src'; +import { + createDefaultSolanaClient, + createMint, + generateKeyPairSignerWithSol, +} from '../../_setup'; + +test('it initializes a mint with permanent delegate', async (t) => { + // Given some signer accounts + const client = createDefaultSolanaClient(); + const [authority] = await Promise.all([generateKeyPairSignerWithSol(client)]); + + // And a permanent delegate extension + const permanentDelegate = address( + '6sPR6MzvjMMP5LSZzEtTe4ZBVX9rhBmtM1dmfFtkNTbW' + ); + const permanentDelegateExtension = extension('PermanentDelegate', { + delegate: permanentDelegate, + }); + + // When we create a mint with this extension + const mintAddress = await createMint({ + authority, + client, + extensions: [permanentDelegateExtension], + payer: authority, + }); + + // Then we expect the mint account to exist with the permanent delegate + const mintAccount = await fetchMint(client.rpc, mintAddress); + t.like(mintAccount, >{ + address: mintAddress, + data: { + mintAuthority: some(authority.address), + isInitialized: true, + extensions: some([permanentDelegateExtension]), + }, + }); +});