ERC-7943 Universal Real-World Asset Token Interface (uRWA - Universal Real World Asset Interface)
Final · Tokens · created 2025-06-10
Defines a common interface for real-world asset (RWA) tokens such as securities and real estate. It adds the regulatory compliance features these tokens need, namely transferability checks, asset freezing, and forced transfers, on top of existing token standards such as ERC-20, ERC-721, and ERC-1155.
Why it is needed
Tokenizing real-world assets such as securities or real estate brings regulation with it. Transfers may need to be allowed only to permitted users, and assets may need to be frozen and forcibly moved for law enforcement. Existing token standards such as ERC-20, ERC-721, and ERC-1155 have no structure to carry these requirements. As a result, each issuer attached compliance features in its own way, and a decentralized finance (DeFi) protocol that wanted to integrate with RWA tokens had to learn each token's different approach individually. Earlier attempts at a universal RWA standard enforced fine-grained role-based access control, mandatory on-chain allowlists, specific on-chain identity systems, and metadata handling, which imposed unnecessary complexity and gas costs even on simple use cases that need none of those features. The assets are also so varied that a single comprehensive framework that specifies everything is hard to build. What was needed was a minimal common interface that leaves implementation to each party and unifies only the outward appearance.
What to implement
event ForcedTransfer(address indexed from, address indexed to, uint256 amount);: An event emitted when tokens are seized from one address and moved to another.event Frozen(address indexed account, uint256 amount);: Emitted when a call to setFrozenTokens changes an account's frozen amount. It carries the frozen amount after the change.function forcedTransfer(address from, address to, uint256 amount) external returns (bool result);: An authorized party forcibly moves tokens from from to to. Used for regulatory compliance or asset recovery.function setFrozenTokens(address account, uint256 amount) external returns (bool result);: Overwrites an account's frozen amount with a new value. Frozen tokens cannot be transferred by that account.function canSend(address account) external view returns (bool allowed);: Checks whether this account is eligible to send tokens. Commonly used for allowlists or KYC checks.function canReceive(address account) external view returns (bool allowed);: Checks whether this account is eligible to receive tokens.function getFrozenTokens(address account) external view returns (uint256 amount);: Returns the account's current frozen amount. The value may be larger than the balance.function canTransfer(address from, address to, uint256 amount) external view returns (bool allowed);: Checks whether this transfer is possible right now under the token's rules. It includes verification of the frozen amount.function setFrozenTokens(address account, uint256 tokenId, bool frozenStatus) external returns (bool result);: A variant for ERC-721. Sets whether a specific tokenId is frozen, as true or false.function getFrozenTokens(address account, uint256 tokenId) external view returns (bool frozenStatus);: A variant for ERC-721. Returns whether a specific tokenId is frozen for that account.function canTransfer(address from, address to, uint256 tokenId) external view returns (bool allowed);: A variant for ERC-721. Checks whether a specific tokenId can be transferred.function forcedTransfer(address from, address to, uint256 tokenId, uint256 amount) external returns (bool result);: A variant for ERC-1155. Forcibly moves the specified amount of tokens of a specific tokenId.function setFrozenTokens(address account, uint256 tokenId, uint256 amount) external returns (bool result);: A variant for ERC-1155. Overwrites the frozen amount of a specific tokenId.function getFrozenTokens(address account, uint256 tokenId) external view returns (uint256 amount);: A variant for ERC-1155. Returns the frozen amount of a specific tokenId.function canTransfer(address from, address to, uint256 tokenId, uint256 amount) external view returns (bool allowed);: A variant for ERC-1155. Checks whether the specified amount of a specific tokenId can be transferred.
In depth
One standard, three interfaces
An ERC-7943 token must extend one of the base token interfaces: ERC-20, ERC-721, ERC-1155, or ERC-6909. Three interfaces are defined to match the base standards: IERC7943Fungible (for ERC-20), IERC7943NonFungible (for ERC-721), and IERC7943MultiToken (for ERC-1155 and ERC-6909). The function set is the same, but the parameters differ. Fungible tokens use an amount, NFTs use a tokenId and a frozen flag (bool), and multi-tokens use a tokenId and an amount. ERC-165 supportsInterface must return true for the interfaceId 0x3edbb4c4 for the fungible interface, 0xbf1ef5fe for the non-fungible interface, and 0x41c4fbad for the multi-token interface. Sensitive functions such as forcedTransfer and setFrozenTokens must have access restrictions such as onlyOwner or role-based access control, and this standard does not specify which approach to use.
canSend and canReceive are account-level eligibility checks
canSend and canReceive check an account's own eligibility, regardless of individual transfer conditions such as the counterparty, amount, or tokenId. They are commonly used for allowlists or KYC/KYB/AML checks. Both functions MUST NOT revert and MUST NOT change contract storage, but they MAY depend on on-chain state such as the current timestamp, block number, or msg.sender. Per-transfer or amount-based rules such as balance checks or quantity limits must not be placed here, and such rules belong in canTransfer. Splitting sending and receiving makes one-directional restrictions possible, such as blocking receiving while still allowing sending. This can express situations common in regulated environments, such as KYC expiry, AML alerts, and jurisdictional constraints.
canTransfer is a transfer-level permission check
canTransfer evaluates whether a specific transfer is possible under the permissioned compliance rules. It MUST NOT revert and MUST NOT change storage. It must verify that the transfer amount does not exceed the unfrozen amount (the current balance minus the frozen amount), and it must perform the canSend check on from and the canReceive check on to. ERC-3643 does not perform these checks inside canTransfer, which is an explicitly noted difference. It must return false whenever a pause mechanism or any permissioned rule involving an authorized party would block the transfer. Conversely, it must not return false because of non-permissioned validation that belongs to the base token standard, such as balance or allowance checks. Public transfer functions such as transfer, transferFrom, and safeTransferFrom must not succeed in a situation where canTransfer would return false, or where canSend on from or canReceive on to is false.
Behavior rules for forcedTransfer
forcedTransfer must move the asset from from to to by directly manipulating balances or ownership, and it may do so either by a direct transfer or by burning from from and minting to to. Access must be restricted, validations such as balance sufficiency must be performed, and the base standard's transfer event matching the method used must be emitted together with the ForcedTransfer event. In a single-party permission environment the canTransfer check may be skipped. If tokens counted as frozen are involved in that case, the freeze must first be released and the Frozen event must be emitted before the base transfer event. This ordering matters for preventing reentrancy attacks on tokens such as ERC-721 and ERC-1155, which make external calls to the recipient. Even in this case, the canReceive check on to SHOULD still be performed. In a multi-party permission environment, the canTransfer check should be performed and the freeze constraint should not be bypassed. If validation or the canReceive check fails, it must revert. The name was chosen as a neutral expression for the act itself rather than a specific motive such as seizure or recovery, and forcedTransfer rather than forceTransfer is used for compatibility with ERC-3643.
setFrozenTokens overwrites with an absolute value
setFrozenTokens is a single function that overwrites the frozen amount with a new value, like ERC-20's approve, instead of splitting into freeze and unfreeze. A single function and a single Frozen event were chosen to keep the standard concise. It must emit the Frozen event, must have restricted access, and must revert on a logical problem or validation failure. In particular, it must allow freezing an amount larger than the current holdings, in order to lock up balances that arrive later. Because overwriting with an absolute value can cause races when there are multiple authorized accounts, the original text shows, as extension examples, setFrozenTokensIf, which checks an expected value, and setFrozenTokensDelta, which adjusts by an increment. It notes, however, that such helper functions still cannot prevent same-block conflicts or mempool ordering races. Extensions such as setFrozenTokensWithProof, which attaches legal evidence, and a multicall pattern similar to ERC-6357 are also given as examples.
Rules also apply to minting and burning
Permissionless minting, such as a public mint function, must not succeed if the recipient's canReceive is false. Permissioned minting performed by an authorized role SHOULD respect canReceive, but MAY skip it when operations or compliance require. Permissionless burning, such as a public burn function, must follow the canTransfer check and the canSend check on the holder, and must not allow burning more than the unfrozen amount. Permissioned burning may succeed even if the holder's canSend is false and may burn beyond the frozen amount. In that case the contract must update the frozen state and must emit the Frozen event before the base token's transfer event.
The error scheme favors specificity
The standard defines four errors: ERC7943CannotSend, ERC7943CannotReceive, ERC7943CannotTransfer, and ERC7943InsufficientUnfrozenBalance. The first three MAY be used as general revert mechanisms when canSend, canReceive, or canTransfer return false internally, and may be replaced by more specific errors or not used at all, depending on the checks performed. The overall principle is that error specificity comes first. ERC7943InsufficientUnfrozenBalance SHOULD be used when the transfer amount is less than or equal to the balance but greater than the unfrozen balance, or when an attempt is made to transfer a frozen tokenId. For failures unrelated to freezing, such as a transfer amount exceeding the entire balance or an account not holding that tokenId, the more specific errors of the base standard SHOULD be used. The error names follow the ERC-6093 guidelines. The status of this standard is finalized as Final.
Things to watch out for
- Access control for forcedTransfer and setFrozenTokens: The security of the access restriction mechanism for these two functions is the most important concern. If the permissions are breached, assets can be stolen. The specification strongly recommends safe patterns such as a multisig or a timelock.
- Front-running risk: Both forcedTransfer and setFrozenTokens can be exposed to front-running, much like approve in ERC-20. In an implementation that does not allow freezing more than the balance, an account has an incentive to evade a freeze attempt on its own balance by front-running it when it receives new funds. Functions that incrementally increase or decrease the frozen amount could be considered as an addition.
- Reentrancy and receiver hooks: Implementations must follow general security practices such as reentrancy guards and the checks-effects-interactions order. In particular, for tokens with receiver-side hooks such as ERC-721 and ERC-1155, a reentrancy guard can be a convenient way to prevent unwanted execution.