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。

参数位置说明
chainpathethereum、base 或 arbitrum
addresspath代币地址,0x 加 40 位十六进制,大小写不限
limitquery最多返回的持有人数,1–5000(默认 5000)

响应字段(data):

字段类型说明
chain、addressstring链和代币
symbol、decimalsstring、integer / null代币元数据
blockinteger统计到这个区块为止的 Transfer
known_total原始数量除排除地址外所有余额之和,pct 的分母
known_total_amountdecimal / nullknown_total 换算成整币;decimals 未知时为 null
total_supply原始数量 / null从 BlockReq 自己的节点读到的 totalSupply();读不到时为 null
consistentboolean / nullTransfer 合计与 total_supply 是否相差在 1% 以内。为 false 时(rebase 类代币等)所有 pct 都是 null
excludedstring[]被排除的销毁地址、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

一个地址在该链上持有的代币,只有两个来源:

  1. 持有人排行里存的余额:该地址在其前 5000 名持有人之内的已跟踪代币(source: "holders");
  2. 对你在 tokens 里列出的代币,从 BlockReq 自己的节点读 balanceOf(source: "node"),并替换这些代币存的余额;余额为 0 的代币不返回。

没有全量钱包索引:既不是该地址进了前 5000 名的已跟踪代币、也没列在 tokens 里的代币不会出现。按美元价值从大到小排序,没有美元价值的排在最后。什么都没找到的地址返回 200 和空的 holdings 列表。

参数位置说明
chainpathethereum、base 或 arbitrum
addresspath持有人地址,0x 加 40 位十六进制,大小写不限
tokensquery最多 50 个逗号分隔的 ERC-20 地址(重复的只算一次),用 balanceOf 读余额;原生币地址会被拒绝

响应字段(data):

字段类型说明
chain、addressstring链和持有人
total_value_usddecimal所有 value_usd 之和,6 位小数;没有美元价值的不计入
holdings[]object[]见下
node_blockinteger / null节点 balanceOf 读取所在区块;没读时为 null
failedstring[]balanceOf 失败的代币;这些代币保留存的余额
holdings[].tokenstring代币
holdings[].symbol、namestring / null代币元数据
holdings[].decimalsinteger / null
holdings[].balance原始数量
holdings[].amountdecimal / null换算成整币的余额;decimals 未知时为 null
holdings[].price_usddecimal / null
holdings[].value_usddecimal / nullamount × price_usd,6 位小数;缺一项时为 null
holdings[].sourceholders、node排行里存的余额,或 BlockReq 节点上的 balanceOf
holdings[].blockinteger余额对应的区块

**示例:**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 线里。

参数位置说明
chainpathethereum、base 或 arbitrum
idpath池子:0x 加 40 位(池子合约)或 64 位(Uniswap v4 pool key)十六进制
limitquery每页最多成交数,1–100(默认 50)
cursorquery上一页的 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,不产生已实现盈亏。未实现盈亏按代币当前美元价格给持有量估值。仓位每小时重算一次。

参数位置说明
addresspath交易者(交易发送者),0x 开头
limitquery最多返回的仓位数,1–1000(默认 100)
字段(草案)类型说明
token、symbol、decimalsstring、string / null、integer / null代币
qty原始数量以最小单位计的持有量;卖出转账进来的代币后可能为负
unmatched_sell_qty原始数量超出已索引买入量的卖出数量,不产生已实现盈亏
cost_usddecimal持有量按加权平均成本计算的美元成本
avg_cost_usddecimal / null平均买入价:每个整币的美元成本;qty 不为正或 decimals 未知时为 null
realized_pnl_usddecimal已实现盈亏(美元)
price_usddecimal / null代币当前美元价格
unrealized_pnl_usddecimal / null整币持有量 × price_usd − cost_usd;没有价格或 decimals 时为 null,qty 不为正时为 0
buys、sellsinteger买入、卖出次数
first_day、last_daydate第一笔和最近一笔已索引交易的 UTC 日期,YYYY-MM-DD

错误

错误是 JSON {"error": {"code": "...", "message": "..."}}。请按 code 分支处理,不要按 message。

状态error.code何时
400invalid_request参数不合法(地址格式错误、limit 超出范围、cursor 无效)
401invalid_keykey 不存在或已吊销,或没有放在 X-API-Key / Authorization: Bearer 里
403forbidden账户被停用,或 key 的端点已过期、不允许你的 IP
404not_found不支持该链、代币还没有持有人排行,或池子未被索引
429rate_limited、quota_exceeded超出速率,或本周期配额用完:见限制与 429
500internal意外故障
502upstream_failed、response_too_large数据服务失败或返回内容过大;退避后重试
503unavailable短暂不可用,或无法从节点读取 tokens;稍后重试
504timeout超时;请重试

另见