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

BOT Mainnet capability-demo configuration:

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',
},
};

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
Token0xA7aEb5cc99C558E7A237aA1B3483e71E59e66036
IdentityRegistry0x5C3f759a7Fe22331750E6CA3293a96C34daE3417
IdentityRegistryStorage0x3fae839c1642BDcFBE94b5A8088a56F8fC787707
ClaimTopicsRegistry0x042a938B2cE1e1AdF2b4101D9730722d5c75F773
TrustedIssuersRegistry0x2323413Abd0BF763ed2f3bE9c319A7a05985C2ff
ModularCompliance0x3aa6A97b6d61039A65C5AEf5649523732379aE9C
Token ONCHAINID0x7528D8d23fE511c658564E1cB346ca7D9B72788d

Bohr Testnet shared addresses:

ContractAddress
ONCHAINID Gateway0xb62261a7193B6F37A5c6A2c242aD542C840d6f5a
IdFactory0xEa3216f557B6598F53fCcd146E63912c4c1be02C
ClaimIssuer0xEa7E7c89F56f167E12570E15d648460104510d47
TREXGateway0xf73CB3A2830bef113Be01CD5eE5C99d21e43107B

Eligibility and rules:

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

6.3.2 BOT Mainnet demo addresses​

Asset Suite:

ContractAddress
Token0x58F96a44594C9C5C8DE282Eeb75208B5f11C0c36
IdentityRegistry0xe0b6FbEa0a9af21ddd455D6cA776478cAbe7c141
IdentityRegistryStorage0x344D8266058FcE40D8229F8eF118da5ee6f88c54
ClaimTopicsRegistry0x7778b431b73F57722cBa7D6205F45858D789ACB1
TrustedIssuersRegistry0xC3216525E30E0369B49d75105b3591b647595d63
ModularCompliance0xE0444e1374d5ca5EdF3DAb0bEB54165c8D1968a4
Token ONCHAINID0x651b1Ad7F96A16228e41cDf6c2eb2282C267dE6B

Shared platform addresses:

ContractAddress
ONCHAINID Gateway0xFCF1E45f42aAa5CccA0A035f7a946BC8Fd55Fac6
IdFactory0xE97099950DFe98535927a650658c0E964c8d6f67
ClaimIssuer0xD40b0c8dC960755C2586601Ff505F036FfE9BF59
TREX ImplementationAuthority0x658B369FFfE2474310224c6DAe2f320A27A76D2E
ONCHAINID ImplementationAuthority0xA5aeF4767C10fE47d1097b65Da55357B29bbbfF6
IAFactory0x394f0720F519e25d63065690d8565018404b69E8
TREXFactory0xCdc6C38438DEd768028EecFd50a8aAf3ad755a92
TREXGateway0x7a8bea060430D7A6B5ac816137733570425Bb777

Eligibility and rules:

ItemAddress / value
KYC_APPROVED Topic7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim scheme1
Country Allowlist module0x8AD1cdF24Bf38E9b649294fb01e2A9E83F0E19E5
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 latest BOT Mainnet capability-demo Suite has only completed contract deployment and source-code verification. Public wallets have not yet gone through ONCHAINID creation, Claim issuance, or IdentityRegistry registration. Therefore, no mainnet sample wallets with isVerified == true are currently published. This section will be completed after independent operational initialization and onchain acceptance.

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 = '0xA7aEb5cc99C558E7A237aA1B3483e71E59e66036';
const IR = '0x5C3f759a7Fe22331750E6CA3293a96C34daE3417';

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