Guest User

exdiv

a guest
Jul 8th, 2026
74
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.46 KB | None | 0 0
  1. # exdiv · contracts and deployment
  2.  
  3. one contract that is the erc 20, the erc 8056 surface, and the uniswap v4 hook. fee capture in native eth on both swap directions, a daily declare and pay cycle on utc bells, payment as a buyback through its own pool that retires what it buys and ticks the multiplier. no owner, no admin functions, no pause, no upgrade. plus the foundry deploy script that mines the hook address flags and initializes the canonical pool.
  4.  
  5. copy each block into the stated path.
  6.  
  7. ## 0. setup
  8.  
  9. ```bash
  10. forge init exdiv && cd exdiv
  11. forge install uniswap/v4-core
  12. forge install uniswap/v4-periphery
  13. forge install OpenZeppelin/openzeppelin-contracts
  14. ```
  15.  
  16. ## 1. foundry.toml
  17.  
  18. ```toml
  19. [profile.default]
  20. src = "src"
  21. out = "out"
  22. libs = ["lib"]
  23. solc_version = "0.8.26"
  24. evm_version = "cancun"
  25. optimizer = true
  26. optimizer_runs = 800
  27. via_ir = true
  28.  
  29. [rpc_endpoints]
  30. robinhood = "https://rpc.mainnet.chain.robinhood.com"
  31. robinhood_testnet = "https://rpc.testnet.chain.robinhood.com"
  32. ```
  33.  
  34. ## 2. remappings.txt
  35.  
  36. ```text
  37. @uniswap/v4-core/=lib/v4-core/
  38. @uniswap/v4-periphery/=lib/v4-periphery/
  39. @openzeppelin/=lib/openzeppelin-contracts/
  40. ```
  41.  
  42. ## 3. src/Exdiv.sol
  43.  
  44. ```solidity
  45. // SPDX-License-Identifier: MIT
  46. pragma solidity 0.8.26;
  47.  
  48. /// exdiv
  49. ///
  50. /// a fixed raw supply erc 20 on robinhood chain that is simultaneously the
  51. /// uniswap v4 hook attached to its canonical exdiv/eth pool. every swap
  52. /// through that pool surrenders FEE_BPS of its native eth leg into an
  53. /// internal dividend reserve. once per day the reserve is declared at the
  54. /// close bell and paid at the next close bell: the hook buys exdiv through
  55. /// its own pool, burns what it bought, and ticks the erc 8056 ui multiplier
  56. /// so every holder's ui balance grows pro rata. raw balances never move.
  57. ///
  58. /// the ritual, mapped to mechanics:
  59. /// 21:00 utc declare reserve snapshot locked, pending tick published
  60. /// through newUIMultiplier and effectiveAt
  61. /// 13:30 utc ex informational bell between declaration and payment;
  62. /// the arithmetic defense against dividend capture is
  63. /// the round trip fee, see the whitepaper
  64. /// 21:00 utc pay buyback executes, tokens burn, multiplier ticks
  65. ///
  66. /// there is no owner. declare and pay are permissionless pokes. nothing in
  67. /// this contract can move reserve eth anywhere except into the buyback.
  68.  
  69. import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
  70.  
  71. import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol";
  72. import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
  73. import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
  74. import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
  75. import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
  76. import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
  77. import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
  78. import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
  79. import {BeforeSwapDelta, toBeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
  80. import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
  81. import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
  82.  
  83. contract Exdiv is ERC20, BaseHook {
  84. using CurrencyLibrary for Currency;
  85. using PoolIdLibrary for PoolKey;
  86. using StateLibrary for IPoolManager;
  87.  
  88. // ===============================================================
  89. // constants: the whole rulebook, immutable at compile time
  90. // ===============================================================
  91.  
  92. /// raw supply, minted once at construction to the deployer, who seeds
  93. /// the canonical pool. the contract itself never mints again.
  94. uint256 public constant RAW_SUPPLY = 100_000_000e18;
  95.  
  96. /// fee taken on the native eth leg of every swap, in basis points.
  97. uint256 public constant FEE_BPS = 200; // 2 percent
  98.  
  99. /// bells, as seconds after 00:00 utc.
  100. uint256 public constant DECLARE_UTC = 75_600; // 21:00
  101. uint256 public constant EX_UTC = 48_600; // 13:30
  102.  
  103. /// slippage guard on the buyback: payment reverts if the pool's spot
  104. /// price has moved more than this many bps against the buy since
  105. /// declaration. anyone can poke pay again next block.
  106. uint256 public constant PAY_MAX_DRIFT_BPS = 500;
  107.  
  108. /// erc 8056 scale.
  109. uint256 public constant ONE = 1e18;
  110.  
  111. /// lp fee and tick spacing of the canonical pool. the hook fee is the
  112. /// economic fee; the pool's own lp fee is zero.
  113. uint24 public constant POOL_FEE = 0;
  114. int24 public constant TICK_SPACING = 60;
  115.  
  116. // ===============================================================
  117. // state
  118. // ===============================================================
  119.  
  120. /// erc 8056: current multiplier. starts at ONE, only ever steps up.
  121. uint256 private _uiMultiplier = ONE;
  122.  
  123. /// cycle state. a declaration locks amount and publishes the pending
  124. /// tick; payment consumes it.
  125. uint256 public declaredReserve; // eth locked for the pending payment
  126. uint256 public declaredAt; // timestamp of the declaration
  127. uint160 public declaredSqrtPriceX96; // pool spot at declaration, for drift
  128. uint64 public payAt; // when the pending tick takes effect
  129. uint256 private _pendingMultiplier; // indicative next multiplier
  130.  
  131. /// reserve accounting. everything the hook holds beyond declaredReserve
  132. /// is the accruing pool for the next cycle.
  133. uint256 public lifetimeCollected;
  134. uint256 public lifetimePaid;
  135. uint256 public cyclesPaid;
  136.  
  137. /// the canonical pool, fixed at initialization, refused elsewhere.
  138. PoolKey public poolKey;
  139. bool public poolInitialized;
  140.  
  141. /// reentrancy and self recognition during the buyback.
  142. bool private _paying;
  143.  
  144. // ===============================================================
  145. // events
  146. // ===============================================================
  147.  
  148. event Collected(uint256 amount, bool onBuySide);
  149. event Declared(uint256 reserve, uint256 pendingMultiplier, uint64 payAt);
  150. event Paid(uint256 ethIn, uint256 rawBought, uint256 newMultiplier);
  151.  
  152. // ===============================================================
  153. // construction
  154. // ===============================================================
  155.  
  156. constructor(IPoolManager manager)
  157. ERC20("exdiv", "exdiv")
  158. BaseHook(manager)
  159. {
  160. _mint(msg.sender, RAW_SUPPLY);
  161. }
  162.  
  163. /// native eth arrives from poolManager.take during fee capture and
  164. /// from settlement during the buyback.
  165. receive() external payable {}
  166.  
  167. // ===============================================================
  168. // erc 8056 · scaled ui amounts
  169. // ===============================================================
  170.  
  171. /// current multiplier. ui units = raw balance * uiMultiplier / 1e18.
  172. function uiMultiplier() external view returns (uint256) {
  173. return _uiMultiplier;
  174. }
  175.  
  176. /// pending multiplier between declaration and payment. indicative:
  177. /// the exact value is fixed by the buyback fill at the bell. zero
  178. /// when nothing is declared.
  179. function newUIMultiplier() external view returns (uint256) {
  180. return _pendingMultiplier;
  181. }
  182.  
  183. /// when the pending multiplier takes effect. zero when nothing is
  184. /// declared.
  185. function effectiveAt() external view returns (uint64) {
  186. return payAt;
  187. }
  188.  
  189. /// convenience: a holder's balance in ui units.
  190. function uiBalanceOf(address who) external view returns (uint256) {
  191. return (balanceOf(who) * _uiMultiplier) / ONE;
  192. }
  193.  
  194. // ===============================================================
  195. // bells
  196. // ===============================================================
  197.  
  198. function _dayStart(uint256 t) internal pure returns (uint256) {
  199. return t - (t % 86_400);
  200. }
  201.  
  202. /// the most recent 21:00 utc at or before t.
  203. function lastCloseBell(uint256 t) public pure returns (uint256) {
  204. uint256 todaysBell = _dayStart(t) + DECLARE_UTC;
  205. return t >= todaysBell ? todaysBell : todaysBell - 86_400;
  206. }
  207.  
  208. /// the next 21:00 utc strictly after t.
  209. function nextCloseBell(uint256 t) public pure returns (uint256) {
  210. return lastCloseBell(t) + 86_400;
  211. }
  212.  
  213. // ===============================================================
  214. // cycle · declare
  215. // ===============================================================
  216.  
  217. /// permissionless. valid once per close bell. locks everything the
  218. /// hook has accrued as the declared reserve and publishes the pending
  219. /// tick, payable at the next close bell.
  220. function declare() external {
  221. require(poolInitialized, "pool not initialized");
  222. require(payAt == 0, "already declared");
  223. uint256 bell = lastCloseBell(block.timestamp);
  224. require(declaredAt < bell, "this bell already declared");
  225.  
  226. uint256 reserve = address(this).balance;
  227. require(reserve > 0, "nothing accrued");
  228.  
  229. declaredReserve = reserve;
  230. declaredAt = block.timestamp;
  231. payAt = uint64(nextCloseBell(block.timestamp));
  232.  
  233. (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());
  234. declaredSqrtPriceX96 = sqrtPriceX96;
  235.  
  236. _pendingMultiplier = _quotePendingMultiplier(reserve, sqrtPriceX96);
  237.  
  238. emit Declared(reserve, _pendingMultiplier, payAt);
  239. }
  240.  
  241. /// indicative tick from spot: raw exdiv per eth at sqrtPriceX96, with
  242. /// the hook's own fee applied to the buyback input, then
  243. /// m' = m * supply / (supply - bought). currency0 is native eth, so
  244. /// price = (sqrtP / 2^96)^2 in token1 per token0.
  245. function _quotePendingMultiplier(uint256 reserve, uint160 sqrtPriceX96)
  246. internal
  247. view
  248. returns (uint256)
  249. {
  250. uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
  251. uint256 ethAfterFee = reserve - ((reserve * FEE_BPS) / 10_000);
  252. uint256 estBought = (ethAfterFee * priceX192) >> 192;
  253. uint256 supply = totalSupply();
  254. if (estBought == 0 || estBought >= supply) return _uiMultiplier;
  255. return (_uiMultiplier * supply) / (supply - estBought);
  256. }
  257.  
  258. // ===============================================================
  259. // cycle · pay
  260. // ===============================================================
  261.  
  262. /// permissionless. valid at or after payAt. buys exdiv through the
  263. /// canonical pool with the declared reserve, burns the fill, ticks
  264. /// the multiplier by exact supply ratio, resets the cycle.
  265. function pay() external {
  266. require(payAt != 0 && block.timestamp >= payAt, "not payable yet");
  267. require(!_paying, "reentrant");
  268. _paying = true;
  269.  
  270. // drift guard: spot must be within PAY_MAX_DRIFT_BPS of the
  271. // declaration spot. a failed guard leaves the declaration standing;
  272. // poke again next block.
  273. (uint160 spot,,,) = poolManager.getSlot0(poolKey.toId());
  274. uint256 hi = uint256(declaredSqrtPriceX96) * (10_000 + PAY_MAX_DRIFT_BPS) / 10_000;
  275. uint256 lo = uint256(declaredSqrtPriceX96) * (10_000 - PAY_MAX_DRIFT_BPS) / 10_000;
  276. require(uint256(spot) >= lo && uint256(spot) <= hi, "drift");
  277.  
  278. uint256 supplyBefore = totalSupply();
  279. uint256 ethIn = declaredReserve;
  280.  
  281. // the buyback: swap native eth (currency0) for exdiv (currency1),
  282. // exact input, no price limit beyond the drift guard above. the
  283. // unlock callback routes through poolManager.swap with this
  284. // contract as sender; beforeSwap recognizes _paying and takes no
  285. // fee on the hook's own flow.
  286. uint256 rawBought = _buyback(ethIn);
  287. require(rawBought > 0, "empty fill");
  288.  
  289. // retire the fill and tick by the exact ratio, conserving total
  290. // ui units: (S - b) * m' = S * m.
  291. _burn(address(this), rawBought);
  292. uint256 supplyAfter = totalSupply();
  293. _uiMultiplier = (_uiMultiplier * supplyBefore) / supplyAfter;
  294.  
  295. lifetimePaid += ethIn;
  296. cyclesPaid += 1;
  297.  
  298. // reset the cycle. anything accrued between declaration and now
  299. // is already in the balance and rolls into the next declaration.
  300. declaredReserve = 0;
  301. declaredSqrtPriceX96 = 0;
  302. payAt = 0;
  303. _pendingMultiplier = 0;
  304. _paying = false;
  305.  
  306. emit Paid(ethIn, rawBought, _uiMultiplier);
  307. }
  308.  
  309. /// executes the swap inside the pool manager's unlock. verify the
  310. /// callback plumbing against the installed v4-periphery version: this
  311. /// uses the unlock pattern with a self dispatch.
  312. function _buyback(uint256 ethIn) internal returns (uint256 rawBought) {
  313. bytes memory result = poolManager.unlock(abi.encode(ethIn));
  314. rawBought = abi.decode(result, (uint256));
  315. }
  316.  
  317. function unlockCallback(bytes calldata data)
  318. external
  319. returns (bytes memory)
  320. {
  321. require(msg.sender == address(poolManager), "not manager");
  322. uint256 ethIn = abi.decode(data, (uint256));
  323.  
  324. SwapParams memory params = SwapParams({
  325. zeroForOne: true,
  326. amountSpecified: -int256(ethIn),
  327. sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
  328. });
  329.  
  330. BalanceDelta delta = poolManager.swap(poolKey, params, "");
  331.  
  332. // settle the eth we owe, take the exdiv we are owed.
  333. poolManager.sync(Currency.wrap(address(0)));
  334. poolManager.settle{value: ethIn}();
  335. int256 amt1 = int256(delta.amount1());
  336. require(amt1 > 0, "bad delta");
  337. poolManager.take(Currency.wrap(address(this)), address(this), uint256(amt1));
  338.  
  339. return abi.encode(uint256(amt1));
  340. }
  341.  
  342. // ===============================================================
  343. // hook · permissions and callbacks
  344. // ===============================================================
  345.  
  346. function getHookPermissions()
  347. public
  348. pure
  349. override
  350. returns (Hooks.Permissions memory)
  351. {
  352. return Hooks.Permissions({
  353. beforeInitialize: true,
  354. afterInitialize: false,
  355. beforeAddLiquidity: false,
  356. afterAddLiquidity: false,
  357. beforeRemoveLiquidity: false,
  358. afterRemoveLiquidity: false,
  359. beforeSwap: true,
  360. afterSwap: true,
  361. beforeDonate: false,
  362. afterDonate: false,
  363. beforeSwapReturnDelta: true,
  364. afterSwapReturnDelta: true,
  365. afterAddLiquidityReturnDelta: false,
  366. afterRemoveLiquidityReturnDelta: false
  367. });
  368. }
  369.  
  370. /// the hook attaches to exactly one pool: native eth against this
  371. /// token, POOL_FEE, TICK_SPACING. everything else is refused.
  372. function _beforeInitialize(address, PoolKey calldata key, uint160)
  373. internal
  374. override
  375. returns (bytes4)
  376. {
  377. require(!poolInitialized, "pool exists");
  378. require(Currency.unwrap(key.currency0) == address(0), "c0 not native");
  379. require(Currency.unwrap(key.currency1) == address(this), "c1 not exdiv");
  380. require(key.fee == POOL_FEE && key.tickSpacing == TICK_SPACING, "bad key");
  381. poolKey = key;
  382. poolInitialized = true;
  383. return BaseHook.beforeInitialize.selector;
  384. }
  385.  
  386. /// fee on buys: exact input eth -> exdiv surrenders FEE_BPS of the
  387. /// input before the swap. the hook's own buyback is exempt.
  388. function _beforeSwap(
  389. address,
  390. PoolKey calldata,
  391. SwapParams calldata params,
  392. bytes calldata
  393. ) internal override returns (bytes4, BeforeSwapDelta, uint24) {
  394. if (_paying) {
  395. return (BaseHook.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0);
  396. }
  397. // zeroForOne exact input: specified currency is native eth.
  398. if (params.zeroForOne && params.amountSpecified < 0) {
  399. uint256 inAmt = uint256(-params.amountSpecified);
  400. uint256 fee = (inAmt * FEE_BPS) / 10_000;
  401. if (fee > 0) {
  402. poolManager.take(Currency.wrap(address(0)), address(this), fee);
  403. lifetimeCollected += fee;
  404. emit Collected(fee, true);
  405. return (
  406. BaseHook.beforeSwap.selector,
  407. toBeforeSwapDelta(int128(int256(fee)), 0),
  408. 0
  409. );
  410. }
  411. }
  412. return (BaseHook.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0);
  413. }
  414.  
  415. /// fee on sells: exdiv -> eth surrenders FEE_BPS of the eth output
  416. /// after the swap. the hook's own buyback is exempt.
  417. function _afterSwap(
  418. address,
  419. PoolKey calldata,
  420. SwapParams calldata params,
  421. BalanceDelta delta,
  422. bytes calldata
  423. ) internal override returns (bytes4, int128) {
  424. if (_paying || params.zeroForOne) {
  425. return (BaseHook.afterSwap.selector, 0);
  426. }
  427. int256 ethOut = int256(delta.amount0());
  428. if (ethOut > 0) {
  429. uint256 fee = (uint256(ethOut) * FEE_BPS) / 10_000;
  430. if (fee > 0) {
  431. poolManager.take(Currency.wrap(address(0)), address(this), fee);
  432. lifetimeCollected += fee;
  433. emit Collected(fee, false);
  434. return (BaseHook.afterSwap.selector, int128(int256(fee)));
  435. }
  436. }
  437. return (BaseHook.afterSwap.selector, 0);
  438. }
  439.  
  440. // ===============================================================
  441. // views for the site
  442. // ===============================================================
  443.  
  444. /// everything accruing for the next cycle, on top of any declared
  445. /// reserve awaiting payment.
  446. function accruing() external view returns (uint256) {
  447. return address(this).balance - declaredReserve;
  448. }
  449. }
  450. ```
  451.  
  452. ## 4. script/Deploy.s.sol
  453.  
  454. ```solidity
  455. // SPDX-License-Identifier: MIT
  456. pragma solidity 0.8.26;
  457.  
  458. import {Script, console2} from "forge-std/Script.sol";
  459. import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
  460. import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
  461. import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
  462. import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
  463. import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
  464. import {HookMiner} from "@uniswap/v4-periphery/src/utils/HookMiner.sol";
  465. import {Exdiv} from "../src/Exdiv.sol";
  466.  
  467. /// deploys exdiv at a mined address whose low bits encode its hook
  468. /// permissions, then initializes the canonical pool. seed liquidity
  469. /// afterwards with the chain's position manager from the deployer, which
  470. /// received the full raw supply at construction.
  471. contract Deploy is Script {
  472. // uniswap v4 pool manager on robinhood chain. fill from the official
  473. // deployment addresses before running.
  474. address constant POOL_MANAGER = address(0);
  475.  
  476. // create2 deployer proxy, canonical at this address on evm chains.
  477. address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
  478.  
  479. // starting price for pool initialization, sqrtPriceX96 for the chosen
  480. // eth per exdiv genesis price. fill deliberately.
  481. uint160 constant START_SQRT_PRICE_X96 = 0;
  482.  
  483. function run() external {
  484. require(POOL_MANAGER != address(0), "fill POOL_MANAGER");
  485. require(START_SQRT_PRICE_X96 != 0, "fill START_SQRT_PRICE_X96");
  486.  
  487. uint160 flags = uint160(
  488. Hooks.BEFORE_INITIALIZE_FLAG |
  489. Hooks.BEFORE_SWAP_FLAG |
  490. Hooks.AFTER_SWAP_FLAG |
  491. Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG |
  492. Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG
  493. );
  494.  
  495. bytes memory constructorArgs = abi.encode(IPoolManager(POOL_MANAGER));
  496.  
  497. (address hookAddress, bytes32 salt) = HookMiner.find(
  498. CREATE2_DEPLOYER,
  499. flags,
  500. type(Exdiv).creationCode,
  501. constructorArgs
  502. );
  503.  
  504. vm.startBroadcast();
  505.  
  506. Exdiv exdiv = new Exdiv{salt: salt}(IPoolManager(POOL_MANAGER));
  507. require(address(exdiv) == hookAddress, "mine mismatch");
  508.  
  509. PoolKey memory key = PoolKey({
  510. currency0: Currency.wrap(address(0)),
  511. currency1: Currency.wrap(address(exdiv)),
  512. fee: exdiv.POOL_FEE(),
  513. tickSpacing: exdiv.TICK_SPACING(),
  514. hooks: IHooks(address(exdiv))
  515. });
  516.  
  517. IPoolManager(POOL_MANAGER).initialize(key, START_SQRT_PRICE_X96);
  518.  
  519. vm.stopBroadcast();
  520.  
  521. console2.log("exdiv token + hook:", address(exdiv));
  522. console2.log("salt:", vm.toString(salt));
  523. console2.log("genesis block: read from the deploy receipt");
  524. }
  525. }
  526. ```
  527.  
  528. ## 5. .env
  529.  
  530. ```bash
  531. PRIVATE_KEY=0x_fill_me
  532. ```
  533.  
  534. ## 6. deploy
  535.  
  536. ```bash
  537. # testnet first. chain id 46630, faucet at faucet.testnet.chain.robinhood.com
  538. forge script script/Deploy.s.sol:Deploy \
  539. --rpc-url robinhood_testnet \
  540. --private-key $PRIVATE_KEY \
  541. --broadcast
  542.  
  543. # mainnet, chain id 4663
  544. forge script script/Deploy.s.sol:Deploy \
  545. --rpc-url robinhood \
  546. --private-key $PRIVATE_KEY \
  547. --broadcast
  548.  
  549. # verify on blockscout
  550. forge verify-contract <address> src/Exdiv.sol:Exdiv \
  551. --verifier blockscout \
  552. --verifier-url https://robinhoodchain.blockscout.com/api
  553. ```
  554.  
  555. ## 7. after deploy
  556.  
  557. - seed the canonical pool as full range liquidity from the deployer through the chain's v4 position manager. the deployer holds the entire raw supply; what goes into the pool and at what genesis price is the launch decision.
  558. - poke the first declare() after the first 21:00 utc with any accrued fees; pay() a day later. both are permissionless, so the site, a keeper, or anyone can call them.
  559. - fill the site config: TOKEN_ADDRESS and HOOK_ADDRESS are the same address, POOL_ID from the initialize receipt.
  560.  
  561. ## 8. before real money
  562.  
  563. two callback and type signatures move between v4-periphery releases: the BaseHook internal overrides and the unlock callback dispatch. compile against your installed commit and fix signatures where the compiler objects; the logic stands. then: a full fork test suite covering the drift guard, the fee deltas in both directions, and the multiplier ratio under repeated cycles, a testnet run through at least three real bells, and a second pair of eyes on the unlock flow before mainnet. the contract moves other people's money on a schedule; it earns the review.
Advertisement
Comments
  • User was banned
Add Comment
Please, Sign In to add comment