Skip to main content

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.

Prerequisites

Set up a Solana client and connect to the user's wallet:

import { createSolanaClient } from '@metamask/connect-solana'
import {
Connection,
PublicKey,
SystemProgram,
TransactionMessage,
VersionedTransaction,
AddressLookupTableProgram,
} from '@solana/web3.js'

const solanaClient = await createSolanaClient({
dapp: {
name: 'My Solana DApp',
url: window.location.origin,
},
})

const wallet = solanaClient.getWallet()
const { accounts } = await wallet.features['standard:connect'].connect()
const account = accounts[0]
const publicKey = new PublicKey(account.address)
const connection = new Connection('https://api.devnet.solana.com')

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:

const { blockhash } = await connection.getLatestBlockhash()

const instructions = [
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: publicKey,
lamports: 10,
}),
]

const messageV0 = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: blockhash,
instructions,
}).compileToV0Message()

const transactionV0 = new VersionedTransaction(messageV0)

Sign and send a versioned transaction

After creating an unsigned versioned transaction, use the wallet's solana:signAndSendTransaction feature to ask the user's MetaMask wallet to sign and send it.

The method returns a promise for an object containing the signature.

const [{ signature }] = await wallet.features['solana:signAndSendTransaction'].signAndSendTransaction({
account,
transaction: transactionV0.serialize(),
chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
})

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:

const slot = await connection.getSlot()
const { blockhash } = await connection.getLatestBlockhash()

const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({
authority: publicKey,
payer: publicKey,
recentSlot: slot,
})

const lookupMessage = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: blockhash,
instructions: [lookupTableInst],
}).compileToV0Message()

const lookupTransaction = new VersionedTransaction(lookupMessage)

const [{ signature: lookupSignature }] = await wallet.features['solana:signAndSendTransaction'].signAndSendTransaction({
account,
transaction: lookupTransaction.serialize(),
chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
})

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:

const extendInstruction = AddressLookupTableProgram.extendLookupTable({
payer: publicKey,
authority: publicKey,
lookupTable: lookupTableAddress,
addresses: [
publicKey,
SystemProgram.programId,
],
})

const { blockhash: extensionBlockhash } = await connection.getLatestBlockhash()

const extensionMessageV0 = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: extensionBlockhash,
instructions: [extendInstruction],
}).compileToV0Message()

const extensionTransactionV0 = new VersionedTransaction(extensionMessageV0)

const [{ signature: extensionSignature }] = await wallet.features['solana:signAndSendTransaction'].signAndSendTransaction({
account,
transaction: extensionTransactionV0.serialize(),
chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
})

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:

const lookupTableAccount = await connection.getAddressLookupTable(lookupTableAddress).then((res) => res.value)
console.log('Table address from cluster:', lookupTableAccount.key.toBase58())

Then, parse and read all the addresses currently stored in the fetched ALT:

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 wallet's solana:signAndSendTransaction feature.

const { blockhash: altBlockhash } = await connection.getLatestBlockhash()

const instructions = [
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: publicKey,
lamports: minRent,
}),
]

const messageV0 = new TransactionMessage({
payerKey: publicKey,
recentBlockhash: altBlockhash,
instructions,
}).compileToV0Message([lookupTableAccount])

const transactionV0 = new VersionedTransaction(messageV0)

const [{ signature }] = await wallet.features['solana:signAndSendTransaction'].signAndSendTransaction({
account,
transaction: transactionV0.serialize(),
chain: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
})