06 · Environment Configuration and Acceptance
This chapter provides the network parameters, demo assets, query examples, and acceptance checklist required to connect to the public environments.
6.1 Environment information
| Item | Test environment | Mainnet demo environment |
|---|---|---|
| Purpose | Development, integration, acceptance | BOT Mainnet capability verification |
| Network | Bohr Testnet | BOT Mainnet |
| Status | Available | Demo Suite deployed |
| Chain ID | 968 | 677 |
| RPC | https://rpc.bohr.life | https://rpc.botchain.ai |
| Explorer | https://scan.bohr.life | https://scan.botchain.ai |
| Native gas asset | Test BOT | BOT |
Contract addresses, Token balances, identities, and Claims are not shared between Bohr Testnet and BOT Mainnet.
6.2 Recommended configuration structure
Use independent configuration files or environment variables for each environment:
type RwaEnvironment = {
chainId: number;
rpcUrl: string;
explorerUrl: string;
contracts: {
token: string;
identityRegistry: string;
onchainIdGateway?: string;
idFactory?: string;
claimIssuer?: string;
};
};
Test environment example:
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 Mainnet capability-demo configuration:
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',
},
};
Do not automatically fall back to test addresses when mainnet configuration is missing or fails validation. See the complete machine-readable configuration in addresses-mainnet.json.
6.3 Public demo asset
Bohr Testnet and BOT Mainnet both have the following sample asset deployed to demonstrate complete ERC-3643 capabilities. Addresses, balances, identities, and Claims are independent on each network.
| Item | Value |
|---|---|
| Name | Project Real World Asset |
| Symbol | PRWA |
| Decimals | 6 |
| Eligibility Topic | KYC_APPROVED |
| Topic value | 7151541257903274562366177585585886223835338030660388602590463654390840817671 |
| Claim scheme | 1 |
| Allowed country codes | 156, 702 |
6.3.1 Bohr Testnet addresses
Asset Suite:
| Contract | Address |
|---|---|
Token | 0x922835859623d6F3b99a2742D585E093bBA0a740 |
IdentityRegistry | 0x17e33C394Ab783D6786011d01681D8Ad540C5F53 |
IdentityRegistryStorage | 0xB834C4FEEADC24A9c5B873e8887CD9b54C40E98D |
ClaimTopicsRegistry | 0xbf8adD07A8722d06c218A3A7DD4Bb44d3a90948A |
TrustedIssuersRegistry | 0x94ffe710F64AB382274A53C8b10003a3287E621D |
ModularCompliance | 0x1E2e2a848b611C121C86F77C703B8DD32DCdE37A |
| Token ONCHAINID | 0x551676B9061b5324676BBbB5587433454EE54521 |
Bohr Testnet shared addresses:
| Contract | Address |
|---|---|
ONCHAINID Gateway | 0x3B0FE2CD501534cbcbD1F97ec823Da535fc0224f |
IdFactory | 0xb4E03AB303f4c42dbced87b65e1d9f5788EAb369 |
ClaimIssuer | 0x67EC753196D6Cd1B65a29b6ff9c1bE41E554a5a7 |
TREXGateway | 0x82e1Fa8022BA1dcFC05061E0A1CE828C189108A9 |
Eligibility and rules:
| Item | Address / value |
|---|---|
KYC_APPROVED Topic | 7151541257903274562366177585585886223835338030660388602590463654390840817671 |
| Claim scheme | 1 |
| Country Allowlist module | 0x532bFc29B0D9a281084203303a8E687259D57149 |
| Allowed country codes | 156 (China), 702 (Singapore) |
6.3.2 BOT Mainnet demo addresses
Asset Suite:
| Contract | Address |
|---|---|
Token | 0x9115A020821fDFdf265Eea8788a500d17557c370 |
IdentityRegistry | 0x7752dd107CDec2dF5feD585BCC70457c5312021a |
IdentityRegistryStorage | 0xD54fD1381c970F19366cb7c50072295045ff5516 |
ClaimTopicsRegistry | 0x645E33c8c67D3Ea478F87b70d76711E110953FB9 |
TrustedIssuersRegistry | 0x4d96f4Bb96795a3EFeb3Cea43459E2ee4C5ad439 |
ModularCompliance | 0xB8A4b2aFd8318e61cDbaEC07559B49bE6bBE20D5 |
| Token ONCHAINID | 0x3677B0F87892B10F1341DB3348dFEcb9CE039146 |
Shared platform addresses:
| Contract | Address |
|---|---|
ONCHAINID Gateway | 0xa6B4E53128915E0f980DD455De9F70B6Dc61E1ee |
IdFactory | 0x124ED389600870D1a5f86f657896Add3E623ca9e |
ClaimIssuer | 0x3950eB42733b3eF6744E8788C741788CCF6c84b3 |
TREX ImplementationAuthority | 0x3E19c7f1e6E53125fF205591fc516e25541d21ff |
ONCHAINID ImplementationAuthority | 0x992CCAC1A33EFdb58b9cfD49CF510d5db15Fc249 |
IAFactory | 0xf3BdD013eb6c49C23f5b8E70bE5F6C8c98c76028 |
TREXFactory | 0xf622AbFDf470912CCb86A54d52D2430951a3e956 |
TREXGateway | 0x3c209A13fc9E91B060C7672874895E117a1A3Ab5 |
Eligibility and rules:
| Item | Address / value |
|---|---|
KYC_APPROVED Topic | 7151541257903274562366177585585886223835338030660388602590463654390840817671 |
| Claim scheme | 1 |
| Country Allowlist module | 0xA41A626bcBF57B3695539f6516E3D482dCFAEDEC |
| Allowed country codes | 156 (China), 702 (Singapore) |
The BOT Mainnet demo Suite uses a Country Allowlist module that has not completed a production audit. It is only for technical capability verification and must not directly carry real assets.
Machine-readable configurations:
6.4 Public query wallets
These addresses are registered in the test asset's IdentityRegistry and can be used for read-only integration:
| Sample | Wallet | ONCHAINID | Country | Expected eligibility |
|---|---|---|---|---|
| Alice | 0xb450970Eece71044acF7cB7f53Ee51e3BC2EA922 | 0xfAB41B1DfD098F439D75cEA0cb48f70B46052544 | 156 | true |
| Bob | 0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F | 0xd3b816e7494e918297E3553d65488a7E0682C6A3 | 702 | true |
| Cathy | 0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe78 | 0xb7522A6649a20A0820E292D8D0f35aB792812011 | 156 | true |
These are public query samples, and their private keys are not provided. Use your own test wallet and request test eligibility or assets for write operations.
The same public wallets are independently registered in the BOT Mainnet capability-demo Suite:
| Sample | Wallet | BOT Mainnet ONCHAINID | Country | isVerified |
|---|---|---|---|---|
| Alice | 0xb450970Eece71044acF7cB7f53Ee51e3BC2EA922 | 0x95d0ae16d03A26BA6a0dA844938C5c50e16e407C | 156 | true |
| Bob | 0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F | 0x3F5059a5eadc24c27809f481e97Bef4834DB4272 | 702 | true |
| Cathy | 0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe78 | 0x29E0cb659BEd6baF5a6Dc19C50cB41dbBdd41194 | 156 | true |
Use an unregistered wallet you control for negative tests. Expected results are contains == false and isVerified == false. Do not use the zero address as a real business wallet.
6.5 Step 1: Validate network and contracts
The JavaScript examples use ethers.js v5. For v6, use the v6 imports for JsonRpcProvider, formatUnits, and parseUnits.
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');
}
Validate Chain ID before every write operation.
6.6 Step 2: Read asset information
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,
});
Acceptance requirements:
- name and symbol match published asset information;
decimalsis readable;identityRegistry()matches configuration;compliance()matches the address table.
6.7 Step 3: Query balance and eligibility
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,
});
For the public test wallets, contains and verified should be true; identity and country should match the table.
6.8 Step 4: Query events
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),
});
}
A long-running indexer should query in segments and resume from checkpoints to avoid oversized requests.
6.9 Write-operation integration
Different operations require different signers:
| Operation | Signer | Prerequisite |
|---|---|---|
| Create ONCHAINID | Investor test wallet | Test gas |
addClaim | ONCHAINID management wallet | Test Claim signature obtained |
registerIdentity | Test IR Agent | Integration support from issuer or platform |
mint / burn | Test Token Agent | Integration support from issuer or platform |
transfer | Token holder | Both parties satisfy eligibility and rules |
Ordinary developers do not need platform or issuer Agent private keys. Operations requiring Agent authority are performed by the target asset issuer. Use your own wallet for investor operations and request test gas, registration, or test assets through public support channels. Never transmit a private key.
Transfer precheck and submission
The public PRWA demo assets on Bohr Testnet and BOT Mainnet are currently paused, so ordinary holder transfers cannot execute. The code below demonstrates the complete integration. Send transfer only after the Token Agent calls unpause().
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 must be the Token holder
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();
A precheck cannot guarantee success. State can change between submission and confirmation, and the receipt is final.
6.10 Launch acceptance checklist
Environment
- Testnet and mainnet use independent configuration;
- Chain ID is strictly validated at startup;
- RPC, explorer, and contract addresses come from official release information;
- Mainnet configuration cannot automatically fall back to test addresses.
Contract reads
- Token name, symbol, and
decimalsare correct; - Token-bound IR and MC match the address table;
- Addresses contain contract bytecode;
- ABI matches the deployed version.
Amounts and transactions
- Every amount uses
parseUnits/formatUnits; - Write operations distinguish submitted, confirmed, and failed states;
- Transaction hashes link to the correct explorer;
- Failed transactions do not update final business state;
- Retry logic does not duplicate mint or burn submissions.
Identity and compliance
- Eligibility is queried through the target asset's IR;
containsandisVerifiedare not conflated;- Multi-asset applications do not reuse the wrong IR;
- Plaintext KYC stays off-chain;
- Compliance rejections are explained clearly to users.
Security
- No private keys exist in the frontend, repository, or logs;
- Agent and Claim Signer use controlled signing;
- Only proxy addresses are used;
- Teams subscribe to platform upgrade and security notices.
6.11 Troubleshooting order
1. Confirm Chain ID
2. Confirm RPC availability
3. Confirm the address has bytecode on the current network
4. Read Token.name / decimals / identityRegistry
5. Query balance, paused, isFrozen
6. Query recipient contains / isVerified
7. Query compliance.canTransfer
8. Confirm signer role
9. Use callStatic / estimateGas for the revert reason
10. Query the receipt and events after submission
When testnet succeeds and mainnet fails, compare network, contract addresses, asset eligibility requirements, and compliance rules first. Do not assume identical configuration.
Previous: 05 · Contract Interfaces and Events · Back to 01 · Project and Integration Overview