Complete source code of all FLAT Protocol smart contracts deployed on Ethereum mainnet. All 10 contracts are verified on Etherscan. This page provides the identical source code inline for transparency, AI verification, and auditor review.
Network: Ethereum Mainnet · Compiler: Solidity 0.8.20 / 0.8.24 / 0.8.26 / 0.8.28 · Optimizations: 200 runs · All contracts verified on Etherscan
| Contract | Address | Verified | Pausable | nSLOC |
|---|---|---|---|---|
| FLAT | 0x6AD2...aE99 | Verified | No | ~43 |
| CPIOracle v3 | 0xd4aA...c664 | Verified | No | ~158 |
| FlatEngine v2 | 0x30a8...9cd5 | Verified | Guardian (3yr) | ~514 |
| FlatSale v2 (Active) | 0x7d8F...ec9d | Verified | Guardian (3yr) | ~646 |
| RISE | 0xc1E1...a14A | Verified | No | ~34 |
| SAVE (Vault) | 0x9f0D...9cAe | Verified | No | ~36 |
| SAVESale v2 (Active) | 0x2840...cd02 | Verified | Guardian (3yr) | ~578 |
| FlatBearer | 0xc9a1...cBcc | Verified | Guardian (3yr) | ~160 |
| FlatIDVault | 0xEe05...1644 | Verified | Admin+Guardian | ~85 |
| FlatIDSaveVault | 0xABB9...89ea | Verified | Admin+Guardian | ~85 |
| FLAT/WETH Pair | 0x2bC1...7205 | Uniswap V2 | No | N/A |
| SAVE/WETH Pair | 0xEC40...bEa | Uniswap V2 | No | N/A |
| RISE/WETH Pair | 0x2cC9...6d5 | Uniswap V2 | No | N/A |
The following claims can be verified by reading the source code below:
Tier 1 — Fully Immutable
The FLAT coin — a CPI-pegged stablecoin with fixed supply. 100 trillion tokens minted once at deployment. No mint, no burn, no admin, no pause, no blacklist, no proxy. Pure immutable ERC-20 + EIP-2612.
Owner: None (no Ownable) · Address: 0x6AD27352CEb1B55A1Cbf885cEfC2Ed5A9183aE99
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.24;34import "@openzeppelin/contracts/token/ERC20/ERC20.sol";5import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";67/// @title FLAT8/// @notice The FLAT coin — a CPI-pegged stablecoin with fixed supply.9///10/// 100 trillion tokens (100,000,000,000,000) are minted once at deployment11/// and sent to the deployer address. No further minting is possible.12/// No burn, no admin keys, no pause, no blacklist, no proxy.13///14/// ERC-2612 permit enables gasless approvals for FlatSale and other15/// integrations without requiring a separate approve transaction.16///17/// CROPS-compliant: Censorship Resistant, Capture Resistant, Open Source,18/// Private, Secure. This contract has zero admin surface — once deployed,19/// no one can modify its behavior. It is pure immutable bytecode.20///21/// @dev Total supply: 100_000_000_000_000 * 1e18 = 1e3222/// uint256 max: ~1.15e7723/// No overflow risk. Verified safe.24contract FLAT is ERC20, ERC20Permit {2526 /// @notice Fixed total supply: 100 trillion FLAT (18 decimals).27 uint256 public constant TOTAL_SUPPLY = 100_000_000_000_000 * 1e18;2829 /// @notice Deploys the FLAT token and mints the entire supply to the deployer.30 /// After construction, no tokens can ever be minted again.31 constructor() ERC20("FLAT", "FLAT") ERC20Permit("FLAT") {32 _mint(msg.sender, TOTAL_SUPPLY);33 }3435 // No mint function.36 // No burn function.37 // No owner.38 // No pause.39 // No blacklist.40 // No admin keys.41 // No proxy.42 // Immutable forever.43}
Tier 2 — Owner-Controlled Oracle
CPI-U interpolating oracle (v3). Stores two CPI data points and linearly interpolates between them block-by-block, so the FLAT target price advances smoothly every ~12 seconds. Circuit breaker limits CPI changes to 2% per update. Public refreshTimestamp() prevents staleness without owner intervention. Inlined Ownable (no external dependencies).
Owner: Ledger (0x11d6...fC2e) · Address: 0xd4aA8f16258451561f7fc2B2CD0f0a59Db43c664
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.24;34/**5 * @title CPIOracle v36 * @notice On-chain CPI price oracle with linear block-by-block interpolation.7 * The owner calls updateCPI() once per month when BLS releases new data.8 * The price smoothly interpolates from old -> new over ~30 days (216,000 blocks).9 *10 * @dev Changes from v2:11 * - Added public refreshTimestamp() — anyone can call to prevent staleness12 * without disrupting interpolation. Only updates lastUpdated.13 * - Added lastCPIUpdateTimestamp for transparency (only set by updateCPI).14 * - MAX_CPI_CHANGE_BPS = 200 (2.0% cap, same as v2).15 * - No cooldown on updateCPI (2% cap is sufficient abuse limiter).16 */1718// Minimal Ownable (from OpenZeppelin 5.x, inlined to avoid dependency)19abstract contract Ownable {20 address private _owner;2122 error OwnableUnauthorizedAccount(address account);23 error OwnableInvalidOwner(address owner);2425 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);2627 constructor(address initialOwner) {28 if (initialOwner == address(0)) revert OwnableInvalidOwner(address(0));29 _owner = initialOwner;30 emit OwnershipTransferred(address(0), initialOwner);31 }3233 modifier onlyOwner() {34 if (msg.sender != _owner) revert OwnableUnauthorizedAccount(msg.sender);35 _;36 }3738 function owner() public view returns (address) { return _owner; }3940 function renounceOwnership() public onlyOwner {41 address old = _owner;42 _owner = address(0);43 emit OwnershipTransferred(old, address(0));44 }4546 function transferOwnership(address newOwner) public onlyOwner {47 if (newOwner == address(0)) revert OwnableInvalidOwner(address(0));48 address old = _owner;49 _owner = newOwner;50 emit OwnershipTransferred(old, newOwner);51 }52}5354contract CPIOracle is Ownable {5556 // -- Storage --57 uint256 public previousCPIValue;58 uint256 public currentCPIValue;59 uint256 public previousBlockNumber;60 uint256 public targetBlockNumber;61 uint256 public lastUpdated; // refreshed by both updateCPI and refreshTimestamp62 uint256 public lastCPIUpdateTimestamp; // only set by updateCPI, for transparency6364 // -- Constants --65 uint256 public constant BLOCKS_PER_MONTH = 216_000;66 uint256 public constant MAX_CPI_CHANGE_BPS = 200; // 2.0% max change per update6768 // -- Events --69 event CPIUpdated(70 uint256 previousCPI,71 uint256 newCPI,72 uint256 previousBlock,73 uint256 targetBlock,74 uint256 timestamp75 );76 event TimestampRefreshed(uint256 timestamp);7778 // -- Constructor --79 constructor(uint256 _initialCPI) Ownable(msg.sender) {80 require(_initialCPI > 0, "invalid initial CPI");81 previousCPIValue = _initialCPI;82 currentCPIValue = _initialCPI;83 previousBlockNumber = block.number;84 targetBlockNumber = block.number;85 lastUpdated = block.timestamp;86 lastCPIUpdateTimestamp = block.timestamp;87 }8889 // -- Public: refresh staleness timestamp without touching CPI data --90 /// @notice Anyone can call this to keep the oracle fresh for FlatSale/FlatEngine.91 /// Only updates lastUpdated. Does NOT touch CPI values or interpolation state.92 function refreshTimestamp() external {93 lastUpdated = block.timestamp;94 emit TimestampRefreshed(block.timestamp);95 }9697 // -- Owner-only: set new CPI target --98 function updateCPI(uint256 _newCPI) external onlyOwner {99 require(_newCPI > 0, "invalid CPI");100101 uint256 refPrice;102 if (block.number <= previousBlockNumber) {103 refPrice = previousCPIValue;104 } else {105 refPrice = getInterpolatedPrice();106 }107108 require(109 _newCPI * 10_000 <= refPrice * (10_000 + MAX_CPI_CHANGE_BPS) &&110 _newCPI * 10_000 >= refPrice * (10_000 - MAX_CPI_CHANGE_BPS),111 "CPI change exceeds 2%"112 );113114 previousCPIValue = refPrice;115 currentCPIValue = _newCPI;116 previousBlockNumber = block.number;117 targetBlockNumber = block.number + BLOCKS_PER_MONTH;118 lastUpdated = block.timestamp;119 lastCPIUpdateTimestamp = block.timestamp;120121 emit CPIUpdated(refPrice, _newCPI, block.number, block.number + BLOCKS_PER_MONTH, block.timestamp);122 }123124 // -- View: linearly interpolated price at current block --125 function getInterpolatedPrice() public view returns (uint256 price) {126 if (block.number >= targetBlockNumber) {127 return currentCPIValue;128 }129130 uint256 prev = previousCPIValue;131 uint256 curr = currentCPIValue;132133 if (prev == curr) return curr;134135 uint256 elapsed = block.number - previousBlockNumber;136 uint256 total = targetBlockNumber - previousBlockNumber;137138 if (curr > prev) {139 price = prev + ((curr - prev) * elapsed) / total;140 } else {141 price = prev - ((prev - curr) * elapsed) / total;142 }143 }144145 // -- View: convenience getters (ICPIOracle interface) --146 function lastUpdateTimestamp() external view returns (uint256 timestamp) {147 return lastUpdated;148 }149150 function getCPIData() external view returns (151 uint256 previousCPI,152 uint256 currentCPI,153 uint256 previousBlock,154 uint256 currentBlock155 ) {156 return (previousCPIValue, currentCPIValue, previousBlockNumber, targetBlockNumber);157 }158}
Tier 2 — Guardian Pattern (3-year expiry)
Autonomous peg management (v2). Maintains FLAT-ETH Uniswap V2 pool price at the CPI oracle target via charge (sell FLAT when above peg) and discharge (buy FLAT when below peg) cycles. Oracle address is mutable via 48-hour timelock. Permissionless pulse() function — anyone can call and earn a 5% bounty. All dependencies inlined (no external imports).
Owner: Ledger (0x11d6...fC2e) · Address: 0x30a81352D9889f73A6D71798b3A98B8558309cd5
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.24;34/**5 * @title FlatEngine v26 * @notice Autonomous peg management contract for FLAT coin. Maintains the FLAT-ETH7 * Uniswap V2 pool price at the CPI oracle target via charge and discharge cycles.8 *9 * CHARGE (FLAT > oracle): Sell FLAT from inventory -> receive ETH -> add liquidity10 * -> battery grows. Price pushed down toward target.11 *12 * DISCHARGE (FLAT < oracle): Remove LP from battery -> keep FLAT -> use ETH to13 * buy FLAT from pool. Price pushed up toward target.14 *15 * All operations execute through a single permissionless pulse() function.16 * The caller receives a bountyBps% bounty on the ETH transacted.17 *18 * @dev Changes from v1:19 * - Oracle address is MUTABLE via 48-hour timelock (proposeOracle/executeOracleChange).20 * - guardianExpiry is passed as constructor parameter (preserves original timeline).21 * - Version string updated to "FlatEngine_v2".22 * - All other logic identical to v1.23 */2425// ============ INTERFACES (inlined) ============2627interface IERC20 {28 function totalSupply() external view returns (uint256);29 function balanceOf(address account) external view returns (uint256);30 function transfer(address to, uint256 value) external returns (bool);31 function allowance(address owner, address spender) external view returns (uint256);32 function approve(address spender, uint256 value) external returns (bool);33 function transferFrom(address from, address to, uint256 value) external returns (bool);34}3536interface ICPIOracle {37 function getInterpolatedPrice() external view returns (uint256 price);38 function lastUpdateTimestamp() external view returns (uint256 timestamp);39 function getCPIData() external view returns (uint256, uint256, uint256, uint256);40}4142interface IAggregatorV3 {43 function decimals() external view returns (uint8);44 function latestRoundData() external view returns (45 uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound46 );47}4849interface IUniswapV2Router02 {50 function WETH() external pure returns (address);51 function addLiquidityETH(52 address token, uint amountTokenDesired, uint amountTokenMin, uint amountETHMin,53 address to, uint deadline54 ) external payable returns (uint amountToken, uint amountETH, uint liquidity);55 function removeLiquidityETH(56 address token, uint liquidity, uint amountTokenMin, uint amountETHMin,57 address to, uint deadline58 ) external returns (uint amountToken, uint amountETH);59 function swapExactTokensForETH(60 uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline61 ) external returns (uint[] memory amounts);62 function swapExactETHForTokens(63 uint amountOutMin, address[] calldata path, address to, uint deadline64 ) external payable returns (uint[] memory amounts);65}6667interface IUniswapV2Pair {68 function token0() external view returns (address);69 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);70 function totalSupply() external view returns (uint256);71 function balanceOf(address owner) external view returns (uint256);72 function transfer(address to, uint value) external returns (bool);73 function approve(address spender, uint value) external returns (bool);74}7576// Minimal Ownable (inlined)77abstract contract Ownable {78 address private _owner;79 error OwnableUnauthorizedAccount(address account);80 error OwnableInvalidOwner(address owner);81 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);8283 constructor(address initialOwner) {84 if (initialOwner == address(0)) revert OwnableInvalidOwner(address(0));85 _owner = initialOwner;86 emit OwnershipTransferred(address(0), initialOwner);87 }88 modifier onlyOwner() {89 if (msg.sender != _owner) revert OwnableUnauthorizedAccount(msg.sender);90 _;91 }92 function owner() public view returns (address) { return _owner; }93 function renounceOwnership() public onlyOwner {94 address old = _owner;95 _owner = address(0);96 emit OwnershipTransferred(old, address(0));97 }98 function transferOwnership(address newOwner) public onlyOwner {99 if (newOwner == address(0)) revert OwnableInvalidOwner(address(0));100 address old = _owner;101 _owner = newOwner;102 emit OwnershipTransferred(old, newOwner);103 }104}105106// Minimal ReentrancyGuard (inlined)107abstract contract ReentrancyGuard {108 uint256 private constant NOT_ENTERED = 1;109 uint256 private constant ENTERED = 2;110 uint256 private _status = NOT_ENTERED;111112 modifier nonReentrant() {113 require(_status != ENTERED, "ReentrancyGuard: reentrant call");114 _status = ENTERED;115 _;116 _status = NOT_ENTERED;117 }118}119120contract FlatEngine is Ownable, ReentrancyGuard {121122 // ============ IMMUTABLES ============123 IERC20 public immutable FLAT;124 IUniswapV2Router02 public immutable router;125 IUniswapV2Pair public immutable pair;126 IAggregatorV3 public immutable ethUsdFeed;127 address public immutable WETH;128 bool public immutable flatIsToken0;129 uint256 public immutable bountyBps;130 uint256 public immutable guardianExpiry;131 uint256 public immutable feedDecimals;132133 // ============ MUTABLE ORACLE (with 48h timelock) ============134 ICPIOracle public oracle;135 address public pendingOracle;136 uint256 public oracleChangeTimestamp;137138 // ============ CONSTANTS ============139 uint256 public constant MIN_DEVIATION_BPS = 10;140 uint256 public constant MAX_INVENTORY_SELL_PCT = 80;141 uint256 public constant MIN_BATTERY_LP = 1000;142 uint256 public constant CPI_STALENESS_THRESHOLD = 172800;143 uint256 public constant ETH_USD_STALENESS_THRESHOLD = 7200;144 uint256 public constant ORACLE_TIMELOCK = 172800; // 48 hours145146 // ============ MUTABLE STATE ============147 bool public paused;148 uint256 public lastPulseBlock;149 uint256 public totalCharged;150 uint256 public totalDischarged;151152 // ============ EVENTS ============153 event Charged(154 address indexed caller, uint256 flatSold, uint256 ethReceived,155 uint256 lpCreated, uint256 bounty156 );157 event Discharged(158 address indexed caller, uint256 lpRemoved, uint256 flatFromLP,159 uint256 ethFromLP, uint256 flatBought, uint256 bounty160 );161 event BatteryTransferred(address indexed from, address indexed to, uint256 lpAmount);162 event InventoryTransferred(address indexed from, address indexed to, uint256 flatAmount);163 event OracleChangeProposed(address indexed newOracle, uint256 executeAfter);164 event OracleChanged(address indexed oldOracle, address indexed newOracle);165 event OracleChangeCancelled(address indexed cancelledOracle);166 event Paused();167 event Unpaused();168169 // ============ CONSTRUCTOR ============170 constructor(171 address _flat,172 address _router,173 address _pair,174 address _oracle,175 address _ethUsdFeed,176 uint256 _bountyBps,177 uint256 _guardianExpiry178 ) Ownable(msg.sender) {179 require(_flat != address(0), "invalid flat");180 require(_router != address(0), "invalid router");181 require(_pair != address(0), "invalid pair");182 require(_oracle != address(0), "invalid oracle");183 require(_ethUsdFeed != address(0), "invalid ethUsdFeed");184 require(_bountyBps > 0 && _bountyBps <= 1000, "invalid bounty");185 require(_guardianExpiry <= block.timestamp + 1095 days + 7 days, "expiry too far");186187 FLAT = IERC20(_flat);188 router = IUniswapV2Router02(_router);189 pair = IUniswapV2Pair(_pair);190 oracle = ICPIOracle(_oracle);191 ethUsdFeed = IAggregatorV3(_ethUsdFeed);192 WETH = router.WETH();193 bountyBps = _bountyBps;194195 flatIsToken0 = pair.token0() == _flat;196 feedDecimals = uint256(ethUsdFeed.decimals());197198 guardianExpiry = _guardianExpiry;199 }200201 // ============ MODIFIERS ============202 modifier whenNotPaused() {203 require(!paused, "paused");204 _;205 }206207 modifier onlyGuardian() {208 require(msg.sender == owner(), "not owner");209 require(block.timestamp <= guardianExpiry, "guardian expired");210 _;211 }212213 // ============ INTERNAL HELPERS ============214215 function _sqrt(uint256 x) internal pure returns (uint256 y) {216 if (x == 0) return 0;217 uint256 z = (x + 1) / 2;218 y = x;219 while (z < y) {220 y = z;221 z = (x / z + z) / 2;222 }223 }224225 function _getReserves() internal view returns (uint256 flatReserve, uint256 ethReserve) {226 (uint112 reserve0, uint112 reserve1,) = pair.getReserves();227 if (flatIsToken0) {228 flatReserve = uint256(reserve0);229 ethReserve = uint256(reserve1);230 } else {231 flatReserve = uint256(reserve1);232 ethReserve = uint256(reserve0);233 }234 }235236 function _getEthPerFlat() internal view returns (uint256 ethPerFlat) {237 uint256 cpiUsdPrice = oracle.getInterpolatedPrice();238 require(cpiUsdPrice > 0, "invalid CPI price");239240 require(241 block.timestamp - oracle.lastUpdateTimestamp() <= CPI_STALENESS_THRESHOLD,242 "CPI oracle stale"243 );244245 (, int256 ethUsdAnswer, , uint256 ethUsdUpdatedAt, ) = ethUsdFeed.latestRoundData();246 require(ethUsdAnswer > 0, "invalid ETH/USD price");247248 require(249 block.timestamp - ethUsdUpdatedAt <= ETH_USD_STALENESS_THRESHOLD,250 "ETH/USD feed stale"251 );252253 ethPerFlat = (cpiUsdPrice * (10 ** feedDecimals)) / uint256(ethUsdAnswer);254 }255256 // ============ VIEW FUNCTIONS ============257258 function getOraclePrice() public view returns (uint256) {259 return _getEthPerFlat();260 }261262 function getOraclePriceUSD() public view returns (uint256) {263 return oracle.getInterpolatedPrice();264 }265266 function getETHUSDPrice() public view returns (uint256) {267 (, int256 answer, , , ) = ethUsdFeed.latestRoundData();268 require(answer > 0, "invalid ETH/USD");269 return uint256(answer) * (10 ** (18 - feedDecimals));270 }271272 function getMarketPrice() public view returns (uint256) {273 (uint256 flatReserve, uint256 ethReserve) = _getReserves();274 require(flatReserve > 0 && ethReserve > 0, "empty pool");275 return (ethReserve * 1e18) / flatReserve;276 }277278 function getDeviation() public view returns (int256) {279 uint256 oraclePrice = _getEthPerFlat();280 (uint256 flatReserve, uint256 ethReserve) = _getReserves();281 require(flatReserve > 0 && ethReserve > 0, "empty pool");282 uint256 marketPrice = (ethReserve * 1e18) / flatReserve;283284 if (marketPrice >= oraclePrice) {285 return int256((marketPrice - oraclePrice) * 10000 / oraclePrice);286 } else {287 return -int256((oraclePrice - marketPrice) * 10000 / oraclePrice);288 }289 }290291 function getBatteryBalance() public view returns (uint256) {292 return pair.balanceOf(address(this));293 }294295 function getBatteryValue() external view returns (uint256) {296 uint256 lpBalance = getBatteryBalance();297 if (lpBalance == 0) return 0;298 uint256 totalSupply = pair.totalSupply();299 if (totalSupply == 0) return 0;300 (, uint256 ethReserve) = _getReserves();301 return (lpBalance * ethReserve * 2) / totalSupply;302 }303304 function getInventoryBalance() public view returns (uint256) {305 return FLAT.balanceOf(address(this));306 }307308 function isPulseProfitable() external view returns (bool) {309 try this.getOraclePrice() returns (uint256 oraclePrice) {310 (uint256 flatReserve, uint256 ethReserve) = _getReserves();311 if (flatReserve == 0 || ethReserve == 0) return false;312 uint256 marketPrice = (ethReserve * 1e18) / flatReserve;313 uint256 deviation;314 if (marketPrice >= oraclePrice) {315 deviation = (marketPrice - oraclePrice) * 10000 / oraclePrice;316 } else {317 deviation = (oraclePrice - marketPrice) * 10000 / oraclePrice;318 }319 return deviation >= MIN_DEVIATION_BPS;320 } catch {321 return false;322 }323 }324325 function version() external pure returns (string memory) {326 return "FlatEngine_v2";327 }328329 // ============ CORE: pulse() ============330331 function pulse() external nonReentrant whenNotPaused {332 require(block.number > lastPulseBlock, "one pulse per block");333 lastPulseBlock = block.number;334335 uint256 oraclePrice = _getEthPerFlat();336337 (uint256 flatReserve, uint256 ethReserve) = _getReserves();338 require(flatReserve > 0 && ethReserve > 0, "pool empty");339340 uint256 marketPrice = (ethReserve * 1e18) / flatReserve;341342 bool isAbovePeg = marketPrice > oraclePrice;343 uint256 deviation;344 if (isAbovePeg) {345 deviation = (marketPrice - oraclePrice) * 10000 / oraclePrice;346 } else {347 deviation = (oraclePrice - marketPrice) * 10000 / oraclePrice;348 }349 require(deviation >= MIN_DEVIATION_BPS, "deviation too small");350351 if (isAbovePeg) {352 _charge(oraclePrice, flatReserve, ethReserve);353 } else {354 _discharge(oraclePrice, flatReserve, ethReserve, deviation);355 }356 }357358 function _charge(359 uint256 oraclePrice, uint256 flatReserve, uint256 ethReserve360 ) internal {361 uint256 k = flatReserve * ethReserve;362 uint256 targetFlatReserve = _sqrt((k * 1e18) / oraclePrice);363364 uint256 flatToSell = targetFlatReserve - flatReserve;365 require(flatToSell > 0, "nothing to sell");366367 uint256 inventory = FLAT.balanceOf(address(this));368 uint256 maxSell = inventory * MAX_INVENTORY_SELL_PCT / 100;369 if (flatToSell > maxSell) {370 flatToSell = maxSell;371 }372 require(inventory >= flatToSell, "insufficient inventory");373374 address[] memory path = new address[](2);375 path[0] = address(FLAT);376 path[1] = WETH;377378 FLAT.approve(address(router), flatToSell);379 uint256[] memory amounts = router.swapExactTokensForETH(380 flatToSell, 0, path, address(this), block.timestamp381 );382 uint256 ethReceived = amounts[amounts.length - 1];383384 uint256 bounty = ethReceived * bountyBps / 10000;385 uint256 ethForLP = ethReceived - bounty;386387 uint256 flatForLP = (ethForLP * 1e18) / oraclePrice;388 uint256 currentInventory = FLAT.balanceOf(address(this));389 if (flatForLP > currentInventory) {390 flatForLP = currentInventory;391 }392393 uint256 lpReceived = 0;394 if (flatForLP > 0 && ethForLP > 0) {395 FLAT.approve(address(router), flatForLP);396 (, , lpReceived) = router.addLiquidityETH{value: ethForLP}(397 address(FLAT), flatForLP, 0, 0, address(this), block.timestamp398 );399 }400401 if (bounty > 0) {402 (bool success, ) = payable(msg.sender).call{value: bounty}("");403 require(success, "bounty transfer failed");404 }405406 totalCharged += lpReceived;407 emit Charged(msg.sender, flatToSell, ethReceived, lpReceived, bounty);408 }409410 function _discharge(411 uint256 oraclePrice, uint256 flatReserve, uint256 ethReserve, uint256 deviation412 ) internal {413 uint256 lpBalance = pair.balanceOf(address(this));414 require(lpBalance >= MIN_BATTERY_LP, "battery too small");415416 uint256 lpToRemove = lpBalance * deviation / 10000;417 if (lpToRemove > lpBalance) lpToRemove = lpBalance;418 if (lpToRemove < MIN_BATTERY_LP && lpBalance >= MIN_BATTERY_LP) {419 lpToRemove = MIN_BATTERY_LP;420 }421422 IERC20(address(pair)).approve(address(router), lpToRemove);423424 (uint256 flatReceived, uint256 ethReceived) = router.removeLiquidityETH(425 address(FLAT), lpToRemove, 0, 0, address(this), block.timestamp426 );427428 uint256 bounty = ethReceived * bountyBps / 10000;429 uint256 ethForBuy = ethReceived - bounty;430431 address[] memory path = new address[](2);432 path[0] = WETH;433 path[1] = address(FLAT);434435 uint256 flatBought = 0;436 if (ethForBuy > 0) {437 uint256[] memory amounts = router.swapExactETHForTokens{value: ethForBuy}(438 0, path, address(this), block.timestamp439 );440 flatBought = amounts[amounts.length - 1];441 }442443 if (bounty > 0) {444 (bool success, ) = payable(msg.sender).call{value: bounty}("");445 require(success, "bounty transfer failed");446 }447448 totalDischarged += lpToRemove;449 emit Discharged(msg.sender, lpToRemove, flatReceived, ethReceived, flatBought, bounty);450 }451452 // ============ ORACLE TIMELOCK (Category 1 — expires after guardian period) ============453454 function proposeOracle(address _newOracle) external onlyGuardian {455 require(_newOracle != address(0), "invalid oracle");456 require(_newOracle != address(oracle), "same oracle");457 pendingOracle = _newOracle;458 oracleChangeTimestamp = block.timestamp + ORACLE_TIMELOCK;459 emit OracleChangeProposed(_newOracle, oracleChangeTimestamp);460 }461462 function executeOracleChange() external onlyGuardian {463 require(pendingOracle != address(0), "no pending oracle");464 require(block.timestamp >= oracleChangeTimestamp, "timelock not expired");465 address oldOracle = address(oracle);466 oracle = ICPIOracle(pendingOracle);467 pendingOracle = address(0);468 oracleChangeTimestamp = 0;469 emit OracleChanged(oldOracle, address(oracle));470 }471472 function cancelOracleChange() external onlyGuardian {473 require(pendingOracle != address(0), "no pending oracle");474 address cancelled = pendingOracle;475 pendingOracle = address(0);476 oracleChangeTimestamp = 0;477 emit OracleChangeCancelled(cancelled);478 }479480 // ============ CATEGORY 2: STRUCTURAL FUNCTIONS (never expire) ============481482 function transferBattery(address _to) external onlyOwner {483 require(_to != address(0), "invalid address");484 uint256 lpBalance = pair.balanceOf(address(this));485 require(lpBalance > 0, "no battery");486 IERC20(address(pair)).transfer(_to, lpBalance);487 emit BatteryTransferred(address(this), _to, lpBalance);488 }489490 function transferInventory(address _to, uint256 _amount) external onlyOwner {491 require(_to != address(0), "invalid address");492 FLAT.transfer(_to, _amount);493 emit InventoryTransferred(address(this), _to, _amount);494 }495496 // ============ CATEGORY 1: GUARDIAN FUNCTIONS (expire after 3 years) ============497498 function setPaused(bool _paused) external onlyGuardian {499 paused = _paused;500 if (_paused) emit Paused();501 else emit Unpaused();502 }503504 function sweepETH() external onlyGuardian {505 uint256 balance = address(this).balance;506 require(balance > 0, "no ETH");507 (bool success, ) = payable(owner()).call{value: balance}("");508 require(success, "sweep failed");509 }510511 // ============ RECEIVE ============512 receive() external payable {}513}
Tier 2 — Guardian Pattern (3-year expiry)
Composable public sale contract (v2). Users send ETH and receive FLAT at the CPI oracle price with slippage protection (minFlatOut). Designed for maximum DeFi integration: buy(), buyTo(recipient), buyToWithCallback(), buyWithToken(ERC-20), buyWithTokenPermit(gasless), buyBatch(up to 100 recipients). Referral tracking via events. EIP-165 interface discoverability. 10% ETH goes to treasury, 90% is paired with FLAT as Uniswap LP (85% treasury, 5% FlatEngine battery). CROPS-compliant Guardian pattern with 3-year expiry. Daily cap enforced. Contract version(): FlatSale_v2.
Owner: Gnosis Safe (0x781f...C714) · Address: 0x7d8F134eD8dEC8fEb0a99a3Acd4701EfC96eec9d
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.24;34import "@openzeppelin/contracts/access/Ownable.sol";5import "@openzeppelin/contracts/token/ERC20/IERC20.sol";6import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";7import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";8import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";9import "@openzeppelin/contracts/utils/introspection/ERC165.sol";10import "./interfaces/ICPIOracle.sol";11import "./interfaces/IAggregatorV3.sol";12import "./interfaces/IUniswapV2Router02.sol";13import "./interfaces/IUniswapV2Pair.sol";1415/// @title IFlatReceiver16/// @notice Callback interface for contracts receiving FLAT from FlatSale V2.17/// Implementing contracts can react to incoming FLAT (auto-stake, auto-rebalance, etc.)18interface IFlatReceiver {19 /// @notice Called on the recipient after FLAT is transferred via buyTo().20 /// @param buyer The original buyer who paid ETH21 /// @param amount The amount of FLAT received22 /// @param data Arbitrary data passed by the buyer (referral codes, routing instructions, etc.)23 /// @return selector Must return IFlatReceiver.onFlatReceived.selector to confirm handling24 function onFlatReceived(25 address buyer,26 uint256 amount,27 bytes calldata data28 ) external returns (bytes4);29}3031/// @title IFlatSaleV232/// @notice Interface for FlatSale V2 — used for EIP-165 discoverability33interface IFlatSaleV2 {34 function buy(uint256 minFlatOut) external payable;35 function buyTo(address recipient, uint256 minFlatOut) external payable;36 function buyToWithCallback(address recipient, uint256 minFlatOut, bytes calldata data) external payable;37 function buyWithToken(address token, uint256 tokenAmount, uint256 minFlatOut, address recipient) external;38 function buyWithTokenPermit(39 address token, uint256 tokenAmount, uint256 minFlatOut, address recipient,40 uint256 deadline, uint8 v, bytes32 r, bytes32 s41 ) external;42 function buyBatch(address[] calldata recipients, uint256[] calldata ethAmounts, uint256 minFlatOutTotal) external payable;43}4445/// @title FlatSale V246/// @notice Composable public sale contract for FLAT coin. Designed for maximum DeFi integration.47///48/// CHANGES FROM V1:49/// ✅ Removed `require(msg.sender == tx.origin)` — contracts can now call buy()50/// ✅ Added `buyTo(recipient)` — buy FLAT and send to any address in one tx51/// ✅ Added `buyToWithCallback(recipient, data)` — notify recipient contracts (ERC-1363 pattern)52/// ✅ Added `buyWithToken(token, amount)` — buy FLAT with any ERC-20 (auto-swaps to ETH)53/// ✅ Added `buyWithTokenPermit()` — gasless token approval + buy in one tx54/// ✅ Added `buyBatch()` — institutional distribution (payroll, rewards, airdrops)55/// ✅ Added `receive()` fallback — raw ETH sends auto-buy FLAT for sender56/// ✅ Added `minFlatOut` slippage parameter — proper sandwich protection57/// ✅ Added referral tracking via `referralCode` in events58/// ✅ Richer events (recipient, oracle prices, referral)59/// ✅ EIP-165 interface discoverability60///61/// UNCHANGED FROM V1:62/// - CPI oracle pricing (USD per FLAT → ETH per FLAT via Chainlink)63/// - 10% ETH → treasury, 90% ETH + FLAT → LP (85% treasury, 5% FlatEngine)64/// - Daily cap, price band, max per tx65/// - CROPS-compliant Guardian pattern (Category 1 expires after 3 years)66/// - Immutable after deployment (no proxy, no upgradability)67///68/// @dev North Star: FLAT as global reserve currency. Every function is an API69/// that the world's financial system can call. Maximize composability.70contract FlatSaleV2 is Ownable, ReentrancyGuard, ERC165 {71 using SafeERC20 for IERC20;7273 // ============ IMMUTABLES ============74 IERC20 public immutable FLAT;75 IUniswapV2Router02 public immutable router;76 IUniswapV2Pair public immutable pair;77 ICPIOracle public immutable oracle;78 IAggregatorV3 public immutable ethUsdFeed;79 address public immutable treasury;80 address public immutable WETH;81 bool public immutable flatIsToken0;82 uint256 public immutable guardianExpiry;83 uint256 public immutable feedDecimals;8485 // ============ CONSTANTS ============86 uint256 public constant SLIPPAGE_BPS = 200; // 2% slippage tolerance for LP87 uint256 public constant CPI_STALENESS_THRESHOLD = 172800; // 48 hours88 uint256 public constant ETH_USD_STALENESS_THRESHOLD = 7200; // 2 hours89 uint256 public constant GUARDIAN_DURATION = 1095 days; // 3 years90 uint256 public constant MAX_BATCH_SIZE = 100; // Max recipients per batch9192 // ============ MUTABLE STATE ============93 address public flatEngine;94 bool public paused;95 uint256 public dailyCap;96 uint256 public maxBuyPerTx;97 uint256 public minPriceUSD;98 uint256 public maxPriceUSD;99 uint256 public soldToday;100 uint256 public totalSold;101 uint256 public totalPOL;102 uint256 public lastReset;103104 // ============ EVENTS ============105 event Bought(106 address indexed buyer,107 address indexed recipient,108 uint256 flatAmount,109 uint256 ethPaid,110 uint256 polEth,111 uint256 engineLP,112 uint256 treasuryLP,113 uint256 oraclePriceETH,114 uint256 oraclePriceUSD,115 bytes32 indexed referralCode116 );117 event BatchBought(118 address indexed buyer,119 uint256 totalFlat,120 uint256 totalEthPaid,121 uint256 recipientCount,122 bytes32 indexed referralCode123 );124 event TokenSwapped(125 address indexed token,126 uint256 tokenAmount,127 uint256 ethReceived128 );129 event DailyCapReset(uint256 timestamp);130 event PriceBandUpdated(uint256 minPriceUSD, uint256 maxPriceUSD);131 event DailyCapUpdated(uint256 newCap);132 event MaxBuyPerTxUpdated(uint256 newMax);133 event FlatEngineUpdated(address indexed oldEngine, address indexed newEngine);134 event Paused();135 event Unpaused();136137 // ============ CONSTRUCTOR ============138 constructor(139 address _flat,140 address _router,141 address _pair,142 address _treasury,143 address _flatEngine,144 address _oracle,145 address _ethUsdFeed,146 uint256 _dailyCap,147 uint256 _minPriceUSD,148 uint256 _maxPriceUSD149 ) Ownable(msg.sender) {150 require(_flat != address(0), "invalid flat");151 require(_router != address(0), "invalid router");152 require(_pair != address(0), "invalid pair");153 require(_treasury != address(0), "invalid treasury");154 require(_flatEngine != address(0), "invalid engine");155 require(_oracle != address(0), "invalid oracle");156 require(_ethUsdFeed != address(0), "invalid ethUsdFeed");157 require(_minPriceUSD > 0 && _maxPriceUSD > _minPriceUSD, "invalid price band");158159 FLAT = IERC20(_flat);160 router = IUniswapV2Router02(_router);161 pair = IUniswapV2Pair(_pair);162 oracle = ICPIOracle(_oracle);163 ethUsdFeed = IAggregatorV3(_ethUsdFeed);164 treasury = _treasury;165 flatEngine = _flatEngine;166 WETH = router.WETH();167168 flatIsToken0 = pair.token0() == _flat;169 feedDecimals = uint256(ethUsdFeed.decimals());170171 dailyCap = _dailyCap;172 minPriceUSD = _minPriceUSD;173 maxPriceUSD = _maxPriceUSD;174 lastReset = block.timestamp;175 maxBuyPerTx = 0;176177 guardianExpiry = block.timestamp + GUARDIAN_DURATION;178 }179180 // ============ MODIFIERS ============181 modifier whenNotPaused() {182 require(!paused, "paused");183 _;184 }185186 modifier onlyGuardian() {187 require(msg.sender == owner(), "not owner");188 require(block.timestamp <= guardianExpiry, "guardian expired");189 _;190 }191192 // ============ EIP-165 INTERFACE DETECTION ============193194 /// @notice Returns true if this contract implements the given interface.195 /// Supports IFlatSaleV2 for protocol discoverability.196 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {197 return198 interfaceId == type(IFlatSaleV2).interfaceId ||199 super.supportsInterface(interfaceId);200 }201202 // ============ INTERNAL HELPERS ============203204 /// @dev Reads both oracles, validates staleness, returns ETH per FLAT (18 decimals).205 function _getEthPerFlat() internal view returns (uint256 ethPerFlat, uint256 cpiUsdPrice) {206 cpiUsdPrice = oracle.getInterpolatedPrice();207 require(cpiUsdPrice > 0, "invalid CPI price");208 require(209 block.timestamp - oracle.lastUpdateTimestamp() <= CPI_STALENESS_THRESHOLD,210 "CPI oracle stale"211 );212 (, int256 ethUsdAnswer, , uint256 ethUsdUpdatedAt, ) = ethUsdFeed.latestRoundData();213 require(ethUsdAnswer > 0, "invalid ETH/USD price");214 require(215 block.timestamp - ethUsdUpdatedAt <= ETH_USD_STALENESS_THRESHOLD,216 "ETH/USD feed stale"217 );218 ethPerFlat = (cpiUsdPrice * (10 ** feedDecimals)) / uint256(ethUsdAnswer);219 }220221 /// @dev Core buy logic. Handles pricing, inventory, LP creation, and distribution.222 /// Returns the amount of FLAT purchased.223 function _executeBuy(224 address recipient,225 uint256 ethAmount,226 uint256 minFlatOut,227 bytes32 referralCode228 ) internal returns (uint256 flatAmount) {229 require(ethAmount >= 0.001 ether, "min 0.001 ETH");230 require(recipient != address(0), "zero recipient");231232 // Daily cap reset233 if (block.timestamp >= lastReset + 1 days) {234 soldToday = 0;235 lastReset = block.timestamp;236 emit DailyCapReset(block.timestamp);237 }238239 // Price determination240 (uint256 oraclePrice, uint256 cpiUsdPrice) = _getEthPerFlat();241 require(cpiUsdPrice >= minPriceUSD && cpiUsdPrice <= maxPriceUSD, "price outside band");242243 // Calculate FLAT amount244 flatAmount = (ethAmount * 1e18) / oraclePrice;245246 // Slippage protection (the REAL sandwich defense, not tx.origin)247 require(flatAmount >= minFlatOut, "slippage: insufficient output");248249 // Per-tx limit250 if (maxBuyPerTx > 0) {251 require(flatAmount <= maxBuyPerTx, "exceeds max per tx");252 }253254 // Daily cap255 require(soldToday + flatAmount <= dailyCap, "daily cap exceeded");256257 // ETH split: 10% operations, 90% LP258 uint256 treasuryEth = ethAmount / 10;259 uint256 polEth = ethAmount - treasuryEth;260261 // FLAT needed for LP262 uint256 polFlat = (polEth * 1e18) / oraclePrice;263264 // Inventory check265 uint256 totalFlatNeeded = flatAmount + polFlat;266 require(FLAT.balanceOf(address(this)) >= totalFlatNeeded, "refill needed");267268 // Update counters269 soldToday += flatAmount;270 totalSold += flatAmount;271 totalPOL += polEth;272273 // Transfer FLAT to recipient274 FLAT.safeTransfer(recipient, flatAmount);275276 // Create LP tokens277 FLAT.safeIncreaseAllowance(address(router), polFlat);278 (, , uint256 liquidity) = router.addLiquidityETH{value: polEth}(279 address(FLAT),280 polFlat,281 polFlat * (10000 - SLIPPAGE_BPS) / 10000,282 polEth * (10000 - SLIPPAGE_BPS) / 10000,283 address(this),284 block.timestamp285 );286287 // Distribute LP tokens: 5% FlatEngine, 95% treasury288 uint256 engineLP = liquidity * 5 / 100;289 uint256 treasuryLP = liquidity - engineLP;290 IERC20(address(pair)).safeTransfer(flatEngine, engineLP);291 IERC20(address(pair)).safeTransfer(treasury, treasuryLP);292293 // Send operations ETH to treasury294 (bool success, ) = payable(treasury).call{value: treasuryEth}("");295 require(success, "treasury transfer failed");296297 // Emit rich event298 emit Bought(299 msg.sender,300 recipient,301 flatAmount,302 ethAmount,303 polEth,304 engineLP,305 treasuryLP,306 oraclePrice,307 cpiUsdPrice,308 referralCode309 );310 }311312 // ============ PUBLIC BUY FUNCTIONS ============313314 /// @notice Buy FLAT with ETH. Tokens sent to msg.sender.315 /// Compatible with V1 callers (same function name, added slippage param).316 /// @param minFlatOut Minimum FLAT to receive (sandwich protection). Use 0 for no limit.317 function buy(uint256 minFlatOut) external payable nonReentrant whenNotPaused {318 _executeBuy(msg.sender, msg.value, minFlatOut, bytes32(0));319 }320321 /// @notice Buy FLAT and send to a specified recipient.322 /// Enables: router contracts, deposit vaults, gifting, payroll.323 /// @param recipient Address to receive the FLAT tokens324 /// @param minFlatOut Minimum FLAT to receive (sandwich protection)325 function buyTo(326 address recipient,327 uint256 minFlatOut328 ) external payable nonReentrant whenNotPaused {329 _executeBuy(recipient, msg.value, minFlatOut, bytes32(0));330 }331332 /// @notice Buy FLAT, send to recipient, and trigger callback if recipient is a contract.333 /// Enables: auto-staking vaults, DAO treasuries, automated strategies.334 /// @param recipient Address to receive the FLAT tokens (may be a contract)335 /// @param minFlatOut Minimum FLAT to receive (sandwich protection)336 /// @param data Arbitrary bytes passed to recipient's onFlatReceived callback337 function buyToWithCallback(338 address recipient,339 uint256 minFlatOut,340 bytes calldata data341 ) external payable nonReentrant whenNotPaused {342 uint256 flatAmount = _executeBuy(recipient, msg.value, minFlatOut, bytes32(0));343344 // If recipient is a contract, notify it345 if (recipient.code.length > 0) {346 bytes4 retval = IFlatReceiver(recipient).onFlatReceived(msg.sender, flatAmount, data);347 require(retval == IFlatReceiver.onFlatReceived.selector, "callback rejected");348 }349 }350351 /// @notice Buy FLAT with ETH and track a referral code.352 /// Enables: affiliate programs, growth partnerships, distribution tracking.353 /// @param recipient Address to receive the FLAT tokens354 /// @param minFlatOut Minimum FLAT to receive (sandwich protection)355 /// @param referralCode 32-byte referral identifier (hashed partner ID, campaign code, etc.)356 function buyToWithReferral(357 address recipient,358 uint256 minFlatOut,359 bytes32 referralCode360 ) external payable nonReentrant whenNotPaused {361 _executeBuy(recipient, msg.value, minFlatOut, referralCode);362 }363364 /// @notice Buy FLAT with any ERC-20 token. Token is swapped to ETH via Uniswap, then used to buy FLAT.365 /// Enables: USDC/USDT/DAI/WETH holders to buy FLAT without manually swapping first.366 /// @param token The ERC-20 token to sell (must have a WETH pair on Uniswap)367 /// @param tokenAmount Amount of token to spend368 /// @param minFlatOut Minimum FLAT to receive (covers both swap slippage + buy slippage)369 /// @param recipient Address to receive the FLAT tokens370 function buyWithToken(371 address token,372 uint256 tokenAmount,373 uint256 minFlatOut,374 address recipient375 ) external nonReentrant whenNotPaused {376 require(token != address(0), "invalid token");377 require(tokenAmount > 0, "zero amount");378 require(token != address(FLAT), "use buy() for ETH");379380 // Pull tokens from caller381 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);382383 // Swap token → ETH via Uniswap384 uint256 ethBefore = address(this).balance;385 IERC20(token).safeIncreaseAllowance(address(router), tokenAmount);386 address[] memory path = new address[](2);387 path[0] = token;388 path[1] = WETH;389 router.swapExactTokensForETH(390 tokenAmount,391 0, // minOut handled by minFlatOut at the end392 path,393 address(this),394 block.timestamp395 );396 uint256 ethReceived = address(this).balance - ethBefore;397 require(ethReceived > 0, "swap returned zero ETH");398399 emit TokenSwapped(token, tokenAmount, ethReceived);400401 // Execute buy with the received ETH402 _executeBuy(recipient, ethReceived, minFlatOut, bytes32(0));403 }404405 /// @notice Buy FLAT with any ERC-20 token using ERC-2612 Permit (gasless approval).406 /// Single transaction: approve + swap + buy. No prior approve() needed.407 /// @param token The ERC-20 token to sell (must support ERC-2612 permit)408 /// @param tokenAmount Amount of token to spend409 /// @param minFlatOut Minimum FLAT to receive410 /// @param recipient Address to receive the FLAT tokens411 /// @param deadline Permit deadline412 /// @param v Permit signature v413 /// @param r Permit signature r414 /// @param s Permit signature s415 function buyWithTokenPermit(416 address token,417 uint256 tokenAmount,418 uint256 minFlatOut,419 address recipient,420 uint256 deadline,421 uint8 v,422 bytes32 r,423 bytes32 s424 ) external nonReentrant whenNotPaused {425 // Execute permit (gasless approval)426 IERC20Permit(token).permit(msg.sender, address(this), tokenAmount, deadline, v, r, s);427428 // Pull tokens429 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);430431 // Swap token → ETH432 uint256 ethBefore = address(this).balance;433 IERC20(token).safeIncreaseAllowance(address(router), tokenAmount);434 address[] memory path = new address[](2);435 path[0] = token;436 path[1] = WETH;437 router.swapExactTokensForETH(438 tokenAmount,439 0,440 path,441 address(this),442 block.timestamp443 );444 uint256 ethReceived = address(this).balance - ethBefore;445 require(ethReceived > 0, "swap returned zero ETH");446447 emit TokenSwapped(token, tokenAmount, ethReceived);448449 // Execute buy450 _executeBuy(recipient, ethReceived, minFlatOut, bytes32(0));451 }452453 /// @notice Buy FLAT for multiple recipients in one transaction.454 /// Enables: payroll, rewards distribution, airdrops, DAO proposals.455 /// @param recipients Array of addresses to receive FLAT456 /// @param ethAmounts Array of ETH amounts to spend per recipient (must sum to msg.value)457 /// @param minFlatOutTotal Minimum total FLAT across all recipients (aggregate slippage protection)458 function buyBatch(459 address[] calldata recipients,460 uint256[] calldata ethAmounts,461 uint256 minFlatOutTotal462 ) external payable nonReentrant whenNotPaused {463 require(recipients.length == ethAmounts.length, "length mismatch");464 require(recipients.length > 0 && recipients.length <= MAX_BATCH_SIZE, "invalid batch size");465466 // Verify ETH amounts sum to msg.value467 uint256 totalEth;468 for (uint256 i = 0; i < ethAmounts.length; i++) {469 totalEth += ethAmounts[i];470 }471 require(totalEth == msg.value, "ETH sum mismatch");472473 // Execute individual buys474 uint256 totalFlat;475 for (uint256 i = 0; i < recipients.length; i++) {476 uint256 flatOut = _executeBuy(recipients[i], ethAmounts[i], 0, bytes32(0));477 totalFlat += flatOut;478 }479480 // Aggregate slippage check481 require(totalFlat >= minFlatOutTotal, "batch slippage: insufficient total output");482483 emit BatchBought(msg.sender, totalFlat, msg.value, recipients.length, bytes32(0));484 }485486 /// @notice Buy FLAT for multiple recipients with referral tracking.487 function buyBatchWithReferral(488 address[] calldata recipients,489 uint256[] calldata ethAmounts,490 uint256 minFlatOutTotal,491 bytes32 referralCode492 ) external payable nonReentrant whenNotPaused {493 require(recipients.length == ethAmounts.length, "length mismatch");494 require(recipients.length > 0 && recipients.length <= MAX_BATCH_SIZE, "invalid batch size");495496 uint256 totalEth;497 for (uint256 i = 0; i < ethAmounts.length; i++) {498 totalEth += ethAmounts[i];499 }500 require(totalEth == msg.value, "ETH sum mismatch");501502 uint256 totalFlat;503 for (uint256 i = 0; i < recipients.length; i++) {504 uint256 flatOut = _executeBuy(recipients[i], ethAmounts[i], 0, referralCode);505 totalFlat += flatOut;506 }507508 require(totalFlat >= minFlatOutTotal, "batch slippage: insufficient total output");509510 emit BatchBought(msg.sender, totalFlat, msg.value, recipients.length, referralCode);511 }512513 // ============ RECEIVE: RAW ETH → AUTO-BUY ============514515 /// @notice Accept raw ETH transfers and automatically buy FLAT for the sender.516 /// Enables: simple "send ETH to this address, get FLAT" flow.517 /// Works from any wallet, any exchange withdrawal, any contract.518 /// No slippage protection (use buy() for that). No reentrancy guard on receive.519 /// @dev Uses a separate internal path to avoid reentrancy issues with receive().520 /// The nonReentrant modifier cannot be used on receive() because it's called521 /// during LP creation (router refunds). We use a dedicated flag instead.522 receive() external payable {523 // Skip if called by the router during LP creation (refund ETH)524 if (msg.sender == address(router)) return;525 // Skip if called by treasury (receiving ETH back)526 if (msg.sender == treasury) return;527 // Skip tiny amounts (dust from router refunds)528 if (msg.value < 0.001 ether) return;529530 // For raw ETH sends, execute buy with no slippage protection531 // Users who want slippage protection should call buy() directly532 // This is intentional: the simplicity of "just send ETH" is the feature533 if (!paused) {534 _executeBuy(msg.sender, msg.value, 0, bytes32(0));535 }536 }537538 // ============ VIEW FUNCTIONS ============539540 /// @notice Returns the current target price in ETH per FLAT (18 decimals).541 function getOraclePrice() public view returns (uint256) {542 (uint256 ethPerFlat, ) = _getEthPerFlat();543 return ethPerFlat;544 }545546 /// @notice Returns the current CPI target price in USD per FLAT (18 decimals).547 function getOraclePriceUSD() public view returns (uint256) {548 return oracle.getInterpolatedPrice();549 }550551 /// @notice Returns the current ETH/USD price from Chainlink, normalized to 18 decimals.552 function getETHUSDPrice() public view returns (uint256) {553 (, int256 answer, , , ) = ethUsdFeed.latestRoundData();554 require(answer > 0, "invalid ETH/USD");555 return uint256(answer) * (10 ** (18 - feedDecimals));556 }557558 /// @notice Returns the current Uniswap V2 spot price (ETH per FLAT, 18 decimals).559 function getMarketPrice() public view returns (uint256) {560 (uint112 reserve0, uint112 reserve1,) = pair.getReserves();561 require(reserve0 > 0 && reserve1 > 0, "empty pool");562 if (flatIsToken0) {563 return (uint256(reserve1) * 1e18) / uint256(reserve0);564 } else {565 return (uint256(reserve0) * 1e18) / uint256(reserve1);566 }567 }568569 /// @notice Returns the FLAT balance held by this contract (available for sale + LP).570 function availableInventory() external view returns (uint256) {571 return FLAT.balanceOf(address(this));572 }573574 /// @notice Estimate FLAT output for a given ETH input at current oracle price.575 /// @param ethAmount The ETH amount to quote576 /// @return flatAmount The estimated FLAT output (before daily cap check)577 function quote(uint256 ethAmount) external view returns (uint256 flatAmount) {578 (uint256 oraclePrice, ) = _getEthPerFlat();579 flatAmount = (ethAmount * 1e18) / oraclePrice;580 }581582 /// @notice Returns remaining daily capacity.583 function remainingDailyCap() external view returns (uint256) {584 if (block.timestamp >= lastReset + 1 days) {585 return dailyCap; // Would reset on next buy586 }587 if (soldToday >= dailyCap) return 0;588 return dailyCap - soldToday;589 }590591 function version() external pure returns (string memory) {592 return "FlatSale_v2";593 }594595 // ============ CATEGORY 2: STRUCTURAL FUNCTIONS (never expire) ============596597 /// @notice Update FlatEngine address when upgrading to a new version.598 function setFlatEngine(address _newEngine) external onlyOwner {599 require(_newEngine != address(0), "invalid engine");600 address oldEngine = flatEngine;601 flatEngine = _newEngine;602 emit FlatEngineUpdated(oldEngine, _newEngine);603 }604605 // ============ CATEGORY 1: GUARDIAN FUNCTIONS (expire after 3 years) ============606607 function setPaused(bool _paused) external onlyGuardian {608 paused = _paused;609 if (_paused) emit Paused();610 else emit Unpaused();611 }612613 function setPriceBand(uint256 _minPriceUSD, uint256 _maxPriceUSD) external onlyGuardian {614 require(_minPriceUSD > 0 && _maxPriceUSD > _minPriceUSD, "invalid band");615 minPriceUSD = _minPriceUSD;616 maxPriceUSD = _maxPriceUSD;617 emit PriceBandUpdated(_minPriceUSD, _maxPriceUSD);618 }619620 function setDailyCap(uint256 _newCap) external onlyGuardian {621 dailyCap = _newCap;622 emit DailyCapUpdated(_newCap);623 }624625 function setMaxBuyPerTx(uint256 _max) external onlyGuardian {626 maxBuyPerTx = _max;627 emit MaxBuyPerTxUpdated(_max);628 }629630 function sweepETH() external onlyGuardian {631 uint256 balance = address(this).balance;632 require(balance > 0, "no ETH");633 (bool success, ) = payable(treasury).call{value: balance}("");634 require(success, "sweep failed");635 }636637 function sweepFLAT(uint256 _amount) external onlyGuardian {638 FLAT.safeTransfer(treasury, _amount);639 }640641 /// @notice Sweep any ERC-20 token accidentally sent to this contract.642 function sweepToken(address token, uint256 amount) external onlyGuardian {643 require(token != address(FLAT), "use sweepFLAT");644 IERC20(token).safeTransfer(treasury, amount);645 }646}
Tier 1 — Fully Immutable
The base token of the FLAT Protocol (RISE/SAVE system). Fixed supply of 425,000,000 RISE minted at deployment. Burnable by holders. No mint function, no owner, no admin, no pause. Fully immutable ERC-20 + EIP-2612.
Owner: None (no Ownable) · Address: 0xc1E141863414f434E46162A1184345E45CF5a14A
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.24;34import "@openzeppelin/contracts/token/ERC20/ERC20.sol";5import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";6import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";78/**9 * @title RISE Token10 * @notice The base token of the FLAT Protocol.11 *12 * Key properties:13 * - Fixed supply of 425,000,000 RISE minted at deployment.14 * - No mint function exists — supply can never increase.15 * - Burnable — any holder can burn their own tokens.16 * - ERC-2612 Permit — supports gasless approvals.17 * - No owner, no admin, no pause — fully immutable after deployment.18 *19 * @dev Inherits from OpenZeppelin v5 contracts:20 * - ERC20: Core token logic21 * - ERC20Burnable: Self-burn capability22 * - ERC20Permit: EIP-2612 gasless approvals23 */24contract RISE is ERC20, ERC20Burnable, ERC20Permit {25 uint256 public constant TOTAL_SUPPLY = 425_000_000 * 10 ** 18;2627 /**28 * @notice Deploys the RISE token and mints the entire fixed supply29 * to the deployer's address.30 */31 constructor( ) ERC20("RISE", "RISE") ERC20Permit("RISE") {32 _mint(msg.sender, TOTAL_SUPPLY);33 }34}
Tier 1 — Fully Immutable
Irreversible lock vault for RISE tokens. Users deposit RISE and receive SAVE 1:1. RISE locked in the vault can never be withdrawn — this is the absorption mechanism that drives the Singularity Equation. Reports real-time alpha (absorption ratio).
Owner: None (no Ownable) · Address: 0x9f0DD6e940478293964aE778e4C720B720cf9cAe
1// SPDX-License-Identifier: MIT2pragma solidity 0.8.26;34import "@openzeppelin/contracts/token/ERC20/ERC20.sol";5import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";67contract SAVE is ERC20 {8 using SafeERC20 for IERC20;910 IERC20 public immutable RISE;1112 event Deposited(address indexed depositor, uint256 amount);1314 constructor(address _rise) ERC20("SAVE", "SAVE") {15 require(_rise != address(0), "RISE address cannot be zero");16 RISE = IERC20(_rise);17 }1819 function deposit(uint256 amount) external {20 require(amount > 0, "Amount must be greater than zero");21 RISE.safeTransferFrom(msg.sender, address(this), amount);22 _mint(msg.sender, amount);23 emit Deposited(msg.sender, amount);24 }2526 function riseLockedInVault() external view returns (uint256) {27 return RISE.balanceOf(address(this));28 }2930 function alpha() external view returns (uint256) {31 uint256 locked = RISE.balanceOf(address(this));32 uint256 total = RISE.totalSupply();33 if (total == 0) return 0;34 return (locked * 1e18) / total;35 }36}
Tier 2 — Guardian Pattern (3-year expiry)
Composable public sale contract for SAVE tokens (v2). Users send ETH and receive SAVE at exact NAV (1.0x, no premium). Designed for maximum DeFi integration: buy(), buyTo(recipient), buyToWithCallback(), buyWithToken(ERC-20), buyWithTokenPermit(gasless), buyBatch(up to 100 recipients). Referral tracking via events. EIP-165 interface discoverability. 90% of ETH + matching SAVE goes to permanent protocol-owned liquidity (POL) in Uniswap V2. 10% ETH goes to operations. LP tokens sent to treasury (Gnosis Safe). CROPS-compliant Guardian pattern with 3-year expiry. Contract version(): SaveSale_v2.
Owner: Gnosis Safe (0x781f...C714) · Address: 0x2840A84Bb26FdECBB4767C526cc53DF21cB7cd02
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.24;34import "@openzeppelin/contracts/access/Ownable.sol";5import "@openzeppelin/contracts/token/ERC20/IERC20.sol";6import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";7import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";8import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";9import "@openzeppelin/contracts/utils/introspection/ERC165.sol";1011interface IUniswapV2Pair {12 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);13 function token0() external view returns (address);14}1516interface IUniswapV2Router02 {17 function addLiquidityETH(18 address token,19 uint amountTokenDesired,20 uint amountTokenMin,21 uint amountETHMin,22 address to,23 uint deadline24 ) external payable returns (uint amountToken, uint amountETH, uint liquidity);25 function swapExactTokensForETH(26 uint amountIn,27 uint amountOutMin,28 address[] calldata path,29 address to,30 uint deadline31 ) external returns (uint[] memory amounts);32 function WETH() external pure returns (address);33}3435/// @title ISaveReceiver36/// @notice Callback interface for contracts receiving SAVE from SaveSale V2.37interface ISaveReceiver {38 /// @notice Called on the recipient after SAVE is transferred via buyTo().39 /// @param buyer The original buyer who paid ETH40 /// @param amount The amount of SAVE received41 /// @param data Arbitrary data passed by the buyer42 /// @return selector Must return ISaveReceiver.onSaveReceived.selector to confirm handling43 function onSaveReceived(44 address buyer,45 uint256 amount,46 bytes calldata data47 ) external returns (bytes4);48}4950/// @title ISaveSaleV251/// @notice Interface for SaveSale V2 — used for EIP-165 discoverability52interface ISaveSaleV2 {53 function buy(uint256 minSaveOut) external payable;54 function buyTo(address recipient, uint256 minSaveOut) external payable;55 function buyToWithCallback(address recipient, uint256 minSaveOut, bytes calldata data) external payable;56 function buyWithToken(address token, uint256 tokenAmount, uint256 minSaveOut, address recipient) external;57 function buyWithTokenPermit(58 address token, uint256 tokenAmount, uint256 minSaveOut, address recipient,59 uint256 deadline, uint8 v, bytes32 r, bytes32 s60 ) external;61 function buyBatch(address[] calldata recipients, uint256[] calldata ethAmounts, uint256 minSaveOutTotal) external payable;62}6364/// @title SaveSale V265/// @notice Composable public sale contract for SAVE token. Designed for maximum DeFi integration.66///67/// CHANGES FROM V4 (previous version):68/// ✅ Removed `require(msg.sender == tx.origin)` — contracts can now call buy()69/// ✅ Added `buyTo(recipient)` — buy SAVE and send to any address in one tx70/// ✅ Added `buyToWithCallback(recipient, data)` — notify recipient contracts71/// ✅ Added `buyWithToken(token, amount)` — buy SAVE with any ERC-2072/// ✅ Added `buyWithTokenPermit()` — gasless token approval + buy in one tx73/// ✅ Added `buyBatch()` — institutional distribution74/// ✅ Added `receive()` fallback — raw ETH sends auto-buy SAVE for sender75/// ✅ Added referral tracking via events76/// ✅ Richer events (recipient, NAV price, referral)77/// ✅ EIP-165 interface discoverability78/// ✅ SafeERC20 throughout (V4 used raw .transfer)79/// ✅ CROPS-compliant Guardian pattern (Category 1 expires after 3 years)80/// ✅ Uses .call{value} instead of .transfer for ETH sends81///82/// UNCHANGED:83/// - NAV-based pricing from Uniswap V2 pool reserves84/// - 10% ETH → treasury operations, 90% ETH + SAVE → LP85/// - Daily cap, price band86/// - LP tokens → treasury87///88/// @dev North Star: FLAT as global reserve currency. SAVE is the locked, yield-bearing89/// form of FLAT. Making SAVE easy to acquire from any source accelerates absorption.90contract SaveSaleV2 is Ownable, ReentrancyGuard, ERC165 {91 using SafeERC20 for IERC20;9293 // ============ IMMUTABLES ============94 IERC20 public immutable SAVE;95 IUniswapV2Router02 public immutable router;96 IUniswapV2Pair public immutable pair;97 bool public immutable saveIsToken0;98 address public immutable treasury;99 address public immutable WETH;100 uint256 public immutable guardianExpiry;101102 // ============ CONSTANTS ============103 uint256 public constant SLIPPAGE_BPS = 200; // 2% max slippage on LP add104 uint256 public constant GUARDIAN_DURATION = 1095 days; // 3 years105 uint256 public constant MAX_BATCH_SIZE = 100; // Max recipients per batch106107 // ============ MUTABLE STATE ============108 uint256 public dailyCap;109 uint256 public soldToday;110 uint256 public lastReset;111 uint256 public minNAV;112 uint256 public maxNAV;113 uint256 public totalSold;114 uint256 public totalPOL;115 uint256 public maxBuyPerTx; // 0 = unlimited (new in V2)116 bool public paused;117118 // ============ EVENTS ============119 event Bought(120 address indexed buyer,121 address indexed recipient,122 uint256 saveAmount,123 uint256 ethPaid,124 uint256 polEth,125 uint256 nav,126 bytes32 indexed referralCode127 );128 event BatchBought(129 address indexed buyer,130 uint256 totalSave,131 uint256 totalEthPaid,132 uint256 recipientCount,133 bytes32 indexed referralCode134 );135 event TokenSwapped(136 address indexed token,137 uint256 tokenAmount,138 uint256 ethReceived139 );140 event DailyCapReset(uint256 timestamp);141 event PriceBandUpdated(uint256 minNAV, uint256 maxNAV);142 event DailyCapUpdated(uint256 newCap);143 event MaxBuyPerTxUpdated(uint256 newMax);144 event Paused();145 event Unpaused();146147 // ============ CONSTRUCTOR ============148 constructor(149 address _save,150 address _router,151 address _pair,152 address _treasury,153 uint256 _dailyCap,154 uint256 _minNAV,155 uint256 _maxNAV156 ) Ownable(msg.sender) {157 require(_save != address(0), "invalid save");158 require(_router != address(0), "invalid router");159 require(_pair != address(0), "invalid pair");160 require(_treasury != address(0), "invalid treasury");161 require(_minNAV > 0 && _maxNAV > _minNAV, "invalid price band");162163 SAVE = IERC20(_save);164 router = IUniswapV2Router02(_router);165 pair = IUniswapV2Pair(_pair);166 treasury = _treasury;167 WETH = router.WETH();168169 saveIsToken0 = (pair.token0() == _save);170171 dailyCap = _dailyCap;172 minNAV = _minNAV;173 maxNAV = _maxNAV;174 lastReset = block.timestamp;175 maxBuyPerTx = 0;176177 guardianExpiry = block.timestamp + GUARDIAN_DURATION;178 }179180 // ============ MODIFIERS ============181 modifier whenNotPaused() {182 require(!paused, "paused");183 _;184 }185186 modifier onlyGuardian() {187 require(msg.sender == owner(), "not owner");188 require(block.timestamp <= guardianExpiry, "guardian expired");189 _;190 }191192 // ============ EIP-165 ============193194 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {195 return196 interfaceId == type(ISaveSaleV2).interfaceId ||197 super.supportsInterface(interfaceId);198 }199200 // ============ INTERNAL HELPERS ============201202 /// @dev Get current NAV (ETH per SAVE) from V2 pool reserves203 function _getCurrentNAV() internal view returns (uint256 nav) {204 (uint112 reserve0, uint112 reserve1,) = pair.getReserves();205 require(reserve0 > 0 && reserve1 > 0, "empty pool");206 if (saveIsToken0) {207 nav = (uint256(reserve1) * 1e18) / uint256(reserve0);208 } else {209 nav = (uint256(reserve0) * 1e18) / uint256(reserve1);210 }211 }212213 /// @dev Core buy logic. Returns the amount of SAVE purchased.214 function _executeBuy(215 address recipient,216 uint256 ethAmount,217 uint256 minSaveOut,218 bytes32 referralCode219 ) internal returns (uint256 saveAmount) {220 require(ethAmount >= 0.001 ether, "min 0.001 ETH");221 require(recipient != address(0), "zero recipient");222223 // Daily cap reset224 if (block.timestamp >= lastReset + 1 days) {225 soldToday = 0;226 lastReset = block.timestamp;227 emit DailyCapReset(block.timestamp);228 }229230 // Get current NAV from pool231 uint256 nav = _getCurrentNAV();232 require(nav >= minNAV && nav <= maxNAV, "price outside band");233234 // Calculate SAVE amount at exact NAV (1.0x, no premium)235 saveAmount = (ethAmount * 1e18) / nav;236237 // Slippage protection238 require(saveAmount >= minSaveOut, "slippage: insufficient output");239240 // Per-tx limit241 if (maxBuyPerTx > 0) {242 require(saveAmount <= maxBuyPerTx, "exceeds max per tx");243 }244245 // Daily cap246 require(soldToday + saveAmount <= dailyCap, "daily cap exceeded");247248 // Revenue split: 10% operations, 90% LP249 uint256 treasuryEth = ethAmount / 10;250 uint256 polEth = ethAmount - treasuryEth;251252 // Matching SAVE for liquidity253 uint256 polSave = (polEth * 1e18) / nav;254255 // Inventory check256 uint256 totalSaveNeeded = saveAmount + polSave;257 require(SAVE.balanceOf(address(this)) >= totalSaveNeeded, "inventory too low");258259 // Update state260 soldToday += saveAmount;261 totalSold += saveAmount;262 totalPOL += polEth;263264 // Transfer SAVE to recipient265 SAVE.safeTransfer(recipient, saveAmount);266267 // Add liquidity: 90% ETH + matching SAVE → V2 pool268 uint256 amountTokenMin = polSave * (10000 - SLIPPAGE_BPS) / 10000;269 uint256 amountETHMin = polEth * (10000 - SLIPPAGE_BPS) / 10000;270 SAVE.safeIncreaseAllowance(address(router), polSave);271 router.addLiquidityETH{value: polEth}(272 address(SAVE),273 polSave,274 amountTokenMin,275 amountETHMin,276 treasury, // LP tokens go directly to treasury277 block.timestamp278 );279280 // Send 10% ETH to treasury281 (bool success, ) = payable(treasury).call{value: treasuryEth}("");282 require(success, "treasury transfer failed");283284 // Emit rich event285 emit Bought(286 msg.sender,287 recipient,288 saveAmount,289 ethAmount,290 polEth,291 nav,292 referralCode293 );294 }295296 // ============ PUBLIC BUY FUNCTIONS ============297298 /// @notice Buy SAVE with ETH at exact NAV. Tokens sent to msg.sender.299 /// @param minSaveOut Minimum SAVE to receive (sandwich protection)300 function buy(uint256 minSaveOut) external payable nonReentrant whenNotPaused {301 _executeBuy(msg.sender, msg.value, minSaveOut, bytes32(0));302 }303304 /// @notice Buy SAVE and send to a specified recipient.305 /// Enables: router contracts, FlatID vaults, gifting, payroll.306 /// @param recipient Address to receive the SAVE tokens307 /// @param minSaveOut Minimum SAVE to receive (sandwich protection)308 function buyTo(309 address recipient,310 uint256 minSaveOut311 ) external payable nonReentrant whenNotPaused {312 _executeBuy(recipient, msg.value, minSaveOut, bytes32(0));313 }314315 /// @notice Buy SAVE, send to recipient, and trigger callback if recipient is a contract.316 /// Enables: auto-compounding vaults, DAO treasuries, automated strategies.317 /// @param recipient Address to receive the SAVE tokens (may be a contract)318 /// @param minSaveOut Minimum SAVE to receive (sandwich protection)319 /// @param data Arbitrary bytes passed to recipient's onSaveReceived callback320 function buyToWithCallback(321 address recipient,322 uint256 minSaveOut,323 bytes calldata data324 ) external payable nonReentrant whenNotPaused {325 uint256 saveAmount = _executeBuy(recipient, msg.value, minSaveOut, bytes32(0));326327 // If recipient is a contract, notify it328 if (recipient.code.length > 0) {329 bytes4 retval = ISaveReceiver(recipient).onSaveReceived(msg.sender, saveAmount, data);330 require(retval == ISaveReceiver.onSaveReceived.selector, "callback rejected");331 }332 }333334 /// @notice Buy SAVE with referral tracking.335 /// @param recipient Address to receive the SAVE tokens336 /// @param minSaveOut Minimum SAVE to receive337 /// @param referralCode 32-byte referral identifier338 function buyToWithReferral(339 address recipient,340 uint256 minSaveOut,341 bytes32 referralCode342 ) external payable nonReentrant whenNotPaused {343 _executeBuy(recipient, msg.value, minSaveOut, referralCode);344 }345346 /// @notice Buy SAVE with any ERC-20 token. Token is swapped to ETH via Uniswap first.347 /// @param token The ERC-20 token to sell (must have a WETH pair on Uniswap)348 /// @param tokenAmount Amount of token to spend349 /// @param minSaveOut Minimum SAVE to receive (covers both swap + buy slippage)350 /// @param recipient Address to receive the SAVE tokens351 function buyWithToken(352 address token,353 uint256 tokenAmount,354 uint256 minSaveOut,355 address recipient356 ) external nonReentrant whenNotPaused {357 require(token != address(0), "invalid token");358 require(tokenAmount > 0, "zero amount");359 require(token != address(SAVE), "use buy() for ETH");360361 // Pull tokens from caller362 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);363364 // Swap token → ETH via Uniswap365 uint256 ethBefore = address(this).balance;366 IERC20(token).safeIncreaseAllowance(address(router), tokenAmount);367 address[] memory path = new address[](2);368 path[0] = token;369 path[1] = WETH;370 router.swapExactTokensForETH(371 tokenAmount,372 0, // minOut handled by minSaveOut at the end373 path,374 address(this),375 block.timestamp376 );377 uint256 ethReceived = address(this).balance - ethBefore;378 require(ethReceived > 0, "swap returned zero ETH");379380 emit TokenSwapped(token, tokenAmount, ethReceived);381382 // Execute buy with the received ETH383 _executeBuy(recipient, ethReceived, minSaveOut, bytes32(0));384 }385386 /// @notice Buy SAVE with any ERC-20 token using ERC-2612 Permit (gasless approval).387 function buyWithTokenPermit(388 address token,389 uint256 tokenAmount,390 uint256 minSaveOut,391 address recipient,392 uint256 deadline,393 uint8 v,394 bytes32 r,395 bytes32 s396 ) external nonReentrant whenNotPaused {397 // Execute permit (gasless approval)398 IERC20Permit(token).permit(msg.sender, address(this), tokenAmount, deadline, v, r, s);399400 // Pull tokens401 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);402403 // Swap token → ETH404 uint256 ethBefore = address(this).balance;405 IERC20(token).safeIncreaseAllowance(address(router), tokenAmount);406 address[] memory path = new address[](2);407 path[0] = token;408 path[1] = WETH;409 router.swapExactTokensForETH(410 tokenAmount,411 0,412 path,413 address(this),414 block.timestamp415 );416 uint256 ethReceived = address(this).balance - ethBefore;417 require(ethReceived > 0, "swap returned zero ETH");418419 emit TokenSwapped(token, tokenAmount, ethReceived);420421 // Execute buy422 _executeBuy(recipient, ethReceived, minSaveOut, bytes32(0));423 }424425 /// @notice Buy SAVE for multiple recipients in one transaction.426 /// Enables: payroll, rewards distribution, institutional allocation.427 /// @param recipients Array of addresses to receive SAVE428 /// @param ethAmounts Array of ETH amounts to spend per recipient (must sum to msg.value)429 /// @param minSaveOutTotal Minimum total SAVE across all recipients430 function buyBatch(431 address[] calldata recipients,432 uint256[] calldata ethAmounts,433 uint256 minSaveOutTotal434 ) external payable nonReentrant whenNotPaused {435 require(recipients.length == ethAmounts.length, "length mismatch");436 require(recipients.length > 0 && recipients.length <= MAX_BATCH_SIZE, "invalid batch size");437438 // Verify ETH amounts sum to msg.value439 uint256 totalEth;440 for (uint256 i = 0; i < ethAmounts.length; i++) {441 totalEth += ethAmounts[i];442 }443 require(totalEth == msg.value, "ETH sum mismatch");444445 // Execute individual buys446 uint256 totalSave;447 for (uint256 i = 0; i < recipients.length; i++) {448 uint256 saveOut = _executeBuy(recipients[i], ethAmounts[i], 0, bytes32(0));449 totalSave += saveOut;450 }451452 // Aggregate slippage check453 require(totalSave >= minSaveOutTotal, "batch slippage: insufficient total output");454455 emit BatchBought(msg.sender, totalSave, msg.value, recipients.length, bytes32(0));456 }457458 /// @notice Buy SAVE for multiple recipients with referral tracking.459 function buyBatchWithReferral(460 address[] calldata recipients,461 uint256[] calldata ethAmounts,462 uint256 minSaveOutTotal,463 bytes32 referralCode464 ) external payable nonReentrant whenNotPaused {465 require(recipients.length == ethAmounts.length, "length mismatch");466 require(recipients.length > 0 && recipients.length <= MAX_BATCH_SIZE, "invalid batch size");467468 uint256 totalEth;469 for (uint256 i = 0; i < ethAmounts.length; i++) {470 totalEth += ethAmounts[i];471 }472 require(totalEth == msg.value, "ETH sum mismatch");473474 uint256 totalSave;475 for (uint256 i = 0; i < recipients.length; i++) {476 uint256 saveOut = _executeBuy(recipients[i], ethAmounts[i], 0, referralCode);477 totalSave += saveOut;478 }479480 require(totalSave >= minSaveOutTotal, "batch slippage: insufficient total output");481482 emit BatchBought(msg.sender, totalSave, msg.value, recipients.length, referralCode);483 }484485 // ============ RECEIVE: RAW ETH → AUTO-BUY ============486487 /// @notice Accept raw ETH transfers and automatically buy SAVE for the sender.488 /// Enables: simple "send ETH to this address, get SAVE" flow.489 /// Works from any wallet, any exchange withdrawal, any contract.490 receive() external payable {491 // Skip if called by the router during LP creation (refund ETH)492 if (msg.sender == address(router)) return;493 // Skip if called by treasury494 if (msg.sender == treasury) return;495 // Skip tiny amounts (dust from router refunds)496 if (msg.value < 0.001 ether) return;497498 // Auto-buy with no slippage protection (use buy() for that)499 if (!paused) {500 _executeBuy(msg.sender, msg.value, 0, bytes32(0));501 }502 }503504 // ============ VIEW FUNCTIONS ============505506 /// @notice Get current NAV (ETH per SAVE) from V2 pool reserves507 function getCurrentNAV() public view returns (uint256) {508 return _getCurrentNAV();509 }510511 /// @notice Check how much SAVE the contract currently holds for sale512 function availableInventory() external view returns (uint256) {513 return SAVE.balanceOf(address(this));514 }515516 /// @notice Estimate SAVE output for a given ETH input at current NAV.517 /// @param ethAmount The ETH amount to quote518 /// @return saveAmount The estimated SAVE output (before daily cap check)519 function quote(uint256 ethAmount) external view returns (uint256 saveAmount) {520 uint256 nav = _getCurrentNAV();521 saveAmount = (ethAmount * 1e18) / nav;522 }523524 /// @notice Returns remaining daily capacity.525 function remainingDailyCap() external view returns (uint256) {526 if (block.timestamp >= lastReset + 1 days) {527 return dailyCap;528 }529 if (soldToday >= dailyCap) return 0;530 return dailyCap - soldToday;531 }532533 function version() external pure returns (string memory) {534 return "SaveSale_v2";535 }536537 // ============ CATEGORY 1: GUARDIAN FUNCTIONS (expire after 3 years) ============538539 function setPaused(bool _paused) external onlyGuardian {540 paused = _paused;541 if (_paused) emit Paused();542 else emit Unpaused();543 }544545 function setPriceBand(uint256 _minNAV, uint256 _maxNAV) external onlyGuardian {546 require(_minNAV > 0 && _maxNAV > _minNAV, "invalid band");547 minNAV = _minNAV;548 maxNAV = _maxNAV;549 emit PriceBandUpdated(_minNAV, _maxNAV);550 }551552 function setDailyCap(uint256 _newCap) external onlyGuardian {553 dailyCap = _newCap;554 emit DailyCapUpdated(_newCap);555 }556557 function setMaxBuyPerTx(uint256 _max) external onlyGuardian {558 maxBuyPerTx = _max;559 emit MaxBuyPerTxUpdated(_max);560 }561562 function sweepETH() external onlyGuardian {563 uint256 balance = address(this).balance;564 require(balance > 0, "no ETH");565 (bool success, ) = payable(treasury).call{value: balance}("");566 require(success, "sweep failed");567 }568569 function sweepSAVE(uint256 _amount) external onlyGuardian {570 SAVE.safeTransfer(treasury, _amount);571 }572573 /// @notice Sweep any ERC-20 token accidentally sent to this contract.574 function sweepToken(address token, uint256 amount) external onlyGuardian {575 require(token != address(SAVE), "use sweepSAVE");576 IERC20(token).safeTransfer(treasury, amount);577 }578}
Tier 1.5 — Guardian-Protected (3yr expiry)
Censorship-resistant bearer transfer for FLAT tokens. Uses a commit-reveal scheme: sender locks FLAT behind a keccak256 hash, recipient reveals with the preimage to claim. Sender can reclaim after 30 days if unrevealed. Guardian (Gnosis Safe) can pause/unpause during first 3 years, with 48-hour timelock on permanent shutdown. reclaim() always works, even when paused or shut down. No proxy, no upgrade, no blacklist, no freeze. After guardian expiry, fully immutable.
Owner: Guardian: Gnosis Safe (0x781f...C714) — expires April 2029 · Address: 0xc9a1fd3cAe394eca5cAf9411c6c5C43df695cBcc
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.20;34import "@openzeppelin/contracts/token/ERC20/IERC20.sol";5import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";67/**8 * @title FlatBearer9 * @notice Censorship-resistant bearer transfer for FLAT tokens.10 *11 * Commit-reveal scheme:12 * 1. Sender locks FLAT behind keccak256(secret, recipient, amount, nonce).13 * 2. Anyone who knows the preimage can reveal, delivering FLAT to recipient.14 * 3. Sender can reclaim after RECLAIM_DELAY if reveal never occurs.15 *16 * Guardian model:17 * - A Gnosis Safe multisig (recommended 4-of-7 or 5-of-9) can pause/unpause18 * during the first 3 years.19 * - After GUARDIAN_PERIOD, pause power is permanently destroyed.20 * - Guardian can schedule permanent shutdown (48h timelock, one-time, irreversible).21 * - reclaim() always works, even when paused or shut down.22 * - Reclaim delay is waived when permanently shut down.23 * - Guardian cannot access, redirect, or freeze individual funds.24 *25 * No proxy. No upgrade. No blacklist. No freeze.26 * After guardian expiry, this contract is fully immutable27 * (unless permanently shut down, in which case only reclaim works).28 */29contract FlatBearer {30 using SafeERC20 for IERC20;3132 // Constants33 uint256 public constant RECLAIM_DELAY = 30 days;34 uint256 public constant GUARDIAN_PERIOD = 1095 days; // 3 years35 uint256 public constant SHUTDOWN_DELAY = 48 hours;3637 // Immutables (set once at deployment)38 IERC20 public immutable flat;39 address public immutable guardian;40 uint256 public immutable guardianExpiry;4142 // Mutable State43 bool public paused;44 bool public permanentlyShutdown;45 uint256 public shutdownScheduledAt;46 uint256 public pendingCount;4748 struct Commitment {49 address sender;50 uint256 amount;51 uint256 timestamp;52 bool settled;53 }5455 mapping(bytes32 => Commitment) public commitments;5657 // Events58 event Deployed(address indexed flat, address indexed guardian, uint256 guardianExpiry);59 event Committed(bytes32 indexed hash, uint256 amount);60 event Revealed(bytes32 indexed hash);61 event Reclaimed(bytes32 indexed hash);62 event Paused(address indexed by);63 event Unpaused(address indexed by);64 event ShutdownScheduled(address indexed by, uint256 executeableAt);65 event ShutdownCancelled(address indexed by);66 event PermanentlyShutdown(address indexed by);6768 // Errors69 error ContractPaused();70 error HashAlreadyUsed();71 error ZeroAmount();72 error ZeroAddress();73 error CommitmentNotFound();74 error AmountMismatch();75 error AlreadySettled();76 error NotSender();77 error TooEarly();78 error NotGuardian();79 error GuardianExpired();80 error AlreadyShutdown();81 error ShutdownNotScheduled();82 error ShutdownTooEarly();83 error ShutdownAlreadyScheduled();8485 // Modifiers86 modifier whenNotPaused() {87 if (permanentlyShutdown) revert AlreadyShutdown();88 if (paused && block.timestamp < guardianExpiry) revert ContractPaused();89 _;90 }9192 modifier onlyGuardian() {93 if (msg.sender != guardian) revert NotGuardian();94 if (block.timestamp >= guardianExpiry) revert GuardianExpired();95 _;96 }9798 // Constructor99 constructor(address _flat, address _guardian) {100 if (_flat == address(0)) revert ZeroAddress();101 if (_guardian == address(0)) revert ZeroAddress();102 flat = IERC20(_flat);103 guardian = _guardian;104 guardianExpiry = block.timestamp + GUARDIAN_PERIOD;105 emit Deployed(_flat, _guardian, guardianExpiry);106 }107108 // Core Functions109 function commit(bytes32 hash, uint256 amount) external whenNotPaused {110 if (amount == 0) revert ZeroAmount();111 if (commitments[hash].amount != 0) revert HashAlreadyUsed();112 flat.safeTransferFrom(msg.sender, address(this), amount);113 commitments[hash] = Commitment({114 sender: msg.sender,115 amount: amount,116 timestamp: block.timestamp,117 settled: false118 });119 pendingCount++;120 emit Committed(hash, amount);121 }122123 function reveal(124 bytes32 secret,125 address recipient,126 uint256 amount,127 uint256 nonce128 ) external whenNotPaused {129 if (recipient == address(0)) revert ZeroAddress();130 bytes32 hash = keccak256(abi.encodePacked(secret, recipient, amount, nonce));131 Commitment storage c = commitments[hash];132 if (c.amount == 0) revert CommitmentNotFound();133 if (c.amount != amount) revert AmountMismatch();134 if (c.settled) revert AlreadySettled();135 c.settled = true;136 flat.safeTransfer(recipient, amount);137 emit Revealed(hash);138 pendingCount--;139 delete commitments[hash];140 }141142 function reclaim(bytes32 hash) external {143 Commitment storage c = commitments[hash];144 if (c.amount == 0) revert CommitmentNotFound();145 if (msg.sender != c.sender) revert NotSender();146 if (c.settled) revert AlreadySettled();147 if (block.timestamp < c.timestamp + RECLAIM_DELAY && !permanentlyShutdown) revert TooEarly();148 c.settled = true;149 flat.safeTransfer(c.sender, c.amount);150 emit Reclaimed(hash);151 pendingCount--;152 delete commitments[hash];153 }154155 // Guardian Functions156 function pause() external onlyGuardian {157 paused = true;158 emit Paused(msg.sender);159 }160161 function unpause() external onlyGuardian {162 paused = false;163 emit Unpaused(msg.sender);164 }165166 function scheduleShutdown() external onlyGuardian {167 if (permanentlyShutdown) revert AlreadyShutdown();168 if (shutdownScheduledAt != 0) revert ShutdownAlreadyScheduled();169 shutdownScheduledAt = block.timestamp;170 paused = true;171 emit ShutdownScheduled(msg.sender, block.timestamp + SHUTDOWN_DELAY);172 }173174 function cancelShutdown() external onlyGuardian {175 if (shutdownScheduledAt == 0) revert ShutdownNotScheduled();176 shutdownScheduledAt = 0;177 paused = false;178 emit ShutdownCancelled(msg.sender);179 }180181 function executeShutdown() external {182 if (shutdownScheduledAt == 0) revert ShutdownNotScheduled();183 if (block.timestamp < shutdownScheduledAt + SHUTDOWN_DELAY) revert ShutdownTooEarly();184 if (permanentlyShutdown) revert AlreadyShutdown();185 permanentlyShutdown = true;186 emit PermanentlyShutdown(msg.sender);187 }188189 // View Functions190 function isGuardianActive() external view returns (bool) {191 return block.timestamp < guardianExpiry;192 }193194 function isShutdown() external view returns (bool) {195 return permanentlyShutdown;196 }197}
Tier 2 — Admin-Controlled Vault
Custodial vault for FLAT tokens backing all FlatID checking accounts. Deposits are unlimited (anyone can send FLAT to this address). Withdrawals are capped by an admin-adjustable daily limit (50,000 FLAT). Guardian (Gnosis Safe multisig) can pause instantly. Admin transferable to Gnosis Safe via setAdmin(). No proxy, no upgradability. All dependencies inlined (no external imports).
Owner: Admin: 0xdDf4...36bC (Ledger) · Guardian: Gnosis Safe (0x781f...C714) · Address: 0xEe0505bc4ecA8cE7a48AA4C7037bc11555881644
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.20;34// Minimal IERC20 interface5interface IERC20 {6 function totalSupply() external view returns (uint256);7 function balanceOf(address account) external view returns (uint256);8 function transfer(address to, uint256 value) external returns (bool);9 function allowance(address owner, address spender) external view returns (uint256);10 function approve(address spender, uint256 value) external returns (bool);11 function transferFrom(address from, address to, uint256 value) external returns (bool);12}1314// Minimal SafeERC20 library (inlined)15library Address {16 function functionCall(address target, bytes memory data) internal returns (bytes memory) {17 (bool success, bytes memory returndata) = target.call(data);18 require(success, "Address: low-level call failed");19 if (returndata.length > 0) {20 require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");21 }22 return returndata;23 }24}2526library SafeERC20 {27 using Address for address;2829 function safeTransfer(IERC20 token, address to, uint256 value) internal {30 _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));31 }3233 function _callOptionalReturn(IERC20 token, bytes memory data) private {34 bytes memory returndata = address(token).functionCall(data);35 if (returndata.length != 0) {36 require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");37 }38 }39}4041contract FlatIDVault {42 using SafeERC20 for IERC20;4344 // State45 IERC20 public immutable flatToken;46 address public admin;47 address public guardian;48 bool public paused;49 uint256 public dailyLimit;50 uint256 public spentToday;51 uint256 public lastResetDay;5253 // Events54 event Withdrawal(address indexed to, uint256 amount);55 event Paused(address indexed by);56 event Unpaused(address indexed by);57 event AdminChanged(address indexed oldAdmin, address indexed newAdmin);58 event GuardianChanged(address indexed oldGuardian, address indexed newGuardian);59 event DailyLimitChanged(uint256 oldLimit, uint256 newLimit);6061 modifier onlyAdmin() {62 require(msg.sender == admin, "FlatIDVault: not admin");63 _;64 }6566 modifier notPaused() {67 require(!paused, "FlatIDVault: paused");68 _;69 }7071 constructor(72 address _flatToken,73 address _admin,74 address _guardian,75 uint256 _dailyLimit76 ) {77 require(_flatToken != address(0), "FlatIDVault: zero token");78 require(_admin != address(0), "FlatIDVault: zero admin");79 require(_guardian != address(0), "FlatIDVault: zero guardian");80 require(_dailyLimit > 0, "FlatIDVault: zero daily limit");8182 flatToken = IERC20(_flatToken);83 admin = _admin;84 guardian = _guardian;85 dailyLimit = _dailyLimit;86 lastResetDay = block.timestamp / 1 days;87 }8889 function withdraw(address to, uint256 amount) external onlyAdmin notPaused {90 require(to != address(0), "FlatIDVault: zero recipient");91 require(amount > 0, "FlatIDVault: zero amount");9293 uint256 today = block.timestamp / 1 days;94 if (today > lastResetDay) {95 spentToday = 0;96 lastResetDay = today;97 }9899 require(spentToday + amount <= dailyLimit, "FlatIDVault: daily limit exceeded");100 spentToday += amount;101102 flatToken.safeTransfer(to, amount);103 emit Withdrawal(to, amount);104 }105106 function pause() external {107 require(msg.sender == admin || msg.sender == guardian, "FlatIDVault: not authorized");108 paused = true;109 emit Paused(msg.sender);110 }111112 function unpause() external onlyAdmin {113 paused = false;114 emit Unpaused(msg.sender);115 }116117 function setDailyLimit(uint256 newLimit) external onlyAdmin {118 require(newLimit > 0, "FlatIDVault: zero limit");119 uint256 oldLimit = dailyLimit;120 dailyLimit = newLimit;121 spentToday = 0;122 lastResetDay = block.timestamp / 1 days;123 emit DailyLimitChanged(oldLimit, newLimit);124 }125126 function setAdmin(address newAdmin) external onlyAdmin {127 require(newAdmin != address(0), "FlatIDVault: zero admin");128 emit AdminChanged(admin, newAdmin);129 admin = newAdmin;130 }131132 function setGuardian(address newGuardian) external onlyAdmin {133 require(newGuardian != address(0), "FlatIDVault: zero guardian");134 emit GuardianChanged(guardian, newGuardian);135 guardian = newGuardian;136 }137138 function vaultBalance() external view returns (uint256) {139 return flatToken.balanceOf(address(this));140 }141142 function remainingDailyAllowance() external view returns (uint256) {143 uint256 today = block.timestamp / 1 days;144 if (today > lastResetDay) {145 return dailyLimit;146 }147 if (spentToday >= dailyLimit) {148 return 0;149 }150 return dailyLimit - spentToday;151 }152}
Tier 2 — Admin-Controlled Vault
Custodial vault for SAVE tokens backing all FlatID savings accounts. Identical architecture to FlatIDVault but deployed for the SAVE token. Deposits are unlimited (anyone can send SAVE to this address). Withdrawals are capped by an admin-adjustable daily limit (5,000 SAVE). Guardian (Gnosis Safe multisig) can pause instantly. Admin transferable to Gnosis Safe via setAdmin(). No proxy, no upgradability. All dependencies inlined (no external imports).
Owner: Admin: 0xdDf4...36bC (Ledger) · Guardian: Gnosis Safe (0x781f...C714) · Address: 0xABB9eFa1C0F8ceaA080c2C6884098B7E0f3689ea
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.20;34// Minimal IERC20 interface5interface IERC20 {6 function totalSupply() external view returns (uint256);7 function balanceOf(address account) external view returns (uint256);8 function transfer(address to, uint256 value) external returns (bool);9 function allowance(address owner, address spender) external view returns (uint256);10 function approve(address spender, uint256 value) external returns (bool);11 function transferFrom(address from, address to, uint256 value) external returns (bool);12}1314// Minimal SafeERC20 library (inlined)15library Address {16 function functionCall(address target, bytes memory data) internal returns (bytes memory) {17 (bool success, bytes memory returndata) = target.call(data);18 require(success, "Address: low-level call failed");19 if (returndata.length > 0) {20 require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");21 }22 return returndata;23 }24}2526library SafeERC20 {27 using Address for address;2829 function safeTransfer(IERC20 token, address to, uint256 value) internal {30 _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));31 }3233 function _callOptionalReturn(IERC20 token, bytes memory data) private {34 bytes memory returndata = address(token).functionCall(data);35 if (returndata.length != 0) {36 require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");37 }38 }39}4041contract FlatIDSaveVault {42 using SafeERC20 for IERC20;4344 // State45 IERC20 public immutable saveToken;46 address public admin;47 address public guardian;48 bool public paused;49 uint256 public dailyLimit;50 uint256 public spentToday;51 uint256 public lastResetDay;5253 // Events54 event Withdrawal(address indexed to, uint256 amount);55 event Paused(address indexed by);56 event Unpaused(address indexed by);57 event AdminChanged(address indexed oldAdmin, address indexed newAdmin);58 event GuardianChanged(address indexed oldGuardian, address indexed newGuardian);59 event DailyLimitChanged(uint256 oldLimit, uint256 newLimit);6061 modifier onlyAdmin() {62 require(msg.sender == admin, "FlatIDSaveVault: not admin");63 _;64 }6566 modifier notPaused() {67 require(!paused, "FlatIDSaveVault: paused");68 _;69 }7071 constructor(72 address _saveToken,73 address _admin,74 address _guardian,75 uint256 _dailyLimit76 ) {77 require(_saveToken != address(0), "FlatIDSaveVault: zero token");78 require(_admin != address(0), "FlatIDSaveVault: zero admin");79 require(_guardian != address(0), "FlatIDSaveVault: zero guardian");80 require(_dailyLimit > 0, "FlatIDSaveVault: zero daily limit");8182 saveToken = IERC20(_saveToken);83 admin = _admin;84 guardian = _guardian;85 dailyLimit = _dailyLimit;86 lastResetDay = block.timestamp / 1 days;87 }8889 function withdraw(address to, uint256 amount) external onlyAdmin notPaused {90 require(to != address(0), "FlatIDSaveVault: zero recipient");91 require(amount > 0, "FlatIDSaveVault: zero amount");9293 uint256 today = block.timestamp / 1 days;94 if (today > lastResetDay) {95 spentToday = 0;96 lastResetDay = today;97 }9899 require(spentToday + amount <= dailyLimit, "FlatIDSaveVault: daily limit exceeded");100 spentToday += amount;101102 saveToken.safeTransfer(to, amount);103 emit Withdrawal(to, amount);104 }105106 function pause() external {107 require(msg.sender == admin || msg.sender == guardian, "FlatIDSaveVault: not authorized");108 paused = true;109 emit Paused(msg.sender);110 }111112 function unpause() external onlyAdmin {113 paused = false;114 emit Unpaused(msg.sender);115 }116117 function setDailyLimit(uint256 newLimit) external onlyAdmin {118 require(newLimit > 0, "FlatIDSaveVault: zero limit");119 uint256 oldLimit = dailyLimit;120 dailyLimit = newLimit;121 spentToday = 0;122 lastResetDay = block.timestamp / 1 days;123 emit DailyLimitChanged(oldLimit, newLimit);124 }125126 function setAdmin(address newAdmin) external onlyAdmin {127 require(newAdmin != address(0), "FlatIDSaveVault: zero admin");128 emit AdminChanged(admin, newAdmin);129 admin = newAdmin;130 }131132 function setGuardian(address newGuardian) external onlyAdmin {133 require(newGuardian != address(0), "FlatIDSaveVault: zero guardian");134 emit GuardianChanged(guardian, newGuardian);135 guardian = newGuardian;136 }137138 function vaultBalance() external view returns (uint256) {139 return saveToken.balanceOf(address(this));140 }141142 function remainingDailyAllowance() external view returns (uint256) {143 uint256 today = block.timestamp / 1 days;144 if (today > lastResetDay) {145 return dailyLimit;146 }147 if (spentToday >= dailyLimit) {148 return 0;149 }150 return dailyLimit - spentToday;151 }152}
Shared interface definitions used by FlatEngine and FlatSale. These define the external contract dependencies (Chainlink, Uniswap V2, CPIOracle).
ERC20, ERC20Permit, Ownable, ReentrancyGuard, SafeERC20
0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f