AXIS Docs

DevelopersGuides

Migrating from the Classic DEX

For apps that trade through Horizon and Classic offers today: how AXIS concepts, endpoints and data formats map to what you know, and what behaves differently.

Important

The AXIS API is not a drop-in replacement for Horizon. It has its own endpoints, parameters and response formats. Plan a port, not a base URL switch.

AXIS and the Classic DEX are separate venues. Classic offers and liquidity pools stay on the Classic DEX, AXIS orders live in the AXIS contract, and neither venue matches against the other.

Concept map#

Classic DEX AXIS Notes
Offer, an account subentry Order, a persistent entry in the AXIS contract storage See Order lifecycle and rent
Offer ID, an integer assigned by the network Order ID, a u128 hash of the owner and a client nonce Known before submission. Not sequential. See Contract design.
ManageSellOffer trade with Sell and Limit (AxisAccount.sell with a price) Same price orientation: buying per selling
ManageBuyOffer trade with Buy and Limit (AxisAccount.buy with a price) Same price orientation: selling per buying
Updating an offer by ID update: new amount, price and expiration in place, up to about 110 orders per call (the JS client sends 90 per transaction) Keeps the ID and pays no new 120-day rent chunk, only a small extension
Deleting an offer (amount 0) update with amount 0 (cancel in the JS client) Works on expired orders and while frozen
CreatePassiveSellOffer No equivalent A trade matches only the orders its caller lists. A Limit trade with an empty orders list only creates an order, even when it crosses the book.
PathPaymentStrictSend swap with Sell: exact input, minimum output Up to 3 hops from /quote. The output always goes to the trader, so paying a third party takes a separate transfer
PathPaymentStrictReceive swap with Buy: exact output, maximum input The output always goes to the trader
Matching inside the protocol, price-time priority The taker lists order IDs. The contract matches only those, in list order. No on-chain priority. See Trades, swaps and crossfills.
Offers never expire Optional expires (UNIX seconds) per order Expiry emits no event
Base reserve per offer, selling liabilities lock the funds Ledger rent per order (about 0.041 XLM per 120 days, not refundable). A standing allowance per selling token. Funds stay spendable. An order can lose its backing. See Settlement and allowances.
Offers are always funded An order is fillable up to the maker's effective depth Unbacked orders are skipped, not removed
Trustline required to receive an asset Same. Tokens are the Stellar Asset Contracts of the Classic assets. What a trader buys passes through the AXIS contract, so an AUTH_REQUIRED asset can be bought only once its issuer has authorized the contract (712 otherwise)
Horizon operations, effects and trades Contract events (new, mod, trade, skip, swap) and the AXIS API See Events

Endpoint mapping#

Horizon AXIS API Differences
GET /order_book GET /depth?market=BASE/QUOTE Aggregated [price, quantity] levels within a percent band (depth), level size step, up to limit levels per side. Only backed amounts count.
GET /paths/strict-send GET /quote?direction=strict_send Returns, per hop, the order IDs to pass to swap. Up to 10 routes, 3 hops and 20 orders per route. AXIS orders only.
GET /paths/strict-receive GET /quote?direction=strict_receive Same as above, amount is the amount to receive
GET /trade_aggregations GET /candles Rows [ts, open, high, low, close, baseVolume, quoteVolume, trades]. Times in UNIX seconds. Always pass from.
GET /trades GET /trades Filters trader and pair. Trade and swap records in one log (type).
GET /accounts/{id}/trades GET /trades?trader=
GET /accounts/{id}/offers GET /order?owner= or GET /account/:address /account/:address returns every live order unpaginated, plus the backing per token
GET /offers?seller= GET /order?owner=
GET /offers/{id} GET /order/:id 404 once the order is filled, removed or expired
No equivalent GET /order-history Archived orders: FILLED, CANCELED, EXPIRED
No equivalent GET /ticker/24h 24h open, high, low, last, volumes and trade count per market
No equivalent GET /markets, GET /contract Markets with live orders, contract state and the markets opened on-chain
Streaming with server-sent events WebSocket wss://<host>/ws Channels trades, depth, candles, ticker, account, contract. Snapshot first, then changes. See WebSocket API.
POST /transactions Stellar RPC AXIS calls are smart contract transactions: simulate, sign and send through RPC. The JS client does it for you.

Full parameter lists are in REST API.

Data format differences#

Assets. Responses identify assets by contract address (C...): the Stellar Asset Contract of each Classic asset. Compute it with new Asset(code, issuer).contractId(networkPassphrase). /quote, /depth and /candles also accept XLM, native, CODE:ISSUER and CODE-ISSUER on input. /order, /trades and /order-history filter by contract address. Markets use the contract's canonical pair order, which compares the XDR bytes of the addresses, not their strings. Use canonicalPair from the JS client instead of sorting strings.

Amounts. Order, trade, quote and backing amounts are integer base units as strings: "1000000000" is 100 XLM, where Horizon writes "100.0000000". /depth, /candles and /ticker/24h are display data and use decimal strings.

Prices. Horizon uses an n/d rational (price_r) plus a decimal string. AXIS stores an 18-decimal integer: buying base units per selling base unit, times 10^18. A price of 0.25 is 250000000000000000. The valid range is 1 to 10^36. Order JSON adds rprice, an approximate number for display. See Prices and rounding.

Orientation. Orders are stored sell-equivalent. A buy order is stored as an order that sells the asset it pays with, at the inverted price. A ManageBuyOffer port must convert prices back for display.

IDs. Order IDs are 128-bit integers written as decimal strings. They are hashes, not counters, so never sort by ID. Trades and swaps have no on-chain ID: the AXIS API derives one from the ledger, transaction and event position.

Sorting and cursors. The paged list endpoints have no order parameter: /order returns oldest first, /trades and /order-history newest first. To page, pass the cursor field of the last row you received. Rows up to and including it are skipped. limit caps differ per endpoint: 200 for /order, 500 for /trades and /order-history, 1,000 for /markets.

No HAL. Responses are plain JSON arrays or objects, without _links or _embedded.records. Errors are {"error": "...", "status": code}, and unknown paths return a plain-text 404.

Converting values#

All Classic assets have 7 decimals, so prices between them carry over without a decimal adjustment:

import {PRICE_SCALE} from '@axis-markets/client'

// Horizon price_r {n, d} of a ManageSellOffer to an AXIS sell price
const toAxisPrice = ({n, d}) => BigInt(n) * PRICE_SCALE / BigInt(d)

// Horizon decimal amount string to base units of a 7-decimal asset
function toBaseUnits(value) {
    const [whole, fraction = ''] = value.split('.')
    return BigInt(whole + fraction.padEnd(7, '0').slice(0, 7))
}

toAxisPrice({n: 1, d: 4}) // 250000000000000000n, a price of 0.25
toBaseUnits('100.5') // 1005000000n

toAxisPrice rounds down, which for a sell order means a minimum price that is at most 10^-18 lower than the rational.

Behavior to handle#

  • Rent per order. Creating an order pays about 0.041 XLM of ledger rent, not refunded on removal, and an order nobody updates archives after about 120 days. Requote with update instead of cancel and recreate. See Order lifecycle and rent.
  • Allowances. Makers and takers grant the contract an absolute, expiring allowance per token they sell, and one allowance backs all orders selling that token. Build approval UX: see Wallet integration.
  • Effective depth. An order is fillable only up to the maker's min(balance, allowance). The AXIS API counts only backed amounts. Show the backed field next to each order.
  • 20 fills per transaction. One trade fills at most 20 orders from distinct makers, and one swap at most 20 across its hops. Large orders sweeping many levels may need several transactions. See Resource limits and costs.
  • No Classic liquidity in AXIS routes. A path payment and an AXIS swap see different books. If you show both, quote both and compare.
  • Takers choose the orders. A trade without listed orders matches nothing: a Limit trade then simply creates an order, even when it crosses the book. AxisAccount.sell and buy look up the crossing orders for you.
  • Self-trades are allowed. The Classic DEX rejects an offer that would cross an offer of the same account. AXIS lets a trade fill the trader's own orders by design, so leave them out of the list when that is not intended.
  • Skips instead of failures. A listed order whose maker cannot settle is skipped with a skip event, and the rest of the trade executes. See Failure modes.
  • Markets and minimum size. Every limit order needs a market opened on-chain (721), a recent oracle price (722) and a minimum value in USD (720), checked on its whole amount even when it fills at once. Swaps and market orders need none of these.
  • Fees from simulation. Network fees depend on the resources of each call. Read them from the simulated transaction.

Migration checklist#

  1. Map every Classic asset you support to its contract address with Asset.contractId. Keep your trustline handling: it still applies.
  2. Replace /order_book with /depth or the depth WebSocket channel. Both count only backed amounts.
  3. Replace /paths/strict-send and /paths/strict-receive with /quote. Build swap calls from the quoted hops and set the bounds from the user's slippage.
  4. Replace ManageSellOffer and ManageBuyOffer with trade (AxisAccount.sell and buy), offer updates with update and deletions with update amount 0.
  5. Add allowance handling: absolute amounts, expiry, renewal before expiry, and the sum of open orders per token.
  6. Show backing per order and react to orders that become unbacked.
  7. Move history and charts to /trades, /order-history, /candles and /ticker/24h.
  8. Replace server-sent event streams with WebSocket channel subscriptions.
  9. Convert amounts to base units and prices to 18-decimal integers at the boundary of your app.
  10. Read fees from simulation and map contract error codes to messages. See Errors.
  11. Requote your orders with update and keep long-lived orders updated so they never archive.
  12. Run the whole flow on Testnet first. See Networks and deployments.