This EIP proposes the Universal RWA (uRWA) standard, an interface for tokenized Real World Assets (RWAs) such as securities, real estate, commodities, or other physical/financial assets on the blockchain.
Real World Assets often require regulatory compliance features not found in standard tokens, including the ability to freeze assets, perform enforcement transfers for legal compliance, and restrict transfers to authorized users. The uRWA standard extends common token standards like ERC-20, ERC-721 or ERC-1155 by introducing essential compliance functions while remaining minimal and not opinionated about specific implementation details.
This enables DeFi protocols and applications to interact with tokenized real-world assets in a standardized way, knowing they can check transfer permissions, whether users are allowed to interact, handle frozen assets appropriately, and integrate with compliant RWA tokens regardless of the underlying asset type or internal compliance logic. It also adopts ERC-165 for introspection.
Motivation
Real World Assets (RWAs) represent a significant opportunity to bridge traditional finance and decentralized finance (DeFi). By tokenizing assets like real estate, corporate bonds, commodities, art, or securities, we can unlock benefits such as fractional ownership, programmable compliance, enhanced liquidity through secondary markets for traditionally illiquid assets and integration with decentralized protocols.
However, tokenizing real world assets introduces regulatory requirements often absent in purely digital assets, such as allowlists for users, transfer restrictions, asset freezing or law enforcement rules. Existing token standards like ERC-20, ERC-721 and ERC-1155 lack the inherent structure to address these compliance needs directly within the standard itself.
Attempts at defining universal RWA standards historically imposed unnecessary complexity and gas overhead for simpler use cases that do not require the full spectrum of features like granular role-based access control, mandatory on-chain whitelisting, specific on-chain identity solutions, or metadata handling solutions mandated by the standard.
Additionally, the broad spectrum of RWA classes inherently suggests the need to move away from a one-size-fits-all solution. With the purpose in mind of defining an EIP for it, a minimalistic approach, unopinionated features list and maximally compatible design should be kept in mind.
The uRWA standard seeks a more refined balance by defining an essential interface, establishing a common ground for interaction regarding compliance and control, without dictating the underlying implementation mechanisms. This allows core token implementations to remain lean while providing standard functions for RWA-specific interactions.
The final goal is to build composable DeFi around RWAs, providing the same interface when dealing with compliance and regulation.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
The following defines the standard interface for an ERC-7943 token contract, which MUST extend from a base token interface such as ERC-20, ERC-721 or ERC-1155:
pragmasolidity^0.8.29;/// @notice Defines the ERC-7943 interface, the uRWA.
/// When interacting with specific token standards:
/// - For ERC-721 like (non-fungible) tokens 'amount' parameters typically represent a single token (i.e., 1).
/// - For ERC-20 like (fungible) tokens, 'tokenId' parameters are generally not applicable and should be set to 0.
interfaceIERC7943/*is IERC165*/{/// @notice Emitted when tokens are taken from one address and transferred to another.
/// @param from The address from which tokens were taken.
/// @param to The address to which seized tokens were transferred.
/// @param tokenId The ID of the token being transferred.
/// @param amount The amount seized.
eventForcedTransfer(addressindexedfrom,addressindexedto,uint256tokenId,uint256amount);/// @notice Emitted when `setFrozen` is called, changing the frozen `amount` of `tokenId` tokens for `user`.
/// @param user The address of the user whose tokens are being frozen.
/// @param tokenId The ID of the token being frozen.
/// @param amount The amount of tokens frozen after the change.
eventFrozen(addressindexeduser,uint256indexedtokenId,uint256amount);/// @notice Error reverted when a user is not allowed to interact.
/// @param account The address of the user which is not allowed for interactions.
errorERC7943NotAllowedUser(addressaccount);/// @notice Error reverted when a transfer is not allowed due to restrictions in place.
/// @param from The address from which tokens are being transferred.
/// @param to The address to which tokens are being transferred.
/// @param tokenId The ID of the token being transferred.
/// @param amount The amount being transferred.
errorERC7943NotAllowedTransfer(addressfrom,addressto,uint256tokenId,uint256amount);/// @notice Error reverted when a transfer is attempted from `user` with an `amount` of `tokenId` less or equal than its balance, but greater than its unfrozen balance.
/// @param user The address holding the tokens.
/// @param tokenId The ID of the token being transferred.
/// @param amount The amount being transferred.
/// @param unfrozen The amount of tokens that are unfrozen and available to transfer.
errorERC7943InsufficientUnfrozenBalance(addressuser,uint256tokenId,uint256amount,uint256unfrozen);/// @notice Takes tokens from one address and transfers them to another.
/// @dev Requires specific authorization. Used for regulatory compliance or recovery scenarios.
/// @param from The address from which `amount` is taken.
/// @param to The address that receives `amount`.
/// @param tokenId The ID of the token being transferred.
/// @param amount The amount to force transfer.
functionforceTransfer(addressfrom,addressto,uint256tokenId,uint256amount)external;/// @notice Changes the frozen status of `amount` of `tokenId` tokens belonging to an `user`.
/// This overwrites the current value, similar to an `approve` function.
/// @dev Requires specific authorization. Frozen tokens cannot be transferred by the user.
/// @param user The address of the user whose tokens are to be frozen/unfrozen.
/// @param tokenId The ID of the token to freeze/unfreeze.
/// @param amount The amount of tokens to freeze/unfreeze.
functionsetFrozen(addressuser,uint256tokenId,uint256amount)external;/// @notice Checks the frozen status/amount of a specific `tokenId`.
/// @param user The address of the user.
/// @param tokenId The ID of the token.
/// @return amount The amount of `tokenId` tokens currently frozen for `user`.
functiongetFrozen(addressuser,uint256tokenId)externalviewreturns(uint256amount);/// @notice Checks if a transfer is currently possible according to token rules. It enforces validations on the frozen tokens.
/// @dev This may involve checks like allowlists, blocklists, transfer limits and other policy-defined restrictions.
/// @param from The address sending tokens.
/// @param to The address receiving tokens.
/// @param tokenId The ID of the token being transferred.
/// @param amount The amount being transferred.
/// @return allowed True if the transfer is allowed, false otherwise.
functionisTransferAllowed(addressfrom,addressto,uint256tokenId,uint256amount)externalviewreturns(boolallowed);/// @notice Checks if a specific user is allowed to interact according to token rules.
/// @dev This is often used for allowlist/KYC/KYB/AML checks.
/// @param user The address to check.
/// @return allowed True if the user is allowed, false otherwise.
functionisUserAllowed(addressuser)externalviewreturns(boolallowed);}
isUserAllowed, isTransferAllowed and getFrozen
These provide views into the implementing contract’s compliance, transfer policy logic and freezing status. These functions:
MUST NOT revert.
MUST NOT change the storage of the contract.
MAY depend on context (e.g., current timestamp, block number).
The isTransferAllowed MUST validate that the amount being transferred doesn’t exceed the unfrozen amount (which is the difference between the current balance and the frozen balance). Additionally it MUST perform an isUserAllowed check on the from and to parameters.
forceTransfer
This function provides a standard mechanism for forcing a transfer from a from to a to address. The function:
MUST directly manipulate balances or ownership to transfer the asset from from to to either by transferring or burning from from and minting to to.
MUST be restricted in access.
MUST perform necessary validation checks (e.g., sufficient balance/ownership of a specific token).
MUST emit both the standard transfer event (from the base standard) and the ForcedTransfer event.
It CAN bypass the freezing validations and update the freezing status accordingly. Only if this happens, it MUST unfreeze the assets first and emit a Frozen event before the underlying base token transfer event reflecting the change. Having the unfrozen amount changed before the actual transfer is critical for tokens that might be susceptible to reentrancy attacks doing external checks on recipients as it is the case for ERC-721 and ERC-1155 tokens.
MUST bypass checks enforced by isTransferAllowed.
MUST perform an isUserAllowed check on the to parameter.
setFrozen
It provides a way to freeze or unfreeze assets held by a specific user. This is useful for temporary lock mechanisms. This function:
MUST emit the Frozen event.
MUST be restricted in access.
SHOULD NOT allow freezing more assets than those held by the user.
Additional Specifications
The contract MUST implement the ERC-165supportsInterface function and MUST return true for the bytes4 value 0xf35fc3be being it the interfaceId of the ERC-7943.
Given the agnostic nature of the standard on the specific base token standard being used, the implementation SHOULD use tokenId = 0 for ERC-20 based implementations, and amount = 1 for ERC-721 based implementations on ForcedTransfer and Frozen events, ERC7943NotAllowedTransfer and ERC7943InsufficientUnfrozenBalance errors, and forceTransfer, setFrozen, getFrozen and isTransferAllowed functions. Integrators MAY decide to not enforce this, however the standard discourages it. This is considered a minor tradeoff for having a unique standard interface for different token standards without overlapping syntaxes.
Implementations of this interface MUST implement the necessary functions of their chosen base standard (e.g., ERC-20, ERC-721 and ERC-1155 functionalities) and MUST also restrict access to sensitive functions like forceTransfer and setFrozen using an appropriate access control mechanism (e.g., onlyOwner, Role-Based Access Control). The specific mechanism is NOT mandated by this interface standard.
Implementations MUST ensure their transfer methods exhibit the following behavior:
Public transfers (transfer, transferFrom, safeTransferFrom, etc.) MUST NOT succeed in cases in which isTransferAllowed or isUserAllowed would return false for either one or both from and to addresses.
Minting MUST NOT succeed to accounts where isUserAllowed would return false.
Burning SHOULD NOT be restricted by isTransferAllowed or isUserAllowed checks on the token holder. It MAY be restricted to prevent burning more assets than the unfrozen amount (e.g., in public burning functions). It MAY burn more assets than the unfrozen amount (e.g., in permissioned burning functions), in which case the contract MUST update the frozen status accordingly and emit a Frozen event before the underlying base token transfer event.
The ERC7943NotAllowedTransfer and ERC7943NotAllowedUser errors CAN be used as a general revert mechanism whenever internal calls to isUserAllowed or isTransferAllowed return false. Those MAY NOT be used or MAY be replaced by more specific errors depending on the custom checks performed inside those calls.
In general, the standard prioritizes error specificity, meaning that broad errors such as ERC7943NotAllowedTransfer SHOULD be thrown if more specific ones such as ERC7943InsufficientUnfrozenBalance do not apply. The same is true for ERC7943InsufficientUnfrozenBalance that SHOULD be triggered when a transfer is attempted from user with an amount of tokenId less than or equal to its balance, but greater than its unfrozen balance. But if the amount is greater than the whole balance, unrelated from the frozen amount, more specific errors from the base standard SHOULD be used instead.
Rationale
Minimalism: Defines only the essential functions (forceTransfer, setFrozen, isUserAllowed, isTransferAllowed, getFrozen) and associated events/errors needed for common RWA compliance and control patterns, avoiding mandated complexity or opinionated features. The reason to introduce specific errors (ERC7943NotAllowedUser, ERC7943NotAllowedTransfer and ERC7943InsufficientUnfrozenBalance) is to provide completeness with the introduced functionalities (isUserAllowed, isTransferAllowed and getFrozen). As dictated in the specifications, error specificity is prioritized, leaving space for implementations to accommodate more explicit errors. Regarding the events Frozen and ForcedTransfer, the reason for their existence is to signal uncommon transfers (like in forceTransfer) but also to help off-chain indexers to correctly keep track and account for asset seizures and freezing. As mentioned in the specifications, special attention should be paid to the order in which these events are emitted in relation to the base token contract events.
Flexible compliance: Provides standard view functions (isUserAllowed, isTransferAllowed, getFrozen) for compliance checks without dictating how those checks are implemented internally by the token contract. This allows diverse compliance strategies.
Compatibility: Designed as an interface layer compatible with existing base standards like ERC-20, ERC-721 and ERC-1155. Implementations extend from ERC-7943 alongside their base standard interface.
Essential enforcement rules: Includes forceTransfer and setFrozen as standard functions, acknowledging its importance for regulatory enforcement in the RWA space, distinct from standard transfers. Mandates access control for this sensitive function.
ERC-165: Ensures implementing contracts can signal support for this interface.
As an example, an AMM pool or a lending protocol can integrate with ERC-7943 based ERC-20 tokens by calling isUserAllowed or isTransferAllowed to handle these assets in a compliant manner. Enforcement actions like forceTransfer and setFrozen can either be called by third party entities or be integrated by external protocols to allow for automated and programmable compliance. Users can then expand these tokens with additional features to fit the specific needs of individual asset types, either with on-chain identity systems, historical balances tracking for dividend distributions, semi-fungibility with token metadata, and other custom functionalities.
Notes on naming:
forceTransfer: This term was selected for its neutrality. While names like “confiscation,” “revocation,” or “recovery” describe specific motivations, forceTransfer purely denotes the direct action of transferring assets, irrespective of the underlying reason.
setFrozen / getFrozen: These names were chosen for managing transfer restrictions.
Consolidated Approach: To maintain a lean EIP, a single setFrozen function (which overwrites the frozen asset quantity) and one Frozen event were favored over distinct freeze/unfreeze functions and events.
Terminology: “Frozen” was selected for its general applicability to both fungible (amount-based) and non-fungible (status-based) assets, as terms like “amount” or “asset(s)” might not be universally fitting.
ERC7943InsufficientUnfrozenBalance: Discussions around “insufficient” being similar to “unavailable” arose, where “unavailable” might have suggested better a temporal condition like a freezing status. However the term “available”/ “unavailable” was also overlapping with “frozen” / “unfrozen” creating more confusion and duality. Finally, coupling “insufficient” with the specified “unfrozen balance” better represents the domain, prefix and subject of the error, according to ERC-6093 guidelines.
Backwards Compatibility
This EIP defines a new interface standard and does not alter existing ones like ERC-20, ERC-721 and ERC-1155. Standard wallets and explorers can interact with the base token functionality of implementing contracts, subject to the rules enforced by that contract’s implementation of isUserAllowed, isTransferAllowed and getFrozen functions. Full support for the ERC-7943 functions requires explicit integration.
Reference Implementation
Examples of basic implementation for ERC-20, ERC-721 and ERC-1155 which include a basic whitelist for users and an enumerable role based access control:
pragmasolidity^0.8.29;/* required imports ... */contractuRWA20isContext,ERC20,AccessControlEnumerable,IERC7943{bytes32publicconstantMINTER_ROLE=keccak256("MINTER_ROLE");bytes32publicconstantBURNER_ROLE=keccak256("BURNER_ROLE");bytes32publicconstantENFORCER_ROLE=keccak256("ENFORCER_ROLE");bytes32publicconstantWHITELIST_ROLE=keccak256("WHITELIST_ROLE");mapping(addressuser=>boolwhitelisted)publicisWhitelisted;mapping(addressuser=>uint256amount)internal_frozenTokens;eventWhitelisted(addressindexedaccount,boolstatus);errorNotZeroAddress();constructor(stringmemoryname,stringmemorysymbol,addressinitialAdmin)ERC20(name,symbol){/* give initialAdmin necessary roles ...*/}functionisTransferAllowed(addressfrom,addressto,uint256,uint256amount)publicvirtualviewreturns(boolallowed){if(amount>balanceOf(from)-_frozenTokens[from])return;if(!isUserAllowed(from)||!isUserAllowed(to))return;allowed=true;}functionisUserAllowed(addressuser)publicvirtualviewreturns(boolallowed){if(isWhitelisted[user])allowed=true;}functiongetFrozen(addressuser,uint256)externalviewreturns(uint256amount){amount=_frozenTokens[user];}functionchangeWhitelist(addressaccount,boolstatus)externalonlyRole(WHITELIST_ROLE){require(account!=address(0),NotZeroAddress());isWhitelisted[account]=status;emitWhitelisted(account,status);}/* standard mint and burn functions with access control ...*/functionsetFrozen(addressuser,uint256,uint256amount)publiconlyRole(ENFORCER_ROLE){require(amount<=balanceOf(user),IERC20Errors.ERC20InsufficientBalance(user,balanceOf(user),amount));_frozenTokens[user]=amount;emitFrozen(user,0,amount);}functionforceTransfer(addressfrom,addressto,uint256,uint256amount)publiconlyRole(ENFORCER_ROLE){require(isUserAllowed(to),ERC7943NotAllowedUser(to));_excessFrozenUpdate(from,amount);super._update(from,to,amount);emitForcedTransfer(from,to,0,amount);}function_excessFrozenUpdate(addressuser,uint256amount)internal{uint256unfrozenBalance=balanceOf(user)-_frozenTokens[user];if(amount>unfrozenBalance&&amount<=balanceOf(user)){// Protect from underflow: if amount > balanceOf(user) the call will revert in super._update with insufficient balance error
_frozenTokens[user]-=amount-unfrozenBalance;// Reduce by excess amount
emitFrozen(user,0,_frozenTokens[user]);}}function_update(addressfrom,addressto,uint256amount)internalvirtualoverride{if(from!=address(0)&&to!=address(0)){// Transfer
require(amount<=balanceOf(from),IERC20Errors.ERC20InsufficientBalance(from,balanceOf(from),amount));require(amount<=balanceOf(from)-_frozenTokens[from],ERC7943InsufficientUnfrozenBalance(from,0,amount,balanceOf(from)-_frozenTokens[from]));require(isTransferAllowed(from,to,0,amount),ERC7943NotAllowedTransfer(from,to,0,amount));}elseif(from==address(0)){// Mint
require(isUserAllowed(to),ERC7943NotAllowedUser(to));}else{// Burn
_excessFrozenUpdate(from,amount);}super._update(from,to,amount);}functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverride(AccessControlEnumerable,IERC165)returns(bool){returninterfaceId==type(IERC7943).interfaceId||interfaceId==type(IERC20).interfaceId||super.supportsInterface(interfaceId);}}
pragmasolidity^0.8.29;/* required imports ... */contractuRWA721isContext,ERC721,AccessControlEnumerable,IERC7943{/* same definitions, constructor and changeWhitelist function as before ...*/mapping(addressuser=>mapping(uint256tokenId=>uint8frozen))internal_frozenTokens;functionisUserAllowed(addressuser)publicviewvirtualoverridereturns(boolallowed){if(isWhitelisted[user])allowed=true;}functionisTransferAllowed(addressfrom,addressto,uint256tokenId,uint256)publicviewvirtualoverridereturns(boolallowed){addressowner=_ownerOf(tokenId);if(owner!=from||owner==address(0))return;if(!isUserAllowed(from)||!isUserAllowed(to))return;if(_frozenTokens[from][tokenId]>0)return;allowed=true;}functiongetFrozen(addressuser,uint256tokenId)externalviewreturns(uint256amount){amount=_frozenTokens[user][tokenId];}functionsetFrozen(addressuser,uint256tokenId,uint256amount)publiconlyRole(ENFORCER_ROLE){require(user==ownerOf(tokenId),IERC721Errors.ERC721InvalidOwner(user));require(amount==0||amount==1,InvalidAmount(amount));_frozenTokens[user][tokenId]=uint8(amount);emitFrozen(user,tokenId,amount);}functionforceTransfer(addressfrom,addressto,uint256tokenId,uint256)publicvirtualoverrideonlyRole(ENFORCER_ROLE){require(to!=address(0),ERC721InvalidReceiver(address(0)));require(isUserAllowed(to),ERC7943NotAllowedUser(to));_excessFrozenUpdate(from,tokenId);super._update(to,tokenId,address(0));// Skip _update override
ERC721Utils.checkOnERC721Received(_msgSender(),from,to,tokenId,"");emitForcedTransfer(from,to,tokenId,1);}function_excessFrozenUpdate(addressfrom,uint256tokenId)internal{_validateCorrectOwner(from,tokenId);if(_frozenTokens[from][tokenId]>0){_frozenTokens[from][tokenId]=0;// Unfreeze the token if it was frozen
emitFrozen(from,tokenId,0);}}function_validateCorrectOwner(addressclaimant,uint256tokenId)internalview{addresscurrentOwner=ownerOf(tokenId);require(currentOwner==claimant,ERC721IncorrectOwner(claimant,tokenId,currentOwner));}/* standard mint function with access control ...*/functionburn(uint256tokenId)externalvirtualonlyRole(BURNER_ROLE){addresspreviousOwner=_update(address(0),tokenId,_msgSender());if(previousOwner==address(0))revertERC721NonexistentToken(tokenId);}function_update(addressto,uint256tokenId,addressauth)internalvirtualoverridereturns(address){addressfrom=_ownerOf(tokenId);if(auth!=address(0)){_checkAuthorized(from,auth,tokenId);}if(from!=address(0)&&to!=address(0)){// Transfer
_validateCorrectOwner(from,tokenId);require(_frozenTokens[from][tokenId]==0,ERC7943InsufficientUnfrozenBalance(from,tokenId,1,0));require(isTransferAllowed(from,to,tokenId,1),ERC7943NotAllowedTransfer(from,to,tokenId,1));}elseif(from==address(0)){// Mint
require(isUserAllowed(to),ERC7943NotAllowedUser(to));}else{// Burn
_excessFrozenUpdate(from,tokenId);}returnsuper._update(to,tokenId,auth);}functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverride(AccessControlEnumerable,ERC721,IERC165)returns(bool){returninterfaceId==type(IERC7943).interfaceId||super.supportsInterface(interfaceId);}}
pragmasolidity^0.8.29;/* required imports ... */contractuRWA1155isContext,ERC1155,AccessControlEnumerable,IERC7943{/* same definitions, constructor and changeWhitelist function as before ...*/mapping(addressuser=>mapping(uint256tokenId=>uint256amount))internal_frozenTokens;functionisTransferAllowed(addressfrom,addressto,uint256tokenId,uint256amount)publicviewvirtualoverridereturns(boolallowed){if(balanceOf(from,tokenId)<amount)return;if(!isUserAllowed(from)||!isUserAllowed(to))return;if(amount>balanceOf(from,tokenId)-_frozenTokens[from][tokenId])return;allowed=true;}functionisUserAllowed(addressuser)publicviewvirtualoverridereturns(boolallowed){if(isWhitelisted[user])allowed=true;}functiongetFrozen(addressuser,uint256tokenId)externalviewreturns(uint256amount){amount=_frozenTokens[user][tokenId];}functionsetFrozen(addressuser,uint256tokenId,uint256amount)publiconlyRole(ENFORCER_ROLE){require(amount<=balanceOf(user,tokenId),ERC1155InsufficientBalance(user,balanceOf(user,tokenId),amount,tokenId));_frozenTokens[user][tokenId]=amount;emitFrozen(user,tokenId,amount);}functionforceTransfer(addressfrom,addressto,uint256tokenId,uint256amount)publiconlyRole(ENFORCER_ROLE){require(isUserAllowed(to),ERC7943NotAllowedUser(to));// Reimplementing _safeTransferFrom to avoid the check on _update
if(to==address(0)){revertERC1155InvalidReceiver(address(0));}if(from==address(0)){revertERC1155InvalidSender(address(0));}_excessFrozenUpdate(from,tokenId,amount);uint256[]memoryids=newuint256[](1);uint256[]memoryvalues=newuint256[](1);ids[0]=tokenId;values[0]=amount;super._update(from,to,ids,values);if(to!=address(0)){addressoperator=_msgSender();if(ids.length==1){uint256id=ids[0];uint256value=values[0];ERC1155Utils.checkOnERC1155Received(operator,from,to,id,value,"");}else{ERC1155Utils.checkOnERC1155BatchReceived(operator,from,to,ids,values,"");}}emitForcedTransfer(from,to,tokenId,amount);}function_excessFrozenUpdate(addressuser,uint256tokenId,uint256amount)internal{uint256unfrozenBalance=balanceOf(user,tokenId)-_frozenTokens[user][tokenId];if(amount>unfrozenBalance&&amount<=balanceOf(user,tokenId)){// Protect from underflow: if amount > balanceOf(user) the call will revert in super._update with insufficient balance error
_frozenTokens[user][tokenId]-=amount-unfrozenBalance;// Reduce by excess amount
emitFrozen(user,tokenId,_frozenTokens[user][tokenId]);}}/* standard mint and burn functions with access control ...*/function_update(addressfrom,addressto,uint256[]memoryids,uint256[]memoryvalues)internalvirtualoverride{if(ids.length!=values.length){revertERC1155InvalidArrayLength(ids.length,values.length);}if(from!=address(0)&&to!=address(0)){// Transfer
for(uint256i=0;i<ids.length;++i){uint256id=ids[i];uint256value=values[i];uint256unfrozenBalance=balanceOf(from,id)-_frozenTokens[from][id];require(value<=balanceOf(from,id),ERC1155InsufficientBalance(from,balanceOf(from,id),value,id));require(value<=unfrozenBalance,ERC7943InsufficientUnfrozenBalance(from,id,value,unfrozenBalance));require(isTransferAllowed(from,to,id,value),ERC7943NotAllowedTransfer(from,to,id,value));}}if(from==address(0)){// Mint
require(isUserAllowed(to),ERC7943NotAllowedUser(to));}elseif(to==address(0)){// Burn
for(uint256j=0;j<ids.length;++j){_excessFrozenUpdate(from,ids[j],values[j]);}}super._update(from,to,ids,values);}functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverride(AccessControlEnumerable,ERC1155,IERC165)returns(bool){returninterfaceId==type(IERC7943).interfaceId||super.supportsInterface(interfaceId);}}
Security Considerations
Access Control for forceTransfer and setFrozen: The security of the mechanism chosen by the implementer to restrict access to these functions is paramount. Unauthorized access could lead to asset theft. Secure patterns (multisig, timelocks) are highly recommended. The setFrozen function might be susceptible to front-running, similar to the approve function of the ERC-20. Additional features to gradually increment or decrement the frozen status CAN be considered for implementation.
Implementation Logic: The correctness of the implementation behind all interface functions is critical. Flaws in this logic could bypass intended transfer restrictions or incorrectly block valid transfers.
Standard Contract Security: Implementations MUST adhere to general smart contract security best practices (reentrancy guards where applicable, checks-effects-interactions, etc.). Specifically in the checks-effects-interactions consideration, implementations need to be aware of tokens having hooks, especially on recipients like in ERC-721 or ERC-1155. In such circumstances it might be convenient to adopt reentrancy guards to prevent unwanted executions.