Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
- Contract name:
- FuseToken
- Optimization enabled
- false
- Compiler version
- v0.8.26+commit.8a97fa7a
- EVM Version
- Verified at
- 2026-07-10T17:58:40.129367Z
Constructor Arguments
00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000d6f5aa9eb7c98d4b9e070f01d744c31149222fd30000000000000000000000000000000000000000033b2e3c9fd0803ce8000000000000000000000000000000000000000000000000000000000000000000000643414e4152590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000643414e41525900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000125646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c65794a706257466e5a534936496d68306448427a4f69387663473170626e517463324e76636d557563473170626e517564323979613256796379356b5a5859766157316e4c7a6c684f575a695a546779596a4d774d546b774e57526b4e6a426b4d5455324e533577626d63694c434a69595735755a5849694f6949694c434a6b5a584e6a636d6c7764476c766269493649694973496e646c596e4e70644755694f694a6f64485277637a6f764c334274615735304c6e64706269396d64584e6c4c794973496e5233615852305a5849694f694a6f64485277637a6f764c336775593239744c334274615735305833426b59576b694c434a305a57786c5a334a686253493649694a39000000000000000000000000000000000000000000000000000000
Arg [0] (string) : CANARY
Arg [1] (string) : CANARY
Arg [2] (string) : data:application/json;base64,eyJpbWFnZSI6Imh0dHBzOi8vcG1pbnQtc2NvcmUucG1pbnQud29ya2Vycy5kZXYvaW1nLzlhOWZiZTgyYjMwMTkwNWRkNjBkMTU2NS5wbmciLCJiYW5uZXIiOiIiLCJkZXNjcmlwdGlvbiI6IiIsIndlYnNpdGUiOiJodHRwczovL3BtaW50Lndpbi9mdXNlLyIsInR3aXR0ZXIiOiJodHRwczovL3guY29tL3BtaW50X3BkYWkiLCJ0ZWxlZ3JhbSI6IiJ9
Arg [3] (address) : 0xd6f5aa9eb7c98d4b9e070f01d744c31149222fd3
Arg [4] (uint256) : 1000000000000000000000000000
Fuse.sol
// 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; }
}
Compiler Settings
{"viaIR":true,"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode"]}},"optimizer":{"runs":200,"enabled":true},"evmVersion":"shanghai"}
Contract ABI
[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"string","name":"n","internalType":"string"},{"type":"string","name":"s","internalType":"string"},{"type":"string","name":"uri_","internalType":"string"},{"type":"address","name":"mintTo","internalType":"address"},{"type":"uint256","name":"supply","internalType":"uint256"}]},{"type":"event","name":"Approval","inputs":[{"type":"address","name":"owner","internalType":"address","indexed":true},{"type":"address","name":"spender","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"type":"address","name":"from","internalType":"address","indexed":true},{"type":"address","name":"to","internalType":"address","indexed":true},{"type":"uint256","name":"value","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"allowance","inputs":[{"type":"address","name":"","internalType":"address"},{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"approve","inputs":[{"type":"address","name":"s","internalType":"address"},{"type":"uint256","name":"v","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"balanceOf","inputs":[{"type":"address","name":"","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint8","name":"","internalType":"uint8"}],"name":"decimals","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"name","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"symbol","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"string","name":"","internalType":"string"}],"name":"tokenURI","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalSupply","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transfer","inputs":[{"type":"address","name":"to","internalType":"address"},{"type":"uint256","name":"v","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"transferFrom","inputs":[{"type":"address","name":"f","internalType":"address"},{"type":"address","name":"t","internalType":"address"},{"type":"uint256","name":"v","internalType":"uint256"}]}]
Contract Creation Code
0x6080604052346104d857610cb180380380610019816104dc565b92833981019060a0818303126104d85780516001600160401b0381116104d85782610045918301610501565b60208201516001600160401b0381116104d85783610064918401610501565b604083015190936001600160401b0382116104d857610084918401610501565b60608301516001600160a01b03811693908490036104d85760800151825190926001600160401b038211610304575f5490600182811c921680156104ce575b60208310146102e65781601f849311610461575b50602090601f83116001146103fe575f926103f3575b50508160011b915f199060031b1c1916175f555b83516001600160401b03811161030457600154600181811c911680156103e9575b60208210146102e657601f8111610386575b50602094601f8211600114610323579481929394955f92610318575b50508160011b915f199060031b1c1916176001555b8051906001600160401b03821161030457600254600181811c911680156102fa575b60208210146102e657601f8111610283575b50602090601f831160011461020c5792825f80516020610c91833981519152936020935f968792610201575b50508160011b9186199060031b1c1916176002555b8060035584845260048252806040852055604051908152a3604051610726908161056b8239f35b015190505f806101c5565b90601f1983169160025f52815f20925f5b81811061026b5750936020935f9693600193835f80516020610c918339815191529810610253575b505050811b016002556101da565b0151871960f88460031b161c191690555f8080610245565b9293602060018192878601518155019501930161021d565b60025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace601f840160051c810191602085106102dc575b601f0160051c01905b8181106102d15750610199565b5f81556001016102c4565b90915081906102bb565b634e487b7160e01b5f52602260045260245ffd5b90607f1690610187565b634e487b7160e01b5f52604160045260245ffd5b015190505f80610150565b601f1982169560015f52805f20915f5b88811061036e57508360019596979810610356575b505050811b01600155610165565b01515f1960f88460031b161c191690555f8080610348565b91926020600181928685015181550194019201610333565b60015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f830160051c810191602084106103df575b601f0160051c01905b8181106103d45750610134565b5f81556001016103c7565b90915081906103be565b90607f1690610122565b015190505f806100ed565b5f8080528281209350601f198516905b8181106104495750908460019594939210610431575b505050811b015f55610101565b01515f1960f88460031b161c191690555f8080610424565b9293602060018192878601518155019501930161040e565b5f80529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563601f840160051c810191602085106104c4575b90601f859493920160051c01905b8181106104b657506100d7565b5f81558493506001016104a9565b909150819061049b565b91607f16916100c3565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761030457604052565b81601f820112156104d8578051906001600160401b03821161030457610530601f8301601f19166020016104dc565b92828452602083830101116104d8575f5b82811061055557505060205f918301015290565b8060208092840101518282870101520161054156fe60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146104c9578063095ea7b31461045057806318160ddd1461043357806323b872dd14610353578063313ce567146103385780633c130d901461026357806370a082311461022b57806395d89b411461010d578063a9059cbb146100db5763dd62ed3e14610087575f80fd5b346100d75760403660031901126100d7576100a06105e2565b6100a86105f8565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b346100d75760403660031901126100d75760206101036100f96105e2565b602435903361060e565b6040519015158152f35b346100d7575f3660031901126100d7576040515f6001548060011c90600181168015610221575b60208310811461020d578285529081156101f1575060011461019b575b50819003601f01601f191681019067ffffffffffffffff8211818310176101875760408290528190610183908261059b565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b8282106101db57506020915082010182610151565b60018160209254838588010152019101906101c6565b90506020925060ff191682840152151560051b82010182610151565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610134565b346100d75760203660031901126100d7576001600160a01b0361024c6105e2565b165f526004602052602060405f2054604051908152f35b346100d7575f3660031901126100d7576040515f6002548060011c9060018116801561032e575b60208310811461020d578285529081156101f157506001146102d85750819003601f01601f191681019067ffffffffffffffff8211818310176101875760408290528190610183908261059b565b60025f9081529091507f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5b82821061031857506020915082010182610151565b6001816020925483858801015201910190610303565b91607f169161028a565b346100d7575f3660031901126100d757602060405160128152f35b346100d75760603660031901126100d75761036c6105e2565b6103746105f8565b6001600160a01b0382165f8181526005602090815260408083203384529091529020546044359391600182016103b2575b602061010386868661060e565b92909391828510610402578285039485116103ee575f9384526005602090815260408086203387528252909420949094559092909182906103a5565b634e487b7160e01b5f52601160045260245ffd5b60405162461bcd60e51b8152602060048201526009602482015268616c6c6f77616e636560b81b6044820152606490fd5b346100d7575f3660031901126100d7576020600354604051908152f35b346100d75760403660031901126100d7576104696105e2565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b346100d7575f3660031901126100d7576040515f80548060011c90600181168015610591575b60208310811461020d578285529081156101f1575060011461053d5750819003601f01601f191681019067ffffffffffffffff8211818310176101875760408290528190610183908261059b565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82821061057b57506020915082010182610151565b6001816020925483858801015201910190610566565b91607f16916104ef565b9190916020815282518060208301525f5b8181106105cc575060409293505f838284010152601f8019910116010190565b80602080928701015160408286010152016105ac565b600435906001600160a01b03821682036100d757565b602435906001600160a01b03821682036100d757565b6001600160a01b03909116919082156106c1576001600160a01b03165f8181526004602052604090205490919081811061069257817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092855f52600484520360405f2055845f526004825260405f20818154019055604051908152a3600190565b60405162461bcd60e51b815260206004820152600760248201526662616c616e636560c81b6044820152606490fd5b60405162461bcd60e51b8152602060048201526007602482015266746f207a65726f60c81b6044820152606490fdfea26469706673582212201f8383ee2a70af02a5c8e162c0cd942e6f960129986742ead6dbd6b6873b93d064736f6c634300081a0033ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000d6f5aa9eb7c98d4b9e070f01d744c31149222fd30000000000000000000000000000000000000000033b2e3c9fd0803ce8000000000000000000000000000000000000000000000000000000000000000000000643414e4152590000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000643414e41525900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000125646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c65794a706257466e5a534936496d68306448427a4f69387663473170626e517463324e76636d557563473170626e517564323979613256796379356b5a5859766157316e4c7a6c684f575a695a546779596a4d774d546b774e57526b4e6a426b4d5455324e533577626d63694c434a69595735755a5849694f6949694c434a6b5a584e6a636d6c7764476c766269493649694973496e646c596e4e70644755694f694a6f64485277637a6f764c334274615735304c6e64706269396d64584e6c4c794973496e5233615852305a5849694f694a6f64485277637a6f764c336775593239744c334274615735305833426b59576b694c434a305a57786c5a334a686253493649694a39000000000000000000000000000000000000000000000000000000
Deployed ByteCode
0x60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146104c9578063095ea7b31461045057806318160ddd1461043357806323b872dd14610353578063313ce567146103385780633c130d901461026357806370a082311461022b57806395d89b411461010d578063a9059cbb146100db5763dd62ed3e14610087575f80fd5b346100d75760403660031901126100d7576100a06105e2565b6100a86105f8565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b346100d75760403660031901126100d75760206101036100f96105e2565b602435903361060e565b6040519015158152f35b346100d7575f3660031901126100d7576040515f6001548060011c90600181168015610221575b60208310811461020d578285529081156101f1575060011461019b575b50819003601f01601f191681019067ffffffffffffffff8211818310176101875760408290528190610183908261059b565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b8282106101db57506020915082010182610151565b60018160209254838588010152019101906101c6565b90506020925060ff191682840152151560051b82010182610151565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610134565b346100d75760203660031901126100d7576001600160a01b0361024c6105e2565b165f526004602052602060405f2054604051908152f35b346100d7575f3660031901126100d7576040515f6002548060011c9060018116801561032e575b60208310811461020d578285529081156101f157506001146102d85750819003601f01601f191681019067ffffffffffffffff8211818310176101875760408290528190610183908261059b565b60025f9081529091507f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace5b82821061031857506020915082010182610151565b6001816020925483858801015201910190610303565b91607f169161028a565b346100d7575f3660031901126100d757602060405160128152f35b346100d75760603660031901126100d75761036c6105e2565b6103746105f8565b6001600160a01b0382165f8181526005602090815260408083203384529091529020546044359391600182016103b2575b602061010386868661060e565b92909391828510610402578285039485116103ee575f9384526005602090815260408086203387528252909420949094559092909182906103a5565b634e487b7160e01b5f52601160045260245ffd5b60405162461bcd60e51b8152602060048201526009602482015268616c6c6f77616e636560b81b6044820152606490fd5b346100d7575f3660031901126100d7576020600354604051908152f35b346100d75760403660031901126100d7576104696105e2565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b346100d7575f3660031901126100d7576040515f80548060011c90600181168015610591575b60208310811461020d578285529081156101f1575060011461053d5750819003601f01601f191681019067ffffffffffffffff8211818310176101875760408290528190610183908261059b565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82821061057b57506020915082010182610151565b6001816020925483858801015201910190610566565b91607f16916104ef565b9190916020815282518060208301525f5b8181106105cc575060409293505f838284010152601f8019910116010190565b80602080928701015160408286010152016105ac565b600435906001600160a01b03821682036100d757565b602435906001600160a01b03821682036100d757565b6001600160a01b03909116919082156106c1576001600160a01b03165f8181526004602052604090205490919081811061069257817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92602092855f52600484520360405f2055845f526004825260405f20818154019055604051908152a3600190565b60405162461bcd60e51b815260206004820152600760248201526662616c616e636560c81b6044820152606490fd5b60405162461bcd60e51b8152602060048201526007602482015266746f207a65726f60c81b6044820152606490fdfea26469706673582212201f8383ee2a70af02a5c8e162c0cd942e6f960129986742ead6dbd6b6873b93d064736f6c634300081a0033