github solana-foundation/surfpool v1.2.0

10 hours ago

Surfpool v1.2.0 Release Notes

Introducing Surfpool SDK

Surfpool now ships surfpool-sdk for embedding a full Surfpool Surfnet directly
inside tests. You can start an isolated local Solana-compatible runtime without
shelling out to surfpool start, get dynamic HTTP and WebSocket endpoints, use a
pre-funded payer, and call cheatcode helpers for fast state setup.

The SDK is available as:

  • Rust crate: surfpool-sdk
  • Node.js package: surfpool-sdk, backed by napi-rs native bindings and
    prebuilt package artifacts for macOS x64, macOS arm64, and Linux x64 GNU

Rust Usage

Add the SDK to integration-test dependencies:

[dev-dependencies]
surfpool-sdk = "1.2.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Start a Surfnet and use the built-in RPC client:

use surfpool_sdk::{Signer, Surfnet};

#[tokio::test]
async fn starts_embedded_surfnet() {
    let surfnet = Surfnet::start().await.unwrap();

    let rpc = surfnet.rpc_client();
    let payer_balance = rpc.get_balance(&surfnet.payer().pubkey()).unwrap();

    assert!(payer_balance > 0);
}

Configure startup behavior when you need deterministic test setup:

use surfpool_sdk::{BlockProductionMode, Pubkey, Surfnet};

#[tokio::test]
async fn starts_with_custom_state() {
    let recipient = Pubkey::new_unique();

    let surfnet = Surfnet::builder()
        .offline(true)
        .block_production_mode(BlockProductionMode::Transaction)
        .airdrop_addresses(vec![recipient])
        .airdrop_sol(10_000_000_000)
        .start()
        .await
        .unwrap();

    surfnet.cheatcodes().fund_sol(&recipient, 5_000_000_000).unwrap();
}

Use cheatcodes for common integration-test setup:

use surfpool_sdk::{Pubkey, Surfnet};

#[tokio::test]
async fn seeds_accounts() {
    let surfnet = Surfnet::start().await.unwrap();
    let cheats = surfnet.cheatcodes();

    let owner = Pubkey::new_unique();
    let mint = Pubkey::new_unique();

    cheats.fund_sol(&owner, 1_000_000_000).unwrap();
    cheats.fund_token(&owner, &mint, 1_000, None).unwrap();
    cheats.time_travel_to_slot(1_000).unwrap();
}

Deploy a local Solana program from build artifacts:

use surfpool_sdk::Surfnet;

#[tokio::test]
async fn deploys_program() {
    let surfnet = Surfnet::start().await.unwrap();
    let cheats = surfnet.cheatcodes();

    // Looks for target/deploy/my_program.so,
    // target/deploy/my_program-keypair.json,
    // and target/idl/my_program.json when present.
    let program_id = cheats.deploy_program("my_program").unwrap();

    assert!(surfnet.rpc_client().get_account(&program_id).is_ok());
}

Use an explicit program id and artifact path when your build output is custom:

use surfpool_sdk::{cheatcodes::builders::DeployProgram, Pubkey, Surfnet};

#[tokio::test]
async fn deploys_from_explicit_artifacts() {
    let surfnet = Surfnet::start().await.unwrap();
    let program_id = Pubkey::new_unique();

    surfnet
        .cheatcodes()
        .deploy(
            DeployProgram::new(program_id)
                .so_path("fixtures/programs/my_program.so")
                .idl_path("fixtures/idls/my_program.json"),
        )
        .unwrap();
}

The Rust API also exposes typed cheatcode builders for setting accounts,
resetting accounts, streaming accounts, setting token-account fields, and
deploying local program artifacts.

Node.js Usage

Install the package in a Node.js 18+ test project:

npm install surfpool-sdk

Start Surfnet and seed state from a Node test:

const assert = require("node:assert/strict");
const test = require("node:test");
const { Surfnet } = require("surfpool-sdk");

test("uses embedded surfpool", async () => {
  const surfnet = Surfnet.start();
  const owner = Surfnet.newKeypair().publicKey;
  const mint = Surfnet.newKeypair().publicKey;

  surfnet.fundSol(owner, 1_000_000_000);
  surfnet.fundToken(owner, mint, 1_000);

  assert.match(surfnet.rpcUrl, /^http:\/\/127\.0\.0\.1:/);
});

For custom startup options, use startWithConfig:

const payer = Surfnet.newKeypair();

const surfnet = Surfnet.startWithConfig({
  offline: true,
  blockProductionMode: "manual",
  slotTimeMs: 1,
  airdropSol: 1_000_000_000,
  payerSecretKey: Uint8Array.from(payer.secretKey),
});

Deploy a local Solana program from build artifacts:

const surfnet = Surfnet.start();

// Looks for target/deploy/my_program.so,
// target/deploy/my_program-keypair.json,
// and target/idl/my_program.json when present.
const programId = surfnet.deployProgram("my_program");

console.log(`deployed ${programId} to ${surfnet.rpcUrl}`);

Use explicit deployment options when you already have the program id and .so
path:

const programId = Surfnet.newKeypair().publicKey;

surfnet.deploy({
  programId,
  soPath: "fixtures/programs/my_program.so",
  idlPath: "fixtures/idls/my_program.json",
});

The Node API includes helpers for SOL and token funding, arbitrary account
updates, token-account state, account reset, remote account streaming, time
travel, program deployment, ATA derivation, event draining, and keypair
generation.

New Features

New Cheatcodes

  • feat(rpc): add rpc methods to enable/disable other cheatcodes (#557) (by @0xzrf)

    Surfnet cheatcode access can now be changed at runtime with
    surfnet_enableCheatcode and surfnet_disableCheatcode. This lets tests or
    hosted environments narrow the mutable RPC surface after setup.

    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "surfnet_disableCheatcode",
      "params": [["surfnet_setAccount", "surfnet_timeTravel"]]
    }
  • feat: add `surfnet_offlineAccount` RPC method (#566) (by @CanardMandarin)

    surfnet_offlineAccount prevents Surfpool from downloading a specific account
    from the configured remote RPC. Pass includeOwnedAccounts to also block
    accounts owned by that pubkey, which is useful when a test must prove behavior
    without remote fallback.

    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "surfnet_offlineAccount",
      "params": ["<account-pubkey>", { "includeOwnedAccounts": true }]
    }
  • feat: add `surfnet_streamAccounts` RPC method (#598) (by @KABBOUCHI)

    Multiple remote accounts can now be registered for streaming in a single
    request. This complements surfnet_streamAccount and helps tests seed a set
    of account subscriptions before exercising transaction behavior.

    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "surfnet_streamAccounts",
      "params": [[
        { "pubkey": "<account-a>", "includeOwnedAccounts": true },
        { "pubkey": "<account-b>" }
      ]]
    }

Jito sendBundle

  • feat(core): add Jito sendBundle RPC support (#480) (by @serejke)

    Surfpool now implements the Jito-compatible sendBundle RPC method for local
    bundle testing. This makes it possible to exercise bundle submission flows
    against Surfpool before pointing the same client code at a Jito block engine
    or bundle-aware infrastructure.

    sendBundle accepts up to five serialized transactions, matching Jito's
    bundle size limit. Surfpool processes them sequentially in the provided order
    and forces skip_preflight to match block-engine send behavior. On success,
    it returns a bundle ID calculated as the SHA-256 hash of the comma-separated
    transaction signatures.

    The first version is intentionally a local compatibility surface, not full
    block-engine semantics. If a later transaction in the bundle fails, Surfpool
    returns an error that includes the failing bundle index, but earlier
    successful transactions are not rolled back yet.

    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "sendBundle",
      "params": [["<base64-transaction-1>", "<base64-transaction-2>"], { "encoding": "base64" }]
    }

Additional Features

  • feat(cli): allow passing feature pubkey instead of name on startup (#577) (by @MicaiahReid)

    surfpool start --feature and surfpool start --disable-feature now accept
    base58 Agave feature-gate pubkeys. Named features still work for compatibility,
    but the CLI warns through help text that names are deprecated.

    surfpool start --feature <feature-pubkey> --disable-feature <feature-pubkey>
  • feat: enable register-tracing feature for litesvm by default (#574) (by @procdump)

    Default CLI builds now enable LiteSVM register tracing through Surfpool's
    register-tracing feature, making register-level execution data available to
    tracing-enabled builds without requiring a custom feature selection.

  • feat(cli): add --artifacts-path flag to override .so program location (#573) (by @procdump)

    surfpool start can now deploy program binaries from a custom artifacts
    directory instead of assuming target/deploy. This is useful for alternate
    build directories, debug artifacts, or CI layouts.

    surfpool start --artifacts-path ./target/deploy/debug
  • feat: add prometheus metrics (#586) (by @dvansari65)

    Builds compiled with the prometheus feature can expose a /metrics endpoint
    with counters and histograms for transaction outcomes, transaction latency, RPC
    requests, RPC latency, and remote account fetch latency.

cargo run -p surfpool-cli --features prometheus -- start --metrics-enabled --metrics-addr 127.0.0.1:9000
curl http://127.0.0.1:9000/metrics
  • feat: add option to skip blockhash validation for transactions (#632) (by @MicaiahReid)

    Surfpool can now disable transaction blockhash validation for workflows that
    intentionally replay old transactions or bypass recent-blockhash freshness.
    The CLI exposes --skip-blockhash-check, and the Rust SDK exposes
    SurfnetBuilder::skip_blockhash_check(true).

    surfpool start --skip-blockhash-check

Bug Fixes

  • fix: use the config arg in the get_balance rpc method (#547) (by @0xzrf)

    getBalance now respects the request configuration instead of ignoring it, so
    callers get results according to the commitment/config they passed.

  • fix: reject duplicate accounts between static keys and ALT (#564) (by @lgalabru)

    Transactions that duplicate an account between static keys and address lookup
    table entries are now rejected, matching Solana transaction validation more
    closely.

  • fix(rpc): respect commitment visibility in getBlock (#572) (by @sergeytimoshin)

    getBlock now filters block visibility by commitment, avoiding responses that
    expose blocks above the requested commitment level.

  • fix: increase max_request_body_size to 15 MiB (#583) (by @arihantbansal)

    The RPC server now accepts larger request bodies, which prevents large
    transactions, programs, or cheatcode payloads from being rejected too early.

  • Set epoch schedule sysvar to match mainnet in surfnet init (#435) (by @rajgoesout)

    Surfnet initialization now seeds the epoch schedule sysvar with mainnet-shaped
    values, improving compatibility for programs that inspect epoch schedule data.

  • fix(core): make 'until' param exclusive for `getSignaturesForAddress` (#582) (by @MicaiahReid)

    getSignaturesForAddress now treats the until signature as an exclusive
    boundary, matching expected Solana RPC pagination semantics.

  • fix: improve write_program logic; add tests (#579) (by @MicaiahReid)

    Program writes now use improved chunking and registration logic, with tests
    covering small-program handling and program deployment behavior.

  • fix: add back in admin plugin methods (2d49771) (by @MicaiahReid)

    Admin plugin RPC methods were restored and covered with plugin tests after
    being dropped during prior cleanup.

  • fix: enable register-tracing feature by default in cli (#606) (by @procdump)

    The CLI feature wiring now actually enables register tracing by default,
    aligning the binary configuration with the intended LiteSVM tracing behavior.

  • fix(core): add back litesvm precompiles (#607) (by @MicaiahReid)

    LiteSVM precompiles are restored during Surfnet setup, fixing transactions
    that depend on built-in precompile program behavior.

  • fix(surfnet): terminates the cli program on sigterm (#589) (by @0xzrf)

    The TUI now handles SIGTERM so Surfpool exits cleanly when managed by process
    supervisors or container runtimes.

  • fix: enable clap help and usage features (ffc1a64) (by @MicaiahReid)

    Clap's help and usage features are enabled again, restoring generated command
    help output.

  • fix: dont reenable ix profiling if not initially configured (701f5a8) (by @MicaiahReid)

    Instruction profiling now stays disabled when it was not configured at
    startup, avoiding unexpected profiling state changes later in the runloop.

  • fix: broken build; remove unused imports (3b2881e) (by @MicaiahReid)

    The build was fixed by removing stale imports and adjusting affected CLI/type
    references.

  • fix(core): handle divide by zero in time travel calculations (#614) (by @MicaiahReid)

    Time travel now guards against zero-valued slot-time calculations instead of
    panicking or producing invalid epoch math.

  • fix(core): shutdown rpc servers on terminate command (#612) (by @MicaiahReid)

    Termination now shuts down RPC servers as part of Surfnet shutdown, avoiding
    lingering server threads and occupied ports.

  • fix(docker): set default network host to 127.0.0.1 (#622) (by @MicaiahReid)

    Docker startup now defaults Surfpool's network host to localhost, matching the
    expected local binding behavior.

  • fix: add serde feature to solana-account and solana-transaction-context dependencies (710798c) (by @MicaiahReid)

    Serialization support is enabled for Solana account and transaction-context
    types that need serde in Surfpool's type layer.

Maintenance, Tests, CI, and Release

New Contributors

Full Changelog: v1.1.1...v1.2.0

Don't miss a new surfpool release

NewReleases is sending notifications on new releases.