Send a versioned transaction
Solana versioned transactions (v0) support Address Lookup Tables, which let you reference up to 256 addresses in a single transaction — useful for complex operations that would exceed the limits of legacy transactions.
This guide shows you how to create, sign, and send versioned transactions through MetaMask.
Create a versioned transaction
Solana versioned transactions are created in a similar way to legacy transactions.
The only difference is to use the VersionedTransaction class instead of the Transaction class.
The following example shows how to create a simple transfer instruction, format the instruction into a v0-compatible transaction message, and create a versioned transaction that parses the message:
// Create an array of instructions.
// This example uses a simple transfer instruction.
const instructions = [
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: publicKey,
lamports: 10,
}),
];
// Create a v0-compatible message.
const messageV0 = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: blockhash,
instructions,
}).compileToV0Message();
// Create a versioned transaction.
const transactionV0 = new VersionedTransaction(messageV0);
Sign and send a versioned transaction
After creating an unsigned versioned transaction, ask the user's MetaMask wallet to sign and send the transaction.
Use the signAndSendTransaction method on the provider, which accepts an optional SendOptions object as a second parameter.
The method returns a promise for an object containing the signature.
const provider = getProvider(); // TO DO: replace with provider snippet
const network = '<NETWORK_URL>';
const connection = new Connection(network);
const { signature } = await provider.signAndSendTransaction(transactionV0);
await connection.getSignatureStatus(signature);
Create an Address Lookup Table
Create an Address Lookup Table (ALT) to efficiently load addresses into tables, significantly increasing the number of addresses that can be used in a single transaction.
Use the createLookupTable method to create the instruction needed to create a new ALT and determine its address. With this instruction, create a transaction, sign it, and send it to create an ALT onchain. For example:
// Create an Address Lookup Table.
const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({
authority: publicKey,
payer: publicKey,
recentSlot: slot,
});
// To create the ALT onchain, send the lookupTableInst instruction in a transaction.
const lookupMessage = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: blockhash,
instructions: [lookupTableInst],
}).compileToV0Message();
const lookupTransaction = new VersionedTransaction(lookupMessage);
const lookupSignature = await signAndSendTransaction(provider, lookupTransaction);
Extend an Address Lookup Table
After creating an ALT, extend it by appending addresses to the table. Use the extendLookupTable method to create a new extend instruction, and send it in a transaction. For example:
// Add addresses to the lookupTableAddress table via an extend instruction.
const extendInstruction = AddressLookupTableProgram.extendLookupTable({
payer: publicKey,
authority: publicKey,
lookupTable: lookupTableAddress,
addresses: [
publicKey,
SystemProgram.programId,
// More publicKey addresses can be listed here.
],
});
// Send this extendInstruction in a transaction to the cluster to insert
// the listing of addresses into your ALT with address lookupTableAddress.
const extensionMessageV0 = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: blockhash,
instructions: [extendInstruction],
}).compileToV0Message();
const extensionTransactionV0 = new VersionedTransaction(extensionMessageV0);
const extensionSignature = await signAndSendTransaction(provider, extensionTransactionV0);
Create, sign, and send a versioned transaction with an ALT
After creating an ALT, create a versioned transaction with the ALT and ask the user's MetaMask wallet to sign and send it.
First, use the getAddressLookupTable method to fetch the account of the created ALT:
// Get the table from the cluster.
const lookupTableAccount = await connection.getAddressLookupTable(lookupTableAddress).then((res) => res.value);
// lookupTableAccount will now be an AddressLookupTableAccount object.
console.log('Table address from cluster:', lookupTableAccount.key.toBase58());
Then, parse and read all the addresses currently stored in the fetched ALT:
// Loop through and parse all the address stored in the table.
for (let i = 0; i < lookupTableAccount.state.addresses.length; i++) {
const address = lookupTableAccount.state.addresses[i];
console.log(i, address.toBase58());
}
The following example creates a simple transfer instruction, formats the instruction into a v0-compatible transaction message using the ALT's account, and creates a versioned transaction that parses the message.
Sign and send the transaction using the signAndSendTransaction method on the provider.
// Create an array of instructions.
// This example uses a simple transfer instruction.
const instructions = [
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: publicKey,
lamports: minRent,
}),
];
// Create a v0-compatible message.
const messageV0 = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: blockhash,
instructions,
}).compileToV0Message([lookupTableAccount]);
// Create a versioned transaction.
const transactionV0 = new VersionedTransaction(messageV0);
const signature = await signAndSendTransaction(provider, transactionV0);