SWOPUS SWOPUS
Docs

Swap and market data API

Get a quote, turn it into a transaction your own key signs, and read pools, trades and prices — from a bot, a backend or another app. There is no Swopus SDK to install: an HTTP API plus the standard Klever SDK is the whole kit.

Updated 29 September 2026

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 named amount_in, total_out …) are in the token's base units. Divide by 10^decimals yourself; precision differs per token (KLV 6, KIRA-31QW 3, WBTC 8), so never assume one.
  • Token ids are KleverChain asset ids: KLV, KFI, and TICKER-XXXX for 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) and https://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 is 429 with Retry-After (and retry_after_s in the body) — wait that long. Cache what the responses let you cache.

Quote and swap in three steps

  1. 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
  2. Encode execution into a smart-contract call — endpoint@arg1@arg2…, base64 — and attach pay_amount of pay_token as the payment. See Building the transaction.
  3. 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.

ParamRequiredMeaning
token_inyesAsset id you pay with.
token_outyesAsset id you receive.
amount_inyesBase units of token_in.
venueyesswopus — Swopus pools only. Always pass it.
slippage_bpsnoTolerance for min_total_out, in basis points (50 = 0.5 %).
includeExecutionnotrue adds execution, the transaction plan. Note the camelCase.
recipientwith executionYour 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
}
FieldMeaning
foundfalse when no route exists for the pair and size.
total_out / min_total_outExpected output, and the floor after slippage_bps. The floor is enforced on-chain.
price_impact_bpsHow 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_rawEstimated KleverChain network fee, in KLV base units.
executionThe 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_msHow 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/route plans for more than one hop.
    klv1qqqqqqqqqqqqqpgqhl9v3xuzhen902jep0uzsst8t5xv939ayg3q9rk3ks

Finding a pool

  • From the API: /api/public/pools lists 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) and getPoolKindByPairId(pair_id) (1 = constant product, 2 = stable).
  • Token order inside a pool is not alphabetical. Read getToken0 / getToken1 from the pool, or take base_asset_id (token0) and quote_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

EndpointWhat it givesCache
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 (take next_cursor) for new trades.
  • Now and then, also re-read the window from rescan_from_block to as_of_block with from_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_block is 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_bps of the input, per pool, to its liquidity providers. Read it live (getFeeBps or 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_fee in /api/route estimates 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's kdaFee option).

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 deprecated pools. 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.