---
title: "SWQoS 的 Durable Nonce 扇出发送"
description: "使用 web3.js 进行 Durable Nonce 账户设置和 SWQoS 扇出发送"
canonical: "https://erpc.global/zh/doc/rpc/durable-nonce-send-transaction/"
locale: "zh"
---

# 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 并使用相同步骤构建交易。
