ERC-3643 Explained: How Security Tokens Enforce KYC On-Chain
ERC-3643 (T-REX) explained for developers: ONCHAINID, identity registry, compliance modules, agent powers, a Solidity sketch, and ERC-1400 compared.
ERC-3643 Explained: How Security Tokens Enforce KYC On-ChainERC-3643, also called T-REX (Token for Regulated EXchanges), is a final Ethereum standard for permissioned tokens. It keeps the ERC-20 interface but checks every transfer against an on-chain identity registry and a compliance contract, so only verified wallets can hold the token and every transfer must pass the issuer's rules. It is widely used for tokenized securities and real-world assets because it encodes KYC and transfer restrictions directly in the token.
Key takeaways
- ERC-3643 was created in July 2021 and has Final status as an Ethereum standard.
- It is ERC-20 compatible, so wallets and tooling can read balances and transfers normally.
- Six parts work together: the token, Identity Registry, Identity Registry Storage, Compliance, Trusted Issuers Registry and Claim Topics Registry.
- Investor identities live in ONCHAINID contracts that hold signed claims (for example "KYC passed") from trusted issuers, based on ERC-734/735.
- Agents appointed by the owner can mint, burn, freeze, pause and force transfers, which is what regulated assets require and also what you must disclose.
- Use the official open-source implementation for anything real; the code below is a simplified, testnet-only sketch for learning.
Why permissioned tokens exist
A plain ERC-20 lets anyone send tokens to anyone. That is fine for many crypto assets, but not for a tokenized bond, fund unit or property share. Regulated assets usually need:
- Known holders: every holder has passed KYC/AML.
- Eligibility rules: some investors or countries may be excluded.
- Limits: caps on holders, balances or transfer sizes.
- Recovery: a way to restore tokens if a holder loses their wallet, or to act on a court order.
- Freezing: the ability to stop a compromised or sanctioned wallet.
You could enforce these off-chain, but then the token can still move freely on-chain and the records drift apart. ERC-3643 puts the rules in the transfer path itself.
The parts of ERC-3643
According to the EIP, the standard defines six core interfaces (EIP-3643):
| Component | Role |
|---|---|
| Token (IERC3643) | ERC-20 compatible token that checks identity and compliance on every transfer |
| Identity Registry | Maps wallets to investor identities and answers isVerified(address) |
| Identity Registry Storage | Stores the list of registered investor identities, can be shared across tokens |
| Compliance (ICompliance) | Encodes offering rules such as caps or country limits; answers canTransfer(from, to, amount) |
| Trusted Issuers Registry | Lists the claim issuers the token trusts (for example a KYC provider) |
| Claim Topics Registry | Lists which claim types a holder must have (for example KYC, accreditation) |
ONCHAINID: the identity layer
Each investor has an identity contract, ONCHAINID, built on the ERC-734/735 key and claim holder standards. Trusted issuers sign claims and attach them to the identity, much like a verifiable credential that says "this person passed KYC" or "this person is resident in country X" (QuickNode guide).
Personal data is not stored on-chain. The claim says that a check was done and by whom; the documents stay with the KYC provider.
Identity Registry and isVerified
When a transfer happens, the token asks the Identity Registry whether the receiver is verified. The registry checks that the wallet is linked to an identity and that the identity holds valid claims, for each required topic, from trusted issuers.
Compliance and canTransfer
The Compliance contract holds the offering's rules. Modern implementations make it modular: you attach modules for things like maximum holders, country restrictions or balance limits. The token calls canTransfer before a transfer and notifies compliance after transfers, mints and burns (transferred, created, destroyed) so modules can keep their state in sync.
Transfer conditions
Per the EIP, a transfer succeeds only if:
- the sender has enough free (unfrozen) balance,
- the receiver is verified in the Identity Registry,
- neither wallet is frozen,
- the token is not paused, and
- compliance allows the transfer.
Agent powers
The owner appoints agents who can mint, burn, freeze addresses or partial balances, pause the token, run batch operations, force transfers and recover tokens to a new wallet. These powers are essential for regulated assets, and they are also central trust assumptions. Any whitepaper or investor disclosure should say who the agents are and how their keys are protected.
A simplified Solidity sketch
The following is a teaching sketch for testnets only. It shows the core idea (identity check plus compliance check in the transfer path) using OpenZeppelin Contracts v5. It is not ERC-3643 compliant, not audited, and leaves out ONCHAINID, claim verification and most agent features. For real work, start from the official ERC-3643 implementation.
// SPDX-License-Identifier: MIT
// EDUCATIONAL / TESTNET ONLY. Simplified illustration of ERC-3643 ideas. Not audited.
pragma solidity ^0.8.24;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
interface IIdentityRegistryLite {
function isVerified(address wallet) external view returns (bool);
}
interface IComplianceLite {
function canTransfer(address from, address to, uint256 amount) external view returns (bool);
}
/// Stand-in for an identity registry: the owner marks wallets as verified.
contract IdentityRegistryLite is IIdentityRegistryLite, Ownable {
mapping(address => bool) private _verified;
constructor(address owner_) Ownable(owner_) {}
function setVerified(address wallet, bool status) external onlyOwner {
_verified[wallet] = status;
}
function isVerified(address wallet) external view returns (bool) {
return _verified[wallet];
}
}
/// Example compliance rule: no wallet may hold more than maxBalance.
contract MaxBalanceCompliance is IComplianceLite {
ERC20 public immutable token;
uint256 public immutable maxBalance;
constructor(ERC20 token_, uint256 maxBalance_) {
token = token_;
maxBalance = maxBalance_;
}
function canTransfer(address, address to, uint256 amount) external view returns (bool) {
return token.balanceOf(to) + amount <= maxBalance;
}
}
contract PermissionedTokenLite is ERC20, Ownable {
IIdentityRegistryLite public identityRegistry;
IComplianceLite public compliance;
mapping(address => bool) public isAgent;
mapping(address => bool) public frozen;
bool private _forced;
error NotAgent();
error NotVerified(address wallet);
error WalletFrozen(address wallet);
error ComplianceRejected();
modifier onlyAgent() {
if (!isAgent[msg.sender]) revert NotAgent();
_;
}
constructor(address owner_, IIdentityRegistryLite registry_)
ERC20("Test Security Token", "tSEC")
Ownable(owner_)
{
identityRegistry = registry_;
}
function setCompliance(IComplianceLite compliance_) external onlyOwner {
compliance = compliance_;
}
function setAgent(address agent, bool enabled) external onlyOwner {
isAgent[agent] = enabled;
}
function mint(address to, uint256 amount) external onlyAgent {
_mint(to, amount);
}
function burn(address from, uint256 amount) external onlyAgent {
_burn(from, amount);
}
function setFrozen(address wallet, bool status) external onlyAgent {
frozen[wallet] = status;
}
/// Agent-only transfer that skips freeze and compliance checks (receiver must still be verified).
function forcedTransfer(address from, address to, uint256 amount) external onlyAgent {
_forced = true;
_transfer(from, to, amount);
_forced = false;
}
/// OpenZeppelin v5 routes mint, burn and transfer through _update.
function _update(address from, address to, uint256 amount) internal override {
if (to != address(0) && !identityRegistry.isVerified(to)) revert NotVerified(to);
bool isPlainTransfer = from != address(0) && to != address(0);
if (isPlainTransfer && !_forced) {
if (frozen[from]) revert WalletFrozen(from);
if (frozen[to]) revert WalletFrozen(to);
if (address(compliance) != address(0) && !compliance.canTransfer(from, to, amount)) {
revert ComplianceRejected();
}
}
super._update(from, to, amount);
}
}
Deployment order on a testnet: deploy IdentityRegistryLite, deploy PermissionedTokenLite with the registry, deploy MaxBalanceCompliance with the token address, then call setCompliance. The compliance contract needs the token address and the token needs the compliance address, which is why compliance is set after deployment.
What to test with Foundry
- Minting to an unverified wallet reverts with
NotVerified. - A transfer between two verified wallets succeeds.
- A transfer to an unverified wallet reverts.
- A frozen sender cannot transfer.
- A transfer that would exceed
maxBalancereverts withComplianceRejected. - An agent's
forcedTransfermoves tokens from a frozen wallet to a verified recovery wallet. - Non-agents cannot mint, burn, freeze or force transfers.
What the real standard adds
The official implementation adds ONCHAINID claim verification against trusted issuers and claim topics, partial freezes, pause, wallet recovery, batch functions, modular compliance with transferred, created and destroyed hooks, and upgradeable proxies with a factory for deploying full token suites. Read its code and audit reports before building production systems (GitHub).
ERC-3643 vs ERC-1400 and other alternatives
| Option | What it is | Notes |
|---|---|---|
| ERC-3643 (T-REX) | Final EIP; permissioned ERC-20 with on-chain identity and modular compliance | Strong identity model; active ecosystem through the ERC-3643 Association |
| ERC-1400 | A family of security token proposals (partitions, document management, transfer checks) | Proposed in 2018 and never finalized as an EIP; used in some older projects (GitHub issue) |
| Custom allowlist ERC-20 | ERC-20 with your own allowlist in _update | Simple and fine for prototypes; you own all the design and audit work |
| Permissioned chain | Restrict who can use the network itself | Simplifies some compliance; reduces openness and interoperability |
When should you use ERC-3643?
Good fit:
- Tokenized securities, fund units, bonds and property shares where holders must be verified.
- Multi-token platforms that want one shared identity registry across many assets.
- Issuers who need recovery and freeze powers to meet legal duties.
Possibly overkill:
- Internal pilots with a handful of known wallets.
- Utility tokens or loyalty points with no investor eligibility rules.
- Stablecoins, which often use simpler allow and deny lists.
In Pakistan, PVARA's issuance rules (effective 21 August 2026) set reserve, capital and disclosure requirements for Asset-Referenced and Fiat-Referenced Tokens but do not mandate a specific token standard (Cryptoverse Lawyers). Choosing ERC-3643 is a design decision, not a legal requirement, though it makes KYC and transfer controls easier to evidence.
At Digital Silk Route (DSR), we see permissioned token standards as a core skill for anyone who wants to work with regulated issuers.
Next step with Digital Silk Route
Permissioned tokens are where tokenization becomes real engineering. Digital Silk Route (DSR) covers identity registries, compliance modules and agent controls in the Advanced level of its Tokenization track, as part of a capstone-based path from Beginner to Advanced. Turn your capstone into a reusable compliance module or test suite and list it on the DSR Marketplace. Learn. Build. Earn.
Related reading
- How to Tokenize an Asset: A Technical Walkthrough
- Real Estate Tokenization in Pakistan: How It Works and What's Allowed
- Built Right, Not Fast: How We Think About DSR Blockchain
Sources
- EIP-3643: T-REX, Token for Regulated EXchanges
- ERC-3643 official implementation (GitHub)
- QuickNode: What is ERC-3643?
- Tokeny: ERC-3643 whitepaper
- Ethereum EIPs issue #1411: ERC-1400 security token standard
- OpenZeppelin Contracts v5 documentation
- Cryptoverse Lawyers: PVARA token issuance rules
This article is for education only and is not financial, legal or investment advice.


