Skip to content

Instantly share code, notes, and snippets.

@jordaniza
Last active September 16, 2025 14:55
Show Gist options
  • Select an option

  • Save jordaniza/8ee959c538b17e060b90dbc5b68fe943 to your computer and use it in GitHub Desktop.

Select an option

Save jordaniza/8ee959c538b17e060b90dbc5b68fe943 to your computer and use it in GitHub Desktop.
TAIKO/TD Liquidity Bootstrap & TWAP Transition Strategy - Leveraging arbitrage for price discovery

TWAP Bootstrap Strategy for Taikodrome

What is TWAP?

TWAP (Time-Weighted Average Price) is a price calculation that averages the price of an asset over a specific time period, making it resistant to manipulation.

How TWAP Works in Aerodrome

Aerodrome pools store price observations at regular intervals (every 30 minutes by default):

  • Each observation records: timestamp, cumulative price
  • TWAP calculation: (Price_end - Price_start) / (Time_end - Time_start)
  • Longer time windows = more manipulation resistance

TWAP vs Spot Price

Spot Price: Current instantaneous price in the AMM

  • Can be manipulated with a single large trade
  • Reflects immediate supply/demand

TWAP Price: Average price over time window (e.g., 4 hours)

  • Requires sustained capital to manipulate
  • Lags behind spot price during volatility

Why TWAP Isn't Available Initially

TWAP requires historical data to calculate:

  1. New Pool = No History: When a pool launches, there are zero observations
  2. Minimum Observations: Need at least 2 observations to calculate TWAP
  3. Reliable TWAP: Requires ~8-12 observations (4-6 hours) for manipulation resistance
// Example: Pool with 0 observations
pool.observe([uint32(3600)]) // Reverts - no data for 1 hour ago

// After 4 hours (8 observations)
pool.observe([uint32(3600)]) // Returns valid TWAP for last hour

High-Level Strategy

Phase 1: Fixed Price Bootstrap (0-6 hours)

Objective: Launch bonding with predictable pricing while accumulating TWAP data

// In BondingCurve.sol
function getCurrentPrice() public view returns (uint256) {
    if (isBootstrapPhase) {
        return FIXED_BOOTSTRAP_PRICE; // e.g., 1 TAIKO = 1 TD
    }
    // ... TWAP logic
}

Actions:

  1. Deploy TAIKO/TD pool via Aerodrome PoolFactory
  2. Seed initial liquidity using POL
  3. Enable bonding at fixed rate
  4. Pool accumulates observations every 30 minutes

Phase 2: TWAP Data Collection (6-24 hours)

Objective: Ensure sufficient price history exists

Required Aerodrome Integration:

// Read from Aerodrome Pool contract
interface IAerodromePool {
    function observe(uint32[] calldata secondsAgos) 
        external view 
        returns (
            int56[] memory tickCumulatives,
            uint160[] memory secondsPerLiquidityCumulativeX128s
        );
    
    function slot0() external view returns (
        uint160 sqrtPriceX96,
        int24 tick,
        uint16 observationIndex,
        uint16 observationCardinality,
        uint16 observationCardinalityNext,
        uint8 feeProtocol,
        bool unlocked
    );
}

// In BondingCurve.sol
function canUseTWAP() public view returns (bool) {
    (, , , uint16 cardinality, , , ) = aerodromePool.slot0();
    return cardinality >= MIN_OBSERVATIONS; // e.g., 8
}

Phase 3: Transition to TWAP Pricing (24+ hours)

Objective: Switch bonding curve to use market-based pricing

// Admin function to end bootstrap
function endBootstrapPhase() external onlyAdmin {
    require(canUseTWAP(), "Insufficient TWAP data");
    isBootstrapPhase = false;
    emit BootstrapEnded(block.timestamp);
}

// Price calculation after bootstrap
function getCurrentPrice() public view returns (uint256) {
    if (isBootstrapPhase) {
        return FIXED_BOOTSTRAP_PRICE;
    }
    
    // Use Aerodrome's built-in TWAP
    uint32 twapWindow = 14400; // 4 hours
    (int56[] memory tickCumulatives, ) = aerodromePool.observe(
        [twapWindow, 0] // Compare now vs 4 hours ago
    );
    
    int56 tickDelta = tickCumulatives[1] - tickCumulatives[0];
    int24 arithmeticMeanTick = int24(tickDelta / int56(uint56(twapWindow)));
    
    return OracleLibrary.getQuoteAtTick(
        arithmeticMeanTick,
        uint128(1e18), // 1 TD
        address(tdToken),
        address(taikoToken)
    );
}

Risk: Permanent Price Divergence

The Problem

If TD and TAIKO permanently diverge in value (e.g., TD becomes worth 5x TAIKO):

  • Fixed bonding price becomes extremely attractive
  • TWAP-based bonding might become unattractive even with discounts
  • Protocol objectives compromised

Solution: Circuit Breakers & Dynamic Adjustments

contract BondingCurve {
    uint256 public constant MAX_PRICE_RATIO = 300; // TD can't exceed 3x TAIKO
    uint256 public constant MIN_PRICE_RATIO = 33;  // TD can't go below 0.33x TAIKO
    
    uint256 public priceAdjustmentFactor = 100; // 100 = no adjustment
    
    function getCurrentPrice() public view returns (uint256) {
        uint256 basePrice = isBootstrapPhase ? 
            FIXED_BOOTSTRAP_PRICE : 
            getTWAPPrice();
            
        // Apply adjustment factor
        uint256 adjustedPrice = (basePrice * priceAdjustmentFactor) / 100;
        
        // Enforce circuit breakers
        if (adjustedPrice > MAX_PRICE_RATIO * 1e16) {
            return MAX_PRICE_RATIO * 1e16; // Cap at 3:1
        }
        if (adjustedPrice < MIN_PRICE_RATIO * 1e16) {
            return MIN_PRICE_RATIO * 1e16; // Floor at 0.33:1
        }
        
        return adjustedPrice;
    }
    
    // Admin can adjust pricing in extreme scenarios
    function setPriceAdjustment(uint256 newFactor) external onlyAdmin {
        require(newFactor >= 50 && newFactor <= 200, "Adjustment too extreme");
        priceAdjustmentFactor = newFactor;
        emit PriceAdjustmentSet(newFactor);
    }
}

POL Management for Liquidity Control

The protocol can actively manage liquidity depth using bonded TAIKO:

contract BondingCurve {
    uint256 public polDeploymentRatio = 50; // Deploy 50% of TAIKO as POL
    
    function deployPOL() external onlyAdmin {
        uint256 taikoBalance = IERC20(TAIKO).balanceOf(address(this));
        uint256 taikoForPOL = (taikoBalance * polDeploymentRatio) / 100;
        
        // Mint matching TD at current market ratio
        uint256 currentPrice = getCurrentPrice(); // TAIKO per TD
        uint256 tdToMint = (taikoForPOL * 1e18) / currentPrice;
        
        // Deploy to Aerodrome pool
        TD.mint(address(this), tdToMint);
        TD.approve(address(aerodromeRouter), tdToMint);
        TAIKO.approve(address(aerodromeRouter), taikoForPOL);
        
        aerodromeRouter.addLiquidity(
            address(TAIKO),
            address(TD),
            stable, // false for volatile pair
            taikoForPOL,
            tdToMint,
            0, // Accept any amounts
            0,
            address(this), // POL owned by protocol
            block.timestamp
        );
    }
    
    // Adjust how much TAIKO goes to POL vs treasury
    function setPOLRatio(uint256 newRatio) external onlyAdmin {
        require(newRatio <= 100, "Invalid ratio");
        polDeploymentRatio = newRatio;
    }
}

Summary

  1. Start Fixed: Launch with predictable pricing (e.g., 1:1)
  2. Accumulate Data: Let Aerodrome pool build observation history
  3. Transition Carefully: Switch to TWAP once sufficient data exists
  4. Manage Risks: Implement circuit breakers and adjustment mechanisms
  5. Control Liquidity: Use POL deployment to manage market depth

This approach balances simplicity, security, and flexibility while leveraging Aerodrome's existing TWAP infrastructure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment