diff --git a/examples/ts/sol/token-extensions/test-fetch-extension-accounts.ts b/examples/ts/sol/token-extensions/test-fetch-extension-accounts.ts
new file mode 100644
index 0000000000..74cdb3b1cd
--- /dev/null
+++ b/examples/ts/sol/token-extensions/test-fetch-extension-accounts.ts
@@ -0,0 +1,97 @@
+import { PublicKey } from '@solana/web3.js';
+import { NetworkType } from '@bitgo/statics';
+import { fetchExtensionAccounts, getSolanaConnection } from '@bitgo/sdk-coin-sol/dist/src/lib/token2022Extensions';
+
+const TEST_MINT_ADDRESS = '4MmJVdwYN8LwvbGeCowYjSx7KoEi6BJWg8XXnW4fDDp6';
+const network = NetworkType.MAINNET;
+/**
+ * Test script to fetch extension accounts for a testnet token
+ */
+async function testFetchExtensionAccounts() {
+ console.log('='.repeat(60));
+ console.log('Testing fetchExtensionAccounts for Token-2022');
+ console.log('='.repeat(60));
+ console.log(`\nToken Mint Address: ${TEST_MINT_ADDRESS}`);
+ console.log('Network: Solana Devnet (Testnet)\n');
+
+ try {
+ // Create a mock coin object to force testnet connection
+ // First, let's verify the connection
+ const connection = getSolanaConnection(network);
+ console.log(`Connection URL: ${connection.rpcEndpoint}`);
+
+ //Get latest blockhash to verify connection is working
+ const { blockhash } = await connection.getLatestBlockhash();
+ console.log(`✓ Connection established. Latest blockhash: ${blockhash.substring(0, 20)}...`);
+
+ // Fetch mint account info directly to see if it exists
+ console.log('\n--- Checking Mint Account ---');
+ const mintPubkey = new PublicKey(TEST_MINT_ADDRESS);
+ const mintAccount = await connection.getAccountInfo(mintPubkey);
+
+ if (!mintAccount) {
+ console.log('❌ Mint account not found on devnet');
+ console.log("This might mean the token doesn't exist on devnet or has been closed.");
+ return;
+ }
+
+ console.log(`✓ Mint account found`);
+ console.log(` Owner: ${mintAccount.owner.toBase58()}`);
+ console.log(` Data length: ${mintAccount.data.length} bytes`);
+ console.log(` Lamports: ${mintAccount.lamports}`);
+
+ // Check if this is a Token-2022 mint (owned by Token-2022 program)
+ const TOKEN_2022_PROGRAM_ID = new PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb');
+ if (!mintAccount.owner.equals(TOKEN_2022_PROGRAM_ID)) {
+ console.log(`⚠️ Warning: This mint is owned by ${mintAccount.owner.toBase58()}`);
+ console.log(` Expected Token-2022 program: ${TOKEN_2022_PROGRAM_ID.toBase58()}`);
+ console.log(' This might not be a Token-2022 token.');
+ } else {
+ console.log('✓ Confirmed Token-2022 token');
+ }
+
+ // Now call fetchExtensionAccounts
+ console.log('\n--- Fetching Extension Accounts ---');
+ const extensionAccounts = await fetchExtensionAccounts(TEST_MINT_ADDRESS, network);
+
+ if (!extensionAccounts || extensionAccounts.length === 0) {
+ console.log('No extension accounts found for this token.');
+ console.log('This token might not have any extensions enabled.');
+ } else {
+ console.log(`\n✓ Found ${extensionAccounts.length} extension account(s):\n`);
+
+ extensionAccounts.forEach((account, index) => {
+ console.log(`Extension Account ${index + 1}:`);
+ console.log(` Pubkey: ${account.pubkey.toBase58()}`);
+ console.log(` Is Signer: ${account.isSigner}`);
+ console.log(` Is Writable: ${account.isWritable}`);
+ console.log('');
+ });
+ }
+
+ console.log('='.repeat(60));
+ console.log('Test completed successfully!');
+ console.log('='.repeat(60));
+ } catch (error) {
+ console.error('\n❌ Error occurred during testing:');
+ console.error(error);
+
+ if (error instanceof Error) {
+ console.error('\nError details:');
+ console.error(` Message: ${error.message}`);
+ console.error(` Stack: ${error.stack}`);
+ }
+ }
+}
+
+// Run the test
+console.log('Starting test...\n');
+testFetchExtensionAccounts()
+ .then(() => {
+ console.log('\n✅ Script execution completed');
+ process.exit(0);
+ })
+ .catch((error) => {
+ console.error('\n❌ Script failed with error:', error);
+ process.exit(1);
+ });
diff --git a/modules/sdk-coin-sol/package.json b/modules/sdk-coin-sol/package.json
index f40bdf4eb9..c109bad619 100644
--- a/modules/sdk-coin-sol/package.json
+++ b/modules/sdk-coin-sol/package.json
@@ -44,6 +44,8 @@
"@bitgo/sdk-core": "^36.9.0",
"@bitgo/sdk-lib-mpc": "^10.7.0",
"@bitgo/statics": "^58.0.0",
+ "@solana/buffer-layout": "4.0.1",
+ "@solana/buffer-layout-utils": "0.2.0",
"@solana/spl-stake-pool": "1.1.8",
"@solana/spl-token": "0.3.1",
"@solana/web3.js": "1.92.1",
diff --git a/modules/sdk-coin-sol/src/lib/iface.ts b/modules/sdk-coin-sol/src/lib/iface.ts
index e48429810e..e3d408885c 100644
--- a/modules/sdk-coin-sol/src/lib/iface.ts
+++ b/modules/sdk-coin-sol/src/lib/iface.ts
@@ -81,6 +81,7 @@ export interface TokenTransfer {
tokenAddress?: string;
decimalPlaces?: number;
programId?: string;
+ extensionAccounts?: Array<{ pubkey: string; isSigner: boolean; isWritable: boolean }>;
};
}
diff --git a/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts b/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts
index 0706687ac5..c25ac20acb 100644
--- a/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts
+++ b/modules/sdk-coin-sol/src/lib/solInstructionFactory.ts
@@ -168,7 +168,7 @@ function transferInstruction(data: Transfer): TransactionInstruction[] {
*/
function tokenTransferInstruction(data: TokenTransfer): TransactionInstruction[] {
const {
- params: { fromAddress, toAddress, amount, tokenName, sourceAddress },
+ params: { fromAddress, toAddress, amount, tokenName, sourceAddress, extensionAccounts },
} = data;
assert(fromAddress, 'Missing fromAddress (owner) param');
assert(toAddress, 'Missing toAddress param');
@@ -204,6 +204,16 @@ function tokenTransferInstruction(data: TokenTransfer): TransactionInstruction[]
[],
TOKEN_2022_PROGRAM_ID
);
+ // Add solana 2022 token extension accounts
+ if (extensionAccounts && extensionAccounts.length > 0) {
+ for (const account of extensionAccounts) {
+ transferInstruction.keys.push({
+ pubkey: new PublicKey(account.pubkey),
+ isSigner: account.isSigner,
+ isWritable: account.isWritable,
+ });
+ }
+ }
} else {
transferInstruction = createTransferCheckedInstruction(
new PublicKey(sourceAddress),
diff --git a/modules/sdk-coin-sol/src/lib/token2022Extensions.ts b/modules/sdk-coin-sol/src/lib/token2022Extensions.ts
new file mode 100644
index 0000000000..9fdc025c46
--- /dev/null
+++ b/modules/sdk-coin-sol/src/lib/token2022Extensions.ts
@@ -0,0 +1,233 @@
+///
+
+import { AccountInfo, AccountMeta, clusterApiUrl, Connection, PublicKey } from '@solana/web3.js';
+import * as splToken from '@solana/spl-token';
+import { bool, publicKey, u64 } from '@solana/buffer-layout-utils';
+import { NetworkType } from '@bitgo/statics';
+import { blob, greedy, seq, u8, struct, u32 } from '@solana/buffer-layout';
+
+export const TransferHookLayout = struct([publicKey('authority'), publicKey('programId')]);
+
+/**
+ * Fetch all extension accounts for Token-2022 tokens
+ * This includes accounts for transfer hooks, transfer fees, metadata, and other extensions
+ * @param tokenAddress - The mint address of the Token-2022 token
+ * @param network TESTNET/MAINNET
+ * @returns Array of AccountMeta objects for all extensions, or undefined if none
+ */
+type Mint = splToken.Mint;
+
+export async function fetchExtensionAccounts(
+ tokenAddress: string,
+ network?: NetworkType
+): Promise {
+ try {
+ const connection = getSolanaConnection(network);
+ const mintPubkey = new PublicKey(tokenAddress);
+ const extensionAccounts: AccountMeta[] = [];
+
+ let extensionTypes: ExtensionType[] = [];
+
+ let mint: Mint | null = null;
+ try {
+ const mintAccount = await connection.getAccountInfo(mintPubkey);
+ mint = splToken.unpackMint(mintPubkey, mintAccount, splToken.TOKEN_2022_PROGRAM_ID);
+ extensionTypes = getExtensionTypes(mint.tlvData);
+ console.log('extensions', extensionTypes);
+ } catch (error) {
+ console.debug('Failed to decode mint data:', error);
+ return undefined;
+ }
+
+ for (const extensionType of extensionTypes) {
+ switch (extensionType) {
+ case ExtensionType.TransferHook:
+ try {
+ const transferHookAccounts = await processTransferHook(mint, mintPubkey, connection);
+ extensionAccounts.push(...transferHookAccounts);
+ } catch (error) {
+ console.debug('Error processing transfer hook extension:', error);
+ }
+ break;
+ case ExtensionType.TransferFeeConfig:
+ console.debug('Transfer fee extension detected');
+ break;
+ // Other extensions can be implemented as and when required
+ default:
+ console.debug(`Extension type ${extensionType} detected`);
+ }
+ }
+ return extensionAccounts.length > 0 ? extensionAccounts : undefined;
+ } catch (error) {
+ console.warn('Failed to fetch extension accounts:', error);
+ }
+ return undefined;
+}
+
+/**
+ * Get or create a connection to the Solana network based on coin name
+ * @returns Connection instance for the appropriate network
+ * @param network
+ */
+export function getSolanaConnection(network?: NetworkType): Connection {
+ const isTestnet = network === NetworkType.TESTNET;
+ if (isTestnet) {
+ return new Connection(clusterApiUrl('devnet'), 'confirmed');
+ } else {
+ return new Connection(clusterApiUrl('mainnet-beta'), 'confirmed');
+ }
+}
+
+/**
+ * Process transfer hook extension and extract account metas
+ * @param mint - The decoded mint data
+ * @param mintPubkey - The mint public key
+ * @param connection - Solana connection
+ * @returns Array of AccountMeta objects for transfer hook accounts
+ * @private
+ */
+async function processTransferHook(
+ mint: Mint | null,
+ mintPubkey: PublicKey,
+ connection: Connection
+): Promise {
+ const accounts: AccountMeta[] = [];
+ if (!mint) {
+ return accounts;
+ }
+ const transferHookData = getTransferHook(mint);
+ if (!transferHookData) {
+ return accounts;
+ }
+ try {
+ // Get the ExtraAccountMetaList PDA
+ const extraMetaPda = getExtraAccountMetaAddress(mintPubkey, transferHookData.programId);
+
+ // Fetch the account info for the extra meta PDA
+ const extraMetaAccount = await connection.getAccountInfo(extraMetaPda);
+
+ if (extraMetaAccount) {
+ // Fetch and parse extra account metas
+ const extraMetas = getExtraAccountMetas(extraMetaAccount);
+ // Add each extra account meta to the list
+ for (const meta of extraMetas) {
+ // For static pubkey (discriminator 0), the addressConfig contains the pubkey bytes
+ accounts.push({
+ pubkey: new PublicKey(meta.addressConfig),
+ isSigner: meta.isSigner,
+ isWritable: meta.isWritable,
+ });
+ // Other discriminator types would need different handling
+ }
+ }
+ } catch (error) {
+ console.error('Error finding PDA:', error);
+ }
+ return accounts;
+}
+
+export function getExtraAccountMetaAddress(mint: PublicKey, programId: PublicKey): PublicKey {
+ const seeds = [Buffer.from('extra-account-metas'), mint.toBuffer()];
+ return PublicKey.findProgramAddressSync(seeds, programId)[0];
+}
+
+export interface TransferHook {
+ /** The transfer hook update authority */
+ authority: PublicKey;
+ /** The transfer hook program account */
+ programId: PublicKey;
+}
+
+/** Buffer layout for de/serializing a list of ExtraAccountMetaAccountData prefixed by a u32 length */
+export interface ExtraAccountMetaAccountData {
+ instructionDiscriminator: bigint;
+ length: number;
+ extraAccountsList: ExtraAccountMetaList;
+}
+
+export interface ExtraAccountMetaList {
+ count: number;
+ extraAccounts: ExtraAccountMeta[];
+}
+
+/** Buffer layout for de/serializing an ExtraAccountMeta */
+export const ExtraAccountMetaLayout = struct([
+ u8('discriminator'),
+ blob(32, 'addressConfig'),
+ bool('isSigner'),
+ bool('isWritable'),
+]);
+
+/** Buffer layout for de/serializing a list of ExtraAccountMeta prefixed by a u32 length */
+export const ExtraAccountMetaListLayout = struct([
+ u32('count'),
+ seq(ExtraAccountMetaLayout, greedy(ExtraAccountMetaLayout.span), 'extraAccounts'),
+]);
+
+export const ExtraAccountMetaAccountDataLayout = struct([
+ u64('instructionDiscriminator'),
+ u32('length'),
+ ExtraAccountMetaListLayout.replicate('extraAccountsList'),
+]);
+
+/** ExtraAccountMeta as stored by the transfer hook program */
+export interface ExtraAccountMeta {
+ discriminator: number;
+ addressConfig: Uint8Array;
+ isSigner: boolean;
+ isWritable: boolean;
+}
+
+/** Unpack an extra account metas account and parse the data into a list of ExtraAccountMetas */
+export function getExtraAccountMetas(account: AccountInfo): ExtraAccountMeta[] {
+ const extraAccountsList = ExtraAccountMetaAccountDataLayout.decode(account.data).extraAccountsList;
+ return extraAccountsList.extraAccounts.slice(0, extraAccountsList.count);
+}
+
+export function getTransferHook(mint: Mint): TransferHook | null {
+ const extensionData = getExtensionData(ExtensionType.TransferHook, mint.tlvData);
+ if (extensionData !== null) {
+ return TransferHookLayout.decode(extensionData);
+ } else {
+ return null;
+ }
+}
+
+export function getExtensionData(extension: ExtensionType, tlvData: Buffer): Buffer | null {
+ let extensionTypeIndex = 0;
+ while (addTypeAndLengthToLen(extensionTypeIndex) <= tlvData.length) {
+ const entryType = tlvData.readUInt16LE(extensionTypeIndex);
+ const entryLength = tlvData.readUInt16LE(extensionTypeIndex + TYPE_SIZE);
+ const typeIndex = addTypeAndLengthToLen(extensionTypeIndex);
+ if (entryType == extension) {
+ return tlvData.slice(typeIndex, typeIndex + entryLength);
+ }
+ extensionTypeIndex = typeIndex + entryLength;
+ }
+ return null;
+}
+
+const TYPE_SIZE = 2;
+const LENGTH_SIZE = 2;
+
+function addTypeAndLengthToLen(len: number): number {
+ return len + TYPE_SIZE + LENGTH_SIZE;
+}
+
+export function getExtensionTypes(tlvData: Buffer): ExtensionType[] {
+ const extensionTypes: number[] = [];
+ let extensionTypeIndex = 0;
+ while (extensionTypeIndex < tlvData.length) {
+ const entryType = tlvData.readUInt16LE(extensionTypeIndex);
+ extensionTypes.push(entryType);
+ const entryLength = tlvData.readUInt16LE(extensionTypeIndex + TYPE_SIZE);
+ extensionTypeIndex += TYPE_SIZE + LENGTH_SIZE + entryLength;
+ }
+ return extensionTypes;
+}
+
+export enum ExtensionType {
+ Uninitialized,
+ TransferFeeConfig,
+ TransferHook = 14,
+}
diff --git a/modules/sdk-coin-sol/src/lib/tokenTransferBuilder.ts b/modules/sdk-coin-sol/src/lib/tokenTransferBuilder.ts
index 97c2a1c2f7..493c1839c2 100644
--- a/modules/sdk-coin-sol/src/lib/tokenTransferBuilder.ts
+++ b/modules/sdk-coin-sol/src/lib/tokenTransferBuilder.ts
@@ -14,6 +14,9 @@ import { AtaInit, TokenAssociateRecipient, TokenTransfer, SetPriorityFee } from
import assert from 'assert';
import { TransactionBuilder } from './transactionBuilder';
import _ from 'lodash';
+import { AccountMeta } from '@solana/web3.js';
+import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
+import { fetchExtensionAccounts } from './token2022Extensions';
export interface SendParams {
address: string;
@@ -138,6 +141,14 @@ export class TokenTransferBuilder extends TransactionBuilder {
throw new Error(`Could not determine token information for ${sendParams.tokenName}`);
}
const sourceAddress = await getAssociatedTokenAccountAddress(tokenAddress, this._sender, false, programId);
+
+ // Only fetch extension accounts for Token-2022 tokens
+ let extensionAccounts: AccountMeta[] | undefined;
+ if (programId === TOKEN_2022_PROGRAM_ID.toString()) {
+ const network = this._coinConfig?.network.type;
+ extensionAccounts = await fetchExtensionAccounts(tokenAddress, network);
+ }
+
return {
type: InstructionBuilderTypes.TokenTransfer,
params: {
@@ -149,6 +160,11 @@ export class TokenTransferBuilder extends TransactionBuilder {
tokenAddress: tokenAddress,
programId: programId,
decimalPlaces: decimals,
+ extensionAccounts: extensionAccounts?.map((meta) => ({
+ pubkey: meta.pubkey.toString(),
+ isSigner: meta.isSigner,
+ isWritable: meta.isWritable,
+ })),
},
};
})
diff --git a/modules/sdk-coin-sol/test/unit/token2022Extensions.ts b/modules/sdk-coin-sol/test/unit/token2022Extensions.ts
new file mode 100644
index 0000000000..972de85491
--- /dev/null
+++ b/modules/sdk-coin-sol/test/unit/token2022Extensions.ts
@@ -0,0 +1,171 @@
+import should from 'should';
+import sinon from 'sinon';
+import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
+import * as splToken from '@solana/spl-token';
+import { NetworkType } from '@bitgo/statics';
+
+import {
+ fetchExtensionAccounts,
+ ExtensionType,
+ TransferHookLayout,
+ ExtraAccountMetaAccountDataLayout,
+ ExtraAccountMetaLayout,
+} from '../../src/lib/token2022Extensions';
+import { Buffer } from 'buffer';
+
+type AccountMetaEntry = {
+ pubkey: PublicKey;
+ isSigner: boolean;
+ isWritable: boolean;
+};
+
+describe('token2022Extensions', function () {
+ let sandbox: sinon.SinonSandbox;
+
+ beforeEach(function () {
+ sandbox = sinon.createSandbox();
+ sandbox.stub(console, 'debug');
+ sandbox.stub(console, 'error');
+ sandbox.stub(console, 'warn');
+ sandbox.stub(console, 'log');
+ });
+
+ afterEach(function () {
+ sandbox.restore();
+ });
+
+ it('returns transfer hook extra accounts for a single entry', async function () {
+ const mintAddress = '3BW95VLH2za2eUQ1PGfjxwMbpsnDFnmkA7m5LDgMKbX7';
+ const authority = new PublicKey('5Uf3MDczwvnMRyKKx5vGnYDHzLeFjjdFz3XJPjfR5Fuj');
+ const programId = new PublicKey('7NL6tb2cqDfMvKEtDpPuMBU5wUmYrmzJwNYDhGJMMXKA');
+ const extraMeta = new PublicKey('8VkqFGMvByZevRFGW5vGnYDHzLeFjjdFz3XJPjfR5Fuj');
+
+ const mintTlv = buildTransferHookTlv(authority, programId);
+ const extraMetaAccountData = buildExtraAccountMetaData([{ pubkey: extraMeta, isSigner: false, isWritable: true }]);
+
+ const getAccountInfoStub = sandbox.stub(Connection.prototype, 'getAccountInfo');
+ getAccountInfoStub.onFirstCall().resolves(mockMintAccount(mintTlv));
+ getAccountInfoStub.onSecondCall().resolves({
+ ...mockMintAccount(),
+ data: extraMetaAccountData,
+ owner: programId,
+ });
+
+ const result = await fetchExtensionAccounts(mintAddress, NetworkType.TESTNET);
+
+ should.exist(result);
+ const accounts = result ?? [];
+ accounts.should.have.length(1);
+ accounts[0].pubkey.equals(extraMeta).should.be.true();
+ accounts[0].isSigner.should.be.false();
+ accounts[0].isWritable.should.be.true();
+ getAccountInfoStub.calledTwice.should.be.true();
+ });
+
+ it('returns all extra accounts and preserves signer flags', async function () {
+ const mintAddress = '2MkHRHX3FSRs4Lg8LXswLf7qFP3qfpSZmD6Zm3jHZKbB';
+ const authority = new PublicKey('7GMGkC7yUoHEvW7L3AfkuL6Kq8EV9drpWNQDyXLKArv2');
+ const programId = new PublicKey('7TG7Wcnc5gcN6nSV7BpwuemhUXeyPZ2wWfZc9xry5yt3');
+
+ const accountEntries: AccountMetaEntry[] = [
+ { pubkey: new PublicKey('5pw4HHVfSvACNaPjA1kCBv3MiNHsftFQp6A8W2XAcw9C'), isSigner: false, isWritable: true },
+ { pubkey: new PublicKey('9ieDnJ7TWMBaC6wpd1JrFmgbeYh9y5zfkCUw9YGkmMbr'), isSigner: true, isWritable: false },
+ { pubkey: new PublicKey('5XyfTzdSowAgF4oXhrKexTeByNuYNaksaKMJtRvAiSkf'), isSigner: true, isWritable: true },
+ ];
+
+ const mintTlv = buildTransferHookTlv(authority, programId);
+ const extraMetaAccountData = buildExtraAccountMetaData(accountEntries);
+
+ const extraMetaPda = PublicKey.findProgramAddressSync(
+ [Buffer.from('extra-account-metas'), new PublicKey(mintAddress).toBuffer()],
+ programId
+ )[0];
+
+ const getAccountInfoStub = sandbox.stub(Connection.prototype, 'getAccountInfo');
+ getAccountInfoStub.onFirstCall().resolves(mockMintAccount(mintTlv));
+ getAccountInfoStub.onSecondCall().callsFake(async (pubkey: PublicKey) => {
+ pubkey.equals(extraMetaPda).should.be.true();
+ return {
+ ...mockMintAccount(),
+ data: extraMetaAccountData,
+ owner: programId,
+ };
+ });
+
+ const result = await fetchExtensionAccounts(mintAddress, NetworkType.TESTNET);
+
+ should.exist(result);
+ const accounts = result ?? [];
+ accounts.should.have.length(accountEntries.length);
+ accounts.forEach((meta, index) => {
+ meta.pubkey.equals(accountEntries[index].pubkey).should.be.true();
+ meta.isSigner.should.equal(accountEntries[index].isSigner);
+ meta.isWritable.should.equal(accountEntries[index].isWritable);
+ });
+ getAccountInfoStub.calledTwice.should.be.true();
+ });
+});
+function buildTransferHookTlv(authority: PublicKey, programId: PublicKey): Buffer {
+ const tlv = Buffer.alloc(4 + TransferHookLayout.span);
+ tlv.writeUInt16LE(ExtensionType.TransferHook, 0);
+ tlv.writeUInt16LE(TransferHookLayout.span, 2);
+ TransferHookLayout.encode({ authority, programId }, tlv, 4);
+ return tlv;
+}
+
+function buildExtraAccountMetaData(entries: AccountMetaEntry[]): Buffer {
+ const extraAccounts = entries.map((entry) => ({
+ discriminator: 0,
+ addressConfig: entry.pubkey.toBuffer(),
+ isSigner: entry.isSigner,
+ isWritable: entry.isWritable,
+ }));
+
+ const bufferLength = 8 + 4 + 4 + ExtraAccountMetaLayout.span * entries.length;
+ const buffer = Buffer.alloc(bufferLength);
+
+ ExtraAccountMetaAccountDataLayout.encode(
+ {
+ instructionDiscriminator: BigInt(0),
+ length: 4 + ExtraAccountMetaLayout.span * entries.length,
+ extraAccountsList: {
+ count: entries.length,
+ extraAccounts,
+ },
+ },
+ buffer
+ );
+
+ return buffer;
+}
+
+function mockMintAccount(tlvData?: Buffer): AccountInfo {
+ const hasTlv = !!tlvData && tlvData.length > 0;
+ const dataLength = hasTlv ? splToken.ACCOUNT_SIZE + splToken.ACCOUNT_TYPE_SIZE + tlvData!.length : splToken.MINT_SIZE;
+ const data = Buffer.alloc(dataLength);
+ splToken.MintLayout.encode(
+ {
+ mintAuthorityOption: 0,
+ mintAuthority: splToken.TOKEN_PROGRAM_ID,
+ supply: BigInt(0),
+ decimals: 0,
+ isInitialized: true,
+ freezeAuthorityOption: 0,
+ freezeAuthority: splToken.TOKEN_PROGRAM_ID,
+ },
+ data
+ );
+
+ if (hasTlv) {
+ data[splToken.ACCOUNT_SIZE] = splToken.AccountType.Mint;
+ tlvData!.copy(data, splToken.ACCOUNT_SIZE + splToken.ACCOUNT_TYPE_SIZE);
+ }
+
+ return {
+ data,
+ executable: false,
+ lamports: 0,
+ owner: splToken.TOKEN_2022_PROGRAM_ID,
+ rentEpoch: 0,
+ } as AccountInfo;
+}
diff --git a/yarn.lock b/yarn.lock
index 9f03248a44..dc27b7f411 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5321,7 +5321,7 @@
resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
-"@solana/buffer-layout-utils@^0.2.0":
+"@solana/buffer-layout-utils@0.2.0", "@solana/buffer-layout-utils@^0.2.0":
version "0.2.0"
resolved "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz"
integrity sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==
@@ -5331,7 +5331,7 @@
bigint-buffer "^1.1.5"
bignumber.js "^9.0.1"
-"@solana/buffer-layout@^4.0.0", "@solana/buffer-layout@^4.0.1":
+"@solana/buffer-layout@4.0.1", "@solana/buffer-layout@^4.0.0", "@solana/buffer-layout@^4.0.1":
version "4.0.1"
resolved "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz"
integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==