Web3 & Blockchain/DApps/RWA

RWA — Real World Assets

RWA (Real World Assets) is the tokenization of off-chain assets — real estate, bonds, commodities, private credit, invoices — bringing them on-chain as digital tokens. It's one of the fastest-growing sectors in DeFi.


What Gets Tokenized

Asset ClassExamplesChains
US TreasuriesOUSG (Ondo), USDY, BUIDL (BlackRock)Ethereum, Base, Solana
Private CreditMaple Finance, GoldfinchEthereum
Real EstateRealT, TangibleEthereum, Polygon
CommoditiesPAX Gold (PAXG), CACHE GoldEthereum
Carbon CreditsToucan Protocol, KlimaDAOPolygon
EquitiesBacked Finance, Swarm MarketsEthereum
InvoicesCentrifugeEthereum

How RWA Tokenization Works

Off-chain asset (e.g., US Treasury bond)
  → Legal entity holds the asset
  → Issues tokens representing ownership/claim
  → Token deployed on-chain (usually ERC-20 or ERC-1400)
  → KYC/AML enforced via whitelist or permissioned transfers
  → Yield/distributions sent on-chain periodically

The legal structure varies — some are direct ownership, some are debt claims, some are yield-bearing wrappers.


Key Standards

ERC-1400 (Security Token Standard)

Extends ERC-20 with compliance features: transfer restrictions, forced transfers (regulatory), partitions (tranches).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract RWAToken is ERC20, AccessControl {
    bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
    bytes32 public constant ISSUER_ROLE = keccak256("ISSUER_ROLE");

    // Whitelist of KYC-verified addresses
    mapping(address => bool) public whitelist;

    event WhitelistUpdated(address indexed account, bool status);

    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(COMPLIANCE_ROLE, msg.sender);
        _grantRole(ISSUER_ROLE, msg.sender);
    }

    // Restricted transfer — only whitelisted addresses
    function _update(address from, address to, uint256 amount) internal override {
        if (from != address(0) && to != address(0)) {
            require(whitelist[from] && whitelist[to], "Transfer: address not whitelisted");
        }
        super._update(from, to, amount);
    }

    // Compliance officer can add/remove from whitelist
    function setWhitelist(address account, bool status) external onlyRole(COMPLIANCE_ROLE) {
        whitelist[account] = status;
        emit WhitelistUpdated(account, status);
    }

    // Issuer mints tokens (represents issuance of underlying asset)
    function issue(address to, uint256 amount) external onlyRole(ISSUER_ROLE) {
        require(whitelist[to], "Recipient not whitelisted");
        _mint(to, amount);
    }

    // Compliance can force-transfer (regulatory requirement, e.g., court order)
    function forceTransfer(address from, address to, uint256 amount)
        external onlyRole(COMPLIANCE_ROLE)
    {
        require(whitelist[to], "Recipient not whitelisted");
        _transfer(from, to, amount);
    }

    // Redeem (burn) tokens when underlying asset is redeemed
    function redeem(uint256 amount) external {
        require(whitelist[msg.sender], "Not whitelisted");
        _burn(msg.sender, amount);
    }
}

ERC-3643 (T-REX — Token for Regulated EXchanges)

The more complete standard used by institutional RWA protocols. Separates identity verification into an external identity registry.

Components:
  Token Contract       ← ERC-20 + transfer restrictions
  Identity Registry    ← Maps wallet → verified identity
  Compliance Module    ← Pluggable rules (country whitelist, max holders, etc.)
  Trusted Issuers      ← Which KYC providers are accepted

Yield-Bearing RWA Token

This pattern mints tokens that accrue yield over time (like a rebasing stablecoin backed by T-Bills):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract YieldRWA is ERC20, Ownable {
    uint256 public constant DENOMINATOR = 1e18;
    uint256 public index = 1e18; // starts at 1:1
    mapping(address => uint256) private _shares;
    uint256 private _totalShares;
    mapping(address => bool) public whitelist;

    constructor() ERC20("Yield RWA", "yRWA") Ownable(msg.sender) {}

    // Owner updates the yield index (called periodically by oracle/admin)
    function updateIndex(uint256 newIndex) external onlyOwner {
        require(newIndex > index, "Index can only increase");
        index = newIndex;
    }

    function mint(address to, uint256 amount) external onlyOwner {
        require(whitelist[to], "Not whitelisted");
        uint256 shares = (amount * DENOMINATOR) / index;
        _shares[to] += shares;
        _totalShares += shares;
        emit Transfer(address(0), to, amount);
    }

    function balanceOf(address account) public view override returns (uint256) {
        return (_shares[account] * index) / DENOMINATOR;
    }

    function totalSupply() public view override returns (uint256) {
        return (_totalShares * index) / DENOMINATOR;
    }

    function setWhitelist(address account, bool status) external onlyOwner {
        whitelist[account] = status;
    }
}

Connect to Existing RWA Protocols

Ondo Finance (OUSG — tokenized US Treasuries)

const OUSG_ADDRESS = "0x1B19C19393e2d034D8Ff31ff34c81252FcBbee92"; // Mainnet
const OUSG_ABI = [
  "function balanceOf(address) view returns (uint256)",
  "function subscribe(uint256 usdcAmount) external",   // deposit USDC, get OUSG
  "function requestRedemption(uint256 ousgAmount) external", // redeem OUSG for USDC
];

const ousg = new ethers.Contract(OUSG_ADDRESS, OUSG_ABI, provider);
const balance = await ousg.balanceOf(userAddress);
console.log("OUSG balance:", ethers.formatEther(balance));

Centrifuge (RWA lending pools)

// Centrifuge Tinlake Pool
const POOL_ABI = [
  "function seniorTrancheTokenSupply() view returns (uint256)",
  "function juniorTrancheTokenSupply() view returns (uint256)",
  "function seniorInterestRate() view returns (uint256)",
];

const pool = new ethers.Contract(POOL_ADDRESS, POOL_ABI, provider);
const [seniorSupply, rate] = await Promise.all([
  pool.seniorTrancheTokenSupply(),
  pool.seniorInterestRate(),
]);
console.log("Senior tranche:", ethers.formatEther(seniorSupply), "DAI");
console.log("APY:", (Number(rate) / 1e27 - 1) * 365 * 100, "%"); // ray math

KYC / Identity Integration

Most RWA tokens require KYC. Common approaches:

ProviderHow It Works
SynapsKYC widget → verification status stored off-chain, address whitelisted on-chain
Fractal IDIssues on-chain credential → contract checks credential validity
CivicOn-chain pass (NFT) → your contract checks if user holds a valid pass
WorldcoinProof of personhood — verifies unique human, not full KYC

Civic Pass example

import "@civic/ethereum-gateway/contracts/IGatewayTokenVerifier.sol";

contract KYCGatedToken is ERC20 {
    IGatewayTokenVerifier public immutable gatewayVerifier;
    bytes32 public immutable gatekeeperNetwork;

    constructor(address _verifier, bytes32 _network) ERC20("KYC Token", "KKYC") {
        gatewayVerifier = IGatewayTokenVerifier(_verifier);
        gatekeeperNetwork = _network;
    }

    function _update(address from, address to, uint256 amount) internal override {
        if (to != address(0)) {
            require(
                gatewayVerifier.verifyToken(to, gatekeeperNetwork),
                "Recipient must hold a valid Civic Pass"
            );
        }
        super._update(from, to, amount);
    }
}

RWA in DApps — Frontend Patterns

// Check if user is whitelisted before showing buy button
const [isWhitelisted, setIsWhitelisted] = useState(false);

useEffect(() => {
  if (!address) return;
  const token = new ethers.Contract(RWA_TOKEN_ADDRESS, RWA_ABI, provider);
  token.whitelist(address).then(setIsWhitelisted);
}, [address]);

// In JSX:
// {isWhitelisted ? <BuyButton /> : <KYCFlow />}

Why RWA Matters for DeFi

  • Stable yield: T-Bill backed tokens (5%+ APY) give DeFi protocols a real yield source vs purely inflationary incentives
  • Institutional bridge: Brings traditional finance capital on-chain
  • Collateral: RWA tokens used as collateral in MakerDAO (USDS), Aave
  • Regulatory clarity: On-chain compliance infrastructure is maturing
  • Scale: BlackRock BUIDL crossed $500M TVL in weeks — institutional demand is real

Related: DeFi DApp | Create ERC-20 Token | NFT Marketplace

Last updated · September 2026