Type/to search
Getting Started
Welcome to FMZ Quant Trading Platform
Quick Start
Key Security
Platform Basics
Account and Billing
Live Robot Billing and Top-up
Sub-accounts
Exchange
General Protocol
Local Credential Files
Exchange-Specific Notes
Securities and Futures
Crypto
Docker
Strategy Library
Live Trading
Writing Strategies
Development Tools
Backtesting System
Advanced Topics
Data and Research
Integrations

With ChainType set to ETH, the Web3 exchange object connects to nodes of Ethereum and every EVM-compatible chain (BSC, Base, Arbitrum, Optimism, Polygon and so on), and uses the exchange.IO() commands to query balances, call contracts and send transactions. This page walks through the commands in the order of a typical on-chain operation; for the full parameters of each command see the corresponding exchange.IO("command", ...) in the Web3 category of the syntax manual.

If you only want to swap tokens on Uniswap or PancakeSwap, use the Uniswap exchange object (see Advanced Topics → Web3 → Uniswap and PancakeSwap): it supports standard functions such as exchange.GetTicker() and exchange.CreateOrder() directly, with no contract calls to encode yourself.

1. Configure the exchange object

Add an exchange on the "Exchange" page (/m/add-platform), choose the protocol "Cryptocurrency" and the exchange Web3:

FieldDescription
ChainTypeETH: Ethereum and all EVM-compatible chains; TRON: TRON, see Advanced Topics → Web3 → TRON
Private KeyWallet private key (hex string, the 0x prefix is optional). The key can be deployed locally on the docker, see Getting Started → Key Security
Rpc AddressNode address, by default https://ethereum-rpc.publicnode.com (a public Ethereum mainnet node). For other chains enter a node of that chain, for example BSC: https://bsc-dataseed.binance.org. http(s):// and ws(s):// are supported. Several nodes separated by commas back each other up
Rpc Api KeyNode authentication, may be left empty. Written as Name: value (such as x-api-key: xxx) it is sent as a request header with that name; otherwise it is sent as Authorization: Basic <value>

With several nodes, requests start from the node that last succeeded and move on only when a node is unavailable (connection failure, timeout, rate limiting); errors such as a failed contract execution are returned directly. Nodes whose chain ID differs from the first node's are skipped, so transactions are never sent to another chain.

At runtime, exchange.IO("base", nodeAddress) switches nodes (several nodes can be passed as an array or a comma-separated string), exchange.IO("key", privateKey) switches the wallet private key, and exchange.IO("address") returns the current wallet address.

Among the standard functions only exchange.GetAccount() and exchange.GetAssets() are available; they return the wallet's native coin balance (the currency is recognized from the chain ID, BNB on BSC for example).

2. Query balances and read contracts

Read-only contract methods (view/pure) cost no gas and return decoded results directly:

javascript
exchange.IO("api", "eth", "eth_getBalance", wallet, "latest") // native coin balance, on-chain integer (hex string) exchange.IO("api", tokenAddress, "balanceOf", wallet) // ERC20 balance, on-chain integer exchange.IO("api", tokenAddress, "decimals") // token decimals
  • exchange.IO("api", "eth", method, ...args) calls the node's JSON-RPC methods directly, such as eth_gasPrice, eth_blockNumber and eth_getTransactionReceipt.
  • exchange.IO("api", contractAddress, method, ...args) calls a contract method. The method can be a name, a full signature (such as "approve(address,uint256)", to tell overloads apart) or a selector (such as "0x095ea7b3").
  • On-chain amounts are integers. exchange.IO("fromUnits", onChainInteger, decimals) converts to a readable amount and exchange.IO("toUnits", "1.5", decimals) converts back; a token contract address can be passed in place of the decimals. Both compute exactly on strings.
  • Use exchange.IO("multicall", ...) to read many contracts in one request, and exchange.IO("logs", ...) to query event logs.

3. Register ABIs

Standard ERC20 methods (balanceOf, decimals, allowance, approve, transfer and others) are built in and need no registration. Before calling methods of other contracts, register the contract's ABI with exchange.IO("abi", contractAddress, abi).

Common contracts can use built-in templates by passing the template name as the third argument: "weth", "uniswapV3Pool", "uniswapV3Factory", "uniswapV3QuoterV2", "uniswapV3SwapRouter02", "uniswapV3PositionManager", "permit2" (PancakeSwap V3 uses the same templates, aliases such as "pancakeV3Pool" also work). Addresses of common contracts are available from exchange.IO("contracts").

javascript
exchange.IO("abi", poolAddress, "uniswapV3Pool") var slot0 = exchange.IO("api", poolAddress, "slot0")

The ABI of other contracts can be obtained from a block explorer, for example Etherscan's V2 API (an Etherscan API key is required, chainid is the chain ID, take the result field of the response):

url
https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getabi&address=0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45&apikey=YourApiKey

4. Send transactions

When a write method of a contract is called, the exchange object signs the transaction with the configured private key, broadcasts it and returns the transaction hash. Before sending you can replace "api" with "call" and rehearse the same call with exchange.IO("call", ...): it is simulated on the node without signing or spending gas; on failure it returns an empty value and GetLastError() holds the reason given by the contract.

Taking approve as an example:

javascript
var amount = exchange.IO("toUnits", "100", tokenAddress) // 100 tokens as an on-chain integer var txHash = exchange.IO("api", tokenAddress, "approve", spender, amount)

When the method's stateMutability is payable, pass one extra argument before the method arguments: the amount of native coin to attach (on-chain integer). The last argument can be an options object:

OptionDescription
gasLimitGas limit. Estimated by the node (eth_estimateGas) when omitted. Do not use 21000 for contract calls; that is only enough for a plain transfer
gasPriceFixed gas price; when given, a legacy transaction is sent. When omitted, chains that support EIP-1559 get an EIP-1559 transaction: the tip is the larger of the node's suggestion and the tips actually paid in recent blocks, and the max fee is 2 × baseFee + tip
nonceA specific nonce. Allocated automatically when omitted and kept in sync with the on-chain pending count, so consecutive sends never reuse a nonce
dryRunWhen true, sign without broadcasting and return fields such as hash, raw (the signed transaction), nonce and gasLimit, for checking the transaction or sending it through another channel

Native coin is sent with exchange.IO("api", "eth", "send", toAddress, amount), where the amount is an on-chain integer (wei). Its options also accept data (hex call data) to send a transaction {to, data, value} returned by an aggregator API as is; gas is then estimated as for a contract call. Note: a plain transfer without gasPrice bids a fixed 100 Gwei with a gas limit of 21000, usually too high on Ethereum mainnet; query eth_gasPrice first and pass it in.

To send transactions through a private channel (such as Flashbots Protect or MEV Blocker) and avoid front-running, set a node used only for broadcasting with exchange.IO("sendBase", nodeAddress).

5. Wait for the transaction to be mined

exchange.IO("waitReceipt", txHash, {timeout, confirmations}) waits until the transaction is mined with the required confirmations and returns the receipt: status is 1 for success and 0 for failure (with the reason in revertReason), and events holds the events decoded with the registered ABIs. It returns an empty value on timeout.

6. Nonce management, speeding up and cancelling

  • exchange.IO("nonce") shows the on-chain and local nonce counts; exchange.IO("nonce", "sync") resyncs from the chain (use it after the same wallet has sent transactions elsewhere).
  • When a transaction stays unmined for a long time, exchange.IO("speedUp", txHash) resends it with the same nonce and a higher fee, and exchange.IO("cancelTx", txHash) replaces it with a zero-value transaction to yourself using the same nonce. Both only work before the original transaction is mined.
  • The local nonce record only exists inside the current live trading instance: instances that share one wallet cannot see each other's records and may still collide, so give each instance its own wallet.

Other commands

  • Encoding and decoding: exchange.IO("encode", ...) encodes contract call data or values by type (like Solidity's abi.encode), exchange.IO("encodePacked", ...) does packed encoding (for example a Uniswap V3 swap path), and exchange.IO("decode", ...) decodes by type.
  • Signing: exchange.IO("sign", ...) signs a 32-byte hash, exchange.IO("signTypedData", ...) signs EIP-712 typed data (such as ERC-20 Permit), and exchange.IO("signMessage", ...) signs a message with EIP-191.
  • Uniswap V3 math: exchange.IO("uniswapV3", ...) converts between ticks, prices and sqrtPrice, and between liquidity and token amounts.
  • Hashing: exchange.IO("hash", "keccak256", "raw", "hex", text) computes keccak256 and other digests, for example method selectors and EIP-712 digests; its parameters are the same as those of the Encode() function.
  • Complete examples: a swap through an aggregator (quote, build the transaction, rehearse it with exchange.IO("call", ...), send it with data) is in the examples of exchange.IO("call", ...) in the syntax manual; an ERC-20 Permit signature verified by the contract is in the examples of exchange.IO("sign", ...) and exchange.IO("signTypedData", ...).

Examples

Example: query balances, approve and wait for the transaction

Uses USDC on Ethereum mainnet. Note that this code sends a real transaction and spends gas.

javascript
function main() { var usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // USDC on Ethereum mainnet var spender = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" // the contract to approve, Uniswap SwapRouter02 here var wallet = exchange.IO("address") // balances: standard ERC20 methods need no ABI registration 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) // read a contract: current allowance var allowance = exchange.IO("api", usdc, "allowance", wallet, spender) Log("current allowance:", exchange.IO("fromUnits", allowance, usdc)) // approve 100 USDC: rehearse first, then send var amount = exchange.IO("toUnits", "100", usdc) if (!exchange.IO("call", usdc, "approve", spender, amount)) { Log("rehearsal failed:", GetLastError()) return } var txHash = exchange.IO("api", usdc, "approve", spender, amount) Log("tx hash:", txHash) // wait at most 3 minutes for the transaction to be mined var receipt = exchange.IO("waitReceipt", txHash, {timeout: 180000}) if (receipt && receipt.status == 1) { Log("approved in block:", receipt.blockNumber) } else if (receipt) { Log("transaction failed:", receipt.revertReason) } else { // not mined yet: speedUp resends with a higher fee, cancelTx cancels it Log("not mined before timeout:", GetLastError()) } }

See Also