Skip to main contentA-REX Core

Whitepaper · 07

Hooks y módulos de referencia

Cómo se añade funcionalidad a un token conforme sin tocar el estándar: el contrato de hook, los hooks de referencia, las extensiones complementarias y cómo escribir, probar y registrar un hook propio.

Los módulos son demostraciones, no el estándar

La relación entre A-REX Core y sus módulos es la que existe entre ERC-3643 y los módulos de cumplimiento de T-REX: la interfaz mediante la cual se conectan los módulos está estandarizada; los módulos, no. Un token conforme sin ningún hook es un token A-REX válido. Un hook escrito por cualquiera es compatible con todo token conforme. Esto es lo que permite que el ecosistema crezca sin que cambie el estándar.

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

El contrato de hook

interface IARexHook is IERC165 {
    function hookFlags() external view returns (uint8);   // CHECK=1 · EXECUTED=2 · ELIGIBILITY=4
    function check(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data)
        external view returns (bytes32 code);            // 0 = allowed
    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);
}
CallbackCuándoDebe
checkantes de que cambien los saldos, por cada id afectado, hooks en orden de registro, se detiene en el primero distinto de ceroser una vista; no revertir nunca por política
onExecuteddespués de que cambien los saldos, por cada id, en ordenno invocar las funciones del token que modifican saldos (el token está bloqueado)
canSend / canReceiveen cada transferencia y para las vistas ERC-7943 del tokenser vistas

op es uno de OP_MINT (1), OP_TRANSFER (2), OP_BURN (3), OP_FORCED_TRANSFER (4), OP_TRANSFORM (5). En una transformación, las entradas llegan con to == 0 y las salidas con from == 0. data son los datos de la transferencia de ERC-1155, abi.encode(processRef) para las transformaciones y vacío para las transferencias forzosas.

El token que llama es msg.sender. Un hook que necesita el lote lee IARexBatch(msg.sender).batchOf(id).

Hooks de referencia

EligibilityHook — permitir, bloquear, sancionar

Demuestra HOOK_ELIGIBILITY. Mantiene una lista de permitidos opcional, una lista de bloqueados y una lista de sanciones; canSend/canReceive devuelven su conjunción; check devuelve SANCTIONED, SENDER_INELIGIBLE o RECEIVER_INELIGIBLE para que las interfaces puedan explicar el rechazo. Las transferencias forzosas siempre se permiten: incautar bienes de una cuenta sancionada es el objeto de la ejecución forzosa. En producción, las listas se alimentan de un registro de identidad (ONCHAINID, EAS, credenciales verificables) o de un oráculo de sanciones; al hook le es indiferente cuál.

ExpiryHook — perecibilidad sin un módulo

Sin estado. Lee expiresAt y producedAt del lote. Bloquea las acuñaciones y las transferencias de lotes caducados (BATCH_EXPIRED), pero no las quemas (baja contable) ni las transferencias forzosas (ejecución forzosa). Opcionalmente bloquea las transferencias de lotes aún no producidos (BATCH_NOT_PRODUCED): una plataforma que financia la precosecha configura el indicador para permitirlas; un mercado al contado lo configura para bloquearlas. Este único hook cubre la parte de bloqueo de la perecibilidad; las curvas de decaimiento se convierten en una vista auxiliar o en una valoración fuera de la cadena.

AttestationHook — control regulatorio por producto básico

Asocia un código de producto básico con un tema de atestación obligatorio. Antes de una transferencia, lee el lote, busca el requisito, consulta al registro de atestaciones isAttested(id, topic) y, en caso contrario, devuelve ATTESTATION_MISSING. «El café necesita una referencia de diligencia debida del EUDR», «los lotes etiquetados como orgánicos necesitan un certificado orgánico válido», «el ganado necesita un certificado sanitario» son, cada uno, una llamada a setRequirement.

PerishabilityHook — modelos de decaimiento

Configuración de decaimiento por código de producto básico con sustitución por lote; modelos NONE, LINEAR (hasta un valor mínimo en la caducidad), EXPONENTIAL (vida media) y STEP (descenso en cada intervalo). Vistas valueBps, secondsRemaining, isExpired. Bloquea las acuñaciones y las transferencias de lotes caducados (BATCH_EXPIRED) y las transferencias de lotes cuyo valor restante es cero (PERISH:NO_VALUE); nunca bloquea las quemas ni la ejecución forzosa.

SeasonalHook — ventanas de cosecha y de comercialización

Ventanas absolutas [start, end] por producto básico y operación (como máximo 16 por clave). Un producto básico/operación sin ventanas no tiene restricciones. Uso típico: OP_MINT limitado a la ventana de cosecha y OP_TRANSFER a la ventana de comercialización; SEASON_CLOSED en caso contrario.

DeliveryHook — custodia y liquidación

Un remitente programa la entrega de amount unidades a un destinatario por medio de un transportista; las unidades quedan bloqueadas (un contador O(1), de modo que check nunca recorre el historial). El transportista y los sensores registran la recogida, el tránsito y las lecturas de temperatura (indicador de infracción); el destinatario confirma la entrega; las disputas pasan a un árbitro. La liquidación es la propia transferencia ERC-1155: remitente → destinatario, cantidad exacta, con data = abi.encode(deliveryId); check solo la permite cuando la entrega está en estado DELIVERED y onExecuted la marca como SETTLED y desbloquea. Las unidades libres (desbloqueadas) siguen circulando.

TraceabilityHook — registro de eventos con forma de EPCIS

Observa cada operación del token y la clasifica con pasos de negocio del CBV (commissioning, shipping, destroying, transforming) y disposiciones (active, in_transit, destroyed, seized); RECORDER_ROLE registra eventos críticos de seguimiento que no son del token (enfriamiento, empaquetado, recepción) con readPoint y bizLocation. Dos modos: solo emisión (aproximadamente +17 k de gas por transferencia; un indexador reconstruye el registro) o persistencia (aproximadamente +184 k; getEvents responde en la cadena, con paginación).

Extensiones complementarias

Tres asuntos nunca necesitaron ser reglas de transferencia; son contratos sencillos que leen o escriben atestaciones:

ExtensiónQué hace
CommodityPriceOraclelos reportadores envían precios por producto básico; las rondas están numeradas y se finalizan por quórum con la mediana, o manualmente una vez alcanzado un mínimo; latestAnswer, isStale
CarbonImmobilizationWrapperregistra los créditos inmovilizados en un registro (registro, rango de números de serie, añada, toneladas, prueba), vincula toneladas a lotes, refleja el retiro y la liberación, escribe la atestación AREX:CARBON:IMMOBILIZED; nunca acuña créditos
ParametricInsurancefondo ERC-20; póliza por lote con un tema desencadenante; la indemnización se abona cuando el desencadenante se atesta antes de la caducidad

Escribir un hook propio

El hook siguiente es contracts/examples/SeasonHook.sol; se compila en CI y test/standard/ImplementationGuide.test.js lo ejercita sobre un token. Para producción, úsese SeasonalHook, que admite varias ventanas por producto básico y por operación.

contract SeasonHook is ARexHookBase, Ownable {
    mapping(bytes32 => uint64[2]) public window; // commodity → [open, close]


    constructor(address owner_) Ownable(owner_) {}


    function setWindow(bytes32 commodity, uint64 open, uint64 close) external onlyOwner {
        window[commodity] = [open, close];
    }


    function hookFlags() public pure override returns (uint8) {
        return ARexCodes.HOOK_CHECK;
    }


    function check(uint8 op, address, address, address, uint256 id, uint256, bytes calldata)
        external
        view
        override
        returns (bytes32)
    {
        if (op != ARexCodes.OP_TRANSFER) return ARexCodes.OK; // never block mints, burns or enforcement
        IARexBatch.Batch memory b = IARexBatch(msg.sender).batchOf(id);
        uint64[2] memory w = window[b.commodity];
        if (w[1] == 0) return ARexCodes.OK; // no window configured
        return (block.timestamp >= w[0] && block.timestamp <= w[1]) ? ARexCodes.OK : ARexCodes.SEASON_CLOSED;
    }
}

Reglas prácticas:

  1. Devolver OK para OP_FORCED_TRANSFER salvo que el propio hook gobierne la ejecución forzosa.
  2. No bloquear las quemas salvo que sea deliberado: las quemas son entregas y bajas contables.
  3. Mantener check barato y acotado; se ejecuta en cada transferencia de cada id. No recorrer nunca el historial.
  4. Organizar los códigos en un espacio de nombres: ARexCodes.code("MYNS", "NAME").
  5. Mantener constantes los indicadores; el token los almacena en caché.
  6. Colocar los cambios de estado en onExecuted, nunca en check.

Registro y orden

await token.registerHook(sanctionsHook);   // evaluated first
await token.registerHook(expiryHook);
await token.registerHook(attestationHook); // evaluated last
await token.getHooks();                    // ordered list
await token.removeHook(expiryHook);        // order of the rest preserved

Colóquense primero las reglas baratas y con alto índice de rechazo. HOOK_ADMIN_ROLE debería estar detrás de una multifirma o un timelock: quien controla la canalización controla qué transferencias son posibles.

Pruebas

test/standard/helpers.js despliega el conjunto de referencia. RecordingHook registra cada callback y puede configurarse para rechazar con cualquier código; ReentrantHook intenta reentrar en el token desde onExecuted y demuestra el bloqueo. DomainHooks.test.js y Extensions.test.js cubren los hooks y los complementos portados; Invariants.test.js ejecuta una secuencia aleatoria de operaciones con semilla y comprueba la conservación del suministro, el estado terminal y que canTransfer predice cada resultado.

Interoperabilidad con el cumplimiento de ERC-3643

Un módulo de cumplimiento de ERC-3643 expone moduleCheck(from, to, value, compliance). Un hook que lo invoca para OP_TRANSFER y asigna false a un código permite reutilizar en tokens de lote las restricciones por país, los topes de saldo y los bloqueos temporales escritos para tokens de valores. Lo inverso, exponer un hook de A-REX a un token ERC-3643, es igualmente mecánico. El Automated Compliance Engine de Chainlink se integra mediante el mismo patrón: su motor de políticas se convierte en un hook.