Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
集成与开发阅读约 11 分钟

加密钱包 API 架构:数据层、签名层与 RPC 层

一份实用的加密钱包 API 栈设计指南,将索引钱包数据与签名、RPC 访问分离,并提供可运行的验证步骤。

TL;DR

加密钱包 API 并不是单一产品。它通常由三个不同的层组成:索引数据 API,返回某个地址的余额、代币和交易历史;签名层,将私钥或密钥分片保留在您的控制之下;以及 RPC 层,读取链上状态并广播已签名交易。将这三者视为一个黑盒,是余额过期、广播失败和托管错误的常见根源。本文为您提供思维模型、每一层的可运行检查、可复现的测量流程以及故障排查步骤,并在选择因提供商而异时指向具体的端点选择。

加密钱包 API 实际涵盖的内容

加密钱包 API 这一说法至少用于三种不同的接口。钱包数据 API 接收一个地址并返回结构化 JSON:原生余额、代币持仓、NFT、转账记录,有时还包括 DeFi 头寸或盈亏数据。钱包即服务 API 创建钱包、管理密钥或密钥分片,并暴露发送和接收操作。RPC API 直接针对节点暴露 JSON-RPC 方法,例如 eth_getBalance、eth_getTransactionCount、eth_sendRawTransaction 和 eth_getTransactionReceipt。

这些层的故障方式各不相同。索引数据可能落后链头数秒或数个区块。签名可能因 nonce 重用、错误的链 ID 或密钥管理策略而失败。RPC 可能因速率限制、重组或节点落后而失败。如果您在单一提供商上构建钱包功能,却不清楚自己调用的是哪一层,就无法诊断正在发生的是哪种问题。

本文假设您正在构建一个显示钱包状态并提交交易的应用程序,并且希望将密钥材料保留在自己的控制之下。本文不假设特定的链或提供商。在具体端点选择很重要时,请使用 OnFinality RPC Assistant,而不是从博客文章中硬编码某个提供商。

  • 数据层:输入地址,输出资产组合和历史记录;通常经过索引和缓存。
  • 签名层:输入未签名交易,输出已签名交易;密钥永远不会离开您的边界。
  • RPC 层:链上读取和交易广播;最接近节点和链头。

集成前的先决条件和假设

您需要为支持的每条链准备一个 RPC 端点,为任何索引数据提供商准备 API 密钥或凭证,并做出密钥管理决策:由用户持有的非托管密钥、由您的后端在 KMS 或 HSM 中持有的密钥,或钱包即服务安排。每种选择都会改变您的合规范围和恢复方案。

您还需要决定真相来源。对于余额,链是权威的,但大规模查询成本高昂;索引器速度快,但最终一致。对于交易状态,来自 RPC 节点的收据是权威的,但您必须处理重组。在编写代码之前将这些决策写下来,因为它们决定了您在产品中每个屏幕调用哪个 API。

最后,明确确认链支持。不要假设数据提供商和 RPC 提供商支持相同的网络、测试网或归档深度。检查您计划上线的链的网络列表,并单独验证测试网可用性与主网可用性。

  • 每条链一个 RPC URL,存储在密钥管理器中,绝不放在客户端代码里。
  • 有文档记录的托管模型和密钥轮换流程。
  • 明确的新鲜度目标,例如 UI 上余额不超过 30 秒。
  • 重组策略:在将存款标记为最终之前需要多少个确认。

思维模型:将索引器、签名器和节点视为独立服务

想象钱包 UI 背后有三个服务。索引器订阅区块,解析日志和追踪,并写入以地址为键的行。签名器持有密钥并对序列化交易生成签名。节点回答 JSON-RPC 调用并将交易 gossip 给对等节点。

资产组合屏幕的读取路径是:UI → 您的后端 → 索引器。写入路径是:UI → 您的后端构建未签名交易 → 签名器签名 → 后端在节点上调用 eth_sendRawTransaction → 后端轮询 eth_getTransactionReceipt。保持这些路径分离意味着落后的索引器永远不会阻塞提现,而繁忙的节点永远不会破坏您显示的余额。

这种分离还允许您按层更换提供商。您可能使用一个供应商提供索引多链数据,而使用不同的 RPC 提供商进行低延迟广播。当您需要可预测的 RPC 容量而非共享公共端点时,OnFinality 网络目录和专用节点选项就很有用。

通过索引数据 API 读取钱包状态

大多数钱包数据 API 暴露类似 GET /addresses/{address}/balances 的 REST 结构或 GraphQL 查询。具体路径因供应商而异,因此请将以下内容视为需要适配的结构,而非字面契约。重要的是您对响应所做的断言:哪条链、哪个区块高度以及数据有多新鲜。

始终在余额旁请求或记录区块高度。没有它,您就无法判断缺失的代币是真实的零余额还是索引器尚未追上。如果提供商返回 synced_at 或 block_number 字段,请将其与响应一起存储,并在超过阈值时在 UI 中显示陈旧状态。

curl -s "https://WALLET_DATA_API_HOST/v1/addresses/0xYOUR_ADDRESS/balances?chain=ethereum" \
  -H "Authorization: Bearer $WALLET_DATA_API_KEY" \
  | jq '{block: .meta.block_number, synced_at: .meta.synced_at, tokens: [.balances[] | {symbol, amount, contract}]}'

直接通过 JSON-RPC 读取链上状态

当您需要链头的权威值时,调用节点。以下两个调用是标准的以太坊 JSON-RPC,适用于任何 EVM 兼容端点。将 RPC_URL 替换为您的提供商端点;切勿提交包含嵌入密钥的真实 URL。

将同一地址的 RPC 余额与索引余额进行比较。持续存在的差距通常意味着索引器落后、地址在您认为之外的链上,或者代币是合约余额而原生余额调用不包含它。

curl -s -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBalance","params":["0xYOUR_ADDRESS","latest"]}' | jq '.result'

curl -s -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"eth_blockNumber","params":[]}' | jq '.result'

在不暴露密钥的情况下构建和签名交易

安全模式是:从节点获取 nonce 和 gas 参数,在后端构建未签名交易,在密钥边界内签名,然后广播原始字节。签名器永远不应接收具有写访问权限的 RPC URL,RPC 凭证也永远不应能够导出密钥。

下面的 JavaScript 示例使用 ethers 在本地构建和签名。它从节点读取 nonce,使用从环境变量加载的密钥进行签名,并打印原始交易。在生产环境中,请将本地钱包替换为 KMS 或 HSM 签名器,并尽可能将 PRIVATE_KEY 排除在进程内存之外。

import { JsonRpcProvider, Wallet, parseEther } from "ethers";

const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY, provider);

const nonce = await provider.getTransactionCount(wallet.address, "pending");
const fee = await provider.getFeeData();

const tx = {
  to: "0xRECIPIENT_ADDRESS",
  value: parseEther("0.001"),
  nonce,
  chainId: (await provider.getNetwork()).chainId,
  maxFeePerGas: fee.maxFeePerGas,
  maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
  gasLimit: 21000n,
};

const raw = await wallet.signTransaction(tx);
console.log(raw);
// Broadcast separately: await provider.broadcastTransaction(raw);

验证广播并确认最终性

广播后,轮询 eth_getTransactionReceipt 直到返回带有区块号的收据,然后等待您的确认策略。status: 0x1 的收据表示交易成功执行;0x0 表示回滚且消耗了 gas。空收据表示节点尚未看到交易,这可能意味着传播延迟、交易被丢弃或 nonce 缺口。

记录交易哈希、区块号和首次收到收据的时间戳。该记录是您以后用来与索引器对账并回答支持问题的依据。如果收据从未出现,在重新提交之前,使用 pending 标签对照 eth_getTransactionCount 检查 nonce。

curl -s -X POST "$RPC_URL" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"eth_getTransactionReceipt","params":["0xYOUR_TX_HASH"]}' \
  | jq '{status: .result.status, block: .result.blockNumber, gasUsed: .result.gasUsed}'

针对钱包 API 栈的可复现测量流程

不要相信供应商对您自己工作负载的延迟声明。测量用户实际访问的路径。在固定时间窗口内,从与后端相同的区域,对每项检查至少运行 100 次,并记录 p50、p95 和 p99。将读取延迟(余额和历史记录)与写入延迟(广播到首次收据)分开。

使用一个为每个请求打时间戳的脚本,然后进行聚合。下表是您要填写的结构;数字必须来自您自己的运行,而不是本文。

  • 读取路径:对同一地址,计时数据 API 上的 GET /balances 和 RPC 端点上的 eth_getBalance。
  • 写入路径:从 eth_sendRawTransaction 到首次非空收据的时间。
  • 新鲜度:在同一时刻比较索引器报告的区块高度与 eth_blockNumber。
  • 错误率:分别统计 HTTP 429、5xx 和 JSON-RPC 错误对象。
| Path | p50 (ms) | p95 (ms) | p99 (ms) | Error rate | Notes |
|------|----------|----------|----------|------------|-------|
| Indexed balances | | | | | record block height |
| RPC eth_getBalance | | | | | same address |
| Broadcast to receipt | | | | | same chain, same fee policy |
| Indexer lag (blocks) | | | | | head minus indexed |

排查常见的钱包 API 故障

余额看起来错误或过时。 将索引器的区块高度与 RPC 链头进行比较。如果差距在扩大,索引器落后;如果差距为零但余额不同,检查您读取的是代币合约余额还是原生余额,以及地址是否校验和正确。

广播返回 nonce too low 或 replacement transaction underpriced。 具有相同 nonce 的另一笔交易已被挖出或正在待处理。使用 pending 查询 eth_getTransactionCount,如果您打算替换,请按节点执行的替换阈值提高费用。

收据从未出现。 交易可能未传播。将相同的原始字节重新广播到第二个端点,或检查区块浏览器。除非您打算替换第一笔交易,否则不要使用相同 nonce 签署新交易。

负载下出现 429 或速率限制错误。 共享公共端点会积极限流。将读取密集型路径迁移到索引数据 API,并为写入保留 RPC 容量。对于持续吞吐量,专用节点可消除吵闹邻居效应。

每一层的局限性和权衡

索引数据 API 快速便捷,但最终一致,且其对链、代币和 DeFi 协议的覆盖范围各不相同。它们还会集中您的依赖:索引器故障会使您的资产组合屏幕空白,即使链是健康的。通过为原生余额提供回退 RPC 读取并缓存最后已知良好响应来缓解。

RPC 端点具有权威性,但用于资产组合视图查询成本高昂,且历史余额的归档查询在非归档节点上可能缓慢或不可用。本地签名提供最强的托管方案,但将密钥管理、nonce 管理和费用估算的责任交给您的团队。钱包即服务转移了这种负担,但改变了谁持有密钥以及您能向审计师证明什么。

没有单一正确的划分。具有许多低价值读取的消费者应用应依赖索引数据。资金管理或交易所集成应将 RPC 收据视为真相来源,并仅将索引器用于显示。

生产钱包集成的后续步骤

首先写下您的托管模型和新鲜度目标。然后端到端搭建一条链:索引读取、RPC 读取、本地签名、广播、收据。在添加第二条链之前,使用上表测量该路径。多链支持会放大每种故障模式,因此请先证明单链路径。

当您准备选择 RPC 端点时,使用 RPC Assistant 获取每个网络的具体建议,而不是从文章中复制端点。查看 OnFinality 网络目录以确认链和测试网覆盖范围,在 RPC 定价页面比较计划,如果您的写入路径需要可预测的延迟,请考虑专用节点。对于构建更广泛 Web3 基础设施的团队,API 服务概览解释了托管 RPC 如何与您自己的服务配合。

  • 在运行手册中分别记录读取路径和写入路径。
  • 为任何来自索引器的余额添加陈旧指示器。
  • 对索引器延迟和收据延迟设置警报,而不仅仅是 HTTP 错误。
  • 在任何提供商或计划变更后重新运行测量流程。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始