# Whitepaper

*Version 1.1 · V3 LOM live on Songbird + Coston2 · Algebra LOM (SparkDEX V4) audit-passed, Flare mainnet pending*

## Abstract

LimitOrderManager (LOM) is a non-custodial smart-contract system that turns a single-tick concentrated-liquidity position into a limit order. A fill occurs as a side-effect of ordinary swap activity against the underlying pool — no off-chain infrastructure is required to detect or trigger one. Anyone can close a filled order and collect a share of the LP fees generated during the fill, compensating them for the gas cost of execution. Order owners pay no protocol fee. The only cost to place an order is a small native-wei anti-spam tip sized to cover the executor's gas in the worst case, which is transferred to the executor on fill and refunded on cancel.

The system ships as TWO sibling contracts that share \~85% of their code: `LimitOrderManager` for Uniswap V3-compatible DEXes (Enosys V3, SparkDEX V3.1) and `LimitOrderManagerAlgebra` for Algebra V1.9+ DEXes with plugins (SparkDEX V4). The split is interface-driven — Algebra has no fee tiers, uses `globalState()` instead of `slot0()`, and exposes `MintParams.deployer` instead of `MintParams.fee`. Treating these as sibling contracts (rather than one polymorphic contract with adapter dispatch) keeps gas low and audit surface tight. Frontend dispatch is transparent: users pick a pool from any registered DEX and the FE routes to the correct contract.

**Status (2026-04-27)**: Both contracts are LIVE on Flare mainnet under a Ledger hardware-wallet owner (`0x98302d47…`). The V3 LOM is at `0x8F47C678…` (registered: Enosys V3, SparkDEX V3.1) and the Algebra LOM is at `0x720994B7…` (registered: SparkDEX V4). The V3 LOM is also live on Songbird (canary, original V1 contract under a hot EOA) and Coston2 (testnet). The 2026-04 external audit absorbed via in-place commit `8a0a4d6` covered the M-01/M-02 fillThreshold fee-bypass plus several lows; a paid third-party audit is planned post-launch.

## 1. The status quo

Limit orders on permissionless DEXes are, on Ethereum-family chains, almost always solved by off-chain infrastructure:

* **1inch Limit Order Protocol.** Users sign an EIP-712 order off-chain and broadcast it to a private relayer network ("resolvers"). Resolvers monitor prices and submit fill transactions when profitable. The user does not pay gas, but the resolver does — and resolvers recoup their cost through a spread baked into the fill price. A resolver can choose not to fill an order; if no resolver finds it profitable, the order simply doesn't execute.
* **Gelato Network.** A keeper network running bots on behalf of protocols. Users deposit gas upfront or pay per-execution, and Gelato's bots watch oracles / on-chain state for trigger conditions. The protocol must trust Gelato to keep its bots online; if Gelato goes down, no orders execute.
* **CoW Protocol / UniswapX.** Batch auction systems where a private set of "solvers" competes to fill user intents. Users get MEV-resistance via batching and settlement at a single clearing price, but only solvers on the protocol's allowlist can submit solutions.
* **On-chain oracle triggers.** Some ecosystems use Chainlink Automation or similar network-of-nodes schemes. These charge a per-trigger fee and depend on an external network of off-chain nodes being financially incentivised to run the keeper task.

These share three structural properties:

1. **Someone has to run the keeper.** Whether it is 1inch's resolver network, a Gelato bot, a UniswapX solver, or a Chainlink node, the monitoring-and-filling logic lives off-chain in a process that a specific party must operate and pay for.
2. **The user pays for the keeper's work.** Either as a spread baked into the fill price, a protocol fee charged at fill time, or a gas deposit consumed by the keeper. The user's economic outcome is always strictly worse than the reference market price by the keeper's margin.
3. **Failure modes are opaque.** If an order does not fill, the user generally cannot tell whether it is because the price was not hit, because the keeper network was down, because their order was deprioritised against more profitable ones, or because the protocol silently deranked their submission. There is no recourse short of cancelling and re-submitting elsewhere.

## 2. A structurally different model

LimitOrderManager sidesteps all three constraints by observing that **a Uniswap-V3-style concentrated liquidity position at a single-tick range already behaves like a limit order.**

When a liquidity provider mints a position at ticks `[T, T+spacing]` with only token0 deposited, the position earns LP fees on any swap that crosses `T`. When the pool's current tick moves beyond `T+spacing`, 100% of the position's liquidity has been converted from token0 to token1 — i.e., the depositor's token0 was swapped into token1 at prices between tick `T` and tick `T+spacing`. From the depositor's perspective this is a fill: a known amount of one asset has been exchanged for the other at a known minimum price. All that remains is to close the position — burn the NFT, collect the converted principal and the LP fees — which any account can do.

LOM productises this:

1. **`createOrder(pool, zeroForOne, targetTick, inputAmount)`.** Mints a single-tick position at the snapped target. The user's token0 (or token1) becomes part of the pool's liquidity. No off-chain infrastructure is aware of this order — from the DEX's perspective it is indistinguishable from any other concentrated-liquidity LP.
2. **The fill happens mechanically.** Whenever a counterparty swaps through the tick range, part of the LP fee from that swap accrues to LOM's NFT. When the tick has fully crossed, the position's liquidity is entirely in the buy-side token.
3. **`queueOrder(id)` + `executeBatch(ids)`.** Anyone — the order owner, an uninvolved EOA, an autonomous bot, another LOM user batching multiple orders through a single tx — can close a filled position. This burns the NFT, transfers the converted principal to the order owner, and splits the accumulated LP fees between the caller ("executor") and the configured treasuries.

The economic insight: **the LP fee that compensates the executor is paid by the swapper who filled the order**, not by the order owner. That swapper would have paid the LP fee regardless — it is the ambient fee on any swap through any concentrated-liquidity pool. LOM does not introduce a new fee; it redirects a slice of an existing one to whoever did the gas-burning work of closing the position.

From the order owner's perspective, this is a **zero-fee limit order**. They receive the full converted principal at or near the target price, minus only the small native-wei anti-spam tip posted upfront (typically a fraction of a cent at normal basefees), and they receive any FTSO delegation rewards accrued to WNAT the position held while open. There is no protocol cut from their proceeds. If a would-be executor does not find the order profitable to close, the order owner can close it themselves — paying only their own gas and collecting the full executor share as compensation.

This model generalises to any V3-interface-compatible DEX. The same V3 contract, unchanged, works against Enosys V3 on Flare, any Uniswap V3 fork, SparkDEX V3.1, or any future V3-compatible deployment on an EVM chain. For Algebra-family DEXes (SparkDEX V4) the same single-tick mechanic applies — only the on-chain interfaces differ — and a sibling `LimitOrderManagerAlgebra` contract carries the same logic translated to Algebra's `poolByPair`/`globalState`/`MintParams.deployer` shape. Both contracts are deployed independently on each chain; the frontend exposes them through one DEX picker so users see one product covering all supported DEXes.

## 3. Trust surface

LOM is not an unowned, immutable system, and a whitepaper that claimed otherwise would be misrepresenting it. The honest breakdown:

**Immutable at deploy:**

* The 25% minimum executor fee floor (`MIN_EXECUTOR_BPS`), guaranteeing the executor always gets paid enough to cover gas — the owner cannot dial this down.
* The upper bounds on the anti-spam gate (`MAX_GAS_ESTIMATE`, `MAX_COVERAGE_MULTIPLE`, `MAX_MIN_BASEFEE_WEI`). The owner can tune within these bounds but cannot set them to values that would brick order creation.
* The 48-hour timelock on reward-redistributor rotations.
* All mechanics of fill detection, order execution, and principal / reward payout.

**Owner-configurable (bounded):**

* The bps split between `executor / treasury1 / treasury2`. Must sum to 10,000 and `executor ≥ 2500`.
* Treasury addresses.
* The anti-spam gate parameters (gas estimate, coverage multiple, basefee floor), within immutable caps.
* Pause state. Pause blocks only `createOrder`; existing orders can always be cancelled, executed, and rewards can always be claimed.
* The set of registered DEXes and their reward redistributors.

**Always user-sovereign (owner cannot touch):**

* Principal held in a user's open order. Cancellation is always available; it routes the underlying token back to the order owner directly.
* Accumulated LP fees at execution — the owner-configurable split operates only within the fee slice, never on principal.
* FTSO delegation rewards routed to the order owner via try/catch-guarded external calls.

In other words: the owner can adjust the economics of new orders and who receives the treasury share, but cannot confiscate, freeze, or redirect funds from an existing order. If the contract is paused, cancellation still works. If the owner's EOA is compromised, an attacker cannot drain user funds; they can only grief new-order economics (within the 25% executor floor) or disable registered DEXes.

This is a weaker property than "fully trustless and immutable" but stronger than every comparable keeper-based system. Ownership is planned to transition from the deployer EOA to a Safe multisig or Ledger-only wallet before Flare mainnet, further reducing single-key risk. Ownership transfer is protected by OpenZeppelin's Ownable2Step — the new owner must explicitly call `acceptOwnership`, defending against misconfigured address entries.

## 4. Architecture

### 4.1 Single-tick positions as orders

The core storage unit is an `Order` struct keyed by a monotonic `orderId`. It packs:

* `owner` — the address that receives the principal on execution.
* `dexId` — which registered DEX this order lives on.
* `pool`, `token0`, `token1`, `fee`, `targetTick`, `zeroForOne` — the position's pool coordinates.
* `liquidity`, `tokenId` — the underlying V3 NFT.
* `inputAmount` — the original deposit size.
* `gasTip` — native wei posted by the user for the executor, or zero.
* `active` — set to false after execution or cancellation.

The NFT is held by the LimitOrderManager contract. Only LOM can call `decreaseLiquidity`, `collect`, or `burn` against it, and only LOM can claim the NFT's accumulated FTSO rewards from the redistributor.

### 4.2 Fill detection

A position is considered "filled" when (a) the pool's current tick is at or beyond the far side of the order's tick range, and (b) the position's `liquidity` is entirely in the buy-side token. Both are checked in the `isFilled(id)` view. The check reads pool `slot0` once and computes a pure derivation from the stored order data — \~50k gas worst-case. Frontends and bots can poll `isFilled` across the full outstanding order set without meaningful RPC cost.

### 4.3 Queue + execute separation

`queueOrder(id)` writes to the in-memory execution queue (\~150k gas, permissionless) and is the cheap batchable step. `executeBatch(ids)` is the gas-heavy close operation (\~2M–12M gas per order depending on FTSO accrual depth). Separating them lets the cheap step be batched up to 50 orders per transaction via Multicall3, while the expensive step is bounded by block gas.

`executeBatch` is capped at `MAX_BATCH_SIZE = 50` on-chain. The practical per-tx ceiling is set by the target chain's block gas limit: on Flare and Songbird (\~15M), a single order with a deep FTSO accrual consumes enough gas that only one can fit per tx; on DEXes without FTSO integration, 8+ orders fit comfortably.

### 4.4 Multi-DEX registry

DEXes are not hard-coded into the contract. `registerDex(...)` is an owner function that appends a DEX to the registry and returns a numeric `dexId`. Subsequent `createOrder` calls auto-discover which DEX a pool belongs to by querying each registered factory; the first non-zero match wins (CREATE2 deployment of pools guarantees uniqueness across factories).

The two LOM contracts have slightly different `registerDex` signatures matching their respective DEX families:

* **V3 LOM**: `registerDex(positionManager, factory, rewardRedistributor)` — the third arg is `address(0)` for DEXes without an FTSO redistributor (e.g., SparkDEX V3.1). Pool discovery uses `factory.getPool(token0, token1, fee)`.
* **Algebra LOM**: `registerDex(positionManager, factory)` — no reward redistributor (Algebra LOM has no reward path). Pool discovery uses `factory.poolByPair(token0, token1)` (single pool per pair, no fee tier). An additional `factory.computePoolAddress(t0, t1) == pool` check at order creation guards against silent misroute on hypothetical custom-deployer Algebra DEXes.

### 4.5 Algebra-specific behaviours

The Algebra LOM picks up two on-chain quirks the V3 LOM doesn't have to deal with:

* **Dynamic fees.** Algebra pools' fees are set per-block by a plugin (typically a volatility-aware fee impl). `Order.fee` snapshots `pool.fee()` at create time; the actual fee charged at fill may differ. The contract uses the snapshot only for the executor-incentive estimate in `_validateIncentive` — accepted as approximation per the gate's "spam filter, not precision tool" framing.
* **Community fee.** Algebra pools take a `communityFee` cut (in 1/1000) off LP fees BEFORE distributing them to LP positions. SparkDEX V4 WFLR/USDT0 has `communityFee = 250` (25%). The Algebra LOM reads `globalState().communityFee` live in `_validateIncentive` and applies a `(1000 − cf)/1000` multiplier — ensures the spam gate correctly sizes the executor's actual share rather than overestimating it from gross-fee math.

### 4.6 Fee split and anti-spam gate

When an order is filled and closed, the accumulated LP fees are divided three ways:

```
executor_fee  = fee × executorBps   / 10_000
treasury2_fee = fee × treasury2Bps  / 10_000
treasury1_fee = fee − executor_fee − treasury2_fee   (dust lands here)
```

`executorBps` is floored at 2500 (25%) and currently set to 4000 (40%) on Songbird. Treasury transfers are wrapped in `try/catch` — if a treasury address is blacklisted on a specific token, that share redirects to the order owner and execution continues. Principal and FTSO rewards always reach the user.

The anti-spam gate on `createOrder` enforces:

```
executorFeeInNative + msg.value ≥ effectiveBasefee × gasEstimate × coverageMultiple
```

`executorFeeInNative` is the executor's expected LP-fee share, priced into native via a spot lookup. (Manipulation-resistant pricing is not required here because this is an anti-spam floor sizing a tip requirement, not a fund-moving calculation.) Users whose order size is too small to self-fund the executor's gas can top up the gap as a native-wei tip on `msg.value`, which is transferred to the executor on fill.

### 4.7 FTSO delegation rewards (V3 LOM, Flare-family chains only)

On Flare, Songbird, and Coston2, WNAT held inside an Enosys V3 position accrues FTSO delegation rewards and FlareDrops, claimable via the DEX's `rewardRedistributor` contract. Because only the NFT owner can claim and LOM holds the NFT, LOM is the only party that can harvest — but the redistributor accepts a `recipient` argument, letting LOM route the claimed WNAT directly to the order owner. Claims happen automatically inside `executeBatch` and `cancelOrder` (before burn, since the redistributor rejects calls after the NFT is burned), and can be triggered standalone via `claimDelegationRewards(orderId)` at any time.

## 5. Risks and mitigations

* **Blacklistable tokens (USDT-family).** If the token issuer blacklists LOM, orders involving that token cannot be closed or refunded. There is deliberately no admin rescue function — adding one would be a worse vulnerability than the blacklist exposure itself. Mitigation is at the user layer: users who consider a specific token issuer hostile should avoid placing orders with that token.
* **Redistributor compromise.** The 48-hour timelock on `proposeDexRewardRedistributor` → `acceptDexRewardRedistributor` gives users advance notice of any rotation. Rewards accrued under a previous redistributor remain accessible via `claimDelegationRewardsVia(id, oldRedistributor)`, which reads from a monotonically-growing allowlist populated at every rotation — an emergency recovery path if a user's order had pending rewards under a now-retired redistributor.
* **Degenerate pools.** The frontend detects pools with zero liquidity or pinned price (|tick| ≥ 800,000) and blocks order creation against them. The contract itself does not gate on pool-state sanity, but a fill at a degenerate tick is obviously unprofitable and the economic incentive for an attacker is negligible.
* **FTSO reward-claim gas overflow.** A long-held order with many accrued reward epochs can exceed the 10M gas cap on the inner claim call during `executeBatch` (V3 LOM only — Algebra LOM has no reward path). The cap was bumped from 7M before the Flare deploy after one real Enosys V3 NFT was observed consuming 7.67M. The outer execution still succeeds (the claim is try/catch-wrapped and its failure is logged); orphaned rewards can then be claimed separately via `claimDelegationRewardsFull(id)` with user-chosen gas.
* **Order owner racing the queuer.** Between a position becoming filled and an executor calling `queueOrder`, the order owner can directly invoke `cancelOrder`, which — by contract design — refunds the principal plus 100% of the LP fees accumulated so far, bypassing the executor/treasury split. The frontend hides this path once `isFilled` is true; in practice the race window closes within one block if any executor is watching the pool.

## 6. Future work

* **Third-party security audit** followed by Flare mainnet deployment of both LOMs under a multisig or hardware-wallet owner.
* **Public executor bot reference implementation** ([`docs/developers/executor-bot-guide.md`](https://thanasdefitools.gitbook.io/thanasdefitools-docs/for-developers/executor-bot-guide)). The more independent executors watch the queue, the stronger the liveness guarantee. LOM's execution path is already permissionless at the contract layer; what is missing is the public codebase to lower the operational barrier. Bots need to scan both LOMs (V3 + Algebra) and decide which orders to queue/execute based on profitability.
* **Algebra LOM enhancements** — the current Algebra LOM uses `Order.fee` as a static snapshot of the dynamic fee at create time. A future revision could derive a TWAP-style `fee` from a rolling window of recent values, tightening the executor-incentive estimate for long-held orders during fee regime changes. Acceptable approximation today; refinement worth considering after observing real Flare mainnet usage.
* **SwapForm Algebra adapter** — the Limit Order tab routes seamlessly to either LOM via the DEX picker, but the standalone Swap tab is V3-only today (uses `QuoterV2` + V3 `SwapRouter`). Adding Algebra router + quoter dispatch is straightforward (the ABIs are already exported in `frontend/lib/contracts.ts`) and would let users swap on SparkDEX V4 directly through the same UI.
* **Optional TWAP-based anti-spam gate** — the current spot-price pricing of `executorFeeInNative` is adequate for its purpose (anti-spam, not fund movement), but a short-window TWAP would further reduce any incentive for a price-manipulation attempt to reduce required tips on new orders.
* **Uniswap V4 sibling** — if a true V4 DEX (singleton PoolManager, hooks, batched `modifyLiquidities` ABI) lands on Flare or any target chain, that's a third sibling contract. The architecture is fundamentally different from both V3 and Algebra V1.9+, but the same single-tick limit-order primitive applies.

## 7. Conclusion

By using single-tick concentrated liquidity as the underlying primitive, LimitOrderManager delivers limit orders without an off-chain keeper network, without a user-facing protocol fee, and without requiring users to trust a private resolver set. Execution is permissionless, fills are structurally guaranteed by DEX mechanics, and every function of the system is a direct on-chain call. The tradeoff — a bounded admin surface for retuning the fee split and registering new DEXes — is made explicit rather than disguised.

For users who want limit orders on Flare-family chains without the overhead of existing keeper models, LimitOrderManager is a strictly better deal: same outcome, no fees, and a failure mode that any on-chain observer can diagnose directly.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://thanasdefitools.gitbook.io/thanasdefitools-docs/whitepaper.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
