10 min read
Overview
ERC-3643 is an Ethereum token standard for permissioned tokens, meaning tokens that can only be held and transferred by identities that satisfy predefined compliance rules such as KYC/AML, investor eligibility, and jurisdiction requirements. It is designed for regulated assets, where you must know who is holding the asset and whether they are allowed to hold or trade it.
Originally known as T-REX (Token for Regulated EXchanges) and initiated by Tokeny, it is now formalized as ERC-3643 and governed by the ERC-3643 Association, which focuses on standardizing permissioned tokenization for real-world assets (RWAs).
In practice, ERC-3643 is a suite of smart contracts that together implement:
- On-chain identity & claims (via ONCHAINID)
- Identity registries for eligible holders
- Modular compliance rules
- A token contract that remains ERC-20-compatible while enforcing compliance at transfer time
Since ERC-3643 integrates identity and compliance rules directly into the token standard, it’s an excellent choice for tokenizing RWAs such as securities, real estate, funds, and other regulated instruments.
What You Will Do
- Understand what problem ERC-3643 is solving compared to plain ERC-20
- Learn the high-level architecture (core contracts, registries, and roles)
- See how identity and compliance are enforced on-chain
- Walk through how transfers work in an ERC-3643 system
- Get an overview of deployment, roles, and common RWA use cases
What You Will Need
- Basic familiarity with Solidity and ERC-20
- Understanding of the EVM
- High-level knowledge of KYC/AML and regulatory concepts
- Awareness of EIP-173 (contract ownership) and role-based access control patterns
The code examples in this guide are unaudited and provided for conceptual understanding only. Do not use them in production without thorough review, testing, and independent security audits.
The Problem ERC-3643 Solves
Before diving in, let's set the context. A standard ERC-20 token is permissionless. Anyone can send it, anyone can receive it, and any address can hold it. This is a feature for assets like DAI or UNI.
However, when dealing with real world assets like a tokenized securities (shares in a company) or fractionalized real estate, this becomes a problem. Real-world regulations require you to know who holds the asset and to restrict transfers based on:
- Identity: Has the holder passed KYC?
- Jurisdiction: Is the recipient in a sanctioned country?
- Accreditation: Is the holder an "accredited investor" allowed to buy this security?
- Lockups: Is the asset in a "lockup" period where it can't be sold?
ERC-3643, originally known as T-REX (Token for Regulated EXchanges), was built to bring these rules onchain. It is a suite of smart contracts that makes an ERC-20-compatible token "compliance-aware".
Standard Requirements
At a high level, an ERC-3643 implementation should provide:
-
Compatibility and surfaces
- Remain ERC-20 compatible
- Expose a standard pre-check for transfers (
canTransfer) - Support batch operations and administrative functions needed for regulated flows
-
Identity and compliance
- Integrate an on-chain identity system (e.g., ONCHAINID)
- Use a compliance layer on transfer, mint, and burn
-
Controls and operations
- Support pausing and freezing (global, address-level, and partial freezes)
- Use EIP-173 ownership plus Agent roles for sensitive actions (forced transfers, recovery, mint/burn)
ERC-3643 Architecture
ERC-3643 is intentionally modular. Instead of putting everything in one monolithic token contract, it separates responsibilities across multiple contracts.
At a glance, the core components are:
| Component | Summary |
|---|---|
| ONCHAINID | A separate identity contract (based on ERC-734/735) that holds verifiable claims for a user, issued by trusted issuers. |
| Trusted Issuers Registry | A list of addresses (e.g., a KYC provider) that are trusted to add claims about a user's identity |
| Claim Topics Registry | Catalog of required claim types (e.g., KYC validated, accredited investor). |
| Identity Registry | Maps wallets to ONCHAINID and country, exposes verification like isVerified. |
| Compliance Contract | Encodes offering and transfer rules, consulted before/after transfers. |
| Permissioned Token (T-REX) | The ERC-20-compatible token. It holds references to the Identity Registry and Compliance contracts and consults them before any transfer. |
ONCHAINID
ONCHAINID is a generic identity contract that records keys and verifiable claims.
Examples of claims include:
- Address X passed KYC with provider Y
- This investor is accredited in jurisdiction Z
Trusted issuers (like KYC providers or regulated institutions) issue these claims. The contract does not store personally identifiable information (PII) directly, it stores references and hashes for privacy.
Multiple wallets can link to the same underlying identity, and in code this identity is typically referenced as IIdentity. ONCHAINID acts as the anchor for compliance-relevant facts.
Trusted Issuers Registry
The Trusted Issuers Registry tracks which claim issuers are trusted for a given token and which claim topics each issuer may sign.
For example, you might trust:
- A specific KYC provider for a
KYC_VALIDATEDtopic. - A regulated entity for
ACCREDITED_INVESTOR.
Only claims by these issuers are considered valid for that token’s compliance checks.
Claim Topics Registry
The Claim Topics Registry defines which claim types matter for holding the token, such as:
KYC_VALIDATEDACCREDITED_INVESTORNOT_IN_RESTRICTED_COUNTRYNOT_ON_SANCTIONS_LIST
Together with the Trusted Issuers Registry, it defines which claims are required and who may issue them.
Identity Registry
The Identity Registry is the gatekeeper for token holders:
- Stores the ONCHAINID for each whitelisted wallet
- Exposes
isVerified(address) - Enforces that only verified identities remain registered
It typically relies on IdentityRegistryStorage so multiple tokens can share a whitelist while keeping token-specific registries. After KYC, an investor links or deploys an ONCHAINID, receives the required claims from trusted issuers, and their wallet is registered with a country code (ISO-3166 numeric).
Compliance Contract
The Compliance contract encodes offering rules and transfer restrictions. Examples:
- Max number of investors per country.
- Max position size per investor.
- Jurisdiction allow/deny lists.
- Time-based rules like lockups and vesting.
Standard interface surface:
function canTransfer(address from, address to, uint256 amount) external view returns (bool);
function transferred(address from, address to, uint256 amount) external;
function created(address to, uint256 amount) external;
function destroyed(address from, uint256 amount) external;
The token contract uses the Compliance contract to:
- Pre-check whether a transfer is allowed (
canTransfer). - Inform the compliance logic after transfers, mints, and burns (
transferred,created,destroyed) so it can maintain internal state (caps, counters, etc.) in sync.
Permissioned Token (T-REX)
The token implements standard ERC-20 functions and holds references to IdentityRegistry and the Compliance contract. Instead of sending tokens blindly, it routes each transfer through a few extra checks to ensure the move is allowed. It wraps transfers with:
- Pause/freeze checks
- Compliance checks
- Post-transfer hooks (
transferred)
Typical extensions include:
- Forced transfers and token recovery
- Pause/freeze (global and per address)
- Partial freezes (only some balance is movable)
- Batch operations
How RWA Token Transfers Work
Now, let's see how an ERC-3643 token transfer works step-by-step.
Step 1: User Initiates Transfer
A user (Alice) calls the token's transfer or transferFrom function as usual to transfer tokens to a new address (Bob):
token.transfer(bob.address, amount);
token.transferFrom(from, bob.address, amount); // if an allowance is used
Step 2: Eligibility Checks
The Token contract does not move the tokens. It first performs eligibility checks via the Identity Registry. The logic is typically as follows:
- Checks whether the token is paused or if either address is frozen
- Ensures the receiver is registered and has the required claims in the
IdentityRegistryby callingisVerified()function
In many implementations, the token calls
identityRegistry.isVerified(...)directly and then callscompliance.canTransfer(...). In others, the eligibility check is bundled insidecanTransferitself. The important part is that both eligibility and compliance rules are enforced before any balances move.
Step 3: Compliance Evaluation
After the eligibility checks, the Token contract proceeds to the second check: compliance.canTransfer(alice.address, bob.address, amount).
The Compliance contract checks its modular logic to determine if the transfer is allowed and does not violate any of the rules.
The Compliance contract may evaluate the following rules:
- Global caps (e.g., number of holders per jurisdiction)
- Per-investor and per-country position limits
- Time windows, lockups, and vesting
- Any custom modules configured for that token
If any rule is violated, canTransfer returns false and the token should revert.
Step 4: Transfer
If all checks pass, the transfer executes like a normal ERC-20 transfer. Then, the Token calls compliance.transferred(from, to, amount) to update compliance state for future checks.
On mint, the Token calls compliance.created(to, amount), and on burn, it calls compliance.destroyed(from, amount) so the compliance engine tracks supply changes too.
Because claims are signed by trusted issuers and stored via ONCHAINID, you maintain a verifiable audit trail for eligibility.
You have an auditable trail:
- Who held what when
- Which claims and rules allowed a transfer
- Governance changes to registries and compliance modules over time
Roles and Permissions
ERC-3643 separates the Owner (per EIP-173) from Agent addresses that can operate sensitive functions. The Agent role is standardized:
interface IAgentRole {
event AgentAdded(address indexed agent);
event AgentRemoved(address indexed agent);
function addAgent(address agent) external;
function removeAgent(address agent) external;
function isAgent(address agent) external view returns (bool);
}
Owner manages agents and high-level configuration. Agents execute actions like freezing, forced transfers, mint/burn, and batch operations, depending on implementation.
Core Interfaces for Developers
Key standardized interfaces surfaced by ERC-3643 implementations:
- ERC3643 Token
- Identity Registry
- Identity Registry Storage
- Compliance
- Trusted Issuers
- Claim Topics
interface IERC3643 is IERC20 {
// getters
function onchainID() external view returns (address);
function identityRegistry() external view returns (IIdentityRegistry);
function compliance() external view returns (ICompliance);
function paused() external view returns (bool);
function isFrozen(address user) external view returns (bool);
function getFrozenTokens(address user) external view returns (uint256);
// admin
function pause() external;
function unpause() external;
function setAddressFrozen(address user, bool freeze) external;
function freezePartialTokens(address user, uint256 amount) external;
function unfreezePartialTokens(address user, uint256 amount) external;
function setIdentityRegistry(address identityRegistry) external;
function setCompliance(address compliance) external;
// lifecycle
function forcedTransfer(address from, address to, uint256 amount) external returns (bool);
function mint(address to, uint256 amount) external;
function burn(address user, uint256 amount) external;
function recoveryAddress(address lostWallet, address newWallet, address investorOnchainID) external returns (bool);
// batch helpers
function batchTransfer(address[] calldata toList, uint256[] calldata amounts) external;
}
interface IIdentityRegistry {
// wiring
function setIdentityRegistryStorage(address storageAddr) external;
function setClaimTopicsRegistry(address topics) external;
function setTrustedIssuersRegistry(address issuers) external;
// actions
function registerIdentity(address user, IIdentity id, uint16 country) external;
function deleteIdentity(address user) external;
function updateCountry(address user, uint16 country) external;
function updateIdentity(address user, IIdentity id) external;
// reads
function isVerified(address user) external view returns (bool);
function identity(address user) external view returns (IIdentity);
function investorCountry(address user) external view returns (uint16);
}
interface IIdentityRegistryStorage {
function storedIdentity(address user) external view returns (IIdentity);
function storedInvestorCountry(address user) external view returns (uint16);
function addIdentityToStorage(address user, IIdentity id, uint16 country) external;
function removeIdentityFromStorage(address user) external;
function modifyStoredInvestorCountry(address user, uint16 country) external;
function modifyStoredIdentity(address user, IIdentity id) external;
function bindIdentityRegistry(address identityRegistry) external;
function unbindIdentityRegistry(address identityRegistry) external;
function linkedIdentityRegistries() external view returns (address[] memory);
}
interface ICompliance {
function canTransfer(address from, address to, uint256 amount) external view returns (bool);
function transferred(address from, address to, uint256 amount) external;
function created(address to, uint256 amount) external;
function destroyed(address from, uint256 amount) external;
}
interface ITrustedIssuersRegistry {
function addTrustedIssuer(IClaimIssuer issuer, uint[] calldata claimTopics) external;
function removeTrustedIssuer(IClaimIssuer issuer) external;
function updateIssuerClaimTopics(IClaimIssuer issuer, uint[] calldata claimTopics) external;
function getTrustedIssuers() external view returns (IClaimIssuer[] memory);
function isTrustedIssuer(address issuer) external view returns (bool);
function getTrustedIssuerClaimTopics(IClaimIssuer issuer) external view returns (uint[] memory);
function getTrustedIssuersForClaimTopic(uint256 claimTopic) external view returns (IClaimIssuer[] memory);
function hasClaimTopic(address issuer, uint claimTopic) external view returns (bool);
}
interface IClaimTopicsRegistry {
function addClaimTopic(uint256 claimTopic) external;
function removeClaimTopic(uint256 claimTopic) external;
function getClaimTopics() external view returns (uint256[] memory);
}
Design Recap
At a minimum an ERC-3643 token deployment involves:
- Deploy registries:
ClaimTopicsRegistry,TrustedIssuersRegistry,IdentityRegistryStorage,IdentityRegistry. - Configure topics and issuers: add claim topics and trusted issuers with their supported topics.
- Bind storage to the identity registry and set registries on the identity registry.
- Deploy
Complianceand bind it to the token once deployed. - Deploy the token with references to
IdentityRegistryandCompliance. - Set up Owner and Agent roles and restrict sensitive functions to agents.
- Register investor identities (
address,ONCHAINID,country) in the identity registry. - Use
mintto allocate supply and ensure mint/burn callcreated/destroyed.
Common Use Cases
- Tokenized private funds / VC / PE: Restrict to accredited or institutional investors only
- Security token offerings (STOs): Enforce jurisdiction limits, max holders, lockups, etc.
- Tokenized real estate: Ensure only eligible investors can hold fractional ownership of a property
- Regulated trading venues: Build compliant secondary markets where every buy and sell order is validated onchain
Because it remains ERC-20 compatible, ERC-3643 tokens can integrate with wallets, custodians, exchanges (centralized or decentralized, where allowed), and portfolio and analytics tools as long as those platforms are ready to handle the permissioned nature of the asset (e.g., dealing with transfer reverts due to compliance).
What’s Next
From here, you can:
-
Read the full ERC-3643 whitepaper and spec
Dive into edge cases like delivery-versus-delivery (DvD), exchange integrations, and upgradeability patterns that go beyond this overview.
-
Explore the reference implementation
Clone the ERC-3643 / T-REX repository, deploy the registries and token to a local or testnet environment, and experiment with compliant vs. non-compliant transfers.
-
Prototype a simple RWA use case
For example:
- A tokenized private fund where only accredited investors from specific jurisdictions can hold the token
- A tokenized real estate asset with lockups and transfer windows
Use ERC-3643 to encode those rules directly into the token.
-
Build monitoring and compliance tooling
Use Quicknode Streams to subscribe to:
Transferevents from your ERC-3643 token- Custom events from your Compliance modules (e.g., rule violations, forced transfers)
Then feed that data into dashboards, alerting systems, or internal compliance tools.
As you explore these next steps, you’ll get a much clearer sense of how ERC-3643 fits into your own RWA or security-token roadmap.
Conclusion
In this guide, you learned how ERC-3643 layers on-chain identity, eligibility checks, and modular compliance rules on top of the familiar ERC-20 model to support regulated, permissioned assets.
If you're building RWA or security token infrastructure on Ethereum or other EVM chains, ERC-3643 offers an open-source foundation (T-REX) instead of reinventing compliance from scratch. Combined with reliable infrastructure from Quicknode, you can design, deploy, and monitor permissioned tokens that fit into real-world regulatory frameworks.
Subscribe to our newsletter for more articles and guides. Share feedback via Twitter or join our Discord community server.
We ❤️ Feedback!
Let us know if you have any feedback or requests for new topics. We'd love to hear from you.