以太坊(EVM)
Web3交易所对象选择ChainType为ETH时,可以连接以太坊以及所有EVM兼容链(BSC、Base、Arbitrum、Optimism、Polygon等)的节点,用exchange.IO()的各个指令查询余额、调用合约、发送交易。本页按一笔链上操作的流程介绍常用指令,每个指令的完整参数见语法手册Web3分类中对应的exchange.IO("指令", ...)。
只是想在Uniswap、PancakeSwap上兑换代币时,请使用Uniswap交易所对象:它直接支持exchange.GetTicker()、exchange.CreateOrder()等标准函数,不需要自己编码合约调用。
1. 配置交易所对象
在「交易所」页面(/m/add-platform)添加交易所,协议选择「加密货币」,交易所选择Web3:
| 字段 | 说明 |
|---|---|
| ChainType | ETH:以太坊及所有EVM兼容链;TRON:波场,见波场(TRON) |
| Private Key | 钱包私钥(十六进制字符串,可以带0x前缀)。支持把私钥本地化部署在托管者上,参看密钥安全性 |
| Rpc Address | 节点地址,默认https://ethereum-rpc.publicnode.com(以太坊主网公共节点)。连接其它链时填写该链的节点,例如BSC:https://bsc-dataseed.binance.org。支持http(s)://和ws(s)://。可以写多个节点,用逗号分隔,互为备用 |
| Rpc Api Key | 节点鉴权,可以留空。写成名称: 值(如x-api-key: xxx)时作为该名称的请求头发送;否则作为Authorization: Basic <值>发送 |
多个节点时,从上次成功的节点开始依次尝试:只有节点不可用(连接失败、超时、限流)时才换下一个,合约执行失败之类的错误直接返回;链ID与第一个节点不同的节点会被跳过,避免把交易发到另一条链。
运行中可以用exchange.IO("base", 节点地址)切换节点(多个节点可以传数组或逗号分隔的字符串),用exchange.IO("key", 私钥)切换钱包私钥,用exchange.IO("address")获取当前钱包地址。
标准函数中只有exchange.GetAccount()、exchange.GetAssets()可用,返回钱包的原生币余额(币种按链ID识别,如BSC为BNB)。
2. 查询余额与读取合约
调用合约的只读方法(view/pure)不消耗gas,直接返回解码后的结果:
javascript
exchange.IO("api", "eth", "eth_getBalance", wallet, "latest") // 原生币余额,链上整数(十六进制字符串)
exchange.IO("api", tokenAddress, "balanceOf", wallet) // ERC20余额,链上整数
exchange.IO("api", tokenAddress, "decimals") // 代币精度
exchange.IO("api", "eth", 方法, ...参数)直接调用节点的JSON-RPC方法,如eth_gasPrice、eth_blockNumber、eth_getTransactionReceipt。exchange.IO("api", 合约地址, 方法, ...参数)调用合约方法。方法可以写方法名、完整签名(如"approve(address,uint256)",用于区分重载)或方法选择器(如"0x095ea7b3")。- 链上数量都是整数。用
exchange.IO("fromUnits", 链上整数, 精度)换算成可读数量,用exchange.IO("toUnits", "1.5", 精度)换算回链上整数;精度也可以直接传代币合约地址。两者都按字符串精确计算。 - 批量读取多个合约时用
exchange.IO("multicall", ...)一次请求完成;查询事件日志用exchange.IO("logs", ...)。
3. 注册ABI
标准ERC20方法(balanceOf、decimals、allowance、approve、transfer等)已内置,不需要注册。调用其它合约的方法前,需要用exchange.IO("abi", 合约地址, ABI)注册该合约的ABI。
常用合约可以直接使用内置模板,第三个参数传模板名:"weth"、"uniswapV3Pool"、"uniswapV3Factory"、"uniswapV3QuoterV2"、"uniswapV3SwapRouter02"、"uniswapV3PositionManager"、"permit2"(PancakeSwap V3使用相同的模板,也可以写"pancakeV3Pool"等别名)。常用合约的地址可以用exchange.IO("contracts")查询。
javascript
exchange.IO("abi", poolAddress, "uniswapV3Pool")
var slot0 = exchange.IO("api", poolAddress, "slot0")
其它合约的ABI可以从区块浏览器获取,例如Etherscan的V2接口(需要Etherscan的API Key,chainid为链ID,取返回结果中的result字段):
url
https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getabi&address=0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45&apikey=YourApiKey
4. 发送交易
调用合约的写方法时,交易所对象用配置的私钥签名并广播交易,返回交易哈希。发送前可以把"api"换成"call",用exchange.IO("call", ...)预演同一笔调用:在节点上模拟执行,不签名、不消耗gas,执行失败时返回空值,GetLastError()中有合约给出的失败原因。
以授权(approve)为例:
javascript
var amount = exchange.IO("toUnits", "100", tokenAddress) // 100个代币换算成链上整数
var txHash = exchange.IO("api", tokenAddress, "approve", spender, amount)
方法的stateMutability为payable时,方法参数之前要多传一个参数:附带的原生币数量(链上整数)。最后一个参数可以传选项对象:
| 选项 | 说明 |
|---|---|
| gasLimit | gas上限。不传时由节点估算(eth_estimateGas)。合约调用不要写21000,那只够普通转账 |
| gasPrice | 固定gas价格,传入时发送传统(legacy)交易。不传时,支持EIP-1559的链发送EIP-1559交易:小费取节点建议值与最近区块实际小费的较大者,最高费用为2 × baseFee + 小费 |
| nonce | 指定nonce。不传时自动分配,并与链上待处理计数同步,连续发送不会重复使用nonce |
| dryRun | 设为true时只签名不广播,返回hash、raw(签名后的交易)、nonce、gasLimit等字段,可用于检查交易或交给其它渠道发送 |
转出原生币使用exchange.IO("api", "eth", "send", 收款地址, 数量),数量是链上整数(wei)。它的选项还支持data(十六进制调用数据),用于原样发送聚合器等API返回的交易{to, data, value},此时gas按合约调用估算。注意:普通转账不传gasPrice时按固定的100 Gwei出价、gas上限为21000,在以太坊主网上通常偏高,建议先用eth_gasPrice查询后传入。
需要把交易发到私有交易通道(如Flashbots Protect、MEV Blocker)避免被抢跑时,用exchange.IO("sendBase", 节点地址)设置只用于广播交易的节点。
5. 等待交易上链
exchange.IO("waitReceipt", 交易哈希, {timeout, confirmations})等待交易上链并达到确认数,返回交易回执:status为1表示成功、0表示失败(revertReason为失败原因),events为按已注册ABI解码的事件。超时未上链时返回空值。
6. nonce管理、加速与取消
exchange.IO("nonce")查看链上与本地的nonce计数;exchange.IO("nonce", "sync")按链上重新同步(在别处用同一个钱包发过交易后使用)。- 交易长时间未上链时,用
exchange.IO("speedUp", 交易哈希)以同一个nonce、更高的手续费重发;用exchange.IO("cancelTx", 交易哈希)发送一笔同nonce、转给自己的0金额交易顶替原交易。两者都在原交易上链前才有效。 - 本地nonce记录只在当前实盘内有效:多个实盘共用一个钱包时彼此看不到对方的记录,仍可能冲突,建议每个实盘使用独立的钱包。
其它指令
- 编码与解码:
exchange.IO("encode", ...)编码合约调用数据或按类型编码(同Solidity的abi.encode),exchange.IO("encodePacked", ...)紧凑编码(如Uniswap V3的兑换路径),exchange.IO("decode", ...)按类型解码。 - 签名:
exchange.IO("sign", ...)对32字节哈希签名,exchange.IO("signTypedData", ...)对EIP-712结构化数据签名(如ERC-20 Permit),exchange.IO("signMessage", ...)对消息做EIP-191签名。 - Uniswap V3数学:
exchange.IO("uniswapV3", ...)在tick、价格、sqrtPrice之间换算,在流动性与代币数量之间换算。 - 哈希:
exchange.IO("hash", "keccak256", "raw", "hex", 文本)计算keccak256等摘要,可用于计算方法选择器、EIP-712摘要,参数与Encode()函数相同。 - 完整范例:通过聚合器兑换(询价、生成交易、用
exchange.IO("call", ...)预演、带data发送)见手册中exchange.IO("call", ...)的范例;ERC-20 Permit签名并由合约验签见exchange.IO("sign", ...)、exchange.IO("signTypedData", ...)的范例。
示例
示例:查余额、授权并等待上链
以以太坊主网的USDC为例。注意这段代码会发出真实交易、消耗gas。
javascript
function main() {
var usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // 以太坊主网 USDC
var spender = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" // 被授权的合约,这里以 Uniswap SwapRouter02 为例
var wallet = exchange.IO("address")
// 查余额:标准 ERC20 方法不需要注册 ABI
var eth = exchange.IO("fromUnits", exchange.IO("api", "eth", "eth_getBalance", wallet, "latest"), 18)
var usdcBalance = exchange.IO("fromUnits", exchange.IO("api", usdc, "balanceOf", wallet), usdc)
Log("ETH:", eth, "USDC:", usdcBalance)
// 读合约:当前授权额度
var allowance = exchange.IO("api", usdc, "allowance", wallet, spender)
Log("当前授权:", exchange.IO("fromUnits", allowance, usdc))
// 授权 100 USDC:先预演,再发送
var amount = exchange.IO("toUnits", "100", usdc)
if (!exchange.IO("call", usdc, "approve", spender, amount)) {
Log("预演失败:", GetLastError())
return
}
var txHash = exchange.IO("api", usdc, "approve", spender, amount)
Log("交易哈希:", txHash)
// 等待上链,最多 3 分钟
var receipt = exchange.IO("waitReceipt", txHash, {timeout: 180000})
if (receipt && receipt.status == 1) {
Log("授权成功,区块:", receipt.blockNumber)
} else if (receipt) {
Log("交易失败:", receipt.revertReason)
} else {
// 未上链:可以用 speedUp 加价重发,或 cancelTx 取消
Log("超时未上链:", GetLastError())
}
}参考