Solana Kit models transaction construction as a sequence of immutable transformations. This keeps the transaction pipeline easy to inspect and test.
Build a transaction message#
Transaction messages are assembled incrementally. The most common pattern is:
- Create an empty message.
- Set the fee payer.
- Set a lifetime constraint using a recent blockhash.
- Append one or more instructions.
import 'dart:typed_data';
import 'package:solana_kit/solana_kit.dart';
Future<void> main() async {
final rpc = createSolanaRpc(url: 'https://api.devnet.solana.com');
final feePayer = generateKeyPairSigner();
final latestBlockhash = await rpc.getLatestBlockhashValue().send();
final instruction = Instruction(
programAddress: const Address('11111111111111111111111111111111'),
accounts: [
AccountMeta(
address: feePayer.address,
role: AccountRole.writableSigner,
),
],
data: Uint8List(0),
);
final message = createTransactionMessage(version: TransactionVersion.v0)
.withFeePayer(feePayer.address)
.withBlockhashLifetime(
BlockhashLifetimeConstraint(
blockhash: latestBlockhash.value.blockhash.value,
lastValidBlockHeight: latestBlockhash.value.lastValidBlockHeight,
),
)
.appendInstruction(instruction);
print(message);
}
This separation keeps transaction construction explicit and makes it easier to reason about fee payment, expiry, and instruction ordering. If you prefer a more fluent style, the transaction-message extension methods build on the same underlying model.
Why the lifetime constraint matters#
Without a recent blockhash or a durable nonce, the network cannot determine whether a transaction is still valid. Solana Kit makes lifetime setup explicit so expiry handling is not hidden behind a large helper.
Blockhash-based lifetime#
Use BlockhashLifetimeConstraint for the common case.
Durable nonce lifetime#
Use setTransactionMessageLifetimeUsingDurableNonce(...) when the transaction must remain valid until a nonce account changes.
Compile the message#
Once the message is fully assembled, compile it into the wire-level message representation:
final compiledMessage = compileTransactionMessage(message);
At this stage you have a canonical message layout that can be signed and serialized.