Versoidut transaktiot

Yhteenveto

Solanassa on kolme transaktioformaattia: legacy, v0 ja v1. v0 lisää Address Lookup Tables (ALT) -taulut, joiden avulla tileihin viitataan 1-tavuisilla indekseillä. v1 nostaa kokorajaksi 4 096 tavua, siirtää resurssirajat itse viestiin ja poistaa ALT-tuen.

Solana tukee kolmea transaktioformaattia: legacy, v0 ja v1. Jokainen näistä kuvataan alla samalla kolmiosaisella rakenteella: miten tavut järjestetään siirtomuodossa, miten tileihin viitataan ja mistä resurssirajat tulevat.

v1-aktivointitila

V1-formaatti on käytössä mainnetissa, devnetissä ja testnetissä. Transaktioiden lukeminen on rikkova muutos: asiakkaat, jotka eivät ota sitä käyttöön, epäonnistuvat v1-transaktioiden kanssa. Olemassa olevien sovellusten kannattaa tutustua kohtaan v1:een valmistautuminen.

Formaattien vertailu

Rajalegacyv0v1
Transaktion enimmäiskoko1 232 tavua1 232 tavua4 096 tavua
Tiliosoitteet~32, kokorajoitettu64, hakutaulujen kautta64, suoraan (96 SIMD-0596:n kanssa)
Osoitehakutaulutei tuettutuettuei tuettu
ResurssirajatComputeBudget-ohjeetComputeBudget-ohjeetviestiasetus

Siirry: Legacy · v0 · v1

Legacy-formaatti

Alkuperäinen formaatti, joka on edelleen useimpien työkalujen oletusarvo. Siinä ei ole lainkaan versioliitettä: transaktion ensimmäinen tavu on allekirjoitustaulukon compact-u16-lukumäärä ja viestin ensimmäinen tavu on num_required_signatures, jonka korkein bitti on aina nollattuna.

Legacy-siirtomuoto

KenttäKokoKuvaus
num_signaturescompact-u16Allekirjoitusten lukumäärä
signaturesnum_signatures × 64 tavuaEd25519-allekirjoitukset
header3 tavuaMessageHeader — ensimmäisessä tavussa versiobittiä ei ole asetettu
num_account_keyscompact-u16Tiliavainten lukumäärä
account_keysnum_account_keys × 32 tavuaJulkiset avaimet, kaikki suoraan
recent_blockhash32 tavuaElinajan määrittäjä
num_instructionscompact-u16Ohjeiden lukumäärä
instructionsvaihtelevaJokainen ohje sarjallistettuna peräkkäin

Jokainen muuttuvanpituinen taulukko alkaa compact-u16-pituudella: 1 tavu arvoille 0–127, 2–3 tavua suuremmille arvoille. Ohjekohtaisen rakenteen ja koon laskentaesimerkin löydät transaktion binäärimuodosta.

Tilit legacy-formaatissa

Jokainen tili kirjoitetaan account_keys-kohtaan täydellisenä 32-tavuisena julkisena avaimena, ja ohjeet viittaavat niihin 1-tavuisella indeksillä kyseiseen taulukkoon. Tiliä ei voi viitata, ellei sitä ole kirjoitettu transaktioon, mikä rajoittaa legacy-transaktion noin 32 tiliin ennen kuin 1 232 tavun raja täyttyy.

Resurssirajat legacy-formaatissa

Laskentayksikkörajaa, ladattujen tilitietojen kokorajaa, keon kokoa ja prioriteettimaksua pyydetään sisällyttämällä ComputeBudget-ohjelmaohjeita transaktioon. Jokainen niistä kuluttaa yhden ohjauspaikan ja 150 laskentayksikköä. Niiden pois jättäminen on turvallista: ajonaikainen ympäristö käyttää oletusarvoja — 200 000 laskentayksikköä per ohje (enintään 1,4 M), 64 MiB:n datatietoraja, 32 KiB:n keko ja nolla prioriteettimaksua.

V0-formaatti

v0 on legacy-viesti lisättynä kahdella asialla: 0x80-versioliitetavulla ja address_table_lookups-taulukolla, joka lisätään ohjeiden jälkeen. Kaikki sitä edeltävä on tavutasolla identtinen legacyn kanssa.

V0-siirtomuoto

KenttäKokoKuvaus
num_signaturescompact-u16Allekirjoitusten lukumäärä
signaturesnum_signatures × 64 tavuaEd25519-allekirjoitukset
0x801 tavuVersioliitetavu — viestin ensimmäinen tavu
header3 tavuaMessageHeader (sama kuin perinteinen)
num_account_keyscompact-u16Staattisten tiliavainten lukumäärä
static_account_keysnum_account_keys × 32 tavuaAvaimet, jotka esiintyvät kirjaimellisesti transaktiossa
recent_blockhash32 tavuaElinajan määrittäjä
num_instructionscompact-u16Ohjeiden lukumäärä
instructionsvaihtelevaSama muoto kuin perinteinen
address_table_lookupscompact-u16 + vaihtelevaALT-viittaukset (katso alla)

Jokainen osoitetaulukon hakumerkintä sisältää:

KenttäKokoKuvaus
account_key32 tavuaALT-tilin julkinen avain
writable_indexescompact-u16 + N x 1 tavuIndeksit ALT:iin kirjoitettaville tileille
readonly_indexescompact-u16 + N x 1 tavuIndeksit ALT:iin vain luku -oikeudella oleville tileille

Osoitehakutaulut

ALT on ketjussa oleva tili, joka tallentaa enintään 256 julkistaavainta. Viittaamalla ALT:iin, transaktio voi sisältyttää lisätilejä käyttäen 1-tavuisia indeksejä 32-tavuisten julkisten avainten sijaan, mikä vähentää merkittävästi tilikohtaista kuormitusta.

Ajonaikana, ennen suorituksen alkamista, validaattori selvittää kaikki ALT-viittaukset täysiksi julkisiksi avaimiksi. Selvitetyt osoitteet liitetään staattisten tiliavainten perään muodostaen täydellisen tiliavainten listan. ALT:n kautta selvitetyt tilit noudattavat samaa järjestystä kuin staattiset tilit: kirjoitettavat haut tulevat ennen vain luku -oikeudella olevia hakuja.

Osoitetaulukot vaikuttavat vain siihen, miten tileihin viitataan verkossa välitettävässä transaktiossa. Suoritusaikana runtime selvittää kaikki indeksit täysiksi tiliosoitteiksi. ALT:n kautta selvitetyt tilit voivat olla vain kirjoitettavia tai vain luku -oikeudella olevia (ei-allekirjoittajia); ne eivät voi olla allekirjoittajia.

Osoitehakutaulun luominen ja käyttö

Tämä esimerkki luo hakutaulun, lisää siihen osoitteen ja käyttää taulua v0-transaktion kokoamiseen ja lähettämiseen.

Hakutaulusta voi ladata vain muita kuin allekirjoittajatilejä. Siirtomaksun maksaja ja jokainen vaadittu allekirjoittaja pysyvät viestin staattisessa tililuettelossa. Osoitetta ei myöskään voi hakea samassa slot-yksikössä, jossa se lisättiin, joten esimerkki odottaa seuraavaa vahvistettua slot-yksikköä ennen v0-viestin kokoamista.

Resurssirajat v0:ssa

Sama kuin legacyssä: ComputeBudget-ohjeet, samoilla oletusarvoilla kun ne jätetään pois.

V1-formaatti

v1 nostaa kokorajaksi 4 096 tavua ja rakentaa viestin uudelleen transaktioasetusten ympärille: resurssirajat siirtyvät ComputeBudget-ohjeista viestin omiin kiinteäpaikkaisiin kenttiin. Tämä mahdollistaa verkolle transaktion prioriteetin luokittelun yhdellä kiinteän siirtymän lukuoperaatiolla sen sijaan, että ohjeluettelo pitäisi käydä läpi ja purkaa sarjallistuksesta.

V1-siirtomuoto

KenttäKokoKuvaus
0x811 tavuVersioliitetavu — transaktion ensimmäinen tavu
header3 tavuaMessageHeader (sama kuin perinteinen)
config_mask4 tavuau32 LE -bittipeite, joka merkitsee läsnä olevat asetusarvot
recent_blockhash32 tavuaElinajan määrittäjä
num_instructions1 tavuKiinteälevyinen laskuri, enintään 64
num_addresses1 tavuKiinteälevyinen laskuri, enintään 64 (96 SIMD-0596:n kanssa)
addressesN × 32 tavuaTiliosoitteet, kaikki suoraan — ei hakutauluviittauksia
config_values0–20 tavuaYksi arvo per asetettu bitti, bittijärjestyksessä (ks. alla)
instruction_headersN × 4 tavuaPer ohje: program_id_index (u8), num_accounts (u8), data_len (u16 LE)
instruction_payloadsvaihtelevaPer ohje: tili-indeksit, sitten instruction data
signaturesN × 64 tavuaLopussa ilman pituusliitettä — lukumäärä saadaan headerista

Kaksi rakenteellista eroa legacyyn ja v0:aan nähden on syytä huomioida dekooderia kirjoitettaessa. Laskurit ovat kiinteälevyisiä u8-kenttiä compact-u16:n sijaan, ja ohjeet on jaettu kahteen jaksoon: ensin kaikki kiinteäkokoiset headerit, sitten kaikki vaihtuvanpituiset hyötykuormat — sen sijaan että jokainen ohje olisi peräkkäinen kokonaisuus.

Tilit v1:ssä: ei osoitehakutauluja

v1 poistaa ALT-tuen tarkoituksella. 64 raakaosoitetta on 2 048 tavua, ja SIMD-0596:n jälkeen sallitut 96 osoitetta ovat 3 072 tavua, molemmat selvästi 4 096 tavun rajan sisällä, joten jokainen osoite on suoraan viestissä kuten legacy-formaatissa. Jos sovelluksesi käyttää hakutaulut, siirtyminen v1:een tarkoittaa näiden osoitteiden sisällyttämistä suoraan viestiin.

Resurssirajat v1:ssä: transaktioasetukset

Asetukset koostuvat u32-bittipeitteestä, jota seuraa kiinteälevyiset arvot kullekin kentälle, jonka bitti on asetettu:

Bitti(t)KenttäLeveysHuomiot
0–1Prioriteettimaksuu64lamport-kokonaissumma — molemmat bitit asetetaan yhdessä
2Laskentayksikörajau32
3Ladattujen tilitietojen kokorajau32
4Pyydetty keon kokou32

Tuntemattomat bitit hylätään. Koska viesti on allekirjoitettu, tunnistamattomia asetuskenttiä ei voida hiljaisesti pudottaa pois.

Prioriteettimaksu on lamport-kokonaissumma, ei yksikköhinta

Legacy- ja v0-formaateissa prioriteettimaksu asetetaan SetComputeUnitPrice-komennolla mikro-lamporteina laskentayksikköä kohti, kerrottuna laskentayksikkörajalla. V1:ssä se on absoluuttinen kokonaissumma lamporteissa — ei kertolaskua, ei pyöristystä. Älä käytä yksikkökohtaista laskukaavaa. Konaismaksukaava on muuten muuttumaton: (allekirjoitukset × lamports_per_signature) + priority_fee.

ComputeBudget-ohjeet ovat no-op-toimintoja v1:ssä

V1-transaktio ei hylkää ComputeBudget-ohjeita — se ohittaa ne asetusten osalta. Ne suoritetaan edelleen onnistuneina no-op-toimintoina kuluttaen 150 laskentayksikköä ja yhden 64:stä ohjausvälistä ilman vaikutusta budjettiin. Poista ne v1-transaktioita rakentaessasi ja lopeta niiden etsiminen v1-transaktioita lukiessasi: arvot sijaitsevat viestiasetusosiossa.

Asetuskentät on asetettava erikseen

Tärkein käyttäytymismuutos lähettäjille: toisin kuin legacy- ja v0-formaateissa, v1-transaktioissa on asetettava laskentayksiköraja ja ladattujen tilitietojen kokoraja erikseen, muuten transaktio epäonnistuu.

Asettamaton kenttälegacy / v0v1Oire puuttuessa
Laskentayksiköraja200k per ohje, enintään 1,4M0 laskentayksikköäEpäonnistuu välittömästi, budjetti ylitetty
Ladattujen tilitietojen koko64 MiB0 tavuaMaxLoadedAccountsDataSizeExceeded ensimmäisellä ladatulla tilillä
Prioriteettimaksu00—
Keon koko32 KiB32 KiB—

Suositeltava lähestymistapa on simuloida kerran molemmilla rajoilla maksimoituina, kirjoittaa sitten palautetut unitsConsumed- ja loadedAccountsDataSize-arvot asetuksiin pyöristäen datatietojen koko ylöspäin seuraavaan 32 KiB:n sivuun pelivaraa varten (lohkon kustannusmalli laskee 32 KiB:n sivuissa, joten varaa seuraavaan sivurajaan asti ei lasketa).

V1-transaktion rakentaminen

V1-viesti koostetaan saman putken kautta kuin legacy- tai v0-viesti. Ero on setTransactionMessageConfig-funktiossa: resurssirajat ovat viestin ominaisuus, joten ohjeluettelo sisältää vain ohjelmaohjeet.

Kit
import {
appendTransactionMessageInstruction,
assertIsTransactionWithBlockhashLifetime,
createClient,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageConfig,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
} from "@solana/kit";
import { rpcAirdrop, solanaRpc } from "@solana/kit-plugin-rpc";
import { airdropPayer, payer } from "@solana/kit-plugin-signer";
import { getTransferSolInstruction } from "@solana-program/system";
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const client = await createClient()
.use(payer(sender))
.use(
solanaRpc({
rpcUrl: "http://localhost:8899",
rpcSubscriptionsUrl: "ws://localhost:8900",
}),
)
.use(rpcAirdrop())
.use(airdropPayer(lamports(1_000_000_000n)));
const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();
// A v1 message is assembled through the same pipeline as a legacy or v0 one.
// The difference is the config: a v1 transaction carries its resource limits in
// the message itself, so no ComputeBudget instructions are compiled in.
const transactionMessage = pipe(
createTransactionMessage({ version: 1 }),
(m) => setTransactionMessageFeePayerSigner(sender, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
(m) =>
appendTransactionMessageInstruction(
getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: lamports(10_000_000n),
}),
m,
),
// The compute unit limit and loaded accounts data size limit both default to
// zero on v1, so a transaction that leaves them unset always fails. The
// priority fee is an absolute total in lamports, not a price per compute
// unit.
(m) =>
setTransactionMessageConfig(
{
computeUnitLimit: 20_000,
loadedAccountsDataSizeLimit: 64 * 1024,
priorityFeeLamports: 5_000n,
},
m,
),
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
assertIsTransactionWithBlockhashLifetime(signedTransaction);
await sendAndConfirmTransactionFactory({
rpc: client.rpc,
rpcSubscriptions: client.rpcSubscriptions,
})(signedTransaction, { commitment: "confirmed" });
const transactionSignature = getSignatureFromTransaction(signedTransaction);
console.log("Transaction Signature:", transactionSignature);
Console
Click to execute the code.

Kit tarjoaa myös erillisen asetusfunktion jokaiselle asetuskentälle — setTransactionMessagePriorityFeeLamports, setTransactionMessageComputeUnitLimit, setTransactionMessageLoadedAccountsDataSizeLimit ja setTransactionMessageHeapSize — joilla ne voi asettaa yksi kerrallaan.

Rajojen mitoittaminen simuloinnilla

Älä arvaa kahta pakollista rajaa, vaan mittaa ne. Simulointi ajetaan siten, että molemmat rajat on nostettu ajonaikaiseen enimmäisarvoonsa, joten se ei voi epäonnistua mitattavien resurssien puutteen vuoksi. Palautettu luku on yhden simuloidun ajon tarkka kustannus, joten lisää siihen marginaali ennen kuin luotat siihen.

Kit
import {
appendTransactionMessageInstruction,
assertIsTransactionWithBlockhashLifetime,
createClient,
createTransactionMessage,
estimateAndSetResourceLimitsFactory,
estimateResourceLimitsFactory,
fillTransactionMessageProvisoryResourceLimits,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
setTransactionMessagePriorityFeeLamports,
signTransactionMessageWithSigners,
} from "@solana/kit";
import { rpcAirdrop, solanaRpc } from "@solana/kit-plugin-rpc";
import { airdropPayer, payer } from "@solana/kit-plugin-signer";
import { getTransferSolInstruction } from "@solana-program/system";
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const client = await createClient()
.use(payer(sender))
.use(
solanaRpc({
rpcUrl: "http://localhost:8899",
rpcSubscriptionsUrl: "ws://localhost:8900",
}),
)
.use(rpcAirdrop())
.use(airdropPayer(lamports(1_000_000_000n)));
const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();
// The priority fee is a pricing decision that simulation cannot measure, so it
// is set directly. The two resource limits are measured below instead of
// guessed. The provisory fill writes placeholder limits so the message
// simulates at the same size it will be sent at.
const draftMessage = pipe(
createTransactionMessage({ version: 1 }),
(m) => setTransactionMessageFeePayerSigner(sender, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
(m) =>
appendTransactionMessageInstruction(
getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: lamports(10_000_000n),
}),
m,
),
(m) => setTransactionMessagePriorityFeeLamports(5_000n, m),
fillTransactionMessageProvisoryResourceLimits,
);
// Simulation runs with both limits raised to the runtime maximum, so it cannot
// fail for want of the resources it is measuring.
const estimateAndSetResourceLimits = estimateAndSetResourceLimitsFactory(
estimateResourceLimitsFactory({ rpc: client.rpc }),
);
const transactionMessage = await estimateAndSetResourceLimits(draftMessage, {
commitment: "confirmed",
});
console.log("Compute unit limit:", transactionMessage.config?.computeUnitLimit);
console.log(
"Loaded accounts data size limit:",
transactionMessage.config?.loadedAccountsDataSizeLimit,
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
assertIsTransactionWithBlockhashLifetime(signedTransaction);
await sendAndConfirmTransactionFactory({
rpc: client.rpc,
rpcSubscriptions: client.rpcSubscriptions,
})(signedTransaction, { commitment: "confirmed" });
// The estimate is the exact cost of one simulated run, with nothing to spare.
// Add a margin before relying on it in production.
console.log(
"Transaction Signature:",
getSignatureFromTransaction(signedTransaction),
);
Console
Click to execute the code.

V1:een valmistautuminen

v1 muuttaa transaktioiden lukemista, ei vain niiden lähettämistä. Jokainen asiakas, joka kutsuu getTransaction- tai getBlock-funktiota ilman käyttöönottoa, epäonnistuu v1-transaktioiden kanssa:

  • Välitä maxSupportedTransactionVersion: 1 — JSON-kokonaisluku 1, ei merkkijono "1" — kohteille getTransaction ja getBlock. Arvon 0 välittäminen epäonnistuu v1-transaktioilla täsmälleen samoin kuin parametrin pois jättäminen, joten v0-käyttöönoton yhteydessä päivitetty koodipohja tarvitsee silti arvon muuttamisen.
  • getTransaction epäonnistuu v1-transaktiolla virheellä -32015, ja yksikin v1- transaktio epäonnistuttaa koko getBlock-vastauksen samalla virheellä — osittaista tulosta ei ole.
  • blockSubscribe lähettää block: null ja lakkaa etenemästä, joten kuluttaja, joka tulkitsee sen tyhjäksi lohkoksi, jää hiljaisesti jälkeen ensimmäisestä v1 slot:sta alkaen.
  • getSignaturesForAddress ei koskaan tutki transaktioiden sisältöä, joten v1-allekirjoitukset näkyvät normaalisti.
  • Käyttöönotetut vastaukset sisältävät v1-transaktioiden viestissä transactionConfig-objektin (legacy- ja v0-transaktioissa sitä ei ole lainkaan). Putket, jotka johtavat prioriteettimaksut tai laskentarajat etsimällä ComputeBudget-ohjeita, raportoivat jokaisen v1-transaktion kohdalla hiljaa arvoksi nolla.
  • Käytä encoding: 'base64' dekoodatessasi transaktioita asiakaspuolella sekä sendTransaction-/simulateTransaction-kutsuissa yli 1 232 tavun transaktioille — base58-koodaus on edelleen rajattu vanhaan kokoon.
  • Asiakaskirjastotuki edellyttää tuoreita versioita: @solana/kit 8.0+, Agave 4.2.x-sukupolven Rust-paketit tai web3.js v3. web3.js v1 lukee v1:tä versiosta 1.99.0 eteenpäin, mutta ei pysty rakentamaan tai lähettämään sitä.

V1-transaktion lukeminen

Käyttöönotto tapahtuu JSON-kokonaisluvulla 1. Palautettujen tavujen dekoodaus antaa version ja asetukset, joissa resurssirajat sijaitsevat — ohjeluettelon läpikäynti ComputeBudget-ohjeiden etsimiseksi ei löydä mitään.

Kit
import {
decompileTransactionMessage,
getBase64Encoder,
getCompiledTransactionMessageDecoder,
getTransactionDecoder,
} from "@solana/kit";
// Reading a v1 transaction requires opting in. The integer 1 is mandatory:
// omitting the parameter, or passing 0, fails on a v1 transaction rather than
// returning it in a degraded form.
const fetched = await client.rpc
.getTransaction(transactionSignature, {
commitment: "confirmed",
encoding: "base64",
maxSupportedTransactionVersion: 1,
})
.send();
if (fetched === null) {
throw new Error("the transaction just sent was not found");
}
// The compiled message stores the config as a bitmask plus a positional list,
// so it is decompiled to address the fields by name. Decompiling fetches no
// accounts, because v1 does not support address lookup tables.
const wireTransaction = getBase64Encoder().encode(fetched.transaction[0]);
const transaction = getTransactionDecoder().decode(wireTransaction);
const compiledMessage = getCompiledTransactionMessageDecoder().decode(
transaction.messageBytes,
);
const message = decompileTransactionMessage(compiledMessage);
console.log("Version:", message.version);
if ("config" in message) {
// Scanning the instruction list for ComputeBudget instructions finds nothing
// here — on v1 the limits live in the config.
console.log("Compute unit limit:", message.config?.computeUnitLimit);
console.log("Priority fee (lamports):", message.config?.priorityFeeLamports);
}
Console
Click to execute the code.

Täydellisen siirto-oppaan — mukaan lukien asiakaskirjastojen tuki, streamauksen (Geyser/gRPC) versiontunnistus ja simuloinnin toiminta — löydät Transaction Format v1 -päivityssivulta. Suoritettavia esimerkkejä TypeScriptillä, Rustilla, Pythonilla ja Golla löytyy osoitteesta solana-foundation/transaction-v1-examples.

Is this page helpful?

© 2026 Solana Foundation. Kaikki oikeudet pidätetään.