Deployed Smart Contracts

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

Deployment Summary

Owner (all contracts): 0x11d6...fC2e(Ledger hardware wallet)
Treasury (Gnosis Safe): 0x781f...C714
Network: Ethereum Mainnet (Chain ID: 1)
Contracts deployed: 10 (all verified on Etherscan)
FLAT contracts framework: Foundry (FlatEngine v2 + CPIOracle v3 inline all deps; FlatSale v2 + SAVESale v2 use OpenZeppelin)
RISE/SAVE contracts framework: Remix + OpenZeppelin 5.x
FlatBearer framework: Hardhat + OpenZeppelin 5.x
FlatIDVault framework: Hardhat + inlined SafeERC20 (0.8.28)
FlatIDSaveVault framework: Hardhat + inlined SafeERC20 (0.8.28) — identical to FlatIDVault

Contract Addresses

ContractAddressVerifiedPausablenSLOC
FLAT0x6AD2...aE99VerifiedNo~43
CPIOracle v30xd4aA...c664VerifiedNo~158
FlatEngine v20x30a8...9cd5VerifiedGuardian (3yr)~514
FlatSale v2 (Active)0x7d8F...ec9dVerifiedGuardian (3yr)~646
RISE0xc1E1...a14AVerifiedNo~34
SAVE (Vault)0x9f0D...9cAeVerifiedNo~36
SAVESale v2 (Active)0x2840...cd02VerifiedGuardian (3yr)~578
FlatBearer0xc9a1...cBccVerifiedGuardian (3yr)~160
FlatIDVault0xEe05...1644VerifiedAdmin+Guardian~85
FlatIDSaveVault0xABB9...89eaVerifiedAdmin+Guardian~85
FLAT/WETH Pair0x2bC1...7205Uniswap V2NoN/A
SAVE/WETH Pair0xEC40...bEaUniswap V2NoN/A
RISE/WETH Pair0x2cC9...6d5Uniswap V2NoN/A

Verification Checklist

The following claims can be verified by reading the source code below:

  • FLAT has no mint() function — supply is fixed at 100 trillion, set in constructor
  • FLAT has no burn() function — supply can never decrease
  • FLAT has no owner, no admin, no pause — pure immutable ERC-20
  • CPIOracle v3 circuit breaker limits CPI changes to 2% per update; public refreshTimestamp() prevents staleness
  • FlatEngine pulse() is permissionless — anyone can call and earn bounty
  • FlatEngine v2 / FlatSale v2 guardian functions expire after 3 years (guardianExpiry set at deployment)
  • FlatSale sends 90% of ETH to LP, 10% to treasury — hardcoded in buy()
  • FlatSale LP split: 85% treasury, 5% FlatEngine battery — hardcoded
  • No contract has blacklist, freeze, or proxy upgrade capability
  • FlatEngine v2 and CPIOracle v3 inline all dependencies (no external imports); FlatSale v2, SAVESale v2, RISE, and SAVE use OpenZeppelin
  • FlatBearer reclaim() works even when paused or permanently shut down
  • FlatBearer shutdown requires 48-hour timelock (schedule → wait → execute)
  • FlatBearer guardian expires after 3 years — contract becomes fully immutable
  • FlatBearer has no proxy, no upgrade, no blacklist, no freeze capability
  • FlatIDVault daily withdrawal limit constrains admin to 50,000 FLAT per day — guardian can pause instantly
  • FlatIDVault has no proxy, no upgradability — immutable after deployment
  • FlatIDVault inlines all dependencies (IERC20, SafeERC20, Address) — no external imports
  • FlatIDSaveVault daily withdrawal limit constrains admin to 5,000 SAVE per day — guardian can pause instantly
  • FlatIDSaveVault has no proxy, no upgradability — immutable after deployment
  • FlatIDSaveVault inlines all dependencies (IERC20, SafeERC20, Address) — identical architecture to FlatIDVault

FLAT.sol

Tier 1 — Fully Immutable

View on Etherscan Verified 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: MIT
2pragma solidity ^0.8.24;
3
4import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
6
7/// @title FLAT
8/// @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 deployment
11/// 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 other
15/// 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 = 1e32
22/// uint256 max: ~1.15e77
23/// No overflow risk. Verified safe.
24contract FLAT is ERC20, ERC20Permit {
25
26 /// @notice Fixed total supply: 100 trillion FLAT (18 decimals).
27 uint256 public constant TOTAL_SUPPLY = 100_000_000_000_000 * 1e18;
28
29 /// @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 }
34
35 // 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}

CPIOracle v3.sol

Tier 2 — Owner-Controlled Oracle

View on Etherscan Verified Immutable

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: MIT
2pragma solidity ^0.8.24;
3
4/**
5 * @title CPIOracle v3
6 * @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 staleness
12 * 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 */
17
18// Minimal Ownable (from OpenZeppelin 5.x, inlined to avoid dependency)
19abstract contract Ownable {
20 address private _owner;
21
22 error OwnableUnauthorizedAccount(address account);
23 error OwnableInvalidOwner(address owner);
24
25 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
26
27 constructor(address initialOwner) {
28 if (initialOwner == address(0)) revert OwnableInvalidOwner(address(0));
29 _owner = initialOwner;
30 emit OwnershipTransferred(address(0), initialOwner);
31 }
32
33 modifier onlyOwner() {
34 if (msg.sender != _owner) revert OwnableUnauthorizedAccount(msg.sender);
35 _;
36 }
37
38 function owner() public view returns (address) { return _owner; }
39
40 function renounceOwnership() public onlyOwner {
41 address old = _owner;
42 _owner = address(0);
43 emit OwnershipTransferred(old, address(0));
44 }
45
46 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}
53
54contract CPIOracle is Ownable {
55
56 // -- 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 refreshTimestamp
62 uint256 public lastCPIUpdateTimestamp; // only set by updateCPI, for transparency
63
64 // -- Constants --
65 uint256 public constant BLOCKS_PER_MONTH = 216_000;
66 uint256 public constant MAX_CPI_CHANGE_BPS = 200; // 2.0% max change per update
67
68 // -- Events --
69 event CPIUpdated(
70 uint256 previousCPI,
71 uint256 newCPI,
72 uint256 previousBlock,
73 uint256 targetBlock,
74 uint256 timestamp
75 );
76 event TimestampRefreshed(uint256 timestamp);
77
78 // -- 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 }
88
89 // -- 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 }
96
97 // -- Owner-only: set new CPI target --
98 function updateCPI(uint256 _newCPI) external onlyOwner {
99 require(_newCPI > 0, "invalid CPI");
100
101 uint256 refPrice;
102 if (block.number <= previousBlockNumber) {
103 refPrice = previousCPIValue;
104 } else {
105 refPrice = getInterpolatedPrice();
106 }
107
108 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 );
113
114 previousCPIValue = refPrice;
115 currentCPIValue = _newCPI;
116 previousBlockNumber = block.number;
117 targetBlockNumber = block.number + BLOCKS_PER_MONTH;
118 lastUpdated = block.timestamp;
119 lastCPIUpdateTimestamp = block.timestamp;
120
121 emit CPIUpdated(refPrice, _newCPI, block.number, block.number + BLOCKS_PER_MONTH, block.timestamp);
122 }
123
124 // -- View: linearly interpolated price at current block --
125 function getInterpolatedPrice() public view returns (uint256 price) {
126 if (block.number >= targetBlockNumber) {
127 return currentCPIValue;
128 }
129
130 uint256 prev = previousCPIValue;
131 uint256 curr = currentCPIValue;
132
133 if (prev == curr) return curr;
134
135 uint256 elapsed = block.number - previousBlockNumber;
136 uint256 total = targetBlockNumber - previousBlockNumber;
137
138 if (curr > prev) {
139 price = prev + ((curr - prev) * elapsed) / total;
140 } else {
141 price = prev - ((prev - curr) * elapsed) / total;
142 }
143 }
144
145 // -- View: convenience getters (ICPIOracle interface) --
146 function lastUpdateTimestamp() external view returns (uint256 timestamp) {
147 return lastUpdated;
148 }
149
150 function getCPIData() external view returns (
151 uint256 previousCPI,
152 uint256 currentCPI,
153 uint256 previousBlock,
154 uint256 currentBlock
155 ) {
156 return (previousCPIValue, currentCPIValue, previousBlockNumber, targetBlockNumber);
157 }
158}

FlatEngine v2.sol

Tier 2 — Guardian Pattern (3-year expiry)

View on Etherscan Verified Guardian: 3yr

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: MIT
2pragma solidity ^0.8.24;
3
4/**
5 * @title FlatEngine v2
6 * @notice Autonomous peg management contract for FLAT coin. Maintains the FLAT-ETH
7 * 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 liquidity
10 * -> battery grows. Price pushed down toward target.
11 *
12 * DISCHARGE (FLAT < oracle): Remove LP from battery -> keep FLAT -> use ETH to
13 * 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 */
24
25// ============ INTERFACES (inlined) ============
26
27interface 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}
35
36interface 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}
41
42interface IAggregatorV3 {
43 function decimals() external view returns (uint8);
44 function latestRoundData() external view returns (
45 uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound
46 );
47}
48
49interface IUniswapV2Router02 {
50 function WETH() external pure returns (address);
51 function addLiquidityETH(
52 address token, uint amountTokenDesired, uint amountTokenMin, uint amountETHMin,
53 address to, uint deadline
54 ) 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 deadline
58 ) external returns (uint amountToken, uint amountETH);
59 function swapExactTokensForETH(
60 uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline
61 ) external returns (uint[] memory amounts);
62 function swapExactETHForTokens(
63 uint amountOutMin, address[] calldata path, address to, uint deadline
64 ) external payable returns (uint[] memory amounts);
65}
66
67interface 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}
75
76// 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);
82
83 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}
105
106// 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;
111
112 modifier nonReentrant() {
113 require(_status != ENTERED, "ReentrancyGuard: reentrant call");
114 _status = ENTERED;
115 _;
116 _status = NOT_ENTERED;
117 }
118}
119
120contract FlatEngine is Ownable, ReentrancyGuard {
121
122 // ============ 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;
132
133 // ============ MUTABLE ORACLE (with 48h timelock) ============
134 ICPIOracle public oracle;
135 address public pendingOracle;
136 uint256 public oracleChangeTimestamp;
137
138 // ============ 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 hours
145
146 // ============ MUTABLE STATE ============
147 bool public paused;
148 uint256 public lastPulseBlock;
149 uint256 public totalCharged;
150 uint256 public totalDischarged;
151
152 // ============ EVENTS ============
153 event Charged(
154 address indexed caller, uint256 flatSold, uint256 ethReceived,
155 uint256 lpCreated, uint256 bounty
156 );
157 event Discharged(
158 address indexed caller, uint256 lpRemoved, uint256 flatFromLP,
159 uint256 ethFromLP, uint256 flatBought, uint256 bounty
160 );
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();
168
169 // ============ CONSTRUCTOR ============
170 constructor(
171 address _flat,
172 address _router,
173 address _pair,
174 address _oracle,
175 address _ethUsdFeed,
176 uint256 _bountyBps,
177 uint256 _guardianExpiry
178 ) 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");
186
187 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;
194
195 flatIsToken0 = pair.token0() == _flat;
196 feedDecimals = uint256(ethUsdFeed.decimals());
197
198 guardianExpiry = _guardianExpiry;
199 }
200
201 // ============ MODIFIERS ============
202 modifier whenNotPaused() {
203 require(!paused, "paused");
204 _;
205 }
206
207 modifier onlyGuardian() {
208 require(msg.sender == owner(), "not owner");
209 require(block.timestamp <= guardianExpiry, "guardian expired");
210 _;
211 }
212
213 // ============ INTERNAL HELPERS ============
214
215 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 }
224
225 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 }
235
236 function _getEthPerFlat() internal view returns (uint256 ethPerFlat) {
237 uint256 cpiUsdPrice = oracle.getInterpolatedPrice();
238 require(cpiUsdPrice > 0, "invalid CPI price");
239
240 require(
241 block.timestamp - oracle.lastUpdateTimestamp() <= CPI_STALENESS_THRESHOLD,
242 "CPI oracle stale"
243 );
244
245 (, int256 ethUsdAnswer, , uint256 ethUsdUpdatedAt, ) = ethUsdFeed.latestRoundData();
246 require(ethUsdAnswer > 0, "invalid ETH/USD price");
247
248 require(
249 block.timestamp - ethUsdUpdatedAt <= ETH_USD_STALENESS_THRESHOLD,
250 "ETH/USD feed stale"
251 );
252
253 ethPerFlat = (cpiUsdPrice * (10 ** feedDecimals)) / uint256(ethUsdAnswer);
254 }
255
256 // ============ VIEW FUNCTIONS ============
257
258 function getOraclePrice() public view returns (uint256) {
259 return _getEthPerFlat();
260 }
261
262 function getOraclePriceUSD() public view returns (uint256) {
263 return oracle.getInterpolatedPrice();
264 }
265
266 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 }
271
272 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 }
277
278 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;
283
284 if (marketPrice >= oraclePrice) {
285 return int256((marketPrice - oraclePrice) * 10000 / oraclePrice);
286 } else {
287 return -int256((oraclePrice - marketPrice) * 10000 / oraclePrice);
288 }
289 }
290
291 function getBatteryBalance() public view returns (uint256) {
292 return pair.balanceOf(address(this));
293 }
294
295 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 }
303
304 function getInventoryBalance() public view returns (uint256) {
305 return FLAT.balanceOf(address(this));
306 }
307
308 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 }
324
325 function version() external pure returns (string memory) {
326 return "FlatEngine_v2";
327 }
328
329 // ============ CORE: pulse() ============
330
331 function pulse() external nonReentrant whenNotPaused {
332 require(block.number > lastPulseBlock, "one pulse per block");
333 lastPulseBlock = block.number;
334
335 uint256 oraclePrice = _getEthPerFlat();
336
337 (uint256 flatReserve, uint256 ethReserve) = _getReserves();
338 require(flatReserve > 0 && ethReserve > 0, "pool empty");
339
340 uint256 marketPrice = (ethReserve * 1e18) / flatReserve;
341
342 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");
350
351 if (isAbovePeg) {
352 _charge(oraclePrice, flatReserve, ethReserve);
353 } else {
354 _discharge(oraclePrice, flatReserve, ethReserve, deviation);
355 }
356 }
357
358 function _charge(
359 uint256 oraclePrice, uint256 flatReserve, uint256 ethReserve
360 ) internal {
361 uint256 k = flatReserve * ethReserve;
362 uint256 targetFlatReserve = _sqrt((k * 1e18) / oraclePrice);
363
364 uint256 flatToSell = targetFlatReserve - flatReserve;
365 require(flatToSell > 0, "nothing to sell");
366
367 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");
373
374 address[] memory path = new address[](2);
375 path[0] = address(FLAT);
376 path[1] = WETH;
377
378 FLAT.approve(address(router), flatToSell);
379 uint256[] memory amounts = router.swapExactTokensForETH(
380 flatToSell, 0, path, address(this), block.timestamp
381 );
382 uint256 ethReceived = amounts[amounts.length - 1];
383
384 uint256 bounty = ethReceived * bountyBps / 10000;
385 uint256 ethForLP = ethReceived - bounty;
386
387 uint256 flatForLP = (ethForLP * 1e18) / oraclePrice;
388 uint256 currentInventory = FLAT.balanceOf(address(this));
389 if (flatForLP > currentInventory) {
390 flatForLP = currentInventory;
391 }
392
393 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.timestamp
398 );
399 }
400
401 if (bounty > 0) {
402 (bool success, ) = payable(msg.sender).call{value: bounty}("");
403 require(success, "bounty transfer failed");
404 }
405
406 totalCharged += lpReceived;
407 emit Charged(msg.sender, flatToSell, ethReceived, lpReceived, bounty);
408 }
409
410 function _discharge(
411 uint256 oraclePrice, uint256 flatReserve, uint256 ethReserve, uint256 deviation
412 ) internal {
413 uint256 lpBalance = pair.balanceOf(address(this));
414 require(lpBalance >= MIN_BATTERY_LP, "battery too small");
415
416 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 }
421
422 IERC20(address(pair)).approve(address(router), lpToRemove);
423
424 (uint256 flatReceived, uint256 ethReceived) = router.removeLiquidityETH(
425 address(FLAT), lpToRemove, 0, 0, address(this), block.timestamp
426 );
427
428 uint256 bounty = ethReceived * bountyBps / 10000;
429 uint256 ethForBuy = ethReceived - bounty;
430
431 address[] memory path = new address[](2);
432 path[0] = WETH;
433 path[1] = address(FLAT);
434
435 uint256 flatBought = 0;
436 if (ethForBuy > 0) {
437 uint256[] memory amounts = router.swapExactETHForTokens{value: ethForBuy}(
438 0, path, address(this), block.timestamp
439 );
440 flatBought = amounts[amounts.length - 1];
441 }
442
443 if (bounty > 0) {
444 (bool success, ) = payable(msg.sender).call{value: bounty}("");
445 require(success, "bounty transfer failed");
446 }
447
448 totalDischarged += lpToRemove;
449 emit Discharged(msg.sender, lpToRemove, flatReceived, ethReceived, flatBought, bounty);
450 }
451
452 // ============ ORACLE TIMELOCK (Category 1 — expires after guardian period) ============
453
454 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 }
461
462 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 }
471
472 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 }
479
480 // ============ CATEGORY 2: STRUCTURAL FUNCTIONS (never expire) ============
481
482 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 }
489
490 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 }
495
496 // ============ CATEGORY 1: GUARDIAN FUNCTIONS (expire after 3 years) ============
497
498 function setPaused(bool _paused) external onlyGuardian {
499 paused = _paused;
500 if (_paused) emit Paused();
501 else emit Unpaused();
502 }
503
504 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 }
510
511 // ============ RECEIVE ============
512 receive() external payable {}
513}

FlatSale v2 (Active).sol

Tier 2 — Guardian Pattern (3-year expiry)

View on Etherscan Verified Guardian: 3yr

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: MIT
2pragma solidity ^0.8.24;
3
4import "@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";
14
15/// @title IFlatReceiver
16/// @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 ETH
21 /// @param amount The amount of FLAT received
22 /// @param data Arbitrary data passed by the buyer (referral codes, routing instructions, etc.)
23 /// @return selector Must return IFlatReceiver.onFlatReceived.selector to confirm handling
24 function onFlatReceived(
25 address buyer,
26 uint256 amount,
27 bytes calldata data
28 ) external returns (bytes4);
29}
30
31/// @title IFlatSaleV2
32/// @notice Interface for FlatSale V2 — used for EIP-165 discoverability
33interface 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 s
41 ) external;
42 function buyBatch(address[] calldata recipients, uint256[] calldata ethAmounts, uint256 minFlatOutTotal) external payable;
43}
44
45/// @title FlatSale V2
46/// @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 tx
51/// ✅ 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 tx
54/// ✅ Added `buyBatch()` — institutional distribution (payroll, rewards, airdrops)
55/// ✅ Added `receive()` fallback — raw ETH sends auto-buy FLAT for sender
56/// ✅ Added `minFlatOut` slippage parameter — proper sandwich protection
57/// ✅ Added referral tracking via `referralCode` in events
58/// ✅ Richer events (recipient, oracle prices, referral)
59/// ✅ EIP-165 interface discoverability
60///
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 tx
65/// - 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 API
69/// that the world's financial system can call. Maximize composability.
70contract FlatSaleV2 is Ownable, ReentrancyGuard, ERC165 {
71 using SafeERC20 for IERC20;
72
73 // ============ 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;
84
85 // ============ CONSTANTS ============
86 uint256 public constant SLIPPAGE_BPS = 200; // 2% slippage tolerance for LP
87 uint256 public constant CPI_STALENESS_THRESHOLD = 172800; // 48 hours
88 uint256 public constant ETH_USD_STALENESS_THRESHOLD = 7200; // 2 hours
89 uint256 public constant GUARDIAN_DURATION = 1095 days; // 3 years
90 uint256 public constant MAX_BATCH_SIZE = 100; // Max recipients per batch
91
92 // ============ 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;
103
104 // ============ 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 referralCode
116 );
117 event BatchBought(
118 address indexed buyer,
119 uint256 totalFlat,
120 uint256 totalEthPaid,
121 uint256 recipientCount,
122 bytes32 indexed referralCode
123 );
124 event TokenSwapped(
125 address indexed token,
126 uint256 tokenAmount,
127 uint256 ethReceived
128 );
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();
136
137 // ============ 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 _maxPriceUSD
149 ) 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");
158
159 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();
167
168 flatIsToken0 = pair.token0() == _flat;
169 feedDecimals = uint256(ethUsdFeed.decimals());
170
171 dailyCap = _dailyCap;
172 minPriceUSD = _minPriceUSD;
173 maxPriceUSD = _maxPriceUSD;
174 lastReset = block.timestamp;
175 maxBuyPerTx = 0;
176
177 guardianExpiry = block.timestamp + GUARDIAN_DURATION;
178 }
179
180 // ============ MODIFIERS ============
181 modifier whenNotPaused() {
182 require(!paused, "paused");
183 _;
184 }
185
186 modifier onlyGuardian() {
187 require(msg.sender == owner(), "not owner");
188 require(block.timestamp <= guardianExpiry, "guardian expired");
189 _;
190 }
191
192 // ============ EIP-165 INTERFACE DETECTION ============
193
194 /// @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 return
198 interfaceId == type(IFlatSaleV2).interfaceId ||
199 super.supportsInterface(interfaceId);
200 }
201
202 // ============ INTERNAL HELPERS ============
203
204 /// @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 }
220
221 /// @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 referralCode
228 ) internal returns (uint256 flatAmount) {
229 require(ethAmount >= 0.001 ether, "min 0.001 ETH");
230 require(recipient != address(0), "zero recipient");
231
232 // Daily cap reset
233 if (block.timestamp >= lastReset + 1 days) {
234 soldToday = 0;
235 lastReset = block.timestamp;
236 emit DailyCapReset(block.timestamp);
237 }
238
239 // Price determination
240 (uint256 oraclePrice, uint256 cpiUsdPrice) = _getEthPerFlat();
241 require(cpiUsdPrice >= minPriceUSD && cpiUsdPrice <= maxPriceUSD, "price outside band");
242
243 // Calculate FLAT amount
244 flatAmount = (ethAmount * 1e18) / oraclePrice;
245
246 // Slippage protection (the REAL sandwich defense, not tx.origin)
247 require(flatAmount >= minFlatOut, "slippage: insufficient output");
248
249 // Per-tx limit
250 if (maxBuyPerTx > 0) {
251 require(flatAmount <= maxBuyPerTx, "exceeds max per tx");
252 }
253
254 // Daily cap
255 require(soldToday + flatAmount <= dailyCap, "daily cap exceeded");
256
257 // ETH split: 10% operations, 90% LP
258 uint256 treasuryEth = ethAmount / 10;
259 uint256 polEth = ethAmount - treasuryEth;
260
261 // FLAT needed for LP
262 uint256 polFlat = (polEth * 1e18) / oraclePrice;
263
264 // Inventory check
265 uint256 totalFlatNeeded = flatAmount + polFlat;
266 require(FLAT.balanceOf(address(this)) >= totalFlatNeeded, "refill needed");
267
268 // Update counters
269 soldToday += flatAmount;
270 totalSold += flatAmount;
271 totalPOL += polEth;
272
273 // Transfer FLAT to recipient
274 FLAT.safeTransfer(recipient, flatAmount);
275
276 // Create LP tokens
277 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.timestamp
285 );
286
287 // Distribute LP tokens: 5% FlatEngine, 95% treasury
288 uint256 engineLP = liquidity * 5 / 100;
289 uint256 treasuryLP = liquidity - engineLP;
290 IERC20(address(pair)).safeTransfer(flatEngine, engineLP);
291 IERC20(address(pair)).safeTransfer(treasury, treasuryLP);
292
293 // Send operations ETH to treasury
294 (bool success, ) = payable(treasury).call{value: treasuryEth}("");
295 require(success, "treasury transfer failed");
296
297 // Emit rich event
298 emit Bought(
299 msg.sender,
300 recipient,
301 flatAmount,
302 ethAmount,
303 polEth,
304 engineLP,
305 treasuryLP,
306 oraclePrice,
307 cpiUsdPrice,
308 referralCode
309 );
310 }
311
312 // ============ PUBLIC BUY FUNCTIONS ============
313
314 /// @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 }
320
321 /// @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 tokens
324 /// @param minFlatOut Minimum FLAT to receive (sandwich protection)
325 function buyTo(
326 address recipient,
327 uint256 minFlatOut
328 ) external payable nonReentrant whenNotPaused {
329 _executeBuy(recipient, msg.value, minFlatOut, bytes32(0));
330 }
331
332 /// @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 callback
337 function buyToWithCallback(
338 address recipient,
339 uint256 minFlatOut,
340 bytes calldata data
341 ) external payable nonReentrant whenNotPaused {
342 uint256 flatAmount = _executeBuy(recipient, msg.value, minFlatOut, bytes32(0));
343
344 // If recipient is a contract, notify it
345 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 }
350
351 /// @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 tokens
354 /// @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 referralCode
360 ) external payable nonReentrant whenNotPaused {
361 _executeBuy(recipient, msg.value, minFlatOut, referralCode);
362 }
363
364 /// @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 spend
368 /// @param minFlatOut Minimum FLAT to receive (covers both swap slippage + buy slippage)
369 /// @param recipient Address to receive the FLAT tokens
370 function buyWithToken(
371 address token,
372 uint256 tokenAmount,
373 uint256 minFlatOut,
374 address recipient
375 ) 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");
379
380 // Pull tokens from caller
381 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);
382
383 // Swap token → ETH via Uniswap
384 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 end
392 path,
393 address(this),
394 block.timestamp
395 );
396 uint256 ethReceived = address(this).balance - ethBefore;
397 require(ethReceived > 0, "swap returned zero ETH");
398
399 emit TokenSwapped(token, tokenAmount, ethReceived);
400
401 // Execute buy with the received ETH
402 _executeBuy(recipient, ethReceived, minFlatOut, bytes32(0));
403 }
404
405 /// @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 spend
409 /// @param minFlatOut Minimum FLAT to receive
410 /// @param recipient Address to receive the FLAT tokens
411 /// @param deadline Permit deadline
412 /// @param v Permit signature v
413 /// @param r Permit signature r
414 /// @param s Permit signature s
415 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 s
424 ) external nonReentrant whenNotPaused {
425 // Execute permit (gasless approval)
426 IERC20Permit(token).permit(msg.sender, address(this), tokenAmount, deadline, v, r, s);
427
428 // Pull tokens
429 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);
430
431 // Swap token → ETH
432 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.timestamp
443 );
444 uint256 ethReceived = address(this).balance - ethBefore;
445 require(ethReceived > 0, "swap returned zero ETH");
446
447 emit TokenSwapped(token, tokenAmount, ethReceived);
448
449 // Execute buy
450 _executeBuy(recipient, ethReceived, minFlatOut, bytes32(0));
451 }
452
453 /// @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 FLAT
456 /// @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 minFlatOutTotal
462 ) 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");
465
466 // Verify ETH amounts sum to msg.value
467 uint256 totalEth;
468 for (uint256 i = 0; i < ethAmounts.length; i++) {
469 totalEth += ethAmounts[i];
470 }
471 require(totalEth == msg.value, "ETH sum mismatch");
472
473 // Execute individual buys
474 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 }
479
480 // Aggregate slippage check
481 require(totalFlat >= minFlatOutTotal, "batch slippage: insufficient total output");
482
483 emit BatchBought(msg.sender, totalFlat, msg.value, recipients.length, bytes32(0));
484 }
485
486 /// @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 referralCode
492 ) 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");
495
496 uint256 totalEth;
497 for (uint256 i = 0; i < ethAmounts.length; i++) {
498 totalEth += ethAmounts[i];
499 }
500 require(totalEth == msg.value, "ETH sum mismatch");
501
502 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 }
507
508 require(totalFlat >= minFlatOutTotal, "batch slippage: insufficient total output");
509
510 emit BatchBought(msg.sender, totalFlat, msg.value, recipients.length, referralCode);
511 }
512
513 // ============ RECEIVE: RAW ETH → AUTO-BUY ============
514
515 /// @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 called
521 /// 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;
529
530 // For raw ETH sends, execute buy with no slippage protection
531 // Users who want slippage protection should call buy() directly
532 // This is intentional: the simplicity of "just send ETH" is the feature
533 if (!paused) {
534 _executeBuy(msg.sender, msg.value, 0, bytes32(0));
535 }
536 }
537
538 // ============ VIEW FUNCTIONS ============
539
540 /// @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 }
545
546 /// @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 }
550
551 /// @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 }
557
558 /// @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 }
568
569 /// @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 }
573
574 /// @notice Estimate FLAT output for a given ETH input at current oracle price.
575 /// @param ethAmount The ETH amount to quote
576 /// @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 }
581
582 /// @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 buy
586 }
587 if (soldToday >= dailyCap) return 0;
588 return dailyCap - soldToday;
589 }
590
591 function version() external pure returns (string memory) {
592 return "FlatSale_v2";
593 }
594
595 // ============ CATEGORY 2: STRUCTURAL FUNCTIONS (never expire) ============
596
597 /// @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 }
604
605 // ============ CATEGORY 1: GUARDIAN FUNCTIONS (expire after 3 years) ============
606
607 function setPaused(bool _paused) external onlyGuardian {
608 paused = _paused;
609 if (_paused) emit Paused();
610 else emit Unpaused();
611 }
612
613 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 }
619
620 function setDailyCap(uint256 _newCap) external onlyGuardian {
621 dailyCap = _newCap;
622 emit DailyCapUpdated(_newCap);
623 }
624
625 function setMaxBuyPerTx(uint256 _max) external onlyGuardian {
626 maxBuyPerTx = _max;
627 emit MaxBuyPerTxUpdated(_max);
628 }
629
630 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 }
636
637 function sweepFLAT(uint256 _amount) external onlyGuardian {
638 FLAT.safeTransfer(treasury, _amount);
639 }
640
641 /// @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}

RISE.sol

Tier 1 — Fully Immutable

View on Etherscan Verified 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: MIT
2pragma solidity ^0.8.24;
3
4import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
6import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
7
8/**
9 * @title RISE Token
10 * @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 logic
21 * - ERC20Burnable: Self-burn capability
22 * - ERC20Permit: EIP-2612 gasless approvals
23 */
24contract RISE is ERC20, ERC20Burnable, ERC20Permit {
25 uint256 public constant TOTAL_SUPPLY = 425_000_000 * 10 ** 18;
26
27 /**
28 * @notice Deploys the RISE token and mints the entire fixed supply
29 * to the deployer's address.
30 */
31 constructor( ) ERC20("RISE", "RISE") ERC20Permit("RISE") {
32 _mint(msg.sender, TOTAL_SUPPLY);
33 }
34}

SAVE (Vault).sol

Tier 1 — Fully Immutable

View on Etherscan Verified 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: MIT
2pragma solidity 0.8.26;
3
4import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
7contract SAVE is ERC20 {
8 using SafeERC20 for IERC20;
9
10 IERC20 public immutable RISE;
11
12 event Deposited(address indexed depositor, uint256 amount);
13
14 constructor(address _rise) ERC20("SAVE", "SAVE") {
15 require(_rise != address(0), "RISE address cannot be zero");
16 RISE = IERC20(_rise);
17 }
18
19 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 }
25
26 function riseLockedInVault() external view returns (uint256) {
27 return RISE.balanceOf(address(this));
28 }
29
30 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}

SAVESale v2 (Active).sol

Tier 2 — Guardian Pattern (3-year expiry)

View on Etherscan Verified Guardian: 3yr

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: MIT
2pragma solidity ^0.8.24;
3
4import "@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";
10
11interface IUniswapV2Pair {
12 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
13 function token0() external view returns (address);
14}
15
16interface IUniswapV2Router02 {
17 function addLiquidityETH(
18 address token,
19 uint amountTokenDesired,
20 uint amountTokenMin,
21 uint amountETHMin,
22 address to,
23 uint deadline
24 ) 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 deadline
31 ) external returns (uint[] memory amounts);
32 function WETH() external pure returns (address);
33}
34
35/// @title ISaveReceiver
36/// @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 ETH
40 /// @param amount The amount of SAVE received
41 /// @param data Arbitrary data passed by the buyer
42 /// @return selector Must return ISaveReceiver.onSaveReceived.selector to confirm handling
43 function onSaveReceived(
44 address buyer,
45 uint256 amount,
46 bytes calldata data
47 ) external returns (bytes4);
48}
49
50/// @title ISaveSaleV2
51/// @notice Interface for SaveSale V2 — used for EIP-165 discoverability
52interface 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 s
60 ) external;
61 function buyBatch(address[] calldata recipients, uint256[] calldata ethAmounts, uint256 minSaveOutTotal) external payable;
62}
63
64/// @title SaveSale V2
65/// @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 tx
70/// ✅ Added `buyToWithCallback(recipient, data)` — notify recipient contracts
71/// ✅ Added `buyWithToken(token, amount)` — buy SAVE with any ERC-20
72/// ✅ Added `buyWithTokenPermit()` — gasless token approval + buy in one tx
73/// ✅ Added `buyBatch()` — institutional distribution
74/// ✅ Added `receive()` fallback — raw ETH sends auto-buy SAVE for sender
75/// ✅ Added referral tracking via events
76/// ✅ Richer events (recipient, NAV price, referral)
77/// ✅ EIP-165 interface discoverability
78/// ✅ 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 sends
81///
82/// UNCHANGED:
83/// - NAV-based pricing from Uniswap V2 pool reserves
84/// - 10% ETH → treasury operations, 90% ETH + SAVE → LP
85/// - Daily cap, price band
86/// - LP tokens → treasury
87///
88/// @dev North Star: FLAT as global reserve currency. SAVE is the locked, yield-bearing
89/// form of FLAT. Making SAVE easy to acquire from any source accelerates absorption.
90contract SaveSaleV2 is Ownable, ReentrancyGuard, ERC165 {
91 using SafeERC20 for IERC20;
92
93 // ============ 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;
101
102 // ============ CONSTANTS ============
103 uint256 public constant SLIPPAGE_BPS = 200; // 2% max slippage on LP add
104 uint256 public constant GUARDIAN_DURATION = 1095 days; // 3 years
105 uint256 public constant MAX_BATCH_SIZE = 100; // Max recipients per batch
106
107 // ============ 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;
117
118 // ============ 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 referralCode
127 );
128 event BatchBought(
129 address indexed buyer,
130 uint256 totalSave,
131 uint256 totalEthPaid,
132 uint256 recipientCount,
133 bytes32 indexed referralCode
134 );
135 event TokenSwapped(
136 address indexed token,
137 uint256 tokenAmount,
138 uint256 ethReceived
139 );
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();
146
147 // ============ CONSTRUCTOR ============
148 constructor(
149 address _save,
150 address _router,
151 address _pair,
152 address _treasury,
153 uint256 _dailyCap,
154 uint256 _minNAV,
155 uint256 _maxNAV
156 ) 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");
162
163 SAVE = IERC20(_save);
164 router = IUniswapV2Router02(_router);
165 pair = IUniswapV2Pair(_pair);
166 treasury = _treasury;
167 WETH = router.WETH();
168
169 saveIsToken0 = (pair.token0() == _save);
170
171 dailyCap = _dailyCap;
172 minNAV = _minNAV;
173 maxNAV = _maxNAV;
174 lastReset = block.timestamp;
175 maxBuyPerTx = 0;
176
177 guardianExpiry = block.timestamp + GUARDIAN_DURATION;
178 }
179
180 // ============ MODIFIERS ============
181 modifier whenNotPaused() {
182 require(!paused, "paused");
183 _;
184 }
185
186 modifier onlyGuardian() {
187 require(msg.sender == owner(), "not owner");
188 require(block.timestamp <= guardianExpiry, "guardian expired");
189 _;
190 }
191
192 // ============ EIP-165 ============
193
194 function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
195 return
196 interfaceId == type(ISaveSaleV2).interfaceId ||
197 super.supportsInterface(interfaceId);
198 }
199
200 // ============ INTERNAL HELPERS ============
201
202 /// @dev Get current NAV (ETH per SAVE) from V2 pool reserves
203 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 }
212
213 /// @dev Core buy logic. Returns the amount of SAVE purchased.
214 function _executeBuy(
215 address recipient,
216 uint256 ethAmount,
217 uint256 minSaveOut,
218 bytes32 referralCode
219 ) internal returns (uint256 saveAmount) {
220 require(ethAmount >= 0.001 ether, "min 0.001 ETH");
221 require(recipient != address(0), "zero recipient");
222
223 // Daily cap reset
224 if (block.timestamp >= lastReset + 1 days) {
225 soldToday = 0;
226 lastReset = block.timestamp;
227 emit DailyCapReset(block.timestamp);
228 }
229
230 // Get current NAV from pool
231 uint256 nav = _getCurrentNAV();
232 require(nav >= minNAV && nav <= maxNAV, "price outside band");
233
234 // Calculate SAVE amount at exact NAV (1.0x, no premium)
235 saveAmount = (ethAmount * 1e18) / nav;
236
237 // Slippage protection
238 require(saveAmount >= minSaveOut, "slippage: insufficient output");
239
240 // Per-tx limit
241 if (maxBuyPerTx > 0) {
242 require(saveAmount <= maxBuyPerTx, "exceeds max per tx");
243 }
244
245 // Daily cap
246 require(soldToday + saveAmount <= dailyCap, "daily cap exceeded");
247
248 // Revenue split: 10% operations, 90% LP
249 uint256 treasuryEth = ethAmount / 10;
250 uint256 polEth = ethAmount - treasuryEth;
251
252 // Matching SAVE for liquidity
253 uint256 polSave = (polEth * 1e18) / nav;
254
255 // Inventory check
256 uint256 totalSaveNeeded = saveAmount + polSave;
257 require(SAVE.balanceOf(address(this)) >= totalSaveNeeded, "inventory too low");
258
259 // Update state
260 soldToday += saveAmount;
261 totalSold += saveAmount;
262 totalPOL += polEth;
263
264 // Transfer SAVE to recipient
265 SAVE.safeTransfer(recipient, saveAmount);
266
267 // Add liquidity: 90% ETH + matching SAVE → V2 pool
268 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 treasury
277 block.timestamp
278 );
279
280 // Send 10% ETH to treasury
281 (bool success, ) = payable(treasury).call{value: treasuryEth}("");
282 require(success, "treasury transfer failed");
283
284 // Emit rich event
285 emit Bought(
286 msg.sender,
287 recipient,
288 saveAmount,
289 ethAmount,
290 polEth,
291 nav,
292 referralCode
293 );
294 }
295
296 // ============ PUBLIC BUY FUNCTIONS ============
297
298 /// @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 }
303
304 /// @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 tokens
307 /// @param minSaveOut Minimum SAVE to receive (sandwich protection)
308 function buyTo(
309 address recipient,
310 uint256 minSaveOut
311 ) external payable nonReentrant whenNotPaused {
312 _executeBuy(recipient, msg.value, minSaveOut, bytes32(0));
313 }
314
315 /// @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 callback
320 function buyToWithCallback(
321 address recipient,
322 uint256 minSaveOut,
323 bytes calldata data
324 ) external payable nonReentrant whenNotPaused {
325 uint256 saveAmount = _executeBuy(recipient, msg.value, minSaveOut, bytes32(0));
326
327 // If recipient is a contract, notify it
328 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 }
333
334 /// @notice Buy SAVE with referral tracking.
335 /// @param recipient Address to receive the SAVE tokens
336 /// @param minSaveOut Minimum SAVE to receive
337 /// @param referralCode 32-byte referral identifier
338 function buyToWithReferral(
339 address recipient,
340 uint256 minSaveOut,
341 bytes32 referralCode
342 ) external payable nonReentrant whenNotPaused {
343 _executeBuy(recipient, msg.value, minSaveOut, referralCode);
344 }
345
346 /// @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 spend
349 /// @param minSaveOut Minimum SAVE to receive (covers both swap + buy slippage)
350 /// @param recipient Address to receive the SAVE tokens
351 function buyWithToken(
352 address token,
353 uint256 tokenAmount,
354 uint256 minSaveOut,
355 address recipient
356 ) 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");
360
361 // Pull tokens from caller
362 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);
363
364 // Swap token → ETH via Uniswap
365 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 end
373 path,
374 address(this),
375 block.timestamp
376 );
377 uint256 ethReceived = address(this).balance - ethBefore;
378 require(ethReceived > 0, "swap returned zero ETH");
379
380 emit TokenSwapped(token, tokenAmount, ethReceived);
381
382 // Execute buy with the received ETH
383 _executeBuy(recipient, ethReceived, minSaveOut, bytes32(0));
384 }
385
386 /// @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 s
396 ) external nonReentrant whenNotPaused {
397 // Execute permit (gasless approval)
398 IERC20Permit(token).permit(msg.sender, address(this), tokenAmount, deadline, v, r, s);
399
400 // Pull tokens
401 IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);
402
403 // Swap token → ETH
404 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.timestamp
415 );
416 uint256 ethReceived = address(this).balance - ethBefore;
417 require(ethReceived > 0, "swap returned zero ETH");
418
419 emit TokenSwapped(token, tokenAmount, ethReceived);
420
421 // Execute buy
422 _executeBuy(recipient, ethReceived, minSaveOut, bytes32(0));
423 }
424
425 /// @notice Buy SAVE for multiple recipients in one transaction.
426 /// Enables: payroll, rewards distribution, institutional allocation.
427 /// @param recipients Array of addresses to receive SAVE
428 /// @param ethAmounts Array of ETH amounts to spend per recipient (must sum to msg.value)
429 /// @param minSaveOutTotal Minimum total SAVE across all recipients
430 function buyBatch(
431 address[] calldata recipients,
432 uint256[] calldata ethAmounts,
433 uint256 minSaveOutTotal
434 ) 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");
437
438 // Verify ETH amounts sum to msg.value
439 uint256 totalEth;
440 for (uint256 i = 0; i < ethAmounts.length; i++) {
441 totalEth += ethAmounts[i];
442 }
443 require(totalEth == msg.value, "ETH sum mismatch");
444
445 // Execute individual buys
446 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 }
451
452 // Aggregate slippage check
453 require(totalSave >= minSaveOutTotal, "batch slippage: insufficient total output");
454
455 emit BatchBought(msg.sender, totalSave, msg.value, recipients.length, bytes32(0));
456 }
457
458 /// @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 referralCode
464 ) 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");
467
468 uint256 totalEth;
469 for (uint256 i = 0; i < ethAmounts.length; i++) {
470 totalEth += ethAmounts[i];
471 }
472 require(totalEth == msg.value, "ETH sum mismatch");
473
474 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 }
479
480 require(totalSave >= minSaveOutTotal, "batch slippage: insufficient total output");
481
482 emit BatchBought(msg.sender, totalSave, msg.value, recipients.length, referralCode);
483 }
484
485 // ============ RECEIVE: RAW ETH → AUTO-BUY ============
486
487 /// @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 treasury
494 if (msg.sender == treasury) return;
495 // Skip tiny amounts (dust from router refunds)
496 if (msg.value < 0.001 ether) return;
497
498 // Auto-buy with no slippage protection (use buy() for that)
499 if (!paused) {
500 _executeBuy(msg.sender, msg.value, 0, bytes32(0));
501 }
502 }
503
504 // ============ VIEW FUNCTIONS ============
505
506 /// @notice Get current NAV (ETH per SAVE) from V2 pool reserves
507 function getCurrentNAV() public view returns (uint256) {
508 return _getCurrentNAV();
509 }
510
511 /// @notice Check how much SAVE the contract currently holds for sale
512 function availableInventory() external view returns (uint256) {
513 return SAVE.balanceOf(address(this));
514 }
515
516 /// @notice Estimate SAVE output for a given ETH input at current NAV.
517 /// @param ethAmount The ETH amount to quote
518 /// @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 }
523
524 /// @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 }
532
533 function version() external pure returns (string memory) {
534 return "SaveSale_v2";
535 }
536
537 // ============ CATEGORY 1: GUARDIAN FUNCTIONS (expire after 3 years) ============
538
539 function setPaused(bool _paused) external onlyGuardian {
540 paused = _paused;
541 if (_paused) emit Paused();
542 else emit Unpaused();
543 }
544
545 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 }
551
552 function setDailyCap(uint256 _newCap) external onlyGuardian {
553 dailyCap = _newCap;
554 emit DailyCapUpdated(_newCap);
555 }
556
557 function setMaxBuyPerTx(uint256 _max) external onlyGuardian {
558 maxBuyPerTx = _max;
559 emit MaxBuyPerTxUpdated(_max);
560 }
561
562 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 }
568
569 function sweepSAVE(uint256 _amount) external onlyGuardian {
570 SAVE.safeTransfer(treasury, _amount);
571 }
572
573 /// @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}

FlatBearer.sol

Tier 1.5 — Guardian-Protected (3yr expiry)

View on Etherscan Verified Guardian: 3yr

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: MIT
2pragma solidity ^0.8.20;
3
4import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6
7/**
8 * @title FlatBearer
9 * @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/unpause
18 * 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 immutable
27 * (unless permanently shut down, in which case only reclaim works).
28 */
29contract FlatBearer {
30 using SafeERC20 for IERC20;
31
32 // Constants
33 uint256 public constant RECLAIM_DELAY = 30 days;
34 uint256 public constant GUARDIAN_PERIOD = 1095 days; // 3 years
35 uint256 public constant SHUTDOWN_DELAY = 48 hours;
36
37 // Immutables (set once at deployment)
38 IERC20 public immutable flat;
39 address public immutable guardian;
40 uint256 public immutable guardianExpiry;
41
42 // Mutable State
43 bool public paused;
44 bool public permanentlyShutdown;
45 uint256 public shutdownScheduledAt;
46 uint256 public pendingCount;
47
48 struct Commitment {
49 address sender;
50 uint256 amount;
51 uint256 timestamp;
52 bool settled;
53 }
54
55 mapping(bytes32 => Commitment) public commitments;
56
57 // Events
58 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);
67
68 // Errors
69 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();
84
85 // Modifiers
86 modifier whenNotPaused() {
87 if (permanentlyShutdown) revert AlreadyShutdown();
88 if (paused && block.timestamp < guardianExpiry) revert ContractPaused();
89 _;
90 }
91
92 modifier onlyGuardian() {
93 if (msg.sender != guardian) revert NotGuardian();
94 if (block.timestamp >= guardianExpiry) revert GuardianExpired();
95 _;
96 }
97
98 // Constructor
99 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 }
107
108 // Core Functions
109 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: false
118 });
119 pendingCount++;
120 emit Committed(hash, amount);
121 }
122
123 function reveal(
124 bytes32 secret,
125 address recipient,
126 uint256 amount,
127 uint256 nonce
128 ) 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 }
141
142 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 }
154
155 // Guardian Functions
156 function pause() external onlyGuardian {
157 paused = true;
158 emit Paused(msg.sender);
159 }
160
161 function unpause() external onlyGuardian {
162 paused = false;
163 emit Unpaused(msg.sender);
164 }
165
166 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 }
173
174 function cancelShutdown() external onlyGuardian {
175 if (shutdownScheduledAt == 0) revert ShutdownNotScheduled();
176 shutdownScheduledAt = 0;
177 paused = false;
178 emit ShutdownCancelled(msg.sender);
179 }
180
181 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 }
188
189 // View Functions
190 function isGuardianActive() external view returns (bool) {
191 return block.timestamp < guardianExpiry;
192 }
193
194 function isShutdown() external view returns (bool) {
195 return permanentlyShutdown;
196 }
197}

FlatIDVault.sol

Tier 2 — Admin-Controlled Vault

View on Etherscan Verified Guardian: 3yr

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: MIT
2pragma solidity ^0.8.20;
3
4// Minimal IERC20 interface
5interface 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}
13
14// 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}
25
26library SafeERC20 {
27 using Address for address;
28
29 function safeTransfer(IERC20 token, address to, uint256 value) internal {
30 _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
31 }
32
33 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}
40
41contract FlatIDVault {
42 using SafeERC20 for IERC20;
43
44 // State
45 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;
52
53 // Events
54 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);
60
61 modifier onlyAdmin() {
62 require(msg.sender == admin, "FlatIDVault: not admin");
63 _;
64 }
65
66 modifier notPaused() {
67 require(!paused, "FlatIDVault: paused");
68 _;
69 }
70
71 constructor(
72 address _flatToken,
73 address _admin,
74 address _guardian,
75 uint256 _dailyLimit
76 ) {
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");
81
82 flatToken = IERC20(_flatToken);
83 admin = _admin;
84 guardian = _guardian;
85 dailyLimit = _dailyLimit;
86 lastResetDay = block.timestamp / 1 days;
87 }
88
89 function withdraw(address to, uint256 amount) external onlyAdmin notPaused {
90 require(to != address(0), "FlatIDVault: zero recipient");
91 require(amount > 0, "FlatIDVault: zero amount");
92
93 uint256 today = block.timestamp / 1 days;
94 if (today > lastResetDay) {
95 spentToday = 0;
96 lastResetDay = today;
97 }
98
99 require(spentToday + amount <= dailyLimit, "FlatIDVault: daily limit exceeded");
100 spentToday += amount;
101
102 flatToken.safeTransfer(to, amount);
103 emit Withdrawal(to, amount);
104 }
105
106 function pause() external {
107 require(msg.sender == admin || msg.sender == guardian, "FlatIDVault: not authorized");
108 paused = true;
109 emit Paused(msg.sender);
110 }
111
112 function unpause() external onlyAdmin {
113 paused = false;
114 emit Unpaused(msg.sender);
115 }
116
117 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 }
125
126 function setAdmin(address newAdmin) external onlyAdmin {
127 require(newAdmin != address(0), "FlatIDVault: zero admin");
128 emit AdminChanged(admin, newAdmin);
129 admin = newAdmin;
130 }
131
132 function setGuardian(address newGuardian) external onlyAdmin {
133 require(newGuardian != address(0), "FlatIDVault: zero guardian");
134 emit GuardianChanged(guardian, newGuardian);
135 guardian = newGuardian;
136 }
137
138 function vaultBalance() external view returns (uint256) {
139 return flatToken.balanceOf(address(this));
140 }
141
142 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}

FlatIDSaveVault.sol

Tier 2 — Admin-Controlled Vault

View on Etherscan Verified Guardian: 3yr

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: MIT
2pragma solidity ^0.8.20;
3
4// Minimal IERC20 interface
5interface 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}
13
14// 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}
25
26library SafeERC20 {
27 using Address for address;
28
29 function safeTransfer(IERC20 token, address to, uint256 value) internal {
30 _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
31 }
32
33 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}
40
41contract FlatIDSaveVault {
42 using SafeERC20 for IERC20;
43
44 // State
45 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;
52
53 // Events
54 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);
60
61 modifier onlyAdmin() {
62 require(msg.sender == admin, "FlatIDSaveVault: not admin");
63 _;
64 }
65
66 modifier notPaused() {
67 require(!paused, "FlatIDSaveVault: paused");
68 _;
69 }
70
71 constructor(
72 address _saveToken,
73 address _admin,
74 address _guardian,
75 uint256 _dailyLimit
76 ) {
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");
81
82 saveToken = IERC20(_saveToken);
83 admin = _admin;
84 guardian = _guardian;
85 dailyLimit = _dailyLimit;
86 lastResetDay = block.timestamp / 1 days;
87 }
88
89 function withdraw(address to, uint256 amount) external onlyAdmin notPaused {
90 require(to != address(0), "FlatIDSaveVault: zero recipient");
91 require(amount > 0, "FlatIDSaveVault: zero amount");
92
93 uint256 today = block.timestamp / 1 days;
94 if (today > lastResetDay) {
95 spentToday = 0;
96 lastResetDay = today;
97 }
98
99 require(spentToday + amount <= dailyLimit, "FlatIDSaveVault: daily limit exceeded");
100 spentToday += amount;
101
102 saveToken.safeTransfer(to, amount);
103 emit Withdrawal(to, amount);
104 }
105
106 function pause() external {
107 require(msg.sender == admin || msg.sender == guardian, "FlatIDSaveVault: not authorized");
108 paused = true;
109 emit Paused(msg.sender);
110 }
111
112 function unpause() external onlyAdmin {
113 paused = false;
114 emit Unpaused(msg.sender);
115 }
116
117 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 }
125
126 function setAdmin(address newAdmin) external onlyAdmin {
127 require(newAdmin != address(0), "FlatIDSaveVault: zero admin");
128 emit AdminChanged(admin, newAdmin);
129 admin = newAdmin;
130 }
131
132 function setGuardian(address newGuardian) external onlyAdmin {
133 require(newGuardian != address(0), "FlatIDSaveVault: zero guardian");
134 emit GuardianChanged(guardian, newGuardian);
135 guardian = newGuardian;
136 }
137
138 function vaultBalance() external view returns (uint256) {
139 return saveToken.balanceOf(address(this));
140 }
141
142 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}

Interface Files

Shared interface definitions used by FlatEngine and FlatSale. These define the external contract dependencies (Chainlink, Uniswap V2, CPIOracle).

External Dependencies

OpenZeppelin Contracts 5.x

ERC20, ERC20Permit, Ownable, ReentrancyGuard, SafeERC20

Chainlink ETH/USD Feed

0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419

Uniswap V2 Router

0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D

Uniswap V2 Factory

0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f