What you can do
- Quote a swap on Swopus pools — one pool, or several hops through the Swopus router — with one request.
- Get the transaction to sign: the same request returns the contract, the endpoint, the arguments and the payment. You sign it with your own key and broadcast it through your own node.
- Call the pools and the router directly on-chain, for the lowest fee or your own routing.
- Read market data: every pool with reserves and fees, every trade, candles, token prices.
Swopus is non-custodial: nothing here holds keys or funds. Your code signs, and the contracts enforce the minimum you accept.
Before you start
- Base URL:
https://api4.swopus.com. Read-only, no API key, CORS open. - Numbers are strings, and amounts ending in
_raw(or namedamount_in,total_out…) are in the token's base units. Divide by10^decimalsyourself; precision differs per token (KLV 6, KIRA-31QW 3, WBTC 8), so never assume one. - Token ids are KleverChain asset ids:
KLV,KFI, andTICKER-XXXXfor everything else (USDT-23V8,USDC-1LN4). - Fields are only ever added. Ignore fields you do not know; unknown values
are
null. - Your node, not ours. Signing, broadcasting and on-chain reads go through
your own KleverChain node or Klever's public endpoints —
https://node.mainnet.klever.org(broadcast) andhttps://api.mainnet.klever.org(reads,sc/query, transaction status). Klever's public endpoints are rate-limited; a bot that polls hard should run its own node. - Rate limits, per IP: 120 requests a minute for data, 60 a minute for
/api/route. Past that the answer is429withRetry-After(andretry_after_sin the body) — wait that long. Cache what the responses let you cache.
Quote and swap in three steps
- Ask for a route with the execution plan.
GET https://api4.swopus.com/api/route ?token_in=KLV &token_out=USDT-23V8 &amount_in=10000000000 # 10,000 KLV in base units (6 decimals) &slippage_bps=50 &venue=swopus &includeExecution=true &recipient=klv1...your-address - Encode
executioninto a smart-contract call —endpoint@arg1@arg2…, base64 — and attachpay_amountofpay_tokenas the payment. See Building the transaction. - Sign and broadcast with the Klever SDK through your node, within
quote_ttl_ms(20 s). If the market moved past your slippage, the contract rejects the swap and only the network fee is spent.
GET /api/route
With venue=swopus, the best route for an amount through Swopus pools — constant
product and stable: on a single pool, or over several hops through the Swopus router. A route
is never split.
| Param | Required | Meaning |
|---|---|---|
token_in | yes | Asset id you pay with. |
token_out | yes | Asset id you receive. |
amount_in | yes | Base units of token_in. |
venue | yes | swopus — Swopus pools only. Always pass it. |
slippage_bps | no | Tolerance for min_total_out, in basis points (50 = 0.5 %). |
includeExecution | no | true adds execution, the transaction plan. Note the camelCase. |
recipient | with execution | Your klv1… address — it must be the account that signs. A single-pool plan pays it explicitly; the router always pays the signer. |
A real answer for 1,000 ONYX → USDT, two hops through KLV, abridged:
{
"found": true,
"token_in": "ONYX-2QTG",
"token_out": "USDT-23V8",
"amount_in": "1000000000",
"total_out": "1818",
"min_total_out": "1808",
"backend": "v2-router",
"is_split": false,
"price_impact_bps": 0,
"paths": [{
"share_bps": 10000,
"legs": [
{ "dex_source": "swopus-v2", "pool_address": "klv1…", "token_in": "ONYX-2QTG",
"token_out": "KLV", "amount_in": "1000000000", "amount_out": "2468296",
"reserves_age_sec": 0 },
{ "dex_source": "swopus-v2", "pool_address": "klv1…", "token_in": "KLV",
"token_out": "USDT-23V8", "amount_in": "2468296", "amount_out": "1818",
"reserves_age_sec": 0 }
]
}],
"network_fee": { "total_estimate_raw": "28000000", "confidence": "estimate" },
"execution": {
"backend": "v2-router",
"target": "klv1qqqqqqqqqqqqqpgqhl9v3xuzhen902jep0uzsst8t5xv939ayg3q9rk3ks",
"endpoint": "executePairPathPlanned",
"args": [
{ "type": "hex", "value": "0000001d0100000000000000080100000000" },
{ "type": "hex", "value": "0000001d000000094f4e59582d32515447…" },
{ "type": "bi", "value": "1808" }
],
"pay_token": "ONYX-2QTG",
"pay_amount": "1000000000",
"min_total_out": "1808"
},
"quote_ttl_ms": 20000
} | Field | Meaning |
|---|---|
found | false when no route exists for the pair and size. |
total_out / min_total_out | Expected output, and the floor after slippage_bps. The floor is enforced on-chain. |
price_impact_bps | How far this size moves the price. |
paths[].legs[] | Each hop: pool, tokens, amounts, and reserves_age_sec — how old the reserves behind the quote are. Pool fees are already inside the amounts. |
network_fee.total_estimate_raw | Estimated KleverChain network fee, in KLV base units. |
execution | The transaction plan: backend, target contract, endpoint, typed args, and the payment (pay_token, pay_amount). backend is v2-pool-direct (swapExactIn on one pool, paid to recipient) or v2-router (the Swopus router over several pools, paid to the signer) — the contract, endpoint and arguments differ with it. |
quote_ttl_ms | How long to treat the quote as current. |
A single-pool plan (backend: "v2-pool-direct") targets the pool itself:
swapExactIn with token_in (string),
amount_in and min_out (bi) and your
recipient (address). Neither plan carries a deadline:
min_total_out is the protection.
Treat args as opaque. Encode and sign them exactly as given;
arguments of type hex are structures already encoded for the contract, and their
layout follows the contracts. The quote is not binding — min_total_out is what the
contract guarantees.
Building the transaction
A KleverChain smart-contract call is a SmartContract transaction of type
invoke with the payment in callValue and the call in data:
the string endpoint@arg1@arg2… — each argument hex-encoded — then base64.
// Top-level argument encoding for a KleverChain smart-contract call.
// Amounts are BigInt end to end — never pass them through Number.
const evenHex = (h) => (h.length % 2 ? '0' + h : h);
function argToHex({ type, value }) {
switch (type) {
case 'string': // token id or other text: raw UTF-8 bytes
return Buffer.from(value, 'utf8').toString('hex');
case 'bi': // BigUint: minimal big-endian bytes
return evenHex(BigInt(value).toString(16));
case 'u64': // 8 bytes, big-endian
return BigInt(value).toString(16).padStart(16, '0');
case 'hex': // already encoded by the API: pass through as given
return value.replace(/^0x/, '');
case 'address':// klv1… → 32 raw bytes (use your SDK's bech32 decoder)
return decodeKleverAddressToHex(value);
default:
throw new Error('unknown arg type ' + type);
}
}
const call = [execution.endpoint, ...execution.args.map(argToHex)].join('@');
const data = [Buffer.from(call, 'utf8').toString('base64')]; // data[] is base64 Then build, sign and broadcast with the Klever SDK — shown here with @klever/sdk-node; check the method names against the SDK version you install:
import { Account, TransactionType, utils } from '@klever/sdk-node';
// Your own Klever node, or Klever's public endpoints.
utils.setProviders({
node: 'https://node.mainnet.klever.org',
api: 'https://api.mainnet.klever.org',
});
const account = new Account(process.env.KLEVER_PRIVATE_KEY);
await account.ready;
const unsigned = await account.buildTransaction(
[{
type: TransactionType.SmartContract,
payload: {
scType: 0, // invoke
address: execution.target,
callValue: { [execution.pay_token]: Number(execution.pay_amount) },
},
}],
data, // [base64("swap@…@…")]
);
const signed = await account.signTransaction(unsigned);
const result = await account.broadcastTransactions([signed]); callValue is a JavaScript number in this SDK. Amounts above 253 base
units lose precision that way — check how your SDK version accepts large amounts before you
send one.
The result of a transaction, including the contract's error message if it reverted, is at
GET https://api.mainnet.klever.org/v1.0/transaction/<hash>?withResults=true.
Blocks are about 4 seconds apart.
Pools and the router on-chain
For the lowest network fee, or your own routing, call the contracts directly.
- Factory — finding pools.
klv1qqqqqqqqqqqqqpgq30cpeswhuyc0tawpnc02huquv5t953mmyg3q075cak - Router — multi-hop swaps across Swopus pools; what
/api/routeplans for more than one hop.
klv1qqqqqqqqqqqqqpgqhl9v3xuzhen902jep0uzsst8t5xv939ayg3q9rk3ks
Finding a pool
- From the API:
/api/public/poolslists every pool with its address,pair_id, tokens, fee and reserves. -
On-chain, from the factory:
getPairIdByTokens(token_a, token_b)(either order,0= no pair),getPoolByPairId(pair_id)(zero address = none) andgetPoolKindByPairId(pair_id)(1 = constant product, 2 = stable). -
Token order inside a pool is not alphabetical. Read
getToken0/getToken1from the pool, or takebase_asset_id(token0) andquote_asset_id(token1) from the pools feed.
Quote on a pool
quoteExactIn(token_in, amount_in) → amount_out runs the same code as the swap
and is the authoritative quote. For constant-product pools you can also compute it from
getReserves and getFeeBps — exactly like this, flooring at each step:
a' = floor(amount_in × (10000 − fee_bps) / 10000)
out = floor(a' × reserve_out / (reserve_in + a'))
Stable pools use a Curve-style invariant with a fee that grows when a trade makes the pool
more imbalanced, and they refuse trades past an imbalance cap. Do not reproduce that locally;
call quoteExactIn.
Swap on a pool
swapExactIn(token_in, amount_in, min_out, recipient), paid with exactly
amount_in of token_in (one payment, the same token and amount as the
arguments). It reverts with slippage if the output would fall below
min_out. There is no deadline argument. Arguments: token id as UTF-8 hex,
amounts as minimal big-endian hex, recipient as its 32-byte hex.
Swap through the router
Plans from /api/route call executePairPathPlanned, whose arguments come
pre-encoded. Calling the router yourself, the simplest endpoints are:
executeSingleHopPlanned(pair_id: u32, zero_for_one: bool, quote_out, min_out)executeTwoHopPlanned(pair0, zfo0, quote_out0, pair1, zfo1, quote_out1, min_out)executeThreeHopPlanned(…the same for three hops…, min_out)— three hops at most.
zero_for_one is true when the hop's input is the pool's token0.
The output goes to the caller. The router does not re-quote: it hands each hop
exactly the quote_out you gave for the hop before it, and checks
min_out only on the last one. A hop quoted higher than the pool delivers fails the
transaction; one quoted lower leaves the difference behind. Quote every hop with
quoteExactIn against the current state — or use /api/route, which
plans this for you.
Market data
| Endpoint | What it gives | Cache |
|---|---|---|
GET /api/public/pools | Every pool: pair_id, pool_address, kind (pool-cp / pool-stable), tokens and decimals, reserve0/1 (raw and scaled), fee_bps, tvl_klv, tvl_usd, deprecated, verified; plus as_of_block. | 15 s |
GET /api/public/trades | Every swap in Swopus pools, from the pools' own events — direct or through a router — each with source (how it was routed), amounts, price and reserves after. Oldest first, ordered by block and log index. Params: since_ts, from_block, to_block (a request spans at most 50,000 blocks), limit (default 500), cursor. See polling trades below. | 5 s |
GET /api/pools/reserves?pair_ids=5,8,29 | Current reserves for the pools you name — constant-product and stable — with last_updated_block, indexer_lag_blocks and stale. The one to poll. | none |
GET /api/pools/<pair_id>/ohlc?interval=1h | Candles: open, high, low, close, volume in each token, swap_count. | — |
GET /api/tokens/overview | Per token, on Swopus pools: price_usd, price_klv, 24 h change, volume_24h_usd, volume_7d_usd, tvl_usd. | — |
GET /api/tokens | Every listed asset: asset_id, ticker, decimals, price_in_klv, 24 h change, tradable, logo. | — |
GET /api/prices/reference | klv_usd and reference dollar prices from an external market source. Both are null while that source is unavailable — handle it. | — |
A pair's price on a constant-product pool is its reserves:
reserve1 / reserve0, each scaled by its decimals — token1 per token0 (the feed's
quote per base). For an executable price at a size, ask
/api/route. Prices derived from pools follow those pools: a thin pool gives a
noisy price. The reserves a bot acts on should come from the chain or from
/api/pools/reserves, not from a cached feed.
Polling trades
- Page forward with
cursor(takenext_cursor) for new trades. -
Now and then, also re-read the window from
rescan_from_blocktoas_of_blockwithfrom_block/to_block, and de-duplicate on(tx_hash, log_idx). A trade indexed late can land below your cursor, and forward paging would never return it. -
as_of_blockis the confirmed ceiling: newer trades exist but are not yet final.
Reading the chain yourself
Every view in this guide can be called through a KleverChain API with
sc/query. Arguments are the same hex encodings as in a transaction, sent as
base64; each return value comes back base64-encoded, one entry per value (so
getReserves returns two).
POST https://api.mainnet.klever.org/v1.0/sc/query
Content-Type: application/json
{
"ScAddress": "klv1…pool-address",
"FuncName": "quoteExactIn",
"Arguments": ["S0xW", "O5rKAA=="] // base64 of "KLV", base64 of 1000000000
}
→ { "data": { "returnData": ["XVbDHJA="], "returnCode": "Ok" }, "code": "successful" }
base64 → 0x5d56c31c90 → 400887585936 base units of the output token
A reverted view answers with an error message instead of returnData. Some
HTTP clients' default user agents are refused by Klever's public API; send an ordinary one.
Swap events. Each pool emits swapExecuted per swap — one per hop
of a multi-hop swap — with the recipient and both tokens as topics and
timestamp, amount_in, amount_out, fee_bps
(the effective fee on stable pools) and the new reserves as data. The router emits no event
of its own. /api/public/trades is this stream, decoded.
Fees
- Pool fee —
fee_bpsof the input, per pool, to its liquidity providers. Read it live (getFeeBpsor the pools feed); it can change. Stable pools add a dynamic part when a trade worsens their balance. - Network fee — paid in KLV to KleverChain, including for a transaction that
reverts. It grows with the number of pools touched and the size of the call data:
network_feein/api/routeestimates about 15 KLV for one pool and 28 KLV for two hops at today's parameters. The fee in the built transaction is the real figure. It can be paid in a KDA that has a fee pool (the SDK'skdaFeeoption).
Limits and good practice
- One transaction is one contract call. KleverChain has no multicall. The Swopus router chains up to three Swopus pools in one call; anything wider needs a contract of your own.
- Quotes age fast. Sign within
quote_ttl_ms, always set a real minimum output, and re-quote after a revert rather than retrying the same transaction. - Pools have floors. Each pool has a minimum swap size and minimum reserves
(
getMinSwapThresholds,getMinReserveThresholds); a trade past them reverts. - Skip
deprecatedpools. They stay listed (deprecated: true) so their history resolves; they are not used for routing.
Terms and support
The API is provided as is, without an SLA, under the Terms of Use — read the Risk Disclosure before you automate anything. Endpoints not listed on this page are internal to the Swopus app and change without notice. Questions and bug reports: contact@swopus.com.