Skip to main content

RWAToken

RWAToken.sol is an ERC-20 token representing tokenized Real-World Assets. It implements ERC-7943 (uRWA) — an on-chain compliance extension that enforces an account allowlist, supports token freezing, and enables regulatory enforcement transfers — all without breaking ERC-20 compatibility.

Source: contracts/core/RWAToken.sol.
ERC-7943 interface ID: 0x29388973


Roles

Rolekeccak256 constantWho holds itWhat it allows
ADMIN_ROLEkeccak256("ADMIN_ROLE")Oracle / adminforcedTransfer, config changes, upgrade
MINTER_ROLEkeccak256("MINTER_ROLE")Oracle (post-governance)mint
BURNER_ROLEkeccak256("BURNER_ROLE")AdminAdmin burn (bypass compliance)
COMPLIANCE_ROLEkeccak256("COMPLIANCE_ROLE")Compliance officersetFrozenTokens
ROLE_MANAGERkeccak256("ROLE_MANAGER")AdminGrant/revoke MINTER, BURNER, COMPLIANCE

ERC-7943 Overview

ERC-7943 extends ERC-20 with three mandatory additions:

AdditionDescription
canTransact(address)Returns true if an account is allowed to send or receive tokens
canTransfer(address from, address to, uint256 amount)Returns true if a specific transfer is permitted (checks both parties and frozen balance)
setFrozenTokens(address, uint256)Sets the absolute frozen (untransferable) balance for an account — COMPLIANCE_ROLE only
getFrozenTokens(address)Returns the frozen balance for an account
forcedTransfer(address from, address to, uint256 amount)Regulatory enforcement transfer — bypasses canTransfer, ADMIN_ROLE only

All normal ERC-20 transfers (transfer, transferFrom) are gated through _update, which enforces ERC-7943 restrictions automatically.


Allowlist: canTransact

function canTransact(address account) public view returns (bool)

Delegates to AccountManager.isAccountActive(account) and checks !isAccountSuspended(account). Returns true unconditionally when accountManager == address(0) (open mode — used during testnet setup).

Mint — the recipient must pass canTransact.
Transfer — both sender and receiver must pass canTransact.
Burn (retire) — the caller must pass canTransact.
Admin burn / forcedTransfer — bypass compliance entirely via _bypassCompliance flag.


Token Freezing

function setFrozenTokens(address account, uint256 amount)
external onlyRole(COMPLIANCE_ROLE) returns (bool)

Sets an absolute frozen token count. If getFrozenTokens[account] == 500 and the account has 800 tokens, only 300 are transferable. The frozen amount can exceed the current balance — the constraint applies at transfer time.

canTransfer(from, to, amount) returns false when unfrozenBalance < amount.

Emits the ERC-7943 Frozen(account, newAmount) event.


forcedTransfer

function forcedTransfer(address from, address to, uint256 amount)
external onlyRole(ADMIN_ROLE) nonReentrant returns (bool)

Regulatory enforcement transfer. Bypasses canTransfer entirely. If the source account has frozen tokens, they are unfrozen by amount first (emits Frozen). Then transfers via _transfer with _bypassCompliance = true. Emits ForcedTransfer(from, to, amount).

Use cases: court-ordered transfers, compliance seizure, correction of erroneous mints.


Minting

function mint(
address _to,
uint256 _amount,
string memory _certificateSerial,
uint8 _assetType,
uint8 _qualityScore
) external onlyRole(MINTER_ROLE) nonReentrant returns (uint256 mintId)

Called by the oracle for each distribution recipient after MultiSigGovernance.executeProposal() succeeds. Requirements:

  • _to must pass canTransact (enforced via _update).
  • _qualityScore > 0 — quality score must be set before minting.

Records a TokenMetadata entry keyed by mintId. Increments totalSupplyByAssetType[_assetType]. Returns the mintId for future reference.


Retirement

function retire(uint256 _amount, uint256 _mintId) external nonReentrant

Permanently burns tokens from the caller's balance. ERC-7943 rules apply: caller must pass canTransact and the amount must not exceed the unfrozen balance. mintId identifies the backing certificate batch (does not restrict which tokens are burned — the entire pool is fungible).

Increments totalRetired and totalRetiredByAssetType. Sets tokenMetadata[_mintId].isRetired = true. Emits TokensRetired.


TokenMetadata Struct

struct TokenMetadata {
string certificateSerial; // Backing certificate
uint8 assetType; // Asset type constant (0–9, 255)
uint256 mintedAmount; // Tokens minted in this batch
uint256 mintedAt; // Unix timestamp
uint8 qualityScore; // Quality score at time of minting
bool isRetired; // True after retire() is called
}

Accessible via getTokenMetadata(mintId).


View Functions

FunctionReturns
canTransact(address)Whether account can send/receive
canTransfer(from, to, amount)Whether a specific transfer is allowed
getFrozenTokens(address)Absolute frozen balance
circulatingSupply()totalSupply() - totalRetired
circulatingSupplyByAssetType(assetType)Circulating supply for one asset type
getTotalSupplyByAssetType(assetType)Cumulative minted (includes retired)
getTotalRetiredByAssetType(assetType)Total retired for one asset type
getCertificateMintedAmount(serial)Total tokens minted for a certificate
getTokenMetadata(mintId)Full TokenMetadata struct
supportsInterface(0x29388973)Returns true — ERC-7943 compliance

Events

EventWhen emitted
TokensMinted(to, amount, certificateSerial, mintId, assetType, qualityScore)mint
TokensRetired(from, amount, mintId, assetType)retire
Frozen(account, newAmount)setFrozenTokens, forcedTransfer
ForcedTransfer(from, to, amount)forcedTransfer
AssetRegistrySet(registry)setAssetRegistry
GovernanceSet(governance)setGovernance
AccountManagerSet(accountManager)setAccountManager

ERC-7943 Custom Errors

ErrorWhen thrown
ERC7943CannotTransact(account)Transfer attempted by/to a non-allowlisted account
ERC7943InsufficientUnfrozenBalance(account, required, available)Transfer amount exceeds unfrozen balance

Open Mode vs. Allowlist Mode

SettingaccountManagerBehavior
Open modeaddress(0)All accounts may transact — used on testnet before AccountManager is set
Allowlist modeAccountManager addressOnly ACTIVE accounts may transact; SUSPENDED accounts are blocked

Switch between modes with setAccountManager(address) (ADMIN_ROLE). Passing address(0) disables the allowlist.


UUPS Upgrade

_authorizeUpgrade requires ADMIN_ROLE.