> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rondo.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrate the contracts

> A complete integration path for reading Rondo markets, quoting and filling orders, posting as a maker, and indexing settlement on Kaia.

This page is for wallets, trading interfaces, bots and backend services that call Rondo directly.
It takes an integration from an empty project to a confirmed fill without requiring knowledge of
the reference frontend.

<CardGroup cols={3}>
  <Card title="Read" icon="book-open">
    Select a market, verify its binding and page the order book at one block.
  </Card>

  <Card title="Trade" icon="arrow-right-arrow-left">
    Quote with the contract, approve the payment ceiling and submit a guarded fill.
  </Card>

  <Card title="Reconcile" icon="list-check">
    Decode settlement events and re-read the order after confirmation.
  </Card>
</CardGroup>

## Integration model

Each market is an independent proxy with its own order ids, roles, pause state, escrow and
configuration. The identity of an order is therefore:

```text theme={"system"}
(chainId, swapper proxy, orderId)
```

The **Swapper proxy** is the source of truth and the only contract that receives transactions. The
**Lens** is a replaceable read helper for atomic book pages and fill diagnostics. ERC-20 approvals
always name the Swapper as spender, never the Lens.

```text theme={"system"}
Lens.openOrders ──→ choose order ──→ Swapper.previewFill
                                            │
payment token.approve(Swapper) ─────────────┤
                                            ▼
                              Swapper.fillOrder
                                            │
                                            ▼
                                    OrderFilled log
```

<Warning>
  Never transfer a token directly to the proxy. Maker escrow is credited only by `createOrder` or
  `createPeggedOrder`; a plain ERC-20 transfer creates no order and no accounting entry.
</Warning>

## Production registry

Network: **Kaia mainnet · chain id 8217**<br />
Public RPC: `https://public-en.node.kaia.io`<br />
Explorer: `https://kaiascan.io`

All current markets use contract version `1.2.0` and the same quote token:

```text theme={"system"}
Kaia USDT  0xd077A400968890Eacc75cdc901F0356c943e4fDb  6 decimals
```

| Market      | Swapper proxy                                | Lens                                         | Base token                                   | Base decimals | First event block |
| ----------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | ------------: | ----------------: |
| JPYC / USDT | `0xF9ac7195D17eeEB96460607a850106192485250A` | `0x9e5502D1e6CDE9A2b1603E9B350fDE3F15919e7D` | `0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29` |            18 |         224142345 |
| IDRX / USDT | `0xd65b29B72599A99E1E8049a549869E1F8F7a3c0B` | `0xB341D5D2074924D81091e3f3cEcB14FCf8411675` | `0x18Bc5bcC660cf2B9cE3cd51a404aFe1a0cBD3C22` |             2 |         224401678 |
| IDRP / USDT | `0x14A41b8F0d18c9F2f5DBca2b0b104D335f16A5EE` | `0xE4FF83DC8A467a083903Dc6099Dd78E9ED993552` | `0xC16d986585407A74Ab87d17C3d0Dc19822E3EB35` |             6 |         224401747 |

Addresses can change only through a new market or Lens deployment; runtime settings can change at
any time. Keep the registry versioned, but read pause, roles, minimums, fees, oracle status and
order data on-chain. [Deployment details](/reference/deployments).

## Obtain the ABI

The complete user-facing signatures and tuple layout are described in [Contract API](/reference/api),
and the full Swapper and Lens arrays are directly available on [ABI JSON](/reference/abi-json).
For a client that discovers and takes orders, use the smaller copy-ready viem fragments below. They
contain only the entries that flow needs. The maker extension appears in
[Maker integration](#maker-integration).

```ts theme={"system"}
import {parseAbi} from "viem";

const orderTuple =
  "(address maker,uint64 expiry,uint16 feeBps,uint8 status,uint8 kind,address sellToken,uint16 premiumBps,address buyToken,uint256 sellAmount,uint256 buyAmount,uint256 sellRemaining,uint256 minRate)" as const;

export const swapperAbi = parseAbi([
  "function version() view returns (string)",
  "function baseToken() view returns (address)",
  "function quoteToken() view returns (address)",
  "function feed() view returns (address)",
  "function ORACLE_TWAP_INTERVAL() view returns (uint256)",
  "function ORACLE_TWAP_MIN_COUNT() view returns (int256)",
  "function paused() view returns (bool)",
  "function canTake(address) view returns (bool)",
  "function minOrderAmount(address) view returns (uint256)",
  "function getOraclePrice() view returns (uint256 answer,uint256 updatedAt,bool stale)",
  `function getOrder(uint256 orderId) view returns (${orderTuple})`,
  "function previewFill(uint256 orderId,uint256 sellAmountOut) view returns (uint256 amountIn,uint256 fee,uint256 netOut)",
  "function previewFillForAmountIn(uint256 orderId,uint256 amountIn) view returns (uint256 sellAmountOut,uint256 actualAmountIn,uint256 fee,uint256 netOut)",
  "function fillOrder(uint256 orderId,uint256 sellAmountOut,uint256 maxAmountIn)",
  "event OrderFilled(uint256 indexed orderId,address indexed taker,address indexed maker,address sellToken,uint256 amountOut,uint256 amountIn,uint256 fee,uint256 sellRemaining)",
]);

export const lensAbi = parseAbi([
  "function swapper() view returns (address)",
  `function openOrders(uint256 offset,uint256 limit) view returns (uint256[] ids,${orderTuple}[] orders,uint256 nextOffset)`,
  "function quoteFill(uint256 orderId,uint256 sellAmountOut,address taker) view returns (uint256 amountIn,uint256 fee,uint256 netOut,uint8 problem)",
]);

export const erc20Abi = parseAbi([
  "function symbol() view returns (string)",
  "function decimals() view returns (uint8)",
  "function balanceOf(address) view returns (uint256)",
  "function allowance(address,address) view returns (uint256)",
  "function approve(address,uint256) returns (bool)",
]);
```

## 1. Connect and verify the selected market

The examples use viem, but the call sequence is library-independent.

```ts theme={"system"}
import {
  createPublicClient,
  createWalletClient,
  custom,
  http,
  parseEventLogs,
  type Address,
} from "viem";
import {kaia} from "viem/chains";

const publicClient = createPublicClient({
  chain: kaia,
  transport: http("https://public-en.node.kaia.io"),
});

const walletClient = createWalletClient({
  chain: kaia,
  transport: custom(window.ethereum),
});

const market = {
  swapper: "0xd65b29B72599A99E1E8049a549869E1F8F7a3c0B" as Address,
  lens: "0xB341D5D2074924D81091e3f3cEcB14FCf8411675" as Address,
  base: "0x18Bc5bcC660cf2B9cE3cd51a404aFe1a0cBD3C22" as Address,
  quote: "0xd077A400968890Eacc75cdc901F0356c943e4fDb" as Address,
};

const [version, base, quote, paused, lensBinding] = await Promise.all([
  publicClient.readContract({address: market.swapper, abi: swapperAbi, functionName: "version"}),
  publicClient.readContract({address: market.swapper, abi: swapperAbi, functionName: "baseToken"}),
  publicClient.readContract({address: market.swapper, abi: swapperAbi, functionName: "quoteToken"}),
  publicClient.readContract({address: market.swapper, abi: swapperAbi, functionName: "paused"}),
  publicClient.readContract({address: market.lens, abi: lensAbi, functionName: "swapper"}),
]);

const same = (a: Address, b: Address) => a.toLowerCase() === b.toLowerCase();
if (version !== "1.2.0") throw new Error(`Unsupported Rondo version: ${version}`);
if (!same(base, market.base) || !same(quote, market.quote)) throw new Error("Token binding mismatch");
if (!same(lensBinding, market.swapper)) throw new Error("Lens is bound to another proxy");
if (paused) throw new Error("This market is paused");
```

Also read each token's live `symbol()` and `decimals()`. The registry value is useful for validation,
not a reason to run every token through JPYC's 18-decimal conversion.

## 2. Read a consistent order book

Prefer `Lens.openOrders` to a separate `openOrderIds` → `getOrders` sequence. It returns each id and
its tuple atomically. Pin a block across pages so an order closing between page requests cannot move
an unseen id into a page already consumed.

```ts theme={"system"}
const blockNumber = await publicClient.getBlockNumber();
const block = await publicClient.getBlock({blockNumber});

const book = [];
let offset = 0n;

while (true) {
  const [ids, orders, nextOffset] = await publicClient.readContract({
    address: market.lens,
    abi: lensAbi,
    functionName: "openOrders",
    args: [offset, 200n],
    blockNumber,
  });

  for (let i = 0; i < ids.length; i++) {
    const order = orders[i];
    // Storage may still say Open after the deadline until someone sweeps it.
    if (order.status === 1 && block.timestamp <= order.expiry) {
      book.push({id: ids[i], ...order});
    }
  }

  if (nextOffset === offset) break;
  offset = nextOffset;
}
```

### Interpret an `Order`

| Field                    | Meaning                                                        |
| ------------------------ | -------------------------------------------------------------- |
| `status`                 | `0 None · 1 Open · 2 Filled · 3 Cancelled · 4 Expired`         |
| `kind`                   | `0 Fixed · 1 Pegged`                                           |
| `sellToken`              | Escrowed by the maker; the taker receives it                   |
| `buyToken`               | Paid by the taker directly to the maker                        |
| `sellAmountOut`          | Not stored: the gross `sellToken` amount requested by one fill |
| `feeBps`                 | Fee snapshot captured when the order was created               |
| `sellRemaining`          | Gross escrow still available                                   |
| `buyAmount`              | Fixed-order full proceeds; zero for Pegged orders              |
| `premiumBps` · `minRate` | Pegged-order terms; zero for Fixed orders                      |

If `sellToken == baseToken`, the maker offers the regional stablecoin and the taker pays USDT. If
`sellToken == quoteToken`, the maker offers USDT and the taker pays the regional stablecoin.

<Info>
  Profitability is not a settlement guard. The contract accepts a valid in-band fixed order even when
  its current taker edge is negative. If your product promises “best” or “profitable” orders, compute
  and disclose that policy separately using the current oracle and the net output after fees.
</Info>

## 3. Quote from what the taker wants to pay

Users commonly enter the token leaving their wallet, while `fillOrder` takes the **gross output
released from escrow**. Convert the payment with `previewFillForAmountIn`:

```ts theme={"system"}
import {parseUnits} from "viem";

const orderId = 1n;
const paymentDecimals = 6; // Read from order.buyToken; this example pays USDT.
const typedAmountIn = parseUnits("25", paymentDecimals);

const [sellAmountOut, actualAmountIn, fee, netOut] =
  await publicClient.readContract({
    address: market.swapper,
    abi: swapperAbi,
    functionName: "previewFillForAmountIn",
    args: [orderId, typedAmountIn],
  });

if (sellAmountOut === 0n) throw new Error("Payment is too small to release one token unit");
```

The return values are all raw token units:

* `sellAmountOut`: gross escrow released and the second argument to `fillOrder`.
* `actualAmountIn`: what the taker currently pays. It can be below the typed amount when the order
  remainder caps the fill.
* `fee`: retained in the order's `sellToken`.
* `netOut = sellAmountOut - fee`: what reaches the taker's wallet.

`previewFill` and `previewFillForAmountIn` price a fill but do not prove that it can settle. A quote
can still be blocked by pause, expiry, size, role, balance or allowance. Use the Lens preflight and
a transaction simulation before opening the wallet.

### Minimum-fill rule

A full fill of `sellRemaining` is always allowed. Otherwise, while the order itself is at least
`minOrderAmount[sellToken]`, `sellAmountOut` must also reach that minimum. The rule is about the
gross output in this fill, not the payment and not the remainder left behind.

## 4. Preflight, approve and fill

For Fixed orders, `maxAmountIn` should equal the quote because their rate cannot change. Pegged
orders reprice from the five-minute Orakl reference when mined, so add a user-selected tolerance:

```ts theme={"system"}
const order = await publicClient.readContract({
  address: market.swapper,
  abi: swapperAbi,
  functionName: "getOrder",
  args: [orderId],
});

const toleranceBps = 50n; // 0.50%; chosen by the taker
const maxAmountIn = order.kind === 1
  ? (actualAmountIn * (10_000n + toleranceBps)) / 10_000n
  : actualAmountIn;

const [account] = await walletClient.getAddresses();
if (!account) throw new Error("Connect a wallet first");

const canTake = await publicClient.readContract({
  address: market.swapper,
  abi: swapperAbi,
  functionName: "canTake",
  args: [account],
});
if (!canTake) throw new Error("This account cannot take orders while the gate is enabled");

const balance = await publicClient.readContract({
  address: order.buyToken,
  abi: erc20Abi,
  functionName: "balanceOf",
  args: [account],
});
if (balance < maxAmountIn) throw new Error("Insufficient payment-token balance");

const allowance = await publicClient.readContract({
  address: order.buyToken,
  abi: erc20Abi,
  functionName: "allowance",
  args: [account, market.swapper],
});

if (allowance < maxAmountIn) {
  const {request} = await publicClient.simulateContract({
    account,
    address: order.buyToken,
    abi: erc20Abi,
    functionName: "approve",
    args: [market.swapper, maxAmountIn],
  });
  const approvalHash = await walletClient.writeContract(request);
  await publicClient.waitForTransactionReceipt({hash: approvalHash});
}

const [amountInNow, feeNow, netOutNow, problem] =
  await publicClient.readContract({
    address: market.lens,
    abi: lensAbi,
    functionName: "quoteFill",
    args: [orderId, sellAmountOut, account],
  });
if (problem !== 0) throw new Error(`Fill preflight failed with problem ${problem}`);
if (amountInNow > maxAmountIn) throw new Error("Price moved beyond the selected tolerance");

const {request} = await publicClient.simulateContract({
  account,
  address: market.swapper,
  abi: swapperAbi,
  functionName: "fillOrder",
  args: [orderId, sellAmountOut, maxAmountIn],
});

const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({hash});
if (receipt.status !== "success") throw new Error("Fill reverted");

const [fill] = parseEventLogs({
  abi: swapperAbi,
  eventName: "OrderFilled",
  logs: receipt.logs,
  strict: true,
}).filter((log) => log.args.orderId === orderId);

if (!fill) throw new Error("Successful receipt did not contain this order's fill event");
```

Approve and compare the balance against `maxAmountIn`, not only the current Pegged quote. The
Lens checks the current `amountIn`; `fillOrder` is the component that enforces the final ceiling.

### Lens `FillProblem`

| Value | Name                    | Action                                                    |
| ----: | ----------------------- | --------------------------------------------------------- |
|     0 | `None`                  | Continue to simulation                                    |
|     1 | `NotOpen`               | Refresh the book                                          |
|     2 | `Paused`                | Do not submit                                             |
|     3 | `NotWhitelisted`        | Ask the operator or choose an eligible account            |
|     4 | `Expired`               | Refresh; anyone may sweep the order                       |
|     5 | `ZeroAmount`            | Enter a positive size                                     |
|     6 | `ExceedsRemaining`      | Cap at `sellRemaining`                                    |
|     7 | `BelowMinFill`          | Raise the gross output to the current minimum or take all |
|     8 | `StaleOracle`           | Pegged only; wait for the feed                            |
|     9 | `BelowMakerFloor`       | Pegged order is temporarily unfillable                    |
|    10 | `InsufficientBalance`   | Fund the taker's payment token                            |
|    11 | `InsufficientAllowance` | Approve the Swapper proxy                                 |

Even `None` is not a settlement guarantee: the order may change before inclusion, `maxAmountIn`
may be crossed, or a token transfer may be unavailable. Always simulate the exact
transaction and still handle an on-chain revert.

## 5. Confirm and reconcile the settlement

Decode `OrderFilled` from the successful receipt. Its amounts are authoritative:

| Event field       | Accounting meaning                      |
| ----------------- | --------------------------------------- |
| `amountOut`       | Gross escrow released (`sellAmountOut`) |
| `amountIn`        | Payment sent from taker to maker        |
| `fee`             | Protocol fee retained in `sellToken`    |
| `amountOut - fee` | Net tokens received by the taker        |
| `sellRemaining`   | Order remainder after this fill         |

The maker's average realised rate for a partially filled Pegged order must be reconstructed from
its `OrderFilled` events because every fill can use a different oracle price. Sum raw `amountIn` and
gross `amountOut` first, then apply token decimals when displaying the ratio.

After the receipt, re-read `getOrder(orderId)`. Do not predict whether the order is still open from
the submitted amount alone: another fill may have landed first.

For event indexing, scan from the market's **First event block** in the registry, not genesis.
Use bounded block windows because public RPC providers limit `eth_getLogs`, persist the last
confirmed block, and make `(chainId, proxy, txHash, logIndex)` the event key. The events required to
reconstruct the book are documented in [Events & errors](/reference/events-errors).

## Maker integration

Making is permissioned per market. Check `isMaker(account)` before presenting an order form. Both
order types escrow `sellToken`, so approve the Swapper for `sellAmount` before creation.

Add these entries when supporting makers:

```ts theme={"system"}
export const makerAbi = parseAbi([
  "function isMaker(address) view returns (bool)",
  "function feeBps() view returns (uint16)",
  "function bandBps() view returns (uint16)",
  "function maxOrderDuration() view returns (uint64)",
  "function maxPremiumBps() view returns (uint16)",
  "function minOrderAmount(address) view returns (uint256)",
  "function isWithinBand(address sellToken,uint256 sellAmount,address buyToken,uint256 buyAmount) view returns (bool)",
  "function peggedRateNow(address sellToken,uint16 premiumBps,uint16 feeBps) view returns (uint256)",
  "function createOrder(address sellToken,uint256 sellAmount,address buyToken,uint256 buyAmount,uint64 expiry) returns (uint256 orderId)",
  "function createPeggedOrder(address sellToken,uint256 sellAmount,uint16 premiumBps,uint256 minRate,uint64 expiry) returns (uint256 orderId)",
  "function cancelOrder(uint256 orderId)",
  "event OrderCreated(uint256 indexed orderId,address indexed maker,address indexed sellToken,address buyToken,uint256 sellAmount,uint256 buyAmount,uint64 expiry,uint16 feeBps,uint256 oracleAnswer)",
  "event PeggedOrderCreated(uint256 indexed orderId,address indexed maker,address indexed sellToken,address buyToken,uint256 sellAmount,uint16 premiumBps,uint256 minRate,uint64 expiry,uint16 feeBps,uint256 oracleAnswer)",
  "event OrderCancelled(uint256 indexed orderId,address indexed by,bool forced,uint256 refunded)",
]);
```

### Fixed order

1. Read `minOrderAmount(sellToken)`, `maxOrderDuration`, `bandBps` and oracle freshness.
2. Convert both amounts using their own decimals.
3. Call `isWithinBand(sellToken,sellAmount,buyToken,buyAmount)`.
4. Simulate and send `createOrder(address,uint256,address,uint256,uint64)`.
5. Decode `OrderCreated` from the receipt to obtain the final `orderId`.

The contract reads the oracle only when a Fixed order is created. Its later fills use the posted
ratio and continue during an oracle outage.

### Oracle-pegged order

1. Read `feeBps`, `bandBps`, `maxPremiumBps`, `maxOrderDuration` and the current oracle state.
2. Ensure `premiumBps <= maxPremiumBps`.
3. Read `currentRate = peggedRateNow(sellToken,premiumBps,feeBps)`.
4. Choose a floor tolerance no wider than `bandBps` and calculate
   `minRate = currentRate × (10,000 − toleranceBps) / 10,000`.
5. Simulate and send `createPeggedOrder(sellToken,sellAmount,premiumBps,minRate,expiry)`.
6. Decode `PeggedOrderCreated` for the `orderId` and terms accepted on-chain.

`minRate` is buy-token raw units per `RATE_SCALE = 1e18` gross sell-token units. Do not construct it
from a formatted decimal string; use `peggedRateNow` and integer arithmetic. A Pegged fill requires
a fresh oracle and stops automatically below the maker's floor. Cancellation remains available.

<Warning>
  `premiumBps` is the taker's edge after the fee, not the maker's total cost. At a 25 bps premium and
  30 bps fee, the maker gives up roughly 55 bps. [Pricing details](/protocol/pricing).
</Warning>

Makers cancel with `cancelOrder(orderId)`. It works while paused and after expiry, and returns the
remaining escrow. Anyone may call `expireOrders(ids)` after deadlines; refunds still go only to the
makers.

## Decode failures into user actions

Add the following directly provided error ABI when simulating or decoding:

```ts theme={"system"}
export const swapperErrorAbi = parseAbi([
  "error AccessControlUnauthorizedAccount(address,bytes32)",
  "error BelowMakerFloor(uint256,uint256)",
  "error BelowMinFill(uint256,uint256)",
  "error BelowMinOrderAmount(uint256,uint256)",
  "error DurationTooLong(uint64,uint64)",
  "error EnforcedPause()",
  "error ExceedsRemaining(uint256,uint256)",
  "error FloorAboveMarket(uint256,uint256)",
  "error FloorTooLoose(uint256,uint256)",
  "error InvalidExpiry(uint64)",
  "error InvalidOracleAnswer(int256)",
  "error OracleTwapUnavailable()",
  "error InvalidTokenPair(address,address)",
  "error NotOrderMaker(uint256,address)",
  "error OrderExpired(uint256,uint64)",
  "error OrderNotOpen(uint256,uint8)",
  "error PremiumTooHigh(uint16,uint16)",
  "error PriceOutOfBand(uint256,uint256,uint256)",
  "error ReentrancyGuardReentrantCall()",
  "error SafeCastOverflowedUintDowncast(uint8,uint256)",
  "error SafeERC20FailedOperation(address)",
  "error SlippageExceeded(uint256,uint256)",
  "error StaleOracle(uint256,uint64)",
  "error TakerNotWhitelisted(address)",
  "error UnexpectedTransferAmount(uint256,uint256)",
  "error UnsupportedToken(address)",
  "error ZeroAddress()",
  "error ZeroAmount()",
  "error ZeroMakerFloor()",
]);
```

Combine it with the flow ABI before decoding:

```ts theme={"system"}
import {decodeErrorResult} from "viem";

try {
  await publicClient.simulateContract(/* exact request */);
} catch (error) {
  const data = extractRevertData(error); // library/app-specific error traversal
  const decoded = decodeErrorResult({abi: [...swapperAbi, ...swapperErrorAbi], data});
  console.error(decoded.errorName, decoded.args);
}
```

At minimum, provide explicit recovery for `OrderNotOpen`, `OrderExpired`, `BelowMinFill`,
`ExceedsRemaining`, `SlippageExceeded`, `StaleOracle`, `OracleTwapUnavailable`, `BelowMakerFloor`, `EnforcedPause`,
`TakerNotWhitelisted` and ERC-20 transfer failures. Refresh order and configuration state after any
revert instead of resubmitting the same arguments. [Complete error meanings](/reference/events-errors).

## Integration checklist

* Assert Kaia chain id **8217** before every write.
* Treat `(chainId, proxy, orderId)` as the order identity.
* Verify `version`, `baseToken`, `quoteToken` and `lens.swapper` at startup.
* Read token symbols and decimals from the actual token contracts.
* Pin one block across all order-book pages.
* Filter dynamically expired orders even if stored status is still `Open`.
* Use contract quotes for transaction arguments; local math is display-only.
* Remember that `sellAmountOut` is gross and the taker receives `sellAmountOut - fee`.
* For Pegged orders, approve and fund `maxAmountIn`, not only the current quote.
* Run Lens preflight, then simulate the exact write with the real account.
* Decode the receipt event and re-read the order after confirmation.

## Related references

<CardGroup cols={2}>
  <Card title="Contract API" icon="brackets-curly" href="/reference/api">
    User-facing callable signatures and tuple layout.
  </Card>

  <Card title="ABI JSON" icon="file-code" href="/reference/abi-json">
    Complete copy-ready Swapper and Lens ABI arrays.
  </Card>

  <Card title="The Lens" icon="magnifying-glass" href="/reference/lens">
    Atomic paging and guard-aware preflight behaviour.
  </Card>

  <Card title="Events & errors" icon="triangle-exclamation" href="/reference/events-errors">
    Settlement logs and recoverable revert reasons.
  </Card>

  <Card title="Building an interface" icon="display" href="/build/frontend">
    UX rules once the direct contract integration is correct.
  </Card>
</CardGroup>
