地址转账监听 · 使用手册
版本:2.0.0 | 适用对象:接入方开发者
目录
一、产品概述
简介
地址转账监听服务面向需要跟进钱包地址在链上转账行为的业务场景:服务持续接收并解析多链交易数据,将相关转账结构化后落库,并通过 HTTP 与 WebSocket 将结果推送给已订阅的应用。
设计目标:接入方无需自建节点或自研解析流水线,只需维护监听地址与连接,即可感知指定地址的链上转账与相关交易信息。
核心能力
| 能力 | 说明 |
|---|---|
| 多链交易监听 | 统一接入 EVM 系、Solana、Tron、Bitcoin 等多条链 |
| 实时推送 | 通过 WebSocket 将链上交易实时推送给订阅方 |
| 历史查询 | 通过 HTTP 接口查询地址的历史交易记录 |
| 地址订阅管理 | 动态增删监听地址,支持多应用隔离 |
| Webhook 通知 | 将交易数据推送到接入方配置的 HTTP 回调地址 |
| 系统监控 | 提供健康检查、版本查询和运行指标接口 |
支持的链
EVM 兼容链
| 链 | chain_type | 主网 chain_id |
|---|---|---|
| Ethereum | ethereum | 1 |
| BNB Smart Chain | bsc | 56 |
| Polygon | polygon | 137 |
| Arbitrum One | arbitrum | 42161 |
| Optimism | optimism | 10 |
| Base | base | 8453 |
非 EVM 链
| 链 | chain_type | 主网 chain_id |
|---|---|---|
| Solana | solana | 1 |
| Tron | tron | 1 |
| Bitcoin | bitcoin | 1 |
说明:
chain_type在请求时大小写不敏感,服务端会自动归一化为小写。
整体数据流
flowchart TD
UPSTREAM[链上数据推送<br/>Stream / Webhook]
API[接入方系统]
CL_STREAM[POST /stream<br/>POST /webhook]
PROCESSOR[链处理器<br/>EVM / Solana / Tron / Bitcoin]
DB[(数据库)]
WS_DO[SubscriptionManager<br/>Durable Object]
WS_CLIENT[WebSocket 客户端]
WEBHOOK_OUT[出站 Webhook 回调]
CRON[Cron 定时任务<br/>每分钟]
UPSTREAM --> CL_STREAM
CL_STREAM --> PROCESSOR
PROCESSOR --> DB
PROCESSOR --> WS_DO
WS_DO -->|实时推送| WS_CLIENT
WS_DO -->|Webhook 通知| WEBHOOK_OUT
API -->|POST /api/v1/transfer| DB
API -->|GET /transaction/transaction/tx| DB
CRON -->|补偿 pending 交易| DB
数据流说明:
- 已配置的链上数据推送将原始数据送达
/stream或/webhook入口(推流接入由服务方部署与授权,无需接入方对接上游基础设施) - 服务根据链类型将数据分发给对应的处理器解析
- 解析后的交易数据写入数据库,同时触发订阅通知
- 已订阅地址的交易通过 WebSocket 实时推送给连接方
- 接入方可通过 HTTP 接口主动查询历史交易
- Cron 任务每分钟运行一次,对未完成的 pending 交易进行补偿处理
核心概念
链标识:chain_type 与 chain_id
每条链通过两个字段唯一标识:
chain_type:链的类型名称,例如ethereum、solana、tronchain_id:链 ID,区分主网与测试网,例如以太坊主网为1,Sepolia 测试网为11155111
这两个字段在所有涉及链的接口中均会用到。
应用标识:app_id
本服务支持多个业务方接入,通过 应用标识(App ID) 实现数据隔离。每个业务方有独立的地址订阅列表和 WebSocket 连接管理。
传递方式(二选一,请求头优先级更高):
- 请求头:
X-BaseSeverAppID: your-app-id - 查询参数:
?app_id=your-app-id
涉及地址管理和 WebSocket 连接的接口均需提供应用标识。
统一响应格式
大多数接口的响应遵循以下 JSON 结构:
{
"code": 0,
"message": "ok",
"data": {}
}
| 字段 | 类型 | 说明 |
|---|---|---|
code | integer | 业务状态码,0 表示成功 |
message | string | 响应消息,成功时通常为 ok |
data | any | 业务数据,失败时为 null |
注意:部分历史接口的消息字段名为
msg而非message,接入时请以具体接口文档为准。
错误响应格式
请求失败时,响应结构如下:
{
"code": 400,
"msg": "Invalid request",
"data": null
}
常见 HTTP 状态码:
| 状态码 | 含义 |
|---|---|
400 | 请求参数校验失败 |
401 | 鉴权失败(推流密钥错误) |
403 | 无权限(管理员密钥错误) |
504 | 请求超时(超过 15 秒) |
500 | 服务内部错误 |
鉴权方式
本服务针对不同场景使用不同的鉴权方式:
推流密钥(Content-Secret-X)
用于签名推流入口 /stream,防止未授权数据写入(由服务方在推流侧配置,一般接入方无需关注)。
Content-Secret-X: your-stream-secret
管理员密钥(X-API-Key)
用于 /v1/system/metrics 等管理类接口,需要配置 ADMIN_KEY 环境变量。
X-API-Key: your-admin-key
应用标识(X-BaseSeverAppID)
用于地址管理和 WebSocket 连接,标识调用方的业务身份。
X-BaseSeverAppID: your-app-id
支持的网络标识(x-chain-net)
在调用链上 JSON-RPC 代理接口(见下文 /api/v1/quicknode)时,需通过 x-chain-net 请求头指定目标网络:
| 网络 | x-chain-net 值 |
|---|---|
| 以太坊主网 | ethereum-mainnet |
| 以太坊 Sepolia | ethereum-sepolia |
| BNB 主网 | bsc-mainnet |
| BNB 测试网 | bsc-testnet |
| Polygon 主网 | polygon-mainnet |
| Polygon Amoy | polygon-amoy |
| Solana 主网 | solana-mainnet |
| Solana Devnet | solana-devnet |
| Tron 主网 | tron-mainnet |
| Bitcoin 主网 | bitcoin-mainnet |
| Arbitrum 主网 | arbitrum-mainnet |
| Optimism 主网 | optimism-mainnet |
| Base 主网 | base-mainnet |
二、快速开始
本节帮助你在约 10 分钟内完成基本接入,实现针对监听地址的链上交易实时通知与查询。
接入前准备
在开始之前,请向服务方获取以下凭证:
| 凭证 | 用途 | 示例 |
|---|---|---|
| App ID | 标识你的业务应用,用于地址管理和 WebSocket 连接 | my-app-001 |
| Admin Key | 访问系统指标等管理接口(可选) | sk-admin-xxx |
将服务的 基础 URL 记录好,后续所有示例中用 BASE_URL 代替。
BASE_URL=https://api.gelabs.org
APP_ID=your-app-id
典型接入流程
flowchart LR
A[验证连通性] --> B[添加监听地址]
B --> C[建立 WebSocket 连接]
C --> D[接收实时交易推送]
B --> E[查询历史交易]
第一步:验证服务连通性
首先通过 Ping 接口确认服务可访问:
curl "$BASE_URL/ping"
预期响应:
{
"message": "pong"
}
也可以通过健康检查接口确认服务和数据库均正常:
curl "$BASE_URL/v1/system/health"
预期响应:
{
"code": 0,
"message": "ok",
"data": {
"status": "healthy",
"version": "2.0.0",
"ts": 1745000000000
}
}
第二步:添加监听地址
将需要监听的钱包地址添加到你的应用地址名单。以下示例添加一个以太坊地址:
curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-BaseSeverAppID: $APP_ID" \
-d '{
"wallets": [
{
"chain_type": "ethereum",
"address": ["0x1234567890abcdef1234567890abcdef12345678"]
}
]
}'
预期响应:
{
"code": 0,
"message": "ok",
"data": "success"
}
可以同时添加多条链的地址:
curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-BaseSeverAppID: $APP_ID" \
-d '{
"wallets": [
{
"chain_type": "ethereum",
"address": ["0xabc...001", "0xabc...002"]
},
{
"chain_type": "solana",
"address": ["So1ana...address"]
}
]
}'
第三步:建立 WebSocket 连接,接收实时推送
地址添加成功后,即可建立 WebSocket 连接开始接收实时交易:
使用 wscat 快速测试
# 安装 wscat(如果尚未安装)
npm install -g wscat
# 建立连接
wscat -c "$BASE_URL/transaction/transaction/ws" \
-H "X-BaseSeverAppID: $APP_ID"
连接成功后,服务端会立即发送一条确认消息:
{
"id": "msg-xxx",
"time": 1745000000000,
"type": "connected",
"code": 0,
"data": {
"clientId": "client-abc123",
"appId": "my-app-001"
},
"msg": "success"
}
接收实时交易
当监听的地址产生链上交易时,服务端会主动推送:
{
"id": "msg-tx-001",
"time": 1745000000000,
"type": "transaction",
"code": 0,
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"txhash": "0xabc123...",
"sender": "0x1234...",
"receiver": "0x5678...",
"amount": "1000000000000000000",
"symbol": "ETH",
"decimals": 18,
"txStatus": "success",
"listenAddress": ["0x1234..."],
"is_reorg_resend": false
},
"msg": "success"
}
收到交易消息后,建议发送 ACK 确认,以避免服务端重发:
{
"id": "ack-001",
"type": "transactionACK",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"txHash": "0xabc123..."
}
}
第四步:查询历史交易
除实时推送外,也可以通过 HTTP 接口查询某个地址的历史交易:
curl -X POST "$BASE_URL/api/v1/transfer" \
-H "Content-Type: application/json" \
-d '{
"chain_type": "ethereum",
"chain_id": 1,
"address": "0x1234567890abcdef1234567890abcdef12345678",
"limit": 10
}'
预期响应(简略):
{
"code": 1,
"msg": "success",
"data": [
{
"chain_type": "ethereum",
"chain_id": 1,
"tx_hash": "0xabc123...",
"sender": "0x1234...",
"receiver": "0x5678...",
"amount": "1000000000000000000",
"symbol": "ETH",
"tx_status": "success"
}
]
}
完整接入示例(Node.js)
const WebSocket = require('ws');
const BASE_URL = 'https://api.gelabs.org';
const APP_ID = 'your-app-id';
const ADDRESS = '0x1234567890abcdef1234567890abcdef12345678';
async function addAddress() {
const res = await fetch(`${BASE_URL}/api/v1/address/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-BaseSeverAppID': APP_ID,
},
body: JSON.stringify({
wallets: [{ chain_type: 'ethereum', address: [ADDRESS] }],
}),
});
const data = await res.json();
console.log('添加地址结果:', data);
}
function connectWebSocket() {
const ws = new WebSocket(
`${BASE_URL.replace('https', 'wss')}/transaction/transaction/ws`,
{ headers: { 'X-BaseSeverAppID': APP_ID } }
);
ws.on('open', () => {
console.log('WebSocket 连接已建立');
setInterval(() => {
ws.send(JSON.stringify({ id: Date.now().toString(), type: 'ping', data: {} }));
}, 30000);
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw);
switch (msg.type) {
case 'connected':
console.log('连接确认,clientId:', msg.data.clientId);
break;
case 'transaction':
console.log('收到交易:', msg.data.txhash);
ws.send(JSON.stringify({
id: `ack-${msg.id}`,
type: 'transactionACK',
data: { chain_type: msg.data.chain_type, chain_id: msg.data.chain_id, txHash: msg.data.txhash },
}));
break;
case 'pong':
break;
}
});
ws.on('close', () => {
console.log('连接断开,5 秒后重连...');
setTimeout(connectWebSocket, 5000);
});
ws.on('error', (err) => {
console.error('WebSocket 错误:', err.message);
});
}
async function main() {
await addAddress();
connectWebSocket();
}
main();
三、系统模块
系统模块提供服务的基础运维接口,包括连通性检查、健康状态、版本查询、运行指标,以及与链上数据通道相关的 WebSocket 管理。
接口列表
| 方法 | 路径 | 说明 | 是否需要鉴权 |
|---|---|---|---|
| GET | /ping | 连通性检查 | 否 |
| GET | /v1/system/health | 服务健康状态 | 否 |
| GET | /v1/system/version | 服务版本 | 否 |
| GET | /v1/system/metrics | 运行指标 | 是(Admin Key) |
| POST | /v1/system/external-ws/connect | 触发链上数据通道 WebSocket 重连 | 否 |
| GET | /v1/system/external-ws/status | 查询链上数据通道 WebSocket 状态 | 否 |
GET /ping
用途
最简单的连通性检查接口。可用于探活、负载均衡健康检查或确认服务进程存活。
示例
curl "$BASE_URL/ping"
响应
{
"message": "pong"
}
说明:此接口响应结构不遵循标准
{ code, message, data }格式,直接返回{ "message": "pong" },适合作为最轻量的健康探针使用。
GET /v1/system/health
用途
执行完整的健康检查,包括对数据库的连通性验证。返回服务当前状态、版本号和服务端时间戳。
示例
curl "$BASE_URL/v1/system/health"
响应(服务正常)
{
"code": 0,
"message": "ok",
"data": {
"status": "healthy",
"version": "2.0.0",
"ts": 1745000000000
}
}
响应(数据库不可用)
HTTP 状态码 500:
{
"code": 500,
"message": "Database unhealthy",
"data": {
"status": "unhealthy",
"version": "2.0.0",
"ts": 1745000000000
}
}
响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
status | string | healthy 表示服务正常;unhealthy 表示数据库不可用 |
version | string | 当前服务版本号 |
ts | integer | 服务端生成时间,毫秒级 Unix 时间戳 |
GET /v1/system/version
用途
返回服务的静态版本号。
示例
curl "$BASE_URL/v1/system/version"
响应
{
"code": 0,
"message": "ok",
"data": {
"version": "2.0.0"
}
}
GET /v1/system/metrics
用途
查询服务的内部运行指标,包括交易总数、订阅地址数和待处理交易数量。支持 JSON 和 Prometheus 两种输出格式。
鉴权
需要鉴权:请求头必须包含
X-API-Key,且值等于服务配置的ADMIN_KEY。
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
format | query | string | 否 | 输出格式,可选值:json(默认)、prometheus |
X-API-Key | header | string | 是 | 管理员密钥 |
示例
# JSON 格式
curl "$BASE_URL/v1/system/metrics" \
-H "X-API-Key: your-admin-key"
# Prometheus 格式
curl "$BASE_URL/v1/system/metrics?format=prometheus" \
-H "X-API-Key: your-admin-key"
响应(JSON 格式)
{
"code": 0,
"message": "ok",
"data": {
"transactions": 1250000,
"subscriptions": 3482,
"pendingTransactions": 12,
"timestamp": 1745000000000
}
}
响应(Prometheus 格式)
# TYPE chain_listen_transactions gauge
chain_listen_transactions 1250000
# TYPE chain_listen_subscriptions gauge
chain_listen_subscriptions 3482
# TYPE chain_listen_pending_transactions gauge
chain_listen_pending_transactions 12
说明:指标名中的
chain_listen_*前缀为历史工程命名,与对外产品名称无关。
响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
transactions | integer | 历史交易总数 |
subscriptions | integer | 订阅地址总数 |
pendingTransactions | integer | 状态为 pending 的待处理交易数 |
timestamp | integer | 指标采集时间,毫秒级 Unix 时间戳 |
POST /v1/system/external-ws/connect
用途
主动触发服务与链上数据订阅通道建立(或重建)WebSocket 连接。
说明:连接是异步建立的,接口返回仅代表"连接流程已触发"。建议调用后通过
/v1/system/external-ws/status确认连接状态。
示例
curl -X POST "$BASE_URL/v1/system/external-ws/connect"
响应
{
"code": 0,
"message": "ok",
"data": "Connection initiated"
}
GET /v1/system/external-ws/status
用途
查询服务与链上数据订阅通道之间的 WebSocket 连接状态。
示例
curl "$BASE_URL/v1/system/external-ws/status"
响应
{
"code": 0,
"message": "ok",
"data": {
"status": "connected"
}
}
status 可选值:connected(已连接)、disconnected(已断开)。
四、数据流入模块
数据流入模块负责接收经授权推送至本服务的链上原始数据(Stream / Webhook),并将其分发给对应的链处理器解析与入库。推流侧网络与供应商由平台维护,接入方只需使用文档中的对外接口。
接口列表
| 方法 | 路径 | 说明 | 是否需要鉴权 |
|---|---|---|---|
| POST | /stream | 签名推流主入口(生产推荐) | 是(推流密钥) |
| POST | /webhook | 兼容模式 Webhook 入口 | 否 |
| POST | /api/v1/quicknode | 链上 JSON-RPC 代理 | 否(需指定链网络头) |
数据处理流程
flowchart LR
IN[推流请求] --> PARSE[解析批次元数据<br/>链类型 / 网络]
PARSE --> PROC[链处理器\nEVM / Solana / Tron / Bitcoin]
PROC --> DB[(数据库写入)]
PROC --> NOTIFY[触发订阅通知]
NOTIFY --> WS[WebSocket 推送]
NOTIFY --> WH[Webhook 回调]
处理是异步的。服务收到推流请求后会立即返回响应,实际的解析和写库工作在后台执行。
POST /stream
用途
接收经授权推送的原始链上数据负载。这是生产环境中链上数据的主要入口之一。
鉴权
需要鉴权:请求必须携带推流密钥请求头,否则返回
401。
Content-Secret-X: your-stream-secret
链网络识别
服务通过以下请求头识别推流数据所属的链网络(按优先级顺序):
cl-stream-networkstream-network
示例
curl -X POST "$BASE_URL/stream" \
-H "Content-Secret-X: your-stream-secret" \
-H "cl-stream-network: ethereum-mainnet" \
-H "Content-Type: application/octet-stream" \
--data-binary @payload.bin
响应
{
"code": 0,
"message": "ok",
"data": null
}
POST /webhook
用途
历史兼容的 Webhook 入口,与 /stream 处理逻辑相同。与 /stream 的主要区别:
- 无推流密钥校验:当前版本默认不校验
Content-Secret-X - 适用场景:历史兼容 Webhook、或内部测试场景
注意:生产环境建议优先使用
/stream并配合密钥校验,以防止未授权数据注入。
示例
curl -X POST "$BASE_URL/webhook" \
-H "cl-stream-network: ethereum-mainnet" \
-H "Content-Type: application/octet-stream" \
--data-binary @payload.bin
响应
{
"code": 0,
"message": "ok",
"data": null
}
POST /api/v1/quicknode(链上 JSON-RPC 代理)
用途
将 JSON-RPC 请求按所选网络代理至对应的链上 RPC 端点,可用于查询区块、余额等标准方法(路径中的 quicknode 为历史兼容命名,与具体供应商无关)。
必要请求头
必须指定链网络:请求头
x-chain-net不能缺失,否则服务返回500错误。
x-chain-net: ethereum-mainnet
请求体字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
method | string | 否 | eth_blockNumber | JSON-RPC 方法名 |
params | array | 否 | [] | JSON-RPC 参数数组 |
示例
# 查询最新区块号
curl -X POST "$BASE_URL/api/v1/quicknode" \
-H "Content-Type: application/json" \
-H "x-chain-net: ethereum-mainnet" \
-d '{"method": "eth_blockNumber", "params": []}'
# 查询账户余额
curl -X POST "$BASE_URL/api/v1/quicknode" \
-H "Content-Type: application/json" \
-H "x-chain-net: ethereum-mainnet" \
-d '{"method": "eth_getBalance", "params": ["0x1234...", "latest"]}'
响应
{
"code": 1,
"msg": "success",
"data": "0x14a5b8c"
}
注意:此接口成功时
code为1,与其他接口的0不同,接入时请注意判断。
支持的网络列表
| 网络 | x-chain-net / cl-stream-network 值 |
|---|---|
| 以太坊主网 | ethereum-mainnet |
| 以太坊 Sepolia 测试网 | ethereum-sepolia |
| BNB Smart Chain 主网 | bsc-mainnet |
| BNB Smart Chain 测试网 | bsc-testnet |
| Polygon 主网 | polygon-mainnet |
| Polygon Amoy 测试网 | polygon-amoy |
| Solana 主网 | solana-mainnet |
| Solana Devnet | solana-devnet |
| Tron 主网 | tron-mainnet |
| Bitcoin 主网 | bitcoin-mainnet |
| Arbitrum One 主网 | arbitrum-mainnet |
| Optimism 主网 | optimism-mainnet |
| Base 主网 | base-mainnet |
五、交易查询模块
交易查询模块提供对链上交易数据的多维度查询能力,支持按地址查询历史列表、批量查询交易详情,以及跨链聚合查询。
接口列表
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/transfer | 按地址查询交易列表(游标分页) |
| POST | /api/v1/transfer/multiple | 多链多地址聚合查询 |
| POST | /api/v1/transfer/details | 按交易哈希批量查询详情 |
| GET | /transaction/transaction/tx | 查询单笔交易详情 V2 |
POST /api/v1/transfer
用途
按单个地址查询其在指定链上的历史交易列表,支持按交易类型、代币过滤,使用游标方式分页。
请求体字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
address | string | 是 | - | 待查询的钱包地址 |
chain_type | string | 否 | - | 链类型,不填则跨链查询 |
chain_id | integer | 否 | - | 链 ID |
tx_type | string | 否 | "" | 交易类型过滤,空字符串表示不过滤 |
token | string | 否 | - | 代币合约地址,用于过滤特定代币的转账 |
limit | integer | 否 | 20 | 每次返回的最大记录数,范围 1~100 |
id | integer | 否 | - | 游标 ID,用于分页续查 |
示例
# 基本查询
curl -X POST "$BASE_URL/api/v1/transfer" \
-H "Content-Type: application/json" \
-d '{
"chain_type": "ethereum",
"chain_id": 1,
"address": "0x1234567890abcdef1234567890abcdef12345678",
"limit": 20
}'
# 按代币过滤(查询 USDT 转账)
curl -X POST "$BASE_URL/api/v1/transfer" \
-H "Content-Type: application/json" \
-d '{
"chain_type": "ethereum",
"chain_id": 1,
"address": "0x1234...",
"token": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
}'
响应
{
"code": 1,
"msg": "success",
"data": [
{
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"tx_time": 1745000000,
"tx_hash": "0xabc123...",
"sender": "0x1234...",
"receiver": "0x5678...",
"amount": "1000000000000000000",
"symbol": "ETH",
"decimals": 18,
"tx_status": "success",
"transfer_type": "native",
"token_transfers": [],
"from_details": [],
"to_details": [],
"internal_transactions": []
}
]
}
注意:此接口成功时
code为1,消息字段为msg,与其他接口略有不同。
POST /api/v1/transfer/multiple
用途
同时查询多条链、多个地址的交易记录,结果聚合后统一返回。
请求体字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
addresses | array | 是 | - | 多链地址分组列表,至少一项,最多 10 组 |
addresses[].chain_type | string | 是 | - | 链类型 |
addresses[].chain_id | integer | 是 | - | 链 ID |
addresses[].address | string[] | 是 | - | 该链下的地址数组,每组最多 20 个地址 |
limit | integer | 否 | 20 | 返回的最大记录数,范围 1~100 |
id | integer | 否 | - | 游标 ID,用于分页续查 |
示例
curl -X POST "$BASE_URL/api/v1/transfer/multiple" \
-H "Content-Type: application/json" \
-d '{
"addresses": [
{"chain_type": "ethereum", "chain_id": 1, "address": ["0xabc...001", "0xabc...002"]},
{"chain_type": "bsc", "chain_id": 56, "address": ["0xdef...003"]},
{"chain_type": "solana", "chain_id": 1, "address": ["SolanaAddress..."]}
],
"limit": 20
}'
响应
与 /api/v1/transfer 格式相同,data 为聚合后的交易记录数组。
POST /api/v1/transfer/details
用途
根据交易哈希批量查询交易详情。
请求体字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chain_type | string | 是 | 链类型 |
chain_id | integer | 是 | 链 ID |
tx_hash | string 或 string[] | 是 | 单个交易哈希或哈希数组 |
示例
# 查询单笔交易
curl -X POST "$BASE_URL/api/v1/transfer/details" \
-H "Content-Type: application/json" \
-d '{"chain_type": "ethereum", "chain_id": 1, "tx_hash": "0xabc123..."}'
# 批量查询多笔交易
curl -X POST "$BASE_URL/api/v1/transfer/details" \
-H "Content-Type: application/json" \
-d '{"chain_type": "ethereum", "chain_id": 1, "tx_hash": ["0xabc123...", "0xdef456..."]}'
GET /transaction/transaction/tx
用途
查询单笔交易的完整详情(V2 版本)。与 /api/v1/transfer/details 的主要区别:
- 采用 GET 请求,参数通过 Query String 传递
- 支持
no_cache参数强制回源:当数据库中无记录时,可按链实时拉取并写入缓存 - 回源成功后会自动将数据写入数据库缓存
- 返回的数据结构采用混合字段命名(历史兼容格式,部分字段为 camelCase)
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
chain_type | query | string | 是 | 链类型 |
chain_id | query | integer | 是 | 链 ID |
tx_hash | query | string | 是 | 交易哈希,支持带或不带 0x 前缀 |
no_cache | query | string | 否 | 传 true 或 1 时跳过本地缓存直接回源 |
示例
# 正常查询
curl "$BASE_URL/transaction/transaction/tx?chain_type=ethereum&chain_id=1&tx_hash=0xabc123..."
# 强制回源查询
curl "$BASE_URL/transaction/transaction/tx?chain_type=ethereum&chain_id=1&tx_hash=0xabc123...&no_cache=true"
响应(找到交易)
{
"code": 0,
"msg": "success",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"txTime": 1745000000000,
"txhash": "0xabc123...",
"sender": "0x1234...",
"fee_payer": "0x1234...",
"receiver": "0x5678...",
"transfer_type": "native",
"gasLimit": "21000",
"gasUsed": "21000",
"gasPrice": "20000000000",
"max_fee_per_gas": "30000000000",
"max_priority_fee_per_gas": "1000000000",
"txFee": "420000000000000",
"nonce": "42",
"symbol": "ETH",
"decimals": 18,
"amount": "1000000000000000000",
"txStatus": "success",
"methodId": "",
"methodCall": "",
"l1OriginHash": "",
"fromDetails": [],
"toDetails": [],
"internalTransactionDetails": [],
"tokenTransferDetails": [],
"listenAddress": null,
"is_reorg_resend": false
}
}
响应(交易不存在)
{
"code": 0,
"msg": "success",
"data": null
}
交易数据结构详解
TransactionDetail(列表查询返回格式)
/api/v1/transfer、/api/v1/transfer/multiple、/api/v1/transfer/details 接口返回的交易格式。
主体字段:
| 字段 | 类型 | 说明 |
|---|---|---|
chain_type | string | 归一化后的链类型,小写 |
chain_id | integer | 链 ID |
height | integer | null | 区块高度 |
tx_time | integer | null | 交易时间,秒级 Unix 时间戳 |
tx_hash | string | 交易哈希 |
tx_version | integer | null | 交易版本号(Solana 等使用) |
sender | string | null | 发送方地址 |
fee_payer | string | null | 手续费支付方地址(Solana 场景) |
receiver | string | null | 接收方地址 |
transfer_type | string | null | 交易类型,如 native(原生转账)、token(代币转账) |
amount | string | null | 交易金额,字符串格式避免精度丢失 |
symbol | string | null | 主币或代币符号,如 ETH、BNB |
decimals | integer | null | 精度位数,如 18 |
tx_status | string | null | 交易状态:success、failed、pending |
tx_fee | string | null | 交易手续费 |
gas_limit | string | null | Gas 上限(EVM) |
gas_used | string | null | 实际消耗 Gas(EVM) |
gas_price | string | null | Gas 单价(EVM) |
max_fee_per_gas | string | null | EIP-1559 最大 Gas 单价 |
max_priority_fee_per_gas | string | null | EIP-1559 优先费单价 |
nonce | string | null | 账户 Nonce(EVM) |
method_id | string | null | 合约调用方法选择器(EVM) |
method_call | string | null | 合约方法名或调用摘要 |
l1_origin_hash | string | null | 二层链对应的 L1 原始交易哈希 |
token_transfers | array | 代币转账明细列表 |
from_details | array | 输入地址明细(UTXO 模型) |
to_details | array | 输出地址明细(UTXO 模型) |
internal_transactions | array | 内部交易或 Solana 指令明细 |
token_transfers(代币转账明细):
| 字段 | 类型 | 说明 |
|---|---|---|
from_address | string | null | 代币转出地址 |
to_address | string | null | 代币转入地址 |
token_contract_address | string | null | 代币合约地址 |
symbol | string | null | 代币符号 |
amount | string | null | 转账数量 |
decimals | integer | null | 代币精度 |
is_from_contract | boolean | 转出方是否为合约地址 |
is_to_contract | boolean | 转入方是否为合约地址 |
from_token_address | string | null | 源 token 账户地址(Solana) |
to_token_address | string | null | 目标 token 账户地址(Solana) |
mint | string | null | Solana Mint 地址 |
program_id | string | null | Solana Program ID |
from_details(输入地址明细,UTXO 模型):
| 字段 | 类型 | 说明 |
|---|---|---|
address | string | null | 输入地址 |
amount | string | null | 输入金额 |
is_contract | boolean | 是否为合约地址 |
vin_index | string | null | UTXO 输入索引 |
pre_vout_index | string | null | 前序交易输出索引 |
ref_tx_hash | string | null | 前序引用交易哈希 |
to_details(输出地址明细,UTXO 模型):
| 字段 | 类型 | 说明 |
|---|---|---|
address | string | null | 输出地址 |
amount | string | null | 输出金额 |
is_contract | boolean | 是否为合约地址 |
vout_index | string | null | UTXO 输出索引 |
internal_transactions(内部交易 / Solana 指令):
| 字段 | 类型 | 说明 |
|---|---|---|
from_address | string | null | 内部调用发送方 |
to_address | string | null | 内部调用接收方 |
amount | string | null | 内部调用金额 |
tx_status | string | null | 内部调用状态 |
program_id | string | null | Solana Program ID |
instr_index | integer | null | Solana 指令索引 |
depth | integer | null | Solana 调用深度 |
instruction | string | null | Solana 指令名称 |
description | string | null | 指令说明 |
V2 接口字段命名差异
/transaction/transaction/tx 接口返回的格式采用混合命名风格(历史兼容),与列表查询格式有所不同:
| V2 格式字段 | 对应列表格式字段 | 主要差异 |
|---|---|---|
txhash | tx_hash | 字段名不同 |
txTime | tx_time | camelCase,且为毫秒级时间戳 |
txStatus | tx_status | camelCase |
txFee | tx_fee | camelCase |
gasLimit | gas_limit | camelCase |
gasUsed | gas_used | camelCase |
gasPrice | gas_price | camelCase |
methodId | method_id | camelCase |
methodCall | method_call | camelCase |
l1OriginHash | l1_origin_hash | camelCase |
fromDetails | from_details | camelCase |
toDetails | to_details | camelCase |
internalTransactionDetails | internal_transactions | 字段名不同 |
tokenTransferDetails | token_transfers | 字段名不同 |
分页说明
交易列表查询使用游标(Cursor)分页而非传统的页码分页:
- 首次请求时不传
id参数,获取最新的 N 条记录 - 如果返回记录数量等于
limit,说明可能还有更多数据 - 取返回数组中最后一条记录的数据库内部
id传给下一次请求的id参数 - 继续请求,直到返回记录数量少于
limit为止
注意:游标分页不支持跳页,只能顺序向前翻页。
金额处理建议
所有金额字段均以字符串形式返回,以避免大数精度问题。展示时需结合 decimals 字段进行换算:
function formatAmount(amount, decimals) {
if (!amount || decimals === null) return '0';
const bigAmount = BigInt(amount);
const divisor = BigInt(10 ** decimals);
const intPart = bigAmount / divisor;
const fracPart = bigAmount % divisor;
return `${intPart}.${fracPart.toString().padStart(decimals, '0')}`;
}
// 示例:1000000000000000000 (decimals=18) → "1.000000000000000000"
console.log(formatAmount('1000000000000000000', 18));
六、地址管理模块
地址管理模块用于维护每个业务应用的监听地址名单。只有添加到名单中的地址,其链上交易才会被推送到对应应用的 WebSocket 订阅者或 Webhook 回调。
接口列表
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/address | Patch 模式:同时增删地址 |
| POST | /api/v1/address/add | 批量添加地址 |
| POST | /api/v1/address/remove | 批量移除地址 |
| POST | /api/v1/address/contains | 查询地址是否存在于名单 |
应用标识说明
所有地址管理接口均需要通过应用标识指定操作所属的业务方,二选一方式传递:
| 方式 | 说明 | 示例 |
|---|---|---|
| 请求头 | X-BaseSeverAppID(优先级更高) | X-BaseSeverAppID: my-app-001 |
| 查询参数 | app_id | ?app_id=my-app-001 |
注意:如果均未提供,服务返回
400错误。
POST /api/v1/address/add
用途
向指定应用的监听地址名单中批量添加地址。支持同时为多条链添加地址。
链特殊处理行为
- Tron 链:Tron 格式地址(以
T开头)会自动转换为等价的 EVM 格式(0x...)存储。 - 其他非 EVM 链:会尝试将地址同步到平台内部监听组件;同步失败不会阻断本地入库。
请求体字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
wallets | array | 是 | 按链分组的地址列表,至少一项 |
wallets[].chain_type | string | 是 | 链类型,如 ethereum、solana、tron |
wallets[].address | string[] | 是 | 该链下的地址数组,至少一个地址 |
示例
# 添加以太坊地址
curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-BaseSeverAppID: my-app-001" \
-d '{
"wallets": [
{
"chain_type": "ethereum",
"address": ["0x1234...", "0xabcd..."]
}
]
}'
# 同时添加多链地址
curl -X POST "$BASE_URL/api/v1/address/add" \
-H "Content-Type: application/json" \
-H "X-BaseSeverAppID: my-app-001" \
-d '{
"wallets": [
{"chain_type": "ethereum", "address": ["0x1234..."]},
{"chain_type": "tron", "address": ["TJDENsfBJs4RFETt1X1uyT9pBxdnAbFnbm"]},
{"chain_type": "solana", "address": ["SolanaAddress..."]}
]
}'
响应
{
"code": 0,
"message": "ok",
"data": "success"
}
POST /api/v1/address/remove
用途
从指定应用的监听地址名单中批量移除地址。移除后,该地址的新交易将不再推送给该应用。
请求体字段说明
与 /api/v1/address/add 相同:wallets[].chain_type + wallets[].address。
示例
curl -X POST "$BASE_URL/api/v1/address/remove" \
-H "Content-Type: application/json" \
-H "X-BaseSeverAppID: my-app-001" \
-d '{
"wallets": [
{"chain_type": "ethereum", "address": ["0x1234..."]}
]
}'
响应
{
"code": 0,
"message": "ok",
"data": "success"
}
POST /api/v1/address
用途
以 Patch 模式同时对某条链执行地址的增删操作,在一次请求中完成地址名单的部分更新。
请求体字段说明
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
chain_type | string | 是 | - | 链类型,此接口一次只操作一条链 |
adds | string[] | 否 | [] | 要加入名单的地址列表 |
removes | string[] | 否 | [] | 要移出名单的地址列表 |
示例
curl -X POST "$BASE_URL/api/v1/address" \
-H "Content-Type: application/json" \
-H "X-BaseSeverAppID: my-app-001" \
-d '{
"chain_type": "ethereum",
"adds": ["0xNewAddress001...", "0xNewAddress002..."],
"removes": ["0xOldAddress001..."]
}'
响应
{
"code": 0,
"message": "ok",
"data": "success"
}
POST /api/v1/address/contains
用途
查询某个地址是否已在指定链的地址名单中。
说明:当前版本此接口只验证地址在全局地址表中是否存在,不按 app_id 隔离过滤。
请求体字段说明
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chain_type | string | 是 | 链类型 |
address | string | 是 | 待查询的地址 |
示例
curl -X POST "$BASE_URL/api/v1/address/contains" \
-H "Content-Type: application/json" \
-H "X-BaseSeverAppID: my-app-001" \
-d '{"chain_type": "ethereum", "address": "0x1234..."}'
响应
{
"code": 0,
"message": "ok",
"data": {
"is_exist": true
}
}
地址管理最佳实践
地址格式规范:
- EVM 链:使用
0x开头的 Hex 格式地址,建议使用 EIP-55 checksum 格式 - Solana:使用 Base58 格式地址
- Tron:使用 Base58Check 格式(以
T开头);服务会自动转换为0x格式存储 - Bitcoin:使用标准 Bitcoin 地址格式(P2PKH、P2SH、Bech32 等)
地址名单与 WebSocket 订阅的关系:
- 通过
/api/v1/address/add添加的地址会被持久化到数据库,应用重启后仍然有效 - WebSocket 连接建立后,服务会自动监听该应用名单中的所有地址
- 也可以在 WebSocket 连接建立后,通过
subscribe命令动态追加监听,但这种方式仅对当前连接有效,断线后不会自动恢复
推荐使用 HTTP 接口管理地址名单,WebSocket 的 subscribe/unsubscribe 命令仅用于临时的细粒度控制。
七、WebSocket 实时推送
WebSocket 模块提供链上交易的实时推送能力。客户端建立 WebSocket 连接后,服务会在监听地址产生链上交易时立即推送消息。
连接端点:
GET /transaction/transaction/ws
建立连接
请求说明
WebSocket 握手需要以下请求头:
| 请求头 | 类型 | 必填 | 说明 |
|---|---|---|---|
Upgrade | string | 是 | 固定值 websocket |
X-BaseSeverAppID | string | 与 app_id 二选一 | 应用标识,优先级高于查询参数 |
X-ResendDuration | string | 否 | 历史消息重发时间窗口(秒),默认 180 |
注意:应用标识至少需要提供一个,否则连接请求返回
400。
连接示例
# 使用 wscat
wscat -c "wss://api.gelabs.org/transaction/transaction/ws" \
-H "X-BaseSeverAppID: my-app-001"
# 使用查询参数
wscat -c "wss://api.gelabs.org/transaction/transaction/ws?app_id=my-app-001"
连接成功消息
连接建立后,服务端立即推送:
{
"id": "msg-uuid-001",
"time": 1745000000000,
"type": "connected",
"code": 0,
"data": {
"clientId": "client-abc123",
"appId": "my-app-001"
},
"msg": "success"
}
| 字段 | 说明 |
|---|---|
data.clientId | 当前连接在服务端的唯一标识,断线重连后会变化 |
data.appId | 当前连接绑定的应用标识 |
连接失败
| HTTP 状态码 | 原因 | 处理建议 |
|---|---|---|
400 | 未提供应用标识 | 确认已正确传入应用标识 |
426 | Upgrade: websocket 头缺失 | 确认使用 WebSocket 协议连接 |
消息格式
所有 WebSocket 消息均使用 JSON 格式,遵循以下统一结构:
{
"id": "消息唯一 ID",
"time": 1745000000000,
"type": "消息类型",
"code": 0,
"data": {},
"msg": "success"
}
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 消息唯一 ID,由发送方生成 |
time | integer | 消息时间戳,毫秒级 Unix 时间戳 |
type | string | 消息类型,决定消息的业务含义 |
code | integer | 业务状态码,0 表示成功,非 0 表示错误 |
data | object | null | 消息业务数据 |
msg | string | 文本消息,成功时通常为 success |
客户端命令
客户端可以向服务端发送以下命令。每条命令都需要提供唯一的 id 字段,服务端的回执会使用相同的 id 值。
ping(心跳)
发送心跳以保持连接活跃。
发送:
{
"id": "ping-001",
"type": "ping",
"data": {}
}
服务端回执:
{
"id": "ping-001",
"time": 1745000000000,
"type": "pong",
"code": 0,
"data": null,
"msg": "success"
}
subscribe(订阅地址)
订阅指定地址在指定链上的实时交易推送。
说明:通过此命令订阅的地址仅对当前连接有效,连接断开后不会保存。如需持久化地址订阅,请使用地址管理接口提前将地址加入名单。
发送:
{
"id": "cmd-001",
"type": "subscribe",
"data": {
"address": "0x1234567890abcdef1234567890abcdef12345678",
"chain_type": "ethereum"
}
}
命令参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
data.address | string | 是 | 待订阅的地址 |
data.chain_type | string | 是 | 链类型,服务端会转为小写 |
成功回执:
{
"id": "cmd-001",
"type": "subscribe",
"code": 0,
"data": {"app_id": "my-app-001", "address": "0x1234...", "chain_type": "ethereum"},
"msg": "success"
}
错误回执:
{
"id": "cmd-001",
"type": "subscribe",
"code": 400,
"data": null,
"msg": "address and chain_type required"
}
unsubscribe(取消订阅)
取消对指定地址的实时交易订阅。
发送(取消特定链):
{
"id": "cmd-002",
"type": "unsubscribe",
"data": {
"address": "0x1234...",
"chain_type": "ethereum"
}
}
发送(取消所有链,省略 chain_type):
{
"id": "cmd-002",
"type": "unsubscribe",
"data": {
"address": "0x1234..."
}
}
命令参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
data.address | string | 是 | 待取消订阅的地址 |
data.chain_type | string | 否 | 链类型,不填则取消该地址所有链的订阅 |
成功回执:
{
"id": "cmd-002",
"type": "unsubscribe",
"code": 0,
"data": {"address": "0x1234...", "chainType": "ethereum"},
"msg": "success"
}
取消所有链时,chainType 为 null。
getTxHash(查询交易详情)
通过 WebSocket 连接查询单笔交易的完整详情。
发送:
{
"id": "cmd-003",
"type": "getTxHash",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"txHash": "0xabc123..."
}
}
命令参数:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
data.chain_type | string | 是 | 链类型 |
data.chain_id | integer | 是 | 链 ID |
data.txHash | string | 否 | 交易哈希 |
data.messageId | string | 否 | 实时推送消息的唯一 ID,优先使用此字段 |
建议:优先使用
messageId(即推送消息的id字段值)查询,服务端可更精确地定位记录。
错误回执(交易不存在):
{
"id": "cmd-003",
"type": "getTxHash",
"code": 404,
"data": null,
"msg": "transaction not found"
}
transactionACK(确认已处理交易)
客户端收到 transaction 消息并成功处理后,应发送 ACK 确认。服务端收到 ACK 后会停止对该笔交易的重发逻辑。
重要:如果客户端长时间未发送 ACK,服务端可能在客户端重连后重新推送未确认的交易,以确保消息不丢失。
发送:
{
"id": "ack-001",
"type": "transactionACK",
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"txHash": "0xabc123..."
}
}
成功回执:
{
"id": "ack-001",
"type": "transactionACK",
"code": 0,
"data": {"txHash": "0xabc123..."},
"msg": "success"
}
服务端推送消息
transaction(实时交易推送)
当监听地址产生链上交易时,服务端主动推送:
{
"id": "msg-tx-uuid-001",
"time": 1745000000000,
"type": "transaction",
"code": 0,
"data": {
"chain_type": "ethereum",
"chain_id": 1,
"height": 21000000,
"txTime": 1745000000000,
"txhash": "0xabc123...",
"sender": "0x1234...",
"fee_payer": "0x1234...",
"receiver": "0x5678...",
"transfer_type": "native",
"gasLimit": "21000",
"gasUsed": "21000",
"gasPrice": "20000000000",
"max_fee_per_gas": "30000000000",
"max_priority_fee_per_gas": "1000000000",
"txFee": "420000000000000",
"nonce": "42",
"symbol": "ETH",
"decimals": 18,
"amount": "1000000000000000000",
"txStatus": "success",
"methodId": "",
"methodCall": "",
"fromDetails": [],
"toDetails": [],
"internalTransactionDetails": null,
"tokenTransferDetails": null,
"listenAddress": ["0x1234..."],
"is_reorg_resend": false
},
"msg": "success"
}
交易消息关键字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
data.listenAddress | string[] | 命中监听规则的地址列表,一笔交易可能命中多个地址 |
data.is_reorg_resend | boolean | 是否为链重组后的重发消息,true 时需检查本地记录 |
data.tokenTransferDetails | array | null | 代币转账明细列表,ERC-20 转账时包含具体转账信息 |
data.txTime | integer | 交易时间戳,毫秒级 |
is_reorg_resend 说明:
当值为 true 时,表示此交易因**链重组(Reorg)**被重新推送。建议检查本地是否已存在该 txhash 的记录,并根据需要更新区块高度等信息。
ERC-20 代币转账消息示例:
{
"id": "msg-tx-002",
"type": "transaction",
"code": 0,
"data": {
"chain_type": "ethereum",
"txhash": "0xdef456...",
"transfer_type": "token",
"tokenTransferDetails": [
{
"from": "0x1234...",
"to": "0x5678...",
"isFromContract": false,
"isToContract": false,
"tokenContractAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"symbol": "USDT",
"amount": "1000000000",
"decimals": 6,
"fromTokenAddress": "",
"toTokenAddress": "",
"mint": "",
"programId": ""
}
],
"listenAddress": ["0x1234..."],
"is_reorg_resend": false
},
"msg": "success"
}
HTTP 辅助接口
GET /transaction/transaction/ws/status
查询指定应用的 WebSocket 连接状态。
curl "$BASE_URL/transaction/transaction/ws/status" \
-H "X-BaseSeverAppID: my-app-001"
应用标识读取顺序:X-BaseSeverAppID 头 → app_id 参数 → 默认值 default。
响应:
{
"code": 0,
"message": "ok",
"data": {
"appId": "my-app-001",
"connectedClients": 2,
"clients": ["client-abc123", "client-def456"],
"details": [
{
"clientId": "client-abc123",
"connectedAt": 1745000000000,
"uptime": 3600,
"pendingTxCount": 0
}
]
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
connectedClients | integer | 当前在线客户端数量 |
details[].uptime | integer | 已存活秒数 |
details[].pendingTxCount | integer | 尚未收到 ACK 的待确认交易数量 |
POST /transaction/transaction/ws/replay
手动重放一条漏发的交易消息。服务端会:
- 将消息幂等写入数据库
- 推送给当前在线的 WebSocket 订阅者
- 并行触发 Webhook 回调
请求体为一条完整的 WsTransactionMessage 格式消息(与服务端推送的 transaction 消息格式相同)。
响应:
{
"code": 0,
"message": "ok",
"data": {
"processed": true,
"txhash": "0xabc123..."
}
}
错误处理
WebSocket 命令的错误以相同的 JSON 格式通过 WebSocket 消息返回,code 字段为非 0:
{
"id": "原始命令的 id",
"type": "命令类型",
"code": 400,
"data": null,
"msg": "错误描述"
}
常见错误汇总:
| 命令 | code | msg |
|---|---|---|
subscribe | 400 | address and chain_type required |
unsubscribe | 400 | address required |
getTxHash | 400 | chain_type, chain_id, txHash required |
getTxHash | 404 | transaction not found |
transactionACK | 400 | chain_type and messageId/txHash required |
最佳实践
心跳保活
建议每 30 秒发送一次 ping 命令:
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ id: `ping-${Date.now()}`, type: 'ping', data: {} }));
}
}, 30000);
断线重连
function connect() {
const ws = new WebSocket(WS_URL, { headers: { 'X-BaseSeverAppID': APP_ID } });
ws.on('close', () => {
console.log('连接断开,5 秒后重连...');
setTimeout(connect, 5000);
});
return ws;
}
ACK 策略
- 及时 ACK:收到
transaction消息并完成业务处理后,立即发送transactionACK - 避免漏 ACK:如果业务处理耗时较长,可先 ACK 再异步处理
- ACK 去重:业务层应以
txhash + chain_type + chain_id为唯一键做幂等处理
地址订阅策略
| 方式 | 持久性 | 适用场景 |
|---|---|---|
HTTP 地址管理(/api/v1/address/add) | 持久,重启后有效 | 常规业务地址的长期监听 |
WebSocket subscribe 命令 | 临时,仅当前连接有效 | 临时调试或动态细粒度控制 |
并发连接
同一个 app_id 支持多个客户端同时连接,服务端会向所有在线连接广播交易消息。适用于多实例水平扩展或主备切换场景(需业务层做幂等处理)。
完整接入示例
const WebSocket = require('ws');
const WS_URL = 'wss://api.gelabs.org/transaction/transaction/ws';
const APP_ID = 'my-app-001';
const processedTxHashes = new Set(); // 幂等去重
function connect() {
const ws = new WebSocket(WS_URL, {
headers: { 'X-BaseSeverAppID': APP_ID }
});
let pingInterval;
ws.on('open', () => {
console.log('WebSocket 连接已建立');
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ id: `ping-${Date.now()}`, type: 'ping', data: {} }));
}
}, 30000);
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString());
switch (msg.type) {
case 'connected':
console.log(`连接确认: appId=${msg.data.appId}, clientId=${msg.data.clientId}`);
break;
case 'pong':
break;
case 'transaction': {
if (msg.code !== 0) {
console.error('收到错误消息:', msg.msg);
break;
}
const tx = msg.data;
const txKey = `${tx.chain_type}-${tx.chain_id}-${tx.txhash}`;
if (!processedTxHashes.has(txKey)) {
processedTxHashes.add(txKey);
console.log(`新交易: chain=${tx.chain_type}, hash=${tx.txhash}, amount=${tx.amount} ${tx.symbol}`);
if (tx.is_reorg_resend) {
console.warn(`链重组重发: ${tx.txhash},请检查本地记录是否需要更新`);
}
if (tx.tokenTransferDetails && tx.tokenTransferDetails.length > 0) {
tx.tokenTransferDetails.forEach(t => {
console.log(` 代币转账: ${t.amount} ${t.symbol} from ${t.from} to ${t.to}`);
});
}
}
// 发送 ACK
ws.send(JSON.stringify({
id: `ack-${msg.id}`,
type: 'transactionACK',
data: { chain_type: tx.chain_type, chain_id: tx.chain_id, txHash: tx.txhash },
}));
break;
}
default:
console.log(`未知消息类型: ${msg.type}`);
}
});
ws.on('close', (code) => {
clearInterval(pingInterval);
console.log(`连接断开 (code=${code}), 5 秒后重连...`);
setTimeout(connect, 5000);
});
ws.on('error', (err) => {
console.error('WebSocket 错误:', err.message);
});
}
connect();