From 872081db630976b1a0b70dd0257a5686732a57e9 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Thu, 10 Oct 2024 15:34:07 +0100 Subject: [PATCH] Add more helpers to get init instructions for extensions (#15) * Add more helpers to get init instructions for extensions * Add memo transfer to post init helper * Refactor tests --- .../getInitializeInstructionsForExtensions.ts | 92 +++++++++++++++++++ ...InitializeInstructionsForMintExtensions.ts | 44 --------- clients/js/src/index.ts | 2 +- clients/js/test/_setup.ts | 51 ++++++++-- .../initializeDefaultAccountState.test.ts | 12 +-- .../disableMemoTransfers.test.ts | 6 +- .../memoTransfers/enableMemoTransfers.test.ts | 6 +- clients/js/test/extensions/reallocate.test.ts | 2 +- clients/js/test/mintTo.test.ts | 7 +- clients/js/test/transfer.test.ts | 4 +- 10 files changed, 146 insertions(+), 80 deletions(-) create mode 100644 clients/js/src/getInitializeInstructionsForExtensions.ts delete mode 100644 clients/js/src/getInitializeInstructionsForMintExtensions.ts diff --git a/clients/js/src/getInitializeInstructionsForExtensions.ts b/clients/js/src/getInitializeInstructionsForExtensions.ts new file mode 100644 index 0000000..961c5bb --- /dev/null +++ b/clients/js/src/getInitializeInstructionsForExtensions.ts @@ -0,0 +1,92 @@ +import { Address, IInstruction, TransactionSigner } from '@solana/web3.js'; +import { + ExtensionArgs, + getDisableMemoTransfersInstruction, + getEnableMemoTransfersInstruction, + getInitializeConfidentialTransferMintInstruction, + getInitializeDefaultAccountStateInstruction, + getInitializeTransferFeeConfigInstruction, +} from './generated'; + +/** + * Given a mint address and a list of mint extensions, returns a list of + * instructions that MUST be run _before_ the `initializeMint` instruction + * to properly initialize the given extensions on the mint account. + */ +export function getPreInitializeInstructionsForMintExtensions( + mint: Address, + extensions: ExtensionArgs[] +): IInstruction[] { + return extensions.flatMap((extension) => { + switch (extension.__kind) { + case 'ConfidentialTransferMint': + return [ + getInitializeConfidentialTransferMintInstruction({ + mint, + ...extension, + }), + ]; + case 'DefaultAccountState': + return [ + getInitializeDefaultAccountStateInstruction({ + mint, + state: extension.state, + }), + ]; + case 'TransferFeeConfig': + return [ + getInitializeTransferFeeConfigInstruction({ + mint, + transferFeeConfigAuthority: extension.transferFeeConfigAuthority, + withdrawWithheldAuthority: extension.withdrawWithheldAuthority, + transferFeeBasisPoints: + extension.newerTransferFee.transferFeeBasisPoints, + maximumFee: extension.newerTransferFee.maximumFee, + }), + ]; + default: + return []; + } + }); +} + +/** + * Given a mint address and a list of mint extensions, returns a list of + * instructions that MUST be run _after_ the `initializeMint` instruction + * to properly initialize the given extensions on the mint account. + */ +export function getPostInitializeInstructionsForMintExtensions( + _mint: Address, + extensions: ExtensionArgs[] +): IInstruction[] { + return extensions.flatMap((extension) => { + switch (extension.__kind) { + default: + return []; + } + }); +} + +/** + * Given a token address, its owner and a list of token extensions, returns a list + * of instructions that MUST be run _after_ the `initializeAccount` instruction + * to properly initialize the given extensions on the token account. + */ +export function getPostInitializeInstructionsForTokenExtensions( + token: Address, + owner: TransactionSigner, + extensions: ExtensionArgs[] +): IInstruction[] { + return extensions.flatMap((extension) => { + switch (extension.__kind) { + case 'MemoTransfer': + return [ + extension.requireIncomingTransferMemos + ? getEnableMemoTransfersInstruction({ owner, token }) + : getDisableMemoTransfersInstruction({ owner, token }), + ]; + default: + return []; + } + }); +} diff --git a/clients/js/src/getInitializeInstructionsForMintExtensions.ts b/clients/js/src/getInitializeInstructionsForMintExtensions.ts deleted file mode 100644 index a85c9ef..0000000 --- a/clients/js/src/getInitializeInstructionsForMintExtensions.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Address, IInstruction } from '@solana/web3.js'; -import { - ExtensionArgs, - getInitializeConfidentialTransferMintInstruction, - getInitializeDefaultAccountStateInstruction, - getInitializeTransferFeeConfigInstruction, -} from './generated'; - -export function getInitializeInstructionsForMintExtensions( - mint: Address, - extensions: ExtensionArgs[] -): IInstruction[] { - return extensions.flatMap((extension) => { - switch (extension.__kind) { - case 'ConfidentialTransferMint': - return [ - getInitializeConfidentialTransferMintInstruction({ - mint, - ...extension, - }), - ]; - case 'DefaultAccountState': - return [ - getInitializeDefaultAccountStateInstruction({ - mint, - state: extension.state, - }), - ]; - case 'TransferFeeConfig': - return [ - getInitializeTransferFeeConfigInstruction({ - mint, - transferFeeConfigAuthority: extension.transferFeeConfigAuthority, - withdrawWithheldAuthority: extension.withdrawWithheldAuthority, - transferFeeBasisPoints: - extension.newerTransferFee.transferFeeBasisPoints, - maximumFee: extension.newerTransferFee.maximumFee, - }), - ]; - default: - return []; - } - }); -} diff --git a/clients/js/src/index.ts b/clients/js/src/index.ts index 50c28b6..026d5f3 100644 --- a/clients/js/src/index.ts +++ b/clients/js/src/index.ts @@ -1,5 +1,5 @@ export * from './generated'; -export * from './getInitializeInstructionsForMintExtensions'; +export * from './getInitializeInstructionsForExtensions'; export * from './getTokenSize'; export * from './getMintSize'; diff --git a/clients/js/test/_setup.ts b/clients/js/test/_setup.ts index b97189f..eba35a6 100644 --- a/clients/js/test/_setup.ts +++ b/clients/js/test/_setup.ts @@ -28,11 +28,13 @@ import { ExtensionArgs, TOKEN_2022_PROGRAM_ADDRESS, getInitializeAccountInstruction, - getInitializeInstructionsForMintExtensions, + getPreInitializeInstructionsForMintExtensions, getInitializeMintInstruction, getMintSize, getMintToInstruction, getTokenSize, + getPostInitializeInstructionsForMintExtensions, + getPostInitializeInstructionsForTokenExtensions, } from '../src'; type Client = { @@ -170,34 +172,67 @@ export const createMint = async ( }); await sendAndConfirmInstructions(input.client, input.payer, [ createAccount, - ...getInitializeInstructionsForMintExtensions( + ...getPreInitializeInstructionsForMintExtensions( mint.address, input.extensions ?? [] ), initMint, + ...getPostInitializeInstructionsForMintExtensions( + mint.address, + input.extensions ?? [] + ), ]); return mint.address; }; export const createToken = async ( - input: Omit[0], 'token'> + input: Omit< + Parameters[0], + 'token' | 'owner' + > & { owner: TransactionSigner } ): Promise
=> { const token = await generateKeyPairSigner(); - const instructions = await getCreateTokenInstructions({ ...input, token }); - await sendAndConfirmInstructions(input.client, input.payer, instructions); + const [createAccount, initToken] = await getCreateTokenInstructions({ + ...input, + owner: input.owner.address, + token, + }); + await sendAndConfirmInstructions(input.client, input.payer, [ + createAccount, + initToken, + ...getPostInitializeInstructionsForTokenExtensions( + token.address, + input.owner, + input.extensions ?? [] + ), + ]); return token.address; }; export const createTokenWithAmount = async ( - input: Omit[0], 'token'> & { + input: Omit< + Parameters[0], + 'token' | 'owner' + > & { amount: number | bigint; mintAuthority: TransactionSigner; + owner: TransactionSigner; } ): Promise
=> { const token = await generateKeyPairSigner(); - const instructions = await getCreateTokenInstructions({ ...input, token }); + const [createAccount, initToken] = await getCreateTokenInstructions({ + ...input, + owner: input.owner.address, + token, + }); await sendAndConfirmInstructions(input.client, input.payer, [ - ...instructions, + createAccount, + initToken, + ...getPostInitializeInstructionsForTokenExtensions( + token.address, + input.owner, + input.extensions ?? [] + ), getMintToInstruction({ mint: input.mint, token: token.address, diff --git a/clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts b/clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts index a79d3bd..2f5bb60 100644 --- a/clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts +++ b/clients/js/test/extensions/defaultAccountState/initializeDefaultAccountState.test.ts @@ -1,4 +1,4 @@ -import { Account, address, generateKeyPairSigner, some } from '@solana/web3.js'; +import { Account, generateKeyPairSigner, some } from '@solana/web3.js'; import test from 'ava'; import { AccountState, @@ -65,9 +65,10 @@ test('it initializes a mint account with a default account state extension', asy 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] = await Promise.all([ + const [authority, freezeAuthority, owner] = await Promise.all([ generateKeyPairSignerWithSol(client), generateKeyPairSigner(), + generateKeyPairSigner(), ]); // And a mint account initialized with a default account state extension. @@ -82,12 +83,7 @@ test('it initializes a token account with the default state defined on the mint }); // When we create a new token account for the mint. - const token = await createToken({ - client, - mint, - owner: address('HHS1XymmkBpYAkg3XTbZLxgHa5n11PAWUCWdiVtRmzzS'), - payer: authority, - }); + const token = await createToken({ client, mint, owner, 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); diff --git a/clients/js/test/extensions/memoTransfers/disableMemoTransfers.test.ts b/clients/js/test/extensions/memoTransfers/disableMemoTransfers.test.ts index c5949f8..183c505 100644 --- a/clients/js/test/extensions/memoTransfers/disableMemoTransfers.test.ts +++ b/clients/js/test/extensions/memoTransfers/disableMemoTransfers.test.ts @@ -5,7 +5,6 @@ import { extension, fetchToken, getDisableMemoTransfersInstruction, - getEnableMemoTransfersInstruction, } from '../../../src'; import { createDefaultSolanaClient, @@ -81,12 +80,9 @@ test('it disables an active memo transfers extension', async (t) => { extension('MemoTransfer', { requireIncomingTransferMemos: true }), ], mint, - owner: owner.address, + owner, payer: authority, }); - await sendAndConfirmInstructions(client, authority, [ - getEnableMemoTransfersInstruction({ token, owner }), - ]); // When we disable the memo transfers extension. await sendAndConfirmInstructions(client, authority, [ diff --git a/clients/js/test/extensions/memoTransfers/enableMemoTransfers.test.ts b/clients/js/test/extensions/memoTransfers/enableMemoTransfers.test.ts index 318dc07..8039485 100644 --- a/clients/js/test/extensions/memoTransfers/enableMemoTransfers.test.ts +++ b/clients/js/test/extensions/memoTransfers/enableMemoTransfers.test.ts @@ -4,7 +4,6 @@ import { Token, extension, fetchToken, - getDisableMemoTransfersInstruction, getEnableMemoTransfersInstruction, } from '../../../src'; import { @@ -81,12 +80,9 @@ test('it enables an disabled memo transfers extension', async (t) => { extension('MemoTransfer', { requireIncomingTransferMemos: false }), ], mint, - owner: owner.address, + owner, payer: authority, }); - await sendAndConfirmInstructions(client, authority, [ - getDisableMemoTransfersInstruction({ token, owner }), - ]); // When we enable the memo transfers extension. await sendAndConfirmInstructions(client, authority, [ diff --git a/clients/js/test/extensions/reallocate.test.ts b/clients/js/test/extensions/reallocate.test.ts index 71d92b9..4c64bd6 100644 --- a/clients/js/test/extensions/reallocate.test.ts +++ b/clients/js/test/extensions/reallocate.test.ts @@ -33,7 +33,7 @@ test('it reallocates token accounts to fit the provided extensions', async (t) = const token = await createToken({ client, mint, - owner: owner.address, + owner, payer: authority, }); t.is(await getAccountLength(client, token), 165); diff --git a/clients/js/test/mintTo.test.ts b/clients/js/test/mintTo.test.ts index f566f1a..9d10ce1 100644 --- a/clients/js/test/mintTo.test.ts +++ b/clients/js/test/mintTo.test.ts @@ -28,12 +28,7 @@ test('it mints tokens to a token account', async (t) => { payer, authority: mintAuthority.address, }); - const token = await createToken({ - client, - payer, - mint, - owner: owner.address, - }); + const token = await createToken({ client, payer, mint, owner }); // When the mint authority mints tokens to the token account. const mintTo = getMintToInstruction({ diff --git a/clients/js/test/transfer.test.ts b/clients/js/test/transfer.test.ts index 08c634b..acb00fc 100644 --- a/clients/js/test/transfer.test.ts +++ b/clients/js/test/transfer.test.ts @@ -37,10 +37,10 @@ test('it transfers tokens from one account to another', async (t) => { payer, mintAuthority, mint, - owner: ownerA.address, + owner: ownerA, amount: 100n, }), - createToken({ client, payer, mint, owner: ownerB.address }), + createToken({ client, payer, mint, owner: ownerB }), ]); // When owner A transfers 50 tokens to owner B.