Solana Transactions

SvmTransaction and SvmTxBuildParams — building from recipient/amount/instructions, supplying recentBlockhash for a fully offline build, and passing @solana/web3.js instructions.

SvmTransaction is the concrete Solana transaction. As on EVM, the common path is address.buildTransaction(params), which binds the address so the transaction can sign, send, and simulate. The underlying @solana/web3.js Transaction is built lazily — on the first sign(), simulate(), or toRawTx() — so constructing one is always offline.

Constructing an SvmTransaction

Both an object form and a positional form (from, recipient, amount, opts?) are supported.

construct-svm.ts
import { SvmTransaction } from "wative-core";

const tx = new SvmTransaction({
  from: "SenderPubkey…",
  recipient: "RecipientPubkey…",
  amount: 5000n, // lamports (raw base units)
});

Built from an address, a native SOL transfer is just recipient + amount:

build-from-address.ts
const svm = acc.wallets[0].svm; // an SvmSigner
const tx = svm.buildTransaction({
  recipient: "RecipientPubkey…",
  amount: 5000n,
});

SvmTxBuildParams

FieldTypeNotes
recipientstringbase58 destination for the default native transfer
amountbigintlamports; coerced, minimum 0
tokenMintstringunimplemented convenience param — throws; route SPL through instructions (see Token Transfers)
tokenProgramstringtoken program id for SPL flows
computeUnitLimitnumberemits a ComputeBudget setComputeUnitLimit instruction
computeUnitPricebigintpriority fee (micro-lamports); auto-defaulted when omitted and an address is bound
recentBlockhashstringsupply for a fully offline build (see below)
feePayerstringdefaults to from; see fee-payer rules
rpcUrlstringper-transaction endpoint override; validated
instructionsReadonlyArray<unknown>arbitrary instructions; overrides the default native transfer
addressLookupTablesReadonlyArray<string>see the lookup-table note below
memostringappended as an SPL Memo instruction

instructions

When you pass a non-empty instructions array, it replaces the default native SOL transfer — the builder adds your instructions instead. Two shapes are accepted:

  1. Real @solana/web3.js TransactionInstruction instances (e.g. from SystemProgram.transfer(...)), passed through unchanged.
  2. The library's plain instruction shape — an object with programId (base58 string), accounts ([{ pubkey, isSigner, isWritable }]), and data (Uint8Array) — which the builder converts to a real TransactionInstruction.
custom-instructions.ts
import { SystemProgram, PublicKey } from "@solana/web3.js";

const ix = SystemProgram.transfer({
  fromPubkey: new PublicKey(sender),
  toPubkey: new PublicKey(recipient),
  lamports: 5000n,
});

const tx = svm.buildTransaction({ recipient, amount: 0n, instructions: [ix] });

Any computeUnitLimit / computeUnitPrice ComputeBudget instructions are prepended, and a memo, if set, is appended — around whatever instructions you supply.

Fully offline build with recentBlockhash

Without a recentBlockhash, the transaction fetches one over RPC through its bound address the first time it is built (at sign / simulate / toRawTx). Supply one yourself and the build is purely structural — no address and no network call are needed, which is exactly what makes an offline toRawTx() handoff possible.

offline-svm.ts
const tx = new SvmTransaction({
  from: "SenderPubkey…",
  recipient: "RecipientPubkey…",
  amount: 5000n,
  recentBlockhash: "9xQe…recentBlockhash",
});

const built = await tx.toRawTx(); // @solana/web3.js Transaction, no RPC

Fee-payer rules

feePayer defaults to from. It accepts a base58 string, a PublicKey, or raw key bytes, and is resolved to canonical base58 at construction; null is treated the same as omitting it (use from).

sign() can only produce the from account's signature. A feePayer distinct from from is therefore refused as a precondition, before an unsignable transaction is built:

// feePayer !== from
await tx.sign();
// PARAMETER_ERROR: SvmTransaction.sign cannot sign a distinct feePayer; it must
// equal from or be undefined. For an external fee payer, use toRawTx() and sign
// it yourself.

For a sponsored / relayer flow where someone else pays the fee, use toRawTx() to get the underlying @solana/web3.js Transaction and have the external fee payer sign it.

Memo and lookup tables

  • memo — a non-empty memo is appended as an SPL Memo-program instruction, so the note actually lands on-chain rather than being dropped.
  • addressLookupTables — Address Lookup Tables require a versioned (v0) transaction, and this builder produces legacy transactions only. A non-empty addressLookupTables is rejected loudly with UNSUPPORTED_OP rather than being silently ignored.

Last updated on