Skip to main contentA-REX Foundation

Whitepaper · 07

Hooks and Reference Modules

How functionality is added to a conformant token without touching the standard: the hook contract, the reference hooks, the companion extensions, and how to write, test and register your own.

Modules are demonstrations, not the standard

The relationship between A-REX Core and its modules is the one between ERC-3643 and T-REX's compliance modules: the interface through which modules plug in is standardized; the modules are not. A conformant token with zero hooks is a valid A-REX token. A hook written by anyone is compatible with every conformant token. This is what makes the ecosystem grow without the standard changing.

graph LR
    T[ARexToken<br/>conformant token] -->|"check / onExecuted / canSend / canReceive"| H1[EligibilityHook]
    T --> H2[ExpiryHook]
    T --> H3[AttestationHook]
    T --> H4[Your hook]
    H3 --> A[ARexAttestations]

The hook contract

CallbackWhenMust
checkbefore balances change, per affected id, hooks in registration order, stop at first non-zerobe a view; never revert for policy
onExecutedafter balances change, per id, in ordernot call the token's balance-changing functions (the token is locked)
canSend / canReceiveon every transfer and for the token's ERC-7943 viewsbe views

op is one of OP_MINT (1), OP_TRANSFER (2), OP_BURN (3), OP_FORCED_TRANSFER (4), OP_TRANSFORM (5). In a transformation, inputs arrive with to == 0 and outputs with from == 0. data is the ERC-1155 transfer data, abi.encode(processRef) for transforms, empty for forced transfers.

The calling token is msg.sender. A hook that needs the batch reads IARexBatch(msg.sender).batchOf(id).

Reference hooks

EligibilityHook — allow, block, sanction

Demonstrates HOOK_ELIGIBILITY. Maintains an optional allowlist, a blocklist and a sanctions list; canSend/canReceive return their conjunction; check returns SANCTIONED, SENDER_INELIGIBLE or RECEIVER_INELIGIBLE so that interfaces can explain the refusal. Forced transfers are always allowed: seizing goods from a sanctioned account is the point of enforcement. In production the lists are fed by an identity registry (ONCHAINID, EAS, verifiable credentials) or a sanctions oracle; the hook does not care which.

ExpiryHook — perishability without a module

Stateless. Reads expiresAt and producedAt from the batch. Blocks mints and transfers of expired batches (BATCH_EXPIRED) but not burns (write-off) or forced transfers (enforcement). Optionally blocks transfers of batches that are not yet produced (BATCH_NOT_PRODUCED) — a platform that finances pre-harvest sets the flag to allow them; a spot marketplace sets it to block them. This one hook covers the blocking part of perishability; decay curves become a view helper or an off-chain valuation.

AttestationHook — regulatory gating per commodity

Maps a commodity code to a required attestation topic. Before a transfer, reads the batch, looks up the requirement, asks the attestation registry isAttested and returns ATTESTATION_MISSING otherwise. "Coffee needs an EUDR due-diligence reference", "organic-labelled batches need a valid organic certificate", "cattle needs a sanitary certificate" are each one call to setRequirement.

PerishabilityHook — decay models

Decay configuration per commodity code with per-batch override; models NONE, LINEAR (to a floor at expiry), EXPONENTIAL (half-life) and STEP (drop every interval). Views valueBps, secondsRemaining, isExpired. Blocks mints and transfers of expired batches (BATCH_EXPIRED) and transfers of batches whose remaining value is zero (PERISH:NO_VALUE); never blocks burns or enforcement.

SeasonalHook — harvest and trading windows

Absolute [start, end] windows per commodity and operation (at most 16 per key). A commodity/op with no windows is unrestricted. Typical use: OP_MINT limited to the harvest window, OP_TRANSFER to the trading window; SEASON_CLOSED otherwise.

DeliveryHook — custody and settlement

A sender schedules a delivery of amount units to a receiver via a carrier; the units are locked (an O(1) counter, so check never iterates over history). Carrier and sensors record pickup, transit and temperature readings (breach flag); the receiver confirms delivery; disputes go to an arbiter. Settlement is the ERC-1155 transfer itself: sender → receiver, exact amount, with data = abi.encode(deliveryId); check allows it only when the delivery is DELIVERED and onExecuted marks it SETTLED and unlocks. Free (unlocked) units keep moving.

TraceabilityHook — EPCIS-shaped event log

Observes every token operation and classifies it with CBV business steps (commissioning, shipping, destroying, transforming) and dispositions (active, in_transit, destroyed, seized); RECORDER_ROLE records non-token critical tracking events (cooling, packing, receiving) with readPoint and bizLocation. Two modes: emit-only (about +17 k gas per transfer; an indexer rebuilds the log) or persist (about +184 k; getEvents answers on-chain, paginated).

Companion extensions

Three concerns never needed to be transfer rules; they are plain contracts that read or write attestations:

ExtensionWhat it does
CommodityPriceOraclereporters submit prices per commodity; rounds are numbered, finalized by quorum with the median, or manually once a minimum is reached; latestAnswer, isStale
CarbonImmobilizationWrapperrecords credits immobilised in a registry (registry, serial range, vintage, tonnes, proof), links tonnes to batches, mirrors retirement and release, writes the attestation AREX:CARBON:IMMOBILIZED; never mints credits
ParametricInsuranceERC-20 pool; policy per batch with a trigger topic; the claim pays when the trigger is attested before expiry

Writing your own hook

The worked example contracts/examples/SeasonHook.sol stays in the repository. test/standard/ImplementationGuide.test.js exercises it for readers with access. SeasonalHook is the reference module for several windows per commodity and per operation.

Rules of thumb:

  1. The symbols OK and OP_FORCED_TRANSFER are defined in the normative text.
  2. Burn behaviour is defined in the normative text.
  3. The callback check is defined in the normative text.
  4. Code names such as ARexCodes.code("MYNS", "NAME") are defined in the normative text.
  5. Flag values are defined in the normative text.
  6. The callbacks onExecuted and check are defined in the normative text.

Registering and ordering

Put cheap, high-rejection rules first. HOOK_ADMIN_ROLE should sit behind a multisig or timelock: whoever controls the pipeline controls which transfers are possible.

Testing

test/standard/helpers.js deploys the reference stack. RecordingHook records every callback and can be told to reject with any code; ReentrantHook attempts to re-enter the token from onExecuted and proves the lock. DomainHooks.test.js and Extensions.test.js cover the ported hooks and companions; Invariants.test.js runs a seeded random sequence of operations and checks supply conservation, terminal status and that canTransfer predicts every outcome.

Interoperating with ERC-3643 compliance

An ERC-3643 compliance module exposes moduleCheck(from, to, value, compliance). A hook that calls it for OP_TRANSFER and maps false to a code makes country restrictions, balance caps and time locks written for security tokens reusable on batch tokens. The reverse — exposing an A-REX hook to an ERC-3643 token — is equally mechanical. Chainlink's Automated Compliance Engine integrates through the same pattern: its policy engine becomes one hook.