Transactions

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.

MethodReturnsDescription
IsOk()boolTransaction succeeded
Error()stringFailure description (empty on success)
Signature()solana.SignatureTransaction signature
ComputeUnits()uint64Compute units consumed
Fee()uint64Fee charged
Logs()[]stringProgram logs
ReturnData()(solana.PublicKey, []byte, bool)Return data set via set_return_data; ok=false if none
InnerInstructions()[][]InnerInstructionCPIs grouped by top-level instruction
PostAccounts()([]PostAccount, error)Post-execution state - simulate only
Close()-Release the underlying Rust handle

InnerInstruction

type CompiledInstruction struct {
ProgramIDIndex uint8
Accounts []byte // indices into the transaction's account table
Data []byte
}
type InnerInstruction struct {
Instruction CompiledInstruction
StackHeight 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.PublicKey
Account *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 unknown
if 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]byte
copy(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?

Table of Contents

Edit Page
©️ 2026 Solana 基金会版权所有
Transactions | Solana