Skip to content

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.

PropertyCurrent rule
Leaf taggrapl:allowlist:leaf:v1
Inner-node taggrapl:allowlist:node:v1
Integer encodingLittle-endian root version and expiry
Node orderingLexicographically sorted child hashes
Proof lengthAt most 12 sibling nodes
Empty proofRoot is the leaf itself
Expirationnow >= expires_at rejects
Chain namespaceExplicit 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 | siblings32

Siblings 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.