输入/搜索内容
内置函数
结构体
内置变量与常量
通用约定
交易所
能力矩阵
中心化交易所
去中心化交易所
预测市场
证券与期货
链上与自定义接入

通过eth_call模拟执行智能合约的任意方法(包括会修改链上状态的写入方法),该过程不签名、不广播交易、不消耗gas,适用于链上询价、交易预演以及检查交易能否成功执行。

exchange.IO(k, address, method, ...args)
exchange.IO(k, address, calldata)
exchange.IO(k, address, calldata, options)

示例

  • 使用 Uniswap QuoterV2 进行询价,并模拟执行写入操作。

    javascript
    function main() { var WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" var USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" var QUOTER = "0x61fFE014bA17989E743c5F6cB21bF9697530B21e" // Uniswap V3 QuoterV2(以太坊主网) // QuoterV2 的询价方法的状态可变性为 nonpayable,使用 exchange.IO("api", ...) 调用时会被视为写入操作并发送交易; // 使用 exchange.IO("call", ...) 调用时仅模拟执行,不签名、不广播、不消耗 gas exchange.IO("abi", QUOTER, JSON.stringify([{ type: "function", name: "quoteExactInputSingle", stateMutability: "nonpayable", inputs: [{ name: "params", type: "tuple", components: [ { name: "tokenIn", type: "address" }, { name: "tokenOut", type: "address" }, { name: "amountIn", type: "uint256" }, { name: "fee", type: "uint24" }, { name: "sqrtPriceLimitX96", type: "uint160" }] }], outputs: [{ name: "amountOut", type: "uint256" }, { name: "sqrtPriceX96After", type: "uint160" }, { name: "initializedTicksCrossed", type: "uint32" }, { name: "gasEstimate", type: "uint256" }] }])) // 询价:1 WETH 在 0.05% 费率池中可兑换多少 USDC var q = exchange.IO("call", QUOTER, "quoteExactInputSingle", [WETH, USDC, "1000000000000000000", 500, 0]) Log("amountOut:", q.amountOut, "gasEstimate:", q.gasEstimate) // 模拟写入:以当前钱包地址的身份执行 transfer,余额不足时返回 null,可通过 GetLastError() 获取合约返回的失败原因 var ok = exchange.IO("call", USDC, "transfer", "0x1111111254EEB25477B68fb85Ed929f73A960582", 1) Log("transfer 模拟:", ok, GetLastError()) // 通过 from 参数指定以其他地址的身份进行模拟 Log("指定 from 模拟:", exchange.IO("call", USDC, "transfer", "0x1111111254EEB25477B68fb85Ed929f73A960582", 1, { from: "0x55FE002aefF02F77364de339a1292923A15844B8" })) }
  • 使用聚合器(KyberSwap 公开 API)完成询价、生成交易、预演和发送交易的完整流程。DRY_RUN为true时,仅签名而不广播交易。

    javascript
    // 聚合器询价与兑换示例(使用 KyberSwap 公开 API,无需 key):询价 → 生成交易 → eth_call 预演 → 发送 // DRY_RUN = true 时仅签名、不广播 var DRY_RUN = true // KyberSwap 使用的链名称:ethereum / bsc / base / arbitrum … var CHAIN = "ethereum" var NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" // 聚合器约定的原生代币地址 var TOKEN_IN = NATIVE // 卖出:ETH var TOKEN_OUT = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // 买入:USDC var AMOUNT_IN = "10000000000000000" // 0.01 ETH(以最小单位表示) var SLIPPAGE_BPS = 50 // 0.5% // 预演使用的发送方地址:默认为当前钱包;钱包余额不足时预演必然失败,可临时改为持币地址,仅用于验证路由能否成交 var SIMULATE_FROM = "" var API = "https://aggregator-api.kyberswap.com/" + CHAIN + "/api/v1" var HEADERS = { "x-client-id": "fmz-example", "Content-Type": "application/json" } function getJSON(url, opts) { var r = JSON.parse(HttpQuery(url, opts || { headers: HEADERS })) if (r.code !== 0) throw "aggregator error: " + JSON.stringify(r) return r.data } // 卖出 ERC20 代币前需先授权给路由合约;原生代币无需授权 function ensureAllowance(token, spender, amount) { if (token.toLowerCase() === NATIVE.toLowerCase()) return var owner = exchange.IO("address") var allowance = BigInt(exchange.IO("api", token, "allowance", owner, spender)) if (allowance >= BigInt(amount)) return Log("授权", token, "→", spender) var tx = exchange.IO("api", token, "approve", spender, amount, DRY_RUN ? { dryRun: true } : {}) Log("approve:", DRY_RUN ? tx.hash + "(dryRun,未广播)" : tx) } function main() { var wallet = exchange.IO("address") // 1. 询价 var route = getJSON(API + "/routes?tokenIn=" + TOKEN_IN + "&tokenOut=" + TOKEN_OUT + "&amountIn=" + AMOUNT_IN, { headers: HEADERS }) var s = route.routeSummary Log("询价:", s.amountIn, "→", s.amountOut, "(约 $" + Number(s.amountOutUsd).toFixed(2) + "),gas≈" + s.gas) // 2. 生成交易:聚合器返回路由合约地址、calldata 以及需附带的原生代币数量 var from = SIMULATE_FROM || wallet var built = getJSON(API + "/route/build", { method: "POST", headers: HEADERS, body: JSON.stringify({ routeSummary: s, sender: from, recipient: from, slippageTolerance: SLIPPAGE_BPS, deadline: Math.floor(Date.now() / 1000) + 600, }), }) var router = built.routerAddress var value = built.transactionValue || "0" Log("路由合约:", router, "| calldata", (built.data.length - 2) / 2, "字节 | value", value) // 3. 预演:将完整的 calldata 交给 IO("call") 执行,不签名、不广播;失败时返回合约给出的 revert 原因 var ret = exchange.IO("call", router, built.data, { from: from, value: value }) if (ret === null) { Log("预演失败,不发送:", GetLastError()) return } var decoded = exchange.IO("decode", "uint256,uint256", String(ret).replace(/^0x/, "")) Log("预演成功,实际可得:", decoded[0], "(报价", s.amountOut + ")") // 4. 发送:授权(卖出 ERC20 代币时)→ 附带 calldata 发送交易;gasLimit 取预估值的 1.3 倍 ensureAllowance(TOKEN_IN, router, AMOUNT_IN) var opts = { data: built.data, gasLimit: Math.ceil(Number(built.gas || s.gas) * 1.3) } if (DRY_RUN) opts.dryRun = true var tx = exchange.IO("api", "eth", "send", router, value, opts) if (DRY_RUN) { Log("已签名(dryRun,未广播):hash", tx && tx.hash, "| nonce", tx && tx.nonce, "| type", tx && tx.type) } else { Log("已发送:", tx) } }

返回值

类型描述

string / number / bool / object / array

使用方法名调用时,返回按ABI解码后的返回值(有多个返回值时为对象);方法没有返回值时,执行成功返回true。使用calldata调用时,返回原始返回数据(以0x开头的十六进制字符串),可以使用exchange.IO("decode", ...)进行解码。执行失败(合约revert)时返回空值,可以通过GetLastError()获取合约给出的失败原因。

参数

名称类型必填描述

k

string

是

固定为call,用于模拟执行合约方法。

address

string

是

智能合约的地址。

method

string

否

要模拟执行的方法,可以是方法名、完整的方法签名(例如"transfer(address,uint256)",用于区分重载方法)或方法选择器。使用方法名时,需要先通过exchange.IO("abi", ...)注册合约ABI(ERC20标准方法已内置,无需注册)。

calldata

string

否

完整的调用数据(以0x开头的十六进制字符串,由方法选择器和编码后的参数组成),例如聚合器API返回的交易data。传入calldata时无需注册ABI,函数返回原始返回数据。

args

string / number / bool / object / array / any

否

方法的参数,排列方式与exchange.IO("api", ...)调用合约方法时完全相同(payable方法第一个参数为附带的原生币数量),把api换成call即可预演同一笔调用。最后一个参数可以是选项对象{from, value, block, gasLimit}:from指定以哪个地址的身份执行(默认为配置的钱包地址,没有配置私钥时为零地址),value为附带的原生币数量,block为执行时的区块(默认latest,可设置区块高度或pending等标签),gasLimit为执行的gas上限。

参考

备注

Uniswap QuoterV2等合约的询价方法声明为nonpayable,使用exchange.IO("api", ...)调用时会被当作写操作,签名并发送交易,因此询价应使用exchange.IO("call", ...)。

合约revert的原因会被解析为可读文本:Error(string)返回原因字符串,Panic(uint256)返回错误码及其含义,自定义错误返回错误选择器和原始数据。

需要实际发送交易时,可以使用exchange.IO("api", "eth", "send", toAddress, value, {data: calldata})发送任意调用数据;设置dryRun: true时只签名、不广播。

目前仅支持以太坊(EVM)链,不支持波场(TRON)。