DevelopersSmart contract
Order lifecycle and rent
How an order moves from creation to removal, which event marks each step, and how long its ledger entry and the contract itself stay alive.
States and transitions#
| Transition | Caused by | Event | Storage |
|---|---|---|---|
| Created | A Limit trade stores its remainder |
new with ID, owner, price, amount and expiration |
Entry written |
| Partially filled | A fill leaves an amount that is not dust | trade with left > 0 |
Entry rewritten |
| Filled | A fill takes the whole order, or leaves dust | trade with left = 0 |
Entry deleted |
| Updated | update with a non-zero amount |
mod with the new price, amount and expiration |
Entry rewritten, TTL extended |
| Removed | update with amount 0 |
mod with amount 0, price and expiration unchanged |
Entry deleted |
| Expired | The ledger time reaches expires |
None | Entry kept, reads as gone |
| Revived | update of an expired order with a non-zero amount |
mod |
Entry rewritten, TTL extended |
| Skipped | A listed fill its maker cannot settle | skip with the order ID |
Unchanged |
| Archived | The entry's TTL runs out | None | Archived, a restore is needed to touch it |
A fill emits only the trade event: its left field is the amount stored after the fill, so an indexer updates the order from the trade alone. When an order serves as the taker order of a crossfill, its own fill is reported the same way, with the caller as taker. Event layouts are in Events.
Expiration#
expires is a UNIX timestamp in seconds, and 0 means the order never expires. An order is expired once expires <= now, where now is the ledger close time.
- A
Limittrade sets the expiration of the remainder it stores, andupdatechanges it (0 lifts it). Both reject a value that is not in the future withInvalidExpiration(707).FillandFillOrKilltrades ignore the argument. - The check runs at execution time. An expiration only a few seconds ahead can already be in the past when the transaction is included, so leave a margin.
- From the moment it expires, the order reads as gone everywhere except in
update. Theorderview returnsNone, matching skips it without an event (intrade,swapand as acrossfillmaker),crossfillwith it as the taker order fails withOrderNotFound(710), and its ID is free for a new order. - Expiry emits no event and writes nothing. The entry stays until its owner removes or revives it, a new order overwrites it, or it archives. Indexers expire orders on their own clock, from the
expiresof the latestnewormod, and keep the record because the order can still be revived.
Reviving an expired order#
Only the owner can act on an expired order, and only through update:
- An amount of 0 removes it and emits
modwith amount 0. This works while the contract is frozen too. - A non-zero amount revives it under the same ID with the new amount, price and expiration. The new expiration must be 0 or in the future, and every other
updatecheck applies: backing, minimum order value, the dust rule. The call emitsmodand extends the entry's TTL.
A revived order fills again like any live order. Until then, an expired order is never filled, crossed or returned by the order view, and no update can leave an order in the expired state.
TTL policy#
The contract sets every lifetime (contract, markets, orders, cached prices) in days or hours and converts it to ledgers with Config.ledger_time, the expected ledger close time, in src/ttl.rs. It is 5 seconds at deployment, that is 17,280 ledgers per day. When the network changes its ledger close time, the safety admin updates it with set_ledger_time (1 to 20 seconds), so entries keep living for the intended time.
| Entry | Initial lifetime | Extended by | Extension |
|---|---|---|---|
| Order | Network minimum for a new entry: 2,073,600 ledgers (about 120 days) on mainnet, 120,960 ledgers (7 days) on Testnet | update that changes or revives it, a new order written over an expired entry |
To its expiration plus 1 day, capped at 120 days, or 120 days without expiration. Never shortened |
| Order | Fills, removals, reads | Never | |
| Market | About 120 days: the network minimum on mainnet, and extended to 120 days when the market is opened where the minimum is shorter | Every read: requote, subsidize, the market view |
To 120 days once less than 30 days are left |
| Market | Every order written to it: Limit trades, update that changes an order |
To 121 days once less than 120 days are left, so the market outlives every order on it | |
| Price cache | 3 days | Every write by requote or subsidize |
To 3 days |
| Contract instance and code | Set at deployment | Every state-changing call | To 3 days once less than 3 days are left |
| Contract instance and code | An ExtendFootprintTTL operation, sent by a keeper |
To the horizon the keeper picks, capped by the network maximum |
An order that nobody updates archives when the lifetime granted at its creation or at its last update runs out, even when it never expires: the network minimum after creation (about 120 days on mainnet, but only 7 days on Testnet), and 120 days or its expiration plus 1 day after an update. A maker who reprices with update keeps the order alive at each update, paying rent only for the time since the previous extension. The market view extends the market entry only when it runs inside a submitted transaction. Reading it through simulation changes nothing.
Archival and restore#
A persistent entry whose TTL runs out is archived, not deleted. Since Protocol 23, a transaction restores archived entries automatically when its footprint marks them for restore:
- Simulation finds the archived entries a transaction touches and marks them for restore in the transaction data. They are restored as part of the transaction, which pays a fresh rent chunk for each.
- An archived entry declared read-write but not marked for restore fails the transaction when it is applied.
- Whoever submits the transaction pays. A taker who lists an archived maker order pays for that order's restore, and an owner who removes or updates an archived order pays to restore it first.
The JS client keeps the restore marks from simulation, so its calls restore the archived orders, markets and token balances they touch, and the simulated fee includes the rent. Footprint completion declares listed orders the simulation did not reach without that mark, so an archived spare order fails the transaction. Routers should therefore not list orders whose lifetime has run out: about 120 days after the last creation or update on mainnet, and on Testnet 7 days after creation when the order was never updated, which the indexer can tell from the creation and update times.
An archived order is not canceled. Anyone willing to pay the restore can bring it back and fill it, as long as its maker still backs it. A maker who wants an order gone for good removes it, or revokes the allowance on the token it sells.
Contract lifetime#
The contract instance (configuration, oracle decimals, frozen flag) and its Wasm code each have a TTL. If they archived, every call would first have to restore them.
- Every state-changing call extends them only when fewer than 3 days are left, and then only to 3 days.
- Keepers extend both further with the standard
ExtendFootprintTTLoperation, with the instance and code entries in its read-only footprint. The contract has no entry point for it. Anyone can send the operation, for any horizon up to the network maximum, and it works while the contract is frozen.AxisContractClient.keepalive(days)in the JS client builds and sends it.
The rent of the code entry dominates, about 2.5 XLM per day of lifetime on Testnet in October 2026. As long as keepers extend the contract, traders pay no contract rent. Without a keeper, every state-changing call that finds less than 3 days left tops the contract up to 3 days and pays the rent of the time since the previous top-up. Running a keeper has the cost figures.
Rent figures#
A September 2026 mainnet snapshot, at the minimum rent rate of 1,000 stroops per KB:
| Item | Cost |
|---|---|
| New order entry (about 250 bytes for 120 days) | About 0.041 XLM, not refundable |
| Creating an order, execution fee included | About 0.042 XLM |
| Restoring an archived order | A fresh 120-day rent chunk, about the cost of a new entry, paid by whoever restores it |
| First token balance of a contract address (a smart wallet, or the AXIS contract itself for every asset bought through it) | About 0.037 XLM, once per address and token |
update extension |
Proportional to the time since the previous extension |
The rate rises up to 10 times as network state grows, so all rent figures can move. Rewriting an order in place with update pays no new chunk, which is why requoting with update costs a fraction of removing and recreating orders. Resource limits and costs has the full fee tables.