Skip to main contentA-REX Foundation

Whitepaper · 07

Hooks y módulos de referencia

Cómo agregar funcionalidad a un token conforme sin tocar el estándar: qué debe cumplir un hook, los hooks de referencia, las extensiones complementarias y cómo escribir, probar y registrar tus propios hooks.

Los módulos son demostraciones, no parte del estándar

A-REX Core se relaciona con sus módulos igual que ERC-3643 con los módulos de cumplimiento de T-REX: la interfaz con la que se conectan los módulos está estandarizada; los módulos, no. Un token conforme sin ninguna regla externa (hook) es un token A-REX válido. Cualquier hook, lo escriba quien lo escriba, es compatible con todos los tokens conformes. Por eso el ecosistema puede crecer 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["Tu hook"]
    H3 --> A[ARexAttestations]

Qué debe cumplir un hook

CallbackCuándo se llamaDebe
checkantes de que cambien los saldos, por cada id afectado, con los hooks en orden de registro; se detiene en el primer código distinto de ceroser una vista; nunca revertir por motivos de política
onExecuteddespués de que cambien los saldos, por cada id, en ordenno llamar a las funciones del token que modifican saldos (el token está bloqueado)
canSend / canReceiveen cada transferencia y en las vistas ERC-7943 del tokenser vistas

op toma uno de estos valores: 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 contiene los datos de transferencia de ERC-1155; en las transformaciones es abi.encode(processRef) y en las transferencias forzosas va vacío.

El token que llama es msg.sender. Si un hook necesita los datos del lote, los lee con IARexBatch(msg.sender).batchOf(id).

Hooks de referencia

EligibilityHook — permitir, bloquear, sancionar

Muestra cómo funciona HOOK_ELIGIBILITY. Mantiene una lista opcional de permitidos, una lista de bloqueados y una lista de sanciones. canSend/canReceive devuelven la conjunción de las tres. check devuelve SANCTIONED, SENDER_INELIGIBLE o RECEIVER_INELIGIBLE, para que las interfaces puedan explicar el rechazo. Las transferencias forzosas siempre se permiten: embargar bienes de una cuenta sancionada es justamente el objetivo de los controles de cumplimiento. 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 da igual cuál.

ExpiryHook — perecibilidad sin un módulo dedicado

No guarda estado. Lee expiresAt y producedAt del lote. Bloquea las acuñaciones y transferencias de lotes vencidos (BATCH_EXPIRED), pero no las quemas (baja contable) ni las transferencias forzosas (controles de cumplimiento). De forma opcional, bloquea las transferencias de lotes que todavía no se produjeron (BATCH_NOT_PRODUCED). Una plataforma que financia la precosecha configura el indicador para permitirlas; un marketplace spot lo configura para bloquearlas. Este único hook cubre la parte de bloqueo de la perecibilidad; las curvas de deterioro pasan a ser una vista auxiliar o una valoración fuera de la cadena.

AttestationHook — requisitos regulatorios por producto

Asocia cada código de producto con el tema de atestación (certificado o constancia verificable) que exige. Antes de una transferencia, lee el lote, busca el requisito y consulta isAttested en el registro de atestaciones; si la atestación no es válida, devuelve ATTESTATION_MISSING. Reglas como «el café necesita una referencia de debida diligencia EUDR», «los lotes con etiqueta orgánica necesitan un certificado orgánico vigente» o «el ganado necesita un certificado sanitario» se configuran cada una con una llamada a setRequirement.

PerishabilityHook — modelos de deterioro

Configura el deterioro por código de producto, con la opción de sobrescribirlo para un lote concreto. Ofrece los modelos NONE, LINEAR (hasta un valor mínimo al vencer), EXPONENTIAL (vida media) y STEP (caída en cada intervalo), y las vistas valueBps, secondsRemaining e isExpired. Bloquea las acuñaciones y transferencias de lotes vencidos (BATCH_EXPIRED) y las transferencias de lotes cuyo valor restante es cero (PERISH:NO_VALUE). Nunca bloquea las quemas ni los controles de cumplimiento.

SeasonalHook — ventanas de cosecha y de comercialización

Define ventanas absolutas [start, end] por producto y por operación (hasta 16 por clave). Si una combinación de producto y operación no tiene ventanas, no tiene restricciones. Uso típico: limitar OP_MINT a la ventana de cosecha y OP_TRANSFER a la ventana de comercialización; fuera de ellas, el hook devuelve SEASON_CLOSED.

DeliveryHook — custodia y liquidación

El remitente programa la entrega de amount unidades a un destinatario a través de un transportista, y esas unidades quedan bloqueadas. El bloqueo usa un contador O(1), así que check nunca recorre el historial. El transportista y los sensores registran la recogida, el tránsito y las lecturas de temperatura (con un indicador de desvío). El destinatario confirma la entrega, y las disputas pasan a un árbitro. La liquidación es la propia transferencia ERC-1155: del remitente al destinatario, por la cantidad exacta y con data = abi.encode(deliveryId). check solo la permite cuando la entrega está en DELIVERED, y onExecuted la marca como SETTLED y libera las unidades. Las unidades libres (no bloqueadas) siguen circulando. Las unidades bloqueadas no se pueden transferir, quemar ni usar como entradas de un transform: check devuelve IN_TRANSIT para cualquier operación que deje el saldo del titular por debajo de la cantidad bloqueada, salvo la transferencia de liquidación, las acuñaciones y las transferencias forzosas. Después de que una transferencia forzosa haya embargado unidades bloqueadas, check sigue devolviendo un código en lugar de revertir.

TraceabilityHook — registro de eventos con formato EPCIS

Observa cada operación del token y la clasifica según los pasos de negocio de CBV (commissioning, shipping, destroying, transforming) y sus disposiciones (active, in_transit, destroyed, seized). RECORDER_ROLE registra eventos críticos de seguimiento ajenos al token (enfriamiento, empaque, recepción) con readPoint y bizLocation. Tiene dos modos: solo emisión (unos +17 k de gas por transferencia; un indexador reconstruye el registro) o persistencia (unos +184 k; getEvents responde en la cadena, con paginación).

Extensiones complementarias

Hay tres necesidades que nunca tuvieron que ser reglas de transferencia. Se resuelven con contratos simples que leen o escriben atestaciones:

ExtensiónQué hace
CommodityPriceOraclelos informantes envían precios por producto; las rondas están numeradas y se cierran por quórum con la mediana, o manualmente cuando se alcanza un mínimo; latestAnswer, isStale
CarbonImmobilizationWrapperregistra créditos inmovilizados en un registro (registro, rango de seriales, vintage, toneladas, prueba), vincula toneladas con lotes, replica el retiro y la liberación, y escribe la atestación AREX:CARBON:IMMOBILIZED; nunca acuña créditos
ParametricInsurancepool ERC-20; una póliza por lote, con un tema que actúa como disparador; la indemnización se paga cuando el disparador se atesta antes del vencimiento

Lotes antiguos y lotes nuevos

Una regla que se activa en un token que ya tiene lotes rige para todos los lotes o solo para los creados a partir de un momento determinado. Los ExpiryHook, AttestationHook, SeasonalHook y PerishabilityHook de referencia reciben ese momento (appliesFrom, 0 para todos los lotes) al desplegarse. No se puede cambiar después, así que nadie puede eximir más lotes más adelante; para elegir otra cosa hay que desplegar la regla de nuevo.

Describir una regla a los sistemas que la usan

Una regla o un complemento puede publicar un descriptor arex.module/1 junto a su código. Es un documento JSON versionado que indica los parámetros y ajustes del contrato, los campos del lote que lee, los tipos de certificado que espera, sus eventos y códigos de rechazo, y cómo pasa su estado a una versión nueva. Una plataforma, una billetera, un indexador o un auditor lo lee para mostrar la regla, configurarla y explicar un rechazo sin escribir código específico para esa regla. Desde la revisión 1.1 el descriptor también puede indicar las operaciones que gobierna la regla (ops: mint, transfer, burn, forced_transfer, transform). El campo es informativo: si falta, las operaciones son desconocidas, y ningún consumidor puede apoyarse en él para aplicar controles, porque deciden las verificaciones del propio hook.

El descriptor es informativo y Propuesto: lo propuso la A-REX Platform, su primer consumidor, y pasará a ser un formato recomendado cuando lo consuma un segundo implementador independiente. Los campos que una regla agrega al documento de metadatos de un lote irían en extensions.<key> y se validarían con el fragmento de esquema del descriptor (ADR-017, Propuesto, para el esquema de metadatos 1.3.0).

Escribir tu propio hook

El ejemplo completo contracts/examples/SeasonHook.sol se mantiene en el repositorio. test/standard/ImplementationGuide.test.js lo pone a prueba para quienes tienen acceso. SeasonalHook es el módulo de referencia cuando necesitas varias ventanas por producto y por operación.

Reglas prácticas:

  1. Los símbolos OK y OP_FORCED_TRANSFER están definidos en el texto normativo.
  2. El comportamiento de la quema está definido en el texto normativo.
  3. El callback check está definido en el texto normativo.
  4. Los nombres de código como ARexCodes.code("MYNS", "NAME") están definidos en el texto normativo.
  5. Los valores de los indicadores están definidos en el texto normativo.
  6. Los callbacks onExecuted y check están definidos en el texto normativo.

Registro y orden

Pon primero las reglas baratas y con más probabilidad de rechazo. HOOK_ADMIN_ROLE debería quedar detrás de una multifirma o un timelock: quien controla la cadena de reglas decide qué transferencias son posibles.

Pruebas

test/standard/helpers.js despliega la pila de referencia. RecordingHook registra cada callback y se puede configurar para rechazar con cualquier código. ReentrantHook intenta volver a entrar al token desde onExecuted y demuestra que el bloqueo funciona. DomainHooks.test.js y Extensions.test.js cubren los hooks y las extensiones portados. Invariants.test.js ejecuta una secuencia aleatoria de operaciones a partir de una semilla y verifica tres cosas: que se conserva la emisión total, que el estado terminal se respeta y que canTransfer anticipa 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 llama en OP_TRANSFER y traduce false a un código permite reutilizar en tokens de lote las restricciones por país, los límites de saldo y los bloqueos temporales escritos para security tokens. El camino inverso, exponer un hook de A-REX a un token ERC-3643, es igual de mecánico. El Automated Compliance Engine de Chainlink se integra con el mismo patrón: su motor de políticas se convierte en un hook. Estas dos direcciones son diseños sin implementar. El adaptador de referencia que existe va en sentido contrario y solo lee: permite que las billeteras y las herramientas de cumplimiento de ERC-3643 vean cada lote como un token ERC-3643 mediante un hook de solo notificación que no puede permitir ni rechazar una transferencia (ADR-020, Propuesto).