diff --git a/clients/typescript/test/setup.ts b/clients/typescript/test/setup.ts index 9c52950..2feed4e 100644 --- a/clients/typescript/test/setup.ts +++ b/clients/typescript/test/setup.ts @@ -1,7 +1,23 @@ -import { type Address, createClient, generateKeyPairSigner, type KeyPairSigner, lamports } from '@solana/kit'; +import { + type Address, + createClient, + generateKeyPairSigner, + getMinimumBalanceForRentExemption, + type KeyPairSigner, + lamports, +} from '@solana/kit'; import { solanaLocalRpc } from '@solana/kit-plugin-rpc'; import { signer } from '@solana/kit-plugin-signer'; -import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, tokenProgram } from '@solana-program/token'; +import { + AccountState, + findAssociatedTokenPda, + getMintEncoder, + getMintSize, + getTokenEncoder, + getTokenSize, + TOKEN_PROGRAM_ADDRESS, + tokenProgram, +} from '@solana-program/token'; import { expect } from 'vitest'; import { subscriptionsProgram } from '../src/index.js'; import { @@ -18,6 +34,8 @@ export const ONE_HOUR_IN_SECONDS = 3600; export const ONE_DAY_IN_SECONDS = 86400; const SYSVAR_CLOCK_ADDRESS = 'SysvarC1ock11111111111111111111111111111111' as Address; const SYSVAR_CLOCK_UNIX_TIMESTAMP_OFFSET = 32; +const SPL_TOKEN_MINT_RENT_LAMPORTS = rentLamports(getMintSize()); +const SPL_TOKEN_ACCOUNT_RENT_LAMPORTS = rentLamports(getTokenSize()); export type SmartWalletName = 'swig' | 'squads'; @@ -136,7 +154,7 @@ export class IntegrationTest { await airdropToAddress(client, payer.address, 10_000_000_000n); - const tokenMint = await createMint(client, payer, 6); + const tokenMint = await createMint(payer, 6); return new IntegrationTest(client, payer, tokenMint, TOKEN_PROGRAM_ADDRESS); } @@ -145,14 +163,14 @@ export class IntegrationTest { * Creates a new token mint with the payer as the mint authority. */ async createTokenMint(decimals: number = 6): Promise
{ - return createMint(this.client, this.payerKeypair, decimals); + return createMint(this.payerKeypair, decimals); } /** * Creates an Associated Token Account for the given owner and mints tokens to it. */ async createAtaWithBalance(mint: Address, owner: Address, amount: bigint, decimals: number = 6): Promise
{ - return createAtaWithTokens(this.client, this.payerKeypair, mint, owner, amount, decimals); + return createAtaWithTokens(mint, owner, amount, decimals); } async createFundedKeypair(lamportsAmount: bigint = 1_000_000_000n): Promise { @@ -316,46 +334,85 @@ async function airdropToAddress(client: KitClient, address: Address, lamportsAmo await client.airdrop(address, lamports(lamportsAmount)); } -async function createMint(client: KitClient, payer: KeyPairSigner, decimals: number): Promise
{ +async function createMint(payer: KeyPairSigner, decimals: number): Promise
{ const mint = await generateKeyPairSigner(); - await client.token.instructions - .createMint({ - payer, - newMint: mint, - decimals, - mintAuthority: payer.address, - freezeAuthority: payer.address, - }) - .sendTransaction(); + await callSurfnetRpc('surfnet_setAccount', [ + mint.address, + { + data: Buffer.from( + getMintEncoder().encode({ + mintAuthority: payer.address, + supply: 0n, + decimals, + isInitialized: true, + freezeAuthority: payer.address, + }), + ).toString('hex'), + lamports: SPL_TOKEN_MINT_RENT_LAMPORTS, + owner: TOKEN_PROGRAM_ADDRESS, + }, + ]); return mint.address; } -async function createAtaWithTokens( - client: KitClient, - payer: KeyPairSigner, - mint: Address, - owner: Address, - amount: bigint, - decimals: number, -): Promise
{ +async function createAtaWithTokens(mint: Address, owner: Address, amount: bigint, _decimals: number): Promise
{ const [ata] = await findAssociatedTokenPda({ mint, owner, tokenProgram: TOKEN_PROGRAM_ADDRESS, }); - await client.token.instructions - .mintToATA({ - payer, - mint, - owner, - mintAuthority: payer, - amount, - decimals, - }) - .sendTransaction(); + await callSurfnetRpc('surfnet_setAccount', [ + ata, + { + data: Buffer.from( + getTokenEncoder().encode({ + mint, + owner, + amount, + delegate: null, + state: AccountState.Initialized, + isNative: null, + delegatedAmount: 0n, + closeAuthority: null, + }), + ).toString('hex'), + lamports: SPL_TOKEN_ACCOUNT_RENT_LAMPORTS, + owner: TOKEN_PROGRAM_ADDRESS, + }, + ]); return ata; } +async function callSurfnetRpc(method: string, params: unknown[]): Promise { + const response = await fetch(SURFPOOL_RPC_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method, + params, + }), + }); + if (!response.ok) { + throw new Error(`${method} failed: HTTP ${response.status}`); + } + + const data = (await response.json()) as { error?: { message: string }; result?: T }; + if (data.error) { + throw new Error(`${method} failed: ${data.error.message}`); + } + return data.result as T; +} + +function rentLamports(space: number): number { + const value = getMinimumBalanceForRentExemption(BigInt(space)); + if (value > BigInt(Number.MAX_SAFE_INTEGER)) { + throw new Error(`rent for ${space} bytes exceeds Number.MAX_SAFE_INTEGER`); + } + return Number(value); +} + function extractErrorCode(error: unknown): number | null { if (error == null) return null; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- error introspection