AXIS Docs

Using AXISCore concepts

Orders

How AXIS orders work: limit and market orders, Fill-or-Kill, swaps, updates, backing, rounding and order lifetime.

Makers and takers#

A maker owns an order on the book. A taker sends a transaction that fills orders stored on the book. The same trader is often both: a limit order first fills what it can as a taker, then places the remainder on the book as a maker.

Limit orders#

A limit order says "sell this amount, but not below this price" or "buy this amount, but not above this price". The AXIS app shows prices in the quote asset per one unit of the base asset, for example 0.25 USDC per XLM (see Base and Quote).

When you place a limit order:

  1. It first fills against the orders your app found for you, each at that maker's price, which is your price or better.
  2. Whatever is left stays on the book as your order until someone fills it, you cancel it or it expires.

Example: you sell 100 XLM at 0.25 USDC. The book has a buy order for 40 XLM at 0.26 USDC. You sell 40 XLM for 10.4 USDC right away, and the other 60 XLM stay on the book at 0.25 USDC. Later another buyer takes 20 XLM from your order. That is a partial fill, and 40 XLM stay on the book in your order.

A limit order needs an open market with a fresh oracle price (see Markets and prices), and its whole amount must meet the minimum order value.

Market orders#

A market order fills what is available now and leaves nothing on the book. In the contract this is the Fill order kind. The AXIS app asks the AXIS API for the orders that cover your amount and uses the worst price among them as your limit. You get every fill available within that limit, and whatever cannot be filled stays in your wallet.

Example: you buy 100 XLM at market. The quote finds 60 XLM at 0.25 USDC and 40 XLM at 0.26 USDC, so your limit is 0.26 USDC. If someone takes the 0.26 order before your transaction lands, you receive 60 XLM, pay 15 USDC and keep the rest of your USDC.

Market orders need no open market and no fresh oracle price, and have no minimum order value.

Fill-or-Kill#

A Fill-or-Kill order fills completely within your limit price or not at all. If the listed orders cannot cover the whole amount, the transaction fails and nothing moves. Usually the app's simulation catches this before you sign, so nothing is paid.

Swaps#

A swap trades one asset for another through one or more markets in a single transaction, for example XLM to EURC through USDC. You specify either the amount you pay or the amount you receive. See Swaps across several markets.

Expiration#

An order can carry an optional expiration time. Once that time passes, the order can no longer be filled and reads as gone. You can still cancel an expired order, and an app can revive it by updating it with a new expiration time, or with none.

Updating an order#

The owner can change an order's price, amount or expiration in place with the contract's update function. The order keeps its ID and its storage entry, so an update pays no new rent transaction fee, only a small extension for the time since its last update. One update costs about 70 times chepaer in network fees compared with a new order creation (see Fees and costs). Market makers can use it to move many quotes in one transaction.

An update goes through the same checks as a new order: your balance and allowance must cover the new amount, the order must meet the minimum order value, and the market needs a fresh oracle price. An update also extends the order's storage lifetime, see Order lifetime.

An update sets the new amount, it does not subtract from the current one, so a fill that lands just before the update is not taken into account.

Canceling an order#

Canceling removes the order from the book. In the contract it is an update call that sets the amount to zero. It costs about very cheap per order in transaction fees, works on expired orders and keeps working even while trading is frozen. The storage rent paid when the order was created is not refunded. Canceling does not change your allowance.

Order backing#

AXIS does not lock your tokens when you place an order. Each order is backed by your balance of the token you sell and the allowance you gave the AXIS contract on it. The backing is the smaller of the two, and all your orders selling that token share it as one budget.

The contract checks backing when you create or update an order. After that you are free to spend your tokens. If a later fill needs more than your remaining backing, the contract skips that fill and leaves your order unchanged.

The indexer assigns the shared budget to your oldest orders first. For example, if you have two orders selling 100 USDC each, and you hold 150 USDC with a large enough allowance, the older order is 100% backed and the newer one 50% backed. The AXIS app shows this as an "N% backed" tag on your open orders, and the orderbook also counts only backed amounts. The orderbook assigns each maker's budget to their best-priced orders first, so its numbers can differ from the tags. Allowances and your funds explains how to keep your orders backed.

Minimum order value#

Limit orders and updates must be worth at least a minimum value in USD, set by the safety admin. On Testnet the minimum is 0.001 USD. The contract estimates the order value with the cached oracle price. For a limit order the whole amount counts, including any part that fills right away. Smaller orders fail with "Order size is below the minimum trade size". Market orders, Fill-or-Kill orders and swaps have no minimum.

Separately, the contract never stores an order worth less than one smallest unit of the asset it buys. Such an amount is called "dust". A fill that leaves only dust removes the order.

Rounding#

Token amounts are whole numbers of each token's smallest unit, which is 0.0000001 for 7-decimal assets such as XLM and USDC. When a fill does not divide evenly, the contract rounds in the maker's favor: what the taker receives is rounded down and what the taker pays is rounded up. A maker never receives less than their price. A taker pays at most one smallest unit extra per fill.

Your limit price is compared with each order's price, not with the rate a fill ends up at after rounding, so a fill can cost up to one smallest unit more than your limit allows. That is negligible for 7-decimal assets such as XLM and USDC, but one unit of a token with few decimals can be worth more, and apps that trade such tokens leave out orders whose rounding costs too much.

Example: buying 1.0000001 XLM at 0.3 USDC would cost exactly 0.30000003 USDC, which is not a whole number of units. You pay 0.3000001 USDC.

Order lifetime#

Each order is a storage entry, and Stellar charges rent for smart contract storage. A new order gets about 120 days of storage on mainnet, and only 7 days on Testnet, where the network minimum is shorter. Each update extends it again, to 120 days or to the order's expiration plus one day, whichever is shorter. Fills and cancels do not extend it.

An order nobody updates is archived when that storage runs out, after about 120 days on mainnet or 7 days on Testnet. Any transaction that touches an archived order restores it automatically and pays a fresh round of rent. To keep an order on the book for longer, update it before it archives.

Crossed orders and crossfill#

Because the contract only matches the orders a trader lists, a new limit order can be created at a price that crosses an existing order, for example a buy at 0.26 USDC while someone sells at 0.25 USDC. The book is then crossed.

Anyone can resolve this with the contract's crossfill function. It matches the crossing orders, pays each owner at least their own price and gives the difference to whoever made the call. Example, ignoring smallest-unit rounding: Alice sells 100 XLM at 0.25 USDC, and Bob buys 100 XLM at up to 0.26 USDC. A crossfill with Alice's order as the taker order sends Alice's 100 XLM to Bob for his 26 USDC, pays Alice 25 USDC and gives the remaining 1 USDC to the caller. The caller needs no funds, only the ability to receive the asset the taker order buys, USDC in this example.

Order IDs#

Each order has an ID derived from the owner's address and a pseudo-random byte sequence ("nonce"). IDs are not sequential. A nonce is unique among all trader's open orders, and apps pick a fresh one for every order.