LiteSVM

solders.litesvm is the Python wrapper for LiteSVM. It brings best-in-class Solana testing to Python, giving you a powerful, fast and ergonomic way to test Solana programs.

For a standard testing workflow, LiteSVM offers an experience superior to solana-test-validator (slow, unwieldy) and the old solders.bankrun module (reasonably fast and powerful, but inherits a lot of warts from solana-program-test).

Minimal example

This example just transfers lamports from Alice to Bob without loading any programs of our own.

from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.system_program import transfer
from solders.transaction import VersionedTransaction


def test_transfer() -> None:
    receiver = Pubkey.new_unique()
    client = LiteSVM()
    payer = Keypair()
    client.airdrop(payer.pubkey(), 1_000_000_000)
    blockhash = client.latest_blockhash()
    transfer_lamports = 1_000_000
    ixs = [
        transfer(
            {
                "from_pubkey": payer.pubkey(),
                "to_pubkey": receiver,
                "lamports": transfer_lamports,
            }
        )
    ]
    msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash)
    tx = VersionedTransaction(msg, [payer])
    client.send_transaction(tx)
    balance_after = client.get_balance(receiver)
    assert balance_after == transfer_lamports

Note: by default the LiteSVM instance includes some core programs such as the System Program and SPL Token.

Deploying programs

Most of the time we want to do more than just mess around with token transfers - we want to test our own programs.

Tip

If you want to pull a Solana program from mainnet or devnet, use the solana program dump command from the Solana CLI.

To add a compiled program to our tests we can use the add_program_from_file method.

Here’s an example using a [simple program](https://github.com/solana-labs/solana-program-library/tree/bd216c8103cd8eb9f5f32e742973e7afb52f3b81/examples/rust/logging) from the Solana Program Library that just does some logging:

from pathlib import Path

from solders.instruction import AccountMeta, Instruction
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.transaction_metadata import TransactionMetadata


def test_logging() -> None:
    program_id = Pubkey.from_string("Logging111111111111111111111111111111111111")
    ix = Instruction(
        program_id,
        bytes([5, 10, 11, 12, 13, 14]),
        [AccountMeta(Pubkey.new_unique(), is_signer=False, is_writable=True)],
    )
    client = LiteSVM()
    payer = Keypair()
    client.add_program_from_file(
        program_id, Path("tests/fixtures/spl_example_logging.so")
    )
    client.airdrop(payer.pubkey(), 1_000_000_000)
    blockhash = client.latest_blockhash()
    msg = Message.new_with_blockhash([ix], payer.pubkey(), blockhash)
    tx = VersionedTransaction(msg, [payer])
    # let's sim it first
    sim_res = client.simulate_transaction(tx)
    meta = client.send_transaction(tx)
    assert isinstance(meta, TransactionMetadata)
    assert sim_res.meta() == meta
    assert meta.logs()[1] == "Program log: static string"
    assert (
        meta.compute_units_consumed() < 10_000
    )  # not being precise here in case it changes

Time travel

Many programs rely on the Clock sysvar: for example, a mint that doesn’t become available until after a certain time. With litesvm you can dynamically overwrite the Clock sysvar using svm.set_clock(). Here’s an example using a program that panics if clock.unix_timestamp is greater than 100 (which is on January 1st 1970):

from pathlib import Path

from solders.instruction import Instruction
from solders.keypair import Keypair
from solders.litesvm import LiteSVM
from solders.message import Message
from solders.pubkey import Pubkey
from solders.transaction import VersionedTransaction
from solders.transaction_metadata import FailedTransactionMetadata, TransactionMetadata


def test_set_clock() -> None:
    program_id = Pubkey.new_unique()
    client = LiteSVM()
    client.add_program_from_file(
        program_id, Path("tests/fixtures/solders_clock_example.so")
    )
    payer = Keypair()
    client.airdrop(payer.pubkey(), 1_000_000_000)
    blockhash = client.latest_blockhash()
    ixs = [Instruction(program_id=program_id, data=b"", accounts=[])]
    msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash)
    tx = VersionedTransaction(msg, [payer])
    # set the time to January 1st 2000
    initial_clock = client.get_clock()
    initial_clock.unix_timestamp = 1735689600
    client.set_clock(initial_clock)
    # this will fail because it's not January 1970 anymore
    bad_res = client.send_transaction(tx)
    assert isinstance(bad_res, FailedTransactionMetadata)
    # so let's turn back time
    clock = client.get_clock()
    clock.unix_timestamp = 50
    client.set_clock(clock)
    ixs2 = [
        Instruction(
            program_id=program_id,
            data=b"foobar",  # unused, this is just to dedup the transaction
            accounts=[],
        )
    ]
    msg2 = Message.new_with_blockhash(ixs2, payer.pubkey(), blockhash)
    tx2 = VersionedTransaction(msg2, [payer])
    # now the transaction goes through
    good_res = client.send_transaction(tx2)
    assert isinstance(good_res, TransactionMetadata)

See also: svm.warp_to_slot(), which lets you jump to a future slot.

Writing arbitrary accounts

LiteSVM lets you write any account data you want, regardless of whether the account state would even be possible.

Here’s an example where we give an account a bunch of USDC, even though we don’t have the USDC mint keypair. This is convenient for testing because it means we don’t have to work with fake USDC in our tests:

from solders.account import Account
from solders.litesvm import LiteSVM
from solders.pubkey import Pubkey
from solders.token import ID as TOKEN_PROGRAM_ID
from solders.token.associated import get_associated_token_address
from solders.token.state import TokenAccount, TokenAccountState


def test_infinite_usdc_mint() -> None:
    owner = Pubkey.new_unique()
    usdc_mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
    ata = get_associated_token_address(owner, usdc_mint)
    usdc_to_own = 1_000_000_000_000
    token_acc = TokenAccount(
        mint=usdc_mint,
        owner=owner,
        amount=usdc_to_own,
        delegate=None,
        state=TokenAccountState.Initialized,
        is_native=None,
        delegated_amount=0,
        close_authority=None,
    )
    client = LiteSVM()
    client.set_account(
        ata,
        Account(
            lamports=1_000_000_000,
            data=bytes(token_acc),
            owner=TOKEN_PROGRAM_ID,
            executable=False,
        ),
    )
    raw_account = client.get_account(ata)
    assert raw_account is not None
    raw_account_data = raw_account.data
    assert TokenAccount.from_bytes(raw_account_data).amount == usdc_to_own

Copying Accounts from a live environment

If you want to copy accounts from mainnet or devnet, you can use the solana account command in the Solana CLI to save account data to a file.

Other features

Other things you can do with litesvm include:

  • Changing the max compute units and other compute budget behaviour using the with_compute_budget method.

  • Disable transaction signature checking using svm.with_sigverify(false).

  • Find previous transactions using the get_transaction method.

When should I use solana-test-validator?

While litesvm is faster and more convenient, it is also less like a real RPC node. So solana-test-validator is still useful when you need to call RPC methods that LiteSVM doesn’t support, or when you want to test something that depends on real-life validator behaviour rather than just testing your program and client code.

In general though it is recommended to use litesvm wherever possible, as it will make your life much easier.

Supported platforms

litesvm is supported on Linux x64 and MacOS targets. If you find a platform that is not supported but which can run the litesvm Rust crate, please open an issue.