Skip to main contentA-REX Foundation

Whitepaper · 07

Hooks e Módulos de Referência

Como a funcionalidade é acrescentada a um token conforme sem tocar no padrão: o contrato de hook, os hooks de referência, as extensões complementares e como escrever, testar e registrar um hook próprio.

Módulos são demonstrações, e não o padrão

A relação entre o A-REX Core e seus módulos é a mesma que existe entre o ERC-3643 e os módulos de conformidade do T-REX: a interface pela qual os módulos se conectam é padronizada; os módulos, não. Um token conforme com zero hooks é um token A-REX válido. Um hook escrito por qualquer pessoa é compatível com todos os tokens conformes. É isso que faz o ecossistema crescer sem que o padrão mude.

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

O contrato de hook

CallbackQuandoDeve
checkantes de os saldos mudarem, por id afetado, hooks na ordem de registro, parando no primeiro código diferente de zeroser uma view; nunca reverter por motivo de política
onExecuteddepois de os saldos mudarem, por id, em ordemnão chamar as funções do token que alteram saldos (o token está travado)
canSend / canReceiveem toda transferência e para as views do ERC-7943 do tokenser views

op é um entre OP_MINT (1), OP_TRANSFER (2), OP_BURN (3), OP_FORCED_TRANSFER (4), OP_TRANSFORM (5). Em uma transformação, as entradas chegam com to == 0 e as saídas com from == 0. data são os dados de transferência do ERC-1155: abi.encode(processRef) para transformações e vazio para transferências forçadas.

O token chamador é msg.sender. Um hook que precisa do lote lê IARexBatch(msg.sender).batchOf(id). Um hook vinculado a um único token na implantação (ADR-025, Proposto) o expõe como token() e recusa chamadas de qualquer outro endereço.

Quando mais de uma regra lê data, o ADR-023 (Proposto) o transforma em um envelope etiquetado: até 8 entradas (bytes32 key, bytes32 value), com chaves em namespaces como os códigos (keccak256("AREX:DELIVERY_ID"), keccak256("AREX:DOCUMENT_REF")), e cada regra lê apenas a própria chave. Um data de 32 bytes mantém o único significado que tinha antes para cada regra; qualquer outro conteúdo não traz valor, e a leitura nunca reverte. Uma regra escrita antes do envelope o lê como ausência de valor, por isso o remetente só monta um envelope quando todas as regras do token que leem data leem envelopes. Os DeliveryHook e TraceabilityHook de referência fazem isso a partir da versão 2.0.0-alpha.7.

Hooks de referência

EligibilityHook — permitir, bloquear, sancionar

Demonstra HOOK_ELIGIBILITY. Mantém uma lista de permissão opcional, uma lista de bloqueio e uma lista de sanções; canSend/canReceive retornam a conjunção delas; check retorna SANCTIONED, SENDER_INELIGIBLE ou RECEIVER_INELIGIBLE para que as interfaces possam explicar a recusa. As transferências forçadas são sempre permitidas: apreender bens de uma conta sancionada é o propósito do enforcement. Em produção, as listas são alimentadas por um registro de identidade (ONCHAINID, EAS, credenciais verificáveis) ou por um oráculo de sanções; o hook é indiferente quanto a isso.

ExpiryHook — perecibilidade sem um módulo

Sem estado. Lê expiresAt e producedAt do lote. Bloqueia cunhagens e transferências de lotes vencidos (BATCH_EXPIRED), mas não queimas (baixa por perda) nem transferências forçadas (enforcement). Opcionalmente bloqueia transferências de lotes ainda não produzidos (BATCH_NOT_PRODUCED) — uma plataforma que financia a pré-colheita define a flag para permiti-las; um marketplace à vista a define para bloqueá-las. Esse único hook cobre a parte de bloqueio da perecibilidade; as curvas de decaimento passam a ser uma função auxiliar de view ou uma valoração off-chain.

AttestationHook — condicionamento regulatório por commodity

Mapeia um código de commodity para um tópico de atestação exigido. Antes de uma transferência, lê o lote, consulta o requisito, pergunta ao registro de atestações isAttested e retorna ATTESTATION_MISSING caso contrário. "O café precisa de uma referência de devida diligência do EUDR", "lotes com rótulo orgânico precisam de um certificado orgânico válido", "o gado precisa de um certificado sanitário" são, cada um, uma chamada a setRequirement. A partir da versão de referência 2.0.0-alpha.7, seu proprietário pode ativar checksTransform (desativado por padrão): os lotes de entrada de uma transform passam então a precisar também de seu certificado, de modo que um titular com TRANSFORMER_ROLE não consegue entregar a outra conta um lote não certificado como um lote novo. As saídas são lotes novos e não são verificadas.

PerishabilityHook — modelos de decaimento

Configuração de decaimento por código de commodity, com sobrescrita por lote; modelos NONE, LINEAR (até um piso no vencimento), EXPONENTIAL (meia-vida) e STEP (queda a cada intervalo). Views valueBps, secondsRemaining, isExpired. Bloqueia cunhagens e transferências de lotes vencidos (BATCH_EXPIRED) e transferências de lotes cujo valor restante é zero (PERISH:NO_VALUE); nunca bloqueia queimas nem enforcement.

SeasonalHook — janelas de colheita e de negociação

Janelas absolutas [start, end] por commodity e por operação (no máximo 16 por chave). Uma commodity/operação sem janelas não tem restrição. Uso típico: OP_MINT limitado à janela de colheita, OP_TRANSFER à janela de negociação; SEASON_CLOSED nos demais casos. A partir da versão de referência 2.0.0-alpha.7, um gestor remove uma janela pelos seus limites (removeWindow), nunca pela posição, e as janelas já encerradas liberam espaço: quando uma chave está cheia, adicionar uma janela descarta primeiro as encerradas. Cada remoção emite WindowRemoved, para que um indexador possa reconstruir as janelas a partir dos eventos.

DeliveryHook — custódia e liquidação

Um remetente agenda uma entrega de amount unidades a um destinatário por meio de uma transportadora; as unidades são travadas (um contador O(1), de modo que check nunca itera sobre o histórico). A transportadora e os sensores registram a coleta, o trânsito e as leituras de temperatura (flag de violação); o destinatário confirma a entrega; as disputas vão a um árbitro. A liquidação é a própria transferência ERC-1155: remetente → destinatário, quantidade exata, com o número da entrega em data (32 bytes, abi.encode(deliveryId), ou, a partir da versão 2.0.0-alpha.7, a entrada AREX:DELIVERY_ID de um envelope); check só a permite quando a entrega está DELIVERED, e onExecuted a marca como SETTLED e destrava. As unidades livres (não travadas) continuam a circular. As unidades travadas não podem ser transferidas, queimadas nem usadas como entradas de uma transform: check retorna IN_TRANSIT para qualquer operação que deixaria o saldo do titular abaixo da quantidade travada, exceto a transferência de liquidação, as cunhagens e as transferências forçadas. Depois que uma transferência forçada apreende unidades travadas, check continua retornando um código em vez de reverter. A partir da versão de referência 2.0.0-alpha.7, as unidades congeladas pelo token (getFrozenTokens do ERC-7943) não estão livres para agendar uma entrega (freeUnits informa quantas estão); uma entrega cujas unidades foram levadas por uma transferência forçada não pode ser confirmada nem aceita como entregue (LockedUnitsMoved), pois sua liquidação nunca poderia ocorrer, mas ainda pode ser contestada e devolvida; e uma faixa de temperatura cujo mínimo não esteja abaixo do máximo é recusada (InvalidRange).

TraceabilityHook — log de eventos no formato EPCIS

Observa cada operação do token e a classifica com etapas de negócio do CBV (commissioning, shipping, destroying, transforming) e disposições (active, in_transit, destroyed, seized); RECORDER_ROLE registra eventos críticos de rastreamento que não envolvem o token (resfriamento, embalagem, recebimento) com readPoint e bizLocation. Dois modos: apenas emitir (cerca de +17 k de gás por transferência; um indexador reconstrói o log) ou persistir (cerca de +184 k; getEvents responde on-chain, com paginação). A versão 2 do hook, incluída na versão de referência 2.0.0-alpha.7, fica vinculada a um único token na implantação: só esse token informa operações, e recordEvent só aceita lotes que existem nele, de modo que ninguém pode gravar entradas em nome do token. Seu EPCISEvent traz todos os campos de uma entrada (operação, ponto de leitura, local e quem a registrou), de modo que o modo apenas emitir não perde nada. A referência do documento de uma movimentação vem de um data de 32 bytes ou da entrada AREX:DOCUMENT_REF de um envelope, nunca da entrada de outra regra. O hook não pode ser atualizado: as instâncias da versão 1 e seus eventos ficam como estavam, e a versão 2 é uma nova implantação.

CompanyFactsHook — os registros próprios de uma empresa

Demonstra apenas HOOK_EXECUTED: nunca recusa uma operação, seu onExecuted nunca reverte e ele é vinculado a um único token. Registra os registros próprios de uma empresa como o descritor de vocabulário do padrão os descreve (interop/VOCABULARY.md, "Records on the ledger"): fatos sobre um lote, um local ou a empresa (um recordHash mais no máximo 8 valores públicos), marcas de etapa (referências de etapa com sal nos índices de bit 0–255, uma ou até 50 por transação), etapas recebidas do token de outra empresa, âncoras diárias (uma raiz de Merkle e uma contagem) e anulações. Cada escrita leva um approvalHash, o hash do registro da empresa sobre quem a aprovou. A Safe da empresa registra as definições e as etapas; uma chave de registro, que pode ser delegada, grava os registros. Em um transform, leva para as saídas as etapas pelas quais cada lote de origem passou. Acrescenta a etapa de um tipo de processo apenas para um processRef comprometido no mesmo bloco (commitRun), e cada compromisso é honrado uma única vez. Da mesma forma leva o modelo de custódia (cocModel, o mais baixo dos lotes de origem) e a cobertura EUDR (eudrCovered, o AND dos lotes de origem). Eventos: FactRecorded, StageRecorded, ReceivedStageRecorded, Anchored, FactVoided, RunCommitted, StagesCarried, CustodyDeclared. Versão de referência 2.0.0-alpha.9.

RequireStageHook — nenhuma entrega sem as etapas exigidas

Demonstra HOOK_CHECK sobre o CompanyFactsHook. Até 16 requisitos, cada um com as operações que cobre (entregas e, opcionalmente, as entradas de um transform), os produtos e as etapas pelas quais um lote deve ter passado. Um lote ao qual falta uma é recusado com REQUIRE_STAGE:MISSING; um lote ou módulo de etapas que ele não consegue ler é recusado com REQUIRE_STAGE:UNREADABLE, nunca com um revert. Cunhagens, queimas e movimentações por ordem legal nunca são recusadas. Cada requisito traz o seu próprio appliesFrom: os lotes criados antes dele não são verificados. O Safe da empresa define os requisitos com configure, de modo que o ponto de partida pode mudar quando os requisitos são substituídos; cada mudança emite Configured. Versão de referência 2.0.0-alpha.9.

Extensões complementares

Três preocupações nunca precisaram ser regras de transferência; são contratos simples que leem ou escrevem atestações:

ExtensãoO que faz
CommodityPriceOracleos reportadores enviam preços por commodity; as rodadas são numeradas e finalizadas por quórum com a mediana, ou manualmente quando um mínimo é atingido; latestAnswer, isStale
CarbonImmobilizationWrapperregistra créditos imobilizados em um registro (registro, faixa de números de série, vintage, toneladas, prova), vincula toneladas a lotes, espelha a aposentadoria e a liberação, grava a atestação AREX:CARBON:IMMOBILIZED; nunca cunha créditos
ParametricInsurancepool ERC-20; apólice por lote com um tópico de gatilho; o sinistro é pago quando o gatilho é atestado antes do vencimento

Lotes antigos e lotes novos

Uma regra ativada para um token que já tem lotes governa todos os lotes ou apenas os lotes criados a partir de um determinado momento. Os hooks de referência ExpiryHook, AttestationHook, SeasonalHook e PerishabilityHook recebem esse momento (appliesFrom, 0 para todos os lotes) na implantação. Ele não pode ser alterado depois, de modo que ninguém pode isentar mais lotes mais tarde; uma escolha diferente exige uma nova implantação da regra. RequireStageHook é a exceção: cada um dos seus requisitos traz o seu próprio appliesFrom, e o Safe da empresa pode substituir os requisitos com configure.

Como descrever uma regra aos sistemas que a usam

Uma regra ou um módulo adicional pode publicar um descritor arex.module/1 junto ao seu código. É um documento JSON versionado que indica os parâmetros e as configurações do contrato, os campos do lote que ele lê, os tipos de certificado que espera, seus eventos e códigos de rejeição e como seu estado passa para uma nova versão. Uma plataforma, uma carteira, um indexador ou um auditor o lê para exibir a regra, configurá-la e explicar uma recusa sem código escrito especificamente para essa regra. Desde a revisão 1.1, o descritor também pode indicar as operações que a regra governa (ops: mint, transfer, burn, forced_transfer, transform). O campo é informativo: quando ausente, as operações são desconhecidas, e nenhum consumidor pode se apoiar nele para aplicar controles, porque quem decide são as verificações do próprio hook. A revisão 1.2 acrescenta duas coisas. Uma configuração pode indicar um local (ref:site): é guardada como o originRef que os lotes criados ali carregam, ou como a referência à evidência de origem fora da cadeia que eles carregam em seu lugar (uma declaração de due diligence EUDR, por exemplo), nunca como um endereço ou coordenadas. E o gás declarado de check e onExecuted é medido com as configurações preenchidas: antes de medir, um validador chama cada operação que define ou acrescenta um valor setup vezes por chave com valores de teste (8, salvo indicação diferente no descritor, no máximo 32), para que uma regra que percorre uma lista configurável declare quanto custa com essa lista cheia. A revisão 1.3 registra o que o primeiro consumidor do descritor já usa, para que os descritores dele passem na validação do esquema publicado: as ações que as pessoas registram por meio de uma aprovação (os registros de um módulo adicional, cada um com o papel que pode chamá-lo), os erros próprios do contrato em palavras, medidas sobre os seus registros, o dado por entrega que uma regra lê, uma forma completa dos seus eventos com os seus campos e textos, e mais tipos de valor (um lote, um registro do módulo, um texto curto, um texto guardado só pela sua impressão digital). Todo campo que ela acrescenta é opcional, então todo descritor anterior continua válido. A revisão 1.4 descreve cada valor o suficiente para que um formulário seja montado a partir dele: novos tipos de valor (um montante com a sua moeda, uma data e hora, um documento guardado pela sua impressão digital, um código com formato como um GTIN, um país ou região, uma unidade, uma pessoa ou uma parte do diretório), o que chega ao registro (o valor, só a sua impressão digital ou nada; nunca dados pessoais), o título, a ajuda, o exemplo, a seção e as condições de cada campo, verificações entre campos de uma lista fechada, valores calculados, detalhes mantidos fora do registro, visualizações somente leitura ao lado de um lote, e como um registro é mostrado: sempre como declarado pela organização que o registrou, nunca como verificado. A revisão 1.5 adiciona sal a cada impressão digital: um descritor que declara fingerprint: { "scheme": "salted-jcs/1" } registra o hash de cada valor junto com seu próprio sal de 32 bytes, para que textos curtos, números ou contornos de talhões não possam ser encontrados testando candidatos. A revisão 1.6 acrescenta medidas que não são totais: além de count e sum, uma medida pode ser o valor médio, o menor ou o maior (avg, min, max) de um campo numérico dos registros. Elas exigem esse campo numérico e nunca são somadas entre grupos nem entre empresas: a média de duas empresas é a média dos seus registros, não a média das suas médias. As medidas também podem ser agrupadas pela parte que criou o lote (party) e pelo seu local (site). O modelo do complemento de umidade da versão de referência 2.0.0-alpha.10 declara uma: a umidade média no recebimento por produto e local. Uma métrica sobre um valor registrado em pontos-base declara unit: "bp", e o consumidor a exibe dividida por 100, como porcentagem. A revisão 1.7 permite que o visibleWhen de um campo use a árvore de condições do descritor de vocabulário sobre os demais campos do mesmo formulário: folhas { field, op, value } combinadas com all, any e not, no máximo 3 níveis e 12 folhas, nunca uma expressão e nunca o contexto do registro. A condição única da revisão 1.4 continua válida, obsoleta, e é lida como a folha equivalente.

O descritor é informativo e está Proposto: foi proposto pela A-REX Platform, seu primeiro consumidor, e passa a ser um formato recomendado quando um segundo implementador independente o consumir. Os campos que uma regra acrescenta ao documento de metadados de um lote ficariam em extensions.<key>, validados pelo fragmento de esquema do descritor (ADR-017, Proposto, para o esquema de metadados 1.3.0).

Uma organização também pode publicar seu próprio vocabulário como definições arex.vocab/1: tipos de trabalho de campo (poda, amostragem de solo), tipos de processo (uma fermentação de 72 horas, uma classificação por peneira) e listas de etapas por produto (colheita, secagem, beneficiamento, classificação, exportação). São dados, não código. Cada uma usa o objeto de campo do descritor, indica sua versão e é associada a uma classe de etapa de referência e a um passo de negócio GS1 CBV, para que quem não conhece a organização ainda a entenda. Um tipo de processo é um transform com um tipo, nunca uma operação nova: seu processRef leva uma etiqueta de 4 bytes do tipo e uma impressão digital dos valores do processo, assim uma regra pode agir conforme o tipo e qualquer pessoa pode verificar o processo. Os registros chegam ao livro-razão um a um ou como uma raiz de Merkle diária, com cada valor comprometido com seu próprio sal, para que uma organização possa revelar alguns campos e guardar outros. Uma impressão digital que confere mostra que um registro não mudou desde que foi registrado, nunca que o trabalho foi feito como declarado. O formato é informativo e Proposto (revisão 1.5 do descritor de vocabulário). A revisão 1.1 acrescenta um form opcional que organiza os campos em seções e larguras e define como cada um se comporta (condições, valores padrão, valores somente leitura e calculados, máscaras e padrões); ele só restringe o que os campos aceitam, de modo que um leitor que o ignora lê cada registro como antes. A revisão 1.2 dá às condições uma só linguagem pequena, compartilhada com o descritor de regra: not, até 3 níveis e 12 folhas, e folhas que leem o que a plataforma sabe do registro em códigos que se leem igual em todos os idiomas (a cultura como código EPPO, os dias desde o plantio, o tipo de local, o país, a posição SH do produto do lote, sua última etapa). Contexto desconhecido é lido como vazio, e toda condição da revisão 1.1 continua válida como foi escrita. A revisão 1.3 permite que uma lista de etapas declare sua ordem (order): com strict, o padrão, um lote não é registrado numa etapa enquanto lhe faltar uma etapa anterior obrigatória, contando a partir de onde ele entrou na lista; com flexible, as etapas são registradas em qualquer ordem e as lacunas ficam para os relatórios. Uma etapa que decorre da própria história do lote (seu registro, uma transformação, um recebimento) nunca é recusada, e toda lista anterior é lida como strict. A revisão 1.4 permite que um tipo de processo diga qual método de processamento do café suas saídas levam (washed, natural, honey e suas variantes amarela, vermelha e preta, pulped_natural, wet_hulled, anaerobic, other), tirado de um campo da execução ou fixado pelo tipo, e os metadados process de um lote podem levar o método (method; mixed só para um lote). É uma declaração da organização, não uma certificação, e acompanha o lote mesmo onde os valores das etapas continuam privados. A revisão 1.5 permite que um mapeamento para uma das classes amplas separating, conditioning, sorting_grading ou heat_treating acrescente uma subclass de uma lista fechada por classe (por exemplo hulling, curing, screen, roasting), e um leitor só associa a etapa de outra organização de uma classe ampla pela sua subclasse ou por um mapeamento explícito por remetente. A revisão 1.6 acrescenta to_processor, uma subclasse de transporting: mercadoria enviada a um processador que a trabalha para o seu dono (prestação de serviço), de modo que muda a custódia e não a propriedade.

Como escrever um hook próprio

O exemplo contracts/examples/SeasonHook.sol permanece no repositório. test/standard/ImplementationGuide.test.js o exercita para leitores com acesso. SeasonalHook é o módulo de referência para várias janelas por commodity e por operação.

Regras práticas:

  1. Os símbolos OK e OP_FORCED_TRANSFER são definidos no texto normativo.
  2. O comportamento da queima é definido no texto normativo.
  3. A chamada check é definida no texto normativo.
  4. Nomes de código como ARexCodes.code("MYNS", "NAME") são definidos no texto normativo.
  5. Os valores das flags são definidos no texto normativo.
  6. As chamadas onExecuted e check são definidas no texto normativo.

Registro e ordenação

Coloque primeiro as regras baratas e de alta taxa de rejeição. HOOK_ADMIN_ROLE deveria ficar atrás de um multisig ou timelock: quem controla o pipeline controla quais transferências são possíveis.

Testes

test/standard/helpers.js implanta a pilha de referência. RecordingHook registra cada callback e pode ser instruído a rejeitar com qualquer código; ReentrantHook tenta reentrar no token a partir de onExecuted e comprova a trava. DomainHooks.test.js e Extensions.test.js cobrem os hooks e os complementares migrados; Invariants.test.js executa uma sequência aleatória de operações com semente e verifica a conservação da oferta, o status terminal e que canTransfer prevê todos os resultados. A partir da versão de referência 2.0.0-alpha.8, a implementação de referência também traz um kit de hooks (conformance/hooks/), suítes Foundry que toda regra precisa passar, e um modelo de módulo (templates/module/). A versão 2.0.0-alpha.9 acrescenta um kit de complementos (conformance/addons/) e três modelos de complementos testados com ele: umidade no recebimento (GrainMoisture), um certificado de comércio justo registrado como fato declarado pela empresa (FairTradeCertificate) e um prêmio pago a um produtor, cujo valor e beneficiário são registrados apenas como impressões digitais com sal (PremiumPayments).

Interoperabilidade com a conformidade do ERC-3643

Um módulo de conformidade do ERC-3643 expõe moduleCheck(from, to, value, compliance). Um hook que o chama para OP_TRANSFER e mapeia false para um código torna restrições por país, limites de saldo e bloqueios temporais, escritos para tokens de valores mobiliários, reutilizáveis em tokens de lote. O caminho inverso — expor um hook do A-REX a um token ERC-3643 — é igualmente mecânico. O Automated Compliance Engine da Chainlink se integra pelo mesmo esquema: seu mecanismo de políticas se torna um hook. Essas duas direções são projetos não implementados. O adaptador de referência que existe vai no sentido contrário e apenas lê: permite que carteiras e ferramentas de conformidade do ERC-3643 vejam cada lote como um token ERC-3643 por meio de um hook apenas de notificação que não pode permitir nem recusar uma transferência (ADR-020, Proposto).