ERC-3643 T-REX, the security token standard (T-REX - Token for Regulated EXchanges)
Final · Tokens · created 2021-07-09
A permissioned token standard that automatically checks on-chain identity and regulatory compliance on every transfer, so that tokens representing regulated assets such as stocks move only among verified investors.
Why it is needed
Turning assets such as stocks or real estate into tokens means complying with securities law. Not just anyone can hold the tokens, and the issuer must track who holds them and ensure that only qualified investors can receive them. However, existing ERC-20 tokens can be freely sent and received by anyone, so they cannot enforce these requirements. There is no standard way to check whether a transfer counterparty has completed identity verification (KYC) or complies with rules such as country restrictions and per-person holding limits. The standard also lacks features that securities management needs, such as token freezing, pausing and recovery of lost wallets. ERC-3643 defines a set of interfaces that make the token contract go through an on-chain identity system and a compliance contract on every transfer, so the whole process of a security token, from issuance to circulation, can be managed on-chain.
What to implement
function addAgent(address _agent) external;: The owner adds an agent address to handle operations.function removeAgent(address _agent) external;: The owner removes an agent address.function isAgent(address _agent) external view returns (bool);: Checks whether the address is an agent.function onchainID() external view returns (address);: Returns the address of the onchain identity contract linked to the token.function identityRegistry() external view returns (IIdentityRegistry);: Returns the Identity Registry contract the token uses.function compliance() external view returns (ICompliance);: Returns the Compliance contract bound to the token.function paused() external view returns (bool);: Reports whether the token is paused.function isFrozen(address _userAddress) external view returns (bool);: Reports whether the wallet address is frozen entirely.function getFrozenTokens(address _userAddress) external view returns (uint256);: Returns the amount of tokens frozen in the wallet.function pause() external;: Pauses transfers for the entire token.function unpause() external;: Lifts the pause.function setAddressFrozen(address _userAddress, bool _freeze) external;: Freezes or unfreezes an entire wallet address.function freezePartialTokens(address _userAddress, uint256 _amount) external;: Freezes only part of a wallet's balance.function unfreezePartialTokens(address _userAddress, uint256 _amount) external;: Unfreezes a partially frozen amount.function setIdentityRegistry(address _identityRegistry) external;: Sets the Identity Registry the token will use.function setCompliance(address _compliance) external;: Sets the Compliance contract to apply to the token.function forcedTransfer(address _from, address _to, uint256 _amount) external returns (bool);: An agent forcibly transfers tokens from an investor's wallet. It requires only identity verification of the recipient and bypasses compliance rules.function mint(address _to, uint256 _amount) external;: Mints new tokens to a verified recipient.function burn(address _userAddress, uint256 _amount) external;: Burns the tokens in the wallet. It bypasses all eligibility checks.function recoveryAddress(address _lostWallet, address _newWallet, address _investorOnchainID) external returns (bool);: Recovers the tokens of an investor who lost their private key to a new wallet, based on their onchain identity.function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external;: Bundles multiple transfers into a single transaction.function batchForcedTransfer(address[] calldata _fromList, address[] calldata _toList, uint256[] calldata _amounts) external;: Processes multiple forced transfers at once.event RecoverySuccess(address _lostWallet, address _newWallet, address _investorOnchainID);: An event recorded when a wallet recovery succeeds.event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner);: An event recorded when the frozen status of a wallet address changes.function registerIdentity(address _userAddress, IIdentity _identity, uint16 _country) external;: An agent registers an investor's wallet address, Identity contract, and country code in the Identity Registry.function isVerified(address _userAddress) external view returns (bool);: Checks that the wallet is in the registry and that the linked Identity contract holds the required claims signed by an approved issuer.function investorCountry(address _userAddress) external view returns (uint16);: Returns the investor's country code of residence.function canTransfer(address _from, address _to, uint256 _amount) external view returns (bool);: Checks in advance whether a transfer complies with token-wide rules (such as a cap on the number of holders or a per-investor limit).function transferred(address _from, address _to, uint256 _amount) external;: Notifies the Compliance contract to update its state after a transfer has been executed.function addTrustedIssuer(IClaimIssuer _trustedIssuer, uint[] calldata _claimTopics) external;: Registers trusted claim issuers and the claim topics each issuer is allowed to handle.function addClaimTopic(uint256 _claimTopic) external;: Adds a claim topic that an investor's identity must satisfy.
In depth
Functions the standard requires
An ERC-3643 token must be compatible with ERC-20 and work together with an onchain identity system. On top of that, it must allow any rules that regulators or issuers require, and it must support a standard interface for checking whether a transfer will succeed before sending it, a recovery system for lost private keys, partial and full freezing of investor wallets, token pausing, minting and burning, definitions of the Agent and Owner roles, forced transfers by agent wallets, and batch transactions that save gas and are processed in the same block. All ERC-20 functions can be called, but because the token is permissioned, every transfer is subject to identity and compliance checks.
Conditions for a transfer to succeed
For transfer to succeed, the sender must have enough free balance (total balance minus frozen tokens), and the recipient must be whitelisted in the Identity Registry and verified with the required claims. Neither wallet may be frozen, the token must not be paused, and canTransfer on the Compliance contract must return TRUE. transferFrom works the same way. mint and forcedTransfer require only that the recipient be registered and verified and bypass compliance rules, and burn bypasses all eligibility checks.
Division of roles between isVerified and canTransfer
The two checks answer different questions. isVerified looks at the individual investor's eligibility. It checks whether the recipient wallet is in the token's Identity Registry, whether the linked Identity contract holds the claims required by the Claim Topics Registry, and whether those claims are signed by an issuer registered in the Trusted Issuers Registry. canTransfer looks at the rules applied to the whole token. It checks whether this transfer fits rules such as the maximum number of holders (including per-country limits) or the maximum holding per investor. Both are called inside the transfer function, and like balanceOf and allowance in ERC-20, they can also be queried before a transfer to avoid failures.
A structure made of six contracts
The standard defines the interfaces of the Token, Identity Registry, Identity Registry Storage, Compliance, Trusted Issuers Registry, and Claim Topics Registry. The Identity Registry links wallet addresses, Identity contracts, and ISO-3166 country codes, with one registry per token. Identity Registry Storage separates whitelist storage from registry functionality. It can be bound to multiple Identity Registries so that they share the investor list while each token keeps different issuer and claim requirements. Compliance holds the rules for the issuance conditions and is called on every transaction. It can be built as a custom contract or deployed as a modular contract to which external Modules can be attached and detached.
Owner and Agent roles
Ownership is defined by ERC-173, and the owner (the token issuer) appoints and dismisses agents. Contracts serving as the Token and the Identity Registry must be compatible with the IAgentRole interface. Only agents can add and remove identities in the Identity Registry, and the owner can designate themselves as an agent to keep full control. Putting agent functions in the standard interface is an intentional design choice. It supports scenarios in which not only externally owned accounts but also automated systems or smart contracts act as agents and automatically execute minting, burning, and freezing under set conditions, for example burning tokens in response to a redemption request or freezing a wallet implicated in fraud.
Recovery, freezing, pausing, and batch processing
recoveryAddress moves the tokens of an investor who lost their private key from the lost wallet to a new wallet, based on the investor's onchain identity (_investorOnchainID). The history of the recovered tokens is preserved on the blockchain for transparency. Freezing works at two levels: setAddressFrozen freezes an entire wallet address, and freezePartialTokens freezes only part of the balance. pause and unpause stop or resume transfers for the whole token. Batch functions such as batchTransfer, batchMint, batchBurn, batchForcedTransfer, and batchSetAddressFrozen are meant to save gas and process multiple operations in the same block.
Audits and standard status
The specification was audited by Kapersky and Hacken, and no notable security issues were found. The audits mainly targeted Tokeny's implementation, but the original document says they also verified the core principles of the T-REX standard. The standard status is Final, so its content will no longer change.