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. This is 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://solana-devnet.infura.io/v3/YOUR_INFURA_API_KEY')

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)

Sign without sending

Use solana:signTransaction when you need a signed versioned transaction without broadcasting it:

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

const txSignature = await connection.sendRawTransaction(signedTransaction)

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',
})

Next steps