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

  • 木西
  • 发布于 14小时前
  • 阅读 34

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

前言

本文梳理了 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 等成熟库用于生产级开发。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
0x5D5C...2dD7
江湖只有他的大名,没有他的介绍。