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