FlowTokenV3
0xbd18ca11450b75a35eed877420b461d083f1737d
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
9,729 bytes
ABI entries
74
34 read · 19 write
License
none
Contract information
- Address
- 0xbd18ca11450b75a35eed877420b461d083f1737d
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x0189676b98…686238efD6
- Creation tx
- 0x4d2eae5119…35742da6de
Token
- Name
- KIT
- Symbol
- KIT
- Decimals
- 18
- Holders
- 2
- Market
- View token page →
Read contract (34)
BPS() → uint256
allowance(address, address) → uint256
balanceOf(address) → uint256
basket(address) → uint256
basketOf(address) → uint256
burned() → uint256
curve() → address
decimals() → uint8
graduated() → bool
holdDuration(address) → uint256
holdSince(address) → uint256
hook() → address
initialSupply() → uint256
isAMMPair(address) → bool
isExcludedFromFees(address) → bool
isExcludedFromLimits(address) → bool
isExcludedFromRewards(address) → bool
magnifiedRewardPerShare() → uint256
maxTx() → uint256
maxWallet() → uint256
name() → string
owner() → address
pendingClaim(address) → uint256
poolManager() → address
positionManager() → address
restricted() → bool
rewardCheckpoint(address) → uint256
symbol() → string
totalShares() → uint256
totalSupply() → uint256
tradingEnabled() → bool
treasury() → address
universalRouter() → address
usdg() → address
Events (10)
ApprovalAutoPaidOnSellBasketFilledBasketMovedClaimedHookSetOwnershipTransferredPendingCreditedTradingEnabledTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
},
{
"internalType": "string",
"name": "symbol_",
"type": "string"
},
{
"internalType": "address",
"name": "treasury_",
"type": "address"
},
{
"internalType": "uint256",
"name": "supply_",
"type": "uint256"
},
{
"internalType": "address",
"name": "usdg_",
"type": "address"
},
{
"internalType": "bool",
"name": "restricted_",
"type": "bool"
},
{
"internalType": "address",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "address",
"name": "positionManager_",
"type": "address"
},
{
"internalType": "address",
"name": "universalRouter_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientAllowance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientBalance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "approver",
"type": "address"
}
],
"name": "ERC20InvalidApprover",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC20InvalidReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "ERC20InvalidSender",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "ERC20InvalidSpender",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "who",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "usdg",
"type": "uint256"
}
],
"name": "AutoPaidOnSell",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "usdgAdded",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newPerShare",
"type": "uint256"
}
],
"name": "BasketFilled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "BasketMoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "who",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "usdg",
"type": "uint256"
}
],
"name": "Claimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "hook",
"type": "address"
}
],
"name": "HookSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "who",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "usdg",
"type": "uint256"
}
],
"name": "PendingCredited",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "TradingEnabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "basket",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "basketOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "burned",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "claim",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "curve",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "enableTrading",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "fundBaskets",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "graduated",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "holdDuration",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "holdSince",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "hook",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "initialSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isAMMPair",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isExcludedFromFees",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isExcludedFromLimits",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isExcludedFromRewards",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "magnifiedRewardPerShare",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxTx",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxWallet",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "onGraduate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "pendingClaim",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "positionManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "removeLimits",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "restricted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "rewardCheckpoint",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "p",
"type": "address"
},
{
"internalType": "bool",
"name": "v",
"type": "bool"
}
],
"name": "setAMMPair",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "c",
"type": "address"
}
],
"name": "setCurve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
},
{
"internalType": "bool",
"name": "v",
"type": "bool"
}
],
"name": "setExcludedFromFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
},
{
"internalType": "bool",
"name": "v",
"type": "bool"
}
],
"name": "setExcludedFromLimits",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "a",
"type": "address"
},
{
"internalType": "bool",
"name": "v",
"type": "bool"
}
],
"name": "setExcludedFromRewards",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "h",
"type": "address"
}
],
"name": "setHook",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "maxTx_",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxWallet_",
"type": "uint256"
}
],
"name": "setLimits",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "t",
"type": "address"
}
],
"name": "setTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalShares",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tradingEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "universalRouter",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "usdg",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "withdrawPending",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FlowTokenV3 — v3 reward token (contract #1) ⚠️ WIP, NOT AUDITED
The plain reward token for the v3 launchpad. Rewards accrue in USDG (the chain's USD base), streamed
pro-rata by balance via the O(1) MasterChef accumulator (same as FLOWv4). Differences vs FLOWv4:
• Reward asset is USDG (an ERC-20), not native ETH. fundBaskets pulls USDG; claim/auto-pay send USDG.
• NO forfeit. Your basket is always yours.
• Claim ANYTIME without burning (soft-claim), keeping your tokens.
• Selling AUTO-PAYS your entire basket in the same tx (any sell — even 1 token — pays the whole basket).
CEI + a fallback: if the USDG send fails, the amount is credited to `pendingClaim` (withdraw() later),
so a sell can never brick.
• Plain wallet-to-wallet transfers CARRY the proportional basket (moving between your own wallets is free).
Pool-dependent mechanics (buyback&burn, LP auto-add) live in the v3 hook/router, not here — this contract
is pure reward accounting + a plain ERC-20, so it needs no live pool and tests in isolation.
The per-holder payout ASSET (USDG vs a tokenized stock) is chosen at claim time by the StockRouter layer;
this token always denominates + pays in USDG.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface ICurve {
function token() external view returns (address);
}
contract FlowTokenV3 is ERC20, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 private constant MAGNITUDE = 2 ** 128;
uint256 public constant BPS = 10_000;
IERC20 public immutable usdg; // the reward/base asset (USDG stablecoin)
// ── reward accounting (USDG baskets, MasterChef accumulator) ──
uint256 public magnifiedRewardPerShare;
mapping(address => uint256) public rewardCheckpoint;
mapping(address => uint256) public basket; // crystallised USDG owed
mapping(address => uint256) private _shares;
uint256 public totalShares;
mapping(address => bool) public isExcludedFromRewards;
mapping(address => uint256) public pendingClaim; // USDG that couldn't be auto-sent → withdraw()
// ── config ──
address public treasury;
address public hook; // authorised to fund baskets with the holder-bucket USDG
address public curve; // launcher (single-sided seller / seeder); acts market-side
// V4 singletons that hold the token transiently — marked market-side at construction so a restricted
// launchpad token needs no post-deploy setAMMPair (mirrors FLOWv4). May be 0 for a standalone token.
address public immutable poolManager;
address public immutable positionManager;
address public immutable universalRouter;
bool public immutable restricted; // launchpad token → accounting setters locked (audit M-1)
uint256 public initialSupply;
bool public graduated; // "live on the pool"
mapping(address => bool) public isAMMPair; // pool side: transfer FROM = buy, TO = sell
mapping(address => bool) public isExcludedFromFees;// pre-launch privileged movers
mapping(address => bool) public isExcludedFromLimits;
bool public tradingEnabled;
uint256 public maxTx;
uint256 public maxWallet;
mapping(address => uint256) public holdSince; // display-only hold time
event BasketFilled(uint256 usdgAdded, uint256 newPerShare);
event Claimed(address indexed who, uint256 usdg);
event AutoPaidOnSell(address indexed who, uint256 usdg);
event BasketMoved(address indexed from, address indexed to, uint256 amount);
event PendingCredited(address indexed who, uint256 usdg);
event TradingEnabled();
event HookSet(address hook);
constructor(
string memory name_,
string memory symbol_,
address treasury_,
uint256 supply_, // whole tokens
address usdg_,
bool restricted_,
address poolManager_, // V4 singletons (may be 0 for a standalone token)
address positionManager_,
address universalRouter_
) ERC20(name_, symbol_) Ownable(msg.sender) {
require(treasury_ != address(0) && usdg_ != address(0), "zero");
restricted = restricted_;
treasury = treasury_;
usdg = IERC20(usdg_);
uint256 total = supply_ * 10 ** decimals();
initialSupply = total;
_excludeFromRewards(address(this), true);
_excludeFromRewards(treasury_, true);
_excludeFromRewards(msg.sender, true);
_excludeFromRewards(address(0xdead), true);
isExcludedFromFees[msg.sender] = true;
isExcludedFromFees[address(this)] = true;
isExcludedFromLimits[msg.sender] = true;
isExcludedFromLimits[address(this)] = true;
isExcludedFromLimits[address(0xdead)] = true;
// On a launchpad token the treasury IS the untrusted creator → do NOT limit-exempt it (audit).
if (!restricted_) {
isExcludedFromFees[treasury_] = true;
isExcludedFromLimits[treasury_] = true;
}
maxTx = total / 100; // 1%
maxWallet = total / 50; // 2%
poolManager = poolManager_;
positionManager = positionManager_;
universalRouter = universalRouter_;
if (poolManager_ != address(0)) _markInfrastructure(poolManager_);
if (positionManager_ != address(0)) _markInfrastructure(positionManager_);
if (universalRouter_ != address(0)) _markInfrastructure(universalRouter_);
_mint(msg.sender, total);
}
// ══════════════════════════ core transfer hook ══════════════════════════
function _update(address from, address to, uint256 amount) internal override {
// mint / burn: keep baskets + shares in sync, no forfeit/auto-pay
if (from == address(0) || to == address(0)) {
if (from != address(0)) _settle(from);
if (to != address(0)) _settle(to);
super._update(from, to, amount);
_syncShares(from);
_syncShares(to);
if (to != address(0)) _touchHoldSinceOnReceive(to, amount);
if (from != address(0)) holdSince[from] = block.timestamp;
return;
}
bool isBuy = isAMMPair[from];
if (!tradingEnabled) {
require(isExcludedFromFees[from] || isExcludedFromFees[to], "trading disabled");
}
if (tradingEnabled) {
if (!isExcludedFromLimits[from] && !isExcludedFromLimits[to]) require(amount <= maxTx, "max tx");
if (!isExcludedFromLimits[to] && !isAMMPair[to]) require(balanceOf(to) + amount <= maxWallet, "max wallet");
}
// outgoing move by an eligible holder: SELL/exit → auto-pay the whole basket; plain move → carry it
if (!isBuy && !isExcludedFromRewards[from]) {
_settle(from);
if (isAMMPair[to] || isExcludedFromRewards[to]) {
_autoPayOnSell(from); // sell (or move to an excluded addr): realise the whole basket
} else {
_settle(to);
_moveBasket(from, to, amount); // wallet-to-wallet: basket travels with the tokens
}
}
_settle(from);
_settle(to);
super._update(from, to, amount);
_syncShares(from);
_syncShares(to);
holdSince[from] = block.timestamp;
_touchHoldSinceOnReceive(to, amount);
}
// ══════════════════════════ reward internals ══════════════════════════
function _settle(address account) internal {
if (isExcludedFromRewards[account]) { rewardCheckpoint[account] = magnifiedRewardPerShare; return; }
uint256 m = magnifiedRewardPerShare;
uint256 delta = m - rewardCheckpoint[account];
if (delta > 0) {
uint256 accrued = (delta * balanceOf(account)) / MAGNITUDE;
if (accrued > 0) basket[account] += accrued;
rewardCheckpoint[account] = m;
}
}
function _moveBasket(address from, address to, uint256 amount) internal {
uint256 bal = balanceOf(from);
if (bal == 0) return;
uint256 b = basket[from];
if (b == 0) return;
uint256 moveAmt = (b * amount) / bal;
if (moveAmt == 0) return;
basket[from] = b - moveAmt;
basket[to] += moveAmt;
emit BasketMoved(from, to, moveAmt);
}
/// On a sell (or move to an excluded addr): pay the seller's ENTIRE basket in USDG, same tx. CEI; on a
/// failed send the amount is credited to pendingClaim so the sell never reverts.
function _autoPayOnSell(address from) internal {
uint256 owed = basket[from];
if (owed == 0) return;
basket[from] = 0; // effect first
_payUsdg(from, owed, true);
emit AutoPaidOnSell(from, owed);
}
/// Send USDG best-effort; credit pendingClaim on failure (so an in-swap auto-pay can't brick).
function _payUsdg(address to, uint256 amt, bool creditOnFail) internal {
if (amt == 0) return;
if (usdg.balanceOf(address(this)) < amt) { // shouldn't happen if funded correctly; be safe
if (creditOnFail) { pendingClaim[to] += amt; emit PendingCredited(to, amt); }
return;
}
(bool ok, bytes memory ret) = address(usdg).call(abi.encodeWithSelector(IERC20.transfer.selector, to, amt));
bool success = ok && (ret.length == 0 || abi.decode(ret, (bool)));
if (!success && creditOnFail) { pendingClaim[to] += amt; emit PendingCredited(to, amt); }
else if (!success) revert("usdg transfer");
}
function _syncShares(address account) internal {
if (account == address(0)) return;
uint256 newS = isExcludedFromRewards[account] ? 0 : balanceOf(account);
uint256 old = _shares[account];
if (newS != old) { totalShares = totalShares - old + newS; _shares[account] = newS; }
}
function _touchHoldSinceOnReceive(address to, uint256 received) internal {
if (received == 0 || isExcludedFromRewards[to]) return;
uint256 newBal = balanceOf(to);
uint256 oldBal = newBal > received ? newBal - received : 0;
holdSince[to] = oldBal == 0 ? block.timestamp
: (oldBal * holdSince[to] + received * block.timestamp) / newBal;
}
// ══════════════════════════ claim (soft, no burn) ══════════════════════════
/// Withdraw your accrued basket in USDG WITHOUT burning — keep your tokens, keep earning.
function claim() external nonReentrant {
_settle(msg.sender);
uint256 owed = basket[msg.sender];
require(owed > 0, "nothing");
basket[msg.sender] = 0;
_payUsdg(msg.sender, owed, false); // direct claim: revert on failure (no in-swap context)
emit Claimed(msg.sender, owed);
}
/// Withdraw any USDG that a failed auto-pay credited to you.
function withdrawPending() external nonReentrant {
uint256 amt = pendingClaim[msg.sender];
require(amt > 0, "nothing");
pendingClaim[msg.sender] = 0;
_payUsdg(msg.sender, amt, false);
}
// ══════════════════════════ USDG in → baskets ══════════════════════════
/// The hook (holder bucket), the curve, or the owner deposits USDG here → baskets. Caller must approve.
function fundBaskets(uint256 amount) external {
require(msg.sender == curve || msg.sender == hook || msg.sender == owner(), "auth");
if (amount == 0) return;
usdg.safeTransferFrom(msg.sender, address(this), amount);
if (totalShares > 0) {
magnifiedRewardPerShare += (amount * MAGNITUDE) / totalShares;
emit BasketFilled(amount, magnifiedRewardPerShare);
} else {
pendingClaim[treasury] += amount; // no holders yet → don't strand it (goes to project treasury)
}
}
// ══════════════════════════ views ══════════════════════════
function basketOf(address account) public view returns (uint256) {
if (isExcludedFromRewards[account]) return basket[account];
uint256 delta = magnifiedRewardPerShare - rewardCheckpoint[account];
return basket[account] + (delta * balanceOf(account)) / MAGNITUDE;
}
function burned() external view returns (uint256) {
return initialSupply - totalSupply() + balanceOf(address(0xdead));
}
function holdDuration(address account) external view returns (uint256) {
uint256 hs = holdSince[account];
return hs == 0 ? 0 : block.timestamp - hs;
}
// ══════════════════════════ admin ══════════════════════════
function enableTrading() external onlyOwner {
require(!tradingEnabled, "already");
require(curve == address(0) || graduated, "use curve");
tradingEnabled = true;
emit TradingEnabled();
}
function setHook(address h) external onlyOwner {
require(hook == address(0) && h != address(0), "hook set");
hook = h;
_markInfrastructure(h);
emit HookSet(h);
}
function setCurve(address c) external onlyOwner {
require(curve == address(0) && c != address(0), "curve set");
require(!tradingEnabled, "already live");
require(ICurve(c).token() == address(this), "bad curve");
curve = c;
_markInfrastructure(c);
isExcludedFromFees[c] = true;
isExcludedFromLimits[c] = true;
}
function onGraduate() external {
require(msg.sender == curve, "auth");
graduated = true;
if (!tradingEnabled) { tradingEnabled = true; emit TradingEnabled(); }
}
function removeLimits() external onlyOwner {
maxTx = type(uint256).max;
maxWallet = type(uint256).max;
}
function setLimits(uint256 maxTx_, uint256 maxWallet_) external onlyOwner notRestricted {
require(maxTx_ >= totalSupply() / 1000 && maxWallet_ >= totalSupply() / 1000, "too low");
maxTx = maxTx_; maxWallet = maxWallet_;
}
function setAMMPair(address p, bool v) external onlyOwner notRestricted {
if (!v) require(!_isProtected(p), "protected");
isAMMPair[p] = v;
_settle(p);
_excludeFromRewards(p, v);
_syncShares(p);
}
function setExcludedFromFees(address a, bool v) external onlyOwner notRestricted { isExcludedFromFees[a] = v; }
function setExcludedFromLimits(address a, bool v) external onlyOwner notRestricted { isExcludedFromLimits[a] = v; }
function setExcludedFromRewards(address a, bool v) external onlyOwner notRestricted {
if (!v) require(!_isProtected(a), "protected");
_settle(a);
_excludeFromRewards(a, v);
_syncShares(a);
}
function setTreasury(address t) external onlyOwner {
require(t != address(0), "zero");
treasury = t;
}
// mark a pool/router/curve as market-side: never accrues, TO = sell, FROM = buy, maxWallet-exempt
function _markInfrastructure(address a) internal {
_settle(a);
isAMMPair[a] = true;
_excludeFromRewards(a, true);
_syncShares(a);
}
function _isProtected(address a) internal view returns (bool) {
return a == address(this) || a == hook || a == curve || a == address(0xdead)
|| (a != address(0) && (a == poolManager || a == positionManager || a == universalRouter));
}
function _excludeFromRewards(address a, bool v) internal {
isExcludedFromRewards[a] = v;
rewardCheckpoint[a] = magnifiedRewardPerShare;
}
modifier notRestricted() { require(!restricted, "restricted"); _; }
}
Chain explorer1931msChain node88ms