본문으로 건너뛰기

06 · 환경 구성 및 인수 검증

공개 환경 연결에 필요한 네트워크 파라미터, 데모 자산, 조회 예제 및 인수 체크리스트를 제공합니다.

6.1 환경 정보

항목테스트 환경메인넷 데모 환경
용도개발, 연동, 인수BOT Mainnet 기능 검증
네트워크Bohr TestnetBOT Mainnet
상태사용 가능데모 Suite 배포 완료
Chain ID968677
RPChttps://rpc.bohr.lifehttps://rpc.botchain.ai
탐색기https://scan.bohr.lifehttps://scan.botchain.ai
네이티브 gas 자산Test BOTBOT

Bohr Testnet과 BOT Mainnet의 컨트랙트 주소, 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 Mainnet 기능 데모:

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

메인넷 구성이 누락되거나 검증에 실패할 때 테스트넷 주소로 자동 fallback하지 마십시오. 메인넷 JSON을 참조하십시오.

6.3 공개 데모 자산

항목
이름Project Real World Asset
심볼PRWA
Decimals6
자격 TopicKYC_APPROVED
Topic 값7151541257903274562366177585585886223835338030660388602590463654390840817671
Claim scheme1
허용 국가 코드156, 702

6.3.1 Bohr Testnet 주소

컨트랙트주소
Token0x922835859623d6F3b99a2742D585E093bBA0a740
IdentityRegistry0x17e33C394Ab783D6786011d01681D8Ad540C5F53
IdentityRegistryStorage0xB834C4FEEADC24A9c5B873e8887CD9b54C40E98D
ClaimTopicsRegistry0xbf8adD07A8722d06c218A3A7DD4Bb44d3a90948A
TrustedIssuersRegistry0x94ffe710F64AB382274A53C8b10003a3287E621D
ModularCompliance0x1E2e2a848b611C121C86F77C703B8DD32DCdE37A
Token ONCHAINID0x551676B9061b5324676BBbB5587433454EE54521
공유 컨트랙트주소
ONCHAINID Gateway0x3B0FE2CD501534cbcbD1F97ec823Da535fc0224f
IdFactory0xb4E03AB303f4c42dbced87b65e1d9f5788EAb369
ClaimIssuer0x67EC753196D6Cd1B65a29b6ff9c1bE41E554a5a7
TREXGateway0x82e1Fa8022BA1dcFC05061E0A1CE828C189108A9
Country Allowlist0x532bFc29B0D9a281084203303a8E687259D57149

6.3.2 BOT Mainnet 데모 주소

컨트랙트주소
Token0x9115A020821fDFdf265Eea8788a500d17557c370
IdentityRegistry0x7752dd107CDec2dF5feD585BCC70457c5312021a
IdentityRegistryStorage0xD54fD1381c970F19366cb7c50072295045ff5516
ClaimTopicsRegistry0x645E33c8c67D3Ea478F87b70d76711E110953FB9
TrustedIssuersRegistry0x4d96f4Bb96795a3EFeb3Cea43459E2ee4C5ad439
ModularCompliance0xB8A4b2aFd8318e61cDbaEC07559B49bE6bBE20D5
Token ONCHAINID0x3677B0F87892B10F1341DB3348dFEcb9CE039146
공유 플랫폼주소
ONCHAINID Gateway0xa6B4E53128915E0f980DD455De9F70B6Dc61E1ee
IdFactory0x124ED389600870D1a5f86f657896Add3E623ca9e
ClaimIssuer0x3950eB42733b3eF6744E8788C741788CCF6c84b3
TREX ImplementationAuthority0x3E19c7f1e6E53125fF205591fc516e25541d21ff
ONCHAINID ImplementationAuthority0x992CCAC1A33EFdb58b9cfD49CF510d5db15Fc249
IAFactory0xf3BdD013eb6c49C23f5b8E70bE5F6C8c98c76028
TREXFactory0xf622AbFDf470912CCb86A54d52D2430951a3e956
TREXGateway0x3c209A13fc9E91B060C7672874895E117a1A3Ab5
Country Allowlist0xA41A626bcBF57B3695539f6516E3D482dCFAEDEC

BOT Mainnet 데모 Suite의 Country Allowlist 모듈은 프로덕션 감사를 완료하지 않았습니다. 기술 기능 검증 전용이며 실제 자산을 직접 수용해서는 안 됩니다.

Bohr Testnet JSON · BOT Mainnet JSON

6.4 공개 조회 지갑

샘플지갑Testnet ONCHAINID국가자격
Alice0xb450970Eece71044acF7cB7f53Ee51e3BC2EA9220xfAB41B1DfD098F439D75cEA0cb48f70B46052544156true
Bob0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F0xd3b816e7494e918297E3553d65488a7E0682C6A3702true
Cathy0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe780xb7522A6649a20A0820E292D8D0f35aB792812011156true

개인 키는 제공되지 않습니다. write 작업에는 본인 지갑을 사용하십시오.

샘플지갑BOT Mainnet ONCHAINID국가isVerified
Alice0xb450970Eece71044acF7cB7f53Ee51e3BC2EA9220x95d0ae16d03A26BA6a0dA844938C5c50e16e407C156true
Bob0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F0x3F5059a5eadc24c27809f481e97Bef4834DB4272702true
Cathy0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe780x29E0cb659BEd6baF5a6Dc19C50cB41dbBdd41194156true

negative test에는 본인이 관리하는 미등록 지갑을 사용합니다. 예상 결과는 contains == false, isVerified == false입니다. 영 주소를 실제 비즈니스 지갑으로 사용하지 마십시오.

6.5 1단계: 네트워크와 컨트랙트 검증

예제는 ethers.js v5를 사용합니다. v6에서는 JsonRpcProvider, formatUnits, parseUnits의 v6 import를 사용하십시오.

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';
if (await provider.getCode(TOKEN) === '0x') {
throw new Error('Token contract not found on current network');
}

write 작업 전에 매번 Chain ID를 검증합니다.

6.6 2단계: 자산 정보 읽기

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 3단계: 잔액과 자격 조회

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

공개 테스트 지갑에서는 containsverifiedtrue이고 신원 및 국가 코드가 표와 일치해야 합니다.

6.8 4단계: 이벤트 조회

const latest = await provider.getBlockNumber();
const logs = await token.queryFilter(
token.filters.Transfer(), 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),
});
}

장기 실행 인덱서는 범위를 나눠 조회하고 checkpoint에서 재개하여 과도한 요청을 방지합니다.

6.9 write 작업 연동

작업서명자전제
ONCHAINID 생성투자자 테스트 지갑test gas
addClaimONCHAINID 관리 지갑test Claim 서명
registerIdentitytest IR Agent발행자 또는 플랫폼 지원
mint / burntest Token Agent발행자 또는 플랫폼 지원
transferToken 보유자양측의 자격 및 규칙 충족

일반 개발자는 Agent 개인 키가 필요하지 않습니다. Agent 권한 작업은 대상 자산 발행자가 수행합니다. 투자자 작업에는 본인 지갑을 사용하고 개인 키를 전송하지 마십시오.

공개 PRWA 데모 자산은 현재 pause 상태입니다. 일반 transfer는 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');
}
if (!await ir.isVerified(recipient)) {
throw new Error('Recipient is not verified for this asset');
}
const compliance = new ethers.Contract(
await token.compliance(),
['function canTransfer(address,address,uint256) view returns (bool)'],
provider
);
if (!await compliance.canTransfer(senderAddress, recipient, amount)) {
throw new Error('Transfer rejected by compliance rules');
}
const receipt =
await (await token.connect(signer).transfer(recipient, amount)).wait();
console.log(receipt.transactionHash);

approve + transferFrom

await (await token.connect(ownerSigner).approve(spender, amount)).wait();
await (await token.connect(spenderSigner)
.transferFrom(ownerAddress, recipient, amount)).wait();

사전 검사 이후에도 상태가 바뀔 수 있으므로 영수증이 최종 결과입니다.

6.10 출시 인수 체크리스트

환경

  • Testnet과 Mainnet 구성을 독립적으로 유지합니다.
  • 시작 시 Chain ID를 엄격히 검증합니다.
  • RPC, 탐색기, 컨트랙트 주소는 공식 릴리스 정보를 사용합니다.
  • Mainnet 구성이 Testnet 주소로 자동 fallback하지 않습니다.

컨트랙트 읽기

  • Token 이름, 심볼, decimals가 정확합니다.
  • Token에 연결된 IR과 MC가 주소표와 일치합니다.
  • 주소에 컨트랙트 bytecode가 있습니다.
  • ABI가 배포 버전과 일치합니다.

금액과 트랜잭션

  • 모든 금액에 parseUnits / formatUnits를 사용합니다.
  • write 작업에서 submitted, confirmed, failed 상태를 구분합니다.
  • tx hash가 올바른 탐색기를 가리킵니다.
  • 실패한 트랜잭션은 최종 비즈니스 상태를 갱신하지 않습니다.
  • retry 로직이 mint 또는 burn을 중복 제출하지 않습니다.

신원과 컴플라이언스

  • 대상 자산의 IR을 통해 자격을 조회합니다.
  • containsisVerified를 구분합니다.
  • 다중 자산 앱에서 잘못된 IR을 재사용하지 않습니다.
  • KYC 평문은 오프체인에 유지합니다.
  • 컴플라이언스 거부 사유를 사용자에게 명확히 설명합니다.

보안

  • 개인 키를 프런트엔드, 저장소 또는 로그에 두지 않습니다.
  • Agent와 Claim Signer는 통제된 서명을 사용합니다.
  • 프록시 주소만 사용합니다.
  • 플랫폼 업그레이드 및 보안 공지를 구독합니다.

6.11 문제 해결 순서

1. Chain ID 확인
2. RPC 가용성 확인
3. 현재 네트워크의 주소에 bytecode가 있는지 확인
4. Token.name / decimals / identityRegistry 읽기
5. balance / paused / isFrozen 조회
6. 수신자 contains / isVerified 조회
7. compliance.canTransfer 조회
8. 서명자 역할 확인
9. callStatic / estimateGas로 revert 사유 확인
10. 제출 후 영수증과 이벤트 조회

Testnet에서 성공하고 Mainnet에서 실패하면 네트워크, 컨트랙트 주소, 자산 자격 요건, 컴플라이언스 규칙을 비교하십시오. 동일한 구성이라고 가정하지 마십시오.

이전: 05 · 컨트랙트 인터페이스 및 이벤트 · 01 · 프로젝트 및 연동 개요로