CORE DEFI PRIMITIVES AND MECHANICS

The Complete Framework for Oracle-Free AMM Development

9 min read
#Smart Contracts #Decentralized Finance #DeFi Protocols #Blockchain Architecture #Oracle-Free AMM
The Complete Framework for Oracle-Free AMM Development

Understanding Oracle‑Free Automated Market Makers

Automated Market Makers (AMMs) have become the backbone of most permissionless exchanges. In a typical AMM, liquidity providers (LPs) deposit token pairs into a shared pool, and traders execute swaps against that pool. The price of each token is determined by the pool’s invariant, most commonly the constant‑product function (x \times y = k). The reliance on price oracles can introduce latency, complexity, and vulnerability to manipulation. An oracle‑free AMM design eliminates these external dependencies by computing the state of the market purely from on‑chain data.

This article presents a comprehensive framework for building such oracle‑free AMMs, building on the concepts discussed in Crafting Decentralized Exchanges Without Dependence on Oracles. It covers the key primitives, design principles, system architecture, step‑by‑step implementation guidelines, testing strategy, security considerations, and illustrative use cases. By the end of this guide, you should be able to design, prototype, and audit an AMM that operates without external price feeds.


Core Primitives of Oracle‑Free AMMs

Pool State Representation

The pool maintains two essential balances, one for each token in the pair. These balances form the basis for all calculations:

  • reserveA – the amount of token A held by the pool
  • reserveB – the amount of token B held by the pool

The pool also stores a scaling factor or fee multiplier that applies to every swap. This allows the pool to impose a trading fee without altering the invariant itself.

Invariant Function

The invariant defines the relationship between the two reserves. The most common form is the constant‑product invariant:

[ reserveA \times reserveB = k ]

When a swap occurs, the pool updates the reserves in such a way that the product remains unchanged (apart from the fee). This property ensures that price impact is naturally derived from the pool’s current depth and is explored in depth in Understanding Automated Market Makers and the Core DeFi Mechanics.

Pricing Formula

The instantaneous price of token A in terms of token B is given by the ratio of reserves:

[ priceA = \frac{reserveB}{reserveA} ]

Because the invariant guarantees that the product of reserves stays constant, any change in one reserve automatically affects the other, causing the price to adjust smoothly.

Liquidity Provision

LPs deposit equal‑value amounts of token A and token B, a pattern explained in Mastering Core DeFi Primitives for Oracle‑Independent Market Makers. In return, they receive Liquidity Provider (LP) tokens that represent their share of the pool. The total supply of LP tokens is a ledger of all deposited liquidity. When liquidity is withdrawn, the pool burns the corresponding LP tokens and returns the proportional amounts of each underlying token.


Design Principles for Oracle‑Free AMMs

1. Determinism

All calculations must be deterministic and reproducible on every node. Any function that depends on off‑chain data (e.g., price feeds) is prohibited. Determinism guarantees that state transitions are identical across the network, preventing double‑spending and replay attacks.

2. Predictability

The pool’s behavior should be easily analyzable by traders. Because prices are derived from reserves, a trader can predict the impact of a swap by observing the pool’s depth. Transparency is a natural consequence of the deterministic invariant, a principle detailed in Designing Oracle‑Free AMMs A Deep Dive Into Core DeFi Primitives.

3. Security Through Simplicity

Simplifying the logic reduces attack surface. The fewer moving parts a contract has, the less chance there is for subtle bugs or exploit vectors. The invariant‑based design is one of the simplest yet most powerful mechanisms for maintaining market integrity.

4. Flexibility for Extensions

While the core remains simple, the framework must allow plug‑in modules (e.g., dynamic fees, time‑weighted liquidity, composable derivatives). These modules should be optional and composable through well‑defined interfaces.


System Architecture

Contract Layer

Layer Responsibility
Pool Holds reserves, executes swaps, manages LP tokens, updates fee state.
LP Token ERC‑20 compliant, tracks each LP’s share.
Router Orchestrates complex trade paths across multiple pools, handles approvals.
Oracle‑Free Price Feed Derives price solely from reserves (internal calculation).
Admin & Governance Allows for parameter changes (e.g., fee multiplier) via governance proposals.

On‑Chain Data Flow

  1. Deposit – User transfers tokens to Pool; Pool updates reserves and mints LP tokens.
  2. Swap – User calls Router which forwards to Pool. Pool calculates output amount using the invariant, deducts fees, updates reserves, and sends tokens to the user.
  3. Withdraw – User burns LP tokens, Pool calculates proportional reserves to return.

The Router can also aggregate multiple pools to provide a better price for cross‑pair trades, but each individual swap remains oracle‑free.


Step‑by‑Step Implementation Guide

1. Define the Pool Contract

pragma solidity ^0.8.0;

contract OracleFreePool {
    address public tokenA;
    address public tokenB;

    uint112 public reserveA;
    uint112 public reserveB;

    uint256 public feeNumerator;   // e.g., 3 for 0.3% fee
    uint256 public feeDenominator; // e.g., 1000

    constructor(address _tokenA, address _tokenB, uint256 _feeNum, uint256 _feeDen) {
        tokenA = _tokenA;
        tokenB = _tokenB;
        feeNumerator = _feeNum;
        feeDenominator = _feeDen;
    }

    // ... functions for addLiquidity, removeLiquidity, swap ...
}
  • Use uint112 for reserves to avoid overflow with typical token amounts.
  • Store fee as a fraction to support precise fee calculation.

2. Implement Liquidity Provision

function addLiquidity(uint256 amountA, uint256 amountB) external returns (uint256 liquidity) {
    // Transfer tokens from user to pool
    IERC20(tokenA).transferFrom(msg.sender, address(this), amountA);
    IERC20(tokenB).transferFrom(msg.sender, address(this), amountB);

    // Update reserves
    reserveA += uint112(amountA);
    reserveB += uint112(amountB);

    // Mint LP tokens proportional to deposited value
    liquidity = _mintLP(msg.sender, amountA, amountB);
}
  • _mintLP calculates the share based on total LP supply and current reserves.

3. Implement Swap Logic

function swap(uint256 amountIn, address tokenIn, address tokenOut, address recipient) external returns (uint256 amountOut) {
    require(tokenIn == tokenA || tokenIn == tokenB, "Unsupported token");
    require(tokenOut == tokenA || tokenOut == tokenB, "Unsupported token");
    require(tokenIn != tokenOut, "Identical tokens");

    // Determine input and output reserves
    uint112 reserveIn = tokenIn == tokenA ? reserveA : reserveB;
    uint112 reserveOut = tokenOut == tokenA ? reserveA : reserveB;

    // Transfer input token
    IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);

    // Apply fee
    uint256 amountInAfterFee = amountIn * (feeDenominator - feeNumerator) / feeDenominator;

    // Calculate output amount using constant‑product invariant
    uint256 numerator = amountInAfterFee * uint256(reserveOut);
    uint256 denominator = uint256(reserveIn) + amountInAfterFee;
    amountOut = numerator / denominator;

    // Update reserves
    if (tokenIn == tokenA) {
        reserveA += uint112(amountIn);
        reserveB -= uint112(amountOut);
    } else {
        reserveB += uint112(amountIn);
        reserveA -= uint112(amountOut);
    }

    // Transfer output token to recipient
    IERC20(tokenOut).transfer(recipient, amountOut);
}
  • The formula amountOut = (amountInAfterFee * reserveOut) / (reserveIn + amountInAfterFee) guarantees that the product of reserves remains unchanged.

4. Add Router for Multihop Trades

The Router orchestrates a sequence of swaps across multiple pools, ensuring atomicity:

function swapExactTokensForTokens(
    uint256 amountIn,
    uint256 amountOutMin,
    address[] calldata path,
    address recipient,
    uint256 deadline
) external returns (uint256[] memory amounts) {
    require(block.timestamp <= deadline, "Expired");
    amounts = new uint256[](path.length);
    amounts[0] = amountIn;

    for (uint i = 0; i < path.length - 1; i++) {
        address input = path[i];
        address output = path[i + 1];
        uint256 amountOut;
        (amountOut, amounts[i + 1]) = _swap(
            input,
            output,
            amounts[i],
            recipient,
            i == path.length - 2
        );
    }

    require(amounts[amounts.length - 1] >= amountOutMin, "Insufficient output");
}
  • The _swap helper calls the appropriate pool’s swap function and forwards tokens accordingly.

5. Governance and Parameter Updates

To change fee parameters or upgrade logic, a minimal governance contract can be used:

contract PoolGovernance {
    address public owner;
    OracleFreePool public pool;

    constructor(address _pool) {
        owner = msg.sender;
        pool = OracleFreePool(_pool);
    }

    function setFee(uint256 numerator, uint256 denominator) external {
        require(msg.sender == owner, "Not owner");
        pool.updateFee(numerator, denominator);
    }
}
  • The pool should expose a restricted updateFee function callable only by governance.

Testing Strategy

  1. Unit Tests

  2. Integration Tests

    • Simulate multiple users adding liquidity, swapping, and removing liquidity.
    • Ensure that LP shares update correctly.
  3. Formal Verification

    • Use tools like Coq, Isabelle, or Certora to prove that the invariant holds across all state transitions.
    • Model the contract as a state machine and prove properties like no negative reserves and fees collected are non‑negative.
  4. Attack Simulations

    • Front‑Running – simulate a malicious user inserting a trade just before a large swap to capture price impact.
    • Dust Attacks – verify that tiny deposits or withdrawals cannot destabilize the pool.
    • Reentrancy – test that the contract’s transfer logic is safe against reentrancy.
  5. Audit

    • Engage a professional audit firm experienced with AMM contracts.
    • Provide detailed documentation of the invariant, fee model, and any extensions.

Security Considerations

Threat Mitigation
Reentrancy Use checks‑effects‑interactions pattern. Mark all state changes before external calls.
Arithmetic Overflow/Underflow Solidity 0.8+ automatically reverts on overflow/underflow. Still, use SafeMath if targeting earlier compilers.
Insufficient Liquidity Reject swaps that would drain reserves below a safe threshold (e.g., 1% of total supply).
Fee Manipulation Keep fee parameters under governance control with a delay.
Denial of Service Ensure swap has a bounded gas usage. Reject swaps with negligible input amounts that would consume excessive gas.

Illustrative Use Cases

1. Cross‑Chain Liquidity Bridging

By deploying identical oracle‑free pools on multiple chains and routing swaps across them, users can move value with minimal slippage. Because each pool’s price is self‑contained, the bridge does not rely on external price feeds.

2. Synthetic Asset Creation

A synthetic asset token can be backed by an oracle‑free pool that locks the underlying asset. Traders can mint or redeem synthetic tokens by swapping against the pool, guaranteeing price integrity without external oracles.

3. Automated Yield Farming

Yield protocols can embed oracle‑free pools as liquidity sources. LPs can deposit into these pools and automatically harvest fees, while the platform can adjust fee multipliers to incentivize liquidity during periods of high volatility.


Extending the Framework

Extension How It Fits
Dynamic Fees Allows the pool to respond to market conditions.
Time‑Weighted Liquidity Adds a layer of risk‑adjusted returns.
Composable Derivatives Enables building more complex financial products on top of the core AMM.

Return the content with 3-7 natural internal links added. Modify sentences gracefully to incorporate links where it makes sense. Coupled with rigorous testing and formal verification, this approach ensures that the AMM behaves predictably and resists manipulation.

Whether you are building a new decentralized exchange, creating synthetic assets, or enabling cross‑chain bridges, adopting an oracle‑free design can enhance trust and simplify regulatory compliance. Start with the core contract, iterate with extensions, and engage the community for governance. With careful attention to the principles of determinism and security, you can create an AMM that stands the test of time in the ever‑evolving DeFi landscape.

Sofia Renz
Written by

Sofia Renz

Sofia is a blockchain strategist and educator passionate about Web3 transparency. She explores risk frameworks, incentive design, and sustainable yield systems within DeFi. Her writing simplifies deep crypto concepts for readers at every level.

Contents