RUJI Trade and CCL

Technical documentation for wallets, swap routers, portfolio apps, and protocols integrating RUJI Trade through the FIN contract and its CCL range liquidity.

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

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:

Query the pair config before building UI or routes:

Response shape:

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:

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:

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

Response shape:

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.

Swap Integration

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

Response:

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:

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

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

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

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

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:

Each order target is:

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

Response shape:

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

Query Orders

limit defaults to 10.

For pagination, pass the last returned order as:

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.

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.

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:

Query One Range

Response shape:

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

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.

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.

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.

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.

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

Transfer a Range

Only the current owner can transfer.

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:

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:

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:

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:

Range events:

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

FIN Executes

Last updated