From 94fb2df086b2890542e61bf72c0155b53d465020 Mon Sep 17 00:00:00 2001 From: Krupananda Reddy Date: Mon, 22 Sep 2025 09:45:42 +0530 Subject: [PATCH] feat(sdk-coin-sol): token 2022 transfer hook implementation transfer hook implementation for tbill token, added transfer hook account in tx object Ticket: WIN-7258 --- .../test-fetch-extension-accounts.ts | 97 ++++++++ modules/sdk-coin-sol/package.json | 2 + modules/sdk-coin-sol/src/lib/iface.ts | 1 + .../src/lib/solInstructionFactory.ts | 12 +- .../src/lib/token2022Extensions.ts | 233 ++++++++++++++++++ .../src/lib/tokenTransferBuilder.ts | 16 ++ .../test/unit/token2022Extensions.ts | 171 +++++++++++++ yarn.lock | 4 +- 8 files changed, 533 insertions(+), 3 deletions(-) create mode 100644 examples/ts/sol/token-extensions/test-fetch-extension-accounts.ts create mode 100644 modules/sdk-coin-sol/src/lib/token2022Extensions.ts create mode 100644 modules/sdk-coin-sol/test/unit/token2022Extensions.ts 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==