---
title: "SWQoS 的 Durable Nonce 扇出傳送"
description: "使用 web3.js 進行 Durable Nonce 賬戶設定和 SWQoS 扇出傳送"
canonical: "https://erpc.global/zh-tw/doc/rpc/durable-nonce-send-transaction/"
locale: "zh-tw"
---

# SWQoS 的 Durable Nonce 扇出傳送

## 背景和使用場景

在 Solana 交易傳送中，slot 進展、Leader 分佈和網路路徑擁塞不斷變化，決定著哪條路由最先到達 Leader。這並非特定於某個 RPC 提供商或傳送服務；這是 Solana 執行模型的結構性特徵。

ERPC 的 SWQoS 端點提供了一條傳送路徑，將交易注入到 Leader 基於質押加權服務品質（SWQoS）分配的優先通道中。此優先頻寬約為非優先通道的 5 倍，且在 Priority fee 評估之前應用。

因此，SWQoS 端點是交易傳送的重要選項，但在生產環境中，單一端點並非總是最快的。即使在同一個 slot 內，瞬時路徑差異或負載偏移也可能使另一個快速端點領先。

鑑於這些特性，將同一交易並行傳送到多個快速路徑、接受最先處理的那個的操作模式可能比依賴單一路由更有效。

然而，當同一交易被傳送到多個端點時，如果沒有額外控制，無法保證它只執行一次。沒有這種控制的扇出可能導致意外的重複執行或破壞重試控制。

Solana 為此提供了 Durable Nonce 機制。使用 Durable Nonce 可以讓您將同一已簽名交易傳送到多條路由，同時將鏈上執行限制為一次。

本頁面說明如何實現結合 ERPC 的 SWQoS 端點與其他快速 RPC 端點的扇出操作，假設使用 Durable Nonce 進行交易傳送。

---

## 範圍和前提條件

本指南涵蓋使用 web3.js 建立 Durable Nonce 賬戶，並將其用於交易傳送和扇出操作。

需要理解的前提條件：

- 對於 Durable Nonce 交易，使用 nonce 值作為 `recentBlockhash`，並將 `nonceAdvance` 作為第一條指令
- 一旦 `nonceAdvance` 執行，即使後續指令失敗，nonce 也可能被消耗。您不能原樣重新傳送相同的 rawTx。
- Nonce 賬戶建立是一次性設定，通常會重複使用

---

## 步驟 1：準備 nonce 授權和連線

nonce 授權是一個可以推進 nonce 的 Keypair。

```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 授權可以執行 `nonceAdvance`
- 它可以是手續費支付者，也可以是單獨的金鑰對

---

## 步驟 2：生成 nonce 賬戶金鑰對

nonce 賬戶是一個**系統賬戶**。

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

此金鑰對用於：

- 儲存 nonce 值（替代 `recentBlockhash`）
- 僅在建立時簽名
- 之後安全儲存；日常傳送不需要使用

---

## 步驟 3：計算免租最低餘額

Nonce 賬戶必須免租。不要硬編碼值；從 RPC 獲取。

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

---

## 步驟 4：建立並初始化 nonce 賬戶

在單個交易中使用 **createAccount + nonceInitialize** 建立 nonce 賬戶。

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

```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：構建 Durable Nonce 交易

Durable Nonce 交易必須滿足以下條件：

- `recentBlockhash = nonce`
- 第一條指令是 `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()
```

注意事項：

- 如果使用 ComputeBudget 指令，必須放在 `nonceAdvance` **之後**
- 如果 `nonceAdvance` 不是第一條指令，交易將被拒絕

---

## 步驟 7：扇出到多個 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
```

- 簽名在所有端點中相同
- 傳送成功**不等於**確認

---

## 步驟 8：使用 Durable Nonce 進行確認

使用 Durable Nonce 時，在確認中包含 nonce 資訊。

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

確認成功後：

- nonce 會推進
- 傳送到其他 RPC 的副本將返回 `InvalidNonce`

---

## 後續傳送

- 確認後，獲取新的 nonce
- 不要重用相同的 rawTx 或 nonce 值
- 對於並行工作流，使用獨立的 nonce 賬戶

對於下一筆交易，獲取更新後的 nonce 並使用相同步驟構建交易。
