ERC-3643 (T-REX)
Status: Spec técnica — fase de planejamento Autor: Time Axia Data: 2026-06-01 Escopo decidido:
- Ativos: NFTs (ERC-721) + Crowdfunding/CreditCore (fungível)
- Chain alvo: Polygon / L2 (EVM)
- Custódia: Custodial (modelo atual mantido)
- Provisionamento de identidade: Lazy / Just-In-Time, puxando do CPM no momento do mint
- Escolha do padrão de contrato: por coleção, no fim do crowdfunding (
ERC721puro vsERC-3643 / ERC-721-compliant) - Smart contract modelo será desenvolvido in-house e auditado por terceiro antes de produção.
1. Objetivo
Permitir que, ao emitir NFTs/tokens no encerramento de um crowdfunding, o emissor escolha o padrão do contrato:
- Padrão (atual): ERC-721 puro (NFT) ou ERC-20
whitelabel(fungível) — sem compliance on-chain. - Regulado: ERC-3643 (fungível) ou ERC-721-compliant (NFT que consulta a mesma camada de identidade do 3643) — com identidade on-chain + enforcement de transferência.
Toda a orquestração vive no TokenController. A identidade on-chain é provisionada lazy/JIT no job de mint, puxando KYC do CustomerProfileService via cpm-lib.
2. Princípios de arquitetura
- Camada de identidade compartilhada, construída uma vez. ONCHAINID + Identity Registry (+ Storage) + Trusted Issuers Registry + Claim Topics Registry são deploy-once por chain. Servem simultaneamente o token fungível 3643 e o ERC-721-compliant.
- Token-contract deploy-per-coleção. Cada crowdfunding regulado deploya seu próprio Token + Modular Compliance via factory, reusando os registries compartilhados.
- CPM é a fonte da verdade de KYC. O TokenController nunca duplica dado de identidade — consulta
cpm-libno mint. - Lazy/JIT. Só paga gas de identidade (ONCHAINID + claims + registro) para quem efetivamente recebe ativo regulado.
- Custodial preservado.
mainWalletsegue pagando gas; wallets custodiais (TKN_OWNER, sinks) são registradas como identidades institucionais. - Idempotência por
(userId, chainId)na identidade e portxHashnas transações on-chain (padrões já existentes).
Webhook DIDIT/BRLA TokenController (mint job, lazy)
status=APPROVED ──┐ │
(NÃO registra) │ 1. lê asset/collection.contract_standard
│ 2. se ERC3643/ERC721_COMPLIANT:
cpm-lib ◀───────┼────── 3. ensureIdentity(userWallet):
(KYC, cpf, │ - já na Identity Registry? skip
país, PEP) │ - não → deploy ONCHAINID + sign claims
│ (Axia = Trusted Issuer) + register
│ 4. mint no contrato (reverte se não verificado)
▼
┌─────────────────────── CAMADA COMPARTILHADA (deploy-once / chain) ──────────────┐
│ IdentityRegistry + IdentityRegistryStorage │
│ TrustedIssuersRegistry (Axia) + ClaimTopicsRegistry (KYC, COUNTRY, PEP, ...) │
│ ModularCompliance (módulos: transfer-restrict, country-allow, max-holders, ...) │
└────────────────▲───────────────────────────────────▲─────────────────────────────┘
│ consultam │ consultam
┌──────────┴───────────┐ ┌───────────┴────────────┐
│ Token ERC-3643 │ │ ERC-721-compliant │
│ (Crowdfunding/Credit)│ │ (NFTs) │
└──────────────────────┘ └────────────────────────┘3. Smart Contracts (track auditado)
Este é o item de maior lead time e maior risco. Estratégia: reusar o máximo da suíte T-REX auditada da Tokeny e auditar apenas o que for custom.
Status (2026-06-01):
AxiaCompliantERC721.solimplementado, compilando e testado emEniato/Backend/TokenController/packages/processor/contracts/— Hardhat (solc 0.8.28, evmVersion cancun), 23 testes verdes, 100% stmts/lines/funcs, solhint limpo. Pronto para hardening (Foundry/Slither) e auditoria externa.
3.1 Repositório e toolchain
- Novo diretório:
Eniato/Backend/TokenController/packages/processor/contracts/(substitui o legadosol/). - Toolchain: Hardhat (TS, alinhado ao stack) + Foundry para fuzzing/invariantes na suíte custom.
- Solidity: ^0.8.20; OpenZeppelin Contracts v5 (ou v4.9 se necessário p/ compat com T-REX upstream — fixar na fase 0).
- Output de build (ABI + bytecode) versionado em
contracts/artifacts/e consumido pelo deploy tool (igual aoNFT.jsonhoje).
Status (2026-06-01) — suíte 3643 integrada e validada: unit dedicado em
Eniato/Backend/TokenController/packages/processor/contracts-trex/(Hardhat, solc 0.8.17, OZ v4 — separado do unit custom OZ v5 por incompatibilidade de versão do OpenZeppelin). 73 arquivos compilam; 5 testes de integração ponta a ponta verdes provando: deploy viaTREXFactory→ ONCHAINID + claim KYC assinado pela Axia (trusted issuer) → registro → mint (verificado vs bloqueado) → transfer (verificado vs bloqueado) → freeze. Reuso total da Tokeny (incl.TREXGatewaypronto — gateway custom dispensado).
3.2 Contratos REUSADOS (auditados pela Tokeny — não reescrever)
| Contrato | Origem | Observação |
|---|---|---|
Token (ERC-3643) | @tokenysolutions/t-rex | Token fungível regulado |
IdentityRegistry / IdentityRegistryStorage | T-REX | Deploy-once compartilhado |
TrustedIssuersRegistry / ClaimTopicsRegistry | T-REX | Deploy-once compartilhado |
ModularCompliance | T-REX | Host dos módulos |
Identity (ONCHAINID) | @onchain-id/solidity | ERC-734/735, 1 por investidor |
TREXFactory / TREXImplementationAuthority | T-REX | Deploy determinístico da suíte |
Reescrever esses contratos é o maior erro possível em ativo regulado. Usar os artefatos auditados.
3.3 Contratos CUSTOM (precisam de auditoria)
AxiaCompliantERC721.sol— o coração do trabalho novo. ERC-721 (OZ) que, no_update/_beforeTokenTransfer, consulta a mesmaIdentityRegistryeIModularCompliancedo 3643:solidity// pseudo — sujeito ao desenho final e à auditoria function _update(address to, uint256 tokenId, address auth) internal override returns (address from) { from = super._update(to, tokenId, auth); if (from != address(0) && to != address(0)) { // ignora mint/burn na checagem de transfer require(_identityRegistry.isVerified(to), "AXIA721: receiver not verified"); require(_compliance.canTransfer(from, to, tokenId), "AXIA721: compliance"); } return from; }- Roles:
AGENT_ROLE(mint/burn/forcedTransfer),DEFAULT_ADMIN_ROLE. freeze(address),recover(oldWallet, newWallet)espelhando semântica 3643.- Mapeia
block_resell/block_sellpara travas de compliance + estado por token (tokenFrozen).
- Roles:
- Módulos de compliance custom (se necessário além dos padrão da Tokeny): ex.
CrowdfundingLockupModule(lock-up até liquidação),JurisdictionModule(allowlist porCOUNTRYclaim). Preferir módulos prontos da Tokeny quando existirem. AxiaTREXGateway.sol(opcional) — wrapper de deploy que padroniza parâmetros Axia naTREXFactory(claim topics default, compliance default), reduzindo superfície de erro do backend.
3.4 Escopo e processo de auditoria
- Em auditoria:
AxiaCompliantERC721, módulos custom,AxiaTREXGateway, e a configuração de deploy (parâmetros passados à factory). - Fora (já auditado): núcleo T-REX/ONCHAINID — citar versões e hashes no relatório.
- Pré-auditoria interna: 100% de cobertura de testes (Hardhat + Foundry invariantes), Slither/Mythril no CI, checklist de acesso (quem pode mintar/freeze/recover).
- Auditor externo (ex.: firmas reconhecidas em security token). Lead time típico 3–6 semanas + janela de correção. Começar cedo.
- Deploy em mainnet só após relatório final + correções aplicadas + re-review.
3.5 Gestão de chaves (crítico)
- A chave do Trusted Issuer (assina claims KYC on-chain) é alvo de altíssimo valor: comprometê-la = forjar KYC. Guardar com rigor ≥ ao
mnemonicParameteratual (idealmente KMS/HSM, assinatura via serviço dedicado, rotação documentada). AGENT_ROLEdos tokens (mint/freeze/recover) idem.
4. Modelo de dados (Liquibase + Entities)
Liquibase centralizado no DataInitializerService (ver guia). Grupo do TokenController. Sempre VARCHAR, CREATE INDEX IF NOT EXISTS, rollback granular, registrado no changelog.base.yml.
4.0 crowdfundings — flag opcional de oferta regulada (origem da escolha)
A escolha é opcional e por crowdfunding. O admin marca um único toggle "Oferta regulada"; o sistema resolve o contract_standard concreto cruzando o flag com o tipo de ativo.
ALTER TABLE public.crowdfundings ADD COLUMN IF NOT EXISTS regulated BOOLEAN DEFAULT false;Entity: CrowdFundingsEntity (Common/Backend/CrowdfundingService/.../crowdfunding.entity.ts) ganha regulated?: boolean.
Status (2026-06-01) — flag regulated implementado e ligado. Liquibase
0027-add_regulated_to_crowdfunding.yml(DataInitializerService, grupocommon/crowdfunding, tabelacrowdfunding_entries). Camporegulatedemcrowdfunding.entity.ts+crowdfunding.dto.ts(@DtoAttribute) +ICrowdfunding(crowdfunding-lib). Nocategories.core.handler.ts, a criação da collection (myTknLib.createCollections) resolvecontract_standard: crowdfunding?.regulated ? 'ERC721_COMPLIANT' : 'ERC721'. Origem da cadeia fechada: admin marca regulated → collection nasce com contract_standard → scheduler deploya AxiaCompliantERC721 → mint provisiona identidade + minta compliant. Falta o toggle no form do BackOffice (UI) + i18n.
Tabela de resolução (flag × tipo de ativo → contract_standard):
regulated | Tipo de ativo | contract_standard | Contrato |
|---|---|---|---|
| false (default) | NFT | ERC721 | NFT.json (atual, intacto) |
| false (default) | Fungível | ERC20 | whitelabel (atual, intacto) |
| true (opt-in) | NFT | ERC721_COMPLIANT | AxiaCompliantERC721 |
| true (opt-in) | Fungível | ERC3643 | Token T-REX (Tokeny) |
Default = não-regulado → zero impacto em ofertas existentes. A resolução roda no encerramento do crowdfunding, ao criar a collection, gravando collections.contract_standard.
4.1 collections — novo campo de padrão
ALTER TABLE public.collections ADD COLUMN IF NOT EXISTS contract_standard VARCHAR(30) DEFAULT 'ERC721';
-- valores: 'ERC721' | 'ERC3643' | 'ERC721_COMPLIANT'
ALTER TABLE public.collections ADD COLUMN IF NOT EXISTS compliance_address VARCHAR(200) NULL; -- ModularCompliance do token
ALTER TABLE public.collections ADD COLUMN IF NOT EXISTS identity_registry_address VARCHAR(200) NULL; -- registry consultadaEntity: adicionar contract_standard, compliance_address, identity_registry_address em CollectionsEntity.
4.2 Nova tabela — vínculo de identidade on-chain (idempotência lazy)
CREATE TABLE IF NOT EXISTS public.onchain_identities (
id VARCHAR(200) PRIMARY KEY,
user_id VARCHAR(200) NOT NULL,
chain_id VARCHAR(50) NOT NULL,
wallet_address VARCHAR(200) NOT NULL,
onchainid_address VARCHAR(200) NULL, -- contrato ONCHAINID deployado
registry_address VARCHAR(200) NOT NULL, -- Identity Registry onde foi registrado
status VARCHAR(30) NOT NULL, -- PENDING | DEPLOYING | REGISTERED | REVOKED | ERROR
claims JSONB NULL, -- topics gravados (KYC, COUNTRY, PEP...)
last_tx_hash VARCHAR(200) NULL,
error_message TEXT NULL,
created_at TIMESTAMP DEFAULT now(),
last_update TIMESTAMP DEFAULT now()
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_onchain_identity_user_chain ON public.onchain_identities (user_id, chain_id);
CREATE INDEX IF NOT EXISTS idx_onchain_identity_wallet ON public.onchain_identities (wallet_address);4.3 Nova tabela — config de Trusted Issuer / Claim Topics por chain
CREATE TABLE IF NOT EXISTS public.trex_chain_config (
id VARCHAR(200) PRIMARY KEY,
chain_id VARCHAR(50) NOT NULL UNIQUE,
identity_registry_addr VARCHAR(200) NOT NULL,
identity_storage_addr VARCHAR(200) NOT NULL,
trusted_issuers_addr VARCHAR(200) NOT NULL,
claim_topics_addr VARCHAR(200) NOT NULL,
trex_factory_addr VARCHAR(200) NOT NULL,
trusted_issuer_wallet VARCHAR(200) NOT NULL, -- endereço público do issuer Axia
claim_topics JSONB NOT NULL, -- [KYC, COUNTRY, PEP, ...]
enabled BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT now()
);DAO de cada tabela: 5 métodos obrigatórios (create, getById, deleteById, update, readAllRelations).
5. Pipeline de deploy do contrato (TokenController)
Status (2026-06-01) — dispatcher de deploy implementado.
actions.tools.tsrefatorado num dispatcher (resolveBaseContract(contract_standard)→nft-whitelabel | nft-3643 | erc3643):deployAxiaCompliantErc721(lêsol/AxiaCompliantERC721.json, constrói com IR/Compliance compartilhadas da config 3643) edeployTRexSuite(chamaTREXFactory.deployTREXSuiteviasol/TREXFactory.json, retorna o token).process.bc.contract.transactions.tspassaresolveBaseContract(transaction.contract_standard). Camposcontract_standard/compliance_address/ identity_registry_addressadicionados ao modeltkn-lib/collections+CollectionsEntity.ITrexChainConfigestendido (trexFactoryAddress,complianceAddress). Mint-call ERC721_COMPLIANT (feito):mint.service.tsagora escolhe ABI+método por standard —ERC721_COMPLIANTusaAxiaCompliantERC721.mint(to, tokenId, uri)(ABI emcore/utils/AxiaCompliantERC721.json), legado seguemintWithTokenURI. AGENT_ROLE confere (mint assinado pelo mnemonicParameter = admin do deploy). Transferências de NFT compliant usam otransferNftexistente (safeTransferFrom, enforcement no_update). Ciclo NFT regulado fechado: flag regulated → deploy → provisionamento lazy → mint.Pendente: mint do fungívelERC3643étoken.mint(to, amount)— fluxo distinto do NFT, fora domintNft(onde quer que tokens fungíveis sejam emitidos).
5.1 actions.tools.ts — branching real por baseContract
Hoje o callFunction ignora baseContract e sempre deploya NFT.json. Refatorar para um dispatcher:
public async callFunction(..., baseContract: string, name: string, symbol: string, ...): Promise<DeployResult> {
switch (baseContract) {
case 'nft-whitelabel': return this.deployErc721Plain(network, from, privateKey, name, symbol); // legado intacto
case 'erc3643': return this.deployTRexSuite({ network, signer, name, symbol, ...claimTopics, compliance });
case 'nft-3643': return this.deployAxiaCompliantErc721({ network, signer, name, symbol, identityRegistry, compliance });
default: throw new Error(`Unknown baseContract: ${baseContract}`);
}
}deployTRexSuitechamaTREXFactory.deployTREXSuite(salt, tokenDetails, claimDetails)→ retorna{ token, ir, irs, tir, ctr, mc }. Persistircompliance_addresseidentity_registry_addressna collection.deployAxiaCompliantErc721deploya o contrato custom apontando para a Identity Registry compartilhada da chain (detrex_chain_config) + umaModularComplianceprópria.- Manter o padrão atual de gas/EIP-1559 (
getFeeData, +20% margem, bump) já presente no arquivo.
5.2 process.bc.contract.transactions.ts — escolher pelo contract_standard
No loop de deploy de coleções pendentes, ler collection.contract_standard e mapear para baseContract. XRPL/Solana continuam intocados (3643 é EVM-only — se contract_standard != ERC721 numa rede não-EVM, rejeitar com erro claro).
6. Camada de identidade lazy (novo módulo no TokenController)
Status (2026-06-01) — implementado no TokenController.
core/utils/identity/identity.provisioning.service.ts(ABIs reais extraídas da suíte paracore/utils/identity/abis/), DAOonchain.identity.dao.ts(5 métodos + getByUserAndChain/setStatus), entity/dto, IoC (Utils.IdentityProvisioningService+Dao.OnchainIdentityDao),ConfigurationReaderService.getTrexParameters(chainId), Liquibase0068(collections.contract_standard) +0069(onchain_identities). Hook emmint.service.tschamaensureVerifiedIdentityantes do mint EVM regulado. Idempotente por (user,chain) via constraint única + status. Custodial: agente é management key (sem chave do investidor). Falta: campocontract_standardno model@axia/tkn-lib/models/collections, dispatcher de deploy emactions.tools.ts, e Controller/ClientLib admin (retry/status) — provisionamento core é interno ao mint.
6.1 IdentityProvisioningService
@Provide(injectionTokenConstant.Identity.ProvisioningService)
export class IdentityProvisioningService {
/**
* Garante que `walletAddress` está verificada na Identity Registry da chain.
* Idempotente por (userId, chainId). Chamado pelo mint job ANTES do mint 3643.
*/
public async ensureVerifiedIdentity(params: {
userId: string;
walletAddress: string;
network: Network;
}): Promise<{ onchainIdAddress: string; alreadyRegistered: boolean }>;
}Fluxo interno:
onchainIdentitiesDao.getByUserAndChain(userId, chainId)→ seREGISTERED, retorna (skip).- Lock atômico
(userId, chainId)(padrãotryAcquireLockSET NX EX já existente). - Puxa KYC do CPM:ts
const info = await this.cpmLib.getUserBasicInformationFromBasicInfo({ id: userId, sensitive: true }); // país (info.address.country), cpf/cnpj, status === APPROVED, PEP, dateOfBirth if (info.status !== 'APPROVED') throw new Error('User not KYC-approved — cannot register on-chain identity'); - Deploy ONCHAINID (
Identityda ONCHAINID lib) para a wallet —idFactory.createIdentity(wallet, salt). - Assina claims como Trusted Issuer Axia: topic
KYC(eCOUNTRY=ISO do país,PEPse aplicável). Assinatura ECDSA do issuer →identity.addClaim(topic, scheme, issuer, signature, data, uri). identityRegistry.registerIdentity(wallet, onchainId, countryCode)(agente = wallet do issuer/agente).- Persiste
onchain_identitiescomstatus=REGISTERED,claims,last_tx_hash. - Erros →
status=ERROR, log, e o mint daquele destinatário falha de forma controlada (não trava o batch inteiro).
6.2 Integração no mint.service.ts
Em mintNft() / mint do fungível, antes da chamada on-chain, se collection.contract_standard ∈ {ERC3643, ERC721_COMPLIANT}:
if (isRegulated(collection.contract_standard) && isEvmNetwork(network)) {
await this.identityProvisioning.ensureVerifiedIdentity({ userId: buyer.id, walletAddress: walletId, network });
// se custódia TKN_OWNER intermedia, garantir TKN_OWNER/sink também registrados (institucional)
}
// ... segue o mint atual (web3 sign + send) — agora reverte se não verificadoPontos de atenção no padrão custodial:
- Se o mint vai para
TKN_OWNERe depois transfere ao usuário, ambos precisam estar na registry. Registrar a malha custodial (TKN_OWNER, sinks, mainWallet) como identidades institucionais num bootstrap único por chain. - Burn 3643/compliant usa
burn()do agente (alinha com a estratégiaNATIVE_BURNv2 já existente).
7. Watcher de eventos (evm.reprocess.service.ts)
Adicionar decodificação dos eventos novos, mantendo idempotência por txHash:
- 3643/registry:
IdentityRegistered,IdentityRemoved,TokensFrozen,TokensUnfrozen,AddressFrozen,RecoverySuccess,ComplianceAdded. - ERC-721-compliant:
Transfer(já tratado) +AddressFrozen/Recoverycustom. - Reconciliar estado on-chain ↔ off-chain (ex.:
onchain_identities.status,assets.block_resell).
8. ClientLib / Gateway / Controller
Entrega completa por feature (guia): Liquibase + Entity + DTO + Model + DAO + Handler + JSONIC + ClientLib + Controller + Postman + GitBook.
- ClientLib (
@axia/...do TokenController): métodos tipadosact<MicroserviceRequest<T>, MicroserviceResponse<U>>para:setCollectionContractStandard,getIdentityStatus(userId, chainId),retryIdentityProvisioning,freezeInvestor,recoverWallet. - Gateway (API-Gateway REST): rotas admin sob
@AuthenticationRequired+ Swagger. - Controller: usa Model (nunca DTO),
@AuthenticationRequired, validauser.status === 'APPROVED'onde aplicável a operação financeira. - JSONIC tokens registrados.
9. Frontend
9.1 BackOffice geral (Common/Frontend/BackOffice/) — onde mora o admin de tokenização
- Seletor de padrão de contrato na configuração da coleção/encerramento de crowdfunding:
- Componente de seleção:
ERC721 (padrão)vsRegulado (ERC-3643 / ERC-721-compliant). - Não expor "ERC721 vs ERC3643" cru — modelar como toggle de compliance + tipo de ativo (fungível/NFT), e o backend resolve o
baseContract. - Aviso de irreversibilidade + implicação de mercado secundário (transferência só para wallets KYC) num modal de confirmação.
- Default por asset class: ofertas reguladas podem forçar regulado (campo travado).
- Componente de seleção:
- Tela de Identidades On-Chain: status por investidor (
PENDING/REGISTERED/REVOKED/ERROR), botão "reprovisionar", link para o explorer (network.scan_url). - Tela de Trusted Issuers / Claim Topics por chain (config de
trex_chain_config). - Ações de compliance: freeze/unfreeze investidor, freeze de token, recovery de wallet — com permissão dupla CPM + módulo DB e idealmente
@StepUpRequired()por serem ações sensíveis.
9.2 Midas-Web (investidor) — mínimo
- Badge "Ativo Regulado (ERC-3643)" no detalhe do investimento/NFT.
- Mensagem clara quando uma transferência/revenda é bloqueada por compliance (ex.: destino sem KYC) — não erro genérico.
- Reuso da infra de UI existente (sem
<app-shell>wrapper; padrões do redesign).
9.3 i18n (obrigatório)
Toda string nova em pt-br.json, en.json, es.json (e devops overrides do Midas-Web: devops/{b4,tokeniza}/pt-br.json Core e Discord; Discord usa TAB). Nada hardcoded.
10. Segurança
- Chave do Trusted Issuer e
AGENT_ROLE: KMS/HSM, serviço de assinatura dedicado, rotação. Comprometimento = KYC forjado / mint indevido. - Ações admin (freeze/recover/contract-standard): permissão dupla CPM + módulo DB +
@StepUpRequired(). - Validar
user.status === 'APPROVED'antes de provisionar identidade e antes de mint regulado. - Idempotência:
(userId, chainId)na identidade;txHashnas transações. Evitar duplo-deploy de ONCHAINID sob corrida (lock atômico). - Auditoria off-chain das ações (AuditService) além dos eventos on-chain.