Wallet API
Wallet API 用 REST 回答钱包相关的问题:谁持有某个代币、一个地址持有什么以及值多少美元、哪些地址在某个池子里交易过。按仓位的盈亏和平均成本即将开放。
Wallet API 和 On-chain DEX API 是 Crypto Data API 下的两个产品,除了产品页,其余全部共用:
- 同一个 key。 On-chain DEX API 端点的 key 两个产品都能调,没有单独的 Wallet API 端点或 key。
- 同一份配额和速率限制。 Wallet API 的调用计入同一份 Crypto Data API 配额,与 RPC Requests 分开。
- 同一套价格。 Wallet API 没有单独的价目表,以 Crypto Data API 定价为准。
https://api.blockreq.com/v1/dex支持的链:Ethereum(ethereum)、Base(base)和 Arbitrum(arbitrum)。Base 的持有人排行仍在回填(2026 年 11 月),所以目前 Base 上任何代币的持有人路由都返回 404。
认证
在 Dashboard 创建 On-chain DEX API 端点(Endpoints → + Add Endpoint),把它的 key 放在 X-API-Key 头里发送,也可以用 Authorization: Bearer <key>。key 的作用范围、RPC key 过渡期和匿名访问都与 On-chain DEX API 相同,见获取 key。
export BLOCKREQ_DATA_API_KEY="你的 On-chain DEX API key"本页的每个请求计 1 次调用。配额、速率限制、429 的处理和用量响应头在 套餐与额度 和 限制与 429 里统一说明,两个产品通用。
也可以在 Wallet API 产品页的 tester 里不带 key 试持有人和持仓路由。
路由
| 方法和路径 | 返回 | 状态 |
|---|---|---|
GET /v1/dex/{chain}/tokens/{address}/holders | 已跟踪代币的最大持有人 | 已上线 |
GET /v1/dex/{chain}/accounts/{address}/portfolio | 一个地址持有的代币及美元价值 | 已上线 |
GET /v1/dex/{chain}/pools/{id}/trades | 池子的成交,每笔带交易者地址 | 已上线 |
GET /v1/dex/{chain}/traders/{address}/positions | 交易者的仓位、平均成本、已实现与未实现盈亏 | 即将开放 |
数量都是字符串:balance 等原始数量是以最小单位计的整数(未按 decimals 换算);amount、价格和美元金额是小数。响应里的地址都是小写。
代币持有人
GET /v1/dex/{chain}/tokens/{address}/holders按代币的 ERC-20 Transfer 统计的前 limit 名持有人。销毁地址、Uniswap v4 PoolManager 和持有该代币的池子本身不参与排名,也不计入 pct 的分母。
只有已跟踪的代币有排行。锚定代币(稳定币和 WETH、USDC 这类主流币)、不在任何已索引池子里的代币、被屏蔽的代币,以及还没追上链头的代币都返回 404。新跟踪的代币要从链的创世块开始回填,完成之前也返回 404。
| 参数 | 位置 | 说明 |
|---|---|---|
chain | path | ethereum、base 或 arbitrum |
address | path | 代币地址,0x 加 40 位十六进制,大小写不限 |
limit | query | 最多返回的持有人数,1–5000(默认 5000) |
响应字段(data):
| 字段 | 类型 | 说明 |
|---|---|---|
chain、address | string | 链和代币 |
symbol、decimals | string、integer / null | 代币元数据 |
block | integer | 统计到这个区块为止的 Transfer |
known_total | 原始数量 | 除排除地址外所有余额之和,pct 的分母 |
known_total_amount | decimal / null | known_total 换算成整币;decimals 未知时为 null |
total_supply | 原始数量 / null | 从 BlockReq 自己的节点读到的 totalSupply();读不到时为 null |
consistent | boolean / null | Transfer 合计与 total_supply 是否相差在 1% 以内。为 false 时(rebase 类代币等)所有 pct 都是 null |
excluded | string[] | 被排除的销毁地址、PoolManager 和池子 |
top[] | object[] | 余额从大到小:rank(最大为 1)、address、balance(原始)、amount(整币)、pct(6 位小数) |
**示例:**Arbitrum 上 ARB 的前 20 名持有人。
curl -s "https://api.blockreq.com/v1/dex/arbitrum/tokens/0x912ce59144191c1204e64559fe8253a0e49e6548/holders?limit=20" \
-H "X-API-Key: $BLOCKREQ_DATA_API_KEY"const res = await fetch(
"https://api.blockreq.com/v1/dex/arbitrum/tokens/0x912ce59144191c1204e64559fe8253a0e49e6548/holders?limit=20",
{ headers: { "X-API-Key": process.env.BLOCKREQ_DATA_API_KEY } },
);
const { data } = await res.json();
for (const holder of data.top) console.log(holder.rank, holder.address, holder.pct);import os, requests
res = requests.get(
"https://api.blockreq.com/v1/dex/arbitrum/tokens/0x912ce59144191c1204e64559fe8253a0e49e6548/holders",
params={"limit": 20},
headers={"X-API-Key": os.environ["BLOCKREQ_DATA_API_KEY"]},
timeout=30,
)
for holder in res.json()["data"]["top"]:
print(holder["rank"], holder["address"], holder["pct"])limit=2 的响应(这里截短了 excluded;ARB 的 excluded 会列出持有它的每个池子):
{
"data": {
"chain": "arbitrum",
"address": "0x912ce59144191c1204e64559fe8253a0e49e6548",
"symbol": "ARB",
"decimals": 18,
"block": 522352929,
"known_total": "9982381908048402768748531744",
"known_total_amount": "9982381908.048402768748531744",
"excluded": [
"0x0000000000000000000000000000000000000000",
"0x000000000000000000000000000000000000dead",
"0xdead000000000000000042069420694206942069"
],
"total_supply": "9999998977610261816650915825",
"consistent": true,
"top": [
{
"rank": 1,
"address": "0xf3fc178157fb3c87548baa86f9d24ba38e649b58",
"balance": "2556141991509656323975221470",
"amount": "2556141991.50965632397522147",
"pct": "25.606534"
},
{
"rank": 2,
"address": "0xf7f468b184a48f6ca37eeffe12733ee1c16b6e26",
"balance": "449339761507879669910372383",
"amount": "449339761.507879669910372383",
"pct": "4.501328"
}
]
}
}地址持仓
GET /v1/dex/{chain}/accounts/{address}/portfolio一个地址在该链上持有的代币,只有两个来源:
- 持有人排行里存的余额:该地址在其前 5000 名持有人之内的已跟踪代币(
source: "holders"); - 对你在
tokens里列出的代币,从 BlockReq 自己的节点读balanceOf(source: "node"),并替换这些代币存的余额;余额为 0 的代币不返回。
没有全量钱包索引:既不是该地址进了前 5000 名的已跟踪代币、也没列在 tokens 里的代币不会出现。按美元价值从大到小排序,没有美元价值的排在最后。什么都没找到的地址返回 200 和空的 holdings 列表。
| 参数 | 位置 | 说明 |
|---|---|---|
chain | path | ethereum、base 或 arbitrum |
address | path | 持有人地址,0x 加 40 位十六进制,大小写不限 |
tokens | query | 最多 50 个逗号分隔的 ERC-20 地址(重复的只算一次),用 balanceOf 读余额;原生币地址会被拒绝 |
响应字段(data):
| 字段 | 类型 | 说明 |
|---|---|---|
chain、address | string | 链和持有人 |
total_value_usd | decimal | 所有 value_usd 之和,6 位小数;没有美元价值的不计入 |
holdings[] | object[] | 见下 |
node_block | integer / null | 节点 balanceOf 读取所在区块;没读时为 null |
failed | string[] | balanceOf 失败的代币;这些代币保留存的余额 |
holdings[].token | string | 代币 |
holdings[].symbol、name | string / null | 代币元数据 |
holdings[].decimals | integer / null | |
holdings[].balance | 原始数量 | |
holdings[].amount | decimal / null | 换算成整币的余额;decimals 未知时为 null |
holdings[].price_usd | decimal / null | |
holdings[].value_usd | decimal / null | amount × price_usd,6 位小数;缺一项时为 null |
holdings[].source | holders、node | 排行里存的余额,或 BlockReq 节点上的 balanceOf |
holdings[].block | integer | 余额对应的区块 |
**示例:**Ethereum 上一个交易所钱包,另外实时读它的 USDC 余额。
curl -s "https://api.blockreq.com/v1/dex/ethereum/accounts/0x28c6c06298d514db089934071355e5743bf21d60/portfolio?tokens=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" \
-H "X-API-Key: $BLOCKREQ_DATA_API_KEY"const url = new URL("https://api.blockreq.com/v1/dex/ethereum/accounts/0x28c6c06298d514db089934071355e5743bf21d60/portfolio");
url.searchParams.set("tokens", "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48");
const res = await fetch(url, { headers: { "X-API-Key": process.env.BLOCKREQ_DATA_API_KEY } });
const { data } = await res.json();
console.log(data.total_value_usd, data.holdings.length);import os, requests
res = requests.get(
"https://api.blockreq.com/v1/dex/ethereum/accounts/0x28c6c06298d514db089934071355e5743bf21d60/portfolio",
params={"tokens": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"},
headers={"X-API-Key": os.environ["BLOCKREQ_DATA_API_KEY"]},
timeout=30,
)
data = res.json()["data"]
print(data["total_value_usd"], len(data["holdings"]))不带 tokens 的响应(12 项持仓只列前 2 项):
{
"data": {
"chain": "ethereum",
"address": "0x28c6c06298d514db089934071355e5743bf21d60",
"total_value_usd": "133198.620099",
"holdings": [
{
"token": "0x15d4c048f83bd7e37d49ea4c83a07267ec4203da",
"symbol": "GALA",
"name": "Gala",
"decimals": 8,
"balance": "496270440907221534",
"amount": "4962704409.07221534",
"price_usd": "0.0000268169615577973807",
"value_usd": "133084.653361",
"source": "holders",
"block": 26369822
},
{
"token": "0x03ab458634910aad20ef5f1c8ee96f1d6ac54919",
"symbol": "RAI",
"name": "Rai Reflex Index",
"decimals": 18,
"balance": "33000000000000000000",
"amount": "33",
"price_usd": "3.01662215219692683",
"value_usd": "99.548531",
"source": "holders",
"block": 26369822
}
],
"node_block": null,
"failed": []
}
}在没有配置节点的链上带 tokens,或节点没有响应时,返回 503 unavailable,message 会说明是哪种情况。去掉 tokens 重试即可拿到存的余额。
池子成交
GET /v1/dex/{chain}/pools/{id}/trades与 On-chain DEX API 文档里的是同一条路由;用于钱包场景时,每笔成交都带 trader,即交易的发送者。按时间从新到旧。满页时带 next_cursor,作为 cursor 传入取下一页。原始成交保留期:已上架的池子 90 天,其他池子 30 天;更早的历史在 K 线里。
| 参数 | 位置 | 说明 |
|---|---|---|
chain | path | ethereum、base 或 arbitrum |
id | path | 池子:0x 加 40 位(池子合约)或 64 位(Uniswap v4 pool key)十六进制 |
limit | query | 每页最多成交数,1–100(默认 50) |
cursor | query | 上一页的 next_cursor;其他值返回 400 |
响应字段:data[] 是成交列表,字段有 block_number、log_index、time(ISO 8601)、tx_hash、trader(地址或 null)、amount0 和 amount1(原始数量,从池子的角度:正数表示池子收到)、price(每个整币 token0 值多少整币 token1)、amount_usd、price0_usd 和 price1_usd(decimal 或 null)。最后一页的 next_cursor 为 null。响应还带 X-Data-Tier 头。
OpenAPI 文档里还定义了 earliest_available(保留窗口起点,Unix 秒)。截至 2026-11-08,生产 API 还没有返回这个字段,出现之前请不要依赖它。
curl -s "https://api.blockreq.com/v1/dex/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/trades?limit=20" \
-H "X-API-Key: $BLOCKREQ_DATA_API_KEY"let cursor = null;
do {
const url = new URL("https://api.blockreq.com/v1/dex/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/trades");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { "X-API-Key": process.env.BLOCKREQ_DATA_API_KEY } });
const page = await res.json();
for (const trade of page.data) console.log(trade.time, trade.trader, trade.amount_usd);
cursor = page.next_cursor;
} while (cursor);import os, requests
res = requests.get(
"https://api.blockreq.com/v1/dex/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/trades",
params={"limit": 20},
headers={"X-API-Key": os.environ["BLOCKREQ_DATA_API_KEY"]},
timeout=30,
)
for trade in res.json()["data"]:
print(trade["time"], trade["trader"], trade["amount_usd"])limit=1 的响应:
{
"data": [
{
"block_number": 26369898,
"log_index": 55,
"time": "2026-11-08T11:37:11.000Z",
"tx_hash": "0x4a2c1e87ccb499d442452a2ef17abafc4c36c7019768c9940fe748b46768d23a",
"trader": "0x8dbee8f3917049bc30fef01924a7ac79d16cf2b9",
"amount0": "6839954413",
"amount1": "-3056128806869109546",
"price": "0.000446805434998315031",
"amount_usd": "6839.954413",
"price0_usd": null,
"price1_usd": null
}
],
"next_cursor": "1794137831000.26369898.55"
}即将开放:仓位与盈亏
GET /v1/dex/{chain}/traders/{address}/positions**尚未开放。**截至 2026-11-08,这个路径返回 404。这里提前写出来方便你规划;下面的字段是根据 OpenAPI 文档整理的草案,开放前仍可能调整。上线之前不提供示例数据。
开放后,这条路由会返回交易者在已上架池子里交易过的每个代币的仓位,按最近交易时间排序,用加权平均成本计算:买入把美元金额加进成本;卖出实现的盈亏是卖出的美元金额减去所卖部分的平均成本。通过转账进来的代币没有成本:卖出它们计入 unmatched_sell_qty,不产生已实现盈亏。未实现盈亏按代币当前美元价格给持有量估值。仓位每小时重算一次。
| 参数 | 位置 | 说明 |
|---|---|---|
address | path | 交易者(交易发送者),0x 开头 |
limit | query | 最多返回的仓位数,1–1000(默认 100) |
| 字段(草案) | 类型 | 说明 |
|---|---|---|
token、symbol、decimals | string、string / null、integer / null | 代币 |
qty | 原始数量 | 以最小单位计的持有量;卖出转账进来的代币后可能为负 |
unmatched_sell_qty | 原始数量 | 超出已索引买入量的卖出数量,不产生已实现盈亏 |
cost_usd | decimal | 持有量按加权平均成本计算的美元成本 |
avg_cost_usd | decimal / null | 平均买入价:每个整币的美元成本;qty 不为正或 decimals 未知时为 null |
realized_pnl_usd | decimal | 已实现盈亏(美元) |
price_usd | decimal / null | 代币当前美元价格 |
unrealized_pnl_usd | decimal / null | 整币持有量 × price_usd − cost_usd;没有价格或 decimals 时为 null,qty 不为正时为 0 |
buys、sells | integer | 买入、卖出次数 |
first_day、last_day | date | 第一笔和最近一笔已索引交易的 UTC 日期,YYYY-MM-DD |
错误
错误是 JSON {"error": {"code": "...", "message": "..."}}。请按 code 分支处理,不要按 message。
| 状态 | error.code | 何时 |
|---|---|---|
| 400 | invalid_request | 参数不合法(地址格式错误、limit 超出范围、cursor 无效) |
| 401 | invalid_key | key 不存在或已吊销,或没有放在 X-API-Key / Authorization: Bearer 里 |
| 403 | forbidden | 账户被停用,或 key 的端点已过期、不允许你的 IP |
| 404 | not_found | 不支持该链、代币还没有持有人排行,或池子未被索引 |
| 429 | rate_limited、quota_exceeded | 超出速率,或本周期配额用完:见限制与 429 |
| 500 | internal | 意外故障 |
| 502 | upstream_failed、response_too_large | 数据服务失败或返回内容过大;退避后重试 |
| 503 | unavailable | 短暂不可用,或无法从节点读取 tokens;稍后重试 |
| 504 | timeout | 超时;请重试 |
另见
- On-chain DEX API:价格、池子、K 线和 WebSocket 订阅,使用同一个 key
- 交互式参考(Swagger UI) 和 OpenAPI JSON
- Crypto Data API 定价,On-chain DEX API 与 Wallet API 共用