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.

Transação SimplificadaTransação Simplificada

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.

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.

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);
Click to execute the code.

As bibliotecas de cliente geralmente 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.

  • Kit
const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});
  • Legacy
const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = SystemProgram.transfer({
fromPubkey: sender.publicKey,
toPubkey: receiver.publicKey,
lamports: transferAmount * LAMPORTS_PER_SOL
});
  • Rust
let transfer_amount = LAMPORTS_PER_SOL / 100; // 0.01 SOL
let transfer_instruction =
system_instruction::transfer(&sender.pubkey(), &recipient.pubkey(), transfer_amount);

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

Instructions

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

Invocar uma instruction de um programa requer três informações essenciais:

  • Program ID: O programa com a lógica de execução para a instruction
  • Accounts: Lista de contas que a instruction necessita
  • Instruction Data: Array de bytes especificando a instruction a ser invocada no programa e quaisquer argumentos requeridos pela instruction
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>,
}

Transaction InstructionTransaction Instruction

AccountMeta

Cada conta requerida por uma instruction deve ser fornecida como um AccountMeta que contém:

  • pubkey: Endereço da conta
  • is_signer: Se a conta deve assinar a transação
  • is_writable: Se a instruction 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,
}

AccountMetaAccountMeta

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

Exemplo de estrutura de Instruction

Execute os exemplos abaixo para ver a estrutura de uma instruction 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));
Click to execute the code.

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

  • Program ID: O endereço do programa que executará a instrução.
  • Accounts: Uma lista de contas necessárias para a instrução. Para cada conta, a instrução deve especificar seu endereço, se ela deve assinar a transação e se será escrita.
  • Data: Um buffer de bytes que informa ao programa qual instrução executar e inclui quaisquer argumentos necessários para a 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

Uma transação da Solana consiste em:

  1. Assinaturas: Um array de assinaturas incluídas na transação.
  2. Mensagem: 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:

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

Mensagem da TransaçãoMensagem da Transação

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 de fragmento).

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 contas (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 utiliza três bytes para definir privilégios de conta.

  1. Assinaturas necessárias
  2. Número de contas assinadas somente leitura
  3. Número de contas não assinadas 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 Instructions nas mensagens de transação.

Array de Endereços de Conta

Uma mensagem de transação contém um array de endereços de conta requeridos por suas instructions. O array começa com um número compact-u16 indicando quantos endereços ele contém. Os endereços são então ordenados por seus privilégios, conforme determinado pelo cabeçalho da mensagem.

  • Contas que são graváveis e signatárias
  • Contas que são somente leitura e signatárias
  • Contas que são graváveis e não signatárias
  • Contas que são somente leitura e não signatárias

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
  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 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. Aqui está um exemplo no Solana Playground.

Array de Instructions

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

Como o array de endereços de conta na mensagem, ele começa com um comprimento compact-u16 seguido pelos dados da instruction. Cada instruction contém:

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

Quando você busca uma transação usando sua assinatura após enviá-la para a rede, você receberá uma resposta com a seguinte estrutura.

O campo message contém os seguintes campos:

  • header: Especifica privilégios de leitura/escrita e assinatura para endereços no array accountKeys

  • accountKeys: Array de todos os endereços de contas usados nas instruções da transação

  • recentBlockhash: Blockhash usado para marcar o timestamp da transação

  • instructions: Array de instruções a serem executadas. Cada account e programIdIndex em uma instrução referencia o array accountKeys por índice.

  • signatures: Array incluindo assinaturas para todas as contas exigidas como signatárias pelas instruções na transação. Uma assinatura é criada assinando a mensagem da transação usando a chave privada correspondente para uma conta.

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?