Developers
Architecture
AXIS splits a DEX into an on-chain contract that verifies and settles, off-chain services that track state and route, and clients that pick orders and sign.
Three layers#
On-chain: the AXIS contract. A Soroban contract that stores orders, checks every order a caller lists against the caller's price limit and the maker's backing, and settles each fill with transfer_from calls on the token contracts within the same call. It also opens markets, caches oracle prices and emits an event for every change. It never searches for liquidity, keeps no price index and holds no user funds between calls.
Off-chain: the indexer and the AXIS API. The open-source indexer reads contract events, rebuilds the orderbook in memory, tracks the balance and allowance behind every maker's orders and stores history. The hosted AXIS API embeds the indexer, feeds it from its own streaming Stellar RPC data source and adds quotes (routing), depth, candles, a 24h ticker and a WebSocket push API. Neither holds keys nor submits transactions for users.
Clients. Wallets, trading interfaces, bots, market makers and keepers. A client picks the orders to cross, from an AXIS API quote or from its own routing, builds the contract call, simulates it through Stellar RPC, has the user sign it and submits it.
Responsibilities#
| Component | Does | Does not |
|---|---|---|
| AXIS contract | Stores orders. Checks price, expiry, backing and receive capability of every listed order. Settles fills, multi-hop swaps and crossfills. Opens markets (subsidize), caches oracle prices (requote). Emits events. |
Search for liquidity, rank orders, hold funds between calls, upgrade itself, charge fees |
| Token contracts | Hold balances and allowances. Run approve, transfer_from and transfer. Report whether an account can receive a token (authorized). |
Know about orders |
| Reflector oracle | Quotes USD prices of the assets it lists. Sells feed access to the AXIS contract, paid by burning XRF. | Price fills. Oracle prices only gate market opening and the minimum order value. |
| Indexer | Rebuilds the book from events, computes effective depth per maker and asset, archives filled, canceled and expired orders and trades, records failed calls reported by its data source, serves a read-only HTTP API | Sign, submit or route |
| AXIS API | Serves the indexer routes plus /quote, /depth, /candles, /ticker/24h and WebSocket channels |
Hold keys or move funds |
| Clients | Choose orders, set price limits and swap bounds, simulate, sign and submit | Bypass the contract checks |
| Keepers | Call requote and subsidize, and extend the contract lifetime. Optionally crossfill crossed books. |
Need any special rights |
Data flow#
- Every state change in the contract emits an event:
new,mod,trade,skip,swap,refresh,freezeorconfig. See Events. - The indexer receives the events through its
DataSourceand updates the in-memory book, keeping the balance and allowance of the makers involved current. - The AXIS API serves the book, quotes and market data over REST and pushes changes over WebSocket.
- A client requests a quote. Every route lists, per hop, the order IDs to cross.
- The client builds a
tradeorswapcall with those IDs, simulates it, declares the entries of every listed order and maker in the transaction footprint (see Footprint completion), and the user signs. The signature covers the call and an optionalapprove, never amounts paid to individual makers. - The contract re-checks every listed order, skips the ones it cannot fill and settles the rest: the taker pays each maker directly, the makers deliver to the contract, and the contract forwards everything bought to the trader at the end of the call (see Settlement order). The new
tradeandskipevents feed step 1. - Keepers call
requoteandsubsidize, and extend the contract lifetime. Only these two calls reach the oracle.tradeandupdateread prices from the contract's own cache.
Trust boundaries#
Nothing off-chain can move funds. Every token transfer happens inside an AXIS contract call and rests on either the trader's signature on that call, or a standing allowance a maker granted to the contract, which the contract spends only to settle a fill of that maker's own order at the maker's price.
- The contract can spend a user's allowance only for the user's own orders, at their prices, and for the user's own signed trades and swaps. It cannot change or remove an order without the owner's signature, other than by filling it at the owner's price. No call leaves the contract holding more than it held before, and there is no upgrade function. See Settlement and allowances.
- The safety admin has exactly six calls:
freeze,delegate,set_oracle,set_floor,set_listing_min_daysandset_ledger_time. It cannot touch funds or orders, upgrade the code, list or delist markets or add fees. See Safety admin and invariants. - The oracle gates market opening and the minimum order value, and never sets a fill price. Stale or missing prices block new
Limitorders and order updates while the minimum order value is enabled. Fills, swaps, crossfills and removals keep working. See Markets and oracle. - The indexer and the AXIS API are read-only. They can be wrong, late or incomplete, which costs a client a worse route or skipped fills, never funds. The contract re-verifies every listed order against the limits the client signs.
- Clients and keepers. Clients hold the keys. Keepers need no special rights:
requoterequires no authorization at all, andsubsidizeonly the signature of the sponsor who pays. - Token contracts. Stellar Asset Contracts (SAC) wrap Classic assets, so trustlines and issuer authorization apply as usual, and an asset whose issuer requires authorization can be bought only once the issuer has authorized the AXIS contract itself (see Assets that require authorization). A custom SEP-41 token must implement the functions listed in Custom tokens. A malicious token can burn the resource budget of a transaction that touches it, but cannot reach funds in other tokens.
Design principles#
- Separation of concerns. The contract verifies and settles. Matching and routing happen off-chain, where any number of routers can compete.
- No custody. Orders are backed by the owner's balance and a standing allowance. Assets pass through the contract only within a call.
- Non-upgradeable, small settlement core. No function replaces the contract code, and only a few settlement paths move funds, which keeps the attack surface small. A future protocol version ships as a new deployment.
- Client-chosen IDs and declarable footprints. An order ID is a hash of the owner and a client nonce, known before simulation. There is no on-chain counter.
- Signatures independent of ledger state. The trader signs the root call and, optionally, an
approvewith an absolute amount, so a signed trade stays valid when listed orders change before inclusion. - Skip instead of fail. An order whose maker cannot settle is left unchanged and reported with a
skipevent. The rest of the trade goes through. - Permissionless. Anyone can trade, open a market, refresh prices, extend the contract lifetime or crossfill a crossed book.
- No stored caps. Every loop is bounded by a list the caller passes. Limits such as 20 fills per
tradeare client-side constants that follow the network settings. See Resource limits and costs. - No fees. There is no protocol fee and no fee switch.
Design rationale explains the reasoning behind each principle and the trade-offs that come with it.
Concurrency#
Since protocol 23, Stellar executes transactions that write no common ledger entry in parallel. A trade writes the orders it fills, the balances and allowances of the makers and the taker, and the contract's own balance of the asset it buys, but no counter or instance entry. Updates on disjoint orders therefore run in parallel, and so do trades that buy different assets from disjoint makers, while trades, swaps and crossfills that buy the same asset serialize on the contract's balance entry. Resource limits and costs lists the entries each call writes.
Self-hosting#
@axis-markets/indexer is an embeddable Node library, not a standalone service. You supply a DataSource that delivers the contract events and reads maker balances and allowances, and a HistoryStorage that persists orders, trades, swaps and the event cursor. The package ships the DataSource interface without an implementation and only an InMemoryHistoryStorage, so production deployments plug in a database. With apiPort set, the indexer serves the read routes of the AXIS API, and in-process code can listen to its events and read its in-memory book directly. See Indexer.
The AXIS API adds what the indexer does not do: routing (/quote), depth aggregation (/depth), candles (/candles), the 24h ticker (/ticker/24h) and the WebSocket push API. If you run only the indexer, you build these on top of it yourself.
The contract itself needs no server. Any client can call its trading and maintenance functions and read orders, markets and the configuration through any Stellar RPC endpoint. What a bare contract does not offer is a listing: there is no view that enumerates orders, so without an index you only know the order IDs you already have.