CORE DEFI PRIMITIVES AND MECHANICS

Practical Steps to Build an Automated Market Maker Free of Oracles

5 min read
#DeFi #Smart Contracts #Liquidity #DEX #AMM
Practical Steps to Build an Automated Market Maker Free of Oracles

Why Go Oracle‑Free?

In many traditional AMM designs the pool’s price is tied to an external oracle. While oracles provide up‑to‑date market data, they introduce latency, trust assumptions, and potential attack vectors. Eliminating the oracle removes a single point of failure and keeps the AMM fully deterministic. This guide shows how to build such a system from the ground up, relying on on‑chain data and the pool’s own trading history to derive fair prices, as outlined in Crafting Decentralized Exchanges Without Dependence on Oracles.


Foundations of an Oracle‑Free AMM

Liquidity Pools as Price Curators

An AMM’s core is a set of reserves for two assets, A and B. The relative size of these reserves implicitly encodes a price: the ratio of reserve B to reserve A is the exchange rate. When users trade, they shift the reserves, which automatically updates the price. Thus the pool itself can serve as a continuous, tamper‑resistant price source, as described in The Secrets of Creating Stable, Oracle‑Free Liquidity Pools.

Time‑Weighted Averages

Raw reserve ratios fluctuate wildly on every trade. To smooth out volatility and reduce manipulation, we aggregate reserves over time to create a time‑weighted average price (TWAP). The TWAP is calculated by sampling the reserve ratio at regular intervals and averaging them. This approach is already used by on‑chain data feeds like Chainlink’s “Time‑Weighted Average” endpoints, but we can implement it entirely on the chain.

Flash Loan Abuse and Rebalancing

Without an oracle, the only information driving trades is the pool’s own state. This simplifies the model but also opens the door for sophisticated flash loan attacks that can temporarily distort prices. To counteract this, the AMM should implement mechanisms that limit the size of a single trade relative to the pool’s depth, and that periodically rebalance the pool using a separate automated market maker (e.g., a stable‑swap kernel) that has stricter constraints, following the approach in The Complete Framework for Oracle‑Free AMM Development.


Step 1 – Define the Token Pair and Initial Reserves

The first practical decision is choosing the assets to be paired. In most cases you want two assets that trade on the same exchange and have comparable liquidity.

  1. Token selection

    • Choose ERC‑20 tokens with proven trading volume.
    • Prefer tokens that have a native oracles‑free market on the same blockchain to avoid cross‑chain complexity.
  2. Reserve size

    • Decide on an initial liquidity amount for each token.
    • A larger initial liquidity reduces slippage for early trades and makes the pool more resistant to manipulation.
  3. Deployment of the Pool contract

    • Use a minimal, upgradable proxy pattern (e.g., UUPS) to allow future feature upgrades.
    • Store token addresses, reserves, and the TWAP buffer in a deterministic storage layout.
  4. Bootstrapping

    • The contract owner deposits the initial amounts via a provideLiquidity function that updates the reserves and records the timestamp.
    • Emit an event LiquidityBootstrapped that contains the initial ratio for external observers.

Step 2 – Implement the Core AMM Formula

A simple constant‑product formula (x · y = k) is the backbone of most AMMs. For an oracle‑free design, the formula must also account for fees and slippage protection.

function getOutputAmount(
    uint256 inputAmount,
    uint256 inputReserve,
    uint256 outputReserve
) internal pure returns (uint256 outputAmount) {
    uint256 feeMultiplier = 997;      // 0.3% fee
    uint256 inputAmountWithFee = inputAmount * feeMultiplier;
    uint256 numerator = inputAmountWithFee * outputReserve;
    uint256 denominator = inputReserve * 1000 + inputAmountWithFee;
    outputAmount = numerator / denominator;
}

Key points:


Step 3 – Build a Time‑Weighted Average Price (TWAP) Engine

To make the pool a reliable price source, the contract must record the reserve ratio at regular intervals.

3.1 Sampling

The contract should expose a sample() function that can be called by anyone (or a cron bot) at least once per block. The function records the current ratio and the timestamp.

function sample() external {
    uint256 ratio = (reserveB * 1e18) / reserveA;
    twapSamples.push(TWAPSample({ratio: ratio, timestamp: block.timestamp}));
}

3.2 Pruning and Averaging

When a user wants a quoted price, the contract fetches the last N samples and returns the average.

function getTWAP() external view returns (uint256 average) {
    uint256 sum = 0;
    uint256 count = twapSamples.length;
    require(count > 0, "No samples");
    for (uint256 i = 0; i < count; i++) {
        sum += twapSamples[i].ratio;
    }
    average = sum / count;
}

Optimization tip:

  • Keep a rolling window by discarding samples older than a threshold (e.g., 24 h).
  • Use a fixed‑size circular buffer to avoid dynamic array growth.

Step 4 – Protect Against Manipulation

Even with a TWAP, a malicious actor can influence the pool by depositing large amounts of one token just before a TWAP calculation. Mitigation strategies include:

4.1 Trade Size Limits

Set a maximum trade size relative to the pool reserves. For example, disallow trades larger than 10 % of the current reserve.

require(inputAmount <= (reserveA / 10), "Trade too large");

4.2 Time‑Based Delay

Introduce a mandatory delay between the last TWAP update and the acceptance of a new trade that could influence the TWAP. For instance, require at least 12 hours between a sample() call and a trade that changes the reserves by more than a threshold.

4.3 Rebalancing with an Internal Market Maker

Periodically run a rebalancing routine that pushes the reserves back towards a target ratio. The routine can be triggered by the TWAP contract itself or by an external bot. The algorithm should use a conservative kernel (e.g., a stable‑swap formula) that penalizes large deviations, following the approach in The Complete Framework for Oracle‑Free AMM Development.

function rebalance() external {
    uint256 targetRatio = getTWAP();
    // Compute desired reserves and adjust
}

Step 5 – User Interaction Flow

5.1 Quoting

  1. User calls getQuote(tokenIn, amountIn) off‑chain.
  2. The function calculates outputAmount using the core AMM formula and the current reserves.
  3. It returns both the immediate output and the TWAP‑based price for reference.

5.2 Executing a Trade

  1. User approves the AMM contract to spend amountIn of tokenA.
  2. User calls swapAForB(amountIn).
  3. Contract checks size limits, applies fee, updates reserves, and transfers tokenB to the user.
  4. After the trade, the contract records a new sample for the TWAP.

5.3 Adding or Removing Liquidity

  • Add liquidity: Users deposit both tokens in proportion to the current ratio. The contract mints LP tokens representing ownership.
  • Remove liquidity: Users burn LP tokens and receive back a proportionate amount of each token.

All liquidity actions are recorded in the same way as swaps to maintain an accurate reserve history.


Step 6 – Auditing and Formal Verification

Because this design eschews external oracles, the correctness of the on‑chain logic is paramount, a topic explored in Designing Oracle‑Free AMMs A Deep Dive Into Core DeFi Primitives.

  1. Unit Tests

    • Test all edge cases: zero reserves, extreme price swings, reentrancy attempts.
    • Use property‑based testing to ensure invariants (reserve balance, fee collection, TWAP integrity).
  2. Formal Verification

    • Model the AMM in a language such as K or F* and prove properties like:
      • No arbitrage: The product of reserves never increases.
      • Fee invariance: Total fees collected equals expected amount.
      • TWAP stability: The TWAP cannot be manipulated below a threshold with a single trade.
  3. Security Audits

    • Engage multiple audit firms, focusing on reentrancy, integer overflow/underflow, and front‑running.
    • Publish the audit reports and address all findings before launch.

Step 7 – Deployment and Governance

7.1 Initial Deployment

Deploy the AMM contract on the target network (e.g., Ethereum mainnet). Use a deterministic deployment address via CREATE2 to allow easier integration with front‑end tooling.

7.2 Governance Token

Issue a lightweight governance token that gives voting rights to LP token holders. Governance decisions can include:

  • Adjusting fee rates.
  • Changing the sampling interval for TWAP.
  • Enabling or disabling rebalancing routines.

A simple on‑chain voting module can be added that records proposals and votes via the LP token balance at a snapshot block.

7.3 Continuous Improvement

  • Monitor the pool’s metrics (volume, liquidity, TWAP variance).
  • Use data analytics dashboards to spot anomalies early.
  • Run periodic rebalancing and reserve audits to ensure stability.

Step 8 – Integrating with the Ecosystem

8.1 Front‑End Integration

  • Expose a quote() endpoint that callers can use to fetch real‑time prices without sending a transaction.
  • Provide a Web3‑enabled UI that allows users to swap, add, or remove liquidity.

8.2 Liquidity Incentives

  • Offer a small yield on LP tokens (e.g., through a yield farming contract) to attract liquidity providers.
  • Design the incentive to decay over time to avoid over‑inflation.

8.3 Cross‑Protocol Compatibility

  • Ensure the AMM complies with the ERC‑4626 standard for vaults to allow other DeFi protocols to deposit and withdraw liquidity seamlessly.
  • Provide an API that lets other protocols read the TWAP and reserve ratios.

Common Pitfalls and How to Avoid Them

Pitfall Description Mitigation
Front‑running Traders observe a pending swap that would affect TWAP and front‑run it. Enforce a minimum block delay before a trade can influence TWAP; use randomized sampling windows.
Flash loan attacks Flash loans temporarily skew the pool’s reserves to manipulate the TWAP. Limit trade sizes, enforce minimum reserve thresholds, and run periodic rebalancing.
Slippage exploitation A trader places a huge trade that causes large slippage. Apply a cap on trade size and implement a dynamic fee that increases with trade size.
Liquidity erosion LP tokens become worthless if the pool loses value. Use a price‑stable pair or a stable‑swap kernel; provide transparent reporting of pool health.

Summary

An oracle‑free AMM relies on the deterministic nature of on‑chain reserves to provide pricing, liquidity, and trading without any external data feeds. By:

  1. Defining a robust token pair and initial reserves,
  2. Using a constant‑product formula with built‑in fees,
  3. Building a time‑weighted average price engine,
  4. Protecting against manipulation with size limits and rebalancing,
  5. Offering a clear user interaction flow,
  6. Rigorously auditing the contract, and
  7. Implementing transparent governance and ecosystem integration,

you can create a resilient, trustless market maker that operates entirely on the blockchain. This approach eliminates oracle latency, reduces attack surfaces, and keeps all price discovery logic transparent and auditable.

Emma Varela
Written by

Emma Varela

Emma is a financial engineer and blockchain researcher specializing in decentralized market models. With years of experience in DeFi protocol design, she writes about token economics, governance systems, and the evolving dynamics of on-chain liquidity.

Contents