DEV Community

flat cash
flat cash

Posted on

FlatSale V5: Atomic Protocol-Owned Liquidity on Every Purchase

How FlatSale V5 Atomically Creates Protocol-Owned Liquidity on Every FLAT Purchase

DeFi protocols often struggle with sustainable liquidity provision. Many rely on external liquidity providers or temporary incentives, which can lead to mercenary capital and volatile liquidity depth. FlatSale V5 (the current sale contract for FLAT) takes a different approach: every purchase of FLAT automatically and atomically creates permanent protocol-owned liquidity (POL) in the Uniswap V2 FLAT-WETH pool.

This article breaks down how FlatSale V5 achieves this atomic POL creation, why it matters for protocol sustainability, and the technical implementation behind it. We'll focus on the buy() function, which handles user ETH, mints FLAT at the Chainlink oracle price, and pairs 90% of the ETH with treasury FLAT to create LP tokens—all in a single transaction that reverts if any step fails.


Why Protocol-Owned Liquidity Matters

Before diving into the code, let's clarify why POL is valuable:

  1. Permanent Liquidity: Unlike liquidity mining rewards, POL doesn't disappear when incentives dry up. It's owned by the protocol and can't be withdrawn by external actors.
  2. Revenue Generation: LP tokens earn trading fees, which accrue to the protocol treasury.
  3. Price Stability: Deep, permanent liquidity reduces slippage and price impact for traders.
  4. Reduced Dependence on Incentives: POL reduces the need for inflationary token emissions to attract liquidity.

FlatSale V5's approach ensures that every FLAT purchase contributes to POL, creating a flywheel effect where more purchases lead to deeper liquidity, which in turn makes FLAT more usable and attractive.


The Atomic POL Mechanism

Here's what happens when a user calls buy() on FlatSale V5:

  1. User sends ETH to the contract.
  2. FLAT is minted at the Chainlink ETH/USD oracle price.
  3. 90% of the ETH is paired with an equivalent value of FLAT from the treasury.
  4. The pair is added to the Uniswap V2 FLAT-WETH pool.
  5. LP tokens are sent to the protocol's Gnosis Safe.
  6. The remaining 10% of ETH is sent to the treasury.
  7. If any step fails, the entire transaction reverts.

All of this happens atomically—either all steps succeed, or none do. This ensures that the protocol never ends up in an inconsistent state (e.g., minting FLAT without creating LP).


Key Contracts and Addresses

Contract Address (Ethereum Mainnet) Purpose
FlatSale V5 0x5F65F7B609678448494De4C87521CdF6cEf1e932 Handles FLAT purchases and POL creation
FLAT Token 0x6a05A68d338f9689746b434B182AE1A560895995 The protocol's stablecoin
Uniswap V2 FLAT-WETH Pool 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 Where POL is created
Chainlink ETH/USD Oracle 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 Provides the ETH/USD price feed
Gnosis Safe (Treasury) 0x182386D5A34313930ae36F97B773c2693988291C Receives LP tokens and treasury funds

You can interact with FlatSale V5 directly at flat.cash/buy-flat or view all contracts at flat.cash/contracts.


The buy() Function: Step-by-Step

Let's examine the core logic of the buy() function. The full contract is open-source and verified on Etherscan (link).

1. Pricing: Chainlink Oracle

FlatSale V5 uses the Chainlink ETH/USD oracle to determine the price of FLAT. Since FLAT is a USD-pegged stablecoin, 1 FLAT = $1. The oracle provides the ETH/USD price, which is used to calculate how much FLAT to mint for the user's ETH.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract FlatSaleV5 {
    using SafeMath for uint256;

    AggregatorV3Interface public immutable ethUsdPriceFeed;
    IERC20 public immutable flatToken;
    IERC20 public immutable wethToken;
    IERC20 public immutable lpToken;
    address public immutable treasury;
    address public immutable uniswapRouter;

    uint256 public constant LP_PERCENT = 90; // 90% of ETH goes to LP
    uint256 public constant TREASURY_PERCENT = 10; // 10% of ETH goes to treasury

    constructor(
        address _ethUsdPriceFeed,
        address _flatToken,
        address _wethToken,
        address _lpToken,
        address _treasury,
        address _uniswapRouter
    ) {
        ethUsdPriceFeed = AggregatorV3Interface(_ethUsdPriceFeed);
        flatToken = IERC20(_flatToken);
        wethToken = IERC20(_wethToken);
        lpToken = IERC20(_lpToken);
        treasury = _treasury;
        uniswapRouter = _uniswapRouter;
    }

    function buy() external payable {
        // 1. Get the latest ETH/USD price from Chainlink
        (, int256 ethUsdPrice, , , ) = ethUsdPriceFeed.latestRoundData();
        require(ethUsdPrice > 0, "Invalid price feed");

        // 2. Calculate how much FLAT to mint (1 FLAT = $1)
        uint256 flatAmount = msg.value.mul(ethUsdPrice).div(1e8); // Chainlink price is 8 decimals
        require(flatAmount > 0, "Insufficient ETH for FLAT");

        // 3. Mint FLAT to the user
        flatToken.mint(msg.sender, flatAmount);

        // 4. Split ETH into LP and treasury portions
        uint256 lpEthAmount = msg.value.mul(LP_PERCENT).div(100);
        uint256 treasuryEthAmount = msg.value.sub(lpEthAmount);

        // 5. Create LP: pair LP ETH with treasury FLAT
        // First, transfer FLAT from treasury to this contract
        flatToken.transferFrom(treasury, address(this), flatAmount);

        // Approve Uniswap Router to spend FLAT and WETH
        flatToken.approve(uniswapRouter, flatAmount);
        wethToken.approve(uniswapRouter, lpEthAmount);

        // Add liquidity to Uniswap V2 FLAT-WETH pool
        (uint256 lpAmount, , ) = IUniswapV2Router02(uniswapRouter).addLiquidity(
            address(flatToken),
            address(wethToken),
            flatAmount,
            lpEthAmount,
            0, // min amounts (slippage protection)
            0,
            treasury, // LP tokens go to treasury
            block.timestamp + 300
        );

        // 6. Send remaining ETH to treasury
        payable(treasury).transfer(treasuryEthAmount);

        emit Buy(msg.sender, msg.value, flatAmount, lpAmount, treasuryEthAmount);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Splitting ETH: 90% to LP, 10% to Treasury

After minting FLAT, the contract splits the user's ETH into two portions:

  • 90% (lpEthAmount) is used to create LP in the Uniswap V2 FLAT-WETH pool.
  • 10% (treasuryEthAmount) is sent directly to the treasury.
uint256 lpEthAmount = msg.value.mul(LP_PERCENT).div(100);
uint256 treasuryEthAmount = msg.value.sub(lpEthAmount);
Enter fullscreen mode Exit fullscreen mode

3. Creating LP: Pairing ETH with Treasury FLAT

The most critical part is adding liquidity to the Uniswap V2 pool. Here's how it works:

  1. Transfer FLAT from Treasury: The contract pulls flatAmount (the same amount minted to the user) from the treasury.
  2. Approve Spending: The contract approves the Uniswap Router to spend FLAT and WETH (wrapped ETH).
  3. Add Liquidity: The addLiquidity function pairs flatAmount FLAT with lpEthAmount WETH and adds it to the pool.
  4. LP Tokens to Treasury: The resulting LP tokens are sent directly to the treasury's Gnosis Safe.
// Transfer FLAT from treasury to this contract
flatToken.transferFrom(treasury, address(this), flatAmount);

// Approve Uniswap Router to spend FLAT and WETH
flatToken.approve(uniswapRouter, flatAmount);
wethToken.approve(uniswapRouter, lpEthAmount);

// Add liquidity to Uniswap V2 FLAT-WETH pool
(uint256 lpAmount, , ) = IUniswapV2Router02(uniswapRouter).addLiquidity(
    address(flatToken),
    address(wethToken),
    flatAmount,
    lpEthAmount,
    0, // min amounts (slippage protection)
    0,
    treasury, // LP tokens go to treasury
    block.timestamp + 300
);
Enter fullscreen mode Exit fullscreen mode

4. Atomicity: Reverting on Failure

The entire buy() function is atomic. If any step fails (e.g., the Uniswap Router reverts due to slippage or insufficient approvals), the entire transaction reverts, and the user's ETH is returned. This ensures that:

  • FLAT is never minted without creating LP.
  • The treasury never receives ETH without the corresponding LP being created.
  • The user always receives the expected amount of FLAT or gets their ETH back.

Why This Design Works

1. Capital Efficiency

FlatSale V5 doesn't require the protocol to hold idle ETH or FLAT. Every purchase automatically deploys capital into liquidity, maximizing efficiency.

2. Price Stability

By creating POL on every purchase, the protocol ensures that liquidity depth scales with demand. This reduces slippage and makes FLAT more stable.

3. Sustainability

Unlike liquidity mining, where rewards must be constantly emitted to attract liquidity, POL is permanent. The protocol owns the LP tokens and earns trading fees indefinitely.

4. Simplicity

The mechanism is simple and transparent. Users don't need to understand LP tokens or Uniswap—they just buy FLAT, and the protocol handles the rest.


Potential Risks and Mitigations

No design is without trade-offs. Here are some risks and

Top comments (0)