EVM On-chain Swaps (Uniswap, KyberSwap, 1inch)
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
| Need | Exchange 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 needed | KyberSwap |
| Let the 1inch aggregator find the best price (needs a 1inch API key) | OneInch |
| Call other contracts, provide liquidity, manage V3 positions, custom transactions | Web3, see Ethereum (EVM) |
| Swaps on Solana | Jupiter, 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:Uniswapexchange object only:Uniswap,PancakeSwap,Aerodrome(Base only)Chain: One exchange object corresponds to one chain; see the table belowPrivate Key: Wallet private key (hex string). The key can be kept locally on the docker, seeLocal Credential FilesRpc 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 usedRpc Api Key: Node authentication, may be empty. Written asName: valueit is sent as that request header, otherwise asAuthorization: Basic <value>.OneInchhas no such field1inch Api Key:OneInchonly, required: apply for it on the 1inch developer portal
DEXs and aggregators by chain:
| Chain | Uniswap | PancakeSwap | Aerodrome | KyberSwap | OneInch |
|---|---|---|---|---|---|
| Ethereum | V2, V3, V4 | V2, V3 | — | ✓ | ✓ |
| Arbitrum | V3, V4 | V2, V3 | — | ✓ | ✓ |
| Base | V3, V4 | V2, V3, Infinity | Slipstream, basic pools | ✓ | ✓ |
| BNB Chain | V3, V4 | V2, V3, Infinity | — | ✓ | ✓ |
| Unichain | V2, V3, V4 | — | — | ✓ | ✓ |
| Polygon | V3, V4 | — | — | ✓ | ✓ |
| OP Mainnet | V3, V4 | — | — | ✓ | ✓ |
| Robinhood | V2, V3, V4 | — | — | ✓ | — |
| Arc | V2, 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:
ETHandWETH,BNBandWBNBare different coins. You cannot place an order between them; convert 1:1 withexchange.IO("wrap", amount)andexchange.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 writtenWETH_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
KyberSwaporOneInchis 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):slippageis the slippage for this order;routerestricts the route type tov2,v3,v4,hop(two hops) ordirect. On Aerodromev2means basic pools andv3means 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'sInfohaschainIdand the chain namechain, which tell which chain an order belongs to when you aggregate or store orders across chains. To query the receipt withexchange.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 withexchange.IO("cancelWait", seconds)): if the replacement is mined first it returnstrue; if the original order is mined first (or was already mined) it returnsfalse; on timeout it returnstrue, 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()andexchange.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