ERC-1404, the Simple Restricted Token Standard, extends ERC-20 with just two functions: detectTransferRestriction returns a code if a transfer is not allowed, and messageForTransferRestriction turns that code into a human-readable reason. It was designed in 2018 as the lightest possible way to make an ERC-20 token compliance-aware without forcing a particular identity or rules framework. It remains a useful pattern for simple private placements and closed investor groups.
Key takeaways
- Only two functions on top of ERC-20 — easy to implement, audit and integrate.
- Restriction logic is entirely up to the issuer: whitelists, lock-ups, max holders, jurisdiction lists.
- Wallets and venues can pre-check transfers and show users a clear reason before they sign.
- No standard identity, agent or recovery model — for those, look at ERC-3643 or ERC-7943.
How ERC-1404 works
function detectTransferRestriction(address from, address to, uint256 value)
external view returns (uint8 restrictionCode);
function messageForTransferRestriction(uint8 restrictionCode)
external view returns (string memory);
A return code of 0 means the transfer is allowed. Any other code identifies a rule — for example 1 = “receiver not whitelisted”, 2 = “sender in lock-up”, 3 = “max holders reached”. The token’s transfer and transferFrom call detectTransferRestriction and revert with the corresponding message if the code is non-zero.
Typical restriction rules
| Code | Rule | Example |
|---|---|---|
| 1 | Whitelist | Only addresses approved by the transfer agent |
| 2 | Lock-up | No transfers until a date (e.g. 12-month holding period) |
| 3 | Holder cap | Maximum number of holders to stay within an exemption |
| 4 | Jurisdiction | Block residents of certain countries |
| 5 | Frozen account | Issuer-frozen address |
Rules are usually stored in a separate restrictions or whitelist contract so they can evolve without redeploying the token.
Where ERC-1404 fits
- Private placements sold to a small, known investor group under a single exemption.
- Employee equity and token vesting, where lock-ups are the main concern.
- Club deals and syndicates in real estate or venture, where the issuer maintains the whitelist.
- Legacy platforms that adopted ERC-1404 early and want to stay compatible.
Limitations
- No identity standard. Whitelists are address lists; you must maintain the link to KYC records off-chain.
- No agent operations. Freezing, forced transfers and recovery are not specified and must be custom-built.
- Proposal status. It has not progressed to Final, so tooling support varies.
- Scaling rules. Complex multi-jurisdiction rules quickly outgrow a single restriction function.
ERC-1404 vs newer options
| ERC-1404 | ERC-3643 | ERC-7943 | |
|---|---|---|---|
| Base | ERC-20 | ERC-20 compatible | ERC-20, 721, 1155 or 6909 |
| Pre-check | detectTransferRestriction |
Registry + canTransfer |
canTransfer, canSend, canReceive |
| Reason messages | Yes | Via errors/events | Custom errors |
| Freeze / forced transfer | Custom | Standard | Standard |
| Identity | Custom | ONCHAINID | Implementation-defined |
Our recommendation
ERC-1404 is fine when the investor set is small and rules are simple. If you expect secondary trading across jurisdictions, multiple claim issuers or transfer-agent interventions, start with ERC-3643 — or implement ERC-7943’s minimal interface on your existing token to gain freeze and forced-transfer semantics with broad compatibility.
Example implementation
// Illustrative ERC-1404 on top of OpenZeppelin ERC20 — not audited.
contract RestrictedShare is ERC20, AccessControl {
uint8 public constant SUCCESS = 0;
uint8 public constant NOT_WHITELISTED = 1;
uint8 public constant LOCKED = 2;
mapping(address => bool) public whitelisted;
mapping(address => uint64) public lockedUntil;
function detectTransferRestriction(address from, address to, uint256) public view returns (uint8) {
if (from != address(0) && block.timestamp < lockedUntil[from]) return LOCKED;
if (to != address(0) && !whitelisted[to]) return NOT_WHITELISTED;
return SUCCESS;
}
function messageForTransferRestriction(uint8 code) public pure returns (string memory) {
if (code == NOT_WHITELISTED) return "Receiver is not an approved investor";
if (code == LOCKED) return "Sender's tokens are in a lock-up period";
return "No restriction";
}
function _update(address from, address to, uint256 amt) internal override {
uint8 code = detectTransferRestriction(from, to, amt);
require(code == SUCCESS, messageForTransferRestriction(code));
super._update(from, to, amt);
}
}
Operating the whitelist
The weak point in ERC-1404 deployments is rarely the code; it is the process around the whitelist:
- Source of truth: link each whitelisted address to a KYC record in your investor management system.
- Change control: whitelist updates should be logged, reviewed and executed by an authorised role.
- Refresh cycles: remove or flag addresses whose KYC has expired.
- Multiple wallets: decide whether one investor may whitelist several addresses and how holdings are aggregated for holder-count limits.
Upgrading the rule set over time
Store rules in a separate restrictions contract referenced by the token. When regulations or offering terms change, deploy a new restrictions contract and point the token to it through a governed, time-locked call. This keeps balances untouched while letting compliance evolve.
When to move beyond ERC-1404
Signs you have outgrown the standard: investors across many jurisdictions, multiple KYC providers, the need to reuse KYC across tokens, frequent agent interventions or venue integrations that expect ERC-3643. At that point, migrate to ERC-3643 or implement ERC-7943 alongside ERC-1404.
Frequently asked questions
Yes. It is ERC-20 with two extra view functions; wallets that do not know ERC-1404 still show balances, but transfers that break rules will fail.
Interfaces call detectTransferRestriction before sending and display messageForTransferRestriction — for example “Receiver is not an approved investor.”
It can, but you must implement all the logic yourself. Multi-jurisdiction programmes usually benefit from ERC-3643’s modular compliance.
Yes, through a migration that onboards holders’ identities and swaps balances into a new ERC-3643 token.
Yes, by tracking holders in the restriction logic, though you must handle edge cases such as partial transfers and multiple wallets per investor.
Usually yes, because it involves fewer contracts and checks — at the cost of fewer built-in features.
This article is for general information only and is not legal, tax or investment advice. Token standards evolve — always confirm the current status of an ERC at eips.ethereum.org and take advice from qualified counsel in each jurisdiction before issuing tokens.


