ERC-7943: Universal RWA Interface (uRWA)

This proposal introduces a minimal, standardized interface for Real World Asset (RWA) tokenization that is designed to be maximally compatible across existing token standards such as ERC-20, ERC-721, and ERC-1155. It focuses on providing only the essential compliance and enforcement functions common to regulated assets, without imposing specific implementation patterns or additional optional features.

Non-essential capabilities like pausing, metadata handling, or identity integrations are intentionally excluded from the standard, as they tend to be opinionated and vary greatly depending on the specific RWA use case. This approach ensures broad interoperability and minimal friction for adoption while allowing developers to extend functionality as needed within their own contracts.

7 Likes

A very basic reference implementation can be found at

1 Like

Thanks for the comment @magicians

I fully agree that RWA is a too broad spectrum.

A key challenge is that each type of RWA possesses unique characteristics that necessitate specific on-chain capabilities for proper representation. Consider tokenized shares: they require features like dividend distribution, which implies a need for the token contract to track historical balances. Additionally, shares are often non-fractional, a property not natively supported by a pure ERC-20 without custom additions. Carbon credits, by contrast, have different needs, such as robust metadata handling to accurately reflect emissions data and manage redemption. Varying regulatory requirements further differentiate what’s needed for compliance across different asset classes.

This analysis of diverse asset requirements, coupled with a review of current RWA tokenization standards, strongly suggested me that a monolithic approach is unsuitable. Instead, a modular and flexible architecture is paramount – one that identifies and focuses on the minimal common base layer required by all RWAs, allowing specific features and compliance modules to be built flexibly on top.

Curious to know your thoughts on the specific of the proposed interface

1 Like

First of all, great job on the proposal, it adds great value to make it token-agnostic.

I think it’s difficult to select which permissioning features to include and which to intentionally exclude from the standard.

If we’re aiming for a truly minimal implementation, one could argue that some parts can be omitted, as it could be reduced to a transfer-check standard. Similar to ERC-902 and ERC-1462.

  1. Exclude recall, and achieve the functionality through mint and burn. Specificity and simplicity are lost.
  2. Exclude isUserAllowed; the same functionality is achieved with isTransferAllowed. Error-handling specificity is lost.

I then believe there are two possible paths for this standard:

  1. Universal token transfer check, non-opinionated on the admin or functionality standpoint. Additional ERCs can be built on top to standardize whitelisting, pausing/unpausing, access-control, or other compliance mechanisms.
  2. Universal RWA token, with core token functionality embedded: forceTransfer/recall, freezeTokens, frozenBalance. Compliance functionality should be built on top, similarly to the previous option.

I believe a security/RWA/permissioned token standard should be opinionated on the core token functionality.

I propose the following changes:

  1. Add mint and burn methods to the interface. Specify that isUserAllowed(to) must be run on mint, and that isUserAllowed(from) must not be run on burn—T-REX-like.
  2. Specify that isUserAllowed(to) must be run on recall and that isUserAllowed(from) must not be run on recall.
  3. Add freezeTokens, unfreezeTokens, and frozenBalance methods, plus TokensFrozen and TokensUnfrozen events. In my experience, token freezing is always required by regulators. It can be implemented as a separate module, but if we assume isUserAllowed provides the necessary specificity, token freezing should be required too. burn and recall should unfreeze tokens as needed.
  4. Prepend errors with an ERCX prefix, similar to ERC-6093.
  5. Rename recall to forceTransfer, and Recalled to ForcedTransfer. Recalled has a meaning of returning or withdrawing; I would stick to the core action and call it a forced transfer, regardless of the underlying reason it’s used. This is obviously a nit.
2 Likes

Thanks a lot @tinom9 for your comments ! Those are extremely valuable.

I think out of the two options we’re converging on the idea of an Universal RWA token standard that includes key token-level functionalities but that is also maximally compatible and easy to integrate. Here are my responses to your suggestions and a few clarifying questions to continue the conversation:


Mint and Burn

Your proposal to include mint and burn functions makes sense in most cases to me, especially in asset classes like equity or tokenized commodities. However, some RWA implementations, such as fixed-supply debt instrument, require immutable supply characteristics. In these cases, mint and burn may not be applicable at all, or maybe only one of the two.

Would you consider making mint and burn a SHOULD requirement in the EIP? This would maintain flexibility for fixed-supply cases while still encouraging standardization for the majority of use cases. Also, specifying that isUserAllowed(to) applies to mint and isUserAllowed(from) is exempt for burn makes sense, will integrate this into the description.


Error Prefixes (ERCX)

I really liked your suggestion. I’ll go ahead and include it.


recall/forceTransfer Naming

I completely agree with renaming recall to forceTransfer. Other terms like confiscation, revocation, or recovery describe reasons, not the underlying action, which is indeed a forced movement of assets. But let’s consider also the next topic for the naming part.


Freeze Functionality

I’m on board with supporting freezing functionality, especially since regulatory requirements often mandate it. However, I’d like to clarify the functional distinction between freezeTokens and forceTransfer.

Would you say the difference is that:

  • freezeTokens keeps tokens in the wallet but locks them;
  • forceTransfer moves the tokens elsewhere?

If so, would it be reasonable to combine both functionalities into a single forceTransfer-like method using a flag (e.g., to address being same as from meaning a freeze or an extra parameter to differentiate between a freeze and a forcedTransfer) ?

(at this point I’d call this function more something like ā€œenforceā€ with no distinction whether is a forced transfer or a freeze).

Alternatively, do you think both freezing and forced movement are fundamentally distinct actions that regulators would want both to be present ? Is it one of the two more primitive than the other ?

Would love your thoughts on whether combining them makes sense or if we should define them separately.


Thanks again for the constructive discussion!

burn

I don’t see a use case where the burn method wouldn’t be necessary for a security or RWA. Pessimistically, every asset should be liquidable at some point, and I only see that happening via the burn method.

For me, if included, it should be a MUST .


mint

Tokens must be minted at some stage. I think there’s a slight trade-off between minting flexibility (ERC20‑style) and standardization (a defined interface).

For me, if included, it should be a MUST , with a MAY revert for custom logic (e.g., when the max supply is reached).


freezeTokens / forceTransfer

I think forceTransfer main serves for court‑ordered or regulatory transfers—inheritances, seizures, etc.—so there’s almost always a recipient for the assets. It can also serve as a general administrative transfer function, and I don’t see any reason to discourage that use.

On the other hand, I see the freezeTokens method used to immobilize assets, during bankruptcy or insolvency proceedings, temporary protective orders, partial claims, etc., with the intention of either unfreezing or forcefully transferring them later.

I believe the debate isn’t so much about merging these functionalities as it is about whether freezing tokens is core enough to be part of the ERC interface. It could be implemented via transfer restrictions—tracking frozen balances in parallel and returning isTransferAllowed = false if the transfer exceeds the unfrozen balance.

I’d prefer built‑in freezing functionality in the standard, as I’ve found regulators in Europe consistently requiring it. However, I could also see the case for a separate freezing module, maybe a different ERC, if making this one leaner is a priority.


Great job, again!

1 Like

Thanks @tinom9 for the follow up !

I’ve done the following changes:

  • Changed errors names to adapt to ERC-6093.
  • Renamed recall to forceTransfer and Recalled to ForcedTransfer.
  • Addressed your feedback on the implementations and examples.

I’ve also adjusted the statements in what integrators should do for transfers,minting and burning. However I still didn’t include the mint and burn functions for the arguments below.

Been thinking about it for the last two days and slightly discussed this also with @frangio and here some argument that I think still holds for not including those functions within this standard:

  • Minting works differently for ERC-20 vs ERC-721/ERC-1155 in the sense that the former doesn’t perform checks on the recipient while the latter usually do.
  • Given the different needed parameters (ie amounts and/or tokenIds), building up a standard function interface for all three kind of tokens might sound as redefining how a minting function should look like for an ERC-20 or an ERC-721.
  • As previously mentioned, fixed-supply debt might represent an use case where an external or public minting function is not needed, and the supply is determined at constructor time. Additionally, liquidating an RWA doesn’t necessarily imply burning them, as secondary market can make them tradeable against stables or other RWAs. Additionally burning might even be prohibited and not necessary for a use case (ie, is it legit to burn tokenized shares ?)

All of this makes me think that a mint and burn function for regulated assets in the form of ERC-20 / ERC-721 / ERC-1155 might be the scope for another standard and might not be extremely necessary to include it here.

Happy to know both of your thoughts @frangio @tinom9

2 Likes

Thanks again @xaler!


mint / burn

In terms of mint and burn, I stand on burning, theoretically, being needed in every asset, as any could be liquidated (shares rebuying, company closure/bankruptcy).

Minting should also happen at some point of the lifecycle and I see a point on enforcing fixed-supply through reverts, as opposed to only through constructor mints.

Nonetheless, I believe there are obvious trade-offs for multi-standard token compatibility that, even if up to some extent are implied in this ERC (already using tokenId and amount), as it aims to be universal, can entangle the implementation.

I am onboard with excluding them to keep the standard simple.


freezeTokens

If we exclude mint and burn, I think there is a stronger point to consider excluding freezing functionality, in favor of a leaner ERC.

Nonetheless, it’s still an inclusion for me, and I’d be more than happy to propose an implementation in the Github PR, if you’re up to it.


Universality

Making this standard universal is a challenge per se, and I see two paths upcoming for common functionality:

  1. Different tokens using different interfaces for common logic:
    • mint(address to, uint256 amount)
    • mint(address to, uint256 tokenId)
    • mint(address to, uint256 tokenId, uint256 amount)
  2. Different tokens converging to a common interface:
    • mint(address to, uint256 tokenId, uint256 amount)

Only if we expect tokens converging to a common interface, it makes sense to standardize it. And, in that case, although I believe it’s marginally better to include it in the ERC, it’s still valid to standardize it in a different ERC. This applies to both points above.


Cheers!

1 Like

Thank you for putting forward ERC-7943. The interface strikes a strong balance between minimalism and practical utility, particularly for projects focused on real-world asset tokenization. As someone actively deploying tokenized equity, debt, and revenue-sharing instruments across multiple jurisdictions, I believe there is value in introducing an optional extension to the standard that includes mint, burn, and freeze primitives. These should not be understood as business logic enforcers, but as standardized interfaces to reflect legally binding events on-chain.

Minting in RWA environments is not merely the technical creation of tokens. It often corresponds to a capital increase, a debt issuance, or the execution of a convertible instrument. A function such as mintByAuthority(address to, uint256 amount, bytes calldata legalProof) would enable implementations to link the minting process to a legal resolution or authorizing event, such as a shareholder vote or board approval, without enforcing off-chain dependencies. We have also explored additional interface primitives related to governance and legal signaling, which may be suitable for standardization. If this initial contribution is aligned with the direction of the group, we would be happy to share those in a follow-up.

Burning is likewise tied to legally significant actions such as debt repayment, share redemption, or cancellation resulting from a court order. It also applies to cases of company liquidation, where all equity must be extinguished, and corporate acquisitions, where equity is either cancelled for cash or converted into the acquiring company’s equity. In a tokenized environment, this can involve burning the target company’s tokens and minting new ones representing the acquiring entity’s shares. A function such as burnByAuthority(address from, uint256 amount, bytes calldata justification) would provide a structured and auditable way to perform these actions, with the justification field preserving legal traceability.

Freezing is a recurring requirement in legal and compliance operations. It is used to enforce lock-up agreements, address AML/KYC issues, or comply with legal injunctions. A minimal implementation such as freeze(address user, uint256 amount, uint256 id), to allow for ERC-20 and ERC-1155 granularity, together with frozen(address user) would provide sufficient flexibility for most real-world use cases without dictating internal enforcement mechanisms.

These extensions maintain the neutral and portable nature of ERC-7943. They remain fully optional and non-intrusive for implementers who do not require them. But for those operating under legal, regulatory, or governance obligations, these standardized hooks offer a necessary foundation for compliant and auditable asset management without compromising the core specification.

1 Like

Thanks @EdwinMata and @tinom9 for your comments.

After thinking a lot about it I think it makes sense to include a freezing/unfreezing functionality.

The main objective of this EIP is to be universal, maximally compatible and future proof. For this to me it must:

  • Be compatible with major token standards
  • Include only the essential and common features of a broad spectrum of use cases
  • Not opinionate on implementation details

Specifically on the second point, I strongly believe that compliance and enforcement actions are the very backbone of RWAs. In this regard isUserAllowed and isTransferAllowed meet the need to define compliance criteria while forceTransfer is an enforcement action. Freezing is another enforcement action that is quite different even if achieving similar results as forceTransfer.

Specifically, freezing is generally associated with temporary actions potentially meant to be reversible. Because of all of this my proposition is:

  • Freezing must not alter balanceOf / totalSupply results. Instead it must live in separate functions, parts of the standard.
  • There must be a way to freeze and unfreeze + a getter to know the freezing status of a specific account.
  • isTransferAllowed must consider frozen amounts in its internal logic
  • isUserAllowed might or might not take into account frozen assets in its internal logic
  • forceTransfer can transfer frozen assets.
  • Freeze and unfreeze have similar restricted access assumptions as forceTransfer

For this I propose adding the following to the interface

    /// @notice Emitted when a specific amount of a token ID is frozen for a 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.
    event Frozen(address indexed user, uint256 indexed tokenId, uint256 amount);

    /// @notice Emitted when a specific amount of a token ID is unfrozen for a user.
    /// @param user The address of the user whose tokens are being unfrozen.
    /// @param tokenId The ID of the token being unfrozen.
    /// @param amount The amount of tokens unfrozen.
    event Unfrozen(address indexed user, uint256 indexed tokenId, uint256 amount);

    /// @notice Freezes a specified amount of a token ID for a user.
    /// @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.
    /// @param tokenId The ID of the token to freeze. Use 0 for ERC-20.
    /// @param amount The amount of tokens to freeze. Use 1 for ERC-721.
    function freeze(address user, uint256 tokenId, uint256 amount) external;

    /// @notice Unfreezes a specified amount of a token ID for a user.
    /// @dev Requires specific authorization.
    /// @param user The address of the user whose tokens are to be unfrozen.
    /// @param tokenId The ID of the token to unfreeze. Use 0 for ERC-20.
    /// @param amount The amount of tokens to unfreeze. Use 1 for ERC-721.
    function unfreeze(address user, uint256 tokenId, uint256 amount) external;

    /// @notice Checks the amount of a specific token ID that is frozen for a user.
    /// @param user The address of the user.
    /// @param tokenId The ID of the token. Use 0 for ERC-20.
    /// @return frozenAmount The amount of the token ID currently frozen for the user.
    function frozenAmount(address user, uint256 tokenId) external view returns (uint256 frozenAmount);

I’m curious to know your thoughts on this, meanwhile I’ll start working to add it in the reference implementation and in the draft of the EIP