跳到内容

Web3 RPC Starter

受众:开发者 摘要:只读 RPC、链路健康诊断、链配置只读清单、区块浏览器链接、合约只读调用、事件日志查询。

何时使用

场景建议
查询链头 / 余额 / Gas / 交易 / 回执 / 合约代码Web3.client(...)
原始 eth_call / eth_estimateGasWeb3.callRaw(...) / estimateGas(...)
合约 ABI 方法级只读调用Web3.callMethod(...)
事件日志查询 / topic 编码Web3.queryEvents(...)
链路健康诊断(连通性 / chain-id / 高度 / Gas)Web3.diagnose(...)
链配置只读快照(不含 private-rpc-urlsWeb3.chains()
交易确认状态Web3.confirmationStatus(...)

只读 RPC

Web3 运行时支持每条链配置多个 RPC 节点。配置来源可以是本地 spring.open.web3.chains.<chain>.rpc-urls/private-rpc-urls、ConfigManager 数据库配置,也可以是后台 Web3 链列表中的公共 / 私有 RPC 节点。运行时优先使用 private-rpc-urls,为空时回退 rpc-urls;节点成功后记录耗时,后续请求优先使用历史成功延迟低的节点,连接失败或超时会降权并自动尝试下一个节点。RPC 返回业务错误时不会盲目切换节点,避免把合约错误误判为节点故障。

普通业务代码通过 Web3 facade 使用链能力:

java
Web3NetworkInfo network = Web3.network("ethereum");
BigInteger balance = Web3.balance("ethereum", "0x0000000000000000000000000000000000000001");
Web3AccountInfo account = Web3.accountInfo("ethereum", "0x0000000000000000000000000000000000000001");
String code = Web3.code("ethereum", "0x0000000000000000000000000000000000000001");
BigInteger nonce = Web3.transactionCount("ethereum", "0x0000000000000000000000000000000000000001");
Optional<Web3Transaction> tx = Web3.transaction("ethereum", "0x...");
Optional<Web3TransactionReceipt> receipt = Web3.transactionReceipt("ethereum", "0x...");
Web3TransactionConfirmation confirmation = Web3.transactionConfirmation("ethereum", "0x...", 6);
String result = Web3.call("ethereum", null, "0x0000000000000000000000000000000000000001", "0x...");
BigInteger estimatedGas = Web3.estimateGas("ethereum", null, "0x0000000000000000000000000000000000000001", "0x...");

默认链可直接省略链名称:

java
BigInteger blockNumber = Web3.blockNumber();
BigInteger chainId = Web3.chainId();
BigInteger gasPrice = Web3.gasPrice();

Web3.call(...) 只负责发起原始 EVM eth_call,返回链节点返回的十六进制结果。需要方法级 ABI 编解码时,优先使用下面的 contractCall(...)

Web3.code(...) 可用于判断地址是否存在合约代码,EOA 地址通常返回 0xWeb3.transactionCount(...) 默认读取 latest,也可以指定 pending 等区块标签,用于交易前 nonce 诊断。Web3.estimateGas(...) 只调用 eth_estimateGas 做交易前模拟,不签名、不广播,也不会修改链上状态。

Web3.accountInfo(...) 用于后台运维和地址巡检,会组合读取余额、交易计数、合约代码摘要和区块浏览器地址。返回字段不包含私钥、助记词、签名材料或完整合约 bytecode:

字段说明
chainName链名称
address查询地址
nativeCurrency原生币符号
balanceRaw原始最小单位余额
balance按 EVM 18 位精度格式化后的余额文本
transactionCount地址交易计数,可用于 nonce 诊断
contract地址是否存在合约代码
codeSize合约代码字节数;EOA 通常为 0
addressUrl区块浏览器地址链接,未配置浏览器时为空

区块浏览器链接

链配置中的 block-explorer-url 可用于生成后台运维页、日志和通知中的跳转链接:

java
Web3ChainSettings settings = Web3.chain("bsc").settings();

String txUrl = Web3ExplorerLinks.transactionUrl(settings, "0x...").orElse(null);
String addressUrl = Web3ExplorerLinks.addressUrl(settings, "0x0000000000000000000000000000000000000001").orElse(null);
String tokenUrl = Web3ExplorerLinks.tokenUrl(settings, "0x0000000000000000000000000000000000000002").orElse(null);
String blockUrl = Web3ExplorerLinks.blockUrl(settings, BigInteger.valueOf(123456)).orElse(null);

如果未配置 block-explorer-url,或传入的 hash、地址、区块号为空,工具方法会返回 Optional.empty()。该工具只拼接浏览器链接,不校验链上状态,不访问 RPC。

链配置只读清单

链配置只读清单用于后台页面、运维面板和多链选择器加载已启用链。Web3 后台链配置接口只返回非敏感摘要,不访问 RPC,也不会返回 rpc-urlsprivate-rpc-urls、密钥、私钥或签名材料:

java
List<Web3ChainInfo> chains = Web3.chainInfos();
Web3ChainInfo bsc = Web3.chainInfo("bsc");

返回字段:

字段说明
chainName链配置名称
chainType链类型,例如 evm
chainId本地或后台配置中的链 ID
nativeCurrency原生代币符号
blockExplorerUrl区块浏览器基础地址
defaultChain是否默认链
connectTimeoutMsRPC 连接超时,单位毫秒
readTimeoutMsRPC 读取超时,单位毫秒

Money Web3 模块提供后台只读 API:

http
GET /api/v1/money/web3/chains
GET /api/v1/money/web3/chains/{chainName}
GET /api/v1/money/web3/chains/{chainName}/accounts/{address}
GET /api/v1/money/web3/chains/{chainName}/transactions/{hash}
GET /api/v1/money/web3/chains/{chainName}/transactions/{hash}/receipt
GET /api/v1/money/web3/chains/{chainName}/transactions/{hash}/confirmation?requiredConfirmations=12
POST /api/v1/admin/web3/transaction-inputs/decode
POST /api/v1/money/web3/chains/{chainName}/event-logs/search
POST /api/v1/money/web3/chains/{chainName}/calls/raw
POST /api/v1/money/web3/chains/{chainName}/contract-calls
POST /api/v1/money/web3/chains/{chainName}/contract-calls/batch
POST /api/v1/money/web3/chains/{chainName}/gas-estimates
POST /api/v1/money/web3/chains/{chainName}/token-prices
GET  /api/v1/money/web3/chains/{chainName}/erc20/{contractAddress}
GET  /api/v1/money/web3/chains/{chainName}/erc20/{contractAddress}/balances/{ownerAddress}
GET  /api/v1/money/web3/chains/{chainName}/erc20/{contractAddress}/allowances
POST /api/v1/money/web3/chains/{chainName}/erc20/{contractAddress}/transfers/search
GET  /api/v1/money/web3/chains/{chainName}/nfts/{contractAddress}/standards
GET  /api/v1/money/web3/chains/{chainName}/erc721/{contractAddress}
GET  /api/v1/money/web3/chains/{chainName}/erc721/{contractAddress}/balances/{ownerAddress}
GET  /api/v1/money/web3/chains/{chainName}/erc721/{contractAddress}/tokens/{tokenId}/owner
GET  /api/v1/money/web3/chains/{chainName}/erc721/{contractAddress}/tokens/{tokenId}/uri
GET  /api/v1/money/web3/chains/{chainName}/erc721/{contractAddress}/tokens/{tokenId}/approved
GET  /api/v1/money/web3/chains/{chainName}/erc721/{contractAddress}/approved-for-all
POST /api/v1/money/web3/chains/{chainName}/erc721/{contractAddress}/transfers/search
GET  /api/v1/money/web3/chains/{chainName}/erc1155/{contractAddress}/balances/{ownerAddress}/tokens/{tokenId}
POST /api/v1/money/web3/chains/{chainName}/erc1155/{contractAddress}/balances/batch
GET  /api/v1/money/web3/chains/{chainName}/erc1155/{contractAddress}/tokens/{tokenId}/uri
GET  /api/v1/money/web3/chains/{chainName}/erc1155/{contractAddress}/approved-for-all
POST /api/v1/money/web3/chains/{chainName}/erc1155/{contractAddress}/transfer-singles/search
POST /api/v1/money/web3/chains/{chainName}/erc1155/{contractAddress}/transfer-batches/search
POST /api/v1/money/web3/chains/{chainName}/transfers/batch
POST /api/v1/money/web3/chains/{chainName}/fund-collections
GET /api/v1/money/web3/chains/diagnostics
GET /api/v1/money/web3/chains/{chainName}/diagnostics

接口权限:

text
money:web3:chain:view
money:web3:transfer:execute
money:web3:fund-collection:execute

写交易运维接口面向可信后台管理员,用于批量转账和多钱包资金归集:

接口权限说明
POST /api/v1/money/web3/chains/{chainName}/transfers/batchmoney:web3:transfer:execute批量转账真实签名;私钥只来自本次请求,不持久化、不输出,默认 broadcast=false 只返回 rawTransaction
POST /api/v1/money/web3/chains/{chainName}/fund-collectionsmoney:web3:fund-collection:execute多钱包资金归集真实签名;每个钱包私钥只用于本次签名,默认 broadcast=false,显式传入 true 才广播

生产环境建议后台页面先使用计划预览或只读巡检能力完成金额、Gas、nonce、余额和接收地址复核,再由具备权限的管理员执行真实签名接口。broadcast=true 会真实发链上交易,应搭配操作日志、审计和二次确认使用。Audit 写操作日志只记录请求路径、query 和 path 参数,不记录请求体;敏感 query/path 参数会脱敏,因此不会把一次性私钥写入操作日志。

链路健康诊断

链路健康诊断用于启动后自检、后台运维页、监控探针和多链配置排查。它只读取 RPC 状态,不暴露 rpc-urls,也不会发起签名或广播:

java
Web3ChainHealth health = Web3.health("ethereum");

if (!health.isHealthy()) {
    // 可在后台运维页展示 errorMessage,并检查 RPC 地址、chain-id 或节点可用性。
}

List<Web3ChainHealth> all = Web3.healthAll();

返回字段:

字段说明
chainName链配置名称
chainType链类型,例如 evm
configuredChainId本地或后台配置中的链 ID
chainIdRPC 实际返回的链 ID
blockNumber当前区块高度
gasPrice当前 Gas Price,单位 wei
nativeCurrency原生代币符号
blockExplorerUrl区块浏览器基础地址;不包含 RPC URL,可用于后台页面跳转
chainIdMatched配置链 ID 与 RPC 链 ID 是否匹配
healthyRPC 可用且链 ID 匹配时为 true
durationMs本次 RPC 健康诊断耗时,单位毫秒
errorMessage连接失败或链 ID 不匹配时的诊断信息

生产环境建议显式配置 chain-id,避免 RPC 地址误配到其他链却被业务误用。未配置 chain-id 时,诊断只校验 RPC 连通性。

Money Web3 模块提供后台运维 API:

http
GET /api/v1/money/web3/chains/health
GET /api/v1/money/web3/chains/health/summary
GET /api/v1/money/web3/chains/{chainName}/health

接口权限:

text
money:web3:chain:view

健康明细接口只返回链名称、链类型、配置链 ID、RPC 返回链 ID、区块高度、Gas Price、原生币符号、链 ID 是否匹配、健康状态、诊断耗时和错误信息。接口不会返回 RPC URL、密钥、私钥或任何可用于签名交易的敏感配置。

健康摘要接口返回:

字段说明
totalCount已启用链数量
healthyCount健康链数量
unhealthyCount异常链数量
chainIdMismatchCountRPC 可连接但链 ID 不匹配数量
rpcFailedCountRPC 失败或没有返回链头信息数量
averageDurationMs本轮诊断平均耗时,单位毫秒
maxDurationMs本轮诊断最大耗时,单位毫秒
slowestChainName本轮诊断最慢链名称
healthy所有链均健康时为 true

交易确认状态

交易确认状态用于支付补偿、链上任务确认、通知提醒等场景。它只读取交易回执和当前区块高度,不修改任何业务订单或资金状态:

java
Web3TransactionConfirmation confirmation = Web3.transactionConfirmation(
        "ethereum",
        "0x0000000000000000000000000000000000000000000000000000000000000000",
        6);

if (confirmation.isConfirmed()) {
    // 达到业务要求确认数,后续业务模块再按自己的幂等规则处理状态。
}

字段含义:

字段说明
mined是否已有交易回执
success链上执行状态是否成功,兼容 0x1 / 1
failed链上执行状态是否失败,兼容 0x0 / 0
confirmed交易成功且确认数达到 requiredConfirmations
confirmations当前确认数,按 currentBlock - txBlock + 1 计算
requiredConfirmations业务要求确认数,小于 1 时按 1 处理

业务模块不要直接把 confirmed=true 等同于本地订单可更新;本地状态更新仍必须放在 Payment 或业务 Service 中做幂等、金额、地址、链 ID 和事件匹配校验。

合约只读调用

需要调用合约只读方法时,使用 Web3ContractCall 描述合约地址、函数名、入参和返回类型:

java
Web3ContractCall call = Web3ContractCall.builder()
        .contractAddress("0x0000000000000000000000000000000000000002")
        .functionName("balanceOf")
        .input(Web3ContractParameter.address("0x0000000000000000000000000000000000000001"))
        .output(Web3ContractTypes.UINT256)
        .build();

Web3ContractCallResult result = Web3.contractCall("ethereum", call);
BigInteger balance = (BigInteger) result.first().getValue();

如果页面或开放 API 需要一次性读取多个只读方法,可以使用批量调用。批量调用仍然只是顺序执行多个 eth_call,不使用私钥、不签名、不广播:

java
Web3ContractBatchCallResult result = Web3.contractBatchCall("ethereum",
        Web3ContractBatchCallRequest.builder()
                .call(Web3ContractCall.of("0x0000000000000000000000000000000000000002", "name"))
                .call(Web3ContractCall.of("0x0000000000000000000000000000000000000002", "symbol"))
                .continueOnError(true)
                .build());

Money Web3 模块也提供对应后台只读 API:

http
POST /api/v1/money/web3/chains/{chainName}/contract-calls/batch

continueOnError=false 时任意一条调用失败会直接返回异常;设置为 true 时,失败项会出现在 items[].success=false,后续调用继续执行。

当前入参优先支持常见原子类型:

text
address
bool
string
bytes
bytes1 ~ bytes32
uint / uint8 ~ uint256
int / int8 ~ int256

数组类型支持一维动态数组、一维静态数组和多维数组入参:

java
Web3ContractParameter.array("address[]", List.of(
        "0x0000000000000000000000000000000000000001",
        "0x0000000000000000000000000000000000000002"));

Web3ContractParameter.array("uint256[2]", List.of(BigInteger.ONE, BigInteger.TWO));

Web3ContractParameter.array(
        Web3ContractTypes.array(Web3ContractTypes.array(Web3ContractTypes.UINT256)),
        List.of(List.of(BigInteger.ONE, BigInteger.TWO), List.of(BigInteger.valueOf(3))));

Web3ContractParameter.array(
        Web3ContractTypes.staticArray(Web3ContractTypes.staticArray(Web3ContractTypes.UINT256, 2), 2),
        List.of(List.of(BigInteger.ONE, BigInteger.TWO),
                List.of(BigInteger.valueOf(3), BigInteger.valueOf(4))));

输出类型使用同样的 ABI 类型字符串,例如 uint256[]address[]uint256[2]uint256[2][2]uint256[][]。当前返回值解码支持一维数组、动态/静态多维基础类型数组和 tuple 数组,例如 uint256[][]uint256[2][3]address[2][3]bytes[](address,uint256)[](address,uint256)[2](address,uint256)[][](address,uint256)[2][](address,string)(address,string)[](address,string)[][]。当前合约调用仍然基于 eth_call,只读、不改链上状态、不签名、不广播。

tuple 类型支持一层结构体输入和输出,可使用 (address,uint256)tuple(address,uint256)

java
String positionType = Web3ContractTypes.tuple(
        Web3ContractTypes.ADDRESS,
        Web3ContractTypes.UINT256);

Web3ContractCall call = Web3ContractCall.builder()
        .contractAddress("0x0000000000000000000000000000000000000002")
        .functionName("positionOf")
        .input(Web3ContractParameter.address("0x0000000000000000000000000000000000000001"))
        .output(positionType)
        .build();

List<?> position = (List<?>) Web3.contractCall("ethereum", call).first().getValue();

tuple 返回值会按 ABI 字段顺序映射成 List<?>。当前 tuple 支持一层结构体输入输出、一维 tuple 数组、多维 tuple 数组,以及 tuple 内部常见动态字段,例如 stringbytes、数组和嵌套 tuple。常用 Token/NFT 写交易已由专用 API 覆盖;任意 ABI 写调用仍作为高风险扩展保留。

tuple 数组可用于读取批量结构体结果,数组元素仍按 ABI 字段顺序映射成 List<?>

java
String positionType = Web3ContractTypes.tuple(Web3ContractTypes.ADDRESS, Web3ContractTypes.UINT256);
String positionArrayType = Web3ContractTypes.array(positionType);

Web3ContractCall call = Web3ContractCall.builder()
        .contractAddress("0x0000000000000000000000000000000000000002")
        .functionName("positions")
        .output(positionArrayType)
        .build();

List<?> positions = (List<?>) Web3.contractCall("ethereum", call).first().getValue();

当前 tuple 数组支持一维动态数组、一维静态数组、动态多维数组和常见静态内层数组。tuple 字段可以使用 address、bool、bytesN、int、uint 等静态原子类型,也可以使用 stringbytes、数组或嵌套 tuple 等常见动态组合。内置回归样本已覆盖 Uniswap V3 exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))、Multicall aggregate((address,bytes)[]) 和动态 bytes[] 返回值;非常复杂的深层嵌套 ABI 建议先补单元测试再接入真实合约。

事件日志查询

需要查询合约事件时,使用 Web3EventDefinition 描述事件名称和参数,使用 Web3EventLogQuery 描述区块范围、合约地址和额外 topic:

java
Web3EventDefinition transfer = Web3EventDefinition.builder()
        .eventName("Transfer")
        .parameter(Web3EventParameter.address("from", true))
        .parameter(Web3EventParameter.address("to", true))
        .parameter(Web3EventParameter.uint256("value", false))
        .build();

List<Web3EventLog> logs = Web3.eventLogs("ethereum", Web3EventLogQuery.range(
        BigInteger.valueOf(18_000_000L),
        BigInteger.valueOf(18_000_100L),
        "0x0000000000000000000000000000000000000002",
        transfer));

BigInteger amount = Web3EventValues.bigInteger(logs.get(0), "value");
String from = Web3EventValues.text(logs.get(0), "from");

事件查询会自动把事件签名作为 topic0,并解码 indexed 和 non-indexed 参数:

  • 必须传入至少一个合约地址,避免生产环境误扫全链日志。
  • indexed 静态类型会从 topic 中解码,例如 addressuint256
  • indexed 动态类型只能读取 topic hash,例如 stringbytes,链上日志本身不包含原文。
  • 不传 event 时返回原始日志,只填充区块、交易、地址、data、topics 等字段。

业务 handler 读取事件参数时,优先使用 Web3EventValues,不要在业务代码中散落 getValues().get(2) 这类位置读取逻辑:

java
for (Web3EventLog log : logs) {
    String from = Web3EventValues.text(log, "from");
    String to = Web3EventValues.text(log, "to");
    BigInteger value = Web3EventValues.bigInteger(log, "value");
}

自定义事件查询需要手动传 indexed topic 时,使用 Web3EventTopics,不要在业务里重复拼 32 字节十六进制字符串:

java
Web3EventLogQuery query = Web3EventLogQuery.builder()
        .fromBlock("18000000")
        .toBlock("18001000")
        .address(contractAddress)
        .topic(Web3EventTopics.address(ownerAddress))
        .topic(Web3EventTopics.uint256(BigInteger.ONE))
        .build();

Web3EventTopics 会校验并规范化 32 字节 topic,当前提供:

方法说明
address(address)indexed address
uint256(value)indexed uint256
int256(value)indexed int256,负数按 256 位补码编码
bool(value)indexed bool
bytes32(value)indexed bytes32
topic(value)原始 32 字节 topic 校验和规范化

Web3.eventLogs(...) 会统一校验合约地址和额外 topic。空 topic 会作为通配 topic 传给节点;非空 topic 必须是 32 字节十六进制字符串。

区块范围支持数字字符串,也支持 earliestlatestpendingsafefinalized。为了避免 RPC 节点超时,生产环境应按业务链的节点限制拆分区块范围,不建议一次查询过大的历史区间。

相关

Released under the Apache License 2.0.