Transações e instruções

Na Solana, os usuários enviam transações para interagir com a rede. As transações contêm uma ou mais instruções que especificam operações a serem processadas. A lógica de execução para instruções é armazenada em programas implantados na rede Solana, onde cada programa define seu próprio conjunto de instruções.

Abaixo estão detalhes importantes sobre o processamento de transações na Solana:

  • Se uma transação inclui múltiplas instruções, as instruções são executadas na ordem em que foram adicionadas à transação.
  • As transações são "atômicas" - todas as instruções devem ser processadas com sucesso, ou a transação inteira falha e nenhuma mudança ocorre.

Uma transação é essencialmente uma solicitação para processar uma ou mais instruções. Você pode pensar em uma transação como um envelope contendo formulários. Cada formulário é uma instrução que diz à rede o que fazer. Enviar a transação é como enviar o envelope pelo correio para que os formulários sejam processados.

Transação SimplificadaTransação Simplificada

Pontos-chave

  • As transações da Solana incluem instruções que invocam programas na rede.
  • As transações são atômicas - se qualquer instrução falhar, toda a transação falha e nenhuma mudança ocorre.
  • As instruções em uma transação são executadas em ordem sequencial.
  • O limite de tamanho da transação é 1232 bytes.
  • Cada instrução requer três informações:
    1. O endereço do programa a ser invocado
    2. As contas que a instrução lê ou escreve
    3. Quaisquer dados extras exigidos pela instrução (por exemplo, argumentos de função)

Exemplo de transferência de SOL

O diagrama abaixo representa uma transação com uma única instrução para transferir SOL de um remetente para um destinatário.

Na Solana, as "carteiras" são contas pertencentes ao System Program. Apenas o programa proprietário pode alterar os dados de uma conta, portanto, transferir SOL requer o envio de uma transação para invocar o System Program.

Transferência de SOLTransferência de SOL

A conta do remetente deve assinar (is_signer) a transação para permitir que o System Program deduza seu saldo em lamports. As contas do remetente e do destinatário devem ser graváveis (is_writable) já que seus saldos em lamports serão alterados.

Após o envio da transação, o System Program processa a instrução de transferência. O System Program então atualiza os saldos em lamports de ambas as contas do remetente e do destinatário.

Processo de transferência de SOLProcesso de transferência de SOL

Os exemplos abaixo mostram como enviar uma transação que transfere SOL de uma conta para outra. Veja o código-fonte da instrução de transferência do System Program aqui.

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.

As bibliotecas de cliente frequentemente abstraem os detalhes para construir instruções de programa. Se uma biblioteca não estiver disponível, você pode construir a instrução manualmente. Isso requer que você conheça os detalhes de implementação da instrução.

Os exemplos abaixo mostram como construir manualmente a instrução de transferência. A aba Expanded Instruction é funcionalmente equivalente à aba Instruction.

const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});

Nas seções abaixo, vamos percorrer os detalhes das transações e instruções.

Instruções

Uma instrução na Solana programa pode ser entendida como uma função pública que pode ser chamada por qualquer pessoa usando a rede Solana.

Você pode pensar em um programa Solana como um servidor web hospedado na rede Solana, onde cada instrução é como um endpoint de API pública que os usuários podem chamar para realizar ações específicas. Invocar uma instrução é semelhante a enviar uma solicitação POST para um endpoint de API, permitindo que os usuários executem a lógica de negócios do programa.

Para chamar uma instrução de programa na Solana, você precisa construir uma Instruction com três informações:

  • Program ID: O endereço do programa com a lógica de negócios para a instrução sendo invocada.
  • Accounts: A lista de todas as contas que a instrução lê ou escreve.
  • Instruction Data: Um array de bytes especificando qual instrução invocar no programa e quaisquer argumentos necessários para a instrução.
Instruction
pub struct Instruction {
/// Pubkey of the program that executes this instruction.
pub program_id: Pubkey,
/// Metadata describing accounts that should be passed to the program.
pub accounts: Vec<AccountMeta>,
/// Opaque data passed to the program for its own interpretation.
pub data: Vec<u8>,
}

Instrução de TransaçãoInstrução de Transação

AccountMeta

Ao criar uma Instruction, você deve fornecer cada conta necessária como um AccountMeta. O AccountMeta especifica o seguinte:

  • pubkey: O endereço da conta
  • is_signer: Se a conta deve assinar a transação
  • is_writable: Se a instrução modifica os dados da conta
AccountMeta
pub struct AccountMeta {
/// An account's public key.
pub pubkey: Pubkey,
/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.
pub is_signer: bool,
/// True if the account data or metadata may be mutated during program execution.
pub is_writable: bool,
}

Ao especificar antecipadamente quais contas uma instrução lê ou escreve, transações que não modificam as mesmas contas podem ser executadas em paralelo.

Para saber quais contas uma instrução requer, incluindo quais devem ser graváveis, somente leitura ou assinar a transação, você deve consultar a implementação da instrução conforme definida pelo programa.

Na prática, geralmente você não precisa construir um Instruction manualmente. A maioria dos desenvolvedores de programas fornecem bibliotecas cliente com funções auxiliares que criam as instruções para você.

AccountMetaAccountMeta

Exemplo de estrutura de instrução

Execute os exemplos abaixo para ver a estrutura de uma instrução de transferência de SOL.

import { generateKeyPairSigner, lamports } from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// 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
});
console.log(JSON.stringify(transferInstruction, null, 2));
Console
Click to execute the code.

Os exemplos a seguir mostram a saída dos trechos de código anteriores. O formato exato difere dependendo do SDK, mas toda instrução Solana requer as seguintes informações:

  • ID do programa: O endereço do programa que executará a instrução.
  • Contas: Uma lista de contas exigidas pela instrução. Para cada conta, a instrução deve especificar seu endereço, se ela deve assinar a transação e se será gravada.
  • Dados: Um buffer de bytes que informa ao programa qual instrução executar e inclui quaisquer argumentos exigidos pela instrução.
{
"accounts": [
{
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"role": 3,
"signer": {
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"keyPair": {
"privateKey": {},
"publicKey": {}
}
}
},
{
"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p",
"role": 1
}
],
"programAddress": "11111111111111111111111111111111",
"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
}
}

Transações

Depois de criar as instruções que deseja invocar, o próximo passo é criar uma Transaction e adicionar as instruções à transação. Uma transação Solana é composta por:

  1. Assinaturas: Um array de assinaturas de todas as contas necessárias como signatárias para as instruções na transação. Uma assinatura é criada assinando a transação Message com a chave privada da conta.
  2. Mensagem: A mensagem da transação inclui a lista de instruções a serem processadas atomicamente.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

Formato da TransaçãoFormato da Transação

A estrutura de uma mensagem de transação consiste em:

  • Cabeçalho da Mensagem: Especifica o número de contas signatárias e somente leitura.
  • Endereços de Contas: Um array de endereços de contas necessários pelas instruções na transação.
  • Blockhash Recente: Atua como um timestamp para a transação.
  • Instruções: Um array de instruções a serem executadas.
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>,
}

Tamanho da Transação

As transações da Solana têm um limite de tamanho de 1232 bytes. Este limite vem do tamanho da Unidade Máxima de Transmissão (MTU) IPv6 de 1280 bytes, menos 48 bytes para cabeçalhos de rede (40 bytes IPv6 + 8 bytes de cabeçalho).

O tamanho total de uma transação (assinaturas e mensagem) deve permanecer abaixo deste limite e inclui:

  • Assinaturas: 64 bytes cada
  • Mensagem: Cabeçalho (3 bytes), chaves de conta (32 bytes cada), blockhash recente (32 bytes) e instruções

Formato da TransaçãoFormato da Transação

Cabeçalho da Mensagem

O cabeçalho da mensagem especifica as permissões para a conta na transação. Ele funciona em combinação com os endereços de contas ordenados rigorosamente para determinar quais contas são signatárias e quais são graváveis.

  1. O número de assinaturas necessárias para todas as instruções na transação.
  2. O número de contas assinadas que são somente leitura.
  3. O 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,
}

Cabeçalho da MensagemCabeçalho da Mensagem

Formato de Array Compacto

Um array compacto em uma mensagem de transação é um array serializado no seguinte formato:

  1. O comprimento do array (codificado como compact-u16)
  2. Os itens do array listados um após o outro

Formato de array compactoFormato de array compacto

Este formato é usado para codificar os comprimentos dos arrays de Endereços de Conta e Instruções nas mensagens de transação.

Array de Endereços de Conta

Uma mensagem de transação contém uma única lista de todos os endereços de conta exigidos por suas instruções. O array começa com um número compact-u16 indicando quantos endereços ele contém.

Para economizar espaço, a transação não armazena permissões para cada conta individualmente. Em vez disso, ela depende de uma combinação do MessageHeader e de uma ordenação estrita dos endereços de conta para determinar as permissões.

Os endereços são sempre ordenados da seguinte maneira:

  1. Contas que são graváveis e signatárias
  2. Contas que são somente leitura e signatárias
  3. Contas que são graváveis e não signatárias
  4. Contas que são somente leitura e não signatárias

O MessageHeader fornece os valores usados para determinar o número de contas para cada grupo de permissão.

Array compacto de endereços de contaArray compacto de endereços de conta

Blockhash Recente

Cada transação requer um blockhash recente que serve para dois propósitos:

  1. Atua como um timestamp para quando a transação foi criada
  2. Previne transações duplicadas

Um blockhash expira após 150 blocos (aproximadamente 1 minuto assumindo tempos de bloco de 400ms), após o qual a transação é considerada expirada e não pode ser processada.

Você pode usar o método RPC getLatestBlockhash para obter o blockhash atual e a altura do último bloco no qual o blockhash será válido.

Array de Instruções

Uma mensagem de transação contém um array de instruções no tipo CompiledInstruction. As instruções são convertidas para este tipo quando adicionadas a uma transação.

Assim como o array de endereços de contas na mensagem, ele começa com um compact-u16 de comprimento seguido pelos dados da instrução. Cada instrução contém:

  1. Índice do ID do Programa: Um índice que aponta para o endereço do programa no array de endereços de contas. Isso especifica o programa que processa a instrução.
  2. Índices de Contas: Um array de índices que apontam para os endereços de contas necessários para esta instrução.
  3. Instruction Data: Um array de bytes que especifica qual instrução invocar no programa e quaisquer dados adicionais necessários pela instrução (ex: argumentos de função).
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

Exemplo de Estrutura de Transação

Execute os exemplos abaixo para ver a estrutura de uma transação com 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.

Os exemplos a seguir mostram a saída da mensagem de transação dos snippets de código anteriores. O formato exato difere dependendo do SDK, mas inclui as mesmas informações.

{
"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
}
}
]
}

Após enviar uma transação, você pode recuperar seus detalhes usando o método RPC getTransaction. A resposta terá uma estrutura semelhante ao snippet a seguir. Alternativamente, você pode inspecionar a transação usando o Solana Explorer.

Uma "assinatura de transação" identifica exclusivamente uma transação na Solana. Você usa esta assinatura para procurar os detalhes da transação na rede. A assinatura da transação é simplesmente a primeira assinatura na transação. Observe que a primeira assinatura também é a assinatura do pagador da taxa da transação.

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?