Time Travel

The Surfpool runtime exposes three time-travel helpers. Each one moves the local clock to an absolute target (not a relative offset) and returns the updated EpochInfo so tests can assert that the runtime actually reached the requested time.

HelperTargetReturns
time_travel_to_slot / timeTravelToSlotAbsolute slot numberEpochInfo with the new absolute_slot
time_travel_to_epoch / timeTravelToEpochAbsolute epoch numberEpochInfo with the new epoch
time_travel_to_timestamp / timeTravelToTimestampUnix timestamp in millisecondsEpochInfo reflecting the implied slot

Forward only

Time travel can only move the clock forward. Calling these methods with a target in the past is a no-op — the helper returns the current EpochInfo unchanged.

Jump To A Slot

use surfpool_sdk::Surfnet;
let surfnet = Surfnet::start().await?;
let cheats = surfnet.cheatcodes();
let info = cheats.time_travel_to_slot(1_000_000)?;
assert!(info.absolute_slot >= 1_000_000);

Jump To An Epoch

let info = cheats.time_travel_to_epoch(420)?;
assert_eq!(info.epoch, 420);

Jump To A Unix Timestamp

Pass the timestamp in milliseconds, not seconds. The runtime computes the closest slot at that timestamp.

// 2030-01-01T00:00:00Z
let info = cheats.time_travel_to_timestamp(1_893_456_000_000)?;

Common Patterns

Test A Lockup Or Vesting Window

The example below is a sketch — assertWithdrawFails and assertWithdrawSucceeds are placeholders for whatever client-side helpers your test suite uses to assert on RPC behavior.

import { Surfnet } from "@solana/surfpool";
const surfnet = Surfnet.start();
const beneficiary = Surfnet.newKeypair();
// 1. Set up a vesting account that unlocks at slot 1,000,000.
surfnet.setAccount(/* ...lockup program state... */);
// 2. Verify withdrawal fails before unlock.
await assertWithdrawFails(surfnet.rpcUrl, beneficiary);
// 3. Travel past the unlock slot.
surfnet.timeTravelToSlot(1_000_001);
// 4. Verify withdrawal succeeds.
await assertWithdrawSucceeds(surfnet.rpcUrl, beneficiary);
surfnet.stop();

Drive A Multi-Epoch Scenario

Intermediate slots are skipped

Time travel jumps directly to the target — moving from epoch 1 to epoch 5 skips the slots in between. If your program needs each intermediate epoch boundary to fire (for example, to credit per-epoch rewards), call time_travel_to_epoch once per epoch with any required transactions in between.

for epoch in 2..=5 {
cheats.time_travel_to_epoch(epoch)?;
surfnet.rpc_client().send_transaction(&claim_rewards_tx)?;
}

EpochInfo Shape

Both SDKs return an EpochInfo-shaped object with these fields:

FieldRustJS
Absolute slotabsolute_slot: u64absoluteSlot: number
Slot within epochslot_index: u64slotIndex: number
Slots per epochslots_in_epoch: u64slotsInEpoch: number
Epoch numberepoch: u64epoch: number
Block heightblock_height: u64blockHeight: number
Transaction counttransaction_count: Option<u64>transactionCount?: number

Is this page helpful?

Table of Contents

Edit Page
© 2026 Solana Foundation. All rights reserved.