Sürümlü işlemler

Özet

Solana'nın üç işlem formatı vardır: legacy, v0 ve v1. v0, hesaplara 1 baytlık indeksler aracılığıyla başvurmak için Adres Arama Tablolarını (ALT'lar) ekler. v1 boyut limitini 4.096 bayta çıkarır, kaynak limitlerini mesajın içine taşır ve ALT'ları kaldırır.

Solana üç işlem formatını destekler: legacy, v0 ve v1. Her biri aşağıda aynı üç başlıkla açıklanmaktadır: baytların kablo üzerinde nasıl düzenlendiği, hesaplara nasıl başvurulduğu ve kaynak limitlerinin nereden geldiği.

v1 etkinleştirme durumu

v1 formatı mainnet, devnet ve testnet'te etkindir. İşlemleri okuma biçimi geriye dönük uyumsuz bir değişikliktir: v1'i etkinleştirmeyen istemciler v1 işlemlerinde başarısız olur. Mevcut uygulamalar v1'e hazırlık bölümünü incelemelidir.

Format karşılaştırması

Limitlegacyv0v1
Maksimum işlem boyutu1.232 bayt1.232 bayt4.096 bayt
Hesap adresleri~32, boyutla sınırlı64, arama tabloları aracılığıyla64, satır içi (SIMD-0596 ile 96)
Adres arama tablolarıdesteklenmiyordestekleniyordesteklenmiyor
Kaynak limitleriComputeBudget talimatlarıComputeBudget talimatlarımesaj yapılandırması

Atla: Legacy · v0 · v1

Legacy format

Özgün format olup çoğu araçta hâlâ varsayılandır. Hiçbir sürüm ön ekine sahip değildir: işlemin ilk baytı imza dizisinin compact-u16 sayısıdır, mesajın ilk baytı ise yüksek biti her zaman sıfır olan num_required_signatures alanıdır.

Legacy kablo düzeni

AlanBoyutAçıklama
num_signaturescompact-u16İmza sayısı
signaturesnum_signatures x 64 baytEd25519 imzaları
header3 baytMessageHeader — ilk baytın sürüm biti sıfırdır
num_account_keyscompact-u16Hesap anahtarı sayısı
account_keysnum_account_keys x 32 baytOrtak anahtarlar, tamamı satır içi
recent_blockhash32 baytYaşam süresi belirleyici
num_instructionscompact-u16Talimat sayısı
instructionsdeğişkenHer talimat ardışık olarak serileştirilir

Her değişken uzunluklu dizi, compact-u16 uzunluk önekiyle başlar: 0–127 arası değerler için 1 bayt, daha büyük değerler için 2–3 bayt. Talimat başına düzen ve çalışılmış bir boyut hesabı için bkz. işlem ikili formatı.

Legacy'de hesaplar

Her hesap, account_keys içinde tam 32 baytlık bir ortak anahtar olarak yazılır; talimatlar bu diziye 1 baytlık indeksle başvurur. İşlemde açıkça belirtilmemiş bir hesaba başvurmanın yolu yoktur; bu durum, legacy işlemlerin 1.232 baytı dolmadan yaklaşık 32 hesapla sınırlanmasının nedenidir.

Legacy'de kaynak limitleri

Hesaplama birimi limiti, yüklenen hesap verisi boyutu limiti, yığın boyutu ve öncelik ücreti; işleme ComputeBudget program talimatları eklenerek talep edilir. Her biri bir talimat slot'u ve 150 hesaplama birimi tüketir. Bunları atlamak güvenlidir: çalışma zamanı varsayılan değerlere döner — talimat başına 200.000 CU (en fazla 1,4M), 64 MiB veri boyutu limiti, 32 KiB yığın ve sıfır öncelik ücreti.

V0 formatı

v0, bir legacy mesajına iki şey ekler: bir 0x80 sürüm ön eki baytı ve talimatların ardından eklenen bir address_table_lookups dizisi. Bunlardan önceki her şey legacy ile bayt düzeyinde aynıdır.

V0 kablo düzeni

AlanBoyutAçıklama
num_signaturescompact-u16İmza sayısı
signaturesnum_signatures x 64 baytEd25519 imzaları
0x801 baytSürüm ön eki baytı — mesajın ilk baytı
header3 baytMessageHeader (eski ile aynı)
num_account_keyscompact-u16Statik hesap anahtarı sayısı
static_account_keysnum_account_keys x 32 baytİşlemde doğrudan görünen anahtarlar
recent_blockhash32 baytYaşam süresi belirleyici
num_instructionscompact-u16Talimat sayısı
instructionsdeğişkenEski ile aynı format
address_table_lookupscompact-u16 + değişkenALT referansları (aşağıya bakın)

Her adres tablosu arama girişi şunları içerir:

AlanBoyutAçıklama
account_key32 baytALT hesabının genel anahtarı
writable_indexescompact-u16 + N x 1 baytYazılabilir hesaplar için ALT içindeki indeksler
readonly_indexescompact-u16 + N x 1 baytSalt okunur hesaplar için ALT içindeki indeksler

Adres arama tabloları

ALT, 256'ya kadar açık anahtar saklayan zincir üstü bir hesaptır. Bir ALT referans alınarak, bir işlem 32-baytlık açık anahtarlar yerine 1-baytlık indeksler kullanarak ek hesapları dahil edebilir ve böylece hesap başına yükü önemli ölçüde azaltır.

Çalışma zamanında, yürütme başlamadan önce, validator tüm ALT referanslarını tam genel anahtarlara çözümler. Çözümlenen adresler, tam hesap anahtarları listesini oluşturmak için statik hesap anahtarlarına eklenir. ALT ile çözümlenen hesaplar, statik hesaplarla aynı sıralamayı takip eder: yazılabilir aramalar, salt okunur aramalardan önce gelir.

Adres arama tabloları yalnızca hesapların hat üzerindeki işlemde nasıl referans alındığını etkiler. Yürütme zamanında, çalışma zamanı tüm indeksleri tam hesap adreslerine çözümler. ALT ile çözümlenen hesaplar yalnızca yazılabilir veya salt okunur (imzalayıcı olmayan) olabilir; imzalayıcı olamazlar.

Adres arama tablosu oluşturma ve kullanma

Bu örnek bir arama tablosu oluşturur, tabloya bir adres ekler ve tabloyu kullanarak bir v0 işlemini derleyip gönderir.

Bir arama tablosundan yalnızca imzalayıcı olmayan hesaplar yüklenebilir. Ücreti ödeyen hesap ve gerekli her imzalayıcı, mesajın statik hesap listesinde kalır. Ayrıca bir adres, eklendiği slot içinde aranamaz; bu nedenle örnek, v0 mesajını derlemeden önce bir sonraki onaylanmış slot'u bekler.

V0'da kaynak limitleri

Legacy'den farklı değildir: ComputeBudget talimatları kullanılır; atlandığında aynı varsayılan değerler geçerlidir.

V1 formatı

v1, boyut limitini 4.096 bayta çıkarır ve mesajı bir işlem yapılandırması etrafında yeniden düzenler: kaynak limitleri ComputeBudget talimatlarından çıkarılarak mesajın içindeki sabit konumlu alanlara taşınır. Bu sayede ağ, talimat listesini tarayıp seriden çıkarmak yerine tek bir sabit ofset okumasıyla bir işlemi öncelik ücretine göre sıralayabilir.

V1 kablo düzeni

AlanBoyutAçıklama
0x811 baytSürüm ön eki baytı — işlemin ilk baytı
header3 baytMessageHeader (eski ile aynı)
config_mask4 baytHangi yapılandırma değerlerinin mevcut olduğunu işaretleyen u32 LE bit maskesi
recent_blockhash32 baytYaşam süresi belirleyici
num_instructions1 baytSabit genişlikli sayı, en fazla 64
num_addresses1 baytSabit genişlikli sayı, en fazla 64 (SIMD-0596 ile 96)
addressesN x 32 baytHesap adresleri, tamamı satır içi — arama tablosu başvurusu yok
config_values0–20 baytAyarlı her bit maskesi için bir değer, bit sırasına göre (aşağıya bakın)
instruction_headersN x 4 baytTalimat başına: program_id_index (u8), num_accounts (u8), data_len (u16 LE)
instruction_payloadsdeğişkenTalimat başına: hesap indeksleri, ardından instruction data
signaturesN x 64 baytSonda yer alır, uzunluk öneki yoktur — sayı başlıktan alınır

Bir çözücü yazarken legacy ve v0'dan iki yapısal fark dikkat çeker. Sayılar compact-u16 yerine sabit genişlikli u8 alanlarıdır; talimatlar ise her talimatın ardışık olduğu yapı yerine iki ayrı bölüme ayrılır: önce tüm sabit boyutlu başlıklar, sonra tüm değişken uzunluklu payload'lar.

V1'de hesaplar: adres arama tablosu yok

v1, ALT desteğini bilinçli olarak kaldırır. 64 ham adres 2.048 bayttır; SIMD-0596 sonrasında izin verilen 96 adres ise 3.072 bayttır. İkisi de 4.096 baytlık sınırın rahatça içinde kaldığından her adres, legacy'de olduğu gibi satır içi yer alır. Uygulamanız arama tablolarına bağımlıysa v1'e geçmek, bu adresleri satır içine almak anlamına gelir.

V1'de kaynak limitleri: işlem yapılandırması

Yapılandırma, biti ayarlı her alan için sabit genişlikli değerlerin izlediği bir u32 bit maskesinden oluşur:

Bit(ler)AlanGenişlikNotlar
0–1Öncelik ücretiu64Toplam lamport — her iki bit birlikte ayarlanır
2Hesaplama birimi limitiu32
3Yüklenen hesap verisi boyutu limitiu32
4İstenen yığın boyutuu32

Bilinmeyen bitler reddedilir. Mesaj imzalandığından, tanınmayan yapılandırma alanları sessizce atılamaz.

Öncelik ücreti toplam lamport'tur, fiyat değil

Legacy ve v0'da öncelik ücreti, SetComputeUnitPrice aracılığıyla hesaplama birimi limiti ile çarpılan hesaplama birimi başına mikro-lamport olarak belirlenir. v1'de ise lamport cinsinden mutlak bir toplam olarak ifade edilir — çarpma işlemi yoktur, yuvarlama yoktur. CU başına aritmetiği taşımayın. Toplam ücret formülü bunun dışında değişmez: (signatures × lamports_per_signature) + priority_fee.

ComputeBudget talimatları v1'de işlemsizdir

Bir v1 işlemi ComputeBudget talimatlarını reddetmez — yapılandırma için onları yok sayar. Bu talimatlar bütçe üzerinde hiçbir etkisi olmaksızın 150 hesaplama birimi ve 64 talimat slot'undan birini tüketerek başarılı birer işlemsiz olarak yürütülmeye devam eder. v1 işlemleri oluştururken bunları çıkarın; v1 işlemlerini okurken onları aramayı bırakın: değerler mesaj yapılandırmasında yer alır.

Yapılandırma alanları açıkça ayarlanmalıdır

Gönderenler açısından en önemli davranış değişikliği şudur: legacy ve v0'ın aksine, v1 işlemlerinde hesaplama birimi limiti ve yüklenen hesap verisi boyutu limiti açıkça ayarlanmazsa işleminiz başarısız olur.

Ayarlanmamış alanlegacy / v0v1Atlandığında belirti
Hesaplama birimi limitiTalimat başına 200k, en fazla 1,4M0 CUHemen başarısız olur, bütçe aşıldı
Yüklenen hesap verisi boyutu64 MiB0 baytİlk yüklenen hesapta MaxLoadedAccountsDataSizeExceeded
Öncelik ücreti00—
Yığın boyutu32 KiB32 KiB—

Önerilen yaklaşım şudur: her iki limiti de maksimuma ayarlayarak bir kez simüle edin, ardından dönen unitsConsumed ve loadedAccountsDataSize değerlerini yapılandırmaya geri yazın; veri boyutunu biraz pay bırakmak amacıyla bir sonraki 32 KiB sayfasına yuvarlatın (blok maliyet modeli 32 KiB sayfa birimi üzerinden ücretlendirir, dolayısıyla sonraki sayfa sınırının altındaki pay ücretsizdir).

v1 işlemi oluşturma

v1 mesajı, legacy veya v0 mesajıyla aynı işlem hattı üzerinden oluşturulur. Fark setTransactionMessageConfig fonksiyonundadır: kaynak limitleri mesajın bir özelliğidir, dolayısıyla talimat listesi yalnızca program talimatlarını içerir.

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 ayrıca her yapılandırma alanı için ayrı bir ayarlayıcı sunar — setTransactionMessagePriorityFeeLamports, setTransactionMessageComputeUnitLimit, setTransactionMessageLoadedAccountsDataSizeLimit ve setTransactionMessageHeapSize — böylece değerleri tek tek ayarlayabilirsiniz.

Limitleri simülasyonla boyutlandırma

Zorunlu olan iki limiti tahmin etmek yerine ölçün. Simülasyon, her iki limit de çalışma zamanı maksimumuna yükseltilerek çalışır; bu nedenle ölçtüğü kaynaklar yetersiz kaldığı için başarısız olamaz. Döndürülen değer, simüle edilen tek bir çalıştırmanın tam maliyetidir; bu yüzden güvenmeden önce bir pay ekleyin.

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'e hazırlık

v1, işlemleri yalnızca göndermeyi değil okumayı da değiştirir. v1'i etkinleştirmeden getTransaction veya getBlock çağıran her istemci, v1 işlemlerinde başarısız olur:

  • getTransaction ve getBlock çağrılarına maxSupportedTransactionVersion: 1 — JSON tam sayısı 1, "1" dizesi değil — parametresini geçin. 0 değeri parametreyi atlamakla aynı şekilde v1 işlemlerinde başarısız olur; bu nedenle v0 kullanıma alınırken güncellenen bir kod tabanında değerin değiştirilmesi gerekir.
  • getTransaction, bir v1 işleminde -32015 hatasıyla başarısız olur; tek bir v1 işlemi, tüm getBlock yanıtını aynı hatayla başarısız kılar — kısmi sonuç döndürülmez.
  • blockSubscribe, block: null yayar ve ilerlemeyi durdurur; dolayısıyla bunu boş blok olarak okuyan bir tüketici, ilk v1 slot'undan itibaren sessizce geri kalmaya başlar.
  • getSignaturesForAddress işlem gövdelerini hiçbir zaman incelemez, bu nedenle v1 imzaları normal şekilde listelenir.
  • v1'i etkinleştirmiş yanıtlar, v1 işlemleri için mesajda bir transactionConfig nesnesi içerir (legacy ve v0 için hiç bulunmaz). Öncelik ücretlerini veya hesaplama limitlerini ComputeBudget talimatlarını tarayarak türeten işlem hatları, her v1 işlemi için sessizce sıfır raporlar.
  • İstemci tarafında işlem çözümlerken encoding: 'base64' kullanın; 1.232 baytı aşan işlemler için sendTransaction/simulateTransaction çağrılarında da aynısını yapın — base58 kodlaması eski boyut sınırında kalmaya devam eder.
  • İstemci kütüphanesi desteği için güncel sürümler gereklidir: @solana/kit 8.0+, Agave 4.2.x nesli Rust crate'leri veya web3.js v3. web3.js v1, 1.99.0 sürümünden itibaren v1'i okuyabilir ancak oluşturamaz veya gönderemez.

v1 işlemini okuma

v1'i etkinleştirmek için JSON tamsayısı 1 verilir. Döndürülen baytların kodu çözüldüğünde sürüm ve yapılandırma elde edilir; kaynak limitleri de buradadır — talimat listesinde ComputeBudget talimatlarını aramak hiçbir sonuç vermez.

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.

İstemci kitaplığı desteği, akış (Geyser/gRPC) sürüm algılama ve simülasyon davranışı dahil olmak üzere tam geçiş kılavuzu için İşlem Formatı v1 yükseltme sayfasına bakın. TypeScript, Rust, Python ve Go ile yazılmış çalıştırılabilir örnekler solana-foundation/transaction-v1-examples deposunda yer alır.

Is this page helpful?

© 2026 Solana Vakfı. Tüm hakları saklıdır.