Estrutura de transação

Resumo

Uma transação tem assinaturas + uma mensagem. A mensagem contém um cabeçalho, endereços de conta, blockhash recente e instruções compiladas. Tamanho máximo serializado: 1.232 bytes.

Uma Transaction tem dois campos de nível superior:

  • signatures: Um array de assinaturas
  • message: Informações da transação, incluindo a lista de instruções a serem processadas
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Diagrama mostrando as duas partes de uma transaçãoDiagrama mostrando as duas partes de uma transação

O tamanho total serializado de uma transação não deve exceder PACKET_DATA_SIZE (1.232 bytes). Este limite equivale a 1.280 bytes (o MTU mínimo IPv6) menos 48 bytes para cabeçalhos de rede (40 bytes IPv6 + 8 bytes de cabeçalho de fragmento). Os 1.232 bytes incluem tanto o array signatures quanto a struct message.

Diagrama mostrando o formato de transação e limites de tamanhoDiagrama mostrando o formato de transação e limites de tamanho

Assinaturas

O campo signatures é um array codificado de forma compacta de valores Signature. Cada Signature é uma assinatura Ed25519 de 64 bytes da Message serializada, assinada com a chave privada da conta do signatário. Uma assinatura é necessária para cada conta signatária referenciada pelas instruções da transação.

A primeira assinatura no array pertence ao pagador de taxa, a conta que paga a taxa base da transação e a taxa de priorização base fee and prioritization fee. Esta primeira assinatura também serve como o ID da transação, usado para procurar a transação na rede. O ID da transação é comumente referido como a assinatura da transação.

Requisitos do pagador de taxas:

  • Deve ser a primeira conta na mensagem (índice 0) e um signatário.
  • Deve ser uma conta pertencente ao System Program ou uma conta nonce (validada por validate_fee_payer).
  • Deve possuir lamports suficientes para cobrir rent_exempt_minimum + total_fee; caso contrário, a transação falha com InsufficientFundsForFee.

Mensagem

O campo message é uma struct Message que contém a carga útil da transação:

Message
pub struct Message {
/// The message header, identifying signed and read-only `account_keys`.
pub header: MessageHeader,
/// All the account keys used by this transaction.
#[serde(with = "short_vec")]
pub account_keys: Vec<Pubkey>,
/// The id of a recent ledger entry.
pub recent_blockhash: Hash,
/// Programs that will be executed in sequence and committed in
/// one atomic transaction if all succeed.
#[serde(with = "short_vec")]
pub instructions: Vec<CompiledInstruction>,
}

Cabeçalho

O campo header é uma struct MessageHeader com três campos u8 que particionam o array account_keys em grupos de permissões:

  • num_required_signatures: Número total de assinaturas exigidas pela transação.
  • num_readonly_signed_accounts: Número de contas assinadas que são somente leitura.
  • num_readonly_unsigned_accounts: Número de contas não assinadas que são somente leitura.
MessageHeader
pub struct MessageHeader {
/// The number of signatures required for this message to be considered
/// valid. The signers of those signatures must match the first
/// `num_required_signatures` of [`Message::account_keys`].
pub num_required_signatures: u8,
/// The last `num_readonly_signed_accounts` of the signed keys are read-only
/// accounts.
pub num_readonly_signed_accounts: u8,
/// The last `num_readonly_unsigned_accounts` of the unsigned keys are
/// read-only accounts.
pub num_readonly_unsigned_accounts: u8,
}

Diagrama mostrando as três partes do cabeçalho da mensagemDiagrama mostrando as três partes do cabeçalho da mensagem

Endereços de contas

O campo account_keys é um array compactado de chaves públicas. Cada entrada identifica uma conta usada por pelo menos uma das instruções da transação. O array deve incluir todas as contas e deve seguir esta ordenação estrita:

  1. Signatário + Gravável
  2. Signatário + Somente leitura
  3. Não signatário + Gravável
  4. Não signatário + Somente leitura

Esta ordenação estrita permite que o array account_keys seja combinado com as três contagens no header da mensagem para determinar as permissões de cada conta sem armazenar flags de metadados por conta. As contagens do cabeçalho particionam o array nos quatro grupos de permissões listados acima.

Diagrama mostrando a ordem do array de endereços de contasDiagrama mostrando a ordem do array de endereços de contas

Blockhash recente

O campo recent_blockhash é um hash de 32 bytes que serve dois propósitos:

  1. Timestamp: prova que a transação foi criada recentemente.
  2. Deduplicação: impede que a mesma transação seja processada duas vezes.

Um blockhash expira após 150 slots. Se o blockhash não for mais válido quando a transação chegar, ela é rejeitada com BlockhashNotFound, a menos que seja uma transação de nonce durável válida.

O método RPC getLatestBlockhash permite obter o blockhash atual e a última altura de bloco na qual o blockhash será válido.

Instruções

O campo instructions é um array compacto codificado de estruturas CompiledInstruction. Cada CompiledInstruction referencia contas por índice no array account_keys em vez de por chave pública completa. Contém:

  1. program_id_index: Índice no account_keys identificando o programa a invocar.
  2. accounts: Array de índices no account_keys especificando as contas a passar para o programa.
  3. data: Array de bytes contendo o discriminador de instrução e argumentos serializados.
CompiledInstruction
pub struct CompiledInstruction {
/// Index into the transaction keys array indicating the program account that executes this instruction.
pub program_id_index: u8,
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program.
#[serde(with = "short_vec")]
pub accounts: Vec<u8>,
/// The program input data.
#[serde(with = "short_vec")]
pub data: Vec<u8>,
}

Array compacto de instruçõesArray compacto de instruções

Formato binário de transação

As transações são serializadas usando um esquema de codificação compacta. Todos os arrays de comprimento variável (assinaturas, chaves de conta, instruções) são prefixados com uma codificação de comprimento compact-u16. Este formato usa 1 byte para valores 0-127 e 2-3 bytes para valores maiores.

Layout de transação legada (na rede):

CampoTamanhoDescrição
num_signatures1-3 bytes (compact-u16)Número de assinaturas
signaturesnum_signatures x 64 bytesAssinaturas Ed25519
num_required_signatures1 byteCampo 1 do MessageHeader
num_readonly_signed1 byteCampo 2 do MessageHeader
num_readonly_unsigned1 byteCampo 3 do MessageHeader
num_account_keys1-3 bytes (compact-u16)Número de chaves de conta estáticas
account_keysnum_account_keys x 32 bytesChaves públicas
recent_blockhash32 bytesBlockhash
num_instructions1-3 bytes (compact-u16)Número de instruções
instructionsvariávelArray de instruções compiladas

Cada instrução compilada é serializada como:

CampoTamanhoDescrição
program_id_index1 byteÍndice nas chaves de conta
num_accounts1-3 bytes (compact-u16)Número de índices de conta
account_indicesnum_accounts x 1 byteÍndices de chaves de conta
data_len1-3 bytes (compact-u16)Comprimento dos dados da instrução
datadata_len bytesDados opacos da instrução

Cálculo de tamanho

Dado PACKET_DATA_SIZE = 1.232 bytes, o espaço disponível pode ser calculado:

Total = 1232 bytes
- compact-u16(num_sigs) # 1 byte
- num_sigs * 64 # signature bytes
- 3 # message header
- compact-u16(num_keys) # 1 byte
- num_keys * 32 # account key bytes
- 32 # recent blockhash
- compact-u16(num_ixs) # 1 byte
- sum(instruction_sizes) # per-instruction overhead + data

Exemplo: transação de transferência de SOL

O diagrama abaixo mostra como transações e instruções funcionam em conjunto para permitir que os utilizadores interajam com a rede. Neste exemplo, SOL é transferido de uma conta para outra.

Os metadados da conta do remetente indicam que esta deve assinar a transação. Isto permite que o System Program deduza lamports. Tanto a conta do remetente como a do destinatário devem ser graváveis, para que o seu saldo de lamports possa ser alterado. Para executar esta instrução, a carteira do remetente envia a transação contendo a sua assinatura e a mensagem contendo a instrução de transferência de SOL.

Diagrama de transferência de SOLDiagrama de transferência de SOL

Após o envio da transação, o System Program processa a instrução de transferência e atualiza o saldo de lamports de ambas as contas.

Diagrama do processo de transferência de SOLDiagrama do processo de transferência de SOL

O exemplo abaixo mostra o código relevante para os diagramas acima. Consulte a função transfer do System Program.

import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// Create a connection to cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const LAMPORTS_PER_SOL = 1_000_000_000n;
const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL
// Fund sender with airdrop
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: sender.address,
lamports: lamports(LAMPORTS_PER_SOL), // 1 SOL
commitment: "confirmed"
});
// Check balance before transfer
const { value: preBalance1 } = await rpc.getBalance(sender.address).send();
const { value: preBalance2 } = await rpc.getBalance(recipient.address).send();
// Create a transfer instruction for transferring SOL from sender to recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount // 0.01 SOL in lamports
});
// Add the transfer instruction to a new transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
// Send the transaction to the network
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
const transactionSignature = getSignatureFromTransaction(signedTransaction);
// Check balance after transfer
const { value: postBalance1 } = await rpc.getBalance(sender.address).send();
const { value: postBalance2 } = await rpc.getBalance(recipient.address).send();
console.log(
"Sender prebalance:",
Number(preBalance1) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Recipient prebalance:",
Number(preBalance2) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Sender postbalance:",
Number(postBalance1) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Recipient postbalance:",
Number(postBalance2) / Number(LAMPORTS_PER_SOL)
);
console.log("Transaction Signature:", transactionSignature);
Console
Click to execute the code.

O exemplo seguinte mostra a estrutura de uma transação que contém uma única instrução de transferência de SOL.

import {
createSolanaRpc,
generateKeyPairSigner,
lamports,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
pipe,
signTransactionMessageWithSigners,
getCompiledTransactionMessageDecoder
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
const rpc = createSolanaRpc("http://localhost:8899");
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const LAMPORTS_PER_SOL = 1_000_000_000n;
const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL
// Create a transfer instruction for transferring SOL from sender to recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Decode the messageBytes
const compiledTransactionMessage =
getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);
console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Console
Click to execute the code.

O código abaixo mostra a saída dos trechos de código anteriores. O formato difere entre SDKs, mas note que cada instrução contém as mesmas informações necessárias.

{
"version": 0,
"header": {
"numSignerAccounts": 1,
"numReadonlySignerAccounts": 0,
"numReadonlyNonSignerAccounts": 1
},
"staticAccounts": [
"HoCy8p5xxDDYTYWEbQZasEjVNM5rxvidx8AfyqA4ywBa",
"5T388jBjovy7d8mQ3emHxMDTbUF8b7nWvAnSiP3EAdFL",
"11111111111111111111111111111111"
],
"lifetimeToken": "EGCWPUEXhqHJWYBfDirq3mHZb4qDpATmYqBZMBy9TBC1",
"instructions": [
{
"programAddressIndex": 2,
"accountIndices": [0, 1],
"data": {
"0": 2,
"1": 0,
"2": 0,
"3": 0,
"4": 128,
"5": 150,
"6": 152,
"7": 0,
"8": 0,
"9": 0,
"10": 0,
"11": 0
}
}
]
}

Obter detalhes da transação

Após o envio, obtenha os detalhes da transação usando a assinatura da transação e o método RPC getTransaction.

Também pode encontrar a transação usando o Solana Explorer.

Transaction Data
{
"blockTime": 1745196488,
"meta": {
"computeUnitsConsumed": 150,
"err": null,
"fee": 5000,
"innerInstructions": [],
"loadedAddresses": {
"readonly": [],
"writable": []
},
"logMessages": [
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"postBalances": [989995000, 10000000, 1],
"postTokenBalances": [],
"preBalances": [1000000000, 0, 1],
"preTokenBalances": [],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": 13049,
"transaction": {
"message": {
"header": {
"numReadonlySignedAccounts": 0,
"numReadonlyUnsignedAccounts": 1,
"numRequiredSignatures": 1
},
"accountKeys": [
"8PLdpLxkuv9Nt8w3XcGXvNa663LXDjSrSNon4EK7QSjQ",
"7GLg7bqgLBv1HVWXKgWAm6YoPf1LoWnyWGABbgk487Ma",
"11111111111111111111111111111111"
],
"recentBlockhash": "7ZCxc2SDhzV2bYgEQqdxTpweYJkpwshVSDtXuY7uPtjf",
"instructions": [
{
"accounts": [0, 1],
"data": "3Bxs4NN8M2Yn4TLb",
"programIdIndex": 2,
"stackHeight": null
}
],
"indexToProgramIds": {}
},
"signatures": [
"3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX"
]
},
"version": "legacy"
}

Is this page helpful?

Índice

Editar Página

Gerenciado por

© 2026 Fundação Solana.
Todos os direitos reservados.
Conecte-se
  • Blog