Configuration
Configure a Surfnet at startup — remote RPC fallback, block production mode, slot timing, airdrops, feature gates, and custom payers.
Surfnet::start() (Rust) and Surfnet.start() (JS) boot an offline Surfnet with transaction-mode block production and a payer pre-funded with 10 SOL. When you need anything else — a mainnet-forked Surfnet, a slower clock, deterministic funding, a specific payer keypair — reach for the builder (Rust) or startWithConfig (JS).
Defaults
If you call start() with no arguments, you get:
| Setting | Value |
|---|---|
| Mode | Offline (no upstream RPC) |
| Block production | Transaction (blocks advance on each tx) |
| Slot time | 1 ms |
| Payer | Random keypair funded with 10 SOL (10,000,000,000 lamports) |
| RPC URL | http://127.0.0.1:<random> |
| WS URL | ws://127.0.0.1:<random> |
| Feature config | Default mainnet feature set |
Custom Configuration
use surfpool_sdk::{BlockProductionMode, Pubkey, Surfnet};
#[tokio::test]
async fn starts_with_custom_configuration() {
let alice = Pubkey::new_unique();
let surfnet = Surfnet::builder()
.remote_rpc_url("https://api.mainnet-beta.solana.com")
.block_production_mode(BlockProductionMode::Transaction)
.slot_time_ms(10)
.airdrop_addresses(vec![alice])
.airdrop_sol(5_000_000_000)
.skip_blockhash_check(true)
.start()
.await
.unwrap();
assert_eq!(
surfnet.rpc_client().get_balance(&alice).unwrap(),
5_000_000_000
);
}Builder Reference
The Rust builder and the JS config object expose the same logical options under camelCase / snake_case naming.
| Option | Rust setter | JS field | Type | Default |
|---|---|---|---|---|
| Offline mode | offline(bool) | offline?: boolean | bool | true |
| Upstream RPC | remote_rpc_url(impl Into<String>) | remoteRpcUrl?: string | URL string | none |
| Block production | block_production_mode(BlockProductionMode) | blockProductionMode?: string | enum / "manual" | "clock" | "transaction" | Transaction |
| Slot time | slot_time_ms(u64) | slotTimeMs?: number | milliseconds | 1 |
| Airdrop addresses | airdrop_addresses(Vec<Pubkey>) | airdropAddresses?: string[] | list of pubkeys | empty |
| Airdrop amount | airdrop_sol(u64) | airdropSol?: number | lamports | 10_000_000_000 |
| Skip blockhash | skip_blockhash_check(bool) | n/a | bool | false |
| Custom payer | payer(Keypair) | payerSecretKey?: Uint8Array | number[] | Keypair / bytes | generated |
| Enable feature | enable_feature(Pubkey) | enableFeatures?: string[] | feature ID(s) | none |
| Disable feature | disable_feature(Pubkey) | disableFeatures?: string[] | feature ID(s) | none |
| All features | n/a | allFeatures?: boolean | bool | false |
| Feature config | feature_config(SvmFeatureConfig) | n/a | full config | default mainnet |
Remote RPC Fallback
Setting remoteRpcUrl (or remote_rpc_url in Rust) flips the Surfnet out of offline mode. Accounts not present locally are fetched on demand from the upstream RPC, which is how mainnet-fork tests work.
let surfnet = Surfnet::builder()
.remote_rpc_url("https://api.mainnet-beta.solana.com")
.start()
.await?;Use a high-quality upstream RPC
Use a paid endpoint (Helius, Triton, QuickNode) for tests that exercise many forked accounts. Public mainnet-beta is heavily rate limited and will flake under parallel test loads.
Block Production Mode
| Mode | When blocks advance |
|---|---|
Transaction (default) | After every transaction. Best for tests that send and immediately assert. |
Clock | At a fixed interval set by slot_time_ms. Best for tests that depend on multiple slots passing without explicit transactions. |
Manual | Only when a SimnetCommand::AdvanceClock is sent. Best for time-sensitive tests where the test drives the clock. |
Custom Payer
By default the payer is a freshly generated keypair. Provide your own when tests need a known address, for example to pre-build instructions referencing the payer's public key.
use surfpool_sdk::{Keypair, Signer, Surfnet};
let payer = Keypair::new();
let surfnet = Surfnet::builder().payer(payer.insecure_clone()).start().await?;
assert_eq!(surfnet.payer().pubkey(), payer.pubkey());Feature Gates
Toggle individual SVM feature gates by ID, or activate every known feature with allFeatures: true (JS only — the Rust builder accepts feature_config for the full set).
use surfpool_sdk::{Pubkey, Surfnet};
let feature_id: Pubkey = "9bn2vTJUsUcnpiZWbu2woSKtTGW3ErZC9ERv88SDqQjK"
.parse()
.unwrap();
let surfnet = Surfnet::builder()
.enable_feature(feature_id)
.start()
.await?;Skip Blockhash Check
skip_blockhash_check(true) (Rust only) disables blockhash validation on all transactions. Useful for tests that sign transactions far in advance of submission or that don't care about expiry.
Errors
A misconfigured builder fails at start(). The Rust SDK returns SurfnetError:
| Variant | When |
|---|---|
PortAllocation(String) | Could not bind to a free RPC or WebSocket port. |
Startup(String) | The runtime failed to initialize (bad config, bad remote URL, etc.). |
Runtime(String) | The runtime crashed during startup before becoming ready. |
Aborted(String) | Startup was cancelled (e.g., shutdown signal during boot). |
Cheatcode(String) | A cheatcode call inside the builder failed. |
The JS SDK throws an Error whose message wraps the same underlying variant.
Installation
Install the Surfpool SDK for Rust or TypeScript. The JS package ships pre-built native binaries through napi-rs for macOS and Linux.
Cheatcodes
Skip transactions and directly mutate Surfnet state. Fund SOL and tokens, set arbitrary account data, reset upstream-cached accounts, and stream live mainnet accounts.