FlowHookV3
0x6b9a1378c6730c87c4fc7f672208cd5f4c1920cc
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
16,430 bytes
ABI entries
70
30 read · 23 write
License
none
Contract information
- Address
- 0x6b9a1378c6730c87c4fc7f672208cd5f4c1920cc
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x31B544B3A8…9705dA3B27
- Creation tx
- 0xe6f121b746…c5f6eda735
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (30)
BPS() → uint256
DEAD() → address
LP_BAND() → int24
MAX_FEE_BPS() → uint256
MIN_FEE_BPS() → uint256
POT_SHARE_BPS() → uint256
SNIPE_GUARD() → uint256
SNIPE_MAX_BPS() → uint256
accruedBuyback(bytes32) → uint256
accruedLP(bytes32) → uint256
accruedPlatform() → uint256
accruedRef(bytes32) → uint256
accruedSnipe(bytes32) → uint256
accruedTreasury(bytes32) → uint256
accruedUsdg(bytes32) → uint256
admin() → address
burnPot() → address
currentFeeBps(tuple) → uint256
factory() → address
getHookPermissions() → tuple
keeper() → address
launchAt(bytes32) → uint64
platformCutBps(uint256) → uint256
platformWallet() → address
poolInitializer(tuple) → address
poolManager() → address
pools(bytes32) → address, uint16, address, address, uint16, uint16, uint16, uint16, uint16, bool
refDistributor() → address
referral() → address
usdg() → address
Events (11)
AccruedBurnPotSetBuybackBurnedFactorySetKeeperSetLPAddedPoolRegisteredRefDistributedRefDistributorSetReferralSetRouted
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "_pm",
"type": "address"
},
{
"internalType": "address",
"name": "_usdg",
"type": "address"
},
{
"internalType": "address",
"name": "_platformWallet",
"type": "address"
},
{
"internalType": "address",
"name": "_admin",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "HookNotImplemented",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPool",
"type": "error"
},
{
"inputs": [],
"name": "NotPoolManager",
"type": "error"
},
{
"inputs": [],
"name": "NotSelf",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Accrued",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "pot",
"type": "address"
}
],
"name": "BurnPotSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "usdgIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokensBurned",
"type": "uint256"
}
],
"name": "BuybackBurned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "factory",
"type": "address"
}
],
"name": "FactorySet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "keeper",
"type": "address"
}
],
"name": "KeeperSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "usdgIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
}
],
"name": "LPAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "poolId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "address",
"name": "treasury",
"type": "address"
}
],
"name": "PoolRegistered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toReferrers",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toHolders",
"type": "uint256"
}
],
"name": "RefDistributed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "refDistributor",
"type": "address"
}
],
"name": "RefDistributorSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "referral",
"type": "address"
}
],
"name": "ReferralSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toHolders",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toPlatform",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toTreasury",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toLP",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toBuyback",
"type": "uint256"
}
],
"name": "Routed",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DEAD",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "LP_BAND",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POT_SHARE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SNIPE_GUARD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SNIPE_MAX_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "accruedBuyback",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "accruedLP",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "accruedPlatform",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "accruedRef",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "accruedSnipe",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "accruedTreasury",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "accruedUsdg",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "admin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "delta0",
"type": "int256"
},
{
"internalType": "BalanceDelta",
"name": "delta1",
"type": "int256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterAddLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterDonate",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "afterInitialize",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "delta0",
"type": "int256"
},
{
"internalType": "BalanceDelta",
"name": "delta1",
"type": "int256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterRemoveLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BalanceDelta",
"name": "",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
}
],
"internalType": "struct SwapParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "BalanceDelta",
"name": "delta",
"type": "int256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "afterSwap",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "int128",
"name": "",
"type": "int128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeAddLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeDonate",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "beforeInitialize",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "int256",
"name": "liquidityDelta",
"type": "int256"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
}
],
"internalType": "struct ModifyLiquidityParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeRemoveLiquidity",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"components": [
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
}
],
"internalType": "struct SwapParams",
"name": "params",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "hookData",
"type": "bytes"
}
],
"name": "beforeSwap",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
},
{
"internalType": "BeforeSwapDelta",
"name": "",
"type": "int256"
},
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "burnPot",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
}
],
"name": "currentFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "address[]",
"name": "buyers",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
},
{
"internalType": "uint256",
"name": "toHolders",
"type": "uint256"
}
],
"name": "distributeRef",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "flushPlatform",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
}
],
"name": "flushTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getHookPermissions",
"outputs": [
{
"components": [
{
"internalType": "bool",
"name": "beforeInitialize",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterInitialize",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeAddLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterAddLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeRemoveLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterRemoveLiquidity",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeSwap",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterSwap",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeDonate",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterDonate",
"type": "bool"
},
{
"internalType": "bool",
"name": "beforeSwapReturnDelta",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterSwapReturnDelta",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterAddLiquidityReturnDelta",
"type": "bool"
},
{
"internalType": "bool",
"name": "afterRemoveLiquidityReturnDelta",
"type": "bool"
}
],
"internalType": "struct Hooks.Permissions",
"name": "",
"type": "tuple"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
}
],
"name": "harvest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "keeper",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "launchAt",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "feeBps",
"type": "uint256"
}
],
"name": "platformCutBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "platformWallet",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
}
],
"name": "poolInitializer",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "pools",
"outputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "address",
"name": "initializer",
"type": "address"
},
{
"internalType": "address",
"name": "treasury",
"type": "address"
},
{
"internalType": "uint16",
"name": "holderBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "lpBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "treasuryBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "buybackBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "refBps",
"type": "uint16"
},
{
"internalType": "bool",
"name": "registered",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "minOut",
"type": "uint256"
}
],
"name": "processBuyback",
"outputs": [
{
"internalType": "uint256",
"name": "tokensBurned",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
}
],
"name": "processLP",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "refDistributor",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "referral",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "Currency",
"name": "currency0",
"type": "address"
},
{
"internalType": "Currency",
"name": "currency1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"internalType": "contract IHooks",
"name": "hooks",
"type": "address"
}
],
"internalType": "struct PoolKey",
"name": "key",
"type": "tuple"
},
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "address",
"name": "initializer",
"type": "address"
},
{
"internalType": "address",
"name": "treasury",
"type": "address"
},
{
"internalType": "uint16[5]",
"name": "split",
"type": "uint16[5]"
}
],
"name": "register",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "p",
"type": "address"
}
],
"name": "setBurnPot",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "f",
"type": "address"
}
],
"name": "setFactory",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "k",
"type": "address"
}
],
"name": "setKeeper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "d",
"type": "address"
}
],
"name": "setRefDistributor",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "r",
"type": "address"
}
],
"name": "setReferral",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "usdg",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FlowHookV3 — v3 tax hook (contract #2) ⚠️ WIP, NOT AUDITED
Same 6909-claim skim + permissionless-flush engine as FlowHookSSb, but on the **USDG** leg (the token
address is mined > USDG so USDG is always currency0 — the slot native ETH held in v2). It computes the
tiered platform cut and splits the launcher's portion across 5 buckets.
Per trade (tax = feeBps of the trade, taken in USDG):
• platform cut = min(feeBps/2, feeBps ≤ 5% ? 0.5% : 1%). Of that: ~20% → the FLOW burn pot, ~80% → the
platform wallet. (accrued → flushPlatform)
• launcher portion = tax − platform cut, split by the pool's holder/lp/treasury/buyback/referral bps:
- holders → the token's baskets (inline USDG fundBaskets — trusted)
- referral → holders for now (unreferred default; real attribution is a later step)
- treasury → accrued (flushTreasury)
- LP → accrued (processLP, deferred: pool op)
- buyback → accrued (processBuyback, deferred: pool op)
• the anti-snipe surcharge goes entirely to holders.
Only the trusted token.fundBaskets call is inline; every other payout accrues and is flushed OUTSIDE the
swap (reentrancy-safe). USDG is treated as a standard, non-reentrant ERC-20.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
import {BaseHook} from "@openzeppelin/uniswap-hooks/src/base/BaseHook.sol";
import {CurrencySettler} from "@openzeppelin/uniswap-hooks/src/utils/CurrencySettler.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {SwapParams, ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary, toBeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface IFlowToken {
function fundBaskets(uint256 amount) external;
}
interface IReferralRegistry {
function credit(address user, uint256 amount) external;
}
contract FlowHookV3 is BaseHook, IUnlockCallback {
using CurrencySettler for Currency;
using BalanceDeltaLibrary for BalanceDelta;
using SafeCast for uint256;
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
using SafeERC20 for IERC20;
uint256 public constant BPS = 10_000;
uint256 public constant MIN_FEE_BPS = 50; // 0.5%
uint256 public constant MAX_FEE_BPS = 1_000; // 10%
uint256 public constant POT_SHARE_BPS = 2_000; // 20% of the platform cut → burn pot
uint256 public constant SNIPE_GUARD = 60;
uint256 public constant SNIPE_MAX_BPS = 400;
address public constant DEAD = 0x000000000000000000000000000000000000dEaD; // buyback burn sink
int24 public constant LP_BAND = 6_000; // width (ticks, 100 spacings) of a single-sided USDG sell-support band
uint8 private constant ACT_REDEEM = 0; // unlock actions
uint8 private constant ACT_BUYBACK = 1;
uint8 private constant ACT_LP = 2;
IERC20 public immutable usdg; // the base currency (currency0)
address public immutable platformWallet;
address public immutable admin;
address public factory;
address public keeper; // LOW-trust: spot-touching bucket ops (buyback/LP) → pool/burn only
address public refDistributor; // FUND-CUSTODIAL: attributes + pays the referral bucket (distributeRef)
address public burnPot; // FLOW burn pot (settable once); until set, the pot share → platform wallet
address public referral; // ReferralRegistry (settable once); until set, the ref slice folds → holders
struct PoolConfig {
address token;
uint16 feeBps;
address initializer;
address treasury;
uint16 holderBps; // split of the LAUNCHER portion (the 5 sum to BPS)
uint16 lpBps;
uint16 treasuryBps;
uint16 buybackBps;
uint16 refBps;
bool registered;
}
mapping(bytes32 => PoolConfig) public pools;
mapping(bytes32 => uint256) public accruedUsdg; // USDG-claim fees per pool, awaiting flush
mapping(bytes32 => uint256) public accruedSnipe; // of accruedUsdg, the surcharge portion (→ holders)
mapping(bytes32 => uint256) public accruedTreasury; // real USDG owed to a pool's treasury
mapping(bytes32 => uint256) public accruedLP; // real USDG for LP auto-add (processLP — deferred)
mapping(bytes32 => uint256) public accruedBuyback; // real USDG for buyback&burn (processBuyback)
mapping(bytes32 => uint256) public accruedRef; // real USDG for referral payout (distributeRef); unref → holders
mapping(bytes32 => uint64) public launchAt;
uint256 public accruedPlatform; // real USDG owed to platform wallet + burn pot
bool private _internalOp; // set while the hook swaps on its OWN pool (buyback) → skip the tax
event FactorySet(address factory);
event BurnPotSet(address pot);
event KeeperSet(address keeper);
event RefDistributorSet(address refDistributor);
event ReferralSet(address referral);
event RefDistributed(address indexed token, uint256 toReferrers, uint256 toHolders);
event PoolRegistered(bytes32 indexed poolId, address token, uint16 feeBps, address treasury);
event Accrued(bytes32 indexed poolId, uint256 amount);
event Routed(address indexed token, uint256 toHolders, uint256 toPlatform, uint256 toTreasury, uint256 toLP, uint256 toBuyback);
event BuybackBurned(address indexed token, uint256 usdgIn, uint256 tokensBurned);
event LPAdded(address indexed token, uint256 usdgIn, int24 tickLower, int24 tickUpper);
constructor(IPoolManager _pm, address _usdg, address _platformWallet, address _admin) BaseHook(_pm) {
require(_usdg != address(0) && _platformWallet != address(0) && _admin != address(0), "zero");
usdg = IERC20(_usdg);
platformWallet = _platformWallet;
admin = _admin;
keeper = _admin; // default; admin delegates to a low-trust backend keeper
refDistributor = _admin; // default; admin delegates to a secured (multisig) referral distributor
}
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: true, afterInitialize: false,
beforeAddLiquidity: false, afterAddLiquidity: false,
beforeRemoveLiquidity: false, afterRemoveLiquidity: false,
beforeSwap: true, afterSwap: true,
beforeDonate: false, afterDonate: false,
beforeSwapReturnDelta: true, afterSwapReturnDelta: true,
afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false
});
}
// ─────────────────────────── admin ───────────────────────────
function setFactory(address f) external {
require(msg.sender == admin && factory == address(0) && f != address(0), "set");
factory = f; emit FactorySet(f);
}
function setBurnPot(address p) external {
require(msg.sender == admin && burnPot == address(0) && p != address(0), "set");
burnPot = p; emit BurnPotSet(p);
}
/// Wire the ReferralRegistry (one-shot). The hook must also be set as the registry's distributor so it can
/// credit referrers. Until set, the referral slice folds into holders (see _route).
function setReferral(address r) external {
require(msg.sender == admin && referral == address(0) && r != address(0), "set");
referral = r; emit ReferralSet(r);
}
/// The keeper triggers the spot-touching bucket ops (processBuyback/processLP) with a fair, quoted minOut,
/// so a third party can't sandwich a permissionless flush. It can move funds ONLY into the pool or the burn
/// sink (DEAD) — never to an arbitrary party; worst case a bad keeper skips the op (USDG just keeps accruing,
/// never lost). Referral payouts (which DO pay arbitrary referrers) are a SEPARATE, higher-trust role — see
/// refDistributor — so this keeper can stay a low-trust hot key.
function setKeeper(address k) external {
require(msg.sender == admin && k != address(0), "auth");
keeper = k; emit KeeperSet(k);
}
/// The referral distributor attributes referred buys off-chain and calls distributeRef; because it can direct
/// the referral bucket to referrer addresses, it is FUND-CUSTODIAL for that bucket — secure it accordingly
/// (ideally a multisig), NOT the same low-trust hot key as the keeper.
function setRefDistributor(address d) external {
require(msg.sender == admin && d != address(0), "auth");
refDistributor = d; emit RefDistributorSet(d);
}
/// split bps: [holder, lp, treasury, buyback, ref] must sum to BPS.
function register(PoolKey calldata key, address token, uint16 feeBps, address initializer, address treasury, uint16[5] calldata split) external {
require(msg.sender == factory, "only factory");
require(feeBps >= MIN_FEE_BPS && feeBps <= MAX_FEE_BPS, "fee range");
require(initializer != address(0) && token != address(0) && treasury != address(0), "zero");
require(uint256(split[0]) + split[1] + split[2] + split[3] + split[4] == BPS, "split sum");
require(Currency.unwrap(key.currency0) == address(usdg) && Currency.unwrap(key.currency1) == token, "key");
require(key.fee == 3000 && key.tickSpacing == 60 && address(key.hooks) == address(this), "key params");
bytes32 id = PoolId.unwrap(key.toId());
require(!pools[id].registered, "registered");
pools[id] = PoolConfig(token, feeBps, initializer, treasury, split[0], split[1], split[2], split[3], split[4], true);
emit PoolRegistered(id, token, feeBps, treasury);
}
function poolInitializer(PoolKey calldata key) external view returns (address) {
return pools[PoolId.unwrap(key.toId())].initializer;
}
function currentFeeBps(PoolKey calldata key) external view returns (uint256) {
bytes32 id = PoolId.unwrap(key.toId());
return pools[id].feeBps + _snipeBps(id);
}
/// the platform cut in bps for a given tax: min(feeBps/2, cap) — cap 0.5% ≤5% tax, 1% above.
function platformCutBps(uint256 feeBps) public pure returns (uint256) {
uint256 cap = feeBps <= 500 ? 50 : 100;
uint256 half = feeBps / 2;
return half < cap ? half : cap;
}
function _beforeInitialize(address sender, PoolKey calldata key, uint160) internal override returns (bytes4) {
bytes32 id = PoolId.unwrap(key.toId());
PoolConfig storage cfg = pools[id];
require(cfg.registered && sender == cfg.initializer, "not initializer");
launchAt[id] = uint64(block.timestamp);
return this.beforeInitialize.selector;
}
function _snipeBps(bytes32 id) internal view returns (uint256) {
uint64 t = launchAt[id];
if (t == 0) return 0;
uint256 elapsed = block.timestamp - t;
if (elapsed >= SNIPE_GUARD) return 0;
return (SNIPE_MAX_BPS * (SNIPE_GUARD - elapsed)) / SNIPE_GUARD;
}
// ─────────────────────────── beforeSwap (BUY exact-in USDG) ───────────────────────────
function _beforeSwap(address, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal override returns (bytes4, BeforeSwapDelta, uint24)
{
if (_internalOp) return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); // hook's own buyback → no tax
bytes32 id = PoolId.unwrap(key.toId());
PoolConfig storage cfg = pools[id];
if (cfg.registered && params.zeroForOne && params.amountSpecified < 0) { // USDG in
uint256 amt = uint256(-params.amountSpecified);
uint256 base = (amt * cfg.feeBps) / BPS;
uint256 snipe = (amt * _snipeBps(id)) / BPS;
uint256 fee = base + snipe;
if (fee > 0) {
key.currency0.take(poolManager, address(this), fee, true);
accruedUsdg[id] += fee; accruedSnipe[id] += snipe;
emit Accrued(id, fee);
_flush(id, key);
return (this.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);
}
}
return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}
// ─────────────────────────── afterSwap (sells / exact-out) ───────────────────────────
function _afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata)
internal override returns (bytes4, int128)
{
if (_internalOp) return (this.afterSwap.selector, int128(0)); // hook's own buyback → no tax
bytes32 id = PoolId.unwrap(key.toId());
PoolConfig storage cfg = pools[id];
bool buyExactIn = params.zeroForOne && params.amountSpecified < 0;
if (!cfg.registered || buyExactIn) return (this.afterSwap.selector, int128(0));
bool exactIn = params.amountSpecified < 0;
bool specifiedIs0 = exactIn ? params.zeroForOne : !params.zeroForOne;
bool unspecifiedIs0 = !specifiedIs0;
// We can ONLY charge a fee we can redeem: the hook has no path to redeem currency1 (token) 6909 claims,
// so an exact-OUTPUT sell (unspecified leg = token) is left untaxed. Never take() a currency with no
// redemption path — doing so would freeze the fee as unredeemable claims and credit no bucket.
if (!unspecifiedIs0) return (this.afterSwap.selector, int128(0));
uint256 mag = delta.amount0() < 0 ? uint256(uint128(-delta.amount0())) : uint256(uint128(delta.amount0()));
uint256 base = (mag * cfg.feeBps) / BPS;
uint256 snipe = (mag * _snipeBps(id)) / BPS;
uint256 fee = base + snipe;
if (fee == 0) return (this.afterSwap.selector, int128(0));
key.currency0.take(poolManager, address(this), fee, true);
accruedUsdg[id] += fee; accruedSnipe[id] += snipe; emit Accrued(id, fee);
_flush(id, key);
return (this.afterSwap.selector, fee.toInt128());
}
function _flush(bytes32 id, PoolKey calldata key) internal {
uint256 amt = accruedUsdg[id];
if (amt == 0 || usdg.balanceOf(address(poolManager)) < amt) return;
uint256 snipe = accruedSnipe[id];
accruedUsdg[id] = 0; accruedSnipe[id] = 0;
poolManager.burn(address(this), key.currency0.toId(), amt);
key.currency0.take(poolManager, address(this), amt, false); // pull real USDG here
_route(id, amt - snipe, snipe);
}
/// permissionless: redeem a pool's accrued USDG claims → real USDG → route.
function harvest(PoolKey calldata key) external {
bytes32 id = PoolId.unwrap(key.toId());
uint256 amount = accruedUsdg[id];
require(amount > 0, "nothing");
uint256 snipe = accruedSnipe[id];
accruedUsdg[id] = 0; accruedSnipe[id] = 0;
poolManager.unlock(abi.encode(ACT_REDEEM, key.currency0, amount));
_route(id, amount - snipe, snipe);
}
function unlockCallback(bytes calldata data) external override returns (bytes memory) {
require(msg.sender == address(poolManager), "auth");
uint8 action = abi.decode(data, (uint8));
if (action == ACT_REDEEM) {
(, Currency cur, uint256 amount) = abi.decode(data, (uint8, Currency, uint256));
poolManager.burn(address(this), cur.toId(), amount);
cur.take(poolManager, address(this), amount, false);
return "";
}
if (action == ACT_BUYBACK) {
(, PoolKey memory key, uint256 amt, uint256 minOut) = abi.decode(data, (uint8, PoolKey, uint256, uint256));
_internalOp = true;
BalanceDelta d = poolManager.swap(
key,
SwapParams({zeroForOne: true, amountSpecified: -int256(amt), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1}),
""
);
_internalOp = false;
uint256 paid = uint256(uint128(-d.amount0())); // USDG owed to the pool
uint256 out = uint256(uint128(d.amount1())); // token received
require(out >= minOut, "slippage");
key.currency0.settle(poolManager, address(this), paid, false); // pay real USDG
key.currency1.take(poolManager, DEAD, out, false); // burn the token
return abi.encode(paid, out);
}
// ACT_LP: single-sided USDG sell-support band strictly ABOVE spot (token0-only → no token needed).
(, PoolKey memory k, uint256 lpAmt) = abi.decode(data, (uint8, PoolKey, uint256));
(, int24 tick, , ) = poolManager.getSlot0(k.toId());
int24 spacing = k.tickSpacing;
int24 lower = _alignUp(tick + 1, spacing);
int24 maxT = TickMath.maxUsableTick(spacing);
int24 upper = lower + LP_BAND > maxT ? maxT : lower + LP_BAND;
require(lower < upper, "lp range");
uint128 liq = LiquidityAmounts.getLiquidityForAmount0(
TickMath.getSqrtPriceAtTick(lower), TickMath.getSqrtPriceAtTick(upper), lpAmt
);
require(liq > 0, "lp dust");
(BalanceDelta ld, ) = poolManager.modifyLiquidity(
k, ModifyLiquidityParams({tickLower: lower, tickUpper: upper, liquidityDelta: int256(uint256(liq)), salt: bytes32(0)}), ""
);
uint256 spent = ld.amount0() < 0 ? uint256(uint128(-ld.amount0())) : 0;
if (spent > 0) k.currency0.settle(poolManager, address(this), spent, false);
// a token-side owe would mean the band straddled spot; lower>tick guarantees it never does.
require(ld.amount1() >= 0, "lp straddle");
return abi.encode(spent, uint256(uint24(lower)) | (uint256(uint24(upper)) << 24));
}
/// Round `x` up to the nearest multiple of `s` (works for negative ticks).
function _alignUp(int24 x, int24 s) internal pure returns (int24 r) {
r = (x / s) * s;
if (r < x) r += s;
}
/// Split a redeemed BASE (USDG) amount across the 5 buckets + platform. Only the trusted token
/// fundBaskets is inline; platform/treasury/LP/buyback accrue and flush outside the swap.
function _route(bytes32 id, uint256 base, uint256 snipe) internal {
PoolConfig storage cfg = pools[id];
uint256 platCut = (base * platformCutBps(cfg.feeBps)) / cfg.feeBps; // tiered platform cut in USDG
uint256 launcher = base - platCut;
uint256 toHolders = (launcher * cfg.holderBps) / BPS;
uint256 toLP = (launcher * cfg.lpBps) / BPS;
uint256 toTreasury = (launcher * cfg.treasuryBps) / BPS;
uint256 toBuyback = (launcher * cfg.buybackBps) / BPS;
uint256 toRef = launcher - toHolders - toLP - toTreasury - toBuyback; // referral remainder
uint256 holderTotal = toHolders + snipe;
// The referral slice accrues for the distributor to pay referrers (unreferred buys → holders, via
// distributeRef). BEFORE a registry is wired, or when there's no ref slice, it folds into holders —
// this is the safe default and preserves behaviour for pools without referrals.
if (referral == address(0) || toRef == 0) holderTotal += toRef;
else accruedRef[id] += toRef;
accruedPlatform += platCut;
accruedTreasury[id] += toTreasury;
accruedLP[id] += toLP;
accruedBuyback[id] += toBuyback;
if (holderTotal > 0) {
usdg.forceApprove(cfg.token, holderTotal);
IFlowToken(cfg.token).fundBaskets(holderTotal); // pulls USDG via transferFrom
}
emit Routed(cfg.token, holderTotal, platCut, toTreasury, toLP, toBuyback);
}
// ─────────────────────────── flushes (permissionless, outside swaps) ───────────────────────────
function flushPlatform() external {
uint256 amt = accruedPlatform;
require(amt > 0, "nothing");
accruedPlatform = 0;
uint256 pot = (burnPot != address(0)) ? (amt * POT_SHARE_BPS) / BPS : 0;
if (pot > 0) usdg.safeTransfer(burnPot, pot);
usdg.safeTransfer(platformWallet, amt - pot);
}
function flushTreasury(PoolKey calldata key) external {
bytes32 id = PoolId.unwrap(key.toId());
uint256 amt = accruedTreasury[id];
require(amt > 0, "nothing");
accruedTreasury[id] = 0;
usdg.safeTransfer(pools[id].treasury, amt);
}
/// Permissionless: spend a pool's accrued buyback USDG on its own token and burn it (→ DEAD).
/// `minOut` bounds slippage; a keeper quotes it. Callers who pass 0 risk being sandwiched.
function processBuyback(PoolKey calldata key, uint256 minOut) external returns (uint256 tokensBurned) {
require(msg.sender == keeper, "keeper"); // spot-touching op: keeper quotes a fair minOut (no third-party sandwich)
bytes32 id = PoolId.unwrap(key.toId());
require(pools[id].registered, "unregistered");
uint256 amt = accruedBuyback[id];
require(amt > 0, "nothing");
accruedBuyback[id] = 0;
(uint256 paid, uint256 out) = abi.decode(
poolManager.unlock(abi.encode(ACT_BUYBACK, key, amt, minOut)), (uint256, uint256)
);
if (paid < amt) accruedBuyback[id] += amt - paid; // refund any unspent dust
emit BuybackBurned(pools[id].token, paid, out);
return out;
}
/// Permissionless: add a pool's accrued LP USDG as single-sided sell-support liquidity above spot,
/// owned by the hook and never removed (permanently locked). Unspent dust is re-accrued.
function processLP(PoolKey calldata key) external {
require(msg.sender == keeper, "keeper"); // spot-touching op: keeper calls it at a fair tick (no manipulation sandwich)
bytes32 id = PoolId.unwrap(key.toId());
require(pools[id].registered, "unregistered");
uint256 amt = accruedLP[id];
require(amt > 0, "nothing");
accruedLP[id] = 0;
(uint256 spent, uint256 packed) = abi.decode(
poolManager.unlock(abi.encode(ACT_LP, key, amt)), (uint256, uint256)
);
if (spent < amt) accruedLP[id] += amt - spent;
emit LPAdded(pools[id].token, spent, int24(uint24(packed)), int24(uint24(packed >> 24)));
}
/// Pay out a pool's accrued referral USDG. The refDistributor reads referred buys off-chain (buyer→referrer
/// via the ReferralRegistry) and passes each referred `buyers[i]` + their earned `amounts[i]`; `toHolders` is
/// the unreferred remainder that folds back to holder baskets. A credit that fails (e.g. the buyer's
/// referrer got unset) is skipped and its amount folds to holders too, so one bad entry can't block the
/// batch. Bounded by accruedRef so the keeper can never distribute more than the pool actually skimmed.
function distributeRef(PoolKey calldata key, address[] calldata buyers, uint256[] calldata amounts, uint256 toHolders) external {
require(msg.sender == refDistributor, "refdist"); // fund-custodial role, separate from the low-trust keeper
require(buyers.length == amounts.length, "len");
bytes32 id = PoolId.unwrap(key.toId());
PoolConfig storage cfg = pools[id];
require(cfg.registered && referral != address(0), "unavailable");
uint256 total = toHolders;
for (uint256 i; i < amounts.length; ++i) total += amounts[i];
require(total <= accruedRef[id], "exceeds accrued");
accruedRef[id] -= total;
uint256 credited;
for (uint256 i; i < buyers.length; ++i) {
uint256 amt = amounts[i];
if (amt == 0) continue;
usdg.forceApprove(referral, amt);
try IReferralRegistry(referral).credit(buyers[i], amt) { credited += amt; }
catch { usdg.forceApprove(referral, 0); } // failed → folds to holders below
}
uint256 toBaskets = total - credited; // intended unreferred remainder + any failed credits
if (toBaskets > 0) {
usdg.forceApprove(cfg.token, toBaskets);
IFlowToken(cfg.token).fundBaskets(toBaskets);
}
emit RefDistributed(cfg.token, credited, toBaskets);
}
}
Chain explorer3613msChain node81ms