06 · 环境配置与验收
本章提供连接公开环境所需的网络参数、演示资产、查询示例和验收清单。
6.1 环境信息
| 项 | 测试环境 | 主网演示环境 |
|---|---|---|
| 用途 | 开发、联调、验收 | BOT 主网能力验证 |
| 网络 | Bohr Testnet | BOT Mainnet |
| 状态 | 可用 | 演示 Suite 已部署 |
| Chain ID | 968 | 677 |
| RPC | https://rpc.bohr.life | https://rpc.botchain.ai |
| 区块浏览器 | https://scan.bohr.life | https://scan.botchain.ai |
| 原生 gas 资产 | 测试 BOT | BOT |
Bohr 测试网和 BOT 主网的合约地址、Token 余额、身份和 Claim 均不互通。
6.2 推荐配置结构
不同环境应使用独立配置文件或环境变量:
type RwaEnvironment = {
chainId: number;
rpcUrl: string;
explorerUrl: string;
contracts: {
token: string;
identityRegistry: string;
onchainIdGateway?: string;
idFactory?: string;
claimIssuer?: string;
};
};
测试环境示例:
const rwaTestnet = {
chainId: 968,
rpcUrl: 'https://rpc.bohr.life',
explorerUrl: 'https://scan.bohr.life',
contracts: {
token: '0x922835859623d6F3b99a2742D585E093bBA0a740',
identityRegistry: '0x17e33C394Ab783D6786011d01681D8Ad540C5F53',
onchainIdGateway: '0x3B0FE2CD501534cbcbD1F97ec823Da535fc0224f',
idFactory: '0xb4E03AB303f4c42dbced87b65e1d9f5788EAb369',
claimIssuer: '0x67EC753196D6Cd1B65a29b6ff9c1bE41E554a5a7',
},
};
BOT 主网能力演示配置:
const rwaMainnet = {
chainId: 677,
rpcUrl: 'https://rpc.botchain.ai',
explorerUrl: 'https://scan.botchain.ai',
contracts: {
token: '0x9115A020821fDFdf265Eea8788a500d17557c370',
identityRegistry: '0x7752dd107CDec2dF5feD585BCC70457c5312021a',
onchainIdGateway: '0xa6B4E53128915E0f980DD455De9F70B6Dc61E1ee',
idFactory: '0x124ED389600870D1a5f86f657896Add3E623ca9e',
claimIssuer: '0x3950eB42733b3eF6744E8788C741788CCF6c84b3',
},
};
不要让程序在主网配置缺失或校验失败时自动回退到测试地址。完整机器可读配置见 addresses-mainnet.json。
6.3 公开演示资产
Bohr Testnet 和 BOT Mainnet 均已部署以下示例资产,用于证明完整 ERC-3643 能力。两个网络上的地址、余额、身份和 Claim 相互独立。
| 项 | 值 |
|---|---|
| 名称 | Project Real World Asset |
| 代号 | PRWA |
| Decimals | 6 |
| 资格 Topic | KYC_APPROVED |
| Topic 数值 | 7151541257903274562366177585585886223835338030660388602590463654390840817671 |
| Claim Scheme | 1 |
| 允许国家码 | 156、702 |
6.3.1 Bohr Testnet 地址
资产 Suite:
| 合约 | 地址 |
|---|---|
Token | 0x922835859623d6F3b99a2742D585E093bBA0a740 |
IdentityRegistry | 0x17e33C394Ab783D6786011d01681D8Ad540C5F53 |
IdentityRegistryStorage | 0xB834C4FEEADC24A9c5B873e8887CD9b54C40E98D |
ClaimTopicsRegistry | 0xbf8adD07A8722d06c218A3A7DD4Bb44d3a90948A |
TrustedIssuersRegistry | 0x94ffe710F64AB382274A53C8b10003a3287E621D |
ModularCompliance | 0x1E2e2a848b611C121C86F77C703B8DD32DCdE37A |
| Token ONCHAINID | 0x551676B9061b5324676BBbB5587433454EE54521 |
Bohr Testnet 平台共享地址:
| 合约 | 地址 |
|---|---|
ONCHAINID Gateway | 0x3B0FE2CD501534cbcbD1F97ec823Da535fc0224f |
IdFactory | 0xb4E03AB303f4c42dbced87b65e1d9f5788EAb369 |
ClaimIssuer | 0x67EC753196D6Cd1B65a29b6ff9c1bE41E554a5a7 |
TREXGateway | 0x82e1Fa8022BA1dcFC05061E0A1CE828C189108A9 |
资格与规则:
| 项 | 地址 / 值 |
|---|---|
KYC_APPROVED Topic | 7151541257903274562366177585585886223835338030660388602590463654390840817671 |
| Claim Scheme | 1 |
| Country Allowlist Module | 0x532bFc29B0D9a281084203303a8E687259D57149 |
| 允许国家码 | 156(中国)、702(新加坡) |
6.3.2 BOT Mainnet 演示地址
资产 Suite:
| 合约 | 地址 |
|---|---|
Token | 0x9115A020821fDFdf265Eea8788a500d17557c370 |
IdentityRegistry | 0x7752dd107CDec2dF5feD585BCC70457c5312021a |
IdentityRegistryStorage | 0xD54fD1381c970F19366cb7c50072295045ff5516 |
ClaimTopicsRegistry | 0x645E33c8c67D3Ea478F87b70d76711E110953FB9 |
TrustedIssuersRegistry | 0x4d96f4Bb96795a3EFeb3Cea43459E2ee4C5ad439 |
ModularCompliance | 0xB8A4b2aFd8318e61cDbaEC07559B49bE6bBE20D5 |
| Token ONCHAINID | 0x3677B0F87892B10F1341DB3348dFEcb9CE039146 |
平台共享地址:
| 合约 | 地址 |
|---|---|
ONCHAINID Gateway | 0xa6B4E53128915E0f980DD455De9F70B6Dc61E1ee |
IdFactory | 0x124ED389600870D1a5f86f657896Add3E623ca9e |
ClaimIssuer | 0x3950eB42733b3eF6744E8788C741788CCF6c84b3 |
TREX ImplementationAuthority | 0x3E19c7f1e6E53125fF205591fc516e25541d21ff |
ONCHAINID ImplementationAuthority | 0x992CCAC1A33EFdb58b9cfD49CF510d5db15Fc249 |
IAFactory | 0xf3BdD013eb6c49C23f5b8E70bE5F6C8c98c76028 |
TREXFactory | 0xf622AbFDf470912CCb86A54d52D2430951a3e956 |
TREXGateway | 0x3c209A13fc9E91B060C7672874895E117a1A3Ab5 |
资格与规则:
| 项 | 地址 / 值 |
|---|---|
KYC_APPROVED Topic | 7151541257903274562366177585585886223835338030660388602590463654390840817671 |
| Claim Scheme | 1 |
| Country Allowlist Module | 0xA41A626bcBF57B3695539f6516E3D482dCFAEDEC |
| 允许国家码 | 156(中国)、702(新加坡) |
BOT 主网演示 Suite 当前使用未经过生产审计的 Country Allowlist 模块,只用于技术能力核验,不得直接承载真实资产。
机器可读配置:
6.4 公开查询钱包
以下地址已在测试资产的 IdentityRegistry 完成登记,可用于只读接口联调:
| 示例 | 钱包 | ONCHAINID | 国家码 | 预期资格 |
|---|---|---|---|---|
| Alice | 0xb450970Eece71044acF7cB7f53Ee51e3BC2EA922 | 0xfAB41B1DfD098F439D75cEA0cb48f70B46052544 | 156 | true |
| Bob | 0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F | 0xd3b816e7494e918297E3553d65488a7E0682C6A3 | 702 | true |
| Cathy | 0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe78 | 0xb7522A6649a20A0820E292D8D0f35aB792812011 | 156 | true |
这些是公开查询样例,不提供对应私钥。需要执行写操作时,应使用接入方自己的测试钱包并申请测试资格或测试资产。
BOT 主网能力演示 Suite 中,同一组公开钱包也已完成独立登记:
| 示例 | 钱包 | BOT 主网 ONCHAINID | 国家码 | isVerified |
|---|---|---|---|---|
| Alice | 0xb450970Eece71044acF7cB7f53Ee51e3BC2EA922 | 0x95d0ae16d03A26BA6a0dA844938C5c50e16e407C | 156 | true |
| Bob | 0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F | 0x3F5059a5eadc24c27809f481e97Bef4834DB4272 | 702 | true |
| Cathy | 0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe78 | 0x29E0cb659BEd6baF5a6Dc19C50cB41dbBdd41194 | 156 | true |
负例测试可使用尚未登记的自有测试钱包。预期 contains == false、isVerified == false。不要使用零地址作为真实业务钱包。
6.5 第一步:校验网络与合约
以下 JavaScript 示例使用 ethers.js v5。如果项目使用 v6,需要改用 JsonRpcProvider、formatUnits 和 parseUnits 的 v6 导入方式。
const { ethers } = require('ethers');
const provider = new ethers.providers.JsonRpcProvider(
'https://rpc.bohr.life'
);
const network = await provider.getNetwork();
if (network.chainId !== 968) {
throw new Error(`Wrong network: expected 968, got ${network.chainId}`);
}
const TOKEN = '0x922835859623d6F3b99a2742D585E093bBA0a740';
const IR = '0x17e33C394Ab783D6786011d01681D8Ad540C5F53';
const code = await provider.getCode(TOKEN);
if (code === '0x') {
throw new Error('Token contract not found on current network');
}
所有写操作前都应执行 Chain ID 校验。
6.6 第二步:读取资产信息
const token = new ethers.Contract(
TOKEN,
[
'function name() view returns (string)',
'function symbol() view returns (string)',
'function decimals() view returns (uint8)',
'function totalSupply() view returns (uint256)',
'function identityRegistry() view returns (address)',
'function compliance() view returns (address)',
'function paused() view returns (bool)',
],
provider
);
const [name, symbol, decimals, supply, registry, compliance, paused] =
await Promise.all([
token.name(),
token.symbol(),
token.decimals(),
token.totalSupply(),
token.identityRegistry(),
token.compliance(),
token.paused(),
]);
console.log({
name,
symbol,
decimals,
totalSupply: ethers.utils.formatUnits(supply, decimals),
registry,
compliance,
paused,
});
验收要求:
- 名称与代号符合资产发布信息;
decimals可正常读取;identityRegistry()与配置地址一致;compliance()与地址表一致。
6.7 第三步:查询余额与资格
const wallet = '0xb450970Eece71044acF7cB7f53Ee51e3BC2EA922';
const ir = new ethers.Contract(
IR,
[
'function contains(address) view returns (bool)',
'function isVerified(address) view returns (bool)',
'function identity(address) view returns (address)',
'function investorCountry(address) view returns (uint16)',
],
provider
);
const [rawBalance, contains, verified, identity, country] =
await Promise.all([
token.balanceOf(wallet),
ir.contains(wallet),
ir.isVerified(wallet),
ir.identity(wallet),
ir.investorCountry(wallet),
]);
console.log({
balance: ethers.utils.formatUnits(rawBalance, decimals),
contains,
verified,
identity,
country,
});
公开测试钱包的 contains 和 verified 预期为 true,identity 和 country 应与测试钱包表一致。
6.8 第四步:事件查询
const transferFilter = token.filters.Transfer();
const latest = await provider.getBlockNumber();
const logs = await token.queryFilter(
transferFilter,
Math.max(0, latest - 5000),
latest
);
for (const log of logs) {
console.log({
txHash: log.transactionHash,
blockNumber: log.blockNumber,
from: log.args.from,
to: log.args.to,
value: ethers.utils.formatUnits(log.args.value, decimals),
});
}
长期运行的事件索引应使用分段查询和断点续扫,避免一次请求过大。
6.9 写操作联调
不同写操作需要不同签名方:
| 操作 | 签名方 | 前置条件 |
|---|---|---|
| 创建 ONCHAINID | 投资者测试钱包 | 有测试 gas |
addClaim | ONCHAINID 管理钱包 | 已取得测试 Claim 签名 |
registerIdentity | 测试 IR Agent | 由发行方或平台提供联调支持 |
mint / burn | 测试 Token Agent | 由发行方或平台提供联调支持 |
transfer | Token 持有人 | 双方满足资格和规则 |
普通开发者不需要也不应取得平台或发行方 Agent 私钥。需要 Agent 权限的操作应由目标资产发行方执行;测试时使用自己的钱包完成投资者操作,并通过项目公开的支持渠道申请测试 gas、资格登记或测试资产。任何情况下都不要传递私钥。
转账预检查与发送
当前公开的 Bohr Testnet 和 BOT Mainnet PRWA 演示资产均处于暂停状态,普通持有人转账不可执行。以下代码用于展示完整接入方式;只有 Token Agent 调用 unpause() 开放流转后,才可以发送 transfer。
const recipient = '0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F';
const senderAddress = await signer.getAddress();
const amount = ethers.utils.parseUnits('10', decimals);
if (await token.paused()) {
throw new Error('Token transfers are currently paused');
}
const verified = await ir.isVerified(recipient);
if (!verified) {
throw new Error('Recipient is not verified for this asset');
}
const complianceAddress = await token.compliance();
const compliance = new ethers.Contract(
complianceAddress,
['function canTransfer(address,address,uint256) view returns (bool)'],
provider
);
const allowed = await compliance.canTransfer(
senderAddress,
recipient,
amount
);
if (!allowed) {
throw new Error('Transfer rejected by compliance rules');
}
// signer 必须是 Token 持有人
const tx = await token.connect(signer).transfer(recipient, amount);
const receipt = await tx.wait();
console.log(receipt.transactionHash);
approve + transferFrom
const approveTx = await token.connect(ownerSigner).approve(spender, amount);
await approveTx.wait();
const transferTx = await token
.connect(spenderSigner)
.transferFrom(ownerAddress, recipient, amount);
await transferTx.wait();
预检查不能保证交易必然成功,发送和确认之间链上状态可能变化,最终以交易回执为准。
6.10 上线验收清单
环境
- 测试网和主网使用独立配置;
- 启动时严格校验 Chain ID;
- RPC、浏览器和合约地址来自正式发布信息;
- 不允许主网配置自动回退测试地址。
合约读取
- Token 名称、代号、
decimals读取正确; - Token 绑定的 IR、MC 与地址表一致;
- 地址有合约 bytecode;
- ABI 与当前合约版本匹配。
金额与交易
- 所有金额使用
parseUnits/formatUnits; - 写操作区分提交、确认和失败状态;
- 交易哈希可跳转正确区块浏览器;
- 失败交易不会更新业务最终状态;
- 重试机制不会重复提交增发或销毁。
身份与合规
- 按目标资产的 IR 查询资格;
contains和isVerified不混用;- 多资产不会复用错误的 IR 地址;
- KYC 明文资料不写链;
- 合规拒绝能向用户展示可理解信息。
安全
- 前端、代码仓库和日志中不存在私钥;
- Agent 和 Claim Signer 使用受控签名方式;
- 只使用代理地址;
- 订阅平台升级和安全公告。
6.11 故障排查顺序
1. 确认 Chain ID
2. 确认 RPC 可用
3. 确认地址在当前网络有 bytecode
4. 读取 Token.name / decimals / identityRegistry
5. 查询余额、paused、isFrozen
6. 查询接收方 contains / isVerified
7. 查询 compliance.canTransfer
8. 确认签名账户角色
9. 使用 callStatic / estimateGas 获取回滚原因
10. 提交后查询交易回执和事件
如果测试网成功但主网失败,应优先比较网络、合约地址、资产资格要求和合规规则,不应假设两个环境配置完全相同。