false
true
0
PulseChain
Blockchain
Blocks
Blocks
Uncles
Forked Blocks (Reorgs)
Transactions
Confirmed
Pending
Verified contracts
Tokens
All
PLS
APIs
GraphQL
RPC
Eth RPC
Apps
PulseChain Beacon Explorer
PulseX
PulseChain Bridge
Become a Validator
PulseChain
Mainnets
PulseChain Mainnet
Testnets
Testnet V4
/
Search
/
Search
Connection Lost
New Solidity Smart Contract Verification
Contract Address
The 0x address supplied on contract creation.
Is Yul contract
No
Yes
Select Yes if you want to verify Yul contract.
Contract Name
Must match the name specified in the code. For example, in
contract MyContract {..}
MyContract
is the contract name.
Include nightly builds
No
Yes
Select yes if you want to show nightly builds.
Compiler
v0.8.36+commit.8a079791
v0.8.35+commit.47b9dedd
v0.8.35-pre.1+commit.a99b6d8c
v0.8.34+commit.80d5c536
v0.8.33+commit.64118f21
v0.8.32+commit.ebbd65e5
v0.8.31+commit.fd3a2265
v0.8.31-pre.1+commit.b59566f6
v0.8.30+commit.73712a01
v0.8.29+commit.ab55807c
v0.8.28+commit.7893614a
v0.8.27+commit.40a35a09
v0.8.26+commit.8a97fa7a
v0.8.25+commit.b61c2a91
v0.8.24+commit.e11b9ed9
v0.8.23+commit.f704f362
v0.8.22+commit.4fc1097e
v0.8.21+commit.d9974bed
v0.8.20+commit.a1b79de6
v0.8.19+commit.7dd6d404
v0.8.18+commit.87f61d96
v0.8.17+commit.8df45f5f
v0.8.16+commit.07a7930e
v0.8.15+commit.e14f2714
v0.8.14+commit.80d49f37
v0.8.13+commit.abaa5c0e
v0.8.12+commit.f00d7308
v0.8.11+commit.d7f03943
v0.8.10+commit.fc410830
v0.8.9+commit.e5eed63a
v0.8.8+commit.dddeac2f
v0.8.7+commit.e28d00a7
v0.8.6+commit.11564f7e
v0.8.5+commit.a4f2e591
v0.8.4+commit.c7e474f2
v0.8.3+commit.8d00100c
v0.8.2+commit.661d1103
v0.8.1+commit.df193b15
v0.8.0+commit.c7dfd78e
v0.7.6+commit.7338295f
v0.7.5+commit.eb77ed08
v0.7.4+commit.3f05b770
v0.7.3+commit.9bfce1f6
v0.7.2+commit.51b20bc0
v0.7.1+commit.f4a555be
v0.7.0+commit.9e61f92b
v0.6.12+commit.27d51765
v0.6.11+commit.5ef660b1
v0.6.10+commit.00c0fcaf
v0.6.9+commit.3e3065ac
v0.6.8+commit.0bbfe453
v0.6.7+commit.b8d736ae
v0.6.6+commit.6c089d02
v0.6.5+commit.f956cc89
v0.6.4+commit.1dca32f3
v0.6.3+commit.8dda9521
v0.6.2+commit.bacdbe57
v0.6.1+commit.e6f7d5a4
v0.6.0+commit.26b70077
v0.5.17+commit.d19bba13
v0.5.16+commit.9c3226ce
v0.5.15+commit.6a57276f
v0.5.14+commit.01f1aaa4
v0.5.13+commit.5b0b510c
v0.5.12+commit.7709ece9
v0.5.11+commit.22be8592
v0.5.10+commit.5a6ea5b1
v0.5.9+commit.c68bc34e
v0.5.8+commit.23d335f2
v0.5.7+commit.6da8b019
v0.5.6+commit.b259423e
v0.5.5+commit.47a71e8f
v0.5.4+commit.9549d8ff
v0.5.3+commit.10d17f24
v0.5.2+commit.1df8f40c
v0.5.1+commit.c8a2cb62
v0.5.0+commit.1d4f565a
v0.4.26+commit.4563c3fc
v0.4.25+commit.59dbf8f1
v0.4.24+commit.e67f0147
v0.4.23+commit.124ca40d
v0.4.22+commit.4cb486ee
v0.4.21+commit.dfe3193c
v0.4.20+commit.3155dd80
v0.4.19+commit.c4cbbb05
v0.4.18+commit.9cf6e910
v0.4.17+commit.bdeb9e52
v0.4.16+commit.d7661dd9
v0.4.15+commit.8b45bddb
v0.4.14+commit.c2215d46
v0.4.13+commit.0fb4cb1a
v0.4.12+commit.194ff033
v0.4.11+commit.68ef5810
v0.4.10+commit.9e8cc01b
The compiler version is specified in
pragma solidity X.X.X
. Use the compiler version rather than the nightly build. If using the Solidity compiler, run
solc —version
to check.
EVM Version
homestead
tangerineWhistle
spuriousDragon
byzantium
constantinople
petersburg
istanbul
berlin
london
paris
shanghai
default
The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version.
EVM version details
.
Optimization
No
Yes
If you enabled optimization during compilation, select yes.
Optimization runs
Enter the Solidity Contract Code
// SPDX-License-Identifier: MIT pragma solidity 0.8.26; /* ============================ interfaces ============================ */ interface IBase { function transfer(address to, uint256 v) external returns (bool); function transferFrom(address f, address t, uint256 v) external returns (bool); function balanceOf(address a) external view returns (uint256); function approve(address s, uint256 v) external returns (bool); } interface IBaseBurn { function burn(uint256 amount) external; } interface IScoreOracle { function scoreOf(address user) external view returns (uint16); } interface IFuseRouter { function factory() external view returns (address); function addLiquidity( address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity); } /* the live pMINT vault NFTs, read only. cvw is the conviction value weight per position. */ interface IReserveNFTr { function ownerOf(uint256 tokenId) external view returns (address); function getReserve(uint256 tokenId) external view returns (uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256); function totalCV() external view returns (uint256); } interface IDiamondNFTr { function ownerOf(uint256 tokenId) external view returns (address); function positions(uint256 tokenId) external view returns (uint256,uint128,uint256,uint256,uint256,uint256,uint256,bool,uint256); function totalCV() external view returns (uint256); } interface IConvictionNFTr { struct AssetView { bool isApproved; bool isCoreAsset; uint256[8] amountTiers; uint256 minSponsorship; uint256 totalLocked; uint256 totalCV; uint256 positionsActive; uint256 positionsPending; uint256 positionsAllTime; uint256 emergencyExits; uint256 matureExits; uint256 approvedAt; } function ownerOf(uint256 tokenId) external view returns (address); function positions(uint256 tokenId) external view returns ( address asset, uint128 amount, uint128 cv, uint64 startTime, uint64 unlockTime, uint32 durationDays, uint16 amountTier, uint16 durationTier, bool closed, bool activated, address referrer ); function getAssetConfig(address asset) external view returns (AssetView memory); } interface IFeeVault { function deposit(address token, uint16 mask, uint256 amount) external; } interface ILiquidityVault { function register(address token, uint256 amount, uint16 legMask) external; } /* ============================ economics config ============================ */ /* Every payout number a launch inherits. The factory holds the live config, the treasury can retune it inside hard bounds, and each token copies it into immutables at birth. Tunable going forward, carved in stone once launched. A key can steer the platform, it can never change the rules on a token someone already bought. */ struct EconConfig { uint16 feeBps; // trade fee each way uint16 feeCreatorBps; // slices of the fee, must sum to 10000 uint16 feeBurnBps; uint16 feeLockerBps; uint16 feeVaultBps; uint16 gradBurnBps; // graduation cuts, the rest pairs into the pool uint16 gradTreasuryBps; uint16 gradCreatorBps; uint16 gradIgniteBps; uint16 gradTokenTreasuryBps; // of total supply, in the token, carved from the reserve } /* ============================ fuse token ============================ */ contract FuseToken { string public name; string public symbol; string public tokenURI; // the meme image and metadata, readable straight off the token uint8 public constant decimals = 18; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); constructor(string memory n, string memory s, string memory uri_, address mintTo, uint256 supply) { name = n; symbol = s; tokenURI = uri_; totalSupply = supply; balanceOf[mintTo] = supply; emit Transfer(address(0), mintTo, supply); } function transfer(address to, uint256 v) external returns (bool) { return _move(msg.sender, to, v); } function transferFrom(address f, address t, uint256 v) external returns (bool) { uint256 a = allowance[f][msg.sender]; if (a != type(uint256).max) { require(a >= v, "allowance"); allowance[f][msg.sender] = a - v; } return _move(f, t, v); } function approve(address s, uint256 v) external returns (bool) { allowance[msg.sender][s] = v; emit Approval(msg.sender, s, v); return true; } function _move(address f, address t, uint256 v) internal returns (bool) { require(t != address(0), "to zero"); uint256 b = balanceOf[f]; require(b >= v, "balance"); unchecked { balanceOf[f] = b - v; balanceOf[t] += v; } emit Transfer(f, t, v); return true; } } /* ============================ curve ============================ */ contract FuseCurve { /* ----- immutables ----- */ IBase public immutable base; // the base asset, CV on PulseChain IScoreOracle public immutable oracle; // read for one thing only, the wallet cap ladder IFuseRouter public immutable router; address public immutable treasury; address public immutable factory; // the deployer that birthed this curve address public immutable launcher; // the FuseFactory that drives creation and starters address public immutable creator; address public immutable feeVault; // where the vault share of every fee lands, in CV address public immutable liquidityVault; // streams LP mining rewards after graduation uint16 public immutable rewardMask; // which NFT groups this token rewards uint16 public immutable maxWalletBps; // the cap a top score takes, the ladder ceiling uint16 public constant ANON_CAP_BPS = 49; // the no score taste, just under half a percent uint16 public immutable memeDropBps; // slice of total supply showered on the vault at ignition uint16 public immutable lpRewardBps; // slice carved to reward liquidity providers uint16 public immutable lpRewardMask; // which pairing legs the creator chose to reward bool public immutable baseBurnable; // true: base has a public burn, supply really shrinks address public constant DEAD = 0x000000000000000000000000000000000000dEaD; uint256 public constant BPS = 10_000; uint256 public immutable FEE_BPS; // trade fee each way, copied from the factory config at birth uint256 public immutable FEE_CREATOR_BPS; uint256 public immutable FEE_BURN_BPS; uint256 public immutable FEE_LOCKER_BPS; uint256 public immutable FEE_VAULT_BPS; uint256 public constant FLIP_FEE_BPS = 500; // quick flip surcharge at t zero, decays to nothing uint256 public constant FLIP_WINDOW = 24 hours; uint256 public immutable GRAD_IGNITE_BPS; uint256 public immutable GRAD_CREATOR_BPS; uint256 public constant BREAK_BPS = 3_000; // 30 percent penalty, part of the doctrine, never tuned uint256 public immutable GRAD_BURN_BPS; uint256 public immutable GRAD_TREASURY_BPS; uint256 public immutable GRAD_TOKEN_TREASURY_BPS; uint256 public constant ACC = 1e12; uint256 public immutable curveSupply; // sellable inventory at birth uint256 public immutable gradReserve; // paired at ignition uint256 public immutable vBase0; // virtual base at birth uint256 public immutable vTok0; // virtual token at birth /* ----- token, set once by the factory ----- */ FuseToken public token; /* ----- curve state ----- */ uint256 public baseCollected; // net CV inside the curve backing the math uint256 public tokensSold; // via curve math, liquid plus locked principal uint256 public inventory; // real sellable tokens remaining, includes bonus drain bool public ignited; bool public panic; // exit only mode, flips if the books ever disagree /* ----- locks ----- */ struct Lock { address owner; uint128 amount; // principal plus bonus, all vesting uint128 weight; // amount times the tier multiplier, longer locks earn more loyalty uint64 unlock; uint256 rewardDebt; // loyalty checkpoint at entry uint256 cvDebt; // CV stream checkpoint at entry bool closed; } Lock[] public locks; uint256 public totalLocked; // sum of open lock amounts, token solvency uint256 public totalLockWeight; // sum of open lock weights, loyalty math uint256 public loyaltyPot; // seized tokens waiting to be claimed uint256 public accLoyal; // loyalty per unit of lock weight, scaled by ACC uint256 public creatorOwed; // base fees waiting for the creator, pull based uint256 public vaultBuffer; // vault share parked here if a vault deposit ever fails mapping(address => uint256) public curveBought; // per wallet curve accumulation, cap enforced mapping(address => uint256) public lastBuyBlock; // same block flip guard mapping(address => uint64) public lastBuyAt; // quick flip fee timer uint256 public accLockCv; // CV per unit of lock weight, trade fees plus the ignition bonus uint256 public cvLockPot; // CV owed to locked holders, not yet claimed uint256 public creatorLockId1; // the creator's auto locked starter buy, id plus one, vests at ignition /* ----- genesis window: pooled buys, one uniform price, snipers get nothing ----- */ uint64 public genesisEnd; // zero means no window bool public genesisSettled; uint256 public genesisPool; // net CV escrowed during the window uint256 public genesisFeePool; // fees escrowed, routed once at settle uint256 public genesisOut; // tokens cleared at the uniform price uint256 public genesisClaimed; bool public starterInGenesis; // the creator's starter went through the window mapping(address => uint256) public genesisContrib; uint256 private _gate; modifier nonReentrant() { require(_gate == 0, "reentrant"); _gate = 1; _; _gate = 0; } modifier live() { require(!ignited, "ignited"); require(!panic, "panic"); _; } event Bought(address indexed who, uint256 baseIn, uint256 tokensOut, uint8 tier, uint256 bonus, uint256 lockId); event Sold(address indexed who, uint256 tokensIn, uint256 baseOut); event Claimed(address indexed who, uint256 lockId, uint256 tokens, uint256 loyalty); event Broke(address indexed who, uint256 lockId, uint256 returned, uint256 burned, uint256 toLoyal); event Ignited(uint256 baseToPool, uint256 tokensToPool, uint256 baseBurned, uint256 baseToTreasury, uint256 baseToCreator); event Panicked(); constructor( address _base, address _oracle, address _router, address _treasury, address _creator, address _launcher, address _feeVault, EconConfig memory econ, uint16 _rewardMask, uint16 _maxWalletBps, uint16 _memeDropBps, uint32 _genesisSecs, address _liquidityVault, uint16 _lpRewardBps, uint16 _lpRewardMask, bool _baseBurnable, uint256 _curveSupply, uint256 _gradReserve, uint256 _vBase0, uint256 _vTok0 ) { base = IBase(_base); oracle = IScoreOracle(_oracle); router = IFuseRouter(_router); treasury = _treasury; creator = _creator; launcher = _launcher; feeVault = _feeVault; rewardMask = _rewardMask; maxWalletBps = _maxWalletBps; memeDropBps = _memeDropBps; liquidityVault = _liquidityVault; lpRewardBps = _lpRewardBps; lpRewardMask = _lpRewardMask; baseBurnable = _baseBurnable; factory = msg.sender; FEE_BPS = econ.feeBps; FEE_CREATOR_BPS = econ.feeCreatorBps; FEE_BURN_BPS = econ.feeBurnBps; FEE_LOCKER_BPS = econ.feeLockerBps; FEE_VAULT_BPS = econ.feeVaultBps; GRAD_BURN_BPS = econ.gradBurnBps; GRAD_TREASURY_BPS = econ.gradTreasuryBps; GRAD_CREATOR_BPS = econ.gradCreatorBps; GRAD_IGNITE_BPS = econ.gradIgniteBps; GRAD_TOKEN_TREASURY_BPS = econ.gradTokenTreasuryBps; if (_genesisSecs > 0) genesisEnd = uint64(block.timestamp + _genesisSecs); IBase(_base).approve(_feeVault, type(uint256).max); curveSupply = _curveSupply; gradReserve = _gradReserve; vBase0 = _vBase0; vTok0 = _vTok0; inventory = _curveSupply; } uint256 public memeDrop; // carved at birth, showered on the vaults at ignition uint256 public lpRewardReserve; // carved at birth, streams to PLS LP providers after ignition function init(address t) external { require(msg.sender == factory, "factory only"); require(address(token) == address(0), "set"); token = FuseToken(t); if (memeDropBps > 0) { memeDrop = (token.totalSupply() * memeDropBps) / BPS; inventory -= memeDrop; require(token.approve(feeVault, memeDrop), "drop approve"); } if (lpRewardBps > 0 && lpRewardMask != 0) { lpRewardReserve = (token.totalSupply() * lpRewardBps) / BPS; inventory -= lpRewardReserve; } } /* ----- pricing views ----- */ function virtualBase() public view returns (uint256) { return vBase0 + baseCollected; } function virtualTok() public view returns (uint256) { return vTok0 - tokensSold; } /// tokens out for a net base amount, before inventory checks function quoteBuy(uint256 baseInNet) public view returns (uint256) { uint256 vb = virtualBase(); uint256 vt = virtualTok(); uint256 k = vb * vt; return vt - (k / (vb + baseInNet)); } /// net base out for tokens in function quoteSell(uint256 tokensIn) public view returns (uint256) { uint256 vb = virtualBase(); uint256 vt = virtualTok(); uint256 k = vb * vt; return vb - (k / (vt + tokensIn)); } /// score sets your slice of the launch supply and nothing else. no score tastes at /// 0.49 percent, a top score of 850 takes the full cap, and every point in between buys /// an equal step. measured on the supply at birth, burns never shrink anyone's room. function walletCapBps(uint16 s) public view returns (uint256) { if (s <= 300) return ANON_CAP_BPS; if (s >= 850) return maxWalletBps; return ANON_CAP_BPS + ((uint256(s) - 300) * (uint256(maxWalletBps) - ANON_CAP_BPS)) / 550; } /* ----- lock bonus, flat by tier ----- */ function bonusBpsFor(uint8 tier) public pure returns (uint256) { if (tier == 0) return 0; return tier == 1 ? 200 : tier == 2 ? 500 : 1_000; } /* ----- buy ----- */ function buy(uint256 baseIn, uint8 tier, uint256 minOut) external nonReentrant live returns (uint256) { return _buy(msg.sender, msg.sender, baseIn, tier, minOut); } /// pay from msg.sender, position and tokens land on the recipient. /// the factory uses this for the atomic create and first buy. function buyFor(address recipient, uint256 baseIn, uint8 tier, uint256 minOut) external nonReentrant live returns (uint256) { require(recipient != address(0), "recipient"); return _buy(msg.sender, recipient, baseIn, tier, minOut); } function _buy(address payer, address recipient, uint256 baseIn, uint8 tier, uint256 minOut) internal returns (uint256 lockId) { require(address(token) != address(0), "not ready"); _settleGenesisIfDue(); require(baseIn > 0, "zero"); require(tier <= 3, "tier"); // credit what actually arrived, never what was promised uint256 balBefore = base.balanceOf(address(this)); require(base.transferFrom(payer, address(this), baseIn), "base in"); uint256 received = base.balanceOf(address(this)) - balBefore; require(received > 0, "nothing arrived"); uint256 fee = (received * FEE_BPS) / BPS; _routeBaseFee(fee); uint256 net = received - fee; uint256 out = quoteBuy(net); require(out > 0, "dust"); uint256 bBps = bonusBpsFor(tier); uint256 bonus = (out * bBps) / BPS; uint256 total = out + bonus; // final fill: cap to the remaining inventory and refund the unused base, // so the curve can always be bought to exactly zero and ignite if (total > inventory) { require(inventory > 0, "sold out, graduate"); total = inventory; out = (total * BPS) / (BPS + bBps); bonus = total - out; uint256 vb = virtualBase(); uint256 vt = virtualTok(); uint256 needNet = ((vb * vt) + (vt - out) - 1) / (vt - out) - vb; // ceil if (needNet < net) { uint256 refund = net - needNet; net = needNet; require(base.transfer(payer, refund), "refund"); } } require(total >= minOut, "slippage"); uint16 sc = 0; try oracle.scoreOf(recipient) returns (uint16 v) { sc = v; } catch {} curveBought[recipient] += total; require(curveBought[recipient] <= ((curveSupply + gradReserve) * walletCapBps(sc)) / BPS, "wallet cap"); if (payer == recipient) { // only your own buy stamps your flip clock, lastBuyBlock[recipient] = block.number; // a stranger's gift can never set it lastBuyAt[recipient] = uint64(block.timestamp); } baseCollected += net; tokensSold += out; // only the priced amount moves the math inventory -= total; // bonus drains real inventory toward ignition if (tier == 0) { require(token.transfer(recipient, total), "tok out"); emit Bought(recipient, baseIn, total, 0, bonus, type(uint256).max); return type(uint256).max; } uint64 unlockAt = uint64(block.timestamp + (tier == 1 ? 7 days : tier == 2 ? 30 days : 90 days)); uint256 w = total * (tier == 1 ? 1 : tier == 2 ? 2 : 4); locks.push(Lock({ owner: recipient, amount: uint128(total), weight: uint128(w), unlock: unlockAt, rewardDebt: (w * accLoyal) / ACC, cvDebt: (w * accLockCv) / ACC, closed: false })); totalLocked += total; totalLockWeight += w; // the starter the launcher routed in vests at ignition if (payer == launcher && creatorLockId1 == 0) creatorLockId1 = locks.length; emit Bought(recipient, baseIn, total, tier, bonus, locks.length - 1); return locks.length - 1; } /* ----- the genesis window ----- */ /// buy during the window. contributions pool and clear at one uniform price when it /// closes, so being first in the block is worth exactly nothing. function buyGenesis(uint256 baseIn) external nonReentrant live { _buyGenesis(msg.sender, msg.sender, baseIn); } function buyGenesisFor(address recipient, uint256 baseIn) public nonReentrant live { require(recipient != address(0), "recipient"); _buyGenesis(msg.sender, recipient, baseIn); } function _buyGenesis(address payer, address recipient, uint256 baseIn) internal { require(genesisEnd > 0 && !genesisSettled && block.timestamp < genesisEnd, "no window"); require(baseIn > 0, "zero"); uint256 balBefore = base.balanceOf(address(this)); require(base.transferFrom(payer, address(this), baseIn), "base in"); uint256 received = base.balanceOf(address(this)) - balBefore; require(received > 0, "nothing arrived"); uint256 fee = (received * FEE_BPS) / BPS; genesisFeePool += fee; uint256 net = received - fee; if (payer == launcher) starterInGenesis = true; // the creator took a starter genesisContrib[recipient] += net; // the solo quote is the MOST tokens this wallet could clear, more pool means fewer, // so bounding the solo quote guarantees the wallet cap holds at any final price uint16 sc = 0; try oracle.scoreOf(recipient) returns (uint16 v) { sc = v; } catch {} require(quoteBuy(genesisContrib[recipient]) <= ((curveSupply + gradReserve) * walletCapBps(sc)) / BPS, "wallet cap"); require(quoteBuy(genesisPool + net) <= inventory, "genesis full"); genesisPool += net; } function _settleGenesisIfDue() internal { if (genesisEnd == 0 || genesisSettled) return; require(block.timestamp >= genesisEnd, "genesis window"); _settleGenesis(); } /// close the window. one price for everyone, computed once. permissionless. function settleGenesis() external nonReentrant { require(genesisEnd > 0 && !genesisSettled, "no window"); require(block.timestamp >= genesisEnd, "still open"); _settleGenesis(); } function _settleGenesis() internal { genesisSettled = true; if (genesisPool == 0) return; uint256 out = quoteBuy(genesisPool); baseCollected += genesisPool; tokensSold += out; inventory -= out; genesisOut = out; _routeBaseFee(genesisFeePool); } /// pull your uniform price share after settle function claimGenesis() external nonReentrant { require(genesisSettled, "not settled"); uint256 c = genesisContrib[msg.sender]; require(c > 0, "nothing"); genesisContrib[msg.sender] = 0; uint256 share = (genesisOut * c) / genesisPool; genesisClaimed += share; curveBought[msg.sender] += share; lastBuyBlock[msg.sender] = block.number; lastBuyAt[msg.sender] = uint64(block.timestamp); if (msg.sender == creator && starterInGenesis) { // a creator who took a starter cannot receive it liquid. the whole genesis // share locks for 7 days and vests only at ignition, same promise, both paths. locks.push(Lock({ owner: msg.sender, amount: uint128(share), weight: uint128(share), unlock: uint64(block.timestamp + 7 days), rewardDebt: (share * accLoyal) / ACC, cvDebt: (share * accLockCv) / ACC, closed: false })); totalLocked += share; totalLockWeight += share; if (creatorLockId1 == 0) creatorLockId1 = locks.length; emit Bought(msg.sender, 0, share, 1, 0, locks.length - 1); return; } require(token.transfer(msg.sender, share), "tok out"); } /* ----- sell back to the curve ----- */ function sell(uint256 tokensIn, uint256 minBaseOut) external nonReentrant returns (uint256) { require(!ignited, "ignited"); _settleGenesisIfDue(); require(block.number > lastBuyBlock[msg.sender], "no same block flip"); // seal the curve the instant it sells out. once inventory is zero the only // move is graduate, so a 1 wei sell can never front run ignition and stall it. require(inventory > 0, "sold out, graduate"); require(tokensIn > 0, "zero"); require(tokensIn <= tokensSold, "exceeds curve"); uint256 gross = quoteSell(tokensIn); require(gross <= baseCollected, "curve dry"); require(token.transferFrom(msg.sender, address(this), tokensIn), "tok in"); uint256 cb = curveBought[msg.sender]; curveBought[msg.sender] = cb > tokensIn ? cb - tokensIn : 0; baseCollected -= gross; tokensSold -= tokensIn; inventory += tokensIn; uint256 fee = (gross * FEE_BPS) / BPS; _routeBaseFee(fee); // quick flip surcharge, 5 percent at the moment of buying, gone after 24 hours, // paid in CV to the holders still locked. dumping fast pays the people who stayed. uint256 flip; { uint64 lb = lastBuyAt[msg.sender]; if (lb != 0 && block.timestamp < lb + FLIP_WINDOW) { flip = (gross * FLIP_FEE_BPS * (lb + FLIP_WINDOW - block.timestamp)) / (BPS * FLIP_WINDOW); if (totalLockWeight > 0) { accLockCv += (flip * ACC) / totalLockWeight; cvLockPot += flip; } else { _burnBase(flip); } } } uint256 net = gross - fee - flip; require(net >= minBaseOut, "slippage"); require(base.transfer(msg.sender, net), "base out"); emit Sold(msg.sender, tokensIn, net); return net; } /* ----- locks: claim and break ----- */ function loyaltyOf(uint256 id) public view returns (uint256) { Lock storage L = locks[id]; if (L.closed) return 0; uint256 accrued = (uint256(L.weight) * accLoyal) / ACC; return accrued > L.rewardDebt ? accrued - L.rewardDebt : 0; } /// CV this lock has earned from trade fees and the ignition bonus function cvEarnedOf(uint256 id) public view returns (uint256) { Lock storage L = locks[id]; if (L.closed) return 0; uint256 accrued = (uint256(L.weight) * accLockCv) / ACC; return accrued > L.cvDebt ? accrued - L.cvDebt : 0; } function claim(uint256 id) external nonReentrant { Lock storage L = locks[id]; require(!L.closed, "closed"); require(L.owner == msg.sender, "not owner"); require(block.timestamp >= L.unlock, "locked"); if (id + 1 == creatorLockId1) require(ignited, "creator vests at ignition"); uint256 loyal = loyaltyOf(id); uint256 cvOwed = cvEarnedOf(id); uint256 amt = L.amount; L.closed = true; totalLocked -= amt; totalLockWeight -= L.weight; if (loyal > 0) loyaltyPot -= loyal; if (cvOwed > 0) { cvLockPot -= cvOwed; require(base.transfer(msg.sender, cvOwed), "cv out"); } if (totalLockWeight == 0 && cvLockPot > 0) { uint256 d = cvLockPot; // rounding dust nobody can ever claim, burn it clean cvLockPot = 0; _burnBase(d); } require(token.transfer(msg.sender, amt + loyal), "tok out"); emit Claimed(msg.sender, id, amt, loyal); } function breakLock(uint256 id) external nonReentrant { Lock storage L = locks[id]; require(!L.closed, "closed"); require(L.owner == msg.sender, "not owner"); require(block.timestamp < L.unlock, "matured, just claim"); uint256 amt = L.amount; uint256 forfeited = loyaltyOf(id); // breaker walks away from accrued loyalty uint256 cvOwed = cvEarnedOf(id); // but keeps the CV CV earned while locked L.closed = true; totalLocked -= amt; totalLockWeight -= L.weight; uint256 cb = curveBought[L.owner]; curveBought[L.owner] = cb > amt ? cb - amt : 0; uint256 penalty = (amt * BREAK_BPS) / BPS; uint256 toBurn = penalty / 2; uint256 toLoyal = (penalty - toBurn) + forfeited; uint256 back = amt - penalty; if (forfeited > 0) loyaltyPot -= forfeited; // leaves owed, redistributed or burned below if (totalLockWeight > 0) { accLoyal += (toLoyal * ACC) / totalLockWeight; loyaltyPot += toLoyal; } else { // nobody left standing. burn this break's slice AND any accumulator dust // stranded by prior integer division, so the pot clears to exactly zero. toBurn += toLoyal + loyaltyPot; loyaltyPot = 0; toLoyal = 0; } if (cvOwed > 0) { cvLockPot -= cvOwed; require(base.transfer(msg.sender, cvOwed), "cv out"); } if (totalLockWeight == 0 && cvLockPot > 0) { uint256 d = cvLockPot; cvLockPot = 0; _burnBase(d); } require(token.transfer(DEAD, toBurn), "burn"); require(token.transfer(msg.sender, back), "tok out"); emit Broke(msg.sender, id, back, toBurn, toLoyal); } /* ----- ignition ----- */ function graduate() external nonReentrant live { _settleGenesisIfDue(); require(inventory == 0, "still charging"); uint256 pot = baseCollected; require(pot > 0, "empty"); uint256 burnCut = (pot * GRAD_BURN_BPS) / BPS; uint256 treasCut = (pot * GRAD_TREASURY_BPS) / BPS; uint256 creatorCut = (pot * GRAD_CREATOR_BPS) / BPS; uint256 igniteCut = (pot * GRAD_IGNITE_BPS) / BPS; // holding locked through ignition is the trade. 5 percent of the raise, in CV, // splits across every lock still open, by weight. if (totalLockWeight > 0 && igniteCut > 0) { accLockCv += (igniteCut * ACC) / totalLockWeight; cvLockPot += igniteCut; } else { igniteCut = 0; // no locks held through, the pool gets it } uint256 toPool = pot - burnCut - treasCut - creatorCut - igniteCut; ignited = true; baseCollected = 0; _burnBase(burnCut); require(base.transfer(treasury, treasCut), "treasury"); require(base.transfer(creator, creatorCut), "creator"); // half a percent of supply to the treasury, carved from the pool reserve so it is // always funded, the pool pairs the rest uint256 tokTreas = (token.totalSupply() * GRAD_TOKEN_TREASURY_BPS) / BPS; require(token.transfer(treasury, tokTreas), "treasury tok"); uint256 tokToPool = gradReserve - tokTreas; require(base.approve(address(router), toPool), "approve base"); require(token.approve(address(router), tokToPool), "approve tok"); router.addLiquidity( address(base), address(token), toPool, tokToPool, 0, 0, DEAD, block.timestamp ); if (memeDrop > 0) { if (_vaultDeposit(address(token), memeDrop)) memeDrop = 0; } if (lpRewardReserve > 0 && liquidityVault != address(0) && liquidityVault.code.length > 0) { uint256 r = lpRewardReserve; lpRewardReserve = 0; token.approve(liquidityVault, r); try ILiquidityVault(liquidityVault).register(address(token), r, lpRewardMask) {} catch { lpRewardReserve = r; } // keep it, retry via flush } emit Ignited(toPool, tokToPool, burnCut, treasCut, creatorCut); } /// retry the ignition drop if the vault choked at graduation. permissionless. function flushMemeDrop() external nonReentrant { require(ignited, "not ignited"); require(memeDrop > 0, "none"); require(feeVault.code.length > 0, "vault has no code"); uint256 drop = memeDrop; memeDrop = 0; IFeeVault(feeVault).deposit(address(token), rewardMask, drop); } /// retry handing the LP reward reserve to the liquidity vault. permissionless. function flushLpReward() external nonReentrant { require(ignited, "not ignited"); require(lpRewardReserve > 0, "none"); require(liquidityVault != address(0) && liquidityVault.code.length > 0, "no vault"); uint256 r = lpRewardReserve; lpRewardReserve = 0; token.approve(liquidityVault, r); ILiquidityVault(liquidityVault).register(address(token), r, lpRewardMask); } /* ----- solvency circuit ----- */ /// token side: what the curve physically holds versus everything it owes function solvency() public view returns (uint256 held, uint256 obligated, bool ok) { held = token.balanceOf(address(this)); obligated = totalLocked + loyaltyPot + inventory + memeDrop + lpRewardReserve + (genesisSettled ? genesisOut - genesisClaimed : 0) + (ignited ? 0 : gradReserve); uint256 baseObl = baseCollected + creatorOwed + cvLockPot + vaultBuffer + (genesisSettled ? 0 : genesisPool + genesisFeePool); ok = held >= obligated && base.balanceOf(address(this)) >= baseObl; } function checkSolvency() external { (, , bool ok) = solvency(); if (!ok && !panic) { panic = true; emit Panicked(); } } /* ----- internals ----- */ function _vaultDeposit(address t, uint256 amount) internal returns (bool) { if (feeVault.code.length == 0) return false; // no code, cannot deposit, park it try IFeeVault(feeVault).deposit(t, rewardMask, amount) { return true; } catch { return false; } } function _burnBase(uint256 v) internal { if (v == 0) return; if (baseBurnable) { IBaseBurn(address(base)).burn(v); } else { require(base.transfer(DEAD, v), "burn"); } } function _routeBaseFee(uint256 fee) internal { if (fee == 0) return; uint256 c = (fee * FEE_CREATOR_BPS) / BPS; uint256 b = (fee * FEE_BURN_BPS) / BPS; creatorOwed += c; _burnBase(b); uint256 l = (fee * FEE_LOCKER_BPS) / BPS; if (totalLockWeight > 0) { accLockCv += (l * ACC) / totalLockWeight; cvLockPot += l; } else { l = 0; // no locks yet, the vault takes the whole share } uint256 v = fee - c - b - l; // exits must never depend on the vault being healthy. if the deposit // fails or the vault has no code, park the share and let anyone flush later. if (!_vaultDeposit(address(base), v)) vaultBuffer += v; } /// retry any parked vault share. permissionless. function flushVaultBuffer() external nonReentrant { uint256 v = vaultBuffer; require(v > 0, "none"); require(feeVault.code.length > 0, "vault has no code"); vaultBuffer = 0; IFeeVault(feeVault).deposit(address(base), rewardMask, v); } /// anyone can poke the creator their earned fees function claimCreatorFees() external nonReentrant { uint256 v = creatorOwed; require(v > 0, "nothing"); creatorOwed = 0; require(base.transfer(creator, v), "creator out"); } function lockCount() external view returns (uint256) { return locks.length; } /// base per token, 18 decimals, for feeds and charts function pricePerToken() external view returns (uint256) { return (virtualBase() * 1e18) / virtualTok(); } /// how charged the fuse is, in basis points of the sellable supply function soldBps() external view returns (uint256) { return ((curveSupply - inventory) * BPS) / curveSupply; } } /* ============================ curve deployer ============================ */ /* Holds the curve and token creation bytecode so the factory does not have to. This keeps the factory under the 24576 byte mainnet contract size limit while the curve keeps growing. Stateless and permissionless, it only ever news a curve, news its token, and links them. */ contract FuseTokenDeployer { function deploy(string calldata name, string calldata symbol, string calldata uri, address mintTo, uint256 supply) external returns (address) { return address(new FuseToken(name, symbol, uri, mintTo, supply)); } } contract FuseCurveDeployer { FuseTokenDeployer public immutable tokenDeployer; constructor(address _tokenDeployer) { tokenDeployer = FuseTokenDeployer(_tokenDeployer); } function deploy( address base, address oracle, address router, address treasury, address creator, address feeVault, address liquidityVault, EconConfig calldata econCfg, uint16 rewardMask, uint16 maxWalletBps, uint16 memeDropBps, uint32 genesisSecs, uint16 lpRewardBps, uint16 lpRewardMask, bool baseBurnable, uint256 curveSupply, uint256 gradReserve, uint256 vBase0, uint256 vTok0, string calldata name, string calldata symbol, string calldata uri, uint256 supply ) external returns (address curveAddr, address tokenAddr) { FuseCurve curve = new FuseCurve( base, oracle, router, treasury, creator, msg.sender, feeVault, econCfg, rewardMask, maxWalletBps, memeDropBps, genesisSecs, liquidityVault, lpRewardBps, lpRewardMask, baseBurnable, curveSupply, gradReserve, vBase0, vTok0 ); address tok = tokenDeployer.deploy(name, symbol, uri, address(curve), supply); curve.init(tok); return (address(curve), tok); } } /* ============================ factory ============================ */ contract FuseFactory { IBase public immutable base; address public immutable oracle; address public immutable router; address public immutable treasury; address public immutable feeVault; bool public immutable baseBurnable; address public constant DEAD = 0x000000000000000000000000000000000000dEaD; uint256 public constant MAX_MEME_DROP_BPS = 500; // creators can shower up to 5 percent uint256 public constant MAX_LP_REWARD_BPS = 1_000; // up to 10 percent funds PLS LP mining FuseCurveDeployer public immutable deployer; address public immutable liquidityVault; uint16 public immutable lpRewardBps; uint16 public immutable maxWalletBps; uint32 public immutable genesisSecs; // every launch opens with this window, zero disables uint256 public immutable createFee; // base, burned uint16 public constant CURVE_BPS = 8_000; // 80 percent sells on the curve, 20 percent seeds the pool uint256 public immutable vBase0; // CV anchor, identical for every launch uint256 public immutable vTokNum; // vTok0 = supply * vTokNum / vTokDen, keeps the curve shape uint256 public immutable vTokDen; uint256 public immutable minSupply; // creators pick any supply in this band uint256 public immutable maxSupply; uint256 public immutable defaultSupply; // the one click default EconConfig public econ; // the live numbers every NEW launch inherits address[] public allTokens; mapping(address => address) public curveOf; // token => curve mapping(address => string) public uriOf; // token => metadata uri event TokenCreated(address indexed creator, address token, address curve, string name, string symbol, string uri); event EconUpdated(uint16 feeBps, uint16 gradIgniteBps, uint16 gradTreasuryBps); constructor( address _base, address _oracle, address _router, address _treasury, address _feeVault, uint16 _maxWalletBps, uint32 _genesisSecs, bool _baseBurnable, uint256 _createFee, uint256 _vBase0, uint256 _vTokNum, uint256 _vTokDen, uint256 _minSupply, uint256 _maxSupply, uint256 _defaultSupply, address _liquidityVault, uint16 _lpRewardBps, address _deployer ) { require(_deployer != address(0), "deployer"); require(_lpRewardBps <= MAX_LP_REWARD_BPS, "lp reward"); require(_lpRewardBps == 0 || _liquidityVault != address(0), "need vault"); require(_minSupply > 0 && _maxSupply >= _minSupply, "supply band"); require(_defaultSupply >= _minSupply && _defaultSupply <= _maxSupply, "default range"); require(_vTokNum > _vTokDen, "vtok ratio"); // vTok0 must exceed curve supply require(_maxWalletBps >= 49, "cap under the floor"); base = IBase(_base); oracle = _oracle; router = _router; treasury = _treasury; feeVault = _feeVault; maxWalletBps = _maxWalletBps; genesisSecs = _genesisSecs; baseBurnable = _baseBurnable; createFee = _createFee; vBase0 = _vBase0; vTokNum = _vTokNum; vTokDen = _vTokDen; minSupply = _minSupply; maxSupply = _maxSupply; defaultSupply = _defaultSupply; liquidityVault = _liquidityVault; lpRewardBps = _lpRewardBps; deployer = FuseCurveDeployer(_deployer); econ = EconConfig({ feeBps: 200, // 2 percent each way feeCreatorBps: 2_000, feeBurnBps: 2_000, feeLockerBps: 3_000, feeVaultBps: 3_000, gradBurnBps: 300, gradTreasuryBps: 50, gradCreatorBps: 100, gradIgniteBps: 500, gradTokenTreasuryBps: 50 }); } /// the treasury steers the economics for FUTURE launches only, inside hard bounds. /// tokens already launched carry their numbers as immutables forever. function setEcon(EconConfig calldata e) external { require(msg.sender == treasury, "treasury only"); require(e.feeBps <= 500, "fee capped at 5 percent"); require(uint256(e.feeCreatorBps) + e.feeBurnBps + e.feeLockerBps + e.feeVaultBps == 10_000, "fee split must sum"); require(uint256(e.gradBurnBps) + e.gradTreasuryBps + e.gradCreatorBps + e.gradIgniteBps <= 2_000, "the pool keeps at least 80 percent"); require(e.gradTokenTreasuryBps <= 100, "token slice capped at 1 percent"); econ = e; emit EconUpdated(e.feeBps, e.gradIgniteBps, e.gradTreasuryBps); } /// launch with the default supply, the simplest path function createToken( string calldata name, string calldata symbol, string calldata uri, uint16 rewardMask, uint16 lpRewardMask, uint16 memeDropBps, uint256 firstBuyIn, uint8 firstBuyTier, uint256 firstBuyMinOut ) external returns (address tokenAddr, address curveAddr) { return _create(name, symbol, uri, defaultSupply, rewardMask, lpRewardMask, memeDropBps, firstBuyIn, firstBuyTier, firstBuyMinOut); } /// launch with a chosen supply anywhere in the band, everything else derives from it function createTokenWithSupply( string calldata name, string calldata symbol, string calldata uri, uint256 supply, uint16 rewardMask, uint16 lpRewardMask, uint16 memeDropBps, uint256 firstBuyIn, uint8 firstBuyTier, uint256 firstBuyMinOut ) external returns (address tokenAddr, address curveAddr) { return _create(name, symbol, uri, supply, rewardMask, lpRewardMask, memeDropBps, firstBuyIn, firstBuyTier, firstBuyMinOut); } function _create( string calldata name, string calldata symbol, string calldata uri, uint256 supply, uint16 rewardMask, uint16 lpRewardMask, uint16 memeDropBps, uint256 firstBuyIn, uint8 firstBuyTier, uint256 firstBuyMinOut ) internal returns (address tokenAddr, address curveAddr) { require(rewardMask != 0 && rewardMask < 512, "mask"); require(lpRewardMask < 1024, "lp mask"); require(lpRewardBps == 0 || lpRewardMask != 0, "pick lp legs"); require(memeDropBps <= MAX_MEME_DROP_BPS, "drop"); require(supply >= minSupply && supply <= maxSupply, "supply range"); uint256 curveSupply = (supply * CURVE_BPS) / 10_000; uint256 gradReserve = supply - curveSupply; uint256 vTok0 = (supply * vTokNum) / vTokDen; if (createFee > 0) { if (baseBurnable) { require(base.transferFrom(msg.sender, address(this), createFee), "fee"); IBaseBurn(address(base)).burn(createFee); } else { require(base.transferFrom(msg.sender, DEAD, createFee), "fee"); } } (address curveAddr_, address tokenAddr_) = deployer.deploy( address(base), oracle, router, treasury, msg.sender, feeVault, liquidityVault, econ, rewardMask, maxWalletBps, memeDropBps, genesisSecs, lpRewardBps, lpRewardMask, baseBurnable, curveSupply, gradReserve, vBase0, vTok0, name, symbol, uri, supply ); FuseCurve curve = FuseCurve(curveAddr_); FuseToken tok = FuseToken(tokenAddr_); allTokens.push(address(tok)); curveOf[address(tok)] = address(curve); uriOf[address(tok)] = uri; // the creator's starter buy. with a genesis window it pools like everyone else, // uniform price, no first fill privilege. without one it goes in atomically and // ALWAYS locks, vesting only at ignition, so a creator can never dump the starter. if (firstBuyIn > 0) { require(base.transferFrom(msg.sender, address(this), firstBuyIn), "buy in"); require(base.approve(address(curve), firstBuyIn), "approve"); if (genesisSecs > 0) { curve.buyGenesisFor(msg.sender, firstBuyIn); } else { curve.buyFor(msg.sender, firstBuyIn, firstBuyTier == 0 ? 1 : firstBuyTier, firstBuyMinOut); } // a first buy that fills the whole curve refunds the excess to this factory. // the factory never holds base, so hand any leftover straight back to the creator. uint256 leftover = base.balanceOf(address(this)); if (leftover > 0) require(base.transfer(msg.sender, leftover), "refund"); } emit TokenCreated(msg.sender, address(tok), address(curve), name, symbol, uri); return (address(tok), address(curve)); } function tokenCount() external view returns (uint256) { return allTokens.length; } } /* ============================ conviction fee vault ============================ */ /* Pays the pMINT vault NFT holders from FUSE activity, in whatever reward token arrives, weighted by conviction value, targeted by each creator's chosen groups. Groups: 0 pMINT Reserves · 1 pDAI Diamonds · 2 to 8 the seven Conviction assets, in the order given at deploy. A creator's rewardMask picks any set of them. Rewards never depend on the score oracle, if the worker sleeps, the money keeps flowing to the NFTs. The bug that broke the old drip was a missing checkpoint at entry. Here a position earns nothing on a reward token until its holder enrolls it, and enrollment stamps the checkpoint at the live accumulator, so it can only ever earn what arrives after it joins. No retroactive credit is possible, by construction, proven by the retroactivity tests. Solvency is structural, accumulators only grow by tokens that physically arrived, claims are bounded by accrual past the checkpoint, rounding leaves surplus never deficit, and anything arriving before conviction weight exists parks unassigned until a permissionless flush. No owner, no admin, no pause. */ contract FuseFeeVault { IReserveNFTr public immutable reserveNFT; IDiamondNFTr public immutable diamondNFT; IConvictionNFTr public immutable convictionNFT; address[7] public convictionAssets; uint256 public constant ACC = 1e12; uint16 public constant ALL_GROUPS = 511; mapping(address => mapping(uint8 => uint256)) public acc; mapping(address => mapping(uint8 => mapping(uint256 => uint256))) public cp; mapping(address => mapping(uint8 => mapping(uint256 => bool))) public enrolled; mapping(address => uint256) public accountedIn; mapping(address => uint256) public totalClaimed; mapping(address => uint256) public unassigned; uint256 private _gate; modifier nonReentrant() { require(_gate == 0, "reentrant"); _gate = 1; _; _gate = 0; } event Deposited(address indexed token, uint16 mask, uint256 amount, uint256 weight); event Enrolled(address indexed token, uint8 kind, uint256 id, address indexed owner); event VaultClaimed(address indexed token, address indexed who, uint256 amount); constructor(address _res, address _dia, address _conv, address[7] memory _assets) { reserveNFT = IReserveNFTr(_res); diamondNFT = IDiamondNFTr(_dia); convictionNFT = IConvictionNFTr(_conv); convictionAssets = _assets; } function groupWeight(uint8 g) public view returns (uint256) { if (g == 0) return reserveNFT.totalCV(); if (g == 1) return diamondNFT.totalCV(); if (g > 8) return 0; address a = convictionAssets[g - 2]; if (a == address(0)) return 0; return convictionNFT.getAssetConfig(a).totalCV; } function maskWeight(uint16 mask) public view returns (uint256 w) { for (uint8 g = 0; g < 9; g++) if (mask & (uint16(1) << g) != 0) w += groupWeight(g); } function deposit(address token, uint16 mask, uint256 amount) external nonReentrant { require(amount > 0, "zero"); require(mask != 0 && mask < 512, "mask"); require(IBase(token).transferFrom(msg.sender, address(this), amount), "in"); uint256 w = maskWeight(mask); if (w == 0) { mask = ALL_GROUPS; w = maskWeight(mask); } if (w == 0) { unassigned[token] += amount; accountedIn[token] += amount; return; } uint256 per = (amount * ACC) / w; for (uint8 g = 0; g < 9; g++) if (mask & (uint16(1) << g) != 0) acc[token][g] += per; accountedIn[token] += amount; emit Deposited(token, mask, amount, w); } function flushUnassigned(address token) external nonReentrant { uint256 x = unassigned[token]; require(x > 0, "none"); uint256 w = maskWeight(ALL_GROUPS); require(w > 0, "still no weight"); unassigned[token] = 0; uint256 per = (x * ACC) / w; for (uint8 g = 0; g < 9; g++) acc[token][g] += per; emit Deposited(token, ALL_GROUPS, x, w); } function _pos(uint8 kind, uint256 id) internal view returns (uint256 cvw, uint8 group, address owner) { if (kind == 0) { owner = reserveNFT.ownerOf(id); (, cvw, , , , , , , , ) = reserveNFT.getReserve(id); group = 0; } else if (kind == 1) { owner = diamondNFT.ownerOf(id); (, uint128 wq, , , , , , bool c, ) = diamondNFT.positions(id); cvw = c ? 0 : uint256(wq); group = 1; } else { owner = convictionNFT.ownerOf(id); (address asset, , uint128 wq, , , , , , bool c, , ) = convictionNFT.positions(id); cvw = c ? 0 : uint256(wq); group = 9; for (uint8 i = 0; i < 7; i++) if (convictionAssets[i] == asset) { group = i + 2; break; } if (group == 9) cvw = 0; } } function enroll(address token, uint8 kind, uint256[] calldata ids) external nonReentrant { require(kind < 3, "kind"); for (uint256 i = 0; i < ids.length; i++) { uint256 id = ids[i]; (uint256 cvw, uint8 group, address owner) = _pos(kind, id); require(owner == msg.sender, "not owner"); require(!enrolled[token][kind][id], "already enrolled"); enrolled[token][kind][id] = true; cp[token][kind][id] = (cvw * acc[token][group]) / ACC; emit Enrolled(token, kind, id, msg.sender); } } function pending(address token, uint8 kind, uint256 id) public view returns (uint256) { if (!enrolled[token][kind][id]) return 0; (uint256 cvw, uint8 group, ) = _pos(kind, id); uint256 accrued = (cvw * acc[token][group]) / ACC; uint256 c = cp[token][kind][id]; return accrued > c ? accrued - c : 0; } function claim(address token, uint8 kind, uint256[] calldata ids) external nonReentrant { require(kind < 3, "kind"); uint256 total; for (uint256 i = 0; i < ids.length; i++) { uint256 id = ids[i]; require(enrolled[token][kind][id], "enroll first"); (uint256 cvw, uint8 group, address owner) = _pos(kind, id); require(owner == msg.sender, "not owner"); uint256 accrued = (cvw * acc[token][group]) / ACC; uint256 c = cp[token][kind][id]; uint256 owed = accrued > c ? accrued - c : 0; cp[token][kind][id] = accrued; total += owed; } if (total > 0) { totalClaimed[token] += total; require(IBase(token).transfer(msg.sender, total), "out"); } emit VaultClaimed(token, msg.sender, total); } function held(address token) external view returns (uint256) { return IBase(token).balanceOf(address(this)); } } /* ============================ liquidity mining vault ============================ */ /* Rewards holders who deepen a graduated token's liquidity, in any of ten pairing legs: CV, pMINT, pDAI, and the seven Conviction assets, WPLS among them. The creator picks at launch which legs earn, and the reward reservoir carved from the token's supply splits evenly across the chosen legs, each with its own stream. Conviction gate. To earn on a leg backed by an NFT, you must hold that NFT, proven by an id you own. pMINT needs a Reserve, pDAI needs a Diamond, each Conviction asset, WPLS included, needs a Conviction NFT of that asset. CV is open. The gate is checked when you enter a position and when you harvest, so rewards flow to committed holders, not drive by farmers. Everything else mirrors the proven vault, a finite reservoir that can never over emit, the checkpoint at entry accumulator so nobody earns history, and lock tiers for longer commit, none 1x, 30 days 1.5x, 90 days 2x, 180 days 3x. Withdrawing after the lock is free, pulling a locked position early costs a 25 percent LP penalty burned into the pool. No owner, no pause. */ interface IPairFactoryLike { function getPair(address a, address b) external view returns (address); } contract FuseLiquidityVault { uint8 public constant LEGS = 10; // 0 CV, 1 pMINT, 2 pDAI, 3..9 the seven conviction assets, WPLS among them uint256 public constant EARLY_EXIT_BPS = 2_500; // pulling a locked position early burns a quarter of the LP into the pool address public constant DEAD = 0x000000000000000000000000000000000000dEaD; address[10] public legToken; IReserveNFTr public immutable reserveNFT; IDiamondNFTr public immutable diamondNFT; IConvictionNFTr public immutable convictionNFT; IPairFactoryLike public immutable pairFactory; uint256 public constant ACC = 1e18; uint256 public constant DURATION = 90 days; struct Pool { address lpToken; // meme/leg LP, resolved lazily uint256 rewardRate; uint256 periodFinish; uint256 lastUpdate; uint256 rewardPerTokenStored; uint256 totalWeight; } mapping(address => mapping(uint8 => Pool)) public pools; // token => leg => pool struct Position { uint256 lp; uint256 weight; uint256 mult; uint256 rewardPerTokenPaid; uint256 rewards; uint64 unlock; } mapping(address => mapping(uint8 => mapping(address => Position))) public positions; uint256 private _gate; modifier nonReentrant() { require(_gate == 0, "reentrant"); _gate = 1; _; _gate = 0; } event Registered(address indexed token, uint16 legMask, uint256 amount); event Deposited(address indexed token, uint8 leg, address indexed who, uint256 lp, uint8 tier); event Withdrawn(address indexed token, uint8 leg, address indexed who, uint256 lp); event Claimed(address indexed token, uint8 leg, address indexed who, uint256 amount); constructor( address _cv, address _pmint, address _pdai, address _res, address _dia, address _conv, address[7] memory assets, address _pairFactory ) { legToken[0] = _cv; legToken[1] = _pmint; legToken[2] = _pdai; for (uint256 i = 0; i < 7; i++) legToken[3 + i] = assets[i]; reserveNFT = IReserveNFTr(_res); diamondNFT = IDiamondNFTr(_dia); convictionNFT = IConvictionNFTr(_conv); pairFactory = IPairFactoryLike(_pairFactory); } /* ----- the conviction gate ----- */ /// does this wallet hold the NFT that a leg requires. PLS and CV are open. function _gateOk(uint8 leg, address who, uint256 proofId) internal view returns (bool) { if (leg == 0) return true; // CV is open, no NFT if (leg == 1) { try reserveNFT.ownerOf(proofId) returns (address o) { return o == who; } catch { return false; } } if (leg == 2) { try diamondNFT.ownerOf(proofId) returns (address o) { return o == who; } catch { return false; } } address owner; try convictionNFT.ownerOf(proofId) returns (address o) { owner = o; } catch { return false; } if (owner != who) return false; (address asset, , , , , , , , , , ) = convictionNFT.positions(proofId); return asset == legToken[leg]; } /* ----- the reward accumulator, checkpoint at entry ----- */ function _ltra(Pool storage p) internal view returns (uint256) { return block.timestamp < p.periodFinish ? block.timestamp : p.periodFinish; } function rewardPerToken(address token, uint8 leg) public view returns (uint256) { Pool storage p = pools[token][leg]; if (p.totalWeight == 0) return p.rewardPerTokenStored; return p.rewardPerTokenStored + ((_ltra(p) - p.lastUpdate) * p.rewardRate * ACC) / p.totalWeight; } function earned(address token, uint8 leg, address who) public view returns (uint256) { Position storage pos = positions[token][leg][who]; return (pos.weight * (rewardPerToken(token, leg) - pos.rewardPerTokenPaid)) / ACC + pos.rewards; } function _update(address token, uint8 leg, address who) internal { Pool storage p = pools[token][leg]; p.rewardPerTokenStored = rewardPerToken(token, leg); p.lastUpdate = _ltra(p); if (who != address(0)) { positions[token][leg][who].rewards = earned(token, leg, who); positions[token][leg][who].rewardPerTokenPaid = p.rewardPerTokenStored; } } function _tier(uint8 t) internal pure returns (uint256 mult, uint64 dur) { if (t == 0) return (10_000, 0); if (t == 1) return (15_000, 30 days); if (t == 2) return (20_000, 90 days); return (30_000, 180 days); } /* ----- funding, called by the curve at graduation ----- */ /// split the reservoir evenly across the creator's chosen legs and start each stream function register(address token, uint256 amount, uint16 legMask) external nonReentrant { require(amount > 0, "zero"); require(legMask != 0 && legMask < (uint16(1) << LEGS), "leg mask"); require(IBase(token).transferFrom(msg.sender, address(this), amount), "in"); uint256 k; for (uint8 g = 0; g < LEGS; g++) if (legMask & (uint16(1) << g) != 0) k++; uint256 per = amount / k; for (uint8 g = 0; g < LEGS; g++) { if (legMask & (uint16(1) << g) == 0) continue; _update(token, g, address(0)); Pool storage p = pools[token][g]; if (block.timestamp >= p.periodFinish) { // a fresh stream runs the full window p.rewardRate = per / DURATION; p.periodFinish = block.timestamp + DURATION; } else { // a top up pours into the REMAINING window and the finish never moves, // so a dust deposit cannot stretch or dilute a live stream. ever. p.rewardRate += per / (p.periodFinish - block.timestamp); } p.lastUpdate = block.timestamp; } emit Registered(token, legMask, amount); } /* ----- provide, harvest, withdraw ----- */ function deposit(address token, uint8 leg, uint256 lpAmount, uint8 tier, uint256 proofId) external nonReentrant { require(leg < LEGS, "leg"); require(lpAmount > 0, "zero"); require(tier <= 3, "tier"); Pool storage p = pools[token][leg]; require(p.periodFinish != 0, "leg not rewarded"); require(_gateOk(leg, msg.sender, proofId), "hold the matching NFT"); if (p.lpToken == address(0)) { address lp = pairFactory.getPair(token, legToken[leg]); require(lp != address(0), "create the pair first"); p.lpToken = lp; } _update(token, leg, msg.sender); (uint256 mult, uint64 dur) = _tier(tier); Position storage pos = positions[token][leg][msg.sender]; require(uint64(block.timestamp) + dur >= pos.unlock, "cannot shorten lock"); require(IBase(p.lpToken).transferFrom(msg.sender, address(this), lpAmount), "lp in"); pos.lp += lpAmount; pos.mult = mult; uint256 nw = (pos.lp * mult) / 10_000; p.totalWeight = p.totalWeight - pos.weight + nw; pos.weight = nw; pos.unlock = uint64(block.timestamp) + dur; emit Deposited(token, leg, msg.sender, lpAmount, tier); } /// harvest is gated too, you must still hold a qualifying NFT to collect function claim(address token, uint8 leg, uint256 proofId) external nonReentrant { require(_gateOk(leg, msg.sender, proofId), "hold the matching NFT"); _update(token, leg, msg.sender); uint256 r = positions[token][leg][msg.sender].rewards; if (r > 0) { positions[token][leg][msg.sender].rewards = 0; require(IBase(token).transfer(msg.sender, r), "out"); } emit Claimed(token, leg, msg.sender, r); } /// withdrawing your own LP after the lock is never gated function withdraw(address token, uint8 leg, uint256 lpAmount) external nonReentrant { Position storage pos = positions[token][leg][msg.sender]; require(lpAmount > 0 && lpAmount <= pos.lp, "amount"); _update(token, leg, msg.sender); Pool storage p = pools[token][leg]; pos.lp -= lpAmount; uint256 nw = (pos.lp * pos.mult) / 10_000; p.totalWeight = p.totalWeight - pos.weight + nw; pos.weight = nw; uint256 out = lpAmount; if (block.timestamp < pos.unlock) { uint256 penalty = (lpAmount * EARLY_EXIT_BPS) / 10_000; out = lpAmount - penalty; require(IBase(p.lpToken).transfer(DEAD, penalty), "burn"); // penalty LP is locked in the pool forever } require(IBase(p.lpToken).transfer(msg.sender, out), "lp out"); emit Withdrawn(token, leg, msg.sender, out); } } /* ============================ treasury multisig ============================ */ /* The keys behind the treasury. Owners propose a call, a quorum confirms, an owner executes. It receives every graduation cut, steers the factory economics through setEcon, and moves funds only by quorum. Keys rotate through the multisig itself, so the treasury ADDRESS never changes, which is exactly why the factory can hold it immutable forever. */ contract FuseTreasury { address[] public owners; mapping(address => bool) public isOwner; uint256 public immutable required; struct Call { address target; uint256 value; bytes data; bool executed; uint16 confirmations; } Call[] public calls; mapping(uint256 => mapping(address => bool)) public confirmed; event Submitted(uint256 indexed id, address indexed by, address target, uint256 value); event Confirmed(uint256 indexed id, address indexed by); event Revoked(uint256 indexed id, address indexed by); event Executed(uint256 indexed id); event OwnerSwapped(address indexed removed, address indexed added); modifier onlyOwner() { require(isOwner[msg.sender], "not an owner"); _; } constructor(address[] memory _owners, uint256 _required) { require(_owners.length > 0 && _required > 0 && _required <= _owners.length, "quorum"); for (uint256 i = 0; i < _owners.length; i++) { address o = _owners[i]; require(o != address(0) && !isOwner[o], "owner"); isOwner[o] = true; owners.push(o); } required = _required; } receive() external payable {} function submit(address target, uint256 value, bytes calldata data) external onlyOwner returns (uint256 id) { calls.push(Call({target: target, value: value, data: data, executed: false, confirmations: 0})); id = calls.length - 1; emit Submitted(id, msg.sender, target, value); _confirm(id); } function confirm(uint256 id) external onlyOwner { _confirm(id); } function _confirm(uint256 id) internal { require(!calls[id].executed, "done"); require(!confirmed[id][msg.sender], "already confirmed"); confirmed[id][msg.sender] = true; calls[id].confirmations += 1; emit Confirmed(id, msg.sender); } function revoke(uint256 id) external onlyOwner { require(!calls[id].executed, "done"); require(confirmed[id][msg.sender], "not confirmed"); confirmed[id][msg.sender] = false; calls[id].confirmations -= 1; emit Revoked(id, msg.sender); } function execute(uint256 id) external onlyOwner { Call storage c = calls[id]; require(!c.executed, "done"); require(c.confirmations >= required, "quorum"); c.executed = true; (bool okCall, bytes memory ret) = c.target.call{value: c.value}(c.data); if (!okCall) { assembly { revert(add(ret, 32), mload(ret)) } } emit Executed(id); } /// keys rotate, the address never does. only a quorum of this multisig can do it. function swapOwner(address removed, address added) external { require(msg.sender == address(this), "only through the multisig"); require(isOwner[removed] && !isOwner[added] && added != address(0), "swap"); isOwner[removed] = false; isOwner[added] = true; for (uint256 i = 0; i < owners.length; i++) if (owners[i] == removed) { owners[i] = added; break; } emit OwnerSwapped(removed, added); } function ownerCount() external view returns (uint256) { return owners.length; } function callCount() external view returns (uint256) { return calls.length; } }
We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the
POA solidity flattener or the
truffle flattener
.
Try to fetch constructor arguments automatically
No
Yes
ABI-encoded Constructor Arguments (if required by the contract)
00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000d6f5aa9eb7c98d4b9e070f01d744c31149222fd30000000000000000000000000000000000000000033b2e3c9fd0803ce8000000000000000000000000000000000000000000000000000000000000000000000643414e4152590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000643414e41525900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000125646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c65794a706257466e5a534936496d68306448427a4f69387663473170626e517463324e76636d557563473170626e517564323979613256796379356b5a5859766157316e4c7a6c684f575a695a546779596a4d774d546b774e57526b4e6a426b4d5455324e533577626d63694c434a69595735755a5849694f6949694c434a6b5a584e6a636d6c7764476c766269493649694973496e646c596e4e70644755694f694a6f64485277637a6f764c334274615735304c6e64706269396d64584e6c4c794973496e5233615852305a5849694f694a6f64485277637a6f764c336775593239744c334274615735305833426b59576b694c434a305a57786c5a334a686253493649694a39000000000000000000000000000000000000000000000000000000
Add arguments in
ABI hex encoded form
. Constructor arguments are written right to left, and will be found at the end of the input created bytecode. They may also be
parsed here.
Add Contract Libraries
Contract Libraries
Library 1 Name
A library name called in the .sol file. Multiple libraries (up to 10) may be added for each contract. Click the Add Library button to add an additional one.
Library 1 Address
The 0x library address. This can be found in the generated json file or Truffle output (if using truffle).
Library 2 Name
Library 2 Address
Library 3 Name
Library 3 Address
Library 4 Name
Library 4 Address
Library 5 Name
Library 5 Address
Library 6 Name
Library 6 Address
Library 7 Name
Library 7 Address
Library 8 Name
Library 8 Address
Library 9 Name
Library 9 Address
Library 10 Name
Library 10 Address
Add Library
Loading...
Verify & publish
Cancel
Ok
Ok
Ok
No
Yes