# RUJI Trade and CCL

### Overview

RUJI Trade is the Rujira spot trading venue built around FIN order book contracts. Each FIN contract represents one market pair. Integrators interact with FIN using standard CosmWasm queries and `MsgExecuteContract` transactions.

Custom Concentrated Liquidity (CCL) ranges are managed by the same FIN contract through the `range` execute message. For integrators, this means swaps (market orders), limit orders, tracking orders, and CCL liquidity management all use the FIN contract address for the pair.

#### Who This Guide Is For

| Integrator         | Typical integration                                                                                                                                      |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Wallets            | Show secured-asset balances, quote swaps (market orders), sign FIN market/limit/tracking orders, and guide users through securing assets before trading. |
| Swap routers       | Quote FIN liquidity for users who already hold secured assets, or split the route into a secure-asset step followed by the FIN swap.                     |
| Portfolio apps     | Query open orders, CCL positions, claimable fees, and pair configuration for display.                                                                    |
| Protocol UIs       | Use FIN swaps and orders directly, optionally with callbacks so the protocol can receive the output funds in the same transaction flow.                  |
| Liquidity managers | Create, update, withdraw, close, and transfer CCL positions.                                                                                             |

#### Funding Requirement: Secured Assets

FIN trades app-layer bank balances. A user cannot send ordinary L1 BTC, ETH, or USDC directly into a FIN swap or order. The user must first hold the relevant THORChain Secured Asset on the Rujira app layer.

Secured Assets use `-` as the asset delimiter. For example:

| Layer 1 Asset                                         | Secured Asset                                         | Example bank denom                                    |
| ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- |
| `BTC.BTC`                                             | `BTC-BTC`                                             | `btc-btc`                                             |
| `ETH.ETH`                                             | `ETH-ETH`                                             | `eth-eth`                                             |
| `ETH.USDC-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | `ETH-USDC-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` | `eth-usdc-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` |

For wallets and routers, this gives two possible user flows:

1. User already has the secured asset balance. Query the user's bank balances on their THOR address and call the FIN contract directly with those denoms.
2. User starts with an L1 asset. Guide the user through securing the asset first. THORChain documents the minting memo as `SECURE+:THORADD`, and the short memo form is commonly shown as `S+:THORADD`. The destination must be the user's THOR address, for example `SECURE+:thor1...`. Once the secured balance appears, submit the FIN transaction.

To redeem back out to an L1 address, THORChain documents `SECURE-:ADDR`; the short form is commonly shown as `S-:ADDR`. This is the reverse flow and is not part of the FIN swap itself.

Useful references:

* Rujira secured assets: <https://docs.rujira.network/developers/secured-assets>
* THORChain secured assets: <https://dev.thorchain.org/concepts/secured-assets.html>
* Deployment list for live FIN pair contracts: <https://rujira.network/developer/deployment>

### Architecture

```
External wallet / router / protocol UI
        |
        | 1. User secures L1 asset if needed
        v
THORChain/Rujira app layer bank balance
        |
        | 2. CosmWasm query / execute
        v
FIN pair contract
        |
        +-- Order book liquidity
        +-- CCL range positions
        +-- Optional protocol callbacks for swap/order outputs
```

Do not integrate the internal `do_swap`, `do_order`, or `do_range` messages. They are used by the contract itself after the public `swap`, `order`, or `range` message wraps execution through FIN's internal arbitrage step.

### Contract Addresses and Denoms

FIN contracts are pair-specific. Look up the current contract address for the pair you want to trade on the Rujira deployment page:

```
https://rujira.network/developer/deployment
```

Query the pair config before building UI or routes:

```json
{ "config": {} }
```

Response shape:

```json
{
  "denoms": ["btc-btc", "eth-usdc-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
  "oracles": null,
  "market_makers": [],
  "tick": 4,
  "range_delta": "0.001",
  "range_min": "1",
  "fee_taker": "0.001",
  "fee_maker": "0.001",
  "fee_range": "0.001",
  "fee_address": "thor1..."
}
```

The first denom is the base asset. The second denom is the quote asset.

All `Uint128` amounts are strings in JSON. All `Decimal` values are also strings. All Secured Assets use 8 decimal places for display, but integrators should read the active asset metadata and denom list instead of hardcoding display rules for every asset.

### FIN Concepts

#### Base, Quote, and Side

FIN uses two denoms:

```
denoms[0] = base
denoms[1] = quote
```

For swaps (market orders), the contract derives the side from the denom sent in `funds`.

| User sends  | Contract side | User receives |
| ----------- | ------------- | ------------- |
| base denom  | `quote`       | quote denom   |
| quote denom | `base`        | base denom    |

For orders, `side` means the denom locked in the resting order.

| Order side | Order locks | Order receives when filled |
| ---------- | ----------- | -------------------------- |
| `base`     | base denom  | quote denom                |
| `quote`    | quote denom | base denom                 |

This is the main side-related gotcha. A swap that sends quote funds routes through the `base` side and receives base. A resting order with side `quote` also uses quote funds, but there the side names what is locked in the order.

#### Price Types

FIN supports two price formats:

```json
{ "fixed": "90000" }
```

```json
{ "oracle": 0 }
```

`fixed` is an explicit decimal price, it is used for standard limit order.

`oracle` is an oracle-relative price in basis points, it is used for "tracking" orders, a novel order type pionnered by Rujira that reprices every block based on enshrined oracle price +/- a premium or discount. For example, `0` means the oracle price, `100` means oracle plus 1%, and `-100` means oracle minus 1%. Oracle values must have absolute value below `10000`.

Order fixed prices and CCL range bounds must be valid for the pair's `tick`. The `tick` is a significant-figure style validation used by the contract. If the price is not aligned to the configured tick, the transaction fails with an invalid price error.

Swap limit prices are also decimal strings, but they are not the same as `Price::Fixed` and are not tick-validated by FIN.

#### Querying the Book

```json
{
  "book": {
    "limit": 100,
    "offset": 0
  }
}
```

Response shape:

```json
{
  "base": [
    { "price": "90000", "total": "100000000" }
  ],
  "quote": [
    { "price": "89900", "total": "5000000000" }
  ]
}
```

The the query returns a paginated book response with:

* `limit`: the maximum number of price levels to return per side (defaults to `100`);
* `offset`: number of price levels to skip from the front of each side (defaults to `0`).

The book response merges all the liquidity that FIN can trade against, including limit orders, tracking orders, CCL range liquidity and THORChain base layer liquidity via the [Virtualization Strategy](/core-products/ruji-amm/base-layer-virtualization-strategy.md).

### Swap Integration

Use `simulate` to quote a market-style swap before asking the user to sign.

```json
{
  "simulate": {
    "denom": "eth-usdc-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    "amount": "1000000000"
  }
}
```

Response:

```json
{
  "returned": "11000",
  "fee": "12"
}
```

`simulate` always simulates a market-style swap over the current available liquidity. It does not apply your UI slippage setting. For user protection, routers and wallets should convert the simulated result into a `min_return` swap.

#### Swap Messages

All swaps send exactly one native coin in `funds`.

Market swap without protection:

```json
{
  "swap": {
    "to": null,
    "callback": null
  }
}
```

Recommended wallet/router swap with slippage protection (return at least `min_return` or fail):

```json
{
  "swap": {
    "min_return": "10900",
    "to": null,
    "callback": null
  }
}
```

Exact-return swap (return exactly `exact_return` or fail):

```json
{
  "swap": {
    "exact_return": "11000",
    "to": null,
    "callback": null
  }
}
```

Limit-priced swap (swap as much of the input as possible at or better than `price`, returning any unused offer):

```json
{
  "swap": {
    "price": "90000",
    "to": null,
    "callback": null
  }
}
```

For a limit-priced swap, `price` is quoted in the ask token, which is the token the user sends. If the user sends quote to buy base, use the normal quote-per-base price. If the user sends base to sell for quote, use the inverse price.

The `to` field is optional. If omitted or `null`, the output goes to the signer. If set, the output goes to that address.

The `callback` field is optional and is mainly for protocol integrations. See the callback section below.

#### TypeScript Example: Quote and Swap

```ts
import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";

const rpc = "https://gateway.liquify.com/chain/thorchain_rpc";
const finContract = "thor1..."; // Pair-specific FIN contract address.
const sender = "thor1...";
const offerDenom = "eth-usdc-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
const offerAmount = "1000000000";

function applySlippage(amount: string, slippageBps: bigint): string {
  const value = BigInt(amount);
  return ((value * (10_000n - slippageBps)) / 10_000n).toString();
}

const queryClient = await CosmWasmClient.connect(rpc);

const quote = await queryClient.queryContractSmart(finContract, {
  simulate: {
    denom: offerDenom,
    amount: offerAmount,
  },
});

const minReturn = applySlippage(quote.returned, 50n); // 0.50% max slippage.

const signingClient = await SigningCosmWasmClient.connectWithSigner(rpc, signer);

const tx = await signingClient.execute(
  sender,
  finContract,
  {
    swap: {
      min_return: minReturn,
      to: null,
      callback: null,
    },
  },
  "auto",
  "",
  [{ denom: offerDenom, amount: offerAmount }],
);
```

#### Router UX Notes

* Check the user's app-layer bank balance before building the FIN transaction.
* If the user only holds the L1 asset, show a separate secure-asset step first.
* Wait for the secured balance to appear before submitting the FIN swap.
* Use `simulate` for the quote, but execute with `min_return`.
* If the user sends the base denom, they are selling base for quote. If the user sends the quote denom, they are buying base with quote.

### Limit & Tracking Order Integration

Use the `order` execute message to create, resize, withdraw from, or cancel one or more orders.

The execute shape is:

```json
{
  "order": [
    [
      ["base", { "fixed": "90000" }, "1000000"],
      ["quote", { "oracle": 0 }, "5000000000"]
    ],
    null
  ]
}
```

Each order target is:

```
[side, price, target_offer_amount]
```

`target_offer_amount` is the desired resting offer amount after the message completes.

The target amount is denominated in the asset locked by the order side:

| Order side | Target amount denom |
| ---------- | ------------------- |
| `base`     | base denom          |
| `quote`    | quote denom         |

| Target value | Meaning                                                                           |
| ------------ | --------------------------------------------------------------------------------- |
| `"1000000"`  | Create the order if missing, or resize the existing order to this offer amount.   |
| `"0"`        | Withdraw filled amount first, then cancel the remaining offer.                    |
| `null`       | If the order exists, withdraw filled amount only. If no order exists, do nothing. |

FIN only allows to have one active limit order per price level. If an order already exists at a given price, a subsequent `order` execute message will update the size of the existing order.

Funds sent with the transaction must cover the net increase in order size. Funds withdrawn from filled or reduced orders in the same execution can be reused inside that same execution.

When creating a new order, FIN may immediately match part of the offer against opposite liquidity. Any remaining offer becomes the resting order.

#### Query One Order

```json
{
  "order": [
    "thor1owner...",
    "base",
    { "fixed": "90000" }
  ]
}
```

Response shape:

```json
{
  "owner": "thor1owner...",
  "side": "base",
  "price": { "fixed": "90000" },
  "rate": "90000",
  "updated_at": "1710000000000000000",
  "offer": "1000000",
  "remaining": "750000",
  "filled": "250000"
}
```

`filled` is not automatically paid out. A later `order` execution touching the same order withdraws the filled amount.

#### Query Orders

```json
{
  "orders": {
    "owner": "thor1owner...",
    "side": null,
    "start_after": null,
    "limit": 10
  }
}
```

`limit` defaults to `10`.

For pagination, pass the last returned order as:

```json
["thor1owner...", "base", { "fixed": "90000" }]
```

The cursor is always shaped as `(owner, side, price)`. When querying a single owner, the contract ignores the owner string inside the cursor, but the schema still requires the tuple.

#### TypeScript Example: Place a Buy Order

In this example the user sends quote funds and places a `quote` side order, meaning the order locks quote and receives base when filled.

```ts
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";

const rpc = "https://gateway.liquify.com/chain/thorchain_rpc";
const finContract = "thor1...";
const sender = "thor1...";
const quoteDenom = "eth-usdc-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
const targetOfferAmount = "5000000000";

const client = await SigningCosmWasmClient.connectWithSigner(rpc, signer);

const result = await client.execute(
  sender,
  finContract,
  {
    order: [
      [
        ["quote", { fixed: "90000" }, targetOfferAmount],
      ],
      null,
    ],
  },
  "auto",
  "",
  [{ denom: quoteDenom, amount: targetOfferAmount }],
);
```

### CCL Range Integration

CCL ranges are concentrated liquidity positions inside FIN. A range has:

| Field    | Meaning                                                                                                                                        |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `low`    | Lower price bound.                                                                                                                             |
| `high`   | Upper price bound.                                                                                                                             |
| `skew`   | Distribution slope. Use `"0"` for normal integrations unless you are deliberately building an advanced strategy.                               |
| `spread` | Distance between the range's internal price and its best bid/ask. Must be below `1`.                                                           |
| `fee`    | Profit from the spread claimable as yield. If `spread` is `0`, `fee` must also be `0`. Otherwise `fee` must be less than or equal to `spread`. |

Range creation validates:

* `high` must be greater than `low`.
* `skew` must be greater than `-2` and less than `2`.
* `spread` must be less than `1`.
* `fee` must be `0` when `spread` is `0`.
* `fee` must be less than or equal to `spread` when `spread` is not `0`.
* `high` and `low` must be valid tick prices for the pair.

#### Create a Range

Send base funds, quote funds, or both. At least one of the two pair denoms must be present.

The range code only reads the pair's base and quote denoms from `funds`. Do not attach unrelated denoms. They are not part of the range deposit logic.

```json
{
  "range": {
    "create": {
      "config": {
        "high": "95000",
        "low": "85000",
        "skew": "0",
        "spread": "0.002",
        "fee": "0.001"
      },
      "slippage": ["90000", "0.005"]
    }
  }
}
```

The optional `slippage` field is `[expected_price, max_relative_difference]`. `expected_price` must be nonzero. In the example above, range creation fails if the resulting range price is more than `0.5%` away from `90000`.

If FIN can calculate a current mid price, the contract balances the sent base and quote amounts for the range and refunds excess funds. If no mid price is available, it creates the range from the sent funds as-is.

TypeScript example:

```ts
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";

const rpc = "https://gateway.liquify.com/chain/thorchain_rpc";
const finContract = "thor1...";
const sender = "thor1...";
const baseDenom = "btc-btc";
const quoteDenom = "eth-usdc-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";

const client = await SigningCosmWasmClient.connectWithSigner(rpc, signer);

await client.execute(
  sender,
  finContract,
  {
    range: {
      create: {
        config: {
          high: "95000",
          low: "85000",
          skew: "0",
          spread: "0.002",
          fee: "0.001",
        },
        slippage: ["90000", "0.005"],
      },
    },
  },
  "auto",
  "",
  [
    { denom: baseDenom, amount: "1000000" },
    { denom: quoteDenom, amount: "90000000000" },
  ],
);
```

#### Query One Range

```json
{ "range": "1" }
```

Response shape:

```json
{
  "idx": "1",
  "owner": "thor1owner...",
  "high": "95000",
  "low": "85000",
  "skew": "0",
  "spread": "0.002",
  "fee": "0.001",
  "base": "1000000",
  "quote": "90000000000",
  "price": "90000",
  "ask": "90180",
  "bid": "89820",
  "fees": ["123", "456"]
}
```

`fees` is `[base_fee, quote_fee]`.

Range `base`, `quote`, and `fees` are decimal strings. They can contain fractional values after trades. Bank sends are still whole-token amounts, so claim, withdraw, and close payouts floor the decimal values to integer coin amounts.

#### Query Ranges

```json
{
  "ranges": {
    "owner": "thor1owner...",
    "cursor": null,
    "limit": 30
  }
}
```

`limit` defaults to `30`. Use the last returned `idx` as `cursor` for the next page.

#### Deposit Into a Range

Only the current owner can deposit.

```json
{
  "range": {
    "deposit": {
      "idx": "1"
    }
  }
}
```

Send base funds, quote funds, or both. At least one pair denom must be present. As with create, do not attach unrelated denoms.

#### Claim Range Fees

Only the current owner can claim.

```json
{
  "range": {
    "claim": {
      "idx": "1"
    }
  }
}
```

Claimed fee amounts are floored to whole bank-token units. Fractional remainder stays in the range.

#### Withdraw From a Range

Only the current owner can withdraw.

```json
{
  "range": {
    "withdraw": {
      "idx": "1",
      "amount": "0.25"
    }
  }
}
```

`amount` is a fraction of the range to withdraw. `"0.25"` means 25%; `"1"` means 100%. Values above `1` fail.

#### Close a Range

Only the current owner can close.

```json
{
  "range": {
    "close": {
      "idx": "1"
    }
  }
}
```

Closing removes the range and returns its remaining base, quote, and claimable fees.

#### Transfer a Range

Only the current owner can transfer.

```json
{
  "range": {
    "transfer": {
      "idx": "1",
      "to": "thor1newowner..."
    }
  }
}
```

### Callbacks for Protocol Integrations

Swaps and orders can use callbacks. Ranges do not expose a callback field.

Callbacks are useful when a protocol wants FIN to send output funds to a contract instead of doing a plain bank send. The receiving contract must implement a `callback` execute message.

For swaps, the callback receiver is the `to` address when `to` is set; otherwise it is the signer. For orders, there is no `to` field, so the callback receiver is the account that submitted the order message. In practice, an order callback is mainly for contracts that call FIN themselves.

Callback execute shape received by your contract:

```json
{
  "callback": {
    "data": "base64_encoded_json_data",
    "callback": "base64_encoded_callback_payload"
  }
}
```

For FIN swaps, `data` is an empty JSON object encoded as binary. The output funds are attached to the callback execute message.

For FIN orders, the callback payload is supplied as the second element of the `order` execute tuple:

```json
{
  "order": [
    [
      ["base", { "fixed": "90000" }, "1000000"]
    ],
    "base64_encoded_callback_payload"
  ]
}
```

Use callbacks only when your receiving contract is prepared to handle the funds and decode the callback payload. Wallets and simple routers usually leave `callback` as `null`.

### Events

CosmWasm event types appear in transaction logs with a `wasm-` prefix.

Swap/trade event:

```
wasm-rujira-fin/trade
```

Common trade attributes:

| Attribute | Meaning                                                         |
| --------- | --------------------------------------------------------------- |
| `rate`    | Execution rate for that fill.                                   |
| `offer`   | Amount consumed from the offered asset.                         |
| `bid`     | Amount returned before swap-level fee accounting for that fill. |

Indexer caveat: treat trade event attributes as an ordered list, not a map with unique keys. A single trade event can include provider-specific attributes, and keys such as `price` or `side` can appear more than once when merged liquidity contributes to the fill.

Order events:

```
wasm-rujira-fin/order.create
wasm-rujira-fin/order.withdraw
wasm-rujira-fin/order.increase
wasm-rujira-fin/order.retract
```

Range events:

```
wasm-rujira-fin/range.create
wasm-rujira-fin/range.claim
wasm-rujira-fin/range.deposit
wasm-rujira-fin/range.withdraw
wasm-rujira-fin/range.close
wasm-rujira-fin/range.transfer
wasm-rujira-fin/range.fee
```

Use events for fast UI updates, but keep contract queries as the source of truth after a transaction is indexed.

### UI and UX Checklist

* Show the secured-asset funding step before FIN actions if the user does not have the required app-layer balance.
* Use the user's THOR address for secured-asset balances and FIN signing.
* Query `config` for denoms, fees, tick, and oracle support before rendering a market.
* Query `simulate` before swaps and execute with `min_return`.
* Display base and quote consistently. The second denom from `config.denoms` is the quote denom.
* Show open orders with `remaining` and `filled`; make it clear that filled orders need a later order touch to withdraw.
* Show CCL range ownership, bounds, current `price`, `ask`, `bid`, and `[base, quote]` fees.
* For CCL UIs, default `skew` to `"0"` unless the user is managing an advanced strategy.
* Refresh state after transaction inclusion by re-querying the contract.

### Common Errors and Gotchas

| Issue                                      | Why it happens                                                   | Integrator fix                                                                                     |
| ------------------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `invalid denom`                            | The sent fund denom is not one of the pair denoms.               | Query `config` and only use `denoms[0]` or `denoms[1]`.                                            |
| Swap fails with multiple funds             | FIN swaps require exactly one native coin.                       | Send one `funds` entry only.                                                                       |
| `InsufficientReturn`                       | `min_return` or `exact_return` was not met.                      | Requote and ask the user to confirm a new slippage setting.                                        |
| Invalid price / tick error                 | Order fixed price or range bound is not valid for the pair tick. | Round or truncate order prices and range bounds to valid tick values before signing.               |
| Limit swap executes in the wrong direction | Swap limit price is quoted in the token being sent.              | Use normal quote-per-base price for quote-to-base buys, and inverse price for base-to-quote sells. |
| `empty funds` on range create/deposit      | No base or quote funds were sent.                                | Send at least one of the pair denoms and no unrelated funds.                                       |
| `amount > 1` on range withdraw             | Withdraw amount is a fraction and cannot exceed 100%.            | Use `"1"` for full withdrawal.                                                                     |
| Callback does not fire                     | No payout funds were produced, or callback was not supplied.     | Only rely on callback when a payout is expected and the field is set.                              |
| User cannot trade L1 balance directly      | FIN uses app-layer secured-asset bank balances.                  | Secure the asset first with `SECURE+`/`S+`, then submit the FIN transaction.                       |

### Message Reference

#### FIN Queries

```json
{ "config": {} }
```

```json
{ "simulate": { "denom": "btc-btc", "amount": "1000000" } }
```

```json
{ "book": { "limit": 100, "offset": 0 } }
```

```json
{ "order": ["thor1owner...", "base", { "fixed": "90000" }] }
```

```json
{
  "orders": {
    "owner": "thor1owner...",
    "side": null,
    "start_after": null,
    "limit": 10
  }
}
```

```json
{ "range": "1" }
```

```json
{
  "ranges": {
    "owner": "thor1owner...",
    "cursor": null,
    "limit": 30
  }
}
```

#### FIN Executes

```json
{ "swap": { "min_return": "1000000", "to": null, "callback": null } }
```

```json
{
  "order": [
    [
      ["base", { "fixed": "90000" }, "1000000"]
    ],
    null
  ]
}
```

```json
{
  "range": {
    "create": {
      "config": {
        "high": "95000",
        "low": "85000",
        "skew": "0",
        "spread": "0.002",
        "fee": "0.001"
      },
      "slippage": ["90000", "0.005"]
    }
  }
}
```

```json
{ "range": { "deposit": { "idx": "1" } } }
```

```json
{ "range": { "claim": { "idx": "1" } } }
```

```json
{ "range": { "withdraw": { "idx": "1", "amount": "0.25" } } }
```

```json
{ "range": { "close": { "idx": "1" } } }
```

```json
{ "range": { "transfer": { "idx": "1", "to": "thor1newowner..." } } }
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.rujira.network/developers/ruji-product-integration-guides/ruji-trade-and-ccl.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
