Type/to search
Built-in Functions
Structures
Built-in Variables and Constants
Conventions
Exchanges
Capability Matrix
Centralized Exchanges
Decentralized Exchanges (DEX)
Prediction Markets
Securities and Futures
On-chain & Custom

Simulates the execution of any smart contract method (including write methods that modify on-chain state) via eth_call. The process does not sign or broadcast a transaction and consumes no gas, making it suitable for on-chain quoting, transaction dry runs, and checking whether a transaction can execute successfully.

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

Examples

  • Use Uniswap QuoterV2 to get a quote and simulate the execution of write operations.

    javascript
    function main() { var WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" var USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // Uniswap V3 QuoterV2 (Ethereum mainnet) var QUOTER = "0x61fFE014bA17989E743c5F6cB21bF9697530B21e" // The quote methods of QuoterV2 have a state mutability of nonpayable; when called with // exchange.IO("api", ...), they are treated as write operations and a transaction is sent; // when called with exchange.IO("call", ...), the execution is only simulated: no signing, no // broadcasting, and no gas consumed 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" }] }])) // Quote: how much USDC 1 WETH can be swapped for in the 0.05% fee tier pool var q = exchange.IO("call", QUOTER, "quoteExactInputSingle", [WETH, USDC, "1000000000000000000", 500, 0]) Log("amountOut:", q.amountOut, "gasEstimate:", q.gasEstimate) // Simulated write: execute transfer as the current wallet address; returns null if the balance // is insufficient, and the revert reason returned by the contract can be obtained via // GetLastError() var ok = exchange.IO("call", USDC, "transfer", "0x1111111254EEB25477B68fb85Ed929f73A960582", 1) Log("transfer simulation:", ok, GetLastError()) // Use the from parameter to simulate the call as another address Log("simulation with specified from:", exchange.IO("call", USDC, "transfer", "0x1111111254EEB25477B68fb85Ed929f73A960582", 1, { from: "0x55FE002aefF02F77364de339a1292923A15844B8" })) }
  • Use an aggregator (the KyberSwap public API) to complete the full workflow of getting a quote, building the transaction, simulating it, and sending it. When DRY_RUN is true, the transaction is only signed and not broadcast.

    javascript
    // Aggregator quote and swap example (using the KyberSwap public API, no key required): quote → // build transaction → eth_call simulation → send // When DRY_RUN = true, only sign without broadcasting var DRY_RUN = true // Chain name used by KyberSwap: ethereum / bsc / base / arbitrum … var CHAIN = "ethereum" // Native token address as defined by aggregator convention var NATIVE = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" var TOKEN_IN = NATIVE // Sell: ETH var TOKEN_OUT = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // Buy: USDC var AMOUNT_IN = "10000000000000000" // 0.01 ETH (in the smallest unit) var SLIPPAGE_BPS = 50 // 0.5% // Sender address used for simulation: defaults to the current wallet; if the wallet balance is // insufficient, the simulation will inevitably fail, so you can temporarily change it to an address // holding the token, solely to verify whether the route can be executed 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 } // Before selling an ERC20 token, it must first be approved for the router contract; the native // token requires no approval 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("Approve", token, "→", spender) var tx = exchange.IO("api", token, "approve", spender, amount, DRY_RUN ? { dryRun: true } : {}) Log("approve:", DRY_RUN ? tx.hash + " (dryRun, not broadcast)" : tx) } function main() { var wallet = exchange.IO("address") // 1. Get a quote var route = getJSON(API + "/routes?tokenIn=" + TOKEN_IN + "&tokenOut=" + TOKEN_OUT + "&amountIn=" + AMOUNT_IN, { headers: HEADERS }) var s = route.routeSummary Log("Quote:", s.amountIn, "→", s.amountOut, "(approx. $" + Number(s.amountOutUsd).toFixed(2) + "), gas≈" + s.gas) // 2. Build the transaction: the aggregator returns the router contract address, the calldata, // and the amount of native token to attach 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 contract:", router, "| calldata", (built.data.length - 2) / 2, "bytes | value", value) // 3. Simulate: pass the complete calldata to IO("call") for execution, without signing or // broadcasting; on failure, the revert reason given by the contract is returned var ret = exchange.IO("call", router, built.data, { from: from, value: value }) if (ret === null) { Log("Simulation failed, not sending:", GetLastError()) return } var decoded = exchange.IO("decode", "uint256,uint256", String(ret).replace(/^0x/, "")) Log("Simulation succeeded, actual amount out:", decoded[0], "(quoted", s.amountOut + ")") // 4. Send: approve (when selling an ERC20 token) → send the transaction with the calldata // attached; gasLimit is set to 1.3x the estimated value 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("Signed (dryRun, not broadcast): hash", tx && tx.hash, "| nonce", tx && tx.nonce, "| type", tx && tx.type) } else { Log("Sent:", tx) } }

Returns

TypeDescription

string / number / bool / object / array

When called with a method name, returns the ABI-decoded return value (an object when there are multiple return values); when the method has no return value, returns true on successful execution. When called with calldata, returns the raw return data (a hexadecimal string starting with 0x), which can be decoded using exchange.IO("decode", ...). When execution fails (the contract reverts), a null value is returned, and the failure reason given by the contract can be obtained via GetLastError().

Arguments

NameTypeRequiredDescription

k

string

Yes

Function of the exchange.IO() function. Setting it to call indicates that the function is used to simulate the execution of a contract method.

address

string

Yes

Address of the smart contract.

method

string

No

Method to simulate. It can be a method name, a full method signature (e.g. "transfer(address,uint256)", used to distinguish overloaded methods), or a method selector. When using a method name, the contract ABI must first be registered via exchange.IO("abi", ...) (ERC20 standard methods are built in and do not require registration).

calldata

string

No

Complete call data (a hexadecimal string starting with 0x, consisting of the method selector and the encoded arguments), such as the transaction data returned by an aggregator API. When calldata is passed, no ABI registration is required and the function returns the raw return data.

args

string / number / bool / object / array / any

No

Method arguments, laid out exactly as when calling a contract method with exchange.IO("api", ...) (for a payable method the first one is the amount of native coin sent), so replacing api with call rehearses the same call. The last argument can be an options object {from, value, block, gasLimit}: from is the address to execute as (the configured wallet address by default, or the zero address when no private key is configured), value is the amount of native coin sent, block is the block to execute on (latest by default; a block number or a tag such as pending can be given), and gasLimit is the gas limit for the execution.

See Also

Remarks

The quoting methods of contracts such as Uniswap QuoterV2 are declared as nonpayable. When called with exchange.IO("api", ...), they are treated as write operations, and a transaction is signed and sent. Therefore, exchange.IO("call", ...) should be used for quoting.

The contract revert reason is parsed into readable text: Error(string) returns the reason string, Panic(uint256) returns the error code and its meaning, and custom errors return the error selector and raw data.

When a transaction actually needs to be sent, exchange.IO("api", "eth", "send", toAddress, value, {data: calldata}) can be used to send arbitrary call data; when dryRun: true is set, the transaction is only signed and not broadcast.

Currently only Ethereum (EVM) chains are supported; TRON is not supported.