Whitepaper · 05
Technical Specification
Normative summary of A-REX Core: conformance, interfaces, operation semantics, the hook contract, attestations, storage and upgrade rules. The authoritative text is standard/AREX-CORE.md; this chapter restates it for readers of the whitepaper.
Conformance
A conformant token MUST implement ERC-1155, ERC-165, IERC7943MultiToken (0x41c4fbad), IARexBatch (0xe22639e0) and expose its pipeline via IARexHookManager.getHooks() (0x964f4861). It SHOULD implement IARexAttest (0xad603a6c) directly or through a companion contract. A conformant hook MUST implement IARexHook (0x332afcbb) and ERC-165. All ids are reproduced by npm run interface-ids.
Fixed constants
| Constant | Value | Meaning |
|---|---|---|
OP_MINT | 1 | new units (createBatch) |
OP_TRANSFER | 2 | holder-initiated transfer |
OP_BURN | 3 | redemption, delivery, write-off |
OP_FORCED_TRANSFER | 4 | enforcement |
OP_TRANSFORM | 5 | inputs (to = 0) and outputs (from = 0) of transform |
HOOK_CHECK | 1 | hook wants check |
HOOK_EXECUTED | 2 | hook wants onExecuted |
HOOK_ELIGIBILITY | 4 | hook answers canSend / canReceive |
STATUS_ACTIVE | 0 | default |
STATUS_RETIRED | 255 | terminal |
Everything else — commodity codes, unit codes, statuses 1..254, rejection codes, attestation topics — is open. Recommended conventions are in ARexCodes.
Layer 1 — IARexBatch
interface IARexBatch is IERC165 {
struct BatchInit { bytes32 commodity; bytes32 unit; uint64 producedAt; uint64 expiresAt; bytes32 originRef; bytes32 metadataHash; string uri; }
struct Batch { address issuer; uint64 createdAt; uint8 status; bytes32 commodity; bytes32 unit; uint64 producedAt; uint64 expiresAt; bytes32 originRef; bytes32 metadataHash; string uri; }
event BatchCreated(uint256 indexed id, address indexed issuer, bytes32 indexed commodity, bytes32 unit, uint256 quantity, address to);
event BatchStatusChanged(uint256 indexed id, uint8 previousStatus, uint8 newStatus, bytes32 reason);
event BatchMetadataUpdated(uint256 indexed id, bytes32 metadataHash, string uri);
event BatchTransformed(address indexed operator, uint256[] inputIds, uint256[] inputAmounts, uint256[] outputIds, uint256[] outputAmounts, bytes32 processRef);
error ARexBatchNotFound(uint256 id);
error ARexBatchRetired(uint256 id);
error ARexInvalidBatch(bytes32 reason);
error ARexInvalidStatusTransition(uint256 id, uint8 from, uint8 to);
error ARexLengthMismatch();
function createBatch(BatchInit calldata init, uint256 quantity, address to, bytes calldata data) external returns (uint256 id);
function batchOf(uint256 id) external view returns (Batch memory);
function batchExists(uint256 id) external view returns (bool);
function totalSupply(uint256 id) external view returns (uint256);
function setBatchStatus(uint256 id, uint8 status, bytes32 reason) external;
function setBatchMetadata(uint256 id, bytes32 metadataHash, string calldata uri) external;
function transform(address from, address to, uint256[] calldata inputIds, uint256[] calldata inputAmounts, BatchInit[] calldata outputs, uint256[] calldata outputAmounts, bytes32 processRef) external returns (uint256[] memory outputIds);
function parentsOf(uint256 id) external view returns (uint256[] memory);
}
Semantics
createBatch
- MUST be restricted to authorized accounts; authorization MUST NOT derive from token balance.
- MUST revert with
ARexInvalidBatchwhenquantity == 0,commodity == 0,unit == 0, orexpiresAt != 0 && producedAt != 0 && expiresAt <= producedAt. producedAt == 0MUST be accepted (pre-harvest).- MUST run the pipeline with
OP_MINT; MUST emitBatchCreatedthenTransferSingle.
setBatchStatus
- MUST be role-restricted.
255is terminal. Retired batches: no mints, no transfers (ARexBatchRetired), burns allowed.
transform
msg.senderMUST befromor approved for all byfrom.- MUST revert with
ARexLengthMismatchon inconsistent lengths and MUST revert on empty input or output lists. - MUST burn inputs, mint outputs, set
parentsOf(output) = inputIds, setissuer = msg.senderon outputs. - MUST run the pipeline with
OP_TRANSFORMfor each burn and each mint, withdata = abi.encode(processRef). - MUST emit
BatchCreatedper output, ERC-1155 events, and oneBatchTransformed. - Quantity conservation is NOT required. The reference distinguishes re-batching (same commodity and unit,
Σin == Σout; any holder) from processing (TRANSFORMER_ROLE).
metadata
metadataHashMUST bekeccak256of the canonical document aturi. The document SHOULD followstandard/schema/batch-metadata.schema.json.originRefMUST NOT contain raw coordinates or identifiers of natural persons.
Layer 0 — IERC7943MultiToken
Verbatim from ERC-7943 (forcedTransfer, setFrozenTokens, canSend, canReceive, getFrozenTokens, canTransfer; events ForcedTransfer, Frozen; errors ERC7943CannotSend, ERC7943CannotReceive, ERC7943CannotTransfer, ERC7943InsufficientUnfrozenBalance). A-REX adds these clarifications:
canTransferMUST be false when paused, when the batch does not exist or is retired, when eligibility fails, whenamount > balance − frozen, or when any CHECK hook rejectsOP_TRANSFER.- Public transfers MUST NOT succeed where
canTransferis false. forcedTransferMUST be role-restricted, MUST bypass eligibility, MUST lower the frozen amount so it never exceeds the remaining balance (emittingFrozen), MUST run hooks withOP_FORCED_TRANSFER, MUST emitForcedTransfer, and SHOULD work while paused.canSend/canReceiveare the conjunction of allHOOK_ELIGIBILITYhooks.- A token SHOULD expose
transferCheck(from, to, id, amount) → bytes32.
Layer 2 — hooks
interface IARexHook is IERC165 {
error ARexHookRejected(address hook, bytes32 code);
function hookFlags() external view returns (uint8);
function check(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data) external view returns (bytes32 code);
function onExecuted(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function canSend(address account) external view returns (bool);
function canReceive(address account) external view returns (bool);
}
interface IARexHookManager {
event HookRegistered(address indexed hook, uint8 flags, uint256 position);
event HookRemoved(address indexed hook);
error ARexHookAlreadyRegistered(address hook);
error ARexHookNotRegistered(address hook);
error ARexInvalidHook(address hook);
function getHooks() external view returns (address[] memory);
function hookFlagsOf(address hook) external view returns (uint8);
function registerHook(address hook) external;
function removeHook(address hook) external;
}
Token obligations: ordered list; verify supportsInterface(0x332afcbb) and cache non-zero flags at registration; call check per id per CHECK hook and revert with ARexHookRejected(hook, code) at the first non-zero code before any balance change; call onExecuted per id per EXECUTED hook after balances; prevent re-entrance during the sequence; forward data; keep list and flags public.
Hook obligations: constant flags; check is a view that returns codes and never reverts for policy; the calling token is msg.sender; return 0 for OP_FORCED_TRANSFER unless the rule governs enforcement; onExecuted never calls balance-changing functions.
Layer 3 — IARexAttest
interface IARexAttest is IERC165 {
struct Attestation { address issuer; bytes32 ref; uint64 issuedAt; uint64 expiresAt; bool revoked; }
event Attested(uint256 indexed id, bytes32 indexed topic, address indexed issuer, bytes32 ref, uint64 expiresAt);
event AttestationRevoked(uint256 indexed id, bytes32 indexed topic, address indexed issuer, bytes32 reason);
event TrustedIssuerUpdated(bytes32 indexed topic, address indexed issuer, bool trusted);
error ARexUntrustedIssuer(bytes32 topic, address issuer);
error ARexAttestationNotFound(uint256 id, bytes32 topic);
function attest(uint256 id, bytes32 topic, bytes32 ref, uint64 expiresAt) external;
function revokeAttestation(uint256 id, bytes32 topic, bytes32 reason) external;
function getAttestation(uint256 id, bytes32 topic) external view returns (Attestation memory);
function isAttested(uint256 id, bytes32 topic) external view returns (bool);
function isTrustedIssuer(bytes32 topic, address issuer) external view returns (bool);
}
attest requires a trusted issuer for the topic and an existing batch. isAttested is false if absent, revoked, expired, or the issuer is no longer trusted. Issuers revoke their own attestations; an admin MAY revoke any.
Rejection codes (recommended)
keccak256("AREX:" + NAME) for PAUSED, FROZEN, SENDER_INELIGIBLE, RECEIVER_INELIGIBLE, SANCTIONED, BATCH_NOT_FOUND, BATCH_RETIRED, BATCH_INACTIVE, BATCH_EXPIRED, BATCH_NOT_PRODUCED, LIMIT_EXCEEDED, DOCUMENT_MISSING, SEASON_CLOSED, IN_TRANSIT, ATTESTATION_MISSING. Custom codes: keccak256("NAMESPACE:NAME").
Reference implementation facts
| Property | Value |
|---|---|
| Base | OpenZeppelin 5.3.0 upgradeable: ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS |
| Storage | ERC-7201 namespace arex.storage.token, slot 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900 |
| Compiler | solc 0.8.22, viaIR, EVM paris; optimizer.runs = 1 for ARexToken (24,136 bytes deployed), 200 elsewhere |
| Roles | ISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN; ATTEST_ADMIN on ARexAttestations |
| Reentrancy | storage lock around the whole _update sequence; exercised by the ReentrantHook test |
| Data forwarding | safeTransferFrom / safeBatchTransferFrom / createBatch stash data for hooks; transform passes abi.encode(processRef) |
| Metadata | uri(id) returns the batch URI when set, else the base URI |
| Tests | 46 in test/standard: lifecycle, lineage, freeze and forced transfer, hook order, rejection, data and reentrancy, attestations, interface ids, upgrade authorization, domain hooks, extensions, randomized invariants, and the Implementation Guide executed end to end |
Versioning of the standard
Any change to a signature, event or MUST is a major version with a migration note. Additive recommendations (codes, schema fields) are minor. Changes are accepted only when two independent implementers confirm adoption. The interfaces will not be submitted as an ERC before one production deployment has run for six months, three independent hook implementations exist and a third-party audit is published.