Example hook policies
Optional example implementations of fee schedules, trading windows, and Merkle membership.
Optional example policies. Read examples/hooks/shared and the three economic programs in examples/hooks/*. Those fee/window/allowlist programs require exactly BeforeSwap. The lifecycle probe and composed reward example have separate contracts. Start with the Hooks guides for implementation guides.
These policies and their shared helpers belong to the examples, not the core library/program. The core supplies ABI/dispatch and supports no-hook pools. An allowlist is not a pool or launch prerequisite.
Dynamic fee
FeeSchedule stores peak_ppm, floor_ppm, starts_at, and ends_at. Validation requires an ordered time interval, floor at most peak, and peak at most the authenticated pool cap.
fee_at(timestamp, cap) clamps time to the endpoints, decreases the premium linearly from peak to floor, and rounds the premium up using integer arithmetic. Before the start it returns peak; at or after the end it returns floor. The core's tighter cap still applies.
The hook reads runtime Clock. A quote implementation needs the same time snapshot, formula, rounding, and cap; a process wall clock is not an equivalent input.
Trading window
TradingWindow { opens_at, closes_at } uses the half-open interval [opens_at, closes_at). The opening timestamp is accepted; the closing timestamp is rejected. Empty and reversed windows reject.
The module name timelock does not imply an LP vesting vault, a withdrawal lock, or delayed governance. It implements an explicitly configured trading window.
Merkle allowlist
Membership is tied to a domain containing 32-byte chain namespace, protocol, pool, mint, and root_version: u64. The leaf then includes the authenticated trader and expires_at: i64.
| Property | Current rule |
|---|---|
| Leaf tag | grapl:allowlist:leaf:v1 |
| Inner-node tag | grapl:allowlist:node:v1 |
| Integer encoding | Little-endian root version and expiry |
| Node ordering | Lexicographically sorted child hashes |
| Proof length | At most 12 sibling nodes |
| Empty proof | Root is the leaf itself |
| Expiration | now >= expires_at rejects |
| Chain namespace | Explicit configured 32 bytes; not inferred from time |
The runtime uses SHA-256 over exact concatenations, without extra field-length prefixes:
leaf = SHA256(leaf_tag | chain32 | protocol32 | pool32 | mint32
| root_version_u64_le | trader32 | expires_at_i64_le)
parent = SHA256(node_tag | lexicographically_smaller_hash32 | larger_hash32)
payload = version_u8(1) | expires_at_i64_le | sibling_count_u8 | siblings32Siblings are ordered from leaf to root; there is no leaf index or implicit padding. Twelve siblings produce a 394-byte payload. The namespace is creator-configured and is not automatically checked against runtime genesis. The root has no update operation in this example; valid membership can be reused before expiry.
The pure policy injects a MerkleHasher interface. Callers must use the exact hasher selected by the runtime implementation and the same byte ordering. Proof generation is not a frontend permission grant: the actor comes from core-authenticated context, not a pubkey supplied in proof bytes.
Integration limits
Quoting these examples requires their policy state and evaluation logic; actor-dependent policies also require explicit identity and proof context. See Metis compatibility for adapter support. Preserve the same policy decisions in quoting and execution. See hook ABI, wallet context, and validation.