---
title: "Durable Nonce Fan-out สำหรับ SWQoS"
description: "การตั้งค่าบัญชี Durable Nonce และการส่งแบบ fan-out สำหรับ SWQoS ด้วย web3.js"
canonical: "https://erpc.global/th/doc/rpc/durable-nonce-send-transaction/"
locale: "th"
---

# Durable Nonce Fan-out สำหรับ SWQoS

## ความเป็นมาและสถานการณ์การใช้งาน

ในการส่ง transaction บน Solana ความคืบหน้าของ slot ตำแหน่งของ leader และความแออัดของเส้นทางเครือข่าย ทำให้เส้นทางที่ไปถึง leader ได้เร็วที่สุดเปลี่ยนไปอยู่ตลอดเวลา เรื่องนี้ไม่ได้เกิดเฉพาะกับผู้ให้บริการ RPC หรือบริการส่ง transaction รายใดรายหนึ่ง แต่เป็นลักษณะเชิงโครงสร้างของโมเดลการประมวลผลของ Solana

SWQoS endpoint ของ ERPC เป็นเส้นทางส่งที่นำ transaction เข้าสู่เลนลำดับความสำคัญ ซึ่ง leader จัดสรรตาม Stake-weighted Quality of Service (SWQoS) แบนด์วิดท์ของเลนลำดับความสำคัญนี้มีขนาดประมาณ 5 เท่าของเลนที่ไม่มีลำดับความสำคัญ และมีผลก่อนการประเมิน priority fee

ด้วยเหตุนี้ SWQoS endpoint จึงเป็นตัวเลือกสำคัญสำหรับการส่ง transaction แต่ในการใช้งานจริง endpoint เดียวไม่ได้เร็วที่สุดเสมอไป แม้จะอยู่ใน slot เดียวกัน ความแตกต่างชั่วขณะของเส้นทางหรือภาระที่ไม่สมดุลก็อาจทำให้ endpoint ที่เร็วอีกตัวหนึ่งไปถึงก่อนได้

ด้วยลักษณะเหล่านี้ แทนที่จะพึ่งพาเส้นทางเดียว รูปแบบการใช้งานที่ส่ง transaction เดียวกันไปยังเส้นทางที่เร็วหลายเส้นทางพร้อมกัน แล้วใช้ผลของสำเนาที่ถูกประมวลผลก่อน ก็อาจได้ผลดี

อย่างไรก็ตาม เมื่อส่ง transaction เดียวกันไปยังหลาย endpoint หากไม่มีการควบคุมเพิ่มเติม คุณจะรับประกันไม่ได้ว่า transaction จะถูกดำเนินการเพียงครั้งเดียว การทำ fan-out โดยไม่มีการควบคุมนี้อาจทำให้เกิดการดำเนินการซ้ำสองครั้งโดยไม่ตั้งใจ หรือทำให้การควบคุม retry ใช้การไม่ได้

Solana มี Durable Nonce เป็นกลไกสำหรับเรื่องนี้ การใช้ Durable Nonce ช่วยให้คุณส่ง transaction ที่ลงนามแล้วตัวเดียวกันผ่านหลายเส้นทางได้ โดยจำกัดให้ถูกดำเนินการ on-chain เพียงครั้งเดียว

หน้านี้อธิบายวิธีทำ fan-out ที่ใช้ SWQoS endpoint ของ ERPC ร่วมกับ RPC endpoint ที่เร็วตัวอื่น โดยถือว่าส่ง transaction ด้วย Durable Nonce

---

## ขอบเขตและข้อกำหนดเบื้องต้น

คู่มือนี้ครอบคลุมการสร้างบัญชี Durable Nonce ด้วย web3.js และการนำไปใช้ส่ง transaction และทำ fan-out

สิ่งที่ควรเข้าใจก่อน:

- สำหรับ transaction แบบ Durable Nonce ให้ใช้ค่า nonce เป็น `recentBlockhash` และวาง `nonceAdvance` เป็น instruction แรก
- เมื่อ `nonceAdvance` ทำงานแล้ว nonce อาจถูกใช้ไปแม้ instruction ที่ตามมาจะล้มเหลว คุณจะส่ง rawTx เดิมซ้ำตามที่เป็นอยู่ไม่ได้
- บัญชี nonce สร้างเพียงครั้งเดียวตอนตั้งค่า และโดยทั่วไปจะนำบัญชีนั้นกลับมาใช้ซ้ำ

---

## ขั้นที่ 1: เตรียม nonce authority และ connection

nonce authority คือ Keypair ที่เลื่อน (advance) nonce ได้

```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 */)
```

- nonce authority สามารถเรียก `nonceAdvance` ได้
- จะเป็น fee payer หรือ keypair แยกต่างหากก็ได้

---

## ขั้นที่ 2: สร้าง Keypair ของบัญชี nonce

บัญชี nonce เป็น **System Account**

```ts
const nonceAccount = Keypair.generate()
```

Keypair นี้ใช้สำหรับ:

- เก็บค่า nonce (ใช้แทน `recentBlockhash`)
- ลงนามเฉพาะตอนสร้างเท่านั้น
- หลังจากนั้นให้เก็บไว้อย่างปลอดภัย ไม่จำเป็นต้องใช้ในการส่งประจำวัน

---

## ขั้นที่ 3: คำนวณยอดขั้นต่ำสำหรับ rent-exempt

บัญชี nonce ต้องเป็น rent-exempt อย่า hardcode ค่า ให้ดึงค่าจาก RPC

```ts
const lamports =
  await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH)
```

---

## ขั้นที่ 4: สร้างและ initialize บัญชี nonce

สร้างบัญชี nonce ด้วย **createAccount + nonceInitialize** ใน transaction เดียว

```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',
)
```

ใช้ `nonceAccount.publicKey` นี้ในการส่งครั้งต่อ ๆ ไป

---

## ขั้นที่ 5: ดึงค่า nonce ก่อนการส่งแต่ละครั้ง

ดึงค่า nonce ปัจจุบันสำหรับทุก transaction

```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
```

- ใช้ `nonce` เป็น `recentBlockhash`
- `context.slot` ใช้ในการยืนยัน

---

## ขั้นที่ 6: สร้าง transaction แบบ Durable Nonce

transaction แบบ Durable Nonce ต้องเป็นไปตามเงื่อนไขต่อไปนี้:

- `recentBlockhash = nonce`
- instruction แรกคือ `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()
```

หมายเหตุ:

- หากใช้ instruction ของ ComputeBudget ต้องวางไว้**หลัง** `nonceAdvance`
- หาก `nonceAdvance` ไม่ได้อยู่ลำดับแรก transaction จะถูกปฏิเสธ

---

## ขั้นที่ 7: Fan-out ไปยังหลาย RPC

ส่ง rawTx เดียวกันพร้อมกัน

```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
```

- signature จะเหมือนกันในทุก endpoint
- การส่งสำเร็จ**ไม่ได้**หมายถึงการยืนยัน

---

## ขั้นที่ 8: ยืนยันด้วย Durable Nonce

เมื่อใช้ Durable Nonce ให้ใส่ข้อมูล nonce ในการยืนยันด้วย

```ts
await connection.confirmTransaction(
  {
    signature,
    nonceAccountPubkey: nonceAccount.publicKey,
    nonceValue: nonce,
    minContextSlot,
  },
  'confirmed',
)
```

หากการยืนยันสำเร็จ:

- nonce จะเลื่อนไป (advance)
- สำเนาที่ส่งไปยัง RPC อื่นจะได้ผลลัพธ์ `InvalidNonce`

---

## การส่งครั้งถัดไป

- หลังยืนยันแล้ว ให้ดึงค่า nonce ใหม่
- อย่านำ rawTx หรือค่า nonce เดิมกลับมาใช้ซ้ำ
- สำหรับ workflow ที่ทำงานแบบขนาน ให้ใช้บัญชี nonce แยกกัน

สำหรับ transaction ถัดไป ให้ดึงค่า nonce ที่อัปเดตแล้ว และสร้าง transaction ตามขั้นตอนเดิม
