Books

A book is identified by its token_pair:
  • Token ids are NEAR Intents multi-token ids (nep141:<ft contract> or nep245:<contract>:<id>).
  • Only one direction of a pair exists: if QTC/USDT exists, USDT/QTC cannot be created.
  • Each book has its own fee config, minimum order amounts and tick size. Only the contract’s managers create books.
  • Orders sit at price levels: asks by price ascending, bids descending; inside a level, by time (created_ts, the block timestamp in nanoseconds).

Sides

Through mt_transfer_call the side follows from the token you send. Events use "s" and "b". An order’s amount / order_balance is in the token it holds: base units for asks, quote units for bids.

Raw prices

Prices are raw: quote units per one base unit, as a decimal string (UD128, up to 38 decimal places).
Back: human_price = raw_price × 10^decimals(base) / 10^decimals(quote). Use decimal strings or big integers, never floats, for anything you sign. Views and events return prices normalized, without trailing zeros: a price placed as "0.0001390" comes back as "0.000139". Compare prices as numbers, not as strings. A fill’s quote amount is base × raw_price in integer units, rounded by the contract; fees round up (ceil). Read exact amounts from the quote view or the trade event instead of recomputing them.

Minimum order amount

Each book has min_order_amount: { base, quote } (raw units), shown by get_active_pairs.
  • A new order below the minimum for the token it holds does not rest. From a transfer, it is refunded in the same transaction (taker_remainder.refund in the quote, rf in the trade event); in a batch, it panics.
  • A maker order a trade leaves below the minimum, or unable to buy one base unit, is closed with a refund (closed_orders in the quote, c in the trade event).

Tick size

Each book has a tick_size (a raw price step, 0 = none). Every new limit price, from a transfer or a batch, must be an exact multiple of it, or the call panics. Resting orders and market orders are not affected. Round your prices to the tick away from the market: bids down, asks up.

Fill limit

A taker order fills at most max_fills maker orders (default and cap 128, 0 is rejected). Set it in the order msg and pass the same value to get_quote_pub. Past the limit the call does not fail: the rest is refunded (truncated: true in the quote). Gas and the 16 KiB log limit can stop a fill earlier, with the same effect. The NEAR Intents relay gives execute_intents 300 Tgas: orders sent through it should use max_fills ≈ 40. Direct mt_transfer_call transactions can attach more gas; see gas.

Quote equals execution

get_quote_pub decides everything a trade does: the maker orders it fills and how much, the ones it closes (closed_orders), whether the taker’s remainder rests (new_order) or is refunded (refund), and every balance it moves (increase_user_balance). Execution applies the quote as is and checks it. If the book did not change in between and the receipt has enough gas, the trade matches the quote to the unit. The view cannot see gas and quotes as if there were enough. With less gas, execution fills a prefix of the quoted fills and refunds the rest (rf); with too little for one fill it panics (Not enough gas to fill the order) and the transfer is refunded. Payouts that do not fit in the remaining gas are credited to internal balances instead of paid out (the trade’s np).

Dead orders

A resting order is dead when:
  • expires_at != 0 and expires_at <= block_timestamp (expired), or
  • its epoch is below its maker’s current epoch in that book (stale epoch, after cancel_all / bump_epoch).
A dead order stays in storage until something closes it:
  • Views hide it. get_orderbook and the depth views skip it. get_volume / get_active_pairs volume still count it (it still holds its balance). is_order_live tells you the state of one hash.
  • Matching closes it. A trade that meets it does not fill it: it refunds its whole balance to the maker and lists it in closed_orders / the trade’s c. It counts against max_fills, gas and the log budget like a closing fill.
  • Anyone can prune it. prune closes listed dead orders; refunds go to the makers’ internal balances.
  • It still takes its slot in the 500-per-level and 200-per-owner limits, and a batch’s search for the best live price scans it (at most 128 orders per side). Close your own dead orders in your next batch’s cancel; see Build a market maker.
The quote uses the view’s block timestamp, so quote equals execution holds within one block. Between a view and execution an order may expire, like any other book change.

Internal balance

The contract keeps a per-account, per-token balance (get_user_balance). Makers fund many orders from it with one atomic batch instead of one transfer per order.

Payout modes

set_payout_mode chooses where a maker’s proceeds go: With internal, filled inventory backs the next batch without a deposit round trip. The mode does not change what you receive as a taker or fee collector. prune refunds always go to the internal balance.

Order hash

An order is identified by sha256(borsh(Order)) with Order = { side, maker, price, token_pair, created_ts }, shown as 64 hex characters. Expiry and epoch are stored separately and are not part of the hash.
Two orders of one maker with the same side, price and pair in the same block have the same hash. From a transfer the second panics and is refunded; in a batch it is skipped (duplicate). Merge such placements into one order.