# Tutorial

# 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 addProgramFromFile method.

Here's an example using a simple program (opens new window) from the Solana Program Library that just does some logging:

import { LiteSVM, TransactionMetadata } from "litesvm";
import {
	Keypair,
	LAMPORTS_PER_SOL,
	PublicKey,
	Transaction,
	TransactionInstruction,
} from "@solana/web3.js";

test("spl logging", () => {
	const programId = PublicKey.unique();
	const svm = new LiteSVM();
	svm.addProgramFromFile(programId, "program_bytes/spl_example_logging.so");
	const payer = new Keypair();
	svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
	const blockhash = svm.latestBlockhash();
	const ixs = [
		new TransactionInstruction({
			programId,
			keys: [
				{ pubkey: PublicKey.unique(), isSigner: false, isWritable: false },
			],
		}),
	];
	const tx = new Transaction();
	tx.recentBlockhash = blockhash;
	tx.add(...ixs);
	tx.sign(payer);
	// let's sim it first
	const simRes = svm.simulateTransaction(tx);
	const sendRes = svm.sendTransaction(tx);
	if (sendRes instanceof TransactionMetadata) {
		expect(simRes.meta().logs()).toEqual(sendRes.logs());
		expect(sendRes.logs()[1]).toBe("Program log: static string");
	} else {
		throw new Error("Unexpected tx failure");
	}
});

# 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.setClock(). Here's an example using a program that panics if clock.unix_timestamp is greater than 100 (which is on January 1st 1970):

import {
	Clock,
	FailedTransactionMetadata,
	LiteSVM,
	TransactionMetadata,
} from "litesvm";
import {
	Keypair,
	LAMPORTS_PER_SOL,
	PublicKey,
	Transaction,
	TransactionInstruction,
} from "@solana/web3.js";

test("clock", () => {
	const programId = PublicKey.unique();
	const svm = new LiteSVM();
	svm.addProgramFromFile(programId, "program_bytes/litesvm_clock_example.so");
	const payer = new Keypair();
	svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL));
	const blockhash = svm.latestBlockhash();
	const ixs = [
		new TransactionInstruction({ keys: [], programId, data: Buffer.from("") }),
	];
	const tx = new Transaction();
	tx.recentBlockhash = blockhash;
	tx.add(...ixs);
	tx.sign(payer);
	// set the time to January 1st 2000
	const initialClock = svm.getClock();
	initialClock.unixTimestamp = 1735689600n;
	svm.setClock(initialClock);
	// this will fail because the contract wants it to be January 1970
	const failed = svm.sendTransaction(tx);
	if (failed instanceof FailedTransactionMetadata) {
		expect(failed.err().toString()).toContain("ProgramFailedToComplete");
	} else {
		throw new Error("Expected transaction failure here");
	}
	// so let's turn back time
	const newClock = svm.getClock();
	newClock.unixTimestamp = 50n;
	svm.setClock(newClock);
	const ixs2 = [
		new TransactionInstruction({
			keys: [],
			programId,
			data: Buffer.from("foobar"), // unused, just here to dedup the tx
		}),
	];
	const tx2 = new Transaction();
	tx2.recentBlockhash = blockhash;
	tx2.add(...ixs2);
	tx2.sign(payer);
	// now the transaction goes through
	const success = svm.sendTransaction(tx2);
	expect(success).toBeInstanceOf(TransactionMetadata);
});

See also: svm.warpToSlot(), 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:

import { LiteSVM } from "litesvm";
import { PublicKey } from "@solana/web3.js";
import {
	getAssociatedTokenAddressSync,
	AccountLayout,
	ACCOUNT_SIZE,
	TOKEN_PROGRAM_ID,
} from "@solana/spl-token";

test("infinite usdc mint", () => {
	const owner = PublicKey.unique();
	const usdcMint = new PublicKey(
		"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
	);
	const ata = getAssociatedTokenAddressSync(usdcMint, owner, true);
	const usdcToOwn = 1_000_000_000_000n;
	const tokenAccData = Buffer.alloc(ACCOUNT_SIZE);
	AccountLayout.encode(
		{
			mint: usdcMint,
			owner,
			amount: usdcToOwn,
			delegateOption: 0,
			delegate: PublicKey.default,
			delegatedAmount: 0n,
			state: 1,
			isNativeOption: 0,
			isNative: 0n,
			closeAuthorityOption: 0,
			closeAuthority: PublicKey.default,
		},
		tokenAccData,
	);
	const svm = new LiteSVM();
	svm.setAccount(ata, {
		lamports: 1_000_000_000,
		data: tokenAccData,
		owner: TOKEN_PROGRAM_ID,
		executable: false,
	});
	const rawAccount = svm.getAccount(ata);
	expect(rawAccount).not.toBeNull();
	const rawAccountData = rawAccount?.data;
	const decoded = AccountLayout.decode(rawAccountData);
	expect(decoded.amount).toBe(usdcToOwn);
});

# 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.

Or, if you want to pull live data every time you test, you can do this with a few lines of code. Here's a simple example that pulls account data from devnet and passes it to LiteSVM:

import { LiteSVM } from "litesvm";
import { PublicKey, Connection } from "@solana/web3.js";

test("copy accounts from devnet", async () => {
	const owner = PublicKey.unique();
	const usdcMint = new PublicKey(
		"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
	);
	const connection = new Connection("https://api.devnet.solana.com");
	const accountInfo = await connection.getAccountInfo(usdcMint);
	// the rent epoch goes above 2**53 which breaks web3.js, so just set it to 0;
	accountInfo.rentEpoch = 0;
	const svm = new LiteSVM();
	svm.setAccount(usdcMint, accountInfo);
	const rawAccount = svm.getAccount(usdcMint);
	expect(rawAccount).not.toBeNull();
});

# Other features

Other things you can do with litesvm include:

  • Changing the max compute units and other compute budget behaviour using the withComputeBudget method.
  • Disable transaction signature checking using svm.withSigverify(false).
  • Find previous transactions using the getTransaction 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 I would recommend using 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.