Methods for sending, simulating, and managing transactions. Both legacy and v0+ versioned transactions are supported.
litesvm-go accepts bincode-encoded transaction bytes produced by
(*solana.Transaction).MarshalBinary from
gagliardetto/solana-go.
SendLegacyTransaction
func (s *LiteSVM) SendLegacyTransaction(txBytes []byte) (*TxOutcome, error)
Submit a bincode-encoded legacy Transaction. On success the transaction
commits to the in-memory ledger.
out, err := svm.SendLegacyTransaction(txBytes)if err != nil {t.Fatal(err)}defer out.Close()if !out.IsOk() {t.Fatalf("tx failed: %s\nlogs: %v", out.Error(), out.Logs())}t.Logf("signature: %s", out.Signature())t.Logf("compute: %d CU", out.ComputeUnits())t.Logf("fee: %d lamports", out.Fee())
The returned error is non-nil only when the bytes could not be decoded or an
internal failure occurred. A transaction that executes and fails still
returns a non-nil *TxOutcome; always check IsOk() before consuming
success-only state.
SendVersionedTransaction
func (s *LiteSVM) SendVersionedTransaction(txBytes []byte) (*TxOutcome, error)
Same shape as SendLegacyTransaction, but for v0+ messages. solana-go's
MarshalBinary produces the right bytes for either case.
SimulateLegacyTransaction / SimulateVersionedTransaction
func (s *LiteSVM) SimulateLegacyTransaction(txBytes []byte) (*TxOutcome, error)func (s *LiteSVM) SimulateVersionedTransaction(txBytes []byte) (*TxOutcome, error)
Execute the transaction without committing state. The same *TxOutcome shape is
returned, plus PostAccounts() is populated on success with the would-be
post-execution account state.
sim, err := svm.SimulateLegacyTransaction(txBytes)if err != nil {t.Fatal(err)}defer sim.Close()if !sim.IsOk() {t.Fatalf("sim failed: %s", sim.Error())}t.Logf("would use %d compute units", sim.ComputeUnits())t.Logf("logs: %v", sim.Logs())posts, err := sim.PostAccounts()if err != nil {t.Fatal(err)}for _, p := range posts {t.Logf("%s: %d lamports", p.Address, p.Account.Lamports())p.Account.Close()}
Simulation does not change state. Use it to size compute budgets or assert on logs without polluting your ledger.
TxOutcome
Every send / simulate call returns a *TxOutcome carrying the same metadata
regardless of success or failure. Always Close it.
| Method | Returns | Description |
|---|---|---|
IsOk() | bool | Transaction succeeded |
Error() | string | Failure description (empty on success) |
Signature() | solana.Signature | Transaction signature |
ComputeUnits() | uint64 | Compute units consumed |
Fee() | uint64 | Fee charged |
Logs() | []string | Program logs |
ReturnData() | (solana.PublicKey, []byte, bool) | Return data set via set_return_data; ok=false if none |
InnerInstructions() | [][]InnerInstruction | CPIs grouped by top-level instruction |
PostAccounts() | ([]PostAccount, error) | Post-execution state - simulate only |
Close() | - | Release the underlying Rust handle |
InnerInstruction
type CompiledInstruction struct {ProgramIDIndex uint8Accounts []byte // indices into the transaction's account tableData []byte}type InnerInstruction struct {Instruction CompiledInstructionStackHeight uint8 // 1 for top-level, higher for deeper CPIs}
InnerInstructions() returns a slice indexed by the top-level instruction that
triggered each batch of CPIs.
PostAccount
type PostAccount struct {Address solana.PublicKeyAccount *Account // close this when done}
Populated only for successful simulations.
Blockhash Management
LatestBlockhash
func (s *LiteSVM) LatestBlockhash() (solana.Hash, error)
Read the current blockhash. Pass it into solana.NewTransaction(...) when
building transactions.
blockhash, err := svm.LatestBlockhash()if err != nil {t.Fatal(err)}tx, _ := solana.NewTransaction([]solana.Instruction{ix},blockhash,solana.TransactionPayer(payer),)
ExpireBlockhash
func (s *LiteSVM) ExpireBlockhash() error
Advance past the current blockhash so the next LatestBlockhash returns a new
value. Useful for testing blockhash-expiry edge cases.
bh1, err := svm.LatestBlockhash()if err != nil {t.Fatal(err)}if err := svm.ExpireBlockhash(); err != nil {t.Fatal(err)}bh2, err := svm.LatestBlockhash()if err != nil {t.Fatal(err)}// bh1 != bh2_, _ = bh1, bh2
Transaction History
Transaction history is on by default. Adjust the capacity (or disable dedup)
with SetTransactionHistory:
// Cap history at 100 entries.if err := svm.SetTransactionHistory(100); err != nil {t.Fatal(err)}// Disable dedup entirely (allows replaying identical transactions).if err := svm.SetTransactionHistory(0); err != nil {t.Fatal(err)}
Look up a prior transaction by signature:
prior := svm.GetTransaction(sig) // nil if unknownif prior != nil {defer prior.Close()t.Logf("found: %s, fee=%d", prior.Signature(), prior.Fee())}
BuildTransferTx (test helper)
func BuildTransferTx(payerSeed [32]byte,to solana.PublicKey,lamports uint64,blockhash solana.Hash,) ([]byte, error)
Produces a bincode-encoded, signed legacy Transaction transferring lamports
from the keypair derived from payerSeed to to, using blockhash. The
returned bytes are ready to hand to SendLegacyTransaction.
var seed [32]bytecopy(seed[:], somePayerSeedBytes)bh, err := svm.LatestBlockhash()if err != nil {t.Fatal(err)}txBytes, err := litesvm.BuildTransferTx(seed, recipient, 1_000_000_000, bh)if err != nil {t.Fatal(err)}out, err := svm.SendLegacyTransaction(txBytes)if err != nil {t.Fatal(err)}defer out.Close()
This exists to bootstrap tests and for callers who want to avoid pulling in
solana-go. Once you are building real transactions, prefer the solana-go
path shown above - it scales to multi-instruction transactions, v0 messages,
and arbitrary programs.
Is this page helpful?