Rendering Uniswap V4 Hooks (and any contract's view state)
You do not need a special "hook resolver." A Uniswap V4 hook is just a contract, and ChainRender already ships
a generic, robust, ethos-compliant way to read + render any contract's view function: the CallResolver
(kind = "staticread") and, for several fields at once, MultiCallResolver (kind = "multiread"). This page
shows how to use them to render live hook state.
Why not a dedicated hook resolver? Because it would duplicate
CallResolver. ChainRender's design is modular + additive: one generic view-reader serves hooks, oracles, ERC-20s, governance, and anything else with aviewfunction. Fewer contracts, one audited path, no per-integration special cases.
What CallResolver does (kind "staticread")
args = abi.encode(address target, bytes callData, uint8 retType, uint8 fmt, uint8 dec, uint8 frac)
- target — the contract to read (the hook address, or a
StateView, or any contract). - callData — the encoded view call (selector + args), e.g.
abi.encodeWithSelector(hook.swapCount.selector, poolId). - retType — how to interpret the returned word:
0=uint 1=int 2=address 3=bool 4=string 5=bytes32(ReadFormat). - fmt / dec / frac — formatting: abbreviate (K/M/B/T), fixed-point decimals, fractional digits.
It staticcalls with a gas cap + reserve, then formats via ReadFormat. Fails closed to a fallback string on
any revert, missing contract, or malformed return — never reverts the render. Immutable, ownerless, view-only.
Example: rendering a hook's swap counter (DinoHook-style)
Say a V4 hook exposes function swapCount(bytes32 poolId) external view returns (uint256). To render it:
bytes memory callData = abi.encodeWithSelector(0xXXXXXXXX /* swapCount */, poolId);
bytes memory args = abi.encode(
hookAddress, // target = the hook
callData, // the view call
uint8(0), // retType = RET_UINT
uint8(1), // fmt = abbreviate (12.3K)
uint8(0), uint8(0) // dec, frac
);
// resolver kind "staticread"; the component slot references this and renders the number live.
In the visual builder, this is a data-bound slot: point it at CallResolver with kind staticread and those
args. The rendered component shows the hook's live swap count, read on-chain during the eth_call render \u2014 no
server, no indexer, fail-closed if the hook is unreachable.
Reading several hook fields at once (kind "multiread")
For a hook dashboard \u2014 swap count, current dynamic fee, last swapper, TVL \u2014 use MultiCallResolver
("multiread"), which batches multiple staticread-shaped calls into one rendered result. Same target/callData/
retType shape per field; one component, many live hook values.
What this renders (and what it doesn't)
- Renders: any hook
view/purefunction \u2014 counters, dynamic fees (getSlot0().lpFeereflects hook-set fees), order-book sizes, reputation scores, custom curve params, whatever the hook exposes. - Does NOT execute: rendering is a
view. To act on a hook (trigger a swap that runs the hook, place an order), that's the declared-action layer \u2014 a button whose action is decoded honestly before signing (see the action decoder + swap honesty flag). ChainRender renders hook state and declares hook-triggering actions; it never becomes the AMM.
The three ways ChainRender meets hooks (complete picture)
- Render hook-influenced pool state \u2014
UniswapV4PriceResolverreadsgetSlot0(poolId); thelpFeeit returns is the current (possibly hook-adjusted) fee. (V3 pools:UniswapV3PriceResolver, kept alongside.) - Render a hook's own state \u2014
CallResolver/MultiCallResolverread the hook's view functions (this page). - A hook that drives ChainRender \u2014 e.g. DinoHook: swap activity in the hook feeds a ChainRender NFT's render. That's hooks feeding ChainRender, the reverse direction.
Expandability (the ethos-correct kind)
New return types or formats are added by versioning ReadFormat / deploying a new resolver additively \u2014 never by
mutating a deployed contract. CallResolver is immutable and ownerless; if you ever need a capability it lacks, you
deploy CallResolverV2 and reference it, leaving V1 untouched. That is how ChainRender "expands": more immutable
pieces, never a mutable one.
Related
contracts/CallResolver.sol(staticread) \u2014 the generic view reader (renders any hook view function).contracts/MultiCallResolver.sol(multiread) \u2014 batch several hook fields.contracts/ReadFormat.sol\u2014 the return-type + formatting layer (uint/int/address/bool/bytes32/string).contracts/UniswapV4PriceResolver.sol\u2014 render V4 pool price (reflects hook-set fees).contracts/UniswapV3PriceResolver.sol\u2014 render V3 pool price (kept alongside V4).