Seashell is a lightweight, deterministic testing framework for Solana programs that enables reproducible testing against real mainnet data.
Seashell provides a minimal SVM (Solana Virtual Machine) runtime that allows you to:
- Execute Solana programs in a controlled environment
- Test against real mainnet account state
- Create reproducible test scenarios
- Cache and replay mainnet data for deterministic testing
The heart of Seashell is the Scenario system, which enables deterministic testing with real mainnet data. Here's how it works:
- Account Fetching: When you configure an RPC URL, Seashell will automatically fetch any missing accounts from mainnet
- Automatic Persistence: Fetched accounts are automatically saved to a compressed JSON file (
scenarios/*.json.gz) - Deterministic Replay: On subsequent test runs, accounts are loaded from the scenario file instead of RPC, ensuring tests are fast and deterministic
- Version Control: Scenario files can be committed to git, allowing your entire team to test against the same mainnet state
When you load a scenario, Seashell follows this account lookup order:
- Check scenario overrides (accounts previously fetched from RPC)
- Check manually set accounts
- If not found and RPC is configured, fetch from mainnet and save to scenario
- If RPC is not configured, panic with a helpful message
This design ensures that:
- You explicitly opt-in to RPC fetching by setting the
RPC_URLenvironment variable - Fetched accounts are automatically cached for future runs
- Tests remain deterministic once accounts are cached
- You can't accidentally fetch accounts without a scenario loaded
use seashell::{Config, Seashell};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::instruction::{AccountMeta, Instruction};
#[test]
fn test_transfer_against_mainnet() {
// Set RPC URL to enable account fetching (only needed on first run)
std::env::set_var("RPC_URL", "https://api.mainnet-beta.solana.com");
// Create a new Seashell instance
let mut seashell = Seashell::new();
// Load a scenario (creates scenarios/my_test.json.gz if it doesn't exist)
seashell.load_scenario("my_test");
// These accounts will be fetched from mainnet on first run,
// then loaded from the scenario file on subsequent runs
let alice = Pubkey::from_str("ALiCE...").unwrap();
let bob = Pubkey::from_str("BoB...").unwrap();
// Check initial balances (fetches from RPC if needed)
let alice_balance = seashell.account(&alice).lamports();
let bob_balance = seashell.account(&bob).lamports();
// Create and execute a transfer instruction
let transfer_ix = system_instruction::transfer(&alice, &bob, 1_000_000);
let result = seashell.process_instruction(transfer_ix);
// Verify the transfer succeeded
assert!(result.error.is_none());
// Check final balances
assert_eq!(seashell.account(&alice).lamports(), alice_balance - 1_000_000);
assert_eq!(seashell.account(&bob).lamports(), bob_balance + 1_000_000);
}
// On subsequent test runs, you can remove the RPC_URL since accounts are cached:
#[test]
fn test_cached_scenario() {
// No RPC_URL needed - accounts will be loaded from scenarios/my_test.json.gz
let mut seashell = Seashell::new();
seashell.load_scenario("my_test");
// Same test code works with cached data
let alice = Pubkey::from_str("ALiCE...").unwrap();
assert!(seashell.account(&alice).lamports() > 0);
}- First Run: Set
RPC_URLenvironment variable to fetch accounts from mainnet - Subsequent Runs: Remove
RPC_URLto use cached scenario data - Version Control: Commit your
scenarios/*.json.gzfiles to ensure reproducible tests across your team - Scenario Names: Use descriptive scenario names that indicate what they're testing
- Account Management: Let Seashell manage account fetching - don't manually set accounts that should come from mainnet
RPC_URL: Solana RPC endpoint URL (e.g.,https://api.mainnet-beta.solana.com). When set, enables automatic account fetching for missing accounts.