Building a DEX in a Weekend

Software design has proven time and again that well-architected, modular systems built on proven components have been the industry unlock needed to accelerate innovation. Yet in 2024, teams still spend months rebuilding basic DEX functionality from scratch or forking basic designs with little attempt to innovate on top.

At Valantis, we see the future of DeFi holding modularity at its core. Teams using the Valantis stack can ship novel DEX designs with development timelines that are an order of magnitude lower than the industry standard.

The beauty of building on Valantis is that developers can focus on their unique features and custom designs rather than reimplementing and re-auditing core DEX logic.

We are building an ecosystem where each DEX can be easily tailored to its specific use case. Protocols can pick from a growing library of audited modules or develop their own. Every innovation matures the ecosystem.

The journey from vision to reality begins with understanding the Valantis Sovereign Pool.

Understanding the Sovereign Pool

The Valantis Sovereign Pool is the token pair contract that has the unique ability to compose with separate feature-specific contracts, known as Modules. The Sovereign Pool comes production-ready, handling fundamental operations like swaps, deposits, and withdrawals.

Unlike traditional DEXs where logic is tightly coupled, the Sovereign Pool provides a secure framework for Modules to define custom behavior. Each Module focuses on a specific feature while the pool coordinates their interactions, enforcing security checks at every step.

Modules and permissions are configured either at deployment or through the Pool Manager. This flexibility allows you to start with a basic DEX and evolve its capabilities over time without migrating liquidity.

The Sovereign Pool recognizes the following Modules and roles

  • Pool manager (optional): If not set, then it's an immutable pool

  • Liquidity Module (required): Handles pricing logic, invariant enforcement, and optionally LP token design

  • Sovereign Vault (optional): An external reserve contract to source and manage liquidity

  • Swap Fee Module (optional): Custom dynamic fee logic

  • Verifier Module (optional): Controls external data verification and access to pool operations

  • Oracle Module (optional): Accumulates pool data to produce custom oracles

  • Gauge Module (optional): Custom logic for LPs to share token emissions.

The Sovereign Pool swap lifecycle is described as follows:

  1. Pool validates basic parameters

  2. Verifier Module checks permissions and verifies any externally passed swap data (if present)

  3. Swap Fee Module calculates dynamic fee (if present)

  4. Liquidity Module provides price quote

  5. Pool handles token transfers directly or instructs the sovereign vault to do so (if present)

  6. Pool updates state and executes callbacks.

Pricing Logic, Your Way

The Liquidity Module sits at the heart of your DEX design - it determines how prices move in response to market behavior; essentially calculating the spot price for any given trade. You are defining your own action space of possible price outcomes. This is a powerful tool for shaping price discovery and market dynamics the way you envision them.

Take the classic constant product bonding curve (x * y = k) - it defines the relationship between the amount of tokens you swap in (x) to the amount you will receive (y).

We can implement constant product price quotes within the quoting function as follows:

  function getLiquidityQuote(
    ALMLiquidityQuoteInput memory _poolInput,
    bytes calldata /*_externalContext*/,
    bytes calldata /*_verifierData*/
  ) external view override returns (ALMLiquidityQuote memory quote) {
    (uint256 reserve0, uint256 reserve1) = POOL.getReserves();

    if (_poolInput.isZeroToOne) {
      quote.amountOut = (reserve1 * _poolInput.amountInMinusFee) / (reserve0 + _poolInput.amountInMinusFee);
    } else {
      quote.amountOut = (reserve0 * _poolInput.amountInMinusFee) / (reserve1 + _poolInput.amountInMinusFee);
    }

    quote.amountInFilled = _poolInput.amountInMinusFee;
  }

Let's say you want to modify the curve to be better equipped for stable pairs and modify the getLiquidityQuote function to implement the stable swap curve, which tightens the curve closer to the 1:1 price. You can find a full implementation of this design within Validly, a Solidly implementation built as a Valantis liquidity module.

Your Liquidity Module can also handle LP token issuance and withdrawal logic - check out this complete example of a constant product liquidity module. For more complex pricing that needs external data or state management, implement the callback interface.

Tailored Fee Incentives

Dynamic fees are a powerful control mechanism in decentralized exchange design. By crafting fee structures that respond to specific conditions, you can create natural economic pressures that guide the system toward desired behavior.

For example, you could define a pool in which once the spot price updates, the fees begin to increase linearly with time, keeping them low when prices are fresh and increasing fees as time passes to protect against stale price exploitation.

This creates an elegant feedback loop where traders are incentivized to execute quickly after price updates while naturally pricing out toxic arbitrage over time. Achieving this is as simple as implementing ISwapFeeModuleMinimal with your desired fee calculation logic:

function getSwapFeeInBips(
    address tokenIn,
    address tokenOut,
    uint256 amountIn,
    address sender,
    bytes calldata swapFeeModuleContext
) external view override returns (SwapFeeModuleData memory) {
    uint256 timeSinceLastUpdate = block.timestamp - lastUpdateTime;
    uint256 dynamicFee = MIN_FEE_BIPS + (FEE_GROWTH_RATE * timeSinceLastUpdate);
    uint256 feeInBips = Math.min(dynamicFee, MAX_FEE_BIPS);
    
    return SwapFeeModuleData({
        feeInBips: feeInBips,
        internalContext: new bytes(0)
    });
}

If your desired fee mechanism needs to maintain state between swaps or take in additional context data, then implement the ISwapFeeModule to add callbacks that execute after each swap. This enables more sophisticated designs like volume-based tiers or dynamic fee adjustments based on factors like trading volume or price volatility.

The flexibility of the Valantis Swap Fee Module enables builders to implement dynamic, context-aware fee mechanisms that can adapt to market conditions, composable on top of any Liquidity Module’s pricing curve.

Arbitrary Verification

The Verifier Module lets you implement sophisticated access control logic for your DEX by verifying externally passed data. It distinguishes between three core operations: swaps, deposits, and withdrawals. This granular control enables you to build custom access patterns.

function verify(
        address _user,
        bytes calldata, // _verificationContext
        uint8 _accessType
) external view returns (bool success, bytes memory returnData) {
        // Permissioned swaps
        if (_accessType == uint8(AccessType.SWAP)) {
            return (isApproved[_user], "");
        }
        
        // Anyone can deposit/withdraw
        if (_accessType == uint8(AccessType.DEPOSIT) || 
            _accessType == uint8(AccessType.WITHDRAW)) {
            return (true, "");
        }
        
        return (false, "");
    }
}

Let’s say you want to compose with an offchain system, just pass in a verification context and decode to validate external computations.

function verify(
    address _user,
    bytes calldata _verificationContext,
    uint8 _accessType
) external view returns (bool success, bytes memory returnData) {
    if (_accessType == uint8(AccessType.SWAP)) {
        (bytes memory proof, bytes memory data) = 
            abi.decode(_verificationContext, (bytes, bytes));
        
        // Custom verification logic
        if (_verify(proof, data)) {
	        return (true, data);
        }
    }
    
    return (false, "");
}

The Sovereign Vault

The final piece of Valantis' modular vision tackles the most fundamental aspect of any DEX: liquidity management.

By default, sovereign pools manage their own reserves. But with a Sovereign Vault, liquidity is stored in an external and customized contract. This is where developers get complete freedom to reimagine how capital moves through their system.

The vault interface is minimal by design, just extend it to include your custom functionality.

interface ISovereignVaultMinimal {
    function getTokensForPool(address _pool) external view returns (address[] memory);
    function getReservesForPool(address _pool, address[] calldata _tokens) 
        external view returns (uint256[] memory);
    function claimPoolManagerFees(uint256 _feePoolManager0, uint256 _feePoolManager1) external;
}

Here are a few ideas we are particularly excited about:

Unified Liquidity

Traditional DEXs trap liquidity in isolated pools, creating fragmentation across markets. A Sovereign Vault allows multiple pools with the same specialized Liquidity Module to share token reserves. The vault coordinates liquidity allocation across these pools, lowering the overall capital requirements for market making.

Idle Liquidity Strategies

Inactive liquidity can automatically earn yield by being deposited into lending markets or other DeFi protocols while maintaining required reserves for active positions. The vault can manage the balance between deploying capital for yield and ensuring sufficient liquidity for trading.

Beyond Token Pairs

When combined with a custom Liquidity Module, a Sovereign Vault enables these modules to implement multi-token pool designs. The Liquidity Module handles the multi-token pricing logic while the vault provides token management, allowing like things for flash accounting on multi-hop swaps. The vault manages capital flow while the pool's algorithms determine how tokens are exchanged.

Bringing it All Together

Once you've designed your modules, deploying a complete DEX on Valantis is straightforward, just set your deployment configuration within your desired contracts and permissions:

SovereignPoolConstructorArgs memory args = SovereignPoolConstructorArgs({ 
    token0: token0,
    token1: token1,
    sovereignVault: address(vault),
    verifierModule: address(verifier),
    protocolFactory: address(factory),
    poolManager: address(deployer),
    isToken0Rebase: false,
    isToken1Rebase: false,
    token0AbsErrorTolerance: 0,
    token1AbsErrorTolerance: 0,
    defaultSwapFeeBips: 30 // 0.3% fee
})

So What?

What has this looked like in practice so far? Some of the designs shipped on the Valantis Stack thus far have been:

HOT-AMM: A completely novel hybrid AMM / RfQ brought fully to production from ideation → audit in ~2 months, live and operating with public liquidity.

Validly: A fully audited version of Solidly that supports rebase tokens, made audit-ready in 1 week and now publicly available.

Bagel: The first published implementation of sandwich-resistant AMM, built in 2 days.

Uni v2: Implemented on Valantis in <200 lines of code and a few hours of dev work.

Shared Liquidity DEX: An index fund Sovereign Vault design built in a week-long hackathon

It’s not just DEX design but faster. Building modular design on Valantis means you can do everything more efficiently than you would have otherwise. Reuse existing components with established security, segment logic into different Modules, and ship exciting new designs that wouldn’t have been possible anywhere else.

The Valantis Stack is freely available for anyone to build on. You can get started with our docs here. If you would like to start building Valantis - we’d love to support you as best as we’re able. Join our Discord and start a conversation with us.

Subscribe to Valantis Labs
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.