Durable Nonce Fan-out voor SWQoS

Achtergrond en gebruiksscenario

Bij het verzenden van Solana-transacties veranderen slotvoortgang, leaderplaatsing en netwerk-padcongestie voortdurend welke route de leader het snelst bereikt. Dit is niet specifiek voor een RPC-provider of verzendservice; het is een structureel kenmerk van Solana's uitvoeringsmodel.
ERPC's SWQoS-endpoint biedt een verzendpad dat transacties injecteert in de prioriteitsbaan die leaders toewijzen op basis van Stake-weighted Quality of Service (SWQoS). Deze prioriteitsbandbreedte is ongeveer 5× die van de niet-prioriteitsbaan en wordt toegepast vóór de evaluatie van de priority fee.
Om die reden is het SWQoS-endpoint een belangrijke optie voor het verzenden van transacties, maar in productie is één endpoint niet altijd het snelst. Zelfs binnen hetzelfde slot kunnen tijdelijke padverschillen of een scheve belasting ervoor zorgen dat een ander snel endpoint voorligt.
Gezien deze kenmerken kan een operationeel patroon van het parallel verzenden van dezelfde transactie naar meerdere snelle paden en het accepteren van de eerste die wordt verwerkt effectief zijn, in plaats van te vertrouwen op een enkele route.
Wanneer dezelfde transactie naar meerdere endpoints wordt verzonden, kunt u echter zonder aanvullende controle niet garanderen dat deze maar één keer wordt uitgevoerd. Fan-out zonder deze controle kan leiden tot onbedoelde dubbele uitvoering of een ontregelde retry-controle.
Solana biedt Durable Nonce als mechanisme hiervoor. Het gebruik van Durable Nonce stelt u in staat dezelfde ondertekende transactie over meerdere routes te verzenden terwijl de on-chain uitvoering beperkt wordt tot een enkele keer.
Deze pagina legt uit hoe u fan-out-operaties implementeert die ERPC's SWQoS-endpoint combineren met andere snelle RPC-endpoints, uitgaande van transactieverzending met Durable Nonce.

Reikwijdte en vereisten

Deze handleiding behandelt het aanmaken van een Durable Nonce-account met web3.js en het gebruik ervan voor transactieverzending en fan-out-operaties.
Wat u vooraf moet weten:
  • Voor Durable Nonce-transacties gebruikt u de nonce-waarde als recentBlockhash en plaatst u nonceAdvance als eerste instructie
  • Zodra nonceAdvance wordt uitgevoerd, kan de nonce worden verbruikt, zelfs als latere instructies mislukken. U kunt dezelfde rawTx niet ongewijzigd opnieuw verzenden.
  • Het aanmaken van een nonce-account is eenmalig en wordt doorgaans hergebruikt

Stap 1: Bereid de nonce-autoriteit en verbinding voor

De nonce-autoriteit is een Keypair dat de nonce kan doorschuiven.
ts
import {
  Connection,
  Keypair,
  SystemProgram,
  NONCE_ACCOUNT_LENGTH,
} from '@solana/web3.js'

const connection = new Connection('https://<primary-rpc-endpoint>', 'confirmed')

const nonceAuthority = Keypair.fromSecretKey(/* secret key */)
  • De nonce-autoriteit kan nonceAdvance uitvoeren
  • Het kan de fee payer zijn, of een apart keypair

Stap 2: Genereer een Keypair voor het nonce-account

Een nonce-account is een System Account.
ts
const nonceAccount = Keypair.generate()
Dit Keypair wordt gebruikt voor:
  • Het bewaren van de nonce-waarde (vervanging voor recentBlockhash)
  • Alleen ondertekenen bij het aanmaken
  • Veilige opslag daarna; het is niet nodig voor dagelijkse verzendingen

Stap 3: Bereken het huurvrijstellingsminimum

Nonce-accounts moeten huurvrijgesteld zijn. Hardcode geen waarden; haal ze op via de RPC.
ts
const lamports =
  await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH)

Stap 4: Maak en initialiseer het nonce-account

Maak het nonce-account aan met createAccount + nonceInitialize in een enkele transactie.
ts
import { Transaction } from '@solana/web3.js'

const tx = new Transaction()

tx.add(
  SystemProgram.createAccount({
    fromPubkey: nonceAuthority.publicKey,
    newAccountPubkey: nonceAccount.publicKey,
    lamports,
    space: NONCE_ACCOUNT_LENGTH,
    programId: SystemProgram.programId,
  }),
  SystemProgram.nonceInitialize({
    noncePubkey: nonceAccount.publicKey,
    authorizedPubkey: nonceAuthority.publicKey,
  }),
)

// fee payer is the nonce authority
tx.feePayer = nonceAuthority.publicKey

// use a normal blockhash for initialization
const { blockhash, lastValidBlockHeight } =
  await connection.getLatestBlockhash('confirmed')

tx.recentBlockhash = blockhash

// sign with both the new account and the authority
tx.sign(nonceAccount, nonceAuthority)

const signature = await connection.sendRawTransaction(tx.serialize())
await connection.confirmTransaction(
  { signature, blockhash, lastValidBlockHeight },
  'confirmed',
)
Gebruik deze nonceAccount.publicKey voor volgende verzendingen.

Stap 5: Haal de nonce op voor elke verzending

Haal voor elke transactie de huidige nonce-waarde op.
ts
import { NonceAccount } from '@solana/web3.js'

const { value, context } = await connection.getAccountInfoAndContext(
  nonceAccount.publicKey,
  'confirmed',
)

if (!value) {
  throw new Error('Nonce account not found')
}

const nonce = NonceAccount.fromAccountData(value.data).nonce
const minContextSlot = context.slot
  • Gebruik nonce als recentBlockhash
  • context.slot wordt gebruikt bij bevestiging

Stap 6: Bouw de Durable Nonce-transactie

Durable Nonce-transacties moeten aan de volgende voorwaarden voldoen:
  • recentBlockhash = nonce
  • De eerste instructie is nonceAdvance
ts
import { TransactionMessage, VersionedTransaction } from '@solana/web3.js'

const instructions = [
  SystemProgram.nonceAdvance({
    noncePubkey: nonceAccount.publicKey,
    authorizedPubkey: nonceAuthority.publicKey,
  }),
  // add your real instructions after this
]

const message = new TransactionMessage({
  payerKey: nonceAuthority.publicKey,
  recentBlockhash: nonce,
  instructions,
}).compileToV0Message()

const tx = new VersionedTransaction(message)
tx.sign([nonceAuthority /* + other signers */])

const rawTx = tx.serialize()
Opmerkingen:
  • Als u ComputeBudget-instructies gebruikt, moeten deze na nonceAdvance komen
  • Als nonceAdvance niet als eerste staat, wordt de transactie afgewezen

Stap 7: Fan-out naar meerdere RPC's

Verzend dezelfde rawTx gelijktijdig.
ts
const endpoints = [
  'https://swqos-fra2.erpc.global?api-key=YOUR_API_KEY',
  'https://swqos-ams1.erpc.global?api-key=YOUR_API_KEY',
  'https://<backup-rpc-1>',
]

const results = await Promise.allSettled(
  endpoints.map((url) =>
    new Connection(url, 'confirmed').sendRawTransaction(rawTx, {
      skipPreflight: true,
      minContextSlot,
    }),
  ),
)

const success = results.find(
  (r): r is PromiseFulfilledResult<string> => r.status === 'fulfilled',
)

if (!success) {
  throw new Error('All sends failed')
}

const signature = success.value
  • De handtekening is identiek voor alle endpoints
  • Een succesvolle verzending is geen bevestiging

Stap 8: Bevestig met Durable Nonce

Bij Durable Nonce neemt u de nonce-info op in de bevestiging.
ts
await connection.confirmTransaction(
  {
    signature,
    nonceAccountPubkey: nonceAccount.publicKey,
    nonceValue: nonce,
    minContextSlot,
  },
  'confirmed',
)
Als de bevestiging slaagt:
  • De nonce schuift door
  • Kopieën die naar andere RPC's zijn verzonden, retourneren InvalidNonce

Volgende verzendingen

  • Haal na bevestiging een nieuwe nonce op
  • Hergebruik niet dezelfde rawTx of nonce-waarde
  • Gebruik voor parallelle workflows aparte nonce-accounts
Haal voor de volgende transactie de bijgewerkte nonce op en bouw de transactie met dezelfde stappen.