跳到主要内容

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: '0xA7aEb5cc99C558E7A237aA1B3483e71E59e66036',
identityRegistry: '0x5C3f759a7Fe22331750E6CA3293a96C34daE3417',
onchainIdGateway: '0xb62261a7193B6F37A5c6A2c242aD542C840d6f5a',
idFactory: '0xEa3216f557B6598F53fCcd146E63912c4c1be02C',
claimIssuer: '0xEa7E7c89F56f167E12570E15d648460104510d47',
},
};

BOT 主网能力演示配置:

const rwaMainnet = {
chainId: 677,
rpcUrl: 'https://rpc.botchain.ai',
explorerUrl: 'https://scan.botchain.ai',
contracts: {
token: '0x58F96a44594C9C5C8DE282Eeb75208B5f11C0c36',
identityRegistry: '0xe0b6FbEa0a9af21ddd455D6cA776478cAbe7c141',
onchainIdGateway: '0xFCF1E45f42aAa5CccA0A035f7a946BC8Fd55Fac6',
idFactory: '0xE97099950DFe98535927a650658c0E964c8d6f67',
claimIssuer: '0xD40b0c8dC960755C2586601Ff505F036FfE9BF59',
},
};

不要让程序在主网配置缺失或校验失败时自动回退到测试地址。完整机器可读配置见 addresses-mainnet.json。


6.3 公开演示资产​

Bohr Testnet 和 BOT Mainnet 均已部署以下示例资产,用于证明完整 ERC-3643 能力。两个网络上的地址、余额、身份和 Claim 相互独立。

项值
名称Project Real World Asset
代号PRWA
Decimals6
资格 TopicKYC_APPROVED
Topic 数值7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim Scheme1
允许国家码156、702

6.3.1 Bohr Testnet 地址​

资产 Suite:

合约地址
Token0xA7aEb5cc99C558E7A237aA1B3483e71E59e66036
IdentityRegistry0x5C3f759a7Fe22331750E6CA3293a96C34daE3417
IdentityRegistryStorage0x3fae839c1642BDcFBE94b5A8088a56F8fC787707
ClaimTopicsRegistry0x042a938B2cE1e1AdF2b4101D9730722d5c75F773
TrustedIssuersRegistry0x2323413Abd0BF763ed2f3bE9c319A7a05985C2ff
ModularCompliance0x3aa6A97b6d61039A65C5AEf5649523732379aE9C
Token ONCHAINID0x7528D8d23fE511c658564E1cB346ca7D9B72788d

Bohr Testnet 平台共享地址:

合约地址
ONCHAINID Gateway0xb62261a7193B6F37A5c6A2c242aD542C840d6f5a
IdFactory0xEa3216f557B6598F53fCcd146E63912c4c1be02C
ClaimIssuer0xEa7E7c89F56f167E12570E15d648460104510d47
TREXGateway0xf73CB3A2830bef113Be01CD5eE5C99d21e43107B

资格与规则:

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

6.3.2 BOT Mainnet 演示地址​

资产 Suite:

合约地址
Token0x58F96a44594C9C5C8DE282Eeb75208B5f11C0c36
IdentityRegistry0xe0b6FbEa0a9af21ddd455D6cA776478cAbe7c141
IdentityRegistryStorage0x344D8266058FcE40D8229F8eF118da5ee6f88c54
ClaimTopicsRegistry0x7778b431b73F57722cBa7D6205F45858D789ACB1
TrustedIssuersRegistry0xC3216525E30E0369B49d75105b3591b647595d63
ModularCompliance0xE0444e1374d5ca5EdF3DAb0bEB54165c8D1968a4
Token ONCHAINID0x651b1Ad7F96A16228e41cDf6c2eb2282C267dE6B

平台共享地址:

合约地址
ONCHAINID Gateway0xFCF1E45f42aAa5CccA0A035f7a946BC8Fd55Fac6
IdFactory0xE97099950DFe98535927a650658c0E964c8d6f67
ClaimIssuer0xD40b0c8dC960755C2586601Ff505F036FfE9BF59
TREX ImplementationAuthority0x658B369FFfE2474310224c6DAe2f320A27A76D2E
ONCHAINID ImplementationAuthority0xA5aeF4767C10fE47d1097b65Da55357B29bbbfF6
IAFactory0x394f0720F519e25d63065690d8565018404b69E8
TREXFactory0xCdc6C38438DEd768028EecFd50a8aAf3ad755a92
TREXGateway0x7a8bea060430D7A6B5ac816137733570425Bb777

资格与规则:

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

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

机器可读配置:


6.4 公开查询钱包​

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

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

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

最新 BOT 主网能力演示 Suite 仅完成合约部署与开源验证,尚未执行公开钱包的 ONCHAINID 创建、Claim 签发和 IdentityRegistry 登记。因此当前不发布主网 isVerified=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 = '0xA7aEb5cc99C558E7A237aA1B3483e71E59e66036';
const IR = '0x5C3f759a7Fe22331750E6CA3293a96C34daE3417';

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
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 查询资格;
  • 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. 提交后查询交易回执和事件

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


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