Skip to main content

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

ItemTest environmentMainnet demo environment
PurposeDevelopment, integration, acceptanceBOT Mainnet capability verification
NetworkBohr TestnetBOT Mainnet
StatusAvailableDemo Suite deployed
Chain ID968677
RPChttps://rpc.bohr.lifehttps://rpc.botchain.ai
Explorerhttps://scan.bohr.lifehttps://scan.botchain.ai
Native gas assetTest BOTBOT

Contract addresses, Token balances, identities, and Claims are not shared between Bohr Testnet and BOT Mainnet.


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.

ItemValue
NameProject Real World Asset
SymbolPRWA
Decimals6
Eligibility TopicKYC_APPROVED
Topic value7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim scheme1
Allowed country codes156, 702

6.3.1 Bohr Testnet addresses

Asset Suite:

ContractAddress
Token0x922835859623d6F3b99a2742D585E093bBA0a740
IdentityRegistry0x17e33C394Ab783D6786011d01681D8Ad540C5F53
IdentityRegistryStorage0xB834C4FEEADC24A9c5B873e8887CD9b54C40E98D
ClaimTopicsRegistry0xbf8adD07A8722d06c218A3A7DD4Bb44d3a90948A
TrustedIssuersRegistry0x94ffe710F64AB382274A53C8b10003a3287E621D
ModularCompliance0x1E2e2a848b611C121C86F77C703B8DD32DCdE37A
Token ONCHAINID0x551676B9061b5324676BBbB5587433454EE54521

Bohr Testnet shared addresses:

ContractAddress
ONCHAINID Gateway0x3B0FE2CD501534cbcbD1F97ec823Da535fc0224f
IdFactory0xb4E03AB303f4c42dbced87b65e1d9f5788EAb369
ClaimIssuer0x67EC753196D6Cd1B65a29b6ff9c1bE41E554a5a7
TREXGateway0x82e1Fa8022BA1dcFC05061E0A1CE828C189108A9

Eligibility and rules:

ItemAddress / value
KYC_APPROVED Topic7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim scheme1
Country Allowlist module0x532bFc29B0D9a281084203303a8E687259D57149
Allowed country codes156 (China), 702 (Singapore)

6.3.2 BOT Mainnet demo addresses

Asset Suite:

ContractAddress
Token0x9115A020821fDFdf265Eea8788a500d17557c370
IdentityRegistry0x7752dd107CDec2dF5feD585BCC70457c5312021a
IdentityRegistryStorage0xD54fD1381c970F19366cb7c50072295045ff5516
ClaimTopicsRegistry0x645E33c8c67D3Ea478F87b70d76711E110953FB9
TrustedIssuersRegistry0x4d96f4Bb96795a3EFeb3Cea43459E2ee4C5ad439
ModularCompliance0xB8A4b2aFd8318e61cDbaEC07559B49bE6bBE20D5
Token ONCHAINID0x3677B0F87892B10F1341DB3348dFEcb9CE039146

Shared platform addresses:

ContractAddress
ONCHAINID Gateway0xa6B4E53128915E0f980DD455De9F70B6Dc61E1ee
IdFactory0x124ED389600870D1a5f86f657896Add3E623ca9e
ClaimIssuer0x3950eB42733b3eF6744E8788C741788CCF6c84b3
TREX ImplementationAuthority0x3E19c7f1e6E53125fF205591fc516e25541d21ff
ONCHAINID ImplementationAuthority0x992CCAC1A33EFdb58b9cfD49CF510d5db15Fc249
IAFactory0xf3BdD013eb6c49C23f5b8E70bE5F6C8c98c76028
TREXFactory0xf622AbFDf470912CCb86A54d52D2430951a3e956
TREXGateway0x3c209A13fc9E91B060C7672874895E117a1A3Ab5

Eligibility and rules:

ItemAddress / value
KYC_APPROVED Topic7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim scheme1
Country Allowlist module0xA41A626bcBF57B3695539f6516E3D482dCFAEDEC
Allowed country codes156 (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:

SampleWalletONCHAINIDCountryExpected eligibility
Alice0xb450970Eece71044acF7cB7f53Ee51e3BC2EA9220xfAB41B1DfD098F439D75cEA0cb48f70B46052544156true
Bob0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F0xd3b816e7494e918297E3553d65488a7E0682C6A3702true
Cathy0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe780xb7522A6649a20A0820E292D8D0f35aB792812011156true

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:

SampleWalletBOT Mainnet ONCHAINIDCountryisVerified
Alice0xb450970Eece71044acF7cB7f53Ee51e3BC2EA9220x95d0ae16d03A26BA6a0dA844938C5c50e16e407C156true
Bob0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F0x3F5059a5eadc24c27809f481e97Bef4834DB4272702true
Cathy0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe780x29E0cb659BEd6baF5a6Dc19C50cB41dbBdd41194156true

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;
  • decimals is 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:

OperationSignerPrerequisite
Create ONCHAINIDInvestor test walletTest gas
addClaimONCHAINID management walletTest Claim signature obtained
registerIdentityTest IR AgentIntegration support from issuer or platform
mint / burnTest Token AgentIntegration support from issuer or platform
transferToken holderBoth 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 decimals are 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;
  • contains and isVerified are 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