सिस्टम डिज़ाइन · AREX-HOOK
Writing an A-REX hook
A hook is the only extension point of an A REX Core token. If you can express a rule as "given this operation on this batch between these accounts, allow or return a code", it is a hook.
- दस्तावेज़ आईडी
- AREX-HOOK
- संस्करण
- 2.0
- तिथि
- 2026-09-21
| Field | Value |
|---|---|
| Document ID | AREX-HOOK |
| Version | 2.0 |
| Date | 2026-09-21 |
| Status | Current |
| Related | AREX-SDD-001, AREX-SPEC |
A hook is the only extension point of an A-REX Core token. If you can express a rule as "given this operation on this batch between these accounts, allow or return a code", it is a hook.
1. Contract skeleton
Source: contracts/examples/MaxLotPerBuyerHook.sol, compiled and tested in CI. It imports IERC1155 from OpenZeppelin and ARexHookBase from contracts/reference/hooks/.
ARexHookBase gives you ERC-165 registration (supportsInterface(0x332afcbb)) and no-op defaults
for check, onExecuted, canSend, canReceive.
2. The callback contract
| Callback | Called when | Flag | Constraints |
|---|---|---|---|
check(op, operator, from, to, id, amount, data) → bytes32 | before balances change, for each affected id, hooks in registration order, stops at first non-zero | HOOK_CHECK | view; MUST NOT revert for policy; bytes32(0) = allowed |
onExecuted(op, operator, from, to, id, amount, data) | after balances changed, for each id, in order | HOOK_EXECUTED | MUST NOT call balance-changing functions of the token (the token is locked; the call reverts) |
canSend(account) / canReceive(account) | eligibility, per transfer and for the token's ERC-7943 views | HOOK_ELIGIBILITY | view |
op values: OP_MINT = 1, OP_TRANSFER = 2, OP_BURN = 3, OP_FORCED_TRANSFER = 4, OP_TRANSFORM = 5
(for transform, inputs arrive with to == address(0) and outputs with from == address(0)).
data: the ERC-1155 data argument (or createBatch's), abi.encode(processRef) for transforms, empty for forced transfers.
3. Rules of thumb
- Return
OKforOP_FORCED_TRANSFERunless your hook exists to govern enforcement (for example, requiring a court-order reference indata). Seizures must not be blocked by expiry or eligibility rules. - Do not block burns unless you really mean it. Burns are write-offs and deliveries.
- Lock against every operation. A hook that reserves units (delivery, escrow, collateral) must also check
OP_BURNand the input legs ofOP_TRANSFORM(to == address(0)), not onlyOP_TRANSFER; otherwise the holder can re-lot the reserved units into a fresh batch that carries no lock.DeliveryHookshows the pattern. - Read the batch through
IARexBatch(msg.sender).batchOf(id). Do not store the token address; one hook can serve many tokens. - Use namespaced codes:
ARexCodes.code("MYNS", "NAME")=keccak256("MYNS:NAME"). Reuse the standard codes inARexCodeswhen the meaning matches; front-ends already know them. - Keep
checkcheap. It runs on every transfer of every id. Avoid unbounded loops over history;DeliveryHookkeeps an O(1) lock counter for exactly this reason. - Constant flags. The token caches
hookFlags()at registration. Changing it later has no effect until re-registration. - Prefer stateless hooks. If you need state (quotas, counters), write it in
onExecuted, never incheck. - Let the token's owner choose old lots or new lots only. A rule turned on for a token that already holds lots
either governs every lot or only lots created from a point in time on. Inherit
ARexAppliesFrom(contracts/reference/hooks/ARexAppliesFrom.sol), take_appliesFromin the constructor (0 = every lot) and returnOKearly when_governs(batch)is false. The value is immutable, so nobody can exempt more lots later.ExpiryHook,AttestationHook,SeasonalHookandPerishabilityHookdo this.
3a. Reference hooks to copy from
| Hook | Flags | Pattern it demonstrates |
|---|---|---|
EligibilityHook | CHECK, ELIGIBILITY | account lists; sanctions with source and TTL |
ExpiryHook | CHECK | stateless rule from the batch record |
AttestationHook | CHECK | rule that consults IARexAttest |
PerishabilityHook | CHECK | per-commodity config with per-batch override; value views |
SeasonalHook | CHECK | bounded configuration (≤ 16 windows per key) |
DeliveryHook | CHECK, EXECUTED | state machine; unit locks against transfer, burn and re-lot; settlement via transfer data; O(1) check |
TraceabilityHook | EXECUTED | observer with emit-only vs persist modes |
4. Testing a hook
Use the fixtures in test/standard/helpers.js:
RecordingHook (records every callback, can be told to reject) and ReentrantHook (tries to
re-enter) in contracts/reference/hooks/mocks/ are useful for pipeline tests.
5. Wrapping an ERC-3643 compliance module
An ERC-3643 IModule exposes moduleCheck(from, to, value, compliance). A thin hook can call it
for OP_TRANSFER and map false to a code, so existing compliance logic (country restrictions,
max balance, time locks) is reusable on batch tokens. The reverse direction — an A-REX hook as an
ERC-3643 module — is equally mechanical.
6. Registering on a token
Order matters: put cheap, high-rejection hooks first (sanctions, pause-like rules), expensive ones last.