Skip to main contentA-REX Foundation

निर्णय रिकॉर्ड · ADR-025

ADR-025: A hook may be bound to one token

AREX CORE §5 says, among the hook side rules: "A hook MUST NOT assume it serves a single token; the calling token is msg.sender." The rule was written so that one hook deployment can serve many tokens, and so that a hook

**Proposed** (2026-10-31). Relaxes a MUST of AREX-CORE §5, so it is a major change under [GOVERNANCE.md](../GOVERNANCE.md) and is not released; the text in AREX-CORE.md §5 is marked Proposed (unreleased)
दस्तावेज़ आईडी
ADR-025
निर्णय की तिथि
2026-10-31
स्रोत
docs/adr/ADR-025.md
केवल अंग्रेज़ी में उपलब्ध
यह पृष्ठ अंग्रेज़ी में है। श्वेतपत्र के अध्याय और आरेख अनूदित हैं। नॉर्मेटिव मानक रिपॉज़िटरी में रहता है; उस पाठ के लिए शीघ्र पहुँच का अनुरोध उपयोग करें। सिस्टम डिज़ाइन मार्गदर्शिकाएँ और निर्णय रिकॉर्ड अंग्रेज़ी में रहते हैं।

A hook that keeps records in a token's name may be bound to one token at deployment. A bound hook names its token publicly and refuses every call from any other address with a named error; a hook that is not bound keeps taking the calling token from msg.sender.

FieldValue
IDADR-025
Subject areaExtensibility, hook-side rules
StatusProposed (2026-10-31). Relaxes a MUST of AREX-CORE §5, so it is a major change under GOVERNANCE.md and is not released; the text in AREX-CORE.md §5 is marked Proposed (unreleased)
Date2026-10-31
OwnersA-REX Contributors; requested by the A-REX Platform (gap audit 2026-10-31, item 17; lot-rules audit, items K1 and K2)
Related requirementsAREX-CORE §5 (hook-side rules), §8 (hook trust, hook denial of service), ADR-004, ADR-009, ADR-019
Supersedes / superseded by—

Context

AREX-CORE §5 says, among the hook-side rules: "A hook MUST NOT assume it serves a single token; the calling token is msg.sender." The rule was written so that one hook deployment can serve many tokens, and so that a hook never applies the rules or the records of one token to another.

The reference TraceabilityHook of arex-contracts 2.0.0-alpha.7 (commit 87962d7, "TraceabilityHook v2 — bound to one token, every field of an entry in its event (K1, K2)") is bound to one token at deployment: the constructor takes the token, token() returns it, onExecuted from any other address reverts with WrongToken(caller), and recordEvent refuses another token (WrongToken) and a lot that does not exist on it (LotNotFound). A reader of §5 would call that hook non-conformant, and a second implementer who follows the text would build the opposite.

The binding fixes item K2 of the A-REX Platform's lot-rules audit. Version 1 of the hook keyed its entries by msg.sender but accepted onExecuted from anyone, and recordEvent took any token and any id, so any contract could write traceability entries "for" a token, and a holder of RECORDER_ROLE on the hook could write entries for lots of tokens it had nothing to do with, including lots that do not exist. The EPCISEvent log is what an EPCIS exporter reads, so these entries end up in documents that relying parties trust. The same release (item K1) put every field of an entry in the event, so a bound instance in emit-only mode loses nothing.

A hook that serves many tokens and keeps settings or records per token needs an answer to a question the standard does not ask: who acts for token X inside the hook? IARexBatch and IARexHookManager expose no administrator, so a multi-token hook has to invent its own per-token roles, or trust whoever calls. ADR-009 met the same question for the attestation companion and bound ARexAttestations to one token.

Issue

May a hook be written for one token only, and if so, what must it do so that a token administrator, an indexer and a second implementer can rely on it?

Assumptions

  • Hooks are cheap to deploy compared with the cost of an authorization model that the standard does not define. The A-REX Platform deploys one instance of each rule per company token.
  • The token cannot tell at registration whether a hook is bound. §5 asks the token to check supportsInterface and cache hookFlags(); it does not and should not ask the hook which tokens it accepts.
  • A hook registered on the wrong token is a mistake of that token's administrator, and must fail loudly, not silently apply another token's rules or records.

Constraints

  • No change to IARexHook, IARexHookManager or any interface id: the binding is hook-side behaviour, not a new function of the interface.
  • The 2.0.0-alpha.7 bytecode stays as released (released reference builds are immutable; the A-REX Platform has deployed instances).
  • Hooks that already conform (they take the token from msg.sender) stay conformant.

Alternatives considered

AlternativeForAgainstOutcome
Keep the MUST and make TraceabilityHook multi-token in 2.0.0-alpha.8 (entries keyed by msg.sender, recorders per token)the text stays as it is; one deployment for many tokensneeds a per-token authorization the standard does not define (who may record for token X?); reopens the K2 surface; a new release and a swap of every deployed instance with no gain for the platform, which deploys one instance per token anywayRejected
Keep the MUST and treat the reference hook as an exception, documented only in arex-contractsno spec changethe specification and its reference implementation disagree on a MUST; a second implementer cannot tell which one to followRejected
Remove the rule altogethersimplest textloses what the rule protects: a hook that silently applies one token's settings to anotherRejected
Allow binding under conditions: the bound token is public, every call from another address is refused with a named error, the binding is declared in the rule descriptor; unbound hooks keep the rulematches the reference and ADR-009; a wrong registration fails loudly; nothing changes for unbound hooks or for tokensa bound deployment serves one token; a consumer must read the binding before registering a hookProposed

Decision (proposed)

AREX-CORE §5, hook-side rules. The rule "A hook MUST NOT assume it serves a single token; the calling token is msg.sender" becomes:

  • A hook that is not bound to a token MUST NOT assume it serves a single token; the calling token is msg.sender.
  • A hook MAY be bound to one token when it is deployed. A bound hook:
    • MUST make the token it is bound to publicly readable, and SHOULD do so as token() returns (address); the binding MUST NOT change for the life of the hook;
    • MUST revert, with an error that names the calling address, every call that the token makes under its flags (check, onExecuted, canSend, canReceive) when msg.sender is not that token;
    • MUST refuse, with a named error, any other function that writes records or settings in the name of another token;
    • SHOULD declare the binding in its rule descriptor, as a constructor parameter of business type token (interop/RULE-DESCRIPTOR.md).
  • Reverting for a wrong caller is not a policy decision, so it does not conflict with "check MUST NOT revert for policy reasons".
  • A bound hook may check more than the token, for example that a lot exists on it before recording an entry (the reference recordEvent refuses an unknown lot with LotNotFound); the standard does not require it.

Nothing changes for tokens: they still register, order and call hooks as §5 says. A token administrator, or the tool acting for one, SHOULD read a hook's binding (its descriptor, or token()) before registering it.

Migration: none for tokens and for unbound hooks. A bound hook built before this record (the reference TraceabilityHook 2.0.0-alpha.7) already meets it: token() is public and immutable, onExecuted reverts with WrongToken(caller), and recordEvent reverts with WrongToken(token) or LotNotFound(id). It has the HOOK_EXECUTED flag only, so the token never calls its check, canSend or canReceive.

Argument

The rule protects one thing: a hook never applies one token's rules or records to another. Binding protects it more strictly than msg.sender keying does, because a bound hook cannot be used by another token at all, and the named error makes a wrong registration visible at the first operation. Requiring every multi-token hook to define per-token authorization would put into the standard a model it has avoided so far (ADR-004: a hook is an independent contract with its own administration). The reference already took this path once, in ADR-009, for the same reason. The conditions (public binding, named refusal, declared in the descriptor) are what a consumer needs to avoid the mistake before it happens.

Consequences

Positive

  • The specification and the reference implementation agree again; a second implementer can build either kind of hook and know what each owes its users.
  • Hooks that write records in a token's name (traceability logs, delivery records) can be bound and need no invented per-token roles.
  • A hook registered on the wrong token fails at its first call with an error that names the caller.

Negative

  • A bound deployment serves one token; a platform with many tokens deploys one instance per token (the A-REX Platform already does).
  • A consumer can no longer assume that any conformant hook may be registered on any token: it has to read the binding first. The token itself cannot check it at registration.
  • A relaxed MUST is a major change; until it is accepted, a bound hook conforms only to the proposed text.

Obligations it creates

  • AREX-CORE.md §5 and erc/erc-draft.md carry the proposed text, marked Proposed (unreleased).
  • interop/RULE-DESCRIPTOR.md says how a bound rule declares its binding.
  • The conformance suite does not cover hook-side rules (they bind hooks, not tokens, see conformance/README.md); the reference hook's own tests are the evidence.

Implementation and evidence

WhatWhere
Normative text (proposed)AREX-CORE.md §5, hook-side rules; the same in erc/erc-draft.md (node scripts/check-erc-sync.mjs)
Reference implementationarex-contracts 2.0.0-alpha.7, contracts/reference/hooks/TraceabilityHook.sol (token, WrongToken, LotNotFound), commit 87962d7
Testsarex-contracts test/foundry/TraceabilityHookV2.t.sol (bound token, refusals of another token and of a lot that does not exist, every field in the event) and test/standard/DomainHooks.test.js
Descriptorinterop/RULE-DESCRIPTOR.md, "Rules bound to one token"
First consumerA-REX Platform: one trace_log instance per company token (packages/releases, release 2.0.0-alpha.7)
  • ADR-004: every rule is an independent hook with its own administration
  • ADR-009: the reference attestation companion is bound to one token for the same reason
  • ADR-019: capabilities in the standard, policy elsewhere; who may record for a token is a deployment choice, not a standard role
  • ADR-023: the transfer-data envelope that TraceabilityHook version 2 reads