Samenvatting
Een transactie heeft handtekeningen + een bericht. Het bericht bevat een header, accountadressen, recente blockhash en gecompileerde instructies. Maximale geserialiseerde grootte: 1.232 bytes.
Een
Transaction
heeft twee velden op het hoogste niveau:
signatures: Een array van handtekeningenmessage: Transactie-informatie, inclusief de lijst met instructies die verwerkt moeten worden
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Diagram dat de twee delen van een transactie toont
De totale geserialiseerde grootte van een transactie mag niet groter zijn dan
PACKET_DATA_SIZE
(1.232 bytes). Deze limiet is gelijk aan 1.280 bytes (de IPv6 minimum MTU) minus
48 bytes voor netwerkheaders (40 bytes IPv6 + 8 bytes fragmentheader). De 1.232
bytes omvatten zowel de signatures array als de
message struct.
Diagram dat het transactieformaat en groottelimieten toont
Handtekeningen
Het signatures veld is een compact-gecodeerde array van
Signature
waarden. Elke Signature is een 64-byte Ed25519 handtekening van het
geserialiseerde Message, ondertekend met de private key van het ondertekenende
account. Eén handtekening is vereist voor elk
ondertekenend account waarnaar wordt verwezen door de
instructies van de transactie.
Elke handtekening wordt gegenereerd door een privésleutel. Waar die sleutel zich bevindt — een lokaal keypair, een cloud HSM of KMS, of een beheerde walletservice — is een productieontwerpbeslissing. Zie Ondertekenen in Productie.
De eerste handtekening in de array behoort toe aan de fee payer, het account dat de basistransactiekosten en prioriteringskosten betaalt. Deze eerste handtekening fungeert ook als de transactie-ID, die wordt gebruikt om de transactie op het netwerk op te zoeken. De transactie-ID wordt gewoonlijk aangeduid als de transactiehandtekening.
Vereisten voor de fee payer:
- Moet het eerste account in het bericht zijn (index 0) en een ondertekenaar zijn.
- Moet een account zijn dat eigendom is van het System Program of een
nonce-account (gevalideerd door
validate_fee_payer). - Moet voldoende lamports bevatten om
rent_exempt_minimum + total_feete dekken; anders mislukt de transactie metInsufficientFundsForFee.
Bericht
Het veld message is een
Message
struct die de payload van de transactie bevat:
header: De bericht-headeraccount_keys: Een array van accountadressen die vereist zijn door de instructies van de transactierecent_blockhash: Een blockhash die fungeert als tijdstempel voor de transactieinstructions: Een array van instructies
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>,}
Header
Het veld header is een
MessageHeader
struct met drie u8-velden die de array account_keys verdelen in
permissiegroepen:
num_required_signatures: Totaal aantal handtekeningen dat vereist is door de transactie.num_readonly_signed_accounts: Aantal ondertekende accounts dat alleen-lezen is.num_readonly_unsigned_accounts: Aantal niet-ondertekende accounts dat alleen-lezen is.
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,}
Legacy en versioned berichtprefixen
In een legacy transactiebericht is de eerste berichtbyte
num_required_signatures, gevolgd door de andere twee MessageHeader bytes.
In een versioned transactiebericht is de eerste byte in plaats daarvan een
versieprefox; de drie-byte MessageHeader begint direct na dat prefix. Zie
versioned transacties voor
de volledige v0 berichtopmaak.
Diagram van de drie delen van de berichtheader
Accountadressen
Het
account_keys
veld is een compact-gecodeerde array van publieke sleutels. Elke vermelding
identificeert een account dat door minimaal één van de instructies van de
transactie wordt gebruikt. De array moet alle accounts bevatten en moet deze
strikte volgorde volgen:
- Ondertekenaar + Schrijfbaar
- Ondertekenaar + Alleen-lezen
- Niet-ondertekenaar + Schrijfbaar
- Niet-ondertekenaar + Alleen-lezen
Deze strikte volgorde maakt het mogelijk om de account_keys array te
combineren met de drie tellingen in de header van het bericht om
de rechten voor elk account te bepalen zonder per-account metadatavlaggen op
te slaan. De headertelling verdeelt de array in de vier hierboven vermelde
rechtengroepen.
Diagram van de volgorde van de array met accountadressen
Recente blockhash
Het recent_blockhash veld is een 32-byte hash die twee doelen dient:
- Tijdstempel: bewijst dat de transactie recentelijk is aangemaakt.
- Deduplicatie: voorkomt dat dezelfde transactie tweemaal wordt verwerkt.
Een blockhash verloopt na 150 slot. Als de blockhash niet langer geldig is
wanneer de transactie binnenkomt, wordt deze afgewezen met
BlockhashNotFound, tenzij het een geldige
durable nonce transactie is.
Met de getLatestBlockhash RPC-methode
kunt u de huidige blockhash en de laatste blokhoogte ophalen waarbij de
blockhash geldig zal zijn.
Instructies
Het
instructions
veld is een compact-gecodeerde array van
CompiledInstruction
structs. Elke CompiledInstruction verwijst naar accounts via een index in
de account_keys array in plaats van via een volledige publieke sleutel. Het
bevat:
program_id_index: Index inaccount_keysdie het aan te roepen programma identificeert.accounts: Array van indices inaccount_keysdie de accounts specificeren die aan het programma worden doorgegeven.data: Byte-array met de instructie discriminator en geserialiseerde argumenten.
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>,}
Compacte array van instructies
Binair transactieformaat
Transacties worden geserialiseerd met behulp van een compact coderingsschema. Alle variabele-lengte arrays (handtekeningen, accountsleutels, instructies) worden voorafgegaan door een compact-u16 lengte-codering. Dit formaat gebruikt 1 byte voor waarden 0-127 en 2-3 bytes voor grotere waarden.
Legacy transactie-indeling (op het netwerk):
| Veld | Grootte | Beschrijving |
|---|---|---|
num_signatures | 1-3 bytes (compact-u16) | Aantal handtekeningen |
signatures | num_signatures x 64 bytes | Ed25519-handtekeningen |
num_required_signatures | 1 byte | MessageHeader veld 1 |
num_readonly_signed | 1 byte | MessageHeader veld 2 |
num_readonly_unsigned | 1 byte | MessageHeader veld 3 |
num_account_keys | 1-3 bytes (compact-u16) | Aantal statische accountsleutels |
account_keys | num_account_keys x 32 bytes | Publieke sleutels |
recent_blockhash | 32 bytes | Blockhash |
num_instructions | 1-3 bytes (compact-u16) | Aantal instructies |
instructions | variabel | Array van gecompileerde instructies |
Elke gecompileerde instructie wordt als volgt geserialiseerd:
| Veld | Grootte | Beschrijving |
|---|---|---|
program_id_index | 1 byte | Index in accountsleutels |
num_accounts | 1-3 bytes (compact-u16) | Aantal accountindices |
account_indices | num_accounts x 1 byte | Accountsleutelindices |
data_len | 1-3 bytes (compact-u16) | Lengte van instruction data |
data | data_len bytes | Opaque instruction data |
Grootteberekening
Gegeven PACKET_DATA_SIZE = 1.232 bytes, kan de beschikbare ruimte worden
berekend:
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
Voorbeeld: SOL-overboekingstransactie
Het onderstaande diagram laat zien hoe transacties en instructies samenwerken om gebruikers in staat te stellen met het netwerk te communiceren. In dit voorbeeld wordt SOL overgemaakt van het ene account naar het andere.
De metadata van het zendersaccount geeft aan dat het de transactie moet ondertekenen. Hierdoor kan het System Program lamports aftrekken. Zowel het zenders- als het ontvangstsaccount moet beschrijfbaar zijn, zodat hun lamport-saldo kan worden gewijzigd. Om deze instructie uit te voeren, stuurt de portemonnee van de verzender de transactie met zijn handtekening en het bericht met de SOL-overboekingsinstructie.
SOL-overboekingsdiagram
Nadat de transactie is verzonden, verwerkt het System Program de overboekingsinstructie en werkt het lamport-saldo van beide accounts bij.
Procesdiagram SOL-overboeking
Controleer de ontvanger voordat u SOL verzendt
Een System Program-overboeking voegt lamports toe aan elk account. Er is geen protocolniveaucontrole of de ontvanger de SOL er weer uit kan halen. Lamports kunnen alleen worden verplaatst door het eigenaarsprogramma van het account, dus het sturen van SOL naar een token mint, een programma, of een PDA die u niet beheert brengt het risico van permanent verlies van middelen met zich mee — alleen een autoriteit die is opgegeven door het eigenaarsprogramma kan ze terugsturen. SOL verzonden naar een token account is alleen terugvorderbaar door de eigenaar van dat account, nooit door de verzender.
SPL token-overboekingen zijn deels zelfbeschermend: het Token Program weigert een overboeking waarvan de accounts niet overeenkomen met de verwachte mint. Native SOL-overboekingen hebben geen dergelijke beveiliging, dus de verzender moet de ontvanger verifiëren voordat hij ondertekent. Zie Adres verifiëren voor de volledige classificatielogica.
Het onderstaande voorbeeld toont de code die relevant is voor de bovenstaande
diagrammen. Zie de System Program's
transfer functie.
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { systemProgram } from "@solana-program/system";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n))).use(systemProgram());const sender = client.payer;const recipient = await generateKeyPairSigner();const LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Check balance before transferconst { value: preBalance1 } = await client.rpc.getBalance(sender.address).send();const { value: preBalance2 } = await client.rpc.getBalance(recipient.address).send();// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = client.system.instructions.transferSol({source: sender,destination: recipient.address,amount: transferAmount // 0.01 SOL in lamports});const transactionSignature = await client.sendTransaction([transferInstruction]);// Check balance after transferconst { value: postBalance1 } = await client.rpc.getBalance(sender.address).send();const { value: postBalance2 } = await client.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.context.signature);
Het volgende voorbeeld toont de structuur van een transactie die een enkel SOL-overboekingsinstructie bevat.
import {createClient,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { systemProgram } from "@solana-program/system";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n))).use(systemProgram());const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();const sender = client.payer;const recipient = await generateKeyPairSigner();// Define the amount to transferconst 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 recipientconst transferInstruction = client.system.instructions.transferSol({source: sender,destination: recipient.address,amount: transferAmount});// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Decode the messageBytesconst compiledTransactionMessage =getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);console.log(JSON.stringify(compiledTransactionMessage, null, 2));
De onderstaande code toont de uitvoer van de vorige codefragmenten. Het formaat verschilt per SDK, maar merk op dat elke instructie dezelfde vereiste informatie bevat.
{"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}}]}
Controleer de ontvanger vóór de overboeking
Omdat een SOL-overboeking naar elk account slaagt, controleer je de ontvanger voordat je ondertekent. Haal het account op en stuur alleen naar een System Program-wallet (of een niet-gefinancierd on-curve adres); wijs mints, token accounts, programma's en PDA's die je niet beheert af.
import {type Address,createSolanaRpc,fetchJsonParsedAccount,isOffCurveAddress} from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const SYSTEM_PROGRAM = "11111111111111111111111111111111" as Address;/*** Throws if `recipient` cannot safely receive native SOL.** Only System Program wallets (or unfunded on-curve addresses) are safe. Any* other account locks the lamports because no authority can debit them.*/async function assertSafeSolRecipient(recipient: Address): Promise<void> {const account = await fetchJsonParsedAccount(rpc, recipient);if (!account.exists) {// Off-curve = a PDA with no account; reject conservatively.if (isOffCurveAddress(recipient)) {throw new Error("Recipient is a PDA with no account; SOL would be locked");}// On-curve = an unfunded wallet, safe to fund.return;}if (account.programAddress !== SYSTEM_PROGRAM) {throw new Error(`Recipient is owned by ${account.programAddress}, not a wallet; SOL would be locked`);}}// A wallet: safe.await assertSafeSolRecipient("H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" as Address);// The USDC mint: rejected before any SOL leaves the sender.await assertSafeSolRecipient("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" as Address);
Dit fragment controleert native SOL-ontvangers. Voor de volledige classificatie die ook SPL token-verzendingen afhandelt (token accounts, ATA's, Token-2022), zie Verify Address.
Transactiedetails ophalen
Na het indienen kun je transactiedetails ophalen met de transactiehandtekening en de getTransaction RPC-methode.
Je kunt de transactie ook vinden via Solana Explorer.
{"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"}
De onbewerkte respons identificeert accounts op index en slaat interne (CPI) instructies op als gecodeerde blobs. Om deze om te zetten naar adressen en de volledige instructieboom te doorlopen, zie Transactie-introspectie.
Is this page helpful?