> ## Documentation Index
> Fetch the complete documentation index at: https://docs.leafage.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 发送 RPC 请求

> 通过 nodex-proxy 或直连 leafage-evm 发送 JSON-RPC 请求：端点、请求头，以及 eth_call、批量调用、blockCtx、gas 估算、模拟与追踪的可复制示例。

Leafage 对外只有一种协议：HTTP 上的 JSON-RPC 2.0。任何能发 HTTP POST 的客户端都可以直接使用，不需要 SDK。本页从第一个请求开始，逐步覆盖批量调用、区块上下文、gas 估算、模拟与追踪。

## 端点

| 场景      | 端点                                 | 说明                              |
| ------- | ---------------------------------- | ------------------------------- |
| 生产      | `POST http://proxy:8663/{chainId}` | 经 nodex-proxy 路由到合适的查询节点，链由路径指定 |
| 开发 / 调试 | `POST http://127.0.0.1:8659`       | 直连单个 leafage-evm，没有路由和兜底        |

`chainId` 用十进制，ETH 主网是 `/1`。十六进制写法会被 proxy 规范化为十进制。

下文示例以生产端点为准，直连时去掉路径即可。

## 请求头

| 头                                                          | 必需 | 作用                       |
| ---------------------------------------------------------- | -- | ------------------------ |
| `Content-Type: application/json`                           | 是  | JSON-RPC 请求体             |
| `client-id`                                                | 建议 | 用量上报的聚合键，缺失记为 `unknown`  |
| `x-dbk-biz`、`x-dbk-source`、`x-dbk-source-host`、`x-dbk-env` | 否  | 来源标识，进入 proxy 的日志与 trace |
| `x-nodex-node-type: archive`                               | 否  | 强制路由到 Archive 节点池        |

## 第一个请求：`eth_call`

读取 USDT 合约的 `totalSupply()`，函数选择器是 `0x18160ddd`：

<CodeGroup>
  ```bash curl theme={null}
  curl -s -X POST http://proxy:8663/1 \
    -H 'Content-Type: application/json' \
    -H 'client-id: my-service' \
    -d '{
      "jsonrpc": "2.0",
      "id": 1,
      "method": "eth_call",
      "params": [
        { "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "data": "0x18160ddd" },
        "latest"
      ]
    }'
  ```

  ```python Python theme={null}
  import requests

  RPC_URL = "http://proxy:8663/1"


  def rpc(method, params):
      resp = requests.post(
          RPC_URL,
          json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params},
          headers={"client-id": "my-service"},
          timeout=10,
      )
      resp.raise_for_status()
      body = resp.json()
      if "error" in body:
          raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
      return body["result"]


  total_supply = rpc(
      "eth_call",
      [{"to": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "data": "0x18160ddd"}, "latest"],
  )
  print(int(total_supply, 16))
  ```

  ```javascript Node.js theme={null}
  const RPC_URL = "http://proxy:8663/1";

  async function rpc(method, params) {
    const res = await fetch(RPC_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json", "client-id": "my-service" },
      body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
    });
    const body = await res.json();
    if (body.error) {
      throw new Error(`${body.error.code}: ${body.error.message}`);
    }
    return body.result;
  }

  const totalSupply = await rpc("eth_call", [
    { to: "0xdAC17F958D2ee523a2206206994597C13D831ec7", data: "0x18160ddd" },
    "latest",
  ]);
  console.log(BigInt(totalSupply));
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"net/http"
  )

  const rpcURL = "http://proxy:8663/1"

  type rpcResponse struct {
  	Result json.RawMessage `json:"result"`
  	Error  *struct {
  		Code    int    `json:"code"`
  		Message string `json:"message"`
  	} `json:"error"`
  }

  func call(method string, params ...any) (json.RawMessage, error) {
  	payload, err := json.Marshal(map[string]any{
  		"jsonrpc": "2.0", "id": 1, "method": method, "params": params,
  	})
  	if err != nil {
  		return nil, err
  	}
  	req, err := http.NewRequest(http.MethodPost, rpcURL, bytes.NewReader(payload))
  	if err != nil {
  		return nil, err
  	}
  	req.Header.Set("Content-Type", "application/json")
  	req.Header.Set("client-id", "my-service")

  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		return nil, err
  	}
  	defer resp.Body.Close()

  	var body rpcResponse
  	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
  		return nil, err
  	}
  	if body.Error != nil {
  		return nil, fmt.Errorf("rpc error %d: %s", body.Error.Code, body.Error.Message)
  	}
  	return body.Result, nil
  }

  func main() {
  	result, err := call("eth_call", map[string]string{
  		"to":   "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  		"data": "0x18160ddd",
  	}, "latest")
  	if err != nil {
  		panic(err)
  	}
  	fmt.Println(string(result))
  }
  ```
</CodeGroup>

响应：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x00000000000000000000000000000000000000000000000000012a0ff6a2ff40"
}
```

`eth_call` 的参数依次是调用对象、区块参数、可选的 `stateOverride` 和 `blockOverrides`。调用对象的字段与以太坊的 `eth_call` 相同：`from`、`to`、`data` / `input`、`value`、`gas`、`gasPrice` 等。

<Tip>
  `eth_*` 方法与标准以太坊兼容，ethers、viem、web3.py 这类库把 `http://proxy:8663/1` 当作普通 RPC 端点即可使用。DeBank 命名空间的方法用它们的原始调用接口发送，例如 ethers 的 `provider.send(method, params)`。
</Tip>

## 批量请求

请求体是数组时按 JSON-RPC 批量处理，一次往返返回多个结果：

```bash theme={null}
curl -s -X POST http://proxy:8663/1 \
  -H 'Content-Type: application/json' \
  -d '[
    { "jsonrpc": "2.0", "id": 1, "method": "eth_blockNumber", "params": [] },
    { "jsonrpc": "2.0", "id": 2, "method": "eth_chainId", "params": [] },
    { "jsonrpc": "2.0", "id": 3, "method": "eth_getBalance",
      "params": ["0xdAC17F958D2ee523a2206206994597C13D831ec7", "latest"] }
  ]'
```

批量请求作为一个整体被路由到同一个节点。要在一个区块上批量执行合约调用，用下面的 `contractMultiCall` 比批量 `eth_call` 更合适：它保证所有调用看到同一份状态。

## 指定区块：`blockCtx`

DeBank 命名空间的方法名没有前缀，用 `blockCtx` 对象声明区块上下文。读取某个历史高度的余额：

```bash theme={null}
curl -s -X POST http://proxy:8663/1 \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getAddressBalance",
    "params": [
      "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      { "block_id": "0x1406f40", "type": "Equals" }
    ]
  }'
```

| `type`     | 含义                      | 路由                                 |
| ---------- | ----------------------- | ---------------------------------- |
| `Equals`   | 精确使用 `block_id` 的状态     | 距链头 64 块内走 State 节点，更早走 Archive 节点 |
| `Contains` | 任何不早于 `block_id` 的状态都可以 | 始终走 State 节点，在 `latest` 上执行        |

省略 `blockCtx` 等同于 `latest`。语义详见[区块上下文与路由](/concepts/block-context)。

## 一次调用多个合约：`contractMultiCall`

在同一份状态上执行一组只读调用，返回每个调用的结果和这份状态对应的区块：

```bash theme={null}
curl -s -X POST http://proxy:8663/1 \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "contractMultiCall",
    "params": [
      [
        { "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "data": "0x18160ddd" },
        { "to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "data": "0x18160ddd" }
      ],
      { "block_id": "latest", "type": "Contains" }
    ]
  }'
```

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "results": [
      { "code": 0, "err": "", "from_cache": false, "result": "0x…", "gas_used": 23675, "time_cost": 0.31 },
      { "code": 0, "err": "", "from_cache": false, "result": "0x…", "gas_used": 30411, "time_cost": 0.28 }
    ],
    "stats": {
      "block_num": 21000000,
      "block_hash": "0x…",
      "block_time": 1735689600,
      "success": true,
      "cache_enabled": false
    }
  }
}
```

参数顺序是 `requests`、`blockCtx`、`blockOverrides`、`stateOverride`、`fastFail`、`useParallel`、`disableCache`。可选参数按位置传入，跳过的位置用 `null` 占位。

| 可选参数                         | 作用              |
| ---------------------------- | --------------- |
| `fastFail`                   | 某个调用失败后不再执行后续调用 |
| `useParallel`、`disableCache` | 当前实现接受但不生效      |

单个调用的 `code` 为 `0` 表示成功；revert 时为 `-39000`，`err` 里是解码后的 revert 原因。整个请求只在参数或区块解析失败时才返回 JSON-RPC 级别的错误。

<Note>
  `eth_multiCall` 是同一能力的 `eth` 命名空间版本：区块参数用标准写法，响应字段是驼峰（`fromCache`、`gasUsed`、`blockNum`），单个调用的 revert 码是 `-40014`。两者不要共用同一套解析代码。
</Note>

## 估算 gas：`estimateGas`

方法名没有 `eth_` 前缀。估算一笔 USDT 转账：

```bash theme={null}
curl -s -X POST http://proxy:8663/1 \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "estimateGas",
    "params": [
      {
        "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
        "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
        "data": "0xa9059cbb00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000005f5e100"
      }
    ]
  }'
```

返回十六进制的 gas 用量。第二个参数是可选的 `blockCtx`，第三个是可选的 `blockOverrides`。

## 模拟交易序列：`simulateTransactions`

按顺序执行一组交易，后一笔看到前一笔的状态变更，返回每笔的调用追踪与事件：

```bash theme={null}
curl -s -X POST http://proxy:8663/1 \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "simulateTransactions",
    "params": [
      [
        {
          "from": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
          "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
          "data": "0xa9059cbb00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000005f5e100"
        }
      ],
      { "block_id": "latest", "type": "Equals" }
    ]
  }'
```

响应的 `results[i]` 包含 `traces`、`events`、`code`、`err`、`gas_used`；`stats` 给出模拟所基于的区块。

## 追踪一次调用：`pre_traceCall`

返回逐指令的 struct log，不需要写节点的 `debug` API：

```bash theme={null}
curl -s -X POST http://proxy:8663/1 \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "pre_traceCall",
    "params": [
      { "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "data": "0x18160ddd" },
      "latest"
    ]
  }'
```

第二个参数是可选的区块参数，写法与 `eth_call` 相同。批量版本是 `pre_traceMany`。

## 处理错误

JSON-RPC 级别的错误出现在响应的 `error` 字段：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -39006, "message": "block … not found for state node" }
}
```

| 错误码                  | 含义                            | 客户端应对                                  |
| -------------------- | ----------------------------- | -------------------------------------- |
| `-39000`             | 执行 revert                     | 业务错误，`message` 含 revert 原因             |
| `-39001` \~ `-39004` | gas 耗尽、余额不足、nonce 错误、EVM 执行失败 | 业务错误，检查调用参数                            |
| `-39006`             | State 节点没有该高度                 | 经 proxy 时已自动转 Archive；直连时改发 Archive 节点 |
| `-39007`             | 区块标识非法                        | 检查高度是否超过链头、哈希是否在规范链上                   |
| `-39008`             | 触及不支持的预编译                     | 经 proxy 时已自动转 Native；直连时改发原链节点         |
| `-41002`             | 执行超时                          | 缩小调用规模或拆分批量请求                          |
| `-32601` / `-32602`  | 方法不存在 / 参数错误                  | 核对方法名（`estimateGas` 无前缀）与参数顺序          |

完整错误码表见 [RPC 参考](/reference/rpc#错误码)。

## 下一步

<Columns cols={2}>
  <Card title="RPC 参考" icon="terminal" href="/reference/rpc">
    全部方法、参数与返回结构。
  </Card>

  <Card title="区块上下文与路由" icon="route" href="/concepts/block-context">
    `Equals` / `Contains` 的语义与 proxy 的路由规则。
  </Card>
</Columns>
