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:
- 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.
- Revenue Generation: LP tokens earn trading fees, which accrue to the protocol treasury.
- Price Stability: Deep, permanent liquidity reduces slippage and price impact for traders.
- 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:
- User sends ETH to the contract.
- FLAT is minted at the Chainlink ETH/USD oracle price.
- 90% of the ETH is paired with an equivalent value of FLAT from the treasury.
- The pair is added to the Uniswap V2 FLAT-WETH pool.
- LP tokens are sent to the protocol's Gnosis Safe.
- The remaining 10% of ETH is sent to the treasury.
- 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);
}
}
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);
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:
-
Transfer FLAT from Treasury: The contract pulls
flatAmount(the same amount minted to the user) from the treasury. - Approve Spending: The contract approves the Uniswap Router to spend FLAT and WETH (wrapped ETH).
-
Add Liquidity: The
addLiquidityfunction pairsflatAmountFLAT withlpEthAmountWETH and adds it to the pool. - 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
);
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)