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
updateinstead 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 thebackedfield next to each order. - 20 fills per transaction. One
tradefills at most 20 orders from distinct makers, and oneswapat 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
tradewithout listed orders matches nothing: aLimittrade then simply creates an order, even when it crosses the book.AxisAccount.sellandbuylook 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
skipevent, 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#
- Map every Classic asset you support to its contract address with
Asset.contractId. Keep your trustline handling: it still applies. - Replace
/order_bookwith/depthor thedepthWebSocket channel. Both count only backed amounts. - Replace
/paths/strict-sendand/paths/strict-receivewith/quote. Buildswapcalls from the quoted hops and set the bounds from the user's slippage. - Replace
ManageSellOfferandManageBuyOfferwithtrade(AxisAccount.sellandbuy), offer updates withupdateand deletions withupdateamount 0. - Add allowance handling: absolute amounts, expiry, renewal before expiry, and the sum of open orders per token.
- Show backing per order and react to orders that become unbacked.
- Move history and charts to
/trades,/order-history,/candlesand/ticker/24h. - Replace server-sent event streams with WebSocket channel subscriptions.
- Convert amounts to base units and prices to 18-decimal integers at the boundary of your app.
- Read fees from simulation and map contract error codes to messages. See Errors.
- Requote your orders with
updateand keep long-lived orders updated so they never archive. - Run the whole flow on Testnet first. See Networks and deployments.