05 · 合约接口与事件
前一章说明了资产发行、投资者准入、增发、转账和销毁流程。本章把这些流程对应到具体合约方法,作为编码时的快速参考。
5.1 先找到正确的合约
外部应用不需要直接连接整套 Suite。通常从资产 Token 代理地址开始:
建议按以下顺序发现依赖:
- 从公开地址表读取目标环境的 Token 代理地址;
- 调用
Token.identityRegistry()获取该资产的注册表; - 调用
Token.compliance()获取该资产的合规入口; - 通过
IdFactory.getIdentity(wallet)查询钱包关联的 ONCHAINID; - 将链上返回地址与公开配置比对,不要把实现合约地址作为业务入口。
5.2 调用权限速查
| 调用方 | 常用写方法 | 说明 |
|---|---|---|
| 任意用户或应用 | 所有 view 方法 | 只读调用不发送交易,不消耗用户 gas |
| Token 持有人 | transfer、approve、transferFrom | 仍需通过身份、暂停、冻结和合规检查 |
| 投资者身份管理钱包 | deployIdentityForWallet、addClaim | 创建和维护自己的 ONCHAINID |
| IR Agent | registerIdentity、updateIdentity、updateCountry、deleteIdentity | 管理投资者在某一项资产中的登记 |
| Token Agent | mint、burn、pause、unpause、冻结、强制转移、钱包恢复 | 资产管理操作 |
| 合约 Owner | 修改注册表、合规模块或治理配置 | 不属于普通应用接入范围 |
应用应先识别当前钱包角色,再展示对应操作。不要依靠前端隐藏按钮代替链上权限控制。
5.3 Token
常用只读方法
| 方法 | 返回值 | 用途 |
|---|---|---|
name() / symbol() | string | 展示资产名称和代号 |
decimals() | uint8 | 金额换算 |
totalSupply() | uint256 | 当前总发行量 |
balanceOf(wallet) | uint256 | 钱包总余额 |
identityRegistry() | address | 获取该资产的资格注册表 |
compliance() | address | 获取该资产的合规入口 |
paused() | bool | 判断普通转账是否暂停 |
isFrozen(wallet) | bool | 判断钱包是否整体冻结 |
getFrozenTokens(wallet) | uint256 | 查询部分冻结数量 |
可用余额应按下式计算:
availableBalance = balanceOf(wallet) - getFrozenTokens(wallet)
持有人写方法
| 方法 | 作用 | 主要前置条件 |
|---|---|---|
transfer(to, amount) | 直接转账 | Token 未暂停、双方状态允许、接收方资格和规则通过 |
approve(spender, amount) | 设置授权额度 | 持有人签名 |
transferFrom(from, to, amount) | 使用授权额度转账 | 授权、余额、资格和规则均通过 |
Token Agent 方法
| 方法 | 作用 | 关键结果 |
|---|---|---|
mint(to, amount) | 增发 | Transfer(0x0, to, amount) |
burn(wallet, amount) | 销毁 | Transfer(wallet, 0x0, amount) |
pause() / unpause() | 暂停或开放普通转账 | Paused / Unpaused |
setAddressFrozen(wallet, frozen) | 整体冻结或解冻钱包 | AddressFrozen |
freezePartialTokens(wallet, amount) | 冻结部分余额 | TokensFrozen |
unfreezePartialTokens(wallet, amount) | 解冻部分余额 | TokensUnfrozen |
forcedTransfer(from, to, amount) | 管理性强制转移 | Transfer |
recoveryAddress(lost, new, identity) | 将资产恢复到新钱包 | RecoverySuccess 和 Transfer |
所有 amount 都是最小单位整数。批量方法可能受区块 gas 上限限制,应用应拆分批次并为每批保存独立交易状态。
不同 Token 操作经过的检查并不相同:
| 操作 | 暂停时是否拦截 | 接收方资格 | canTransfer | 冻结余额处理 |
|---|---|---|---|---|
transfer / transferFrom | 是 | 检查 | 检查 | 冻结部分不可转 |
mint | 否 | 检查 | 检查 | 不涉及 |
burn | 否 | 不检查 | 不检查 | 必要时由 Agent 操作解冻 |
forcedTransfer | 否 | 检查 | 不检查 | 必要时由 Agent 操作解冻 |
recoveryAddress | 否 | 按恢复流程处理 | 不检查 | 可迁移冻结状态 |
暂停主要限制普通持有人转账,不会阻止 Agent 的增发、销毁、强制转移和钱包恢复。应用不得把 paused == true 理解为所有资产管理操作均已停止。
5.4 IdentityRegistry
IdentityRegistry 属于具体资产。查询或登记投资者前,应先通过目标 Token 的 identityRegistry() 获取地址。
常用只读方法
| 方法 | 含义 |
|---|---|
contains(wallet) | 钱包是否已登记 |
isVerified(wallet) | 钱包是否满足该资产当前全部资格要求 |
identity(wallet) | 已登记的 ONCHAINID 地址 |
investorCountry(wallet) | 已登记国家或地区代码 |
topicsRegistry() | 该资产要求的 Claim Topic 注册表 |
issuersRegistry() | 该资产认可的 ClaimIssuer 注册表 |
contains == true 只代表存在登记记录,不能替代 isVerified == true。
IR Agent 方法
| 方法 | 作用 | 事件 |
|---|---|---|
registerIdentity(wallet, identity, country) | 首次登记投资者 | IdentityRegistered |
updateIdentity(wallet, identity) | 替换关联身份 | IdentityUpdated |
updateCountry(wallet, country) | 更新国家或地区代码 | CountryUpdated |
deleteIdentity(wallet) | 移除该资产中的登记 | IdentityRemoved |
同一个 ONCHAINID 可以被多项资产引用,但每项资产使用自己的 IdentityRegistry。参与多项独立资产时,通常需要分别登记。
5.5 ONCHAINID Gateway、IdFactory 与 Identity
创建或查询身份
| 合约 | 方法 | 调用方 | 用途 |
|---|---|---|---|
| ONCHAINID Gateway | deployIdentityForWallet(wallet) | 投资者 | 为钱包创建确定性 ONCHAINID |
| IdFactory | getIdentity(wallet) | 任意应用 | 查询钱包关联身份 |
创建成功后,IdFactory 会触发 Deployed 和 WalletLinked。应用仍应再次调用 getIdentity(wallet),以链上最终映射为准。
Claim 方法
投资者在自己的 ONCHAINID 上调用:
addClaim(
uint256 topic,
uint256 scheme,
address issuer,
bytes signature,
bytes data,
string uri
) returns (bytes32 claimId)
常用读取方法:
| 方法 | 用途 |
|---|---|
getClaim(claimId) | 读取一条 Claim 的完整字段 |
getClaimIdsByTopic(topic) | 查询某 Topic 下的 Claim ID |
isClaimValid(identity, topic, signature, data) | 校验 ClaimIssuer 签名 |
关键事件:
ClaimAdded:首次写入 Claim;ClaimChanged:更新已有 Claim;ClaimRemoved:移除 Claim。
ClaimSigner 生成链下签名,ClaimIssuer 是签名归属的链上机构合约,两者不是同一个概念。
5.6 ModularCompliance
canTransfer 定义在 ModularCompliance,不是 Token:
canTransfer(
address from,
address to,
uint256 amount
) view returns (bool)
常用只读方法:
| 方法 | 用途 |
|---|---|
canTransfer(from, to, amount) | 预检查当前规则是否允许转账 |
getModules() | 获取当前绑定的规则模块 |
getTokenBound() | 获取绑定的 Token |
isModuleBound(module) | 判断某模块是否已绑定 |
canTransfer == true 只表示查询时规则通过。发送交易前后,暂停、冻结、余额、资格或规则都可能变化,最终仍以交易回执为准。
transferred、created 和 destroyed 由绑定的 Token 调用,用于通知合规模块更新状态。普通应用不应直接调用。
5.7 事件索引
建议至少索引以下事件:
| 事件 | 来源 | 用途 |
|---|---|---|
Transfer | Token | 增发、销毁和转账 |
Approval | Token | 授权额度变化 |
Paused / Unpaused | Token | 资产流转状态变化 |
AddressFrozen | Token | 钱包整体冻结状态 |
TokensFrozen / TokensUnfrozen | Token | 部分冻结数量变化 |
RecoverySuccess | Token | 钱包恢复完成 |
IdentityRegistered / IdentityRemoved | IdentityRegistry | 投资者登记变化 |
IdentityUpdated / CountryUpdated | IdentityRegistry | 身份或国家码变化 |
ClaimAdded / ClaimChanged / ClaimRemoved | ONCHAINID | 资格证明变化 |
ModuleAdded / ModuleRemoved | ModularCompliance | 规则模块变化 |
事件用于发现变化,但不能单独作为当前状态。发生区块重组、断点补扫或长时间离线后,应重新读取合约状态校准。
5.8 最小 ABI
不使用完整 ABI 时,可以从以下最小集合开始:
const tokenAbi = [
'function name() view returns (string)',
'function symbol() view returns (string)',
'function decimals() view returns (uint8)',
'function totalSupply() view returns (uint256)',
'function balanceOf(address) view returns (uint256)',
'function identityRegistry() view returns (address)',
'function compliance() view returns (address)',
'function paused() view returns (bool)',
'function isFrozen(address) view returns (bool)',
'function getFrozenTokens(address) view returns (uint256)',
'function transfer(address,uint256) returns (bool)',
'event Transfer(address indexed from,address indexed to,uint256 value)',
];
const identityRegistryAbi = [
'function contains(address) view returns (bool)',
'function isVerified(address) view returns (bool)',
'function identity(address) view returns (address)',
'function investorCountry(address) view returns (uint16)',
'event IdentityRegistered(address indexed investorAddress,address indexed identity)',
];
const complianceAbi = [
'function canTransfer(address,address,uint256) view returns (bool)',
'function getModules() view returns (address[])',
];
实际写操作应使用与当前部署版本一致的正式 ABI。不要仅凭方法名猜测参数顺序。
5.9 推荐调用顺序
提交投资者转账前:
校验 Chain ID
→ 读取 Token.decimals
→ 读取 paused / isFrozen / getFrozenTokens
→ 通过 Token.identityRegistry 获取正确注册表
→ 查询接收方 contains / isVerified
→ 通过 Token.compliance 获取合规入口
→ 调用 canTransfer
→ estimateGas 或静态模拟
→ 发送交易并等待回执
→ 按事件和最新状态更新应用
下一章将使用本章接口连接公开环境,并给出读取、事件查询和验收步骤: