Transactions
Build and compile transaction messages
Introduction
To take an action on Solana, whether to place an order, transfer an asset, or more generally to modify data on the blockchain, you need to prepare a transaction and sign to pay for it to be executed on the network. You can use Kit to create, sign, encode, and decode transactions.
Installation
Transaction utilities are included within the @solana/kit
library but you may also install them using their standalone packages.
To install transaction message builder utilities:
To install utilities that let you sign and compile transaction messages into transactions that can be landed on the network:
What is a Transaction?
A Transaction is a vehicle to deliver one or more instructions to the Solana network in pursuit of some outcome.
Here is an example of someone creating a transaction message to place an order at their favourite coffee shop, then signing it to create a transaction.
For more detail see the ‘Build a transaction’ and ‘Send a transaction’ sections of the Getting Started guide.
Building transaction messages
Creating an empty message
Given a TransactionVersion
, the createTransactionMessage
method will return an empty transaction having the capabilities of that version.
Setting the fee payer
The TransactionMessageWithFeePayer
type represents a transaction message for which a fee payer has been declared. A transaction must conform to this type to be compiled and landed on the network.
Using a signer
Given a TransactionSigner
, this method will return a new transaction message having the same type as the one supplied plus the TransactionMessageWithFeePayer
type. Additionally, the resulting message will have the capability to self-sign using the signTransactionMessageWithSigners
function.
Using an address
Given a base58-encoded address of a system account, this method will return a new transaction message having the same type as the one supplied plus the TransactionMessageWithFeePayer
type.
Defining the lifetime
A signed transaction can be only be landed on the network if certain conditions are met:
- It includes the hash of a recent block
- Or it includes the value of an unused nonce known to the network
These conditions define a transaction's lifetime, after which it can no longer be landed, even if signed. The lifetime must be added to the transaction message before it is compiled to be sent.
Using a recent blockhash
The TransactionMessageWithBlockhashLifetime
type represents a transaction message whose expiry is tied to the age of a block. Such a transaction can only be landed on the network if the current block height of the network is less than or equal to the value of TransactionMessageWithBlockhashLifetime.lifetimeConstraint.lastValidBlockHeight
.
Given a blockhash and the last block height at which that blockhash is considered usable to land transactions, the setTransactionMessageLifetimeUsingBlockhash
method will return a new transaction message having the same type as the one supplied plus the TransactionMessageWithBlockhashLifetime
type.
Using a durable nonce
The TransactionMessageWithDurableNonceLifetime
type represents a transaction message whose lifetime is defined by the value of a nonce account on-chain. Such a transaction can only be landed on the network if the nonce value in the transaction message matches the one in the nonce account at the time the transaction executes.
Given a nonce, the account where the value of the nonce is stored, and the address of the account authorized to consume that nonce, this method will return a new transaction having the same type as the one supplied plus the TransactionMessageWithDurableNonceLifetime
type.
In particular, this method prepends an instruction to the transaction message designed to consume (or ‘advance’) the nonce in the same transaction whose lifetime is defined by it.
Adding instructions
There are three types that correspond to the different parts of an instruction. Any given instruction may conform to one or more of these types, but must conform in every case to the Instruction
type.
Instruction
: An instruction having aprogramAddress
property that is the base58-encoded address of the program to invoke.InstructionWithAccounts
: An instruction that specifies a list of accounts that a program may read from, write to, or require be signers of the transaction itself. Objects that conform to this type have anaccounts
property that is an array ofAccountMeta | AccountLookupMeta
in the order the instruction requires.InstructionWithData
: An instruction that supplies some data as input to the program. Objects that conform to this type have adata
property that can be any type ofUint8Array
.
Given an instruction, the appendTransactionMessageInstruction
method will return a new transaction message with that instruction having been added to the end of the list of existing instructions.
To add an instruction to the beginning of the list instead, see prependTransactionMessageInstruction
To add an array of instructions to a transaction message, see appendTransactionMessageInstructions
and prependTransactionMessageInstructions
.
Compressing transaction messages
Every transaction message must include a reference to the account addresses it will read from and write to. One alternative to storing these addresses in the message itself is to store them in an on-chain account called an address lookup table. This lets you save space in the message by replacing many 32-byte account addresses with one or more 32-byte address lookup table account addresses then a 1-byte index into those tables for each address.
Addresses that are required signers of a transaction message can not be looked up in an address lookup table; they must be encoded in the message in the conventional way.
Given a transaction message and a mapping of lookup tables to the ordered addresses stored in them, the compressTransactionMessageUsingAddressLookupTables
function will return a new transaction message with the same instructions but with all non-signer accounts that are found in the given lookup tables represented by an AccountLookupMeta
instead of an AccountMeta
.
Consider how compressing transaction messages creates more space for instructions. This might enable you to prepare more complex transactions, or to execute the same number of instructions over fewer transactions thereby saving on network fees.
This technique can not be applied to transaction messages having the version 'legacy'
.
Signing transaction messages
In order to be executed a transaction message must be signed by all of the private keys belonging to accounts that are required signers of the transaction, and must not exceed the size allowable by the network. You may encounter these types when using functions that send transactions.
FullySignedTransaction
: A transaction that is signed by all of its required signers.TransactionWithinSizeLimit
: A transaction that is under or equal to the maximum size limit for transactions on the Solana network.SendableTransaction
: A union ofFullySignedTransaction
andTransactionWithinSizeLimit
Transaction messages whose signers are specified using TransactionSigner
objects have the ability to self-sign. This is because signers encapsulate both the address of the signing account as well as an implementation of the signing algorithm for the private key associated with that account.
The signTransactionMessageWithSigners
method will return a new signed transaction of type FullySignedTransaction
.
This function will throw if the transaction message does not carry a TransactionSigner
implementation for every required signer. To partially sign a message that you know to carry a
strict subset of the required TransactionSigners
, use the
partiallySignTransactionMessageWithSigners
method.
If exactly one of the TransactionSigners
in the message is a
TransactionSendingSigner
then you can use the
signAndSendTransactionMessageWithSigners
method to sign and send the transaction in a single step.
Building transaction messages using TransactionSigners
is the recommended way to create self-signable transaction messages. To sign with a CryptoKey
directly, you first have to compile the transaction message.
This produces an unsigned transaction. Follow the instructions for signing transactions with CryptoKeyPairs
to sign it.
Signing transactions
Wherever you have a Transaction
instead of a TransactionMessage
you can add or replace a signature using the signTransaction
method and one or more CryptoKeyPairs
.
This function will throw if the resultant transaction is missing a signature for one of the
transaction's required signers. To partially sign a transaction, use the
partiallySignTransaction
method.
Serializing transactions
If you would like to send a transaction to the network manually, you must first serialize it in a particular way. The Base64EncodedWireTransaction
represents the wire format of a transaction as a base64-encoded string.
Given a transaction, the getBase64EncodedWireTransaction
method returns the transaction as a string that conforms to the Base64EncodedWireTransaction
type.
Typically you would not serialize and send transactions to the network manually. See how to use send-and-confirm helpers to make sending transactions easier and more reliable.
Deserializing transactions
You can fetch the raw bytes of a transaction from the network using an RPC server.
Decoding the wire transaction bytes yields a Transaction
object. This takes the form of a transaction message encoded as a byte array, and a list of signatures of those bytes created by those who must authorize the message and pay for it to be executed on Solana.
Decoding the network wire format bytes of the message will yield a transaction message in its compiled form – a message in a form suitable for execution on the network. Encountering a compiled message in your application is rare, but it's important to know that they exist.
Finally, decompiling a compiled message will yield a TransactionMessage
object. This is the most common form of transaction message that you will encounter when using Kit to build an application.
You can not fully reconstruct a source message from a compiled message without extra
information. In particular, supporting details about the lifetime constraint and the concrete
addresses of accounts sourced from account lookup tables are lost to compilation, but can be
supplied in the config
argument of the decompileTransactionMessage
or
decompileTransactionMessageFetchingLookupTables
methods.