Cheatcodes are state mutations that bypass the normal transaction flow. They run instantly without consuming a blockhash or paying fees, which is exactly what you want for test setup.
In Rust, cheatcodes live under Surfnet::cheatcodes(). In JS, they're methods
directly on the Surfnet instance.
Fund SOL
fundSol / fund_sol sets the lamport balance on any account, creating the
account if it doesn't exist.
use surfpool_sdk::{Pubkey, Surfnet};let surfnet = Surfnet::start().await?;let cheats = surfnet.cheatcodes();let wallet = Pubkey::new_unique();cheats.fund_sol(&wallet, 1_000_000_000)?;// Fund several accounts in one call.let bob = Pubkey::new_unique();let carol = Pubkey::new_unique();cheats.fund_sol_many(&[(&bob, 2_000_000_000), (&carol, 3_000_000_000)])?;
Fund Tokens
fundToken / fund_token mints tokens to a wallet by computing the associated
token account, creating it if needed, and setting the amount.
Token-2022 mints
By default fundToken uses the classic SPL Token program. For Token-2022
mints, pass the Token-2022 program ID
(TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb) as the final argument.
use surfpool_sdk::{Pubkey, Surfnet};let surfnet = Surfnet::start().await?;let cheats = surfnet.cheatcodes();let wallet = Pubkey::new_unique();let mint: Pubkey = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".parse().unwrap();// Classic SPL Token mint.cheats.fund_token(&wallet, &mint, 5_000_000, None)?;// Token-2022 mint — pass the program id explicitly.let token_2022: Pubkey = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb".parse().unwrap();cheats.fund_token(&wallet, &mint, 5_000_000, Some(&token_2022))?;// Fund many wallets with the same mint.let mob = vec![Pubkey::new_unique(), Pubkey::new_unique()];cheats.fund_token_many(&mob.iter().collect::<Vec<_>>(), &mint, 1_000_000, None)?;
To derive the ATA without funding, use getAta / get_ata:
let ata = cheats.get_ata(&wallet, &mint, None);
Set Arbitrary Account State
The setAccount method writes lamports, owner, and raw data bytes for any
account in one call. The Rust SDK additionally exposes a SetAccount builder
for more advanced fields like rent_epoch and executable.
use surfpool_sdk::cheatcodes::builders::SetAccount;use surfpool_sdk::{Pubkey, Surfnet};let surfnet = Surfnet::start().await?;let cheats = surfnet.cheatcodes();let address = Pubkey::new_unique();let owner = Pubkey::new_unique();// Direct method.cheats.set_account(&address, 500_000, &[1, 2, 3], &owner)?;// Builder when you need finer control.cheats.execute(SetAccount::new(address).lamports(500_000).owner(owner).data(vec![1, 2, 3]).rent_epoch(0).executable(false),)?;
Mutate Token Account Fields
setTokenAccount updates the advanced fields of an existing token account —
delegate, state, close authority, delegated amount. Use it when fundToken
alone isn't enough.
use surfpool_sdk::cheatcodes::builders::SetTokenAccount;use surfpool_sdk::{Pubkey, Surfnet};let cheats = Surfnet::start().await?.cheatcodes();let owner = Pubkey::new_unique();let mint: Pubkey = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".parse()?;let delegate = Pubkey::new_unique();cheats.execute(SetTokenAccount::new(owner, mint).amount(2_000_000).delegate(delegate).delegated_amount(500_000).state("initialized"),)?;// Later: clear the delegation.cheats.execute(SetTokenAccount::new(owner, mint).clear_delegate())?;
Reset Accounts To Upstream State
resetAccount discards any local mutations and re-fetches the account from the
upstream RPC. Pass includeOwnedAccounts: true to also reset every account
owned by the target — useful when resetting a program and all of its PDAs.
Needs `remoteRpcUrl` to re-fetch
On an offline Surfnet, resetAccount clears the local copy but has no
upstream to fetch from. Configure remoteRpcUrl at startup to make resets
restore upstream state.
use surfpool_sdk::cheatcodes::builders::ResetAccount;use surfpool_sdk::{Pubkey, Surfnet};let cheats = Surfnet::start().await?.cheatcodes();let token_program: Pubkey = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA".parse()?;cheats.execute(ResetAccount::new(token_program).include_owned_accounts(true),)?;
Stream Live Accounts
streamAccount registers an account for background polling from the upstream
RPC. The local Surfnet copy stays in sync with mainnet without the test having
to poll explicitly. Combine with includeOwnedAccounts to stream every PDA
owned by a program.
Requires `remoteRpcUrl`
streamAccount only works on a Surfnet started with remoteRpcUrl (or
remote_rpc_url in Rust). Without an upstream RPC there is nothing to stream
from, and the call will return an error.
use surfpool_sdk::cheatcodes::builders::StreamAccount;use surfpool_sdk::{Pubkey, Surfnet};let cheats = Surfnet::builder().remote_rpc_url("https://api.mainnet-beta.solana.com").start().await?.cheatcodes();let oracle: Pubkey = "H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG".parse()?;cheats.execute(StreamAccount::new(oracle).include_owned_accounts(false))?;
Cheatcode Builders (Rust)
The Rust SDK exposes typed builders under surfpool_sdk::cheatcodes::builders
for cases where the convenience methods aren't enough. Every builder implements
the CheatcodeBuilder trait and is executed via cheats.execute(builder).
| Builder | Constructor | Common setters |
|---|---|---|
SetAccount | new(address: Pubkey) | lamports, data, owner, rent_epoch, executable |
SetTokenAccount | new(owner: Pubkey, mint: Pubkey) | amount, delegate, clear_delegate, state, delegated_amount, close_authority, clear_close_authority, token_program |
ResetAccount | new(address: Pubkey) | include_owned_accounts(bool) |
StreamAccount | new(address: Pubkey) | include_owned_accounts(bool) |
DeployProgram | new(program_id: Pubkey), from_keypair_path(path) | so_path, so_bytes, idl_path |
For the full JS surface, see JS Reference. For the full Rust surface, see Rust Reference.
Is this page helpful?