跳到主要内容

06 · 环境配置与验收

本章提供连接公开环境所需的网络参数、演示资产、查询示例和验收清单。


6.1 环境信息

测试环境主网演示环境
用途开发、联调、验收BOT 主网能力验证
网络Bohr TestnetBOT Mainnet
状态可用演示 Suite 已部署
Chain ID968677
RPChttps://rpc.bohr.lifehttps://rpc.botchain.ai
区块浏览器https://scan.bohr.lifehttps://scan.botchain.ai
原生 gas 资产测试 BOTBOT

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
Decimals6
资格 TopicKYC_APPROVED
Topic 数值7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim Scheme1
允许国家码156702

6.3.1 Bohr Testnet 地址

资产 Suite:

合约地址
Token0x922835859623d6F3b99a2742D585E093bBA0a740
IdentityRegistry0x17e33C394Ab783D6786011d01681D8Ad540C5F53
IdentityRegistryStorage0xB834C4FEEADC24A9c5B873e8887CD9b54C40E98D
ClaimTopicsRegistry0xbf8adD07A8722d06c218A3A7DD4Bb44d3a90948A
TrustedIssuersRegistry0x94ffe710F64AB382274A53C8b10003a3287E621D
ModularCompliance0x1E2e2a848b611C121C86F77C703B8DD32DCdE37A
Token ONCHAINID0x551676B9061b5324676BBbB5587433454EE54521

Bohr Testnet 平台共享地址:

合约地址
ONCHAINID Gateway0x3B0FE2CD501534cbcbD1F97ec823Da535fc0224f
IdFactory0xb4E03AB303f4c42dbced87b65e1d9f5788EAb369
ClaimIssuer0x67EC753196D6Cd1B65a29b6ff9c1bE41E554a5a7
TREXGateway0x82e1Fa8022BA1dcFC05061E0A1CE828C189108A9

资格与规则:

地址 / 值
KYC_APPROVED Topic7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim Scheme1
Country Allowlist Module0x532bFc29B0D9a281084203303a8E687259D57149
允许国家码156(中国)、702(新加坡)

6.3.2 BOT Mainnet 演示地址

资产 Suite:

合约地址
Token0x9115A020821fDFdf265Eea8788a500d17557c370
IdentityRegistry0x7752dd107CDec2dF5feD585BCC70457c5312021a
IdentityRegistryStorage0xD54fD1381c970F19366cb7c50072295045ff5516
ClaimTopicsRegistry0x645E33c8c67D3Ea478F87b70d76711E110953FB9
TrustedIssuersRegistry0x4d96f4Bb96795a3EFeb3Cea43459E2ee4C5ad439
ModularCompliance0xB8A4b2aFd8318e61cDbaEC07559B49bE6bBE20D5
Token ONCHAINID0x3677B0F87892B10F1341DB3348dFEcb9CE039146

平台共享地址:

合约地址
ONCHAINID Gateway0xa6B4E53128915E0f980DD455De9F70B6Dc61E1ee
IdFactory0x124ED389600870D1a5f86f657896Add3E623ca9e
ClaimIssuer0x3950eB42733b3eF6744E8788C741788CCF6c84b3
TREX ImplementationAuthority0x3E19c7f1e6E53125fF205591fc516e25541d21ff
ONCHAINID ImplementationAuthority0x992CCAC1A33EFdb58b9cfD49CF510d5db15Fc249
IAFactory0xf3BdD013eb6c49C23f5b8E70bE5F6C8c98c76028
TREXFactory0xf622AbFDf470912CCb86A54d52D2430951a3e956
TREXGateway0x3c209A13fc9E91B060C7672874895E117a1A3Ab5

资格与规则:

地址 / 值
KYC_APPROVED Topic7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim Scheme1
Country Allowlist Module0xA41A626bcBF57B3695539f6516E3D482dCFAEDEC
允许国家码156(中国)、702(新加坡)

BOT 主网演示 Suite 当前使用未经过生产审计的 Country Allowlist 模块,只用于技术能力核验,不得直接承载真实资产。

机器可读配置:


6.4 公开查询钱包

以下地址已在测试资产的 IdentityRegistry 完成登记,可用于只读接口联调:

示例钱包ONCHAINID国家码预期资格
Alice0xb450970Eece71044acF7cB7f53Ee51e3BC2EA9220xfAB41B1DfD098F439D75cEA0cb48f70B46052544156true
Bob0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F0xd3b816e7494e918297E3553d65488a7E0682C6A3702true
Cathy0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe780xb7522A6649a20A0820E292D8D0f35aB792812011156true

这些是公开查询样例,不提供对应私钥。需要执行写操作时,应使用接入方自己的测试钱包并申请测试资格或测试资产。

BOT 主网能力演示 Suite 中,同一组公开钱包也已完成独立登记:

示例钱包BOT 主网 ONCHAINID国家码isVerified
Alice0xb450970Eece71044acF7cB7f53Ee51e3BC2EA9220x95d0ae16d03A26BA6a0dA844938C5c50e16e407C156true
Bob0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F0x3F5059a5eadc24c27809f481e97Bef4834DB4272702true
Cathy0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe780x29E0cb659BEd6baF5a6Dc19C50cB41dbBdd41194156true

负例测试可使用尚未登记的自有测试钱包。预期 contains == falseisVerified == false。不要使用零地址作为真实业务钱包。


6.5 第一步:校验网络与合约

以下 JavaScript 示例使用 ethers.js v5。如果项目使用 v6,需要改用 JsonRpcProviderformatUnitsparseUnits 的 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,
});

公开测试钱包的 containsverified 预期为 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
addClaimONCHAINID 管理钱包已取得测试 Claim 签名
registerIdentity测试 IR Agent由发行方或平台提供联调支持
mint / burn测试 Token Agent由发行方或平台提供联调支持
transferToken 持有人双方满足资格和规则

普通开发者不需要也不应取得平台或发行方 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 查询资格;
  • containsisVerified 不混用;
  • 多资产不会复用错误的 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. 提交后查询交易回执和事件

如果测试网成功但主网失败,应优先比较网络、合约地址、资产资格要求和合规规则,不应假设两个环境配置完全相同。


上一章:05 · 合约接口与事件 · 回到 01 · 项目与接入概览