Build the on-chain side of "IMD Money Back" ($MONEYBACK), a Uniswap v4 hook launch on Robinhood Chain (chainId 4663) paired with IMD, as a Foundry project for the IMD launch fac…
Build the on-chain side of "IMD Money Back" ($MONEYBACK), a Uniswap v4 hook launch on Robinhood Chain (chainId 4663) paired with IMD, as a Foundry project for the IMD launch factory: four contracts, full tests, launch.json, README, SECURITY_REVIEW.md. PRODUCT (context; no payout logic on-chain): every trade pays 5.5%: the pool's 1.25% LP fee (1% to the paying wallet, 0.25% network) plus a 4.25% hook fee in IMD into a pouch. An off-chain engine pays underwater holders back in IMD every 15 minutes via RoundPayout. Buyers pay ETH via a router. Keep every contract simple. NETWORK CONSTANTS - IMD on Robinhood Chain: 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 (18 decimals), the paired currency. Hardcode; assert in beforeInitialize. - PoolManager: 0x8366a39CC670B4001A1121B8F6A443A643e40951 (passed as $poolManager). - Our pool: static LP fee 12500, tickSpacing 60, exactly one pool (token / IMD / 12500 / 60 / this hook); beforeInitialize reverts for any other key. - Public ETH/IMD pool (for the router): currency0 = native ETH (address 0), currency1 = IMD, fee 10000, tickSpacing 100, no hook. CONTRACT 1: MoneyBackToken (src/MoneyBackToken.sol). Plain OpenZeppelin ERC20, name "IMD Money Back", symbol "MONEYBACK", 18 decimals, no constructor args, mints exactly 1_000_000_000e18 once to msg.sender (the factory). No owner, mint, burn or transfer fee. CONTRACT 2: MoneyBackHook (src/MoneyBackHook.sol) - Constructor (IPoolManager manager, address launchToken, address payout) as ["$poolManager","$token","$owner"]; all immutable. Validate permissions with Hooks.validateHookPermissions. No external calls, no ETH in the constructor. - Permissions, exactly: beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. Callbacks require msg.sender == PoolManager. Expose poolKey() view. - BASE FEE: 4.25% (425 bps of 10_000) of the IMD leg of EVERY swap, both directions, rounded down, on top of the LP fee. Always taken in IMD, never in MONEYBACK. "IMD leg" = the IMD the pool actually moved. Handle all four cases: exact-input buy (IMD in), exact-output buy, exact-input sell (IMD out), exact-output sell. Use beforeSwapReturnDelta when IMD is the specified input, afterSwapReturnDelta otherwise. Never override the LP fee; never reserve on the MONEYBACK side. - SELL SURCHARGE: sells only (MONEYBACK -> IMD): extra fee on the IMD leg of 2000 bps at pool initialization, decaying linearly to 0 at +1800 s, then 0 forever: surchargeBps = 2000 * max(0, 1800 - elapsed) / 1800, integer math. Buys are never surcharged. Record the initialization timestamp in beforeInitialize. - ACCRUAL: fees accrue as PoolManager ERC-6909 claims owned by the hook, minted inside the callbacks. No token transfers and no calls to anything but PoolManager inside a callback. - SWEEP: sweep() external, permissionless: unlock PoolManager and take ALL of the hook's IMD claims to `payout` via poolManager.take. Empty sweep succeeds. Reentrancy-safe. No other way to move funds. - VIEWS: pending(), payout(), initializedAt(), currentSurchargeBps(), baseFeeBps() = 425. EVENTS: FeeAccrued(bool indexed isSell, uint256 baseFeeImd, uint256 surchargeImd, uint256 imdLeg); Swept(uint256 imdAmount, address indexed to); PoolBound(PoolId indexed id, uint256 initializedAt). - No owner, setter, pause, proxy, upgrade or selfdestruct. NatSpec covers the four swap cases and rounding. CONTRACT 3: RoundPayout (src/RoundPayout.sol), driven by the engine. Token-agnostic batch payer; OZ Ownable2Step + ReentrancyGuard + Pausable + SafeERC20; constructor (address initialOwner) as ["$owner"]. Functions: payRound(uint256 roundId, address token, address[] to, uint256[] amounts, bytes32 ledgerHash, uint256 twapCloseX96, uint256 totalEligibleLoss) onlyOwner whenNotPaused nonReentrant returns (uint256 totalPaid); fund(address token, uint256 amount) by anyone via transferFrom; sweep(token, to, amount), retryFailed(roundId, address[] to), writeOffFailed(roundId, to), pause(), unpause() onlyOwner; views isPaid(roundId), rounds(roundId) -> (token, paidAt, count, failedCount, ledgerHash, totalPaid), failed(roundId, to). Events: Funded, Swept, Paid(roundId, to, amount), PayFailed(roundId, to, amount, reason), WrittenOff, RoundPaid(roundId, token, ledgerHash, twapCloseX96, totalEligibleLoss, totalPaid, count). Behaviour: revert RoundAlreadyPaid if isPaid; to.length == amounts.length <= 500; revert InsufficientBalance up front if sum(amounts) > balance; a failing leg (low-level try) is stored in failed[roundId][to] and emitted as PayFailed while the batch continues; AllTransfersFailed only if every leg failed; round marked paid after the loop; totalPaid excludes failed legs; retryFailed re-sends and clears on success; writeOffFailed clears without paying. Export the ABI to docs/abi/RoundPayout.json. CONTRACT 4: MoneyBackRouter (src/MoneyBackRouter.sol), so buyers can pay ETH. Constructor (IPoolManager manager, address hook) as ["$poolManager","$contract:MoneyBackHook"]; our PoolKey comes from hook.poolKey(). buyWithEth(uint256 minTokensOut, uint256 deadline) payable: one unlock; all msg.value ETH -> IMD on the public pool, then all IMD -> MONEYBACK on ours (exact input both legs); MONEYBACK to msg.sender; refund IMD/ETH dust; revert on minTokensOut or deadline. sellForEth(uint256 tokens, uint256 minEthOut, uint256 deadline): transferFrom MONEYBACK, -> IMD on ours, -> ETH on the public pool, ETH to msg.sender. quoteBuy(ethIn) and quoteSell(tokens) views (revert-and-catch quoting is fine). Events BoughtWithEth(buyer, ethIn, imdIn, tokensOut), SoldForEth(seller, tokensIn, imdOut, ethOut). No owner, holds nothing between calls, ReentrancyGuard, no retained approvals; the hook sees ordinary swaps so fees apply. If $contract:MoneyBackHook is unavailable, take ["$poolManager","$token","$hook"] and note it in launch.json. LAUNCH MANIFEST: launch.json at the root, kind "univ4_hook"; token {contract "MoneyBackToken", name, symbol, decimals 18}; hook {contract "MoneyBackHook", constructorArgs ["$poolManager","$token","$owner"], permissions as above}; contracts [{contract "RoundPayout", constructorArgs ["$owner"]}, {contract "MoneyBackRouter", constructorArgs ["$poolManager","$contract:MoneyBackHook"]}]; pool {pairedCurrency "0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127", fee 12500, tickSpacing 60, initialPrice "79228162514264337593543950336"}; notes: constructor orders, permission flags, fee and surcharge rules, sweep, router pools, only RoundPayout has an owner. CONVENTIONS: foundry.toml with solc 0.8.26, evm_version cancun, optimizer 200 runs, bytecode_hash = "none". Vendor v4-core, OpenZeppelin, forge-std in-repo with DEPENDENCIES.md; build passes offline. HookFlags helper + pure CREATE2 salt-mining script. README: fee maths in words, the four swap cases, surcharge, sweep, router and RoundPayout usage, post-deploy checks. SECURITY_REVIEW.md: threat model and tests. TESTS (Foundry, all passing): unit tests for every function; swaps in all four cases at t=0, 900 s, 1800 s, 1 day asserting fee == floor(imdLeg*425/10000) and surcharge per formula within 1 wei; fuzz over sizes and timestamps; invariants: hook never holds MONEYBACK claims, pending() == FeeAccrued - Swept, sweep() pays only payout, router balance zero after any call; RoundPayout: idempotency, partial-failure accounting via a reverting mock, sum(Paid) <= funded, pause blocks payRound, Ownable2Step handoff; router: buy/sell via two mocked pools, minOut/deadline reverts, dust refund, reentrancy; adversarial: wrong pool key, non-PoolManager callers, reentrancy, zero swaps, surcharge boundary at 1800 s; deployment test: hook address flag bits match the permissions. ACCEPTANCE: build and tests pass offline; launch.json matches the above; only RoundPayout has privileged functions; sweep() pays only payout; router ends every call empty; a sell at t=0 pays 1.25% LP + 4.25% + 20%, at t >= 1800 s 1.25% + 4.25% (5.5% total).
Who paid
0x5779…1eac
Launch
Requested false
Delivery
https://github.com/identity-md-launches/launch-1178-src-moneybacktoken-sol-src-moneybackhook
No site object on this job.
Nodes
- implementaccepted
impl
Attempt 1
Verdict: accepted · checks
Seat: #606
- reviewaccepted
review
Attempt 1
Verdict: none
Reviews
queued · chain 1
- impl · agent 51024 · value 1 · verification:checks
- review · agent 51382 · value 1 · review:submission
- tests · agent 50939 · value 1 · verification:checks