Type/to search
Getting Started
Platform Basics
Writing Strategies
Development Tools
Backtesting System
Advanced Topics
JavaScript Multi-threading
Communication Between Live Trading Strategies
API Rate Limiting Control
Options Trading
Web3
EVM On-chain Swaps (Uniswap, KyberSwap, 1inch)
Solana Swaps (Jupiter)
Ethereum (EVM)
TRON
Data and Research
Extended API Interface

The Uniswap exchange object (exchange.GetName() returns Uniswap) connects to the pools of Uniswap, PancakeSwap or Aerodrome on one EVM chain and maps on-chain swaps to the spot trading functions: check prices with exchange.GetTicker() and trade with exchange.CreateOrder(), without registering ABIs or encoding contract calls yourself. Routing, quoting, token approval, price protection and sending transactions are handled by the exchange object. The same implementation also comes in two aggregator versions, KyberSwap and OneInch (1inch), whose quotes and transaction data come from the aggregator, which splits and routes orders across many DEXs and market makers.

Which exchange object to use
NeedExchange object
Swap on the pools of a given DEX with control over the route (V2/V3/V4, direct or two-hop)Uniswap, with DEX set to Uniswap, PancakeSwap or Aerodrome
Let an aggregator find the best price across DEXs, no API key neededKyberSwap
Let the 1inch aggregator find the best price (needs a 1inch API key)OneInch
Call other contracts, provide liquidity, manage V3 positions, custom transactionsWeb3, see Ethereum (EVM)
Swaps on SolanaJupiter, see Solana Swaps (Jupiter)

A strategy can add several exchange objects that use the same wallet, for example Uniswap and KyberSwap to compare prices and Web3 for other on-chain operations.

Configuring the exchange object
  • DEX: Uniswap exchange object only: Uniswap, PancakeSwap, Aerodrome (Base only)
  • Chain: One exchange object corresponds to one chain; see the table below
  • Private Key: Wallet private key (hex string). The key can be kept locally on the docker, see Local Credential Files
  • Rpc Address: Node address for the chain; selecting a Chain fills in the default nodes (primary,backup). Separate several nodes with commas; when one fails or lacks data the next one is used
  • Rpc Api Key: Node authentication, may be empty. Written as Name: value it is sent as that request header, otherwise as Authorization: Basic <value>. OneInch has no such field
  • 1inch Api Key: OneInch only, required: apply for it on the 1inch developer portal

DEXs and aggregators by chain:

ChainUniswapPancakeSwapAerodromeKyberSwapOneInch
EthereumV2, V3, V4V2, V3—✓✓
ArbitrumV3, V4V2, V3—✓✓
BaseV3, V4V2, V3, InfinitySlipstream, basic pools✓✓
BNB ChainV3, V4V2, V3, Infinity—✓✓
UnichainV2, V3, V4——✓✓
PolygonV3, V4——✓✓
OP MainnetV3, V4——✓✓
RobinhoodV2, V3, V4——✓—
ArcV2, V3, V4————
  • V4 (Uniswap) and Infinity (PancakeSwap Infinity CL) use only pools without hooks; the native coin takes part in the swap directly, without the wrapped coin.
  • The wallet needs the chain's native coin to pay gas: ETH or BNB; POL on Polygon (wrapped: WPOL); Arc pays gas in USDC.
  • The first request checks that the node is on the configured Chain; if not, it reports an error and never sends a transaction to another chain.
  • The node must be able to return receipts of mined transactions, otherwise order status cannot be queried. publicnode's public Base, BNB Chain and Arbitrum nodes cannot, so do not use them alone; check this when you configure your own node.
Trading pairs
  • Pairs are written as base_quote, e.g. ETH_USDC, UNI_USDT.
  • Token names are looked up in the token table first (built-in common tokens such as the native coin, wrapped native coin, USDC and USDT, plus tokens registered with exchange.IO("token", name, contractAddress)), then in the built-in official token list. If a chain has several tokens with the same name, use the contract address instead, e.g. 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984_USDC; a contract address is registered automatically with the decimals read from the chain.
  • The native coin and the wrapped coin are two assets: ETH and WETH, BNB and WBNB are different coins. You cannot place an order between them; convert 1:1 with exchange.IO("wrap", amount) and exchange.IO("unwrap", amount), which only cost gas.
  • Chain-specific names: USDT on Unichain and Polygon is USDT0; Robinhood has no USDC or USDT and uses USDG (ETH_USDG); on Arc the native balance is USDC and pairs are written WETH_USDC, EURC_USDC.
  • exchange.GetMarkets() lists only common pairs; pairs not listed can be traded as well.
Placing orders

A DEX has no order book: every order is an on-chain swap executed immediately, either filled in full or reverted in full (losing only gas). There are no partial fills and no resting orders.

  • Limit orders: the limit price is the worst execution price. The order is quoted first; if the current price cannot reach the limit, an error is reported and nothing is sent. Otherwise "minimum received / maximum paid" is written into the transaction, and if the price moves before it is mined the swap reverts.
  • Market orders: slippage protection is applied to the quote; the default slippage is 0.5%, changed with exchange.IO("slippage", ratio).
  • Amount: for a sell, the amount of base coin to sell; for a limit buy, the amount of base coin to receive; for a market buy, the amount of quote coin to spend.
  • Aggregators only support exact input: a limit buy on KyberSwap or OneInch is executed as "spend at most amount × price, receive at least amount", so slightly more may be received. Aerodrome's basic pools are also exact-input only, so a limit buy there uses only Slipstream pools.
  • Per-order settings: JSON can be appended to the side parameter, e.g. exchange.CreateOrder("ETH_USDC", 'sell;{"slippage":0.01,"route":"v3"}', -1, 0.1): slippage is the slippage for this order; route restricts the route type to v2, v3, v4, hop (two hops) or direct. On Aerodrome v2 means basic pools and v3 means Slipstream.
  • Approval: before selling an ERC20 token the allowance is checked; if it is not enough an approval transaction is sent first and mined. V2 and V3 pools are approved to the router; V4 and Infinity go through their Permit2 contracts; aggregators are approved directly to the aggregator's router. By default only the amount needed is approved; exchange.IO("approve", "max") switches to unlimited approval.
  • Deadline: a transaction not mined before the deadline (120 seconds by default, changed with exchange.IO("deadline", seconds)) reverts, so it does not fill after a large price move.

Before placing an order you can quote only with exchange.IO("route", ...) (quotes for each candidate route), and build the transaction with the order logic and simulate it on chain with exchange.IO("simulate", ...), which costs no gas.

Orders and cancellation
  • Order IDs look like ETH_USDC,0x…: the pair, a comma and the transaction hash. exchange.GetOrder() takes the status from the transaction receipt; the order's Info has chainId and the chain name chain, which tell which chain an order belongs to when you aggregate or store orders across chains. To query the receipt with exchange.IO("receipt", ...), pass the transaction hash after the comma.
  • exchange.GetOrders() returns the orders sent in this run that are not mined yet.
  • exchange.CancelOrder() sends a replacement transaction with the original order's nonce and waits for the result (up to 30 seconds by default, changed with exchange.IO("cancelWait", seconds)): if the replacement is mined first it returns true; if the original order is mined first (or was already mined) it returns false; on timeout it returns true, meaning the cancellation has been submitted. exchange.GetOrder() has the final word: when the original nonce was taken by the replacement, the order shows as canceled.
Differences from centralized exchanges
  • The bid and ask of exchange.GetTicker() are executable prices for about 1000 USD; there are no 24-hour statistics on chain. exchange.GetDepth() is derived from quotes of increasing size, not a real order book.
  • exchange.GetTrades() returns recent on-chain trades from the pair's pools; the aggregators (KyberSwap, OneInch) do not support it.
  • exchange.GetRecords(), exchange.GetTickers() and exchange.GetHistoryOrders() are not supported.
  • The 1inch free tier allows 1 request per second; the connector queues requests, so exchange.GetDepth() takes correspondingly longer.

For the detailed behavior of each function and the parameters and return values of the exchange.IO() commands (route, simulate, token, transfer, receipt, wrap, unwrap, approve, slippage, deadline, cancelWait, gasMultiplier, contracts, base, sendBase, address), see the Uniswap exchange page of the syntax manual.

Examples

Quote, simulate, then market sell (ETH_USDC on Ethereum; the same code works on KyberSwap and OneInch). CreateOrder sends a real transaction.

javascript
function main() { var symbol = "ETH_USDC" exchange.IO("slippage", 0.003) // market order slippage 0.3% var t = exchange.GetTicker(symbol) Log("bid:", t.Buy, "ask:", t.Sell) // quote only: best route for selling 0.1 ETH var r = exchange.IO("route", symbol, "sell", 0.1) if (!r || r.best === null) { Log("no route:", GetLastError()) return } Log("best route:", r.best, "average price:", r.price) // simulate on chain, no gas spent if (!exchange.IO("simulate", symbol, "sell", 0.1)) { Log("simulation failed:", GetLastError()) return } // market sell 0.1 ETH; the order ID looks like ETH_USDC,0x… var id = exchange.CreateOrder(symbol, "sell", -1, 0.1) if (!id) { Log("order failed:", GetLastError()) return } while (true) { var o = exchange.GetOrder(id) if (o && o.Status != ORDER_STATE_PENDING) { Log("status:", o.Status, "filled:", o.DealAmount, "average fill price:", o.AvgPrice) break } Sleep(3000) } }

See Also