KyberSwap
Connects to the KyberSwap aggregator: quotes and transaction data come from the KyberSwap Aggregator API (which splits and routes orders across many DEXs and market makers; an API key is optional, see "API key and rate limits" below), and the docker signs them with the configured private key and sends them to KyberSwap's router contract MetaAggregationRouterV2 (0x6131B5fae19EA4f9D964eAc0408E4408b66337b5). One exchange object covers one chain: Ethereum, Arbitrum, Base, BNB Chain, Unichain, Polygon, OP Mainnet or Robinhood.
It is the aggregator version of the same implementation as the Uniswap exchange object: the token table, balances, approvals, orders, cancels, transfers and wrapping work the same way; the difference is that the swap route comes from KyberSwap.
Names and Configuration
exchange.GetName(): KyberSwap (Spot); aliases: Kyber.
| Field | Required | Default | Description |
|---|---|---|---|
| Chain | Yes | Ethereum | Options: Ethereum, Arbitrum, Base, BNB Chain, Unichain, Polygon, OP Mainnet, Robinhood |
| Private Key | Yes | — | Wallet private key (hex). |
| Rpc Address | No | https://ethereum-rpc.publicnode.com,https://eth.drpc.org | RPC node URL; several nodes separated by commas fail over to each other when one errors or lacks data; when empty, each chain's built-in default (a primary node plus a backup, comma-separated) is used. (more below the table) |
| Rpc Api Key | No | — | RPC authentication: a Header-Name: value header or a Basic auth value. |
| KyberSwap Api Key | No | — | API key of the KyberSwap Aggregator API, optional. (more below the table) |
The rest of the longer descriptions in the table:
- Rpc Address: Order states need a node that returns receipts of mined transactions; the publicnode public nodes for Base, BNB Chain and Arbitrum cannot (archive access is required), so do not use them alone.
- KyberSwap Api Key: With it, quotes and swap builds go through KyberSwap's API gateway (header
X-Api-Key) with a higher rate limit; without it the public endpoint (about 3 requests per second) is used. See "API key and rate limits" below for how to get one;exchange.IO("kyberApiKey", key)changes it at run time.
Trading Pairs and Contract Codes
Pair examples: ETH_USDC, WETH_USDC, ETH_USDG
Pairs are written as ETH_USDC; the native coin (ETH, BNB, POL) and its wrapped token (WETH, WBNB, WPOL) are different assets. The native coin of Polygon is POL; the Robinhood chain has no USDC or USDT and its main stablecoin is USDG, so pairs are written as ETH_USDG; USDT on Unichain and Polygon is written as USDT0.
The token table is the one of the Uniswap exchange object on the same chain; other tokens can be used in pairs after registering them with exchange.IO("token", name, address), and exchange.GetMarkets() lists the pairs of the token table that have a Uniswap pool on the chain.
Exchange-specific IO Commands
| Command | Arguments | Description | Returns |
|---|---|---|---|
transfer | to, amount[, token] | Transfer the native coin or an ERC20 token out of the configured wallet (token is a token name or contract address, the native coin when omitted); amount may be all (the native coin keeps the fee). Waits for the broadcast only, not for confirmation. See the sub-page exchange.IO("transfer", ...). | {txHash, from, to, token, tokenAddress, amount, raw} |
receipt | txHash[, waitMs] | Query the receipt of any transaction on this chain (not only those sent by this exchange object), optionally waiting for confirmation (up to 10 minutes). For an order, pass the transaction hash after the comma in the order ID. See the sub-page exchange.IO("receipt", ...). | {txHash, status, blockNumber, gasUsed, fee} once mined (status is success or reverted), {txHash, pending: true, known} otherwise. |
route | symbol, side, qty | Quote only, no order. The aggregator has a single candidate route (such as kyberswap:ETH-USDC); buy quotes receiving qty base (approximated with several exact-input quotes), sell quotes selling qty base. See the sub-page exchange.IO("route", ...). | {symbol, side, qty, best, quote, price, candidates} |
simulate | symbol, side, qty[, price[, stateOverride]] | Build the swap transaction with the order logic (quoting, price protection, building the transaction with KyberSwap) and only simulate it with eth_call; nothing is signed, broadcast or paid. See the sub-page exchange.IO("simulate", ...). | {route, exactIn, amountIn, amountOut, quoted, executed, value, calls}, plus allowanceOverride when an allowance was supplied. |
token | [name, address] | Register a token so it can be used in pairs; without arguments, list the token table. See the sub-page exchange.IO("token", ...). | {symbol, address, decimals} when registering, an array of {symbol, address, decimals, native} when listing. |
wrap | amount | Wrap the native coin into its wrapped token (e.g. ETH→WETH), 1:1 without slippage, gas only; all is not accepted (gas must remain). See the sub-page exchange.IO("wrap", ...). | {txHash, action, from, to, amount, raw} |
unwrap | amount | Unwrap the wrapped token into the native coin (e.g. WETH→ETH); amount may be all. See the sub-page exchange.IO("unwrap", ...). | {txHash, action, from, to, amount, raw} |
approve | ["exact" | "max"] | Token approval mode (tokens are approved directly to the KyberSwap router contract, not through Permit2): exact (default) approves only what the swap needs, max grants an unlimited allowance. Queries when called without an argument. See the sub-page exchange.IO("approve", ...). | The current mode. |
slippage | [ratio] | Slippage protection of market orders, range [0, 0.5), default 0.005 (0.5%). Queries when called without an argument. See the sub-page exchange.IO("slippage", ...). | The current slippage ratio. |
deadline | [seconds] | Transaction deadline in seconds, 10 to 86400, default 120. Queries when called without an argument. The deadline is passed to KyberSwap when the swap transaction is built. See the sub-page exchange.IO("deadline", ...). | The current deadline (seconds). |
cancelWait | [seconds] | How long a cancel waits for the result, in seconds, 0 to 600, default 30; 0 returns right after the cancel transaction is sent. Queries when called without an argument. See the sub-page exchange.IO("cancelWait", ...). | The current wait time (seconds). |
gasMultiplier | [x] | Gas limit multiplier, 1 to 5, default 1.2; a transaction's gasLimit is the node's gas estimate times this value. The gasLimit is only a cap; the gas actually used is what is charged. Queries when called without an argument. See the sub-page exchange.IO("gasMultiplier", ...). | The current multiplier. |
base | [url] | Without an argument, returns the current node address; with an address, switches the node. Several nodes are written as one comma-separated string (with an array only the first address is used). After a switch, the first request checks which chain the node is on and fails if it is not the configured chain. | The previous address. |
sendBase | url | Set a node used only to broadcast transactions (a private transaction channel). Unlike Web3, it does not return the previous address, does not query when called without an argument, and an empty string does not clear the setting. | true |
address | — | The wallet address of the configured private key (lowercase, starting with 0x). | — |
contracts | — | The deployment on this chain, with the same fields as the Uniswap exchange object: dex is KyberSwap and router is the KyberSwap router contract; quoterV2, v3Factory, v4 and the like are the chain's Uniswap deployment (used to list markets and estimate USD prices), and aerodrome is null; tokenList gives the generation time of the built-in token table and its token count on this chain. | — |
bridge.chains | — | Chains supported by the bridge (LI.FI): EVM chains and Solana, cached for an hour. See the sub-page exchange.IO("bridge.*", ...). | An array of {id, key, name, chainType, nativeToken}, chainType being EVM or SVM. |
bridge.tokens | [chain] | Tokens LI.FI knows on a chain (this chain by default), cached for an hour. See the sub-page exchange.IO("bridge.*", ...). | An array of {symbol, address, decimals, name, priceUSD}. |
bridge.quote | params | A bridge quote (read-only) from this exchange object's chain. See the sub-page exchange.IO("bridge.*", ...). | {tool, toolName, fromChain, toChain, toChainName, fromToken, toToken, fromAmount, toAmount, toAmountMin, fromAmountRaw, toAmountRaw, toAmountMinRaw, fromAmountUSD, toAmountUSD, slippage, toAddress, feeCosts, gasCosts, executionDuration, approvalAddress, steps, transactionId, request}: amounts are in token units (*Raw in the smallest unit); toAmountMin is the amount guaranteed after slippage; feeCosts is [{name, amount, token, amountUSD, percentage, included}] (fees with included false are paid on top); gasCosts estimates source-chain gas; executionDuration is the expected time in seconds; approvalAddress is the contract to approve (null for the native coin and on Solana); steps lists the steps (cross bridges, swap swaps); request echoes the parameters. |
bridge.simulate | params|quote[, stateOverride] | Re-quotes and runs LI.FI's transaction with eth_call: nothing is signed, broadcast or paid. See the sub-page exchange.IO("bridge.*", ...). | The bridge.quote result plus simulated (true), to, value and walletBalance, and allowanceOverride when an allowance was supplied. |
bridge.send | params|quote | Bridges out: re-quotes → checks the balance → approves LI.FI's approvalAddress when the token allowance is short (following exchange.IO("approve") exact/max, resetting USDT-like tokens to zero first, waiting for the approval to be mined and re-quoting after it) → simulates with eth_call → signs and broadcasts, with a gas limit of the larger of the eth_estimateGas estimate × gasMultiplier and LI.FI's suggested gasLimit (bridge contracts call into other contracts, so the estimate can fall short; only the gas used is charged). See the sub-page exchange.IO("bridge.*", ...). | {txHash, tool, fromChain, toChain, toChainName, fromToken, toToken, fromAmount, toAmount, toAmountMin, toAddress, value, transactionId}; txHash is the source-chain transaction, whose receipt receipt returns. |
bridge.status | txHash[, options] | Bridge progress (LI.FI /status) for the source-chain transaction hash returned by bridge.send; options may give {toChain, bridge} to speed up the lookup. See the sub-page exchange.IO("bridge.*", ...). | {status, substatus, substatusMessage, tool, sending, receiving, fromAddress, toAddress, explorer, bridgeExplorer}: status is NOT_FOUND, INVALID, PENDING, DONE or FAILED; when DONE, substatus is COMPLETED (delivered as quoted), PARTIAL (a different token arrived) or REFUNDED; sending and receiving are {txHash, txLink, chainId, amount, amountRaw, token, timestamp}, with receiving.amount and receiving.token being what actually arrived. |
bridge.apiKey | [key] | Sets the LI.FI API key (header x-lifi-api-key, for this exchange object only, never echoed); "" clears it and no argument just queries. Without a key requests are rate-limited per outgoing IP, see below. See the sub-page exchange.IO("bridge.*", ...). | Whether a key is set (a boolean). |
clientId | [id] | Set the client id sent with KyberSwap Aggregator API requests (the x-client-id header, also sent when an API key is set), 1 to 64 letters, digits, ., _ or -. (more below the table) | The current client id. |
kyberApiKey | [key] | Sets the KyberSwap API key (for this exchange object only, never echoed), the same as the KyberSwap Api Key setting: with a key, quotes and swap builds go to KyberSwap's API gateway https://api.kyberswap.com/swap with the X-Api-Key header and a higher rate limit; "" clears it and returns to the public endpoint. (more below the table) | Whether a key is set (a boolean). |
The rest of the longer descriptions in the table:
clientId: Without an API key KyberSwap's public rate limit (3 requests per second) is counted per client id, so programs sharing an id share that budget; use your own id if HTTP 429 errors are frequent, or set an API key (seekyberApiKey). The default isfmz-followed by the first 8 hex digits of a hash of the wallet address (stable across restarts for the same wallet, without revealing the address; a value generated randomly for the run when no private key is configured). Queries when called without an argument.kyberApiKey: A key is 1 to 256 visible ASCII characters without spaces; an invalid one fails and keeps the previous setting. No argument just queries. When the key is invalid or expired the gateway answers HTTP 401 and the call fails withKyberSwap API key rejected (HTTP 401: invalid api key); it does not fall back to the public endpoint.
For the common command currency see exchange.IO.
The common command api is not supported (the call fails with an error and returns null).
Function Support
Not supported (the call fails with an error and returns null): exchange.GetTickers, exchange.GetHistoryOrders, exchange.ModifyOrder, exchange.CreateConditionOrder, exchange.ModifyConditionOrder, exchange.CancelConditionOrder, exchange.GetConditionOrder, exchange.GetConditionOrders, exchange.GetHistoryConditionOrders, exchange.GetTrades.
Served through a fallback:
exchange.GetRecords: Native periods are 1m, 5m, 15m, 1h, 4h, 12h, 1d (GeckoTerminal's timeframes); other periods of a minute or longer are synthesized by the docker, while second-level periods are unavailable (there is no trade feed to build them from).
Notes
| Function | Behavior on KyberSwap |
|---|---|
| exchange.GetTicker() | Bid and ask are KyberSwap quotes for about $1000; there are no 24h statistics. The two sides are quoted separately and their best sources can differ (such as different market-maker quotes on Base), so two individually executable quotes occasionally cross (bid above ask); both sides then report the mid price and Info.crossed gives how far they crossed |
| exchange.GetDepth() | Derived from KyberSwap quotes at increasing sizes (one quote per level and side, queued with back-off and retries when rate-limited, so more levels take longer: about 20 seconds for 10); if a quote fails midway, the levels already obtained are returned (none are made up) with the reason in Info.partial, and it fails only when no level was obtained; when the two sides' best sources differ and cross, crossed prices are reported at the mid price with quantities unchanged, and Info.crossed gives the mid price and how far it crossed |
| exchange.GetRecords() | K-lines come from GeckoTerminal's pool-level data: an aggregator has no pools of its own, so the pair's pool (on any DEX) with the largest USD liquidity on GeckoTerminal is used (searched among the top 20 pools of each of the two tokens for one holding exactly this pair; the chosen pool is cached for an hour). Prices are that pool's trade prices (including the pool fee, so they may differ from the mid price of exchange.GetTicker() by about one pool fee) in quote per base, and Volume is in the base token; periods without trades repeat the previous close with zero volume, and the last bar may still be open. GeckoTerminal data lags by tens of seconds up to a minute and its public API allows about 10 requests per minute: bars of the same period are cached for a minute, requests queue inside the docker and wait and retry when rate-limited, and fail if that still does not succeed. Fails when GeckoTerminal has no pool of the pair |
| exchange.GetAssets() | Wallet balances of the native coin and of the tokens in the token table |
| exchange.CreateOrder() | Swaps on chain immediately: a limit price is the worst acceptable fill, market orders are protected by the slippage setting; a market buy is sized in the quote currency. KyberSwap supports exact input only: a limit buy is executed as "spend at most amount × price and receive at least amount", so the amount actually bought may be slightly more than the order amount. The order extra parameter slippage overrides the slippage for that order; route does not apply to an aggregator and fails with route is not applicable to the KyberSwap aggregator |
| exchange.GetOrder() | The order ID looks like ETH_USDC,0x… (the pair, a comma, then the transaction hash); the state comes from the transaction receipt, and the order is reported as canceled once the nonce of the original transaction has been taken by the cancel transaction (or another transaction). Filled amounts come from the router contract's Swapped event (which also covers the native coin) |
| exchange.GetOrders() | Lists only the swap transactions sent by this exchange object during the current run that are not yet mined |
| exchange.CancelOrder() | Sends a replacement transaction with the same nonce (a zero-amount transfer to itself) and waits for the result (at most 30 seconds by default, see exchange.IO("cancelWait", ...)): returns true if the cancel transaction lands first; if the original order is already mined or lands first, it returns false (the cancel did not happen, but this is not a call error), with the reason in exchange.GetLastError(); returns true on timeout, meaning the cancel has been submitted, and exchange.GetOrder() gives the final state |
API key and rate limits
- Without an API key: requests go to the public endpoint
https://aggregator-api.kyberswap.comand are rate-limited per client id (x-client-id, seeexchange.IO("clientId")) to 3 requests per second (30 per 10 seconds in the response headers). The docker queues KyberSwap requests within a process (at least about 0.34 seconds apart). - With an API key, set in the
KyberSwap Api Keysetting or withexchange.IO("kyberApiKey", key): requests go to the API gatewayhttps://api.kyberswap.com/swap(same paths as the public endpoint) with theX-Api-Keyheader (x-client-idis still sent), at a higher rate limit that depends on the key (KyberSwap's documentation gives no figure). - Getting a key: there is no self-service sign-up; email the KyberSwap business team at
[email protected]. KyberSwap no longer whitelistsx-client-idvalues, so a higher limit is only available through an API key. - When rate-limited (HTTP 429), requests back off for the time in the
x-ratelimit-reset-afterresponse header (at most 10 seconds) or 1, 2 and 4 seconds, retrying at most 3 times; if still limited the call fails withrate limited (HTTP 429) after 4 attempts, and without a key the error suggests setting one.exchange.GetDepth()quotes level by level and is the most likely to hit the limit. - A missing, invalid or expired key makes the gateway answer HTTP 401 and the call fails with
KyberSwap API key rejected (HTTP 401: …); it does not fall back to the public endpoint; clear the key withexchange.IO("kyberApiKey", "").
Complete runnable strategies (DEX-CEX arbitrage, dollar-cost averaging) are in DEX Strategy Examples.
Cross-chain bridge
exchange.IO("bridge.*", ...) bridges tokens from this exchange object's chain to another chain through the bridges LI.FI aggregates (Across, Stargate, CCTP, deBridge, Mayan, Relay, NEAR Intents and more), between EVM chains and between EVM and Solana (from this exchange object's chain). LI.FI supplies the quote and transaction, and the docker approves, simulates with eth_call, signs and broadcasts. Requests carry the integrator tag integrator=fmz and add no fee; fees in a quote such as LIFI Fixed Fee are LI.FI's and the bridges' own and are already deducted from toAmount.
Typical flow: bridge.quote for a quote → bridge.simulate to simulate → bridge.send to send → bridge.status every 10 to 30 seconds until DONE or FAILED.
- Bridging is asynchronous: when
bridge.sendreturns, only the source-chain transaction has been broadcast; delivery takes seconds to tens of minutes (seeexecutionDurationin the quote) and must be confirmed by pollingbridge.status;PARTIALmeans a different token arrived on the destination chain andREFUNDEDthat the funds went back to the source chain. - Cross-VM transfers (EVM↔Solana) require
toAddress, checked against the destination chain's address format; funds sent to a wrong address cannot be recovered. - Risks: the security of bridge contracts and cross-chain messaging rests with each bridge; quotes move, and
bridge.sendre-quotes before sending; the destination wallet needs gas to use what arrives. Test with a small amount first. - Rate limits: without an API key LI.FI limits by outgoing IP,
/quoteto 75 requests per 2 hours (bridge.quote,bridge.simulateandbridge.senduse one each, plus one more when re-quoting after an approval) and other endpoints to 100 per minute; strategies on the same docker share the quota. With a key set throughexchange.IO("bridge.apiKey", key)the limit is 100 per minute. A rate-limited call fails withrate limited (HTTP 429 …).