Skip to content

快速开始 ​

本页用 Node.js(18+,无第三方依赖)走完一遍最小闭环:创建 API Key → 签名第一个请求 → 注册回调 → 获取充值地址 → 接收充值回调 → 发起提现。其他语言的签名实现见 认证与签名。

1. 创建 API Key ​

API Key 在租户后台 → API Keys 页面创建(需 OWNER 角色)。平台只保存公钥;私钥由你方生成和保管,平台无法找回。

方式 A:在浏览器中生成密钥对(适合测试)。点击“创建”后浏览器在本地生成 Ed25519 密钥对,只把公钥提交给服务器,私钥以 PKCS#8 PEM 文件只下载一次。

方式 B:使用自有公钥(生产推荐,私钥不经过浏览器)。在你的业务服务器上执行:

bash
# 生成 Ed25519 私钥(PKCS#8 PEM),妥善保存
openssl genpkey -algorithm ed25519 -out waas-api-key.pem
# 导出 32 字节原始公钥的 hex(64 个字符),粘贴到控制台“Ed25519 公钥(hex)”
openssl pkey -in waas-api-key.pem -pubout -outform DER | tail -c 32 | xxd -p -c 64

创建时还可填写:

字段说明
名称1–100 个字符,便于识别
允许的 IP每行一个 IP 或 CIDR(如 203.0.113.10、203.0.113.0/24、IPv6 亦可);留空 = 不限 IP。生产环境强烈建议填写
每秒请求上限1–1000,默认 50;超出返回 429 RATE_LIMITED

创建成功后得到 Key ID(形如 ak_ + 24 个十六进制字符),它就是请求头 X-API-Key 的值。若租户开启了配置变更双人复核,提交后需另一位管理员批准,批准后才能在列表中看到 Key ID。

吊销 Key 后,用它签名的请求立即返回 401 UNAUTHORIZED。

2. 签名第一个请求 ​

把 认证与签名 中的 Node.js 客户端保存为 waas-client.mjs,然后:

bash
export WAAS_BASE_URL=https://api.example.com
export WAAS_API_KEY=ak_0123456789abcdef01234567
export WAAS_PRIVATE_KEY_PEM=./waas-api-key.pem
js
// first.mjs
import { call } from "./waas-client.mjs";

const assets = await call("GET", "/v1/supported_assets");
for (const a of assets) {
  console.log(a.assetId, a.networkKey, a.decimals, "minDeposit", a.minDeposit, "withdrawFee", a.withdrawFeeRaw);
}

返回 200 和资产数组即表示签名正确。如果返回 401,按 签名错误排查 逐项检查。

3. 注册回调地址 ​

js
const endpoint = await call("POST", "/v1/webhooks", {
  url: "https://exchange.example.com/waas/webhook",
  events: [],              // 空数组 = 订阅全部事件
  description: "prod",
});
const { publicKey } = await call("GET", "/v1/webhooks/public_key"); // 用于验签,可缓存
await call("POST", `/v1/webhooks/${endpoint.id}/test`);             // 触发一条 WEBHOOK_TEST

接收端必须先验签再处理,见 回调 · 签名验证。

4. 获取充值地址 ​

用你方的用户 ID 作为 customerRefId,一次调用即可:平台自动创建用户(若不存在)和该网络所属链族的默认钱包,并返回地址。

js
const ref = "u-10001";
const r = await call("POST", `/v1/users/${encodeURIComponent(ref)}/deposit_address`, { networkKey: "ethereum" });
// { customerRefId, uid, walletId, networkKey, chainFamily: "EVM", address: "0x…", status: "READY" }

若 status 为 PROVISIONING(address 为 null),说明地址仍在分配中,几秒后用同样的参数再调用一次即可(接口幂等,地址不会变)。同一用户在所有 EVM 网络上的地址相同。

5. 接收充值回调 ​

用户转账到该地址后,平台会依次推送:

  1. TRANSACTION_CREATED:data.status = CONFIRMING(链上已发现,等待确认);
  2. TRANSACTION_STATUS_UPDATED:data.status = COMPLETED(已入账)。
js
// 在已验签的回调处理函数中
if (event.type === "TRANSACTION_STATUS_UPDATED" && event.data.kind === "DEPOSIT" && event.data.status === "COMPLETED" && !event.data.subStatus) {
  const { id, destination, assetId, netAmountRaw, txHash } = event.data;
  // 以 data.id 为唯一键入账:给 destination.customerRefId 增加 netAmountRaw(已扣除充值手续费)
}

注意 COMPLETED 但 subStatus 不为 null(如 BELOW_MIN_DEPOSIT:低于最小充值额或不高于充值手续费)的充值没有入账,不要给用户加钱。详见 充值。

6. 发起提现 ​

js
const walletId = r.walletId;
// 可选:先估算手续费
const fee = await call("POST", "/v1/transactions/estimate_fee", {
  assetId: "USDT_ERC20", sourceWalletId: walletId, amountRaw: "10000000",
  destination: "0x90f79bf6eb2c4f870365e785982e1f101e93b906",
});
// 发起提现:externalTxId = 你方提现单号(同时作为幂等键,重试时保持请求体完全一致)
const tx = await call("POST", "/v1/transactions", {
  assetId: "USDT_ERC20",
  source: { type: "WALLET", id: walletId },
  destination: { type: "ONE_TIME_ADDRESS", oneTimeAddress: { address: "0x90f79bf6eb2c4f870365e785982e1f101e93b906" } },
  amountRaw: "10000000",
  externalTxId: "wd-20261003-000001",
});
// HTTP 202:{ settlement: "ON_CHAIN", transactionId, withdrawalId, status: "SUBMITTED", ... }

之后通过 TRANSACTION_STATUS_UPDATED 回调或 GET /v1/transactions/external_tx_id/wd-20261003-000001 跟踪到终态(COMPLETED / FAILED / REJECTED / CANCELLED)。如果目标地址是本租户另一个用户的充值地址,平台会自动改为站内结算,响应中 settlement = "OFF_CHAIN"。详见 提现与站内转账。

下一步 ​

  • 阅读 通用约定:金额、分页、幂等、限流、错误体。
  • 阅读 交易状态,确认你的状态机覆盖了所有终态。
  • 对照 错误码 实现重试策略(只重试 retryable = true 的错误)。