REST API
Arcscan 通过一套 HTTP API 读取 Arc,而这套 API 无需密钥、无需账户即可从外部访问——本页上的每一条路径都是先经公网真实调用过,才被写下来的。
基础 URL#
API 有自己的主机名,每条链一个:api.arc-scan.io 对应 Arc 主网,链 ID 5042;api-testnet.arc-scan.io 对应 Arc 测试网,链 ID 5042002。请以它们为开发对象:
https://api.arc-scan.io/v1 # Arc mainnet, chain 5042 https://api-testnet.arc-scan.io/v1 # Arc Testnet, chain 5042002
两个主机提供同样的两套接口:/v1/… 是类型化的 REST API,一个资源一条路径,返回的正是本浏览器自身用来渲染页面的文档;/api?module=…&action=… 则是大家熟悉的兼容契约,现成的工具已经会说这门话。两个主机之间的差别只在于各自回答哪条链,所以把客户端切到测试网就只是换一个主机名,别的都不用动。
本站自己的 /_api 路径并不是 API
你可能会注意到,浏览器页面是从本站自己的源站去取 /_api/v1/… 的,而且你手动调用它也确实有响应。那是网站通往同一个服务的内部改写路由,是留给它自己的页面用的:它不返回任何 CORS 头,带着 noindex 头下发,并且可能因为与你毫无关系的原因被改掉。请以上面的主机名为准,并把主机名固定在一个常量里,这样你只需要改一处。可以从浏览器调用——但只限这两个主机
API 主机允许跨源调用:Access-Control-Allow-Origin: *,预检中允许 Content-Type、X-Api-Key 和 If-None-Match,并向调用方开放 ETag、X-Request-Id 以及限流相关的响应头。而本站自己的 /_api 路径完全不带 Access-Control- 头,所以指向它的 fetch() 在浏览器里会失败,同样一条请求用 curl 却能成功——错误的地址正是这样才一直没被发现。两套接口#
同一个节点和同一份索引,通过两种不同的形态作答。类型化 API 正是这个浏览器渲染所依据的,因此页面上可见的任何内容都能以同样的形式读成 JSON。兼容 API 复现了钱包、部署脚本和索引器已经在说的那套事实标准浏览器 REST 契约,因此现有工具只要改一个基础 URL 就能指向 Arc。
| 接口 | 形态 | 成功信封 |
|---|---|---|
/v1/… | 类型化 REST,每种资源一条路径 | 文档本身,snake_case 键名 |
/api?module=…&action=… | 一条路径,按查询字符串分发 | {"status":"1","message":"OK","result":…} |
端点#
下面每一条路径在写这一页时都对主网真实调用过。它们全都作了回答;其中两条回答 501,而这两条都被写在了它们各自所属的位置,而不是被省略——一次有文档记载的拒绝,比一处空白更有价值。
链、首页与链头#
/v1/chain 是第一个该调用的:它给出链 ID、原生货币及其小数位数、本部署具备哪些能力,以及这条链究竟被索引了多少。请读取它,而不要把这些内容写死——两条链并不相同,而数据覆盖范围解释了其中的索引部分告诉你什么。
curl https://api.arc-scan.io/v1/chain # {"chain_id":5042,"name":"Arc","is_testnet":false, # "native":{"symbol":"USDC","decimals":18}, # "block_time_ms":506,"finality":"instant", # "capabilities":{"trace":true,"archive":true,"debug":false,…}, # "index":{"available":true,"complete":true,…}}
| 路径 | 它回答什么 |
|---|---|
GET /v1/chain | 链常量、能力与索引覆盖范围 |
GET /v1/home | 最新区块与交易,合在一份文档里 |
GET /v1/stats/summary | 首页显示的那些头条计数器 |
GET /v1/stats/gas | 费用追踪器:当前和近期的 Gas 价格 |
GET /v1/stream/head | 服务器推送事件,每个新区块一帧 |
/v1/stream/head 是一个 SSE 流,不是 JSON 文档——它保持连接打开,大约每秒推送两帧。每一帧都带有 server_now,因此客户端不必信任自己的时钟就能算出诚实的数据年龄。
curl -N https://api.arc-scan.io/v1/stream/head # event: head # data: {"height": 14852424, "hash": "0x68ce06…f3775", "timestamp": 1786360005, # "tx_count": 0, "server_now": 1786360007, "available": true}
区块#
区块引用可以是高度、哈希,或者字面量 latest。列表接受 limit 和一个不透明的 cursor;各字段的含义见区块。
curl "https://api.arc-scan.io/v1/blocks?limit=2" curl https://api.arc-scan.io/v1/blocks/latest curl https://api.arc-scan.io/v1/blocks/14852000 curl "https://api.arc-scan.io/v1/blocks/14852000/txs?limit=10"
交易#
交易按哈希寻址。调用树在主网上可用,因为那里的 trace 为 true;状态差异不可用,因为 debug 为 false,而它会以 501 说明这一点,而不是给出一个空回答。
H=0x128cb07da24289341bcc57e3129ef78f82b5a1769c30156774487fe9e98251e9 curl "https://api.arc-scan.io/v1/txs?limit=5" curl "https://api.arc-scan.io/v1/txs/$H" curl "https://api.arc-scan.io/v1/txs/$H/raw" curl "https://api.arc-scan.io/v1/txs/$H/trace" curl "https://api.arc-scan.io/v1/txs/$H/state" # 501 {"error":{"code":"CAPABILITY_UNAVAILABLE",…}} — no debug namespace on mainnet
地址与代币#
A=0xf40dc55b06e4e8c042430ecf1995d0ec6b9ae033 T=0x3600000000000000000000000000000000000000 # the native currency as an ERC-20 curl "https://api.arc-scan.io/v1/address/$A" curl "https://api.arc-scan.io/v1/address/$A/txs?limit=10" curl "https://api.arc-scan.io/v1/address/$A/activity?limit=10" curl "https://api.arc-scan.io/v1/address/$A/logs?limit=10" curl "https://api.arc-scan.io/v1/address/$A/tokens" curl "https://api.arc-scan.io/v1/tokens/$T" curl "https://api.arc-scan.io/v1/tokens/$T/info"
持有者在主网上有索引,因此 /v1/tokens/{address}/holders 对普通代币会作答。它对 0x3600…0000 处的原生货币予以拒绝,消息里解释了原因:那些余额就是账户余额,索引两遍会把每个账户重复计算一次。
搜索、图表与解码#
搜索解析的内容与浏览器自己的搜索框相同——高度、哈希、地址。图表索引列出每个指标及其 id,而 /v1/charts/{metric} 返回那一条序列;tx 就是其中之一。解码是本页上唯一的 POST,而且完全不需要查询链。
curl "https://api.arc-scan.io/v1/search?q=0x3600000000000000000000000000000000000000" curl "https://api.arc-scan.io/v1/search/suggest?q=0x36" curl https://api.arc-scan.io/v1/charts curl https://api.arc-scan.io/v1/charts/tx curl -X POST https://api.arc-scan.io/v1/decode \ -H 'content-type: application/json' \ -d '{"input":"0xa9059cbb…"}' # {"selector":"0xa9059cbb","decoded":{"name":"transfer", # "signature":"transfer(address,uint256)","args":[…]},"error":null}
兼容接口#
/api?module=…&action=… 说的是大家熟悉的那套浏览器契约:一条路径、一个 module 和一个 action,result 中的每个标量都是字符串。proxy 模块会把 JSON-RPC 读取透传出去,并以 JSON-RPC 的形态而不是状态信封作答。
curl "https://api.arc-scan.io/api?module=proxy&action=eth_blockNumber" # {"jsonrpc":"2.0","id":1,"result":"0xe2a12f"} curl "https://api.arc-scan.io/api?module=account&action=balance&address=0xf40dc55b06e4e8c042430ecf1995d0ec6b9ae033" # {"status":"1","message":"OK","result":"20456053552414099311"} curl "https://api.arc-scan.io/api?module=stats&action=ethsupply"
那套契约的动作并非在这里全部存在——凡是需要 Arc 所没有的数据、或者需要我们并未保留的索引的动作,都会被按名拒绝。站内的 API 参考在新标签页中打开列出了完整的分发表以及哪些动作不可用;它由服务本身生成,因此不会像本页这样发生偏离。
如何读这些 JSON#
有四条约定造成了所有值得一提的集成缺陷。第一条代价最高:18 位小数的金额在 JavaScript 的 Number 中无法保全,因此金额以字符串跨越网络传输,也必须按字符串解析。
| 约定 | 它在传输层上意味着什么 |
|---|---|
| 金额 | 是一个对象,不是一个数字:raw(整数字符串)、decimals、formatted(精确值)、usd、symbol。Gas 数值和供应量同样是十进制字符串。区块高度是 JSON 数字。 |
| 大小写 | 键名是 snake_case。地址和哈希是小写,其显示形式放在旁边的 checksum 字段里。输入时任何大小写都接受。 |
| 时间 | Unix 秒,绝不预先格式化。区块大约每秒到达两个,而时间戳只精确到秒,因此它们不构成全序——切勿按它们排序或分页。 |
| 分页 | page.next 下的不透明游标,不是页码。page.has_more 告诉你是否要继续。 |
已提交的历史是不可变的,缓存方式也照此对待
以已提交高度或已上链哈希为键的响应,会以Cache-Control: public, max-age=31536000, immutable 提供。Arc 具有即时最终性且没有重组,因此这是一个真实的保证而不是概率性的:把那些响应永久缓存起来,然后完全跳过请求。错误#
一次拒绝是一个 JSON 对象,包含一个机器可读的 code、一条写给人看的消息,以及一个可选的 detail。请按 code 分支,绝不要按消息文本。
curl https://api.arc-scan.io/v1/blocks/999999999999 # 404 # {"error":{"code":"NOT_FOUND","message":"No block at height 999999999999","detail":null}} curl https://api.arc-scan.io/v1/tokens/0x3600000000000000000000000000000000000000/holders # 501 # {"error":{"code":"CAPABILITY_UNAVAILABLE", # "message":"The native gas token has no separate holder list: its balances are account # balances, and indexing them again would double-count every account.", # "detail":{"capability":"holder_index","detail_key":"holdersNativeToken"}}}
| 状态码 | code | 何时出现 |
|---|---|---|
| 404 | NOT_FOUND | 没有这样的区块、交易、地址记录或代币。 |
| 429 | RATE_LIMITED | 请求过多。附带以秒为单位的 Retry-After。 |
| 501 | CAPABILITY_UNAVAILABLE | 该数据需要一项本链或本部署并不具备的能力。 |
| 502 | UPSTREAM_ERROR | 节点作了回答,但没有回答出有用的内容。 |
| 503 | INDEX_OVERLOADED | 一个重查询被丢弃,而不是排队。 |
| 504 | UPSTREAM_TIMEOUT | 节点没有及时回答。 |
读错误信封,而不只是状态码
格式错误或超出范围的参数——无法解析的区块引用、超过上限的limit、缺少的必需查询、拼错的 JSON 字段名——会以 400 {"error":{"code":"INVALID_INPUT","message":"limit: Input should be greater than or equal to 1","detail":null}} 返回。code 指出错误的类别,message 指出引发错误的字段,这通常比重读 URL 更快。这里的 404 表示该资源确实不存在。限制与访问#
没有 API 密钥、没有等级,也没有什么需要注册。请求按调用方以令牌桶计量,每一个响应都会告诉你当前的处境——请读取响应头,而不要把某个数字写死,因为这个预算是一项运维设置,不是一个公开承诺。
| 响应头 | 含义 |
|---|---|
X-RateLimit-Limit | 该请求所计量的那个桶的容量。 |
X-RateLimit-Remaining | 桶中剩余的令牌数。 |
Retry-After | 仅在 429 或 503 时出现:重试前需要等待的整秒数。 |
X-Request-Id | 出现在 API 响应上。如果你需要就某个请求询问我们,请引用它。 |
另有两个上限是真实存在的,而且会被明确报告而不是隐藏。日志查询被限制在一个最大区块跨度内,超范围的请求会被拒绝,并在消息中写明那个最大值,而不是被无声地截断。Trace 和状态差异调用运行在一个专用小池上,带有实际耗时预算和大小上限,并会设置 truncated: true,而不是一直挂着。
不通过我们的索引读取 Arc
如果你想要的是这条链本身而不是我们对它的视图,我们还为主网运行一个公共只读 JSON-RPC 端点——见公共 RPC。另外还有一份纯文本的 /llms.txt在新标签页中打开,描述这个浏览器持有什么,供那些先读散文再读 schema 的程序使用。