From 91ffbcbff8079fbf0da4b411bbbb8c9202ea1d30 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Tue, 8 Oct 2024 09:36:40 +0100 Subject: [PATCH] Define IDL instructions for default account state extension (#9) * Add instructions * Add tests --- .../js/src/generated/instructions/index.ts | 2 + .../initializeDefaultAccountState.ts | 195 ++++++++++++++ .../instructions/updateDefaultAccountState.ts | 239 ++++++++++++++++++ .../js/src/generated/programs/token2022.ts | 24 +- clients/js/test/_setup.ts | 2 + .../initializeDefaultAccountState.test.ts | 106 ++++++++ .../updateDefaultAccountState.test.ts | 68 +++++ program/idl.json | 170 +++++++++++++ 8 files changed, 805 insertions(+), 1 deletion(-) create mode 100644 clients/js/src/generated/instructions/initializeDefaultAccountState.ts create mode 100644 clients/js/src/generated/instructions/updateDefaultAccountState.ts create mode 100644 clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts create mode 100644 clients/js/test/extensions/defaultAccountState/updateDefaultAccountState.test.ts diff --git a/clients/js/src/generated/instructions/index.ts b/clients/js/src/generated/instructions/index.ts index 2886041..a3eddc7 100644 --- a/clients/js/src/generated/instructions/index.ts +++ b/clients/js/src/generated/instructions/index.ts @@ -33,6 +33,7 @@ export * from './initializeAccount'; export * from './initializeAccount2'; export * from './initializeAccount3'; export * from './initializeConfidentialTransferMint'; +export * from './initializeDefaultAccountState'; export * from './initializeImmutableOwner'; export * from './initializeMint'; export * from './initializeMint2'; @@ -53,5 +54,6 @@ export * from './transferChecked'; export * from './transferCheckedWithFee'; export * from './uiAmountToAmount'; export * from './updateConfidentialTransferMint'; +export * from './updateDefaultAccountState'; export * from './withdrawWithheldTokensFromAccounts'; export * from './withdrawWithheldTokensFromMint'; diff --git a/clients/js/src/generated/instructions/initializeDefaultAccountState.ts b/clients/js/src/generated/instructions/initializeDefaultAccountState.ts new file mode 100644 index 0000000..f4dadb8 --- /dev/null +++ b/clients/js/src/generated/instructions/initializeDefaultAccountState.ts @@ -0,0 +1,195 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +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'; +import { + getAccountStateDecoder, + getAccountStateEncoder, + type AccountState, + type AccountStateArgs, +} from '../types'; + +export const INITIALIZE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR = 28; + +export function getInitializeDefaultAccountStateDiscriminatorBytes() { + return getU8Encoder().encode(INITIALIZE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR); +} + +export const INITIALIZE_DEFAULT_ACCOUNT_STATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR = 0; + +export function getInitializeDefaultAccountStateDefaultAccountStateDiscriminatorBytes() { + return getU8Encoder().encode( + INITIALIZE_DEFAULT_ACCOUNT_STATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR + ); +} + +export type InitializeDefaultAccountStateInstruction< + 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 InitializeDefaultAccountStateInstructionData = { + discriminator: number; + defaultAccountStateDiscriminator: number; + /** The state each new token account should start with. */ + state: AccountState; +}; + +export type InitializeDefaultAccountStateInstructionDataArgs = { + /** The state each new token account should start with. */ + state: AccountStateArgs; +}; + +export function getInitializeDefaultAccountStateInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['defaultAccountStateDiscriminator', getU8Encoder()], + ['state', getAccountStateEncoder()], + ]), + (value) => ({ + ...value, + discriminator: INITIALIZE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR, + defaultAccountStateDiscriminator: + INITIALIZE_DEFAULT_ACCOUNT_STATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR, + }) + ); +} + +export function getInitializeDefaultAccountStateInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['defaultAccountStateDiscriminator', getU8Decoder()], + ['state', getAccountStateDecoder()], + ]); +} + +export function getInitializeDefaultAccountStateInstructionDataCodec(): Codec< + InitializeDefaultAccountStateInstructionDataArgs, + InitializeDefaultAccountStateInstructionData +> { + return combineCodec( + getInitializeDefaultAccountStateInstructionDataEncoder(), + getInitializeDefaultAccountStateInstructionDataDecoder() + ); +} + +export type InitializeDefaultAccountStateInput< + TAccountMint extends string = string, +> = { + /** The mint. */ + mint: Address; + state: InitializeDefaultAccountStateInstructionDataArgs['state']; +}; + +export function getInitializeDefaultAccountStateInstruction< + TAccountMint extends string, +>( + input: InitializeDefaultAccountStateInput +): InitializeDefaultAccountStateInstruction< + typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint +> { + // Program address. + const 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: getInitializeDefaultAccountStateInstructionDataEncoder().encode( + args as InitializeDefaultAccountStateInstructionDataArgs + ), + } as InitializeDefaultAccountStateInstruction< + typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint + >; + + return instruction; +} + +export type ParsedInitializeDefaultAccountStateInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + /** The mint. */ + mint: TAccountMetas[0]; + }; + data: InitializeDefaultAccountStateInstructionData; +}; + +export function parseInitializeDefaultAccountStateInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedInitializeDefaultAccountStateInstruction { + 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: getInitializeDefaultAccountStateInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/instructions/updateDefaultAccountState.ts b/clients/js/src/generated/instructions/updateDefaultAccountState.ts new file mode 100644 index 0000000..ea10f6d --- /dev/null +++ b/clients/js/src/generated/instructions/updateDefaultAccountState.ts @@ -0,0 +1,239 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + AccountRole, + combineCodec, + getStructDecoder, + getStructEncoder, + getU8Decoder, + getU8Encoder, + transformEncoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type IAccountMeta, + type IAccountSignerMeta, + type IInstruction, + type IInstructionWithAccounts, + type IInstructionWithData, + 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'; +import { + getAccountStateDecoder, + getAccountStateEncoder, + type AccountState, + type AccountStateArgs, +} from '../types'; + +export const UPDATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR = 28; + +export function getUpdateDefaultAccountStateDiscriminatorBytes() { + return getU8Encoder().encode(UPDATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR); +} + +export const UPDATE_DEFAULT_ACCOUNT_STATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR = 1; + +export function getUpdateDefaultAccountStateDefaultAccountStateDiscriminatorBytes() { + return getU8Encoder().encode( + UPDATE_DEFAULT_ACCOUNT_STATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR + ); +} + +export type UpdateDefaultAccountStateInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint extends string | IAccountMeta = string, + TAccountFreezeAuthority extends string | IAccountMeta = string, + TRemainingAccounts extends readonly IAccountMeta[] = [], +> = IInstruction & + IInstructionWithData & + IInstructionWithAccounts< + [ + TAccountMint extends string + ? WritableAccount + : TAccountMint, + TAccountFreezeAuthority extends string + ? ReadonlyAccount + : TAccountFreezeAuthority, + ...TRemainingAccounts, + ] + >; + +export type UpdateDefaultAccountStateInstructionData = { + discriminator: number; + defaultAccountStateDiscriminator: number; + /** The state each new token account should start with. */ + state: AccountState; +}; + +export type UpdateDefaultAccountStateInstructionDataArgs = { + /** The state each new token account should start with. */ + state: AccountStateArgs; +}; + +export function getUpdateDefaultAccountStateInstructionDataEncoder(): Encoder { + return transformEncoder( + getStructEncoder([ + ['discriminator', getU8Encoder()], + ['defaultAccountStateDiscriminator', getU8Encoder()], + ['state', getAccountStateEncoder()], + ]), + (value) => ({ + ...value, + discriminator: UPDATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR, + defaultAccountStateDiscriminator: + UPDATE_DEFAULT_ACCOUNT_STATE_DEFAULT_ACCOUNT_STATE_DISCRIMINATOR, + }) + ); +} + +export function getUpdateDefaultAccountStateInstructionDataDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU8Decoder()], + ['defaultAccountStateDiscriminator', getU8Decoder()], + ['state', getAccountStateDecoder()], + ]); +} + +export function getUpdateDefaultAccountStateInstructionDataCodec(): Codec< + UpdateDefaultAccountStateInstructionDataArgs, + UpdateDefaultAccountStateInstructionData +> { + return combineCodec( + getUpdateDefaultAccountStateInstructionDataEncoder(), + getUpdateDefaultAccountStateInstructionDataDecoder() + ); +} + +export type UpdateDefaultAccountStateInput< + TAccountMint extends string = string, + TAccountFreezeAuthority extends string = string, +> = { + /** The mint. */ + mint: Address; + /** The mint freeze authority or its multisignature account. */ + freezeAuthority: + | Address + | TransactionSigner; + state: UpdateDefaultAccountStateInstructionDataArgs['state']; + multiSigners?: Array; +}; + +export function getUpdateDefaultAccountStateInstruction< + TAccountMint extends string, + TAccountFreezeAuthority extends string, +>( + input: UpdateDefaultAccountStateInput +): UpdateDefaultAccountStateInstruction< + typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint, + (typeof input)['freezeAuthority'] extends TransactionSigner + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountFreezeAuthority +> { + // Program address. + const programAddress = TOKEN_2022_PROGRAM_ADDRESS; + + // Original accounts. + const originalAccounts = { + mint: { value: input.mint ?? null, isWritable: true }, + freezeAuthority: { + value: input.freezeAuthority ?? 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.freezeAuthority), + ...remainingAccounts, + ], + programAddress, + data: getUpdateDefaultAccountStateInstructionDataEncoder().encode( + args as UpdateDefaultAccountStateInstructionDataArgs + ), + } as UpdateDefaultAccountStateInstruction< + typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMint, + (typeof input)['freezeAuthority'] extends TransactionSigner + ? ReadonlySignerAccount & + IAccountSignerMeta + : TAccountFreezeAuthority + >; + + return instruction; +} + +export type ParsedUpdateDefaultAccountStateInstruction< + TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, + TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], +> = { + programAddress: Address; + accounts: { + /** The mint. */ + mint: TAccountMetas[0]; + /** The mint freeze authority or its multisignature account. */ + freezeAuthority: TAccountMetas[1]; + }; + data: UpdateDefaultAccountStateInstructionData; +}; + +export function parseUpdateDefaultAccountStateInstruction< + TProgram extends string, + TAccountMetas extends readonly IAccountMeta[], +>( + instruction: IInstruction & + IInstructionWithAccounts & + IInstructionWithData +): ParsedUpdateDefaultAccountStateInstruction { + 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(), + freezeAuthority: getNextAccount(), + }, + data: getUpdateDefaultAccountStateInstructionDataDecoder().decode( + instruction.data + ), + }; +} diff --git a/clients/js/src/generated/programs/token2022.ts b/clients/js/src/generated/programs/token2022.ts index 5e0ce22..c40632a 100644 --- a/clients/js/src/generated/programs/token2022.ts +++ b/clients/js/src/generated/programs/token2022.ts @@ -38,6 +38,7 @@ import { type ParsedInitializeAccount3Instruction, type ParsedInitializeAccountInstruction, type ParsedInitializeConfidentialTransferMintInstruction, + type ParsedInitializeDefaultAccountStateInstruction, type ParsedInitializeImmutableOwnerInstruction, type ParsedInitializeMint2Instruction, type ParsedInitializeMintCloseAuthorityInstruction, @@ -57,6 +58,7 @@ import { type ParsedTransferInstruction, type ParsedUiAmountToAmountInstruction, type ParsedUpdateConfidentialTransferMintInstruction, + type ParsedUpdateDefaultAccountStateInstruction, type ParsedWithdrawWithheldTokensFromAccountsInstruction, type ParsedWithdrawWithheldTokensFromMintInstruction, } from '../instructions'; @@ -135,6 +137,8 @@ export enum Token2022Instruction { EnableNonConfidentialCredits, DisableNonConfidentialCredits, ConfidentialTransferWithFee, + InitializeDefaultAccountState, + UpdateDefaultAccountState, } export function identifyToken2022Instruction( @@ -339,6 +343,18 @@ export function identifyToken2022Instruction( ) { return Token2022Instruction.ConfidentialTransferWithFee; } + if ( + containsBytes(data, getU8Encoder().encode(28), 0) && + containsBytes(data, getU8Encoder().encode(0), 1) + ) { + return Token2022Instruction.InitializeDefaultAccountState; + } + if ( + containsBytes(data, getU8Encoder().encode(28), 0) && + containsBytes(data, getU8Encoder().encode(1), 1) + ) { + return Token2022Instruction.UpdateDefaultAccountState; + } throw new Error( 'The provided instruction could not be identified as a token-2022 instruction.' ); @@ -484,4 +500,10 @@ export type ParsedToken2022Instruction< } & ParsedDisableNonConfidentialCreditsInstruction) | ({ instructionType: Token2022Instruction.ConfidentialTransferWithFee; - } & ParsedConfidentialTransferWithFeeInstruction); + } & ParsedConfidentialTransferWithFeeInstruction) + | ({ + instructionType: Token2022Instruction.InitializeDefaultAccountState; + } & ParsedInitializeDefaultAccountStateInstruction) + | ({ + instructionType: Token2022Instruction.UpdateDefaultAccountState; + } & ParsedUpdateDefaultAccountStateInstruction); diff --git a/clients/js/test/_setup.ts b/clients/js/test/_setup.ts index 013edfc..809ee6c 100644 --- a/clients/js/test/_setup.ts +++ b/clients/js/test/_setup.ts @@ -104,6 +104,7 @@ export const getCreateMintInstructions = async (input: { client: Client; decimals?: number; extensions?: ExtensionArgs[]; + freezeAuthority?: Address; mint: TransactionSigner; payer: TransactionSigner; programAddress?: Address; @@ -123,6 +124,7 @@ export const getCreateMintInstructions = async (input: { getInitializeMintInstruction({ mint: input.mint.address, decimals: input.decimals ?? 0, + freezeAuthority: input.freezeAuthority, mintAuthority: input.authority, }), ]; diff --git a/clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts b/clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts new file mode 100644 index 0000000..829b3dd --- /dev/null +++ b/clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts @@ -0,0 +1,106 @@ +import { Account, address, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + AccountState, + Mint, + extension, + fetchMint, + fetchToken, + getInitializeDefaultAccountStateInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + createToken, + generateKeyPairSignerWithSol, + getCreateMintInstructions, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it initializes a mint account with a default account state extension', async (t) => { + // Given an authority and a mint account. + const client = createDefaultSolanaClient(); + const [authority, freezeAuthority, mint] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + generateKeyPairSigner(), + ]); + + // And a default account state extension. + const defaultAccountStateExtension = extension('DefaultAccountState', { + state: AccountState.Frozen, + }); + + // When we create and initialize a mint account with this extension. + const [createMintInstruction, initMintInstruction] = + await getCreateMintInstructions({ + authority: authority.address, + client, + extensions: [defaultAccountStateExtension], + freezeAuthority: freezeAuthority.address, + mint, + payer: authority, + }); + await sendAndConfirmInstructions(client, authority, [ + createMintInstruction, + getInitializeDefaultAccountStateInstruction({ + mint: mint.address, + state: defaultAccountStateExtension.state, + }), + initMintInstruction, + ]); + + // Then we expect the mint account to exist and have the following data. + const mintAccount = await fetchMint(client.rpc, mint.address); + t.like(mintAccount, >{ + address: mint.address, + data: { + mintAuthority: some(authority.address), + isInitialized: true, + extensions: some([defaultAccountStateExtension]), + }, + }); +}); + +test('it initializes a token account with the default state defined on the mint account', async (t) => { + // Given some signer accounts. + const client = createDefaultSolanaClient(); + const [authority, freezeAuthority, mint] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + generateKeyPairSigner(), + ]); + + // And a mint account initialized with a default account state extension. + const defaultAccountStateExtension = extension('DefaultAccountState', { + state: AccountState.Frozen, + }); + const [createMintInstruction, initMintInstruction] = + await getCreateMintInstructions({ + authority: authority.address, + client, + extensions: [defaultAccountStateExtension], + freezeAuthority: freezeAuthority.address, + mint, + payer: authority, + }); + await sendAndConfirmInstructions(client, authority, [ + createMintInstruction, + getInitializeDefaultAccountStateInstruction({ + mint: mint.address, + state: defaultAccountStateExtension.state, + }), + initMintInstruction, + ]); + + // When we create a new token account for the mint. + const token = await createToken({ + client, + mint: mint.address, + owner: address('HHS1XymmkBpYAkg3XTbZLxgHa5n11PAWUCWdiVtRmzzS'), + payer: authority, + }); + + // Then we expect the token account to have the default state defined on the mint account. + const tokenAccount = await fetchToken(client.rpc, token); + t.is(tokenAccount.data.state, AccountState.Frozen); +}); diff --git a/clients/js/test/extensions/defaultAccountState/updateDefaultAccountState.test.ts b/clients/js/test/extensions/defaultAccountState/updateDefaultAccountState.test.ts new file mode 100644 index 0000000..9ebd835 --- /dev/null +++ b/clients/js/test/extensions/defaultAccountState/updateDefaultAccountState.test.ts @@ -0,0 +1,68 @@ +import { Account, generateKeyPairSigner, some } from '@solana/web3.js'; +import test from 'ava'; +import { + AccountState, + Mint, + extension, + fetchMint, + getInitializeDefaultAccountStateInstruction, + getUpdateDefaultAccountStateInstruction, +} from '../../../src'; +import { + createDefaultSolanaClient, + generateKeyPairSignerWithSol, + getCreateMintInstructions, + sendAndConfirmInstructions, +} from '../../_setup'; + +test('it updates the default state account on a mint account', async (t) => { + // Given some signer accounts. + const client = createDefaultSolanaClient(); + const [authority, freezeAuthority, mint] = await Promise.all([ + generateKeyPairSignerWithSol(client), + generateKeyPairSigner(), + generateKeyPairSigner(), + ]); + + // And a mint account initialized with a default account state extension. + const defaultAccountStateExtension = extension('DefaultAccountState', { + state: AccountState.Frozen, + }); + const [createMintInstruction, initMintInstruction] = + await getCreateMintInstructions({ + authority: authority.address, + client, + extensions: [defaultAccountStateExtension], + freezeAuthority: freezeAuthority.address, + mint, + payer: authority, + }); + await sendAndConfirmInstructions(client, authority, [ + createMintInstruction, + getInitializeDefaultAccountStateInstruction({ + mint: mint.address, + state: defaultAccountStateExtension.state, + }), + initMintInstruction, + ]); + + // When we update the default account state on the mint account. + await sendAndConfirmInstructions(client, authority, [ + getUpdateDefaultAccountStateInstruction({ + mint: mint.address, + freezeAuthority, + state: AccountState.Initialized, + }), + ]); + + // Then we expect the mint account to have the following updated data. + const mintAccount = await fetchMint(client.rpc, mint.address); + t.like(mintAccount, >{ + address: mint.address, + data: { + extensions: some([ + extension('DefaultAccountState', { state: AccountState.Initialized }), + ]), + }, + }); +}); diff --git a/program/idl.json b/program/idl.json index 928f952..ae869fc 100644 --- a/program/idl.json +++ b/program/idl.json @@ -4905,6 +4905,176 @@ "offset": 1 } ] + }, + { + "kind": "instructionNode", + "name": "initializeDefaultAccountState", + "docs": [ + "Initialize a new mint with the default state for new Accounts.", + "", + "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."] + } + ], + "arguments": [ + { + "kind": "instructionArgumentNode", + "name": "discriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 28 + } + }, + { + "kind": "instructionArgumentNode", + "name": "defaultAccountStateDiscriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 0 + } + }, + { + "kind": "instructionArgumentNode", + "name": "state", + "docs": ["The state each new token account should start with."], + "type": { + "kind": "definedTypeLinkNode", + "name": "accountState" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + }, + { + "kind": "fieldDiscriminatorNode", + "name": "defaultAccountStateDiscriminator", + "offset": 1 + } + ] + }, + { + "kind": "instructionNode", + "name": "updateDefaultAccountState", + "docs": [ + "Update the default state for new Accounts. Only supported for mints that", + "include the `DefaultAccountState` extension." + ], + "optionalAccountStrategy": "programId", + "accounts": [ + { + "kind": "instructionAccountNode", + "name": "mint", + "isWritable": true, + "isSigner": false, + "isOptional": false, + "docs": ["The mint."] + }, + { + "kind": "instructionAccountNode", + "name": "freezeAuthority", + "isWritable": false, + "isSigner": "either", + "isOptional": false, + "docs": ["The mint freeze authority or its multisignature account."] + } + ], + "arguments": [ + { + "kind": "instructionArgumentNode", + "name": "discriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 28 + } + }, + { + "kind": "instructionArgumentNode", + "name": "defaultAccountStateDiscriminator", + "defaultValueStrategy": "omitted", + "docs": [], + "type": { + "kind": "numberTypeNode", + "format": "u8", + "endian": "le" + }, + "defaultValue": { + "kind": "numberValueNode", + "number": 1 + } + }, + { + "kind": "instructionArgumentNode", + "name": "state", + "docs": ["The state each new token account should start with."], + "type": { + "kind": "definedTypeLinkNode", + "name": "accountState" + } + } + ], + "remainingAccounts": [ + { + "kind": "instructionRemainingAccountsNode", + "isOptional": true, + "isSigner": true, + "docs": [], + "value": { + "kind": "argumentValueNode", + "name": "multiSigners" + } + } + ], + "discriminators": [ + { + "kind": "fieldDiscriminatorNode", + "name": "discriminator", + "offset": 0 + }, + { + "kind": "fieldDiscriminatorNode", + "name": "defaultAccountStateDiscriminator", + "offset": 1 + } + ] } ], "definedTypes": [