From 1cbdb4cf7ca3205134ac366219a9920f362dbe18 Mon Sep 17 00:00:00 2001 From: 0xCipherCoder Date: Wed, 13 Nov 2024 21:51:37 +0530 Subject: [PATCH] Define IDL instructions for the transfer hook extension (#42) * Added transfer hook extension in idl * Used createMint helper * Used createMint helper * Reverted test change for createMint --- .../js/src/generated/instructions/index.ts | 2 + .../instructions/initializeTransferHook.ts | 221 ++++++++++++++++ .../instructions/updateTransferHook.ts | 248 ++++++++++++++++++ .../js/src/generated/programs/token2022.ts | 22 ++ .../getInitializeInstructionsForExtensions.ts | 9 + .../initializeTransferHook.test.ts | 66 +++++ .../transferHook/updateTransferHook.test.ts | 61 +++++ program/idl.json | 189 +++++++++++++ 8 files changed, 818 insertions(+) create mode 100644 clients/js/src/generated/instructions/initializeTransferHook.ts create mode 100644 clients/js/src/generated/instructions/updateTransferHook.ts create mode 100644 clients/js/test/extensions/transferHook/initializeTransferHook.test.ts create mode 100644 clients/js/test/extensions/transferHook/updateTransferHook.test.ts diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 5e9bcdc..4a80b4a 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -53,6 +53,7 @@ export * from './initializeTokenGroup'; export * from './initializeTokenGroupMember'; export * from './initializeTokenMetadata'; export * from './initializeTransferFeeConfig'; +export * from './initializeTransferHook'; export * from './mintTo'; export * from './mintToChecked'; export * from './reallocate'; @@ -76,5 +77,6 @@ export * from './updateTokenGroupMaxSize'; export * from './updateTokenGroupUpdateAuthority'; export * from './updateTokenMetadataField'; export * from './updateTokenMetadataUpdateAuthority'; +export * from './updateTransferHook'; export * from './withdrawWithheldTokensFromAccounts'; export * from './withdrawWithheldTokensFromMint'; diff --git a/clients/js/src/generated/instructions/initializeTransferHook.ts b/clients/js/src/generated/instructions/initializeTransferHook.ts new file mode 100644 index 0000000..491d743 --- /dev/null +++ b/clients/js/src/generated/instructions/initializeTransferHook.ts @@ -0,0 +1,221 @@ +/** + * 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_TRANSFER_HOOK_DISCRIMINATOR = 36; + +export function getInitializeTransferHookDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_TRANSFER_HOOK_DISCRIMINATOR); +} + +export const INITIALIZE_TRANSFER_HOOK_TRANSFER_HOOK_DISCRIMINATOR = 0; + +export function getInitializeTransferHookTransferHookDiscriminatorBytes() { + return getU8Encoder().encode( + INITIALIZE_TRANSFER_HOOK_TRANSFER_HOOK_DISCRIMINATOR + ); +} + +export type InitializeTransferHookInstruction< + 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 InitializeTransferHookInstructionData = { + discriminator: number; + transferHookDiscriminator: number; + /** The public key for the account that can update the program id */ + authority: Option
; + /** The program id that performs logic during transfers */ + programId: Option
; +}; + +export type InitializeTransferHookInstructionDataArgs = { + /** The public key for the account that can update the program id */ + authority: OptionOrNullable
; + /** The program id that performs logic during transfers */ + programId: OptionOrNullable
; +}; + +export function getInitializeTransferHookInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['transferHookDiscriminator', getU8Encoder()], + [ + 'authority', + getOptionEncoder(getAddressEncoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + [ + 'programId', + getOptionEncoder(getAddressEncoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]), + (value) => ({ + ...value, + discriminator: INITIALIZE_TRANSFER_HOOK_DISCRIMINATOR, + transferHookDiscriminator: + INITIALIZE_TRANSFER_HOOK_TRANSFER_HOOK_DISCRIMINATOR, + }) + ); +} + +export function getInitializeTransferHookInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['transferHookDiscriminator', getU8Decoder()], + [ + 'authority', + getOptionDecoder(getAddressDecoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + [ + 'programId', + getOptionDecoder(getAddressDecoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]); +} + +export function getInitializeTransferHookInstructionDataCodec(): Codec< + InitializeTransferHookInstructionDataArgs, + InitializeTransferHookInstructionData +> { + return combineCodec( + getInitializeTransferHookInstructionDataEncoder(), + getInitializeTransferHookInstructionDataDecoder() + ); +} + +export type InitializeTransferHookInput = + { + /** The mint to initialize. */ + mint: Address; + authority: InitializeTransferHookInstructionDataArgs['authority']; + programId: InitializeTransferHookInstructionDataArgs['programId']; + }; + +export function getInitializeTransferHookInstruction< + TAccountMint extends string, + TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS, +>( + input: InitializeTransferHookInput, + config?: { programAddress?: TProgramAddress } +): InitializeTransferHookInstruction { + // 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: getInitializeTransferHookInstructionDataEncoder().encode( + args as InitializeTransferHookInstructionDataArgs + ), + } as InitializeTransferHookInstruction; + + return instruction; +} + +export type ParsedInitializeTransferHookInstruction< + 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: InitializeTransferHookInstructionData; +}; + +export function parseInitializeTransferHookInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedInitializeTransferHookInstruction { + 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: getInitializeTransferHookInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/instructions/updateTransferHook.ts b/clients/js/src/generated/instructions/updateTransferHook.ts new file mode 100644 index 0000000..1ef9bd1 --- /dev/null +++ b/clients/js/src/generated/instructions/updateTransferHook.ts @@ -0,0 +1,248 @@ +/** + * 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_TRANSFER_HOOK_DISCRIMINATOR = 36; + +export function getUpdateTransferHookDiscriminatorBytes() { + return getU8Encoder().encode(UPDATE_TRANSFER_HOOK_DISCRIMINATOR); +} + +export const UPDATE_TRANSFER_HOOK_TRANSFER_HOOK_DISCRIMINATOR = 1; + +export function getUpdateTransferHookTransferHookDiscriminatorBytes() { + return getU8Encoder().encode( + UPDATE_TRANSFER_HOOK_TRANSFER_HOOK_DISCRIMINATOR + ); +} + +export type UpdateTransferHookInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint extends string | IAccountMeta = string, + TAccountAuthority extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountMint extends string + ? WritableAccount + : TAccountMint, + TAccountAuthority extends string + ? ReadonlyAccount + : TAccountAuthority, + ...TRemainingAccounts, + ] + >; + +export type UpdateTransferHookInstructionData = { + discriminator: number; + transferHookDiscriminator: number; + /** The program id that performs logic during transfers */ + programId: Option
; +}; + +export type UpdateTransferHookInstructionDataArgs = { + /** The program id that performs logic during transfers */ + programId: OptionOrNullable
; +}; + +export function getUpdateTransferHookInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['transferHookDiscriminator', getU8Encoder()], + [ + 'programId', + getOptionEncoder(getAddressEncoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]), + (value) => ({ + ...value, + discriminator: UPDATE_TRANSFER_HOOK_DISCRIMINATOR, + transferHookDiscriminator: + UPDATE_TRANSFER_HOOK_TRANSFER_HOOK_DISCRIMINATOR, + }) + ); +} + +export function getUpdateTransferHookInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['transferHookDiscriminator', getU8Decoder()], + [ + 'programId', + getOptionDecoder(getAddressDecoder(), { + prefix: null, + noneValue: 'zeroes', + }), + ], + ]); +} + +export function getUpdateTransferHookInstructionDataCodec(): Codec< + UpdateTransferHookInstructionDataArgs, + UpdateTransferHookInstructionData +> { + return combineCodec( + getUpdateTransferHookInstructionDataEncoder(), + getUpdateTransferHookInstructionDataDecoder() + ); +} + +export type UpdateTransferHookInput< + TAccountMint extends string = string, + TAccountAuthority extends string = string, +> = { + /** The mint. */ + mint: Address; + /** The transfer hook authority. */ + authority: Address | TransactionSigner; + programId: UpdateTransferHookInstructionDataArgs['programId']; + multiSigners?: Array; +}; + +export function getUpdateTransferHookInstruction< + TAccountMint extends string, + TAccountAuthority extends string, + TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS, +>( + input: UpdateTransferHookInput, + config?: { programAddress?: TProgramAddress } +): UpdateTransferHookInstruction< + TProgramAddress, + TAccountMint, + (typeof input)['authority'] extends TransactionSigner + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountAuthority +> { + // Program address. + const programAddress = config?.programAddress ?? TOKEN_2022_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + mint: { value: input.mint ?? null, isWritable: true }, + authority: { value: input.authority ?? 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.authority), + ...remainingAccounts, + ], + programAddress, + data: getUpdateTransferHookInstructionDataEncoder().encode( + args as UpdateTransferHookInstructionDataArgs + ), + } as UpdateTransferHookInstruction< + TProgramAddress, + TAccountMint, + (typeof input)['authority'] extends TransactionSigner + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountAuthority + >; + + return instruction; +} + +export type ParsedUpdateTransferHookInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + /** The mint. */ + mint: TAccountMetas[0]; + /** The transfer hook authority. */ + authority: TAccountMetas[1]; + }; + data: UpdateTransferHookInstructionData; +}; + +export function parseUpdateTransferHookInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedUpdateTransferHookInstruction { + 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(), + authority: getNextAccount(), + }, + data: getUpdateTransferHookInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/programs/token2022.ts b/clients/js/src/generated/programs/token2022.ts index 44e34ed..8b16e3b 100644 --- a/clients/js/src/generated/programs/token2022.ts +++ b/clients/js/src/generated/programs/token2022.ts @@ -58,6 +58,7 @@ import { type ParsedInitializeTokenGroupMemberInstruction, type ParsedInitializeTokenMetadataInstruction, type ParsedInitializeTransferFeeConfigInstruction, + type ParsedInitializeTransferHookInstruction, type ParsedMintToCheckedInstruction, type ParsedMintToInstruction, type ParsedReallocateInstruction, @@ -80,6 +81,7 @@ import { type ParsedUpdateTokenGroupUpdateAuthorityInstruction, type ParsedUpdateTokenMetadataFieldInstruction, type ParsedUpdateTokenMetadataUpdateAuthorityInstruction, + type ParsedUpdateTransferHookInstruction, type ParsedWithdrawWithheldTokensFromAccountsInstruction, type ParsedWithdrawWithheldTokensFromMintInstruction, } from '../instructions'; @@ -166,6 +168,8 @@ export enum Token2022Instruction { InitializeNonTransferableMint, EnableCpiGuard, DisableCpiGuard, + InitializeTransferHook, + UpdateTransferHook, InitializeMetadataPointer, UpdateMetadataPointer, InitializeGroupPointer, @@ -427,6 +431,18 @@ export function identifyToken2022Instruction( ) { return Token2022Instruction.DisableCpiGuard; } + if ( + containsBytes(data, getU8Encoder().encode(36), 0) && + containsBytes(data, getU8Encoder().encode(0), 1) + ) { + return Token2022Instruction.InitializeTransferHook; + } + if ( + containsBytes(data, getU8Encoder().encode(36), 0) && + containsBytes(data, getU8Encoder().encode(1), 1) + ) { + return Token2022Instruction.UpdateTransferHook; + } if ( containsBytes(data, getU8Encoder().encode(39), 0) && containsBytes(data, getU8Encoder().encode(0), 1) @@ -706,6 +722,12 @@ export type ParsedToken2022Instruction< | ({ instructionType: Token2022Instruction.DisableCpiGuard; } & ParsedDisableCpiGuardInstruction) + | ({ + instructionType: Token2022Instruction.InitializeTransferHook; + } & ParsedInitializeTransferHookInstruction) + | ({ + instructionType: Token2022Instruction.UpdateTransferHook; + } & ParsedUpdateTransferHookInstruction) | ({ instructionType: Token2022Instruction.InitializeMetadataPointer; } & ParsedInitializeMetadataPointerInstruction) diff --git a/clients/js/src/getInitializeInstructionsForExtensions.ts b/clients/js/src/getInitializeInstructionsForExtensions.ts index eac75d4..f9a8fae 100644 --- a/clients/js/src/getInitializeInstructionsForExtensions.ts +++ b/clients/js/src/getInitializeInstructionsForExtensions.ts @@ -21,6 +21,7 @@ import { getInitializeTokenMetadataInstruction, getInitializeTransferFeeConfigInstruction, getInitializeNonTransferableMintInstruction, + getInitializeTransferHookInstruction, } from './generated'; /** @@ -85,6 +86,14 @@ export function getPreInitializeInstructionsForMintExtensions( ]; case 'NonTransferable': return getInitializeNonTransferableMintInstruction({ mint }); + case 'TransferHook': + return [ + getInitializeTransferHookInstruction({ + mint, + authority: extension.authority, + programId: extension.programId, + }), + ]; default: return []; } diff --git a/clients/js/test/extensions/transferHook/initializeTransferHook.test.ts b/clients/js/test/extensions/transferHook/initializeTransferHook.test.ts new file mode 100644 index 0000000..4fc1eef --- /dev/null +++ b/clients/js/test/extensions/transferHook/initializeTransferHook.test.ts @@ -0,0 +1,66 @@ +import { Account, address, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + Mint, + extension, + fetchMint, + getInitializeTransferHookInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + generateKeyPairSignerWithSol, + getCreateMintInstructions, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it initializes a mint with transfer hook extension', async (t) => { + // Given some signer accounts + const client = createDefaultSolanaClient(); + const [authority, mint] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + ]); + + // And a transfer hook extension + const transferHookAuthority = address( + '6sPR6MzvjMMP5LSZzEtTe4ZBVX9rhBmtM1dmfFtkNTbW' + ); + const transferHookProgramId = address( + 'BTNEPmmWuj7Sg4Fo5i1FC5eiV2Aj4jiv9boarvE5XeaX' + ); + const transferHookExtension = extension('TransferHook', { + authority: transferHookAuthority, + programId: transferHookProgramId, + }); + + // When we create and initialize a mint account with this extension + const [createMintInstruction, initMintInstruction] = + await getCreateMintInstructions({ + authority: authority.address, + client, + extensions: [transferHookExtension], + mint, + payer: authority, + }); + + await sendAndConfirmInstructions(client, authority, [ + createMintInstruction, + getInitializeTransferHookInstruction({ + mint: mint.address, + authority: some(transferHookAuthority), + programId: some(transferHookProgramId), + }), + initMintInstruction, + ]); + + // Then we expect the mint account to exist with the transfer hook extension + const mintAccount = await fetchMint(client.rpc, mint.address); + t.like(mintAccount, >{ + address: mint.address, + data: { + mintAuthority: some(authority.address), + isInitialized: true, + extensions: some([transferHookExtension]), + }, + }); +}); diff --git a/clients/js/test/extensions/transferHook/updateTransferHook.test.ts b/clients/js/test/extensions/transferHook/updateTransferHook.test.ts new file mode 100644 index 0000000..5911ed3 --- /dev/null +++ b/clients/js/test/extensions/transferHook/updateTransferHook.test.ts @@ -0,0 +1,61 @@ +import { Account, address, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + Mint, + extension, + fetchMint, + getUpdateTransferHookInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + generateKeyPairSignerWithSol, + createMint, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it updates transfer hook program ID on a mint', async (t) => { + // Given some signer accounts and client + const client = createDefaultSolanaClient(); + const [authority, hookAuthority] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + ]); + + // And a mint with initial transfer hook configuration + const oldProgramId = address('BTNEPmmWuj7Sg4Fo5i1FC5eiV2Aj4jiv9boarvE5XeaX'); + const transferHookExtension = extension('TransferHook', { + authority: hookAuthority.address, + programId: oldProgramId, + }); + + const mint = await createMint({ + authority, + client, + extensions: [transferHookExtension], + payer: authority, + }); + + // When we update the program ID + const newProgramId = address('6sPR6MzvjMMP5LSZzEtTe4ZBVX9rhBmtM1dmfFtkNTbW'); + await sendAndConfirmInstructions(client, authority, [ + getUpdateTransferHookInstruction({ + mint, + authority: hookAuthority, + programId: some(newProgramId), + }), + ]); + + // Then we expect the mint to have the updated program ID + const mintAccount = await fetchMint(client.rpc, mint); + t.like(mintAccount, >{ + address: mint, + data: { + extensions: some([ + extension('TransferHook', { + authority: hookAuthority.address, + programId: newProgramId, + }), + ]), + }, + }); +}); diff --git a/program/idl.json b/program/idl.json index 43613d0..8789869 100644 --- a/program/idl.json +++ b/program/idl.json @@ -5565,6 +5565,195 @@ } ] }, + { + "kind": "instructionNode", + "name": "initializeTransferHook", + "docs": [ + "Initialize a new mint with a transfer hook program.", + "", + "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": 36 + } + }, + { + "kind": "instructionArgumentNode", + "name": "transferHookDiscriminator", + "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 program id"], + "type": { + "kind": "zeroableOptionTypeNode", + "item": { + "kind": "publicKeyTypeNode" + } + } + }, + { + "kind": "instructionArgumentNode", + "name": "programId", + "docs": ["The program id that performs logic during transfers"], + "type": { + "kind": "zeroableOptionTypeNode", + "item": { + "kind": "publicKeyTypeNode" + } + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + }, + { + "kind": "fieldDiscriminatorNode", + "name": "transferHookDiscriminator", + "offset": 1 + } + ] + }, + { + "kind": "instructionNode", + "name": "updateTransferHook", + "docs": [ + "Update the transfer hook program id. Only supported for mints that", + "include the `TransferHook` extension.", + "", + "Accounts expected by this instruction:", + "", + " 0. `[writable]` The mint.", + " 1. `[signer]` The transfer hook authority." + ], + "optionalAccountStrategy": "programId", + "accounts": [ + { + "kind": "instructionAccountNode", + "name": "mint", + "isWritable": true, + "isSigner": false, + "isOptional": false, + "docs": ["The mint."] + }, + { + "kind": "instructionAccountNode", + "name": "authority", + "isWritable": false, + "isSigner": "either", + "isOptional": false, + "docs": ["The transfer hook authority."] + } + ], + "arguments": [ + { + "kind": "instructionArgumentNode", + "name": "discriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 36 + } + }, + { + "kind": "instructionArgumentNode", + "name": "transferHookDiscriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 1 + } + }, + { + "kind": "instructionArgumentNode", + "name": "programId", + "docs": ["The program id that performs logic during transfers"], + "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": "transferHookDiscriminator", + "offset": 1 + } + ] + }, { "kind": "instructionNode", "name": "initializeMetadataPointer",