eip: 505 title: Native Liquidity Token Standard description: An ERC-20 extension embedding an AMM and limit order book directly in the token contract, enabling approval-free trading against the chain's native currency. author: MaximusDAO discussions-to: https://github.com/MaximusDAO/FlexDex status: Draft type: Standards Track category: ERC created: 2026-02-17 requires: 20
Abstract
This proposal defines Native Liquidity Tokens (NLTs)—ERC-20 tokens whose contracts natively embed a constant-product automated market maker (AMM) and an on-chain limit order book, both denominated in the chain's native currency (ETH, etc.). Because the exchange is the token, buying requires only a payable call with no prior approval, and selling requires only a standard transfer to the contract itself. The standard eliminates the approval→swap two-step, removes dependence on external DEX contracts, and guarantees that every NLT is liquid from the moment of deployment.
Motivation
The approval problem
The ERC-20 approve + transferFrom pattern is the dominant way tokens interact with DeFi. Every approval is an attack surface: unlimited approvals have led to hundreds of millions of dollars lost through approval-based exploits, phishing attacks, and malicious contract upgrades. Users are conditioned to accept this risk as a cost of participation.
A token that can be traded without ever granting a spending allowance to a third party eliminates this entire class of vulnerability.
The external liquidity problem
Today, a newly deployed ERC-20 is inert until someone pairs it on an external DEX and seeds liquidity. This creates a dependency chain: the token depends on a router, the router depends on a factory, the factory depends on a pair contract, and the pair contract depends on approval of both tokens. Every link is a trust assumption and a potential point of failure.
A token that carries its own exchange needs no external infrastructure. Liquidity exists from the first block.
The fragmented liquidity problem
When a token is listed across multiple DEXes, liquidity fragments. Arbitrageurs profit from the inefficiency, extracting value from holders. A single canonical exchange embedded in the token contract consolidates all liquidity in one place, producing tighter spreads and better execution for all participants.
The limit order gap
Constant-product AMMs offer infinite liquidity along a curve but provide no mechanism for price-specific orders. Users wanting to buy or sell at a target price must rely on external protocols, centralized services, or manual monitoring. Embedding a limit order book alongside the AMM gives users native price-targeted execution with hybrid routing that fills limit orders first and routes the remainder through the AMM.
Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Definitions
- Native currency: The chain's base denomination (ETH on Ethereum, etc.).
- Sell-side reserve (
reserveSellSide): The quantity of tokens held by the AMM. - Buy-side reserve (
reserveBuySide): The quantity of native currency held by the AMM. - Limit order: A resting order offering a fixed quantity of one asset in exchange for a desired quantity of the other.
- Floor order: A special protocol-owned limit buy order whose maker is
address(0), making it uncancellable. Tokens sold into a floor order are burned.
Interface
Every compliant contract MUST implement ERC-20 (IERC20) and MUST additionally implement the following interface:
1// SPDX-License-Identifier: CC0-1.0
2pragma solidity >=0.8.0;
3
4interface IERC505 /* is IERC20 */ {
5
6 // ───────────────────────── Structs ─────────────────────────
7
8 /// @notice A resting order in the on-chain order book.
9 struct LimitOrder {
10 address maker; // Creator (address(0) for floor orders)
11 bool isBuy; // true = offering native for tokens
12 uint256 offerAmount; // Amount offered (native for buys, tokens for sells)
13 uint256 desiredAmount; // Amount desired (tokens for buys, native for sells)
14 bool isActive; // Whether the order can still be filled
15 }
16
17 /// @notice Instruction to partially or fully fill a resting order.
18 struct LimitOrderFill {
19 uint256 orderId; // ID of the order to fill
20 uint256 fillAmount; // Amount to fill from this order
21 }
22
23 // ───────────────────────── Errors ──────────────────────────
24
25 error InsufficientLiquidity();
26 error InsufficientValue();
27 error LiquidityPoolNotActive();
28 error InvalidAmount();
29 error LessThanMinimum();
30 error TransferFailed();
31 error OrderDoesNotExist();
32 error OrderNotActive();
33 error NotOrderMaker();
34 error InvalidFillAmount();
35 error BadRatio();
36 error TooManyOrderFills();
37
38 // ───────────────────────── Events ──────────────────────────
39
40 /// @notice Emitted after every AMM swap.
41 event Swap(
42 address indexed user,
43 bool indexed isBuy,
44 uint256 amountIn,
45 uint256 amountOut,
46 uint256 fee,
47 uint256 newReserveSellSide,
48 uint256 newReserveBuySide
49 );
50
51 /// @notice Emitted when a limit order is created.
52 event LimitOrderPlaced(
53 uint256 indexed orderId,
54 address indexed maker,
55 bool indexed isBuy,
56 uint256 offerAmount,
57 uint256 desiredAmount
58 );
59
60 /// @notice Emitted when a limit order is (partially or fully) filled.
61 event LimitOrderFilled(
62 uint256 indexed orderId,
63 address indexed filler,
64 address indexed maker,
65 uint256 amountFilled,
66 uint256 remainingOffer,
67 uint256 remainingDesired,
68 bool orderCompleted
69 );
70
71 /// @notice Emitted when a limit order is cancelled by its maker.
72 event LimitOrderCancelled(
73 uint256 indexed orderId,
74 address indexed maker,
75 uint256 refundedAmount,
76 bool wasBuyOrder
77 );
78
79 /// @notice Emitted when a fill is skipped during combo execution.
80 event OrderSkipped(uint256 indexed orderId, string reason);
81
82 // ─────────────────── AMM View Functions ────────────────────
83
84 /// @notice Token reserve in the AMM.
85 function reserveSellSide() external view returns (uint256);
86
87 /// @notice Native currency reserve in the AMM.
88 function reserveBuySide() external view returns (uint256);
89
90 /// @notice Preview the output amount for a given swap direction and input.
91 /// @param isBuy true = native→token, false = token→native
92 /// @param amountIn Input amount
93 /// @return amountOut Tokens (if buying) or native currency (if selling)
94 function getSwapAmount(bool isBuy, uint256 amountIn)
95 external view returns (uint256 amountOut);
96
97 // ─────────────────── Trading Functions ─────────────────────
98
99 /// @notice Buy tokens with native currency.
100 /// @dev MUST accept native currency via `msg.value`.
101 /// If `limitOrderFills` is non-empty, matching sell-side limit orders
102 /// MUST be filled first at their stated prices; any remaining native
103 /// currency MUST be swapped through the AMM.
104 /// @param minAmountOut Minimum tokens to receive (slippage protection)
105 /// @param limitOrderFills Orders to fill before the AMM (may be empty)
106 function buy(
107 uint256 minAmountOut,
108 LimitOrderFill[] calldata limitOrderFills
109 ) external payable;
110
111 /// @notice Sell tokens for native currency.
112 /// @dev Tokens MUST be pulled from `msg.sender` via an internal transfer
113 /// (no prior approval to an external contract required).
114 /// If `limitOrderFills` is non-empty, matching buy-side limit orders
115 /// MUST be filled first; any remaining tokens MUST be swapped through
116 /// the AMM.
117 /// If a filled buy order has `maker == address(0)` (floor order),
118 /// the delivered tokens MUST be burned.
119 /// @param amountIn Tokens to sell
120 /// @param minAmountOut Minimum native currency to receive
121 /// @param limitOrderFills Orders to fill before the AMM (may be empty)
122 function sell(
123 uint256 amountIn,
124 uint256 minAmountOut,
125 LimitOrderFill[] calldata limitOrderFills
126 ) external;
127
128 // ─────────────── Limit Order Functions ─────────────────────
129
130 /// @notice Place a limit buy order (offer native currency for tokens).
131 /// @dev MUST revert with `BadRatio` if the requested price is better than
132 /// the current AMM spot price (limit orders rest *behind* the curve).
133 /// @param desiredAmount Tokens the maker wants to receive
134 /// @return orderId Unique order identifier
135 function limitBuy(uint256 desiredAmount)
136 external payable returns (uint256 orderId);
137
138 /// @notice Place a limit sell order (offer tokens for native currency).
139 /// @dev Tokens MUST be transferred into the contract upon placement.
140 /// MUST revert with `BadRatio` if the requested price is better than
141 /// the current AMM spot price.
142 /// @param offerAmount Tokens the maker is selling
143 /// @param desiredAmount Native currency the maker wants to receive
144 /// @return orderId Unique order identifier
145 function limitSell(uint256 offerAmount, uint256 desiredAmount)
146 external returns (uint256 orderId);
147
148 /// @notice Cancel an active limit order and reclaim escrowed assets.
149 /// @dev MUST revert if caller is not the order maker. Floor orders
150 /// (maker == address(0)) are inherently uncancellable.
151 /// @param orderId The order to cancel
152 function cancelLimitOrder(uint256 orderId) external;
153
154 // ─────────────── Order Book View Functions ─────────────────
155
156 /// @notice Read a limit order by ID.
157 function limitOrders(uint256 orderId)
158 external view returns (
159 address maker,
160 bool isBuy,
161 uint256 offerAmount,
162 uint256 desiredAmount,
163 bool isActive
164 );
165}AMM Specification
-
Formula. The AMM MUST use the constant-product invariant: the product of reserves after a swap (minus fees) MUST be greater than or equal to the product before the swap.
-
Fee. Implementations SHOULD charge a swap fee. The fee MUST be applied to the input amount before computing the output. The RECOMMENDED fee is 0.3% (computed as
amountIn / 333). -
Reserve tracking. The contract MUST maintain
reserveSellSide(token reserve) andreserveBuySide(native currency reserve) as public state variables. -
Output calculation. For a given input
amountInagainst reserves(reserveIn, reserveOut):
1amountInAfterFee = amountIn − fee(amountIn)
2amountOut = (reserveOut × amountInAfterFee) / (reserveIn + amountInAfterFee)Limit Order Specification
-
Placement. Limit orders MUST rest at prices behind the current AMM spot price (i.e., worse for the maker than an immediate AMM swap). This ensures limit orders represent resting liquidity rather than instant arbitrage.
-
Partial fills. Limit orders MUST support proportional partial fills. When a fill cannot be completed in full, the filled portion MUST be computed proportionally and the order's
offerAmountanddesiredAmountMUST be decremented accordingly. -
Fill-then-swap routing. When
limitOrderFillsis provided, the contract MUST attempt to fill each specified order sequentially. Any remaining input after all fills MUST be routed through the AMM. Orders that are inactive or of the wrong direction MUST be silently skipped (emittingOrderSkipped). -
Cancellation. Only the
makerof an order MAY cancel it. Cancellation MUST refund the full remainingofferAmount. -
Maximum fills. Implementations SHOULD impose a per-transaction limit on the number of orders that can be filled (RECOMMENDED: 50) to bound gas costs.
Floor Order Specification
-
Identity. A floor order is any limit buy order whose
makerisaddress(0). -
Uncancellable. Because
address(0)cannot callcancelLimitOrder, floor orders are permanent by construction. -
Token burning. When tokens are sold into a floor order, the contract MUST burn the delivered tokens rather than transferring them to the maker. This creates deflationary pressure and ensures the floor price ratio is maintained as the order is filled.
-
Floor price invariant. Because partial fills reduce both
offerAmountanddesiredAmountproportionally, the floor price (offerAmount / desiredAmountin native currency per token) remains constant throughout the order's lifetime.
Liquidity Initialization
Before trading can begin, the AMM reserves must be seeded. This standard defines the mechanism for initialization but is deliberately unopinionated about how the contract acquires its initial balances.
-
Precondition. The contract MUST hold a non-zero balance of both its own token and native currency before the AMM can be initialized.
-
Initialization. The contract MUST expose an internal or external mechanism that sets
reserveSellSideandreserveBuySideto the token and native currency balances designated for the pool, respectively. The initial spot price is determined by the ratio of these two values:
1initialPrice = reserveBuySide / reserveSellSide (native currency per token)-
One-time. Initialization MUST be callable at most once. After initialization,
buy(),sell(),limitBuy(), andlimitSell()become operational. Before initialization, these functions MUST revert. -
Seeding strategies. The standard imposes no requirements on how the contract arrives at its initial balances. Conforming implementations MAY use any strategy, including but not limited to:
- Direct funding: The deployer mints tokens to the contract and sends native currency in the constructor or a setup transaction, then calls an initialization function.
- Crowdfunded bootstrapping: A deposit phase collects native currency from participants, mints tokens proportionally, then initializes the pool with the collected funds and a designated token allocation (see the FlexDex reference implementation below).
- Migration: An existing token migrates liquidity from an external DEX into the NLT contract.
- Programmatic seeding: A factory contract mints tokens and forwards native currency in a single atomic transaction.
The only constraint is that by the time initialization executes, the contract MUST hold sufficient balances of both assets to establish the desired opening price.
-
Floor order at initialization (OPTIONAL). Implementations MAY create one or more floor orders during initialization using any native currency not allocated to the AMM reserves. This allows the initial seeding transaction to simultaneously establish both trading liquidity and a price floor.
Transfer Restrictions (OPTIONAL)
Implementations MAY restrict ERC-20 transfer and transferFrom during initialization or bootstrapping phases, provided that mint and burn operations remain functional. Once the liquidity pool is initialized, all standard ERC-20 transfer functions MUST operate normally.
Rationale
Why embed the exchange in the token?
The fundamental insight is that an ERC-20 token contract already holds all the state needed for an exchange: balances, total supply, and transfer logic. Adding reserve variables and swap math to the same contract eliminates cross-contract calls, approval requirements, and the entire class of vulnerabilities that arise from token↔exchange interactions.
Why native currency only?
Trading against the chain's native currency (rather than arbitrary ERC-20 pairs) enables the simplest possible UX: send ETH, receive tokens. It also eliminates the need for wrapped native tokens and the associated approval/deposit flows. For tokens that primarily need a way to bootstrap and maintain liquidity, a single native-currency pair covers the dominant use case.
Why constant-product?
The constant-product formula is the simplest AMM design that provides continuous liquidity across all prices. More complex curves (concentrated liquidity, stableswap, etc.) are not precluded by this standard—they are simply not required. Implementations MAY substitute alternative AMM formulas provided they satisfy the same interface.
Why a limit order book alongside the AMM?
AMMs provide passive, always-on liquidity but offer no mechanism for price-targeted orders. A complementary order book lets users express precise price preferences. The hybrid fill-then-swap routing gives takers the best of both worlds: specific-price fills where available, continuous AMM liquidity for the remainder.
Why must limit orders rest behind the curve?
If a limit order offered a better price than the AMM, it would be immediately arbitraged. Requiring limit orders to rest behind the spot price ensures they represent genuine resting liquidity that will be filled when the market moves to that level.
Why is liquidity initialization strategy-agnostic?
Different tokens have fundamentally different needs. A community token may want a public deposit phase for fair distribution. A protocol token may want the DAO treasury to seed liquidity directly. A migrating token may need to port existing external DEX liquidity into the NLT contract. Mandating a specific seeding mechanism would unnecessarily limit the standard's applicability. The only invariant that matters is: the contract holds tokens and native currency, and the ratio defines the opening price. Everything upstream of that moment is application-specific.
Why burn tokens on floor order fills?
Burning creates a deflationary feedback loop: as tokens are sold into the floor, supply decreases, increasing the value of remaining tokens. Without burning, floor-purchased tokens would accumulate in the contract with no owner, serving no economic purpose. Burning converts dead capital into value for remaining holders.
Backwards Compatibility
NLTs are fully ERC-20 compliant. All standard transfer, transferFrom, approve, balanceOf, and allowance functions behave identically to any ERC-20 token. Existing wallets, block explorers, and indexers will recognize NLTs as standard ERC-20 tokens without modification.
The additional trading interface (buy, sell, limitBuy, limitSell, cancelLimitOrder) extends rather than modifies the ERC-20 surface area. Contracts and applications that interact only with ERC-20 methods will function correctly. The approval-free trading path is additive—traditional approve + transferFrom workflows remain available for interoperability with existing DeFi infrastructure.
Reference Implementation
Minimal NLT
A minimal conforming implementation requires:
- An ERC-20 token with
buy()andsell()functions per this specification - A constant-product AMM with reserve tracking
- A limit order book with placement, fill, and cancellation logic
- Hybrid fill-then-swap routing
Extended Implementation: FlexDex (Crowdfunded Bootstrapping with Price Floor)
FlexDex extends the NLT standard with a crowdfunded liquidity bootstrapping mechanism—one of the seeding strategies described in the Liquidity Initialization section above. It demonstrates how the floor order primitive can be combined with a structured deposit phase to produce safe, fair token launches where every participant has a guaranteed exit. The extension introduces three phases:
Phase 1: Liquidity Bootstrapping (Launch)
During the launch phase, users deposit native currency directly to the token contract (via receive()), which mints tokens according to a two-component formula:
- Baseline allocation: Linear proportional minting (
deposit / maxDeposits × baselineTokensMax) - Fast bonus: A quadratically-decreasing bonus curve that rewards earlier depositors, computed by integrating a linearly-decreasing rate function over the deposit range
The launch phase is bounded by both a maximum duration and a maximum deposit cap. Token transfers are restricted during this phase (mint and burn remain functional) to prevent secondary market activity before pricing is established.
Phase 2: Liquidity Deployment (Transition)
After the launch phase concludes, anyone may trigger deployLiquidityPool(), which:
- Calculates the liquidity token allocation proportional to actual deposits received (if deposits fall short of the cap, fewer liquidity tokens are minted)
- Splits collected deposits between the AMM pool and a floor order based on the final mint rate
- Creates the floor order (order ID 0, maker
address(0)) with the remaining native currency, sized to buy back the entire depositor-minted supply - Mints liquidity tokens to the contract and initializes the AMM pair
- Enables transfers by setting the
liquidityDeployedflag
The result is a token that launches with both AMM liquidity and a permanent price floor from block one of trading.
Phase 3: Trading
Normal NLT trading commences. The floor order persists as a permanent backstop: any holder can sell into it at the floor price, and the received tokens are burned, maintaining the floor price ratio indefinitely. This creates bounded downside risk for all holders, with the asymmetric property that upside remains unlimited while maximum loss from any entry point is quantifiable.
Standardized Launch Parameters
FlexDex uses a factory pattern with configurable launch "standards"—predefined parameter sets controlling supply, depositor allocation percentage, fast bonus scalar, duration, and deposit cap. This enables protocol governance to define safe launch templates while preserving flexibility for different use cases.
1struct Standard {
2 uint256 supply; // Maximum token supply
3 uint256 depositorAllocation; // Basis points to depositors (0–10000)
4 uint256 fastBonusScalar; // Divisor for fast bonus pool size
5 uint256 duration; // Max launch duration in seconds
6 uint256 maxDeposits; // Max native currency deposits
7}Security Considerations
Reentrancy
NLT contracts send native currency to external addresses during sells, limit order fills, and cancellations. All state mutations MUST be completed before any external calls. Implementations SHOULD use reentrancy guards on all public trading functions. The fill-then-swap pattern in buy() and sell() SHOULD batch all native currency transfers and execute them after all order book and reserve state has been finalized.
Reserve manipulation
Because the AMM reserves are internal to the token contract, they cannot be manipulated by external flash loans against separate pool contracts. However, large swaps can still move the price significantly within a single block. Implementations SHOULD provide minAmountOut slippage protection on all swap functions.
Limit order griefing
Limit orders that are very small or placed at extreme prices could be used to grief takers who specify them in their limitOrderFills array. Implementations SHOULD skip invalid or unfillable orders gracefully (emitting OrderSkipped) rather than reverting the entire transaction.
Floor order solvency
The floor order's price ratio is maintained by proportional fill mechanics: each partial fill reduces both offerAmount and desiredAmount by the same ratio. Implementations MUST ensure that floor order fills are computed with the same proportional arithmetic as regular limit orders to preserve this invariant.
Front-running
NLT swap transactions are subject to the same front-running risks as any on-chain AMM. The minAmountOut parameter provides protection against sandwich attacks. Limit orders, once placed, execute at their stated price regardless of market manipulation, providing additional front-running resistance for patient traders.
Transfer restriction bypass
During any bootstrapping phase where transfers are restricted, implementations MUST ensure that the restriction applies at the ERC-20 _update (or _transfer) level and cannot be bypassed through transferFrom with pre-existing approvals.
Rounding and dust
Proportional fill arithmetic may leave dust amounts in limit orders (where offerAmount or desiredAmount rounds to zero). Implementations MUST deactivate orders when either side rounds to zero to prevent division-by-zero errors in subsequent fill attempts.
Copyright
Copyright and related rights waived via CC0.