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
| Role | keccak256 constant | Who holds it | What it allows |
|---|---|---|---|
ADMIN_ROLE | keccak256("ADMIN_ROLE") | Oracle / admin | forcedTransfer, config changes, upgrade |
MINTER_ROLE | keccak256("MINTER_ROLE") | Oracle (post-governance) | mint |
BURNER_ROLE | keccak256("BURNER_ROLE") | Admin | Admin burn (bypass compliance) |
COMPLIANCE_ROLE | keccak256("COMPLIANCE_ROLE") | Compliance officer | setFrozenTokens |
ROLE_MANAGER | keccak256("ROLE_MANAGER") | Admin | Grant/revoke MINTER, BURNER, COMPLIANCE |
ERC-7943 Overview
ERC-7943 extends ERC-20 with three mandatory additions:
| Addition | Description |
|---|---|
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:
_tomust passcanTransact(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
| Function | Returns |
|---|---|
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
| Event | When 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
| Error | When 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
| Setting | accountManager | Behavior |
|---|---|---|
| Open mode | address(0) | All accounts may transact — used on testnet before AccountManager is set |
| Allowlist mode | AccountManager address | Only 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.