Open the app

HookrSwapAccountingKernelV3

Read about the delegatecall target that walks the modules in order, enforces the per-pool caps, and builds the swap deltas.

Deployed at 0x530523DEcC9523dDF8A87842fFBBD24a59C7E060, compiled from src/HookrSwapAccountingKernelV3.sol at source commit 8db7fc940938f811f508ba9cb0c8f2d3f24c9a25. Its source is verified on the explorer; see Verification.

Implements: IHooks

The callback bodies the root hook delegates to. Never called directly by the PoolManager

Every function here runs in the root hook's storage context by DELEGATECALL. Deploying it separately is what keeps the root's own runtime inside the EIP-170 limit while carrying the full accounting path; the kernel itself is 23,965 bytes, 611 short of the limit.

Identity

Two pure getters every contract in the graph carries, so an integrator can confirm what it is holding without a bytecode comparison.

function contractName() external pure returns (string memory);   // "HookrSwapAccountingKernelV3"
function contractVersion() external pure returns (string memory); // "3.0.0"

Constants

NameTypeValue
REQUIRED_FLAGSuint1600x28cc (10444)
KERNEL_FAMILY_IDbytes32keccak256("HOOKR_SWAP_DELTA_V1")
KERNEL_INSTANCE_LAYOUT_IDbytes32keccak256("HOOKR_KERNEL_INSTANCE_LAYOUT_V1")
DYNAMIC_FEE_FLAGuint240x800000
MAX_HOOK_DATA_LENGTHuint2564096
MIN_SQRT_PRICE_LIMITuint1604295128740
MAX_SQRT_PRICE_LIMITuint1601461446703485210103287273052203988822378723970341

State Variables

NameTypeDescription
poolManagerIPoolManagerImmutable. The v4 singleton
stackRegistryIHookrStackRegistryV1Immutable. Source of every pool's frozen stack
coordinatoraddressImmutable. The only address allowed to initialize a pool

The root hook checks all three against its own at construction.

Functions

constructor

constructor(IPoolManager poolManager_, IHookrStackRegistryV1 stackRegistry_, address coordinator_);

Reverts InvalidWiring for a zero or codeless argument.

beforeInitialize

Accepts pool initialization from the coordinator only, once per pool, only when the PoolKey matches the frozen stack. Marks the stack initialized and emits MarketInitialized.

function beforeInitialize(address sender, PoolKey calldata key, uint160) external returns (bytes4);

Reverts NotCoordinator, StackAlreadyInitialized, InvalidStack, or NotPoolManager.

beforeAddLiquidity

Walks the pool's frozen modules and calls each one's beforeAddLiquidity inside its registered gas limit. A module returning false or reverting fails the add.

function beforeAddLiquidity(
    address sender,
    PoolKey calldata key,
    ModifyLiquidityParams calldata params,
    bytes calldata hookData
) external view returns (bytes4);

Reverts HookDataTooLarge, ModuleCallFailed(moduleId, phase, reasonHash), InvalidModuleResult(moduleId), ModuleCodeChanged(moduleId, expected, actual).

beforeSwap

Resolves the caller, walks the modules, aggregates their requested takes, checks them against the pool's frozen caps, credits the claim recipients, and returns the BeforeSwapDelta and the fee override.

function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
    external
    returns (bytes4, BeforeSwapDelta, uint24);

Reverts AggregateCapExceeded, SwapAmountOutOfRange, UnsupportedSwapDirection, UnsupportedSpecifiedQuoteTake, PartialFillUnsupportedWithInputCuts, UntrustedHookData, InvalidHookData, TrustedIntegrationCodeChanged(integrationId, expected, actual).

afterSwap

Same walk for the unspecified leg and the subject take. Reverts MissingBeforeSwap if the in-flight context from beforeSwap is absent.

function afterSwap(
    address sender,
    PoolKey calldata key,
    SwapParams calldata params,
    BalanceDelta delta,
    bytes calldata hookData
) external returns (bytes4, int128);

syncBaseFee

Pushes the pool's frozen baseLpFeePips into the PoolManager's dynamic-fee cache. Permissionless; reverts StackNotInitialized before the pool is initialized. Useful for any consumer that reads the cached fee directly rather than simulating a swap.

function syncBaseFee(PoolKey calldata key) external;

Parameters

NameTypeDescription
keyPoolKeyThe pool to sync

inFlight

The beforeSwap context recorded for a pool inside the current transaction.

function inFlight(PoolId poolId) external view returns (bytes32 contextHash, uint128 specifiedQuoteTake);

Returns

NameTypeDescription
contextHashbytes32Hash binding sender, pool, direction, amount, price limit and hookData
specifiedQuoteTakeuint128Quote already taken off the specified leg

previewQuoteTax

Pure helper computing the quote take for an aggregate rate, either grossing up or netting down.

function previewQuoteTax(uint256 poolQuote, uint16 aggregateRateBps, bool grossUp)
    external
    pure
    returns (uint256 tax, uint256 grossOrNetQuote);

Parameters

NameTypeDescription
poolQuoteuint256Quote amount to compute against
aggregateRateBpsuint16Sum of the module take rates
grossUpboolTrue to add the tax on top, false to carve it out

Returns

NameTypeDescription
taxuint256Quote taken
grossOrNetQuoteuint256Gross or net amount depending on grossUp

contractName, contractVersion

function contractName() external pure returns (string memory);
function contractVersion() external pure returns (string memory);

Return "HookrSwapAccountingKernelV3" and "3.0.0".

kernelInstanceLayoutId, statefulModuleKernelMagic

function kernelInstanceLayoutId() external pure returns (bytes32);
function statefulModuleKernelMagic() external pure returns (bytes32);

Return KERNEL_INSTANCE_LAYOUT_ID and the stateful-module marker keccak256("HOOKR_STATEFUL_SWAP_MODULE_V1"). The root hook requires both to match its own constants at construction, and the registry reads the marker before it freezes a STATEFUL_V1 module against the root.

Unimplemented callbacks

afterInitialize, afterAddLiquidity, beforeRemoveLiquidity, afterRemoveLiquidity, beforeDonate and afterDonate all revert HookNotCalled. Their flag bits are not set, so the PoolManager never calls them; the reverts exist so a mis-mined address fails loudly.

Caller Authentication

if (sender == core.limits.trustedRouter) {
    if (sender.codehash != core.trustedRouterCodeHash) revert TrustedIntegrationCodeChanged(...);
    resolved.trustedCaller = true;
} else if (sender == core.limits.trustedQuoter) {
    if (sender.codehash != core.trustedQuoterCodeHash) revert TrustedIntegrationCodeChanged(...);
    resolved.trustedCaller = true;
}
if (!resolved.trustedCaller) {
    if (rawHookData.length != 0) revert UntrustedHookData();
    resolved.payer = sender;
    resolved.recipient = sender;
    return resolved;
}

An untrusted caller must send empty hookData and is treated as its own payer and recipient. A trusted caller's hookData is decoded as a HookrHookDataV1.Envelope and must carry the pool's own stackHash, or the decode reverts InvalidHookData.

This is why a Universal Router swap works normally but skips the pot leg: the Universal Router is not the pool's registered router, so no authenticated recipient exists to credit.

Events

MarketInitialized

Emitted once per pool when the coordinator initializes it

event MarketInitialized(PoolId indexed poolId, bytes32 indexed stackHash, address indexed subject, address quote);

ModuleFeeAccrued

Emitted per module per phase when a take is credited

event ModuleFeeAccrued(
    PoolId indexed poolId,
    bytes32 indexed moduleId,
    bytes32 indexed attributionKey,
    address recipient,
    address quote,
    uint256 amount,
    bool isBuy,
    bool exactInput,
    bool afterSwapPhase
);

ModuleFeeSkipped

Emitted when a take was requested but could not be credited to its recipient

event ModuleFeeSkipped(
    PoolId indexed poolId, bytes32 indexed moduleId, bytes32 indexed attributionKey, address recipient, bytes32 reason
);

StatefulModuleAction

Emitted per stateful module callback with the action the kernel executed

event StatefulModuleAction(
    PoolId indexed poolId,
    bytes32 indexed moduleId,
    bytes32 indexed attributionKey,
    address currency,
    address recipient,
    uint256 amount,
    bool donation,
    bool afterSwapPhase
);

HookFee

Emitted with the total hook-side fee amounts for a swap

event HookFee(bytes32 indexed poolId, address indexed sender, uint128 feeAmount0, uint128 feeAmount1);

Errors

NotPoolManager, NotCoordinator, InvalidWiring, InvalidStack, StackNotInitialized, StackAlreadyInitialized, ModuleCodeChanged(moduleId, expected, actual), TrustedIntegrationCodeChanged(integrationId, expected, actual), ModuleCallFailed(moduleId, phase, reasonHash), InvalidModuleResult(moduleId), AggregateCapExceeded, SwapAmountOutOfRange, UnsupportedSwapDirection, UnsupportedSpecifiedQuoteTake, PartialFillUnsupportedWithInputCuts, StrictClaimSinkUnavailable(moduleId, recipient), ClaimCreditFailed(moduleId, recipient, reasonHash), ReentrantCallback, MissingBeforeSwap, HookDataTooLarge, UntrustedHookData, InvalidHookData, HookNotCalled, DeferredDonationUnavailable.

DeferredDonationUnavailable(poolId) comes from the shared settlement library (HookrStatefulSettlementLibV1) and surfaces from afterSwap when a deferred quote donation cannot be settled.