搞懂ERC-3643:玩转合规证券与 RWA 代币化

51 阅读8分钟

前言

本文梳理了 ERC-3643 标准的相关理论知识,并通过代码案例演示其实际应用。案例采用极简实现方案,未依赖任何第三方工具,同时也补充说明:生态中已有成熟库可直接复用(如 T-REX Protocol)。此外,本文还将围绕该极简实现的合约,完整介绍基于 Hardhat V3 的开发、测试与部署全流程,为开发者提供可落地的实操指南。

ERC-3643标准简介

ERC-3643 是以太坊生态中面向合规证券代币和现实世界资产(RWA)代币化的核心标准,由 ERC-3643 协会治理,是在 ERC-20 代币标准基础上的合规增强版,专为满足传统金融监管要求设计,同时兼容 EVM(以太坊虚拟机)和 OnchainID 去中心化身份系统;

核心功能与特性

  1. 投资者身份校验:集成 KYC/AML(反洗钱 / 客户身份识别)验证接口,通过isValidSenderisValidRecipient等方法校验交易双方是否为合规投资者,支持地址黑白名单管理。
  2. 资产管控能力:新增代币冻结(freeze)、强制转移(forceTransfer)、销毁(destroy)、批量操作等接口,满足监管机构对异常资产的处置需求。
  3. 交易限制与审计:支持对代币转移额度、交易频率进行限制,同时提供可追溯的审计日志,方便监管机构核查。
  4. 兼容性:完全兼容 ERC-20 的核心接口,现有 ERC-20 生态工具可无缝适配,降低开发和迁移成本。

适用场景

  • 证券、债券等金融衍生品的代币化发行;
  • 房地产、艺术品等大额现实世界资产(RWA)的链上确权与交易;
  • 需合规监管的私募股权、基金份额代币化。

核心价值

将传统金融的合规监管逻辑映射到区块链上,既保留了代币化的高效性,又解决了监管机构对资产溯源、投资者资质审核的核心需求,是连接传统金融与 Web3 的重要标准之一。

智能合约开发、测试、部署案例

智能合约

  • 注册系统合约
// contracts/IdentityRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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

contract IdentityRegistry is AccessControl {
    bytes32 public constant AGENT_ROLE = keccak256("AGENT_ROLE");
    
    // 存储地址到身份的映射
    mapping(address => address) public identity;
    mapping(address => bool) public isVerified;
    mapping(address => string) public investorCountry;
    
    event IdentityRegistered(address indexed wallet, address indexed identityContract);
    event IdentityRemoved(address indexed wallet);
    
    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(AGENT_ROLE, msg.sender);
    }
    
    // 注册身份
    function registerIdentity(
        address _wallet,
        address _identity,
        string memory _country
    ) external onlyRole(AGENT_ROLE) {
        require(_wallet != address(0), "Invalid wallet");
        require(_identity != address(0), "Invalid identity");
        
        identity[_wallet] = _identity;
        isVerified[_wallet] = true;
        investorCountry[_wallet] = _country;
        
        emit IdentityRegistered(_wallet, _identity);
    }
    
    // 移除身份
    function removeIdentity(address _wallet) external onlyRole(AGENT_ROLE) {
        delete identity[_wallet];
        delete isVerified[_wallet];
        delete investorCountry[_wallet];
        
        emit IdentityRemoved(_wallet);
    }
    
    // 验证身份
    function verifyIdentity(address _wallet) external view returns (bool) {
        return isVerified[_wallet];
    }
}
  • 合规合约
// contracts/ModularCompliance.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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

contract ModularCompliance is AccessControl {
    bytes32 public constant AGENT_ROLE = keccak256("AGENT_ROLE");
    
    // 合规规则
    mapping(string => bool) public allowedCountries;
    mapping(address => bool) public frozenAddresses;
    bool public complianceEnabled = true;
    
    event CountryAllowed(string country);
    event CountryRestricted(string country);
    event AddressFrozen(address indexed addr);
    event AddressUnfrozen(address indexed addr);
    
    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(AGENT_ROLE, msg.sender);
    }
    
    // 设置允许的国家
    function allowCountry(string memory _country) external onlyRole(AGENT_ROLE) {
        allowedCountries[_country] = true;
        emit CountryAllowed(_country);
    }
    
    function restrictCountry(string memory _country) external onlyRole(AGENT_ROLE) {
        allowedCountries[_country] = false;
        emit CountryRestricted(_country);
    }
    
    // 冻结地址
    function freezeAddress(address _addr) external onlyRole(AGENT_ROLE) {
        frozenAddresses[_addr] = true;
        emit AddressFrozen(_addr);
    }
    
    function unfreezeAddress(address _addr) external onlyRole(AGENT_ROLE) {
        frozenAddresses[_addr] = false;
        emit AddressUnfrozen(_addr);
    }
    
    // 合规检查
    function canTransfer(
        address _from,
        address _to,
        string memory _toCountry
    ) external view returns (bool) {
        if (!complianceEnabled) return true;
        
        // 检查地址是否被冻结
        if (frozenAddresses[_from] || frozenAddresses[_to]) return false;
        
        // 检查国家是否允许
        if (!allowedCountries[_toCountry]) return false;
        
        return true;
    }
}
  • 代币合约
// contracts/SecurityToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./IdentityRegistry.sol";
import "./ModularCompliance.sol";

contract SecurityToken is ERC20, AccessControl, Pausable, ReentrancyGuard {
    bytes32 public constant AGENT_ROLE = keccak256("AGENT_ROLE");
    
    IdentityRegistry public identityRegistry;
    ModularCompliance public compliance;
    
    // 代币元数据
    address public onchainID;
    
    event IdentityRegistryUpdated(address indexed registry);
    event ComplianceUpdated(address indexed compliance);
    
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply,
        address _identityRegistry,
        address _compliance,
        address _onchainID,
        address _admin
    ) ERC20(name, symbol) {
        require(_identityRegistry != address(0), "Invalid identity registry");
        require(_compliance != address(0), "Invalid compliance");
        
        identityRegistry = IdentityRegistry(_identityRegistry);
        compliance = ModularCompliance(_compliance);
        onchainID = _onchainID;
        
        // 设置角色
        _grantRole(DEFAULT_ADMIN_ROLE, _admin);
        _grantRole(AGENT_ROLE, _admin);
        
        // 铸造初始供应
        _mint(_admin, initialSupply);
    }
    
    // 修改 transfer 函数以添加合规检查
    function transfer(address to, uint256 amount) 
        public 
        override 
        whenNotPaused 
        nonReentrant 
        returns (bool) 
    {
        // 验证发送方和接收方身份
        require(identityRegistry.isVerified(msg.sender), "Sender not verified");
        require(identityRegistry.isVerified(to), "Recipient not verified");
        
        // 获取接收方国家并检查合规性
        string memory toCountry = identityRegistry.investorCountry(to);
        require(
            compliance.canTransfer(msg.sender, to, toCountry), 
            "Transfer not compliant"
        );
        
        return super.transfer(to, amount);
    }
    
    // 修改 transferFrom 函数
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public override whenNotPaused nonReentrant returns (bool) {
        require(identityRegistry.isVerified(from), "From address not verified");
        require(identityRegistry.isVerified(to), "To address not verified");
        
        string memory toCountry = identityRegistry.investorCountry(to);
        require(
            compliance.canTransfer(from, to, toCountry), 
            "Transfer not compliant"
        );
        
        return super.transferFrom(from, to, amount);
    }
    
    // 铸币功能(仅 AGENT)
    function mint(address to, uint256 amount) external onlyRole(AGENT_ROLE) {
        require(identityRegistry.isVerified(to), "Recipient not verified");
        _mint(to, amount);
    }
    
    // 销毁代币
    function burn(uint256 amount) external {
        _burn(msg.sender, amount);
    }
    
    // 暂停功能
    function pause() external onlyRole(AGENT_ROLE) {
        _pause();
    }
    
    function unpause() external onlyRole(AGENT_ROLE) {
        _unpause();
    }
    
    // 更新身份注册表
    function setIdentityRegistry(address _registry) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(_registry != address(0), "Invalid registry");
        identityRegistry = IdentityRegistry(_registry);
        emit IdentityRegistryUpdated(_registry);
    }
    
    // 更新合规模块
    function setCompliance(address _compliance) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(_compliance != address(0), "Invalid compliance");
        compliance = ModularCompliance(_compliance);
        emit ComplianceUpdated(_compliance);
    }
}

编译指令

npx hardhat compile

智能合约部署脚本

// scripts/deploy.ts
import { network, artifacts } from "hardhat";
import {parseEther} from "viem"
async function main() {
   // 连接网络
  const { viem } = await network.connect({ network: network.name });//指定网络进行链接
  // 获取客户端
  const [deployer] = await viem.getWalletClients();
  const publicClient = await viem.getPublicClient();
  // 部署身份注册表
  const IdentityRegistry = await artifacts.readArtifact("IdentityRegistry");
  // 部署(构造函数参数:recipient, initialOwner)
  const IdentityRegistryHash = await deployer.deployContract({
    abi: IdentityRegistry.abi,//获取abi
    bytecode: IdentityRegistry.bytecode,//硬编码
    args: [],//process.env.RECIPIENT, process.env.OWNER
  });
// console.log("IdentityRegistry合约部署哈希:", IdentityRegistryHash);
  // 等待确认并打印地址
  const IdentityRegistryReceipt = await publicClient.getTransactionReceipt({ hash: IdentityRegistryHash });
  console.log("IdentityRegistry合约地址:", IdentityRegistryReceipt.contractAddress);


  // 部署合规模块
  const ModularCompliance = await  artifacts.readArtifact("ModularCompliance");

  const ModularComplianceHash = await deployer.deployContract({
    abi: ModularCompliance.abi,//获取abi
    bytecode: ModularCompliance.bytecode,//硬编码
    args: [],//process.env.RECIPIENT, process.env.OWNER
  });
    // 等待确认并打印地址
  const ModularComplianceReceipt = await publicClient.getTransactionReceipt({ hash: ModularComplianceHash });
  console.log("ModularCompliance合约地址:", await ModularComplianceReceipt.contractAddress);

  // 部署权限代币
  const IdentityRegistryAddress = IdentityRegistryReceipt.contractAddress;
  console.log("IdentityRegistryReceipt.contractAddress:", IdentityRegistryAddress);
  const ModularComplianceAddress = ModularComplianceReceipt.contractAddress;
  console.log("ModularComplianceReceipt.contractAddress:", ModularComplianceAddress);
  const SecurityToken = await artifacts.readArtifact("SecurityToken");
  const SecurityTokenHash = await deployer.deployContract({  // ← 使用 deployContract
    abi: SecurityToken.abi,
    bytecode: SecurityToken.bytecode,
    args: [
        "My Security Token",
        "MST",
        parseEther("1000000"),
        IdentityRegistryAddress as `0x${string}`,
        ModularComplianceAddress as `0x${string}`,
        deployer.account.address,//"0x0000000000000000000000000000000000000000"  
        deployer.account.address
    ],
  });
  
  const SecurityTokenReceipt = await publicClient.waitForTransactionReceipt({ 
    hash: SecurityTokenHash 
  });
  console.log("SecurityToken合约地址:", SecurityTokenReceipt.contractAddress);
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

部署指令

npx hardhat run ./scripts/xxx.ts

智能合约测试脚本

import assert from "node:assert/strict";
import { describe, it,beforeEach  } from "node:test";
import { formatEther,parseEther } from 'viem'
import { network } from "hardhat";
describe("Token", async function () {
    let viem: any;
    let publicClient: any;
    let owner: any, user1: any, user2: any, user3: any;
    let deployerAddress: string;
    let IdentityRegistry: any;
    let ModularCompliance: any;
    let SecurityToken: any;
    beforeEach (async function () {
        const { viem } = await network.connect();
         publicClient = await viem.getPublicClient();//创建一个公共客户端实例用于读取链上数据(无需私钥签名)。
         [owner,user1,user2,user3] = await viem.getWalletClients();//获取第一个钱包客户端 写入联合交易
        deployerAddress = owner.account.address;//钱包地址
        IdentityRegistry = await viem.deployContract("IdentityRegistry");//部署合约
        ModularCompliance = await viem.deployContract("ModularCompliance");//部署合约
        SecurityToken = await viem.deployContract("SecurityToken", [
            "My Security Token",
            "MST",
            parseEther("1000000"),
            IdentityRegistry.address,
            ModularCompliance.address,
            deployerAddress,//"0x0000000000000000000000000000000000000000"  
            deployerAddress
        ]);//部署合约
        // 设置合规规则:允许美国和中国投资者
        console.log("\n=== 设置合规规则 ===");
        await owner.writeContract({
            address: ModularCompliance.address,
            abi: ModularCompliance.abi,
            functionName: "allowCountry",
            args: ["US"],
        });
        console.log("✓ 允许美国(US)投资者");

        await owner.writeContract({
            address: ModularCompliance.address,
            abi: ModularCompliance.abi,
            functionName: "allowCountry",
            args: ["CN"],
        });
        console.log("✓ 允许中国(CN)投资者");
    });
    it("测试IdentityRegistry", async function () {
        await owner.writeContract({
            address: IdentityRegistry.address,
            abi: IdentityRegistry.abi,
            functionName: "registerIdentity",
            args: [owner.account.address,user1.account.address,"EN"],
        });
        await owner.writeContract({
            address: IdentityRegistry.address,
            abi: IdentityRegistry.abi,
            functionName: "registerIdentity",
            args: [user1.account.address,user1.account.address,"US"],
        });
        await owner.writeContract({
            address: IdentityRegistry.address,
            abi: IdentityRegistry.abi,
            functionName: "registerIdentity",
            args: [user2.account.address,user2.account.address,"CN"],
        });
        await owner.writeContract({
            address: IdentityRegistry.address,
            abi: IdentityRegistry.abi,
            functionName: "registerIdentity",
            args: [user3.account.address,user3.account.address,"VN"],
        });
       const isUser1Verified = await publicClient.readContract({
            address: IdentityRegistry.address,
            abi: IdentityRegistry.abi,
            functionName: "verifyIdentity",
            args: [user1.account.address],
        }); 
        console.log("isUser1Verified:",isUser1Verified);
        const IsVerified=await publicClient.readContract({
            address: IdentityRegistry.address,
            abi: IdentityRegistry.abi,
            functionName: "isVerified",
            args: [user2.account.address],
        }); 
        console.log("isUser2Verified:",IsVerified);
        const INVESTORCOUNTRY=await publicClient.readContract({
            address: IdentityRegistry.address,
            abi: IdentityRegistry.abi,
            functionName: "investorCountry",
            args: [user2.account.address],
        }); 
        console.log("INVESTORCOUNTRY:",INVESTORCOUNTRY);
        //删除用户2的身份注册
        // await owner.writeContract({
        //     address: IdentityRegistry.address,
        //     abi: IdentityRegistry.abi,
        //     functionName: "removeIdentity",
        //     args: [user2.account.address],
        // });
        // const IsVerifiedUser2=await publicClient.readContract({
        //     address: IdentityRegistry.address,
        //     abi: IdentityRegistry.abi,
        //     functionName: "isVerified",
        //     args: [user2.account.address],
        // });
        // console.log("删除后的:",IsVerifiedUser2);
        // ModularCompliance合约
        // 允许用户2的地址
        const allowCountry=await owner.writeContract({
            address: ModularCompliance.address,
            abi: ModularCompliance.abi,
            functionName: "allowCountry",
            args: ["EN"],
        }); 
        console.log("allowCountry:",allowCountry);
        const restrictCountry=await owner.writeContract({
            address: ModularCompliance.address,
            abi: ModularCompliance.abi,
            functionName: "restrictCountry",
            args: ["EN"],
        }); 
        console.log("restrictCountry:",restrictCountry);
        const canTransfer=await owner.writeContract({
            address: ModularCompliance.address,
            abi: ModularCompliance.abi,
            functionName: "canTransfer",
            args: [owner.account.address,user1.account.address,parseEther("10")],
        }); 
        console.log("canTransfer:",canTransfer);
        // 冻结用户2的地址
       const freezeAddress=await owner.writeContract({
            address: ModularCompliance.address,
            abi: ModularCompliance.abi,
            functionName: "freezeAddress",
            args: [user2.account.address],
        }); 
        console.log("freezeAddress user2:",freezeAddress);
    
        // SecurityToken合约
        const name=await publicClient.readContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "name",
            args: [],
        }); 
        console.log("name:",name);
        const symbol=await publicClient.readContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "symbol",
            args: [],
        }); 
        console.log("symbol:",symbol);
        const totalSupply=await publicClient.readContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "totalSupply",
            args: [],
        }); 
        console.log("totalSupply:",formatEther(totalSupply) + " MST");
            await owner.writeContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "mint",
            args: [user1.account.address,parseEther("1000")],
        }); 
        const balance=await publicClient.readContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "balanceOf",
            args: [user1.account.address],
        }); 
        console.log("user1余额:",formatEther(balance) + " MST");
    // 测试用户1转账给用户2 应该失败 因为用户2被冻结了
await assert.rejects(
                user1.writeContract({
                    address: SecurityToken.address,
                    abi: SecurityToken.abi,
                    functionName: "transfer",
                    args: [user2.account.address, parseEther("50")],
                }),
                /Transfer not compliant/
            );   
        //尝试测试用户1转账给用户2 应该失败 因为用户2被冻结了
        const unfreezeAddress=await owner.writeContract({
            address: ModularCompliance.address,
            abi: ModularCompliance.abi,
            functionName: "unfreezeAddress",
            args: [user2.account.address],
        }); 
        console.log("unfreezeAddress user2:",unfreezeAddress);
        // 测试用户1转账给用户2
        await user1.writeContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "transfer",
            args: [user2.account.address,parseEther("500")],
        });

        const balance2=await publicClient.readContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "balanceOf",
            args: [user2.account.address],
        }); 
        console.log("user2余额:",formatEther(balance2) + " MST");
        //应该允许用户3从用户1那里转账500个MST
       await owner.writeContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "approve",
            args: [user3.account.address,parseEther("500")],
        });
        // 测试用户3从用户1那里转账500个MST
        await user3.writeContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "transferFrom",
            args: [owner.account.address,user2.account.address,parseEther("200")],
        });
        // 测试用户2的余额是否增加200个MST
        const balance3=await publicClient.readContract({
            address: SecurityToken.address,
            abi: SecurityToken.abi,
            functionName: "balanceOf",
            args: [user2.account.address],
        }); 
        console.log("user2余额:",formatEther(balance3) + " MST");
    });

});

测试指令

npx hardhat test ./test/xxx.ts

总结

至此,本文围绕 ERC-3643 合规证券与 RWA 代币化标准,构建了从理论到实践的完整指南。理论上,明晰其 ERC-20 合规增强版定位、核心特性(身份校验、资产管控等)及适用场景,凸显其连接传统金融与 Web3 的价值;实践上,以极简模块化思路拆分身份注册、合规校验、代币合约三大核心模块实现标准逻辑,并基于 Hardhat V3 完整呈现合约开发、测试与部署全流程。本文既助力开发者快速掌握标准核心,也为合规代币化项目落地提供可复用的技术方案,开发者可按需替换为 T-REX 等成熟库用于生产级开发。