Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 90 additions & 33 deletions clients/typescript/test/setup.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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';

Expand Down Expand Up @@ -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);
}
Expand All @@ -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<Address> {
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<Address> {
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<KeyPairSigner> {
Expand Down Expand Up @@ -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<Address> {
async function createMint(payer: KeyPairSigner, decimals: number): Promise<Address> {
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<Address> {
async function createAtaWithTokens(mint: Address, owner: Address, amount: bigint, _decimals: number): Promise<Address> {
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<T = unknown>(method: string, params: unknown[]): Promise<T> {
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
Expand Down
Loading