본문으로 건너뛰기

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

BOT Mainnet 기능 데모:

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

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

6.3 공개 데모 자산​

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

6.3.1 Bohr Testnet 주소​

컨트랙트주소
Token0xA7aEb5cc99C558E7A237aA1B3483e71E59e66036
IdentityRegistry0x5C3f759a7Fe22331750E6CA3293a96C34daE3417
IdentityRegistryStorage0x3fae839c1642BDcFBE94b5A8088a56F8fC787707
ClaimTopicsRegistry0x042a938B2cE1e1AdF2b4101D9730722d5c75F773
TrustedIssuersRegistry0x2323413Abd0BF763ed2f3bE9c319A7a05985C2ff
ModularCompliance0x3aa6A97b6d61039A65C5AEf5649523732379aE9C
Token ONCHAINID0x7528D8d23fE511c658564E1cB346ca7D9B72788d
공유 컨트랙트주소
ONCHAINID Gateway0xb62261a7193B6F37A5c6A2c242aD542C840d6f5a
IdFactory0xEa3216f557B6598F53fCcd146E63912c4c1be02C
ClaimIssuer0xEa7E7c89F56f167E12570E15d648460104510d47
TREXGateway0xf73CB3A2830bef113Be01CD5eE5C99d21e43107B
Country Allowlist0x5Da275CcC6eAb8bd43800AB54B9c8Ec48129aB30

6.3.2 BOT Mainnet 데모 주소​

컨트랙트주소
Token0x58F96a44594C9C5C8DE282Eeb75208B5f11C0c36
IdentityRegistry0xe0b6FbEa0a9af21ddd455D6cA776478cAbe7c141
IdentityRegistryStorage0x344D8266058FcE40D8229F8eF118da5ee6f88c54
ClaimTopicsRegistry0x7778b431b73F57722cBa7D6205F45858D789ACB1
TrustedIssuersRegistry0xC3216525E30E0369B49d75105b3591b647595d63
ModularCompliance0xE0444e1374d5ca5EdF3DAb0bEB54165c8D1968a4
Token ONCHAINID0x651b1Ad7F96A16228e41cDf6c2eb2282C267dE6B
공유 플랫폼주소
ONCHAINID Gateway0xFCF1E45f42aAa5CccA0A035f7a946BC8Fd55Fac6
IdFactory0xE97099950DFe98535927a650658c0E964c8d6f67
ClaimIssuer0xD40b0c8dC960755C2586601Ff505F036FfE9BF59
TREX ImplementationAuthority0x658B369FFfE2474310224c6DAe2f320A27A76D2E
ONCHAINID ImplementationAuthority0xA5aeF4767C10fE47d1097b65Da55357B29bbbfF6
IAFactory0x394f0720F519e25d63065690d8565018404b69E8
TREXFactory0xCdc6C38438DEd768028EecFd50a8aAf3ad755a92
TREXGateway0x7a8bea060430D7A6B5ac816137733570425Bb777
Country Allowlist0x8AD1cdF24Bf38E9b649294fb01e2A9E83F0E19E5

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

Bohr Testnet JSON · BOT Mainnet JSON

6.4 공개 조회 지갑​

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

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

최신 BOT Mainnet 기능 데모 Suite는 컨트랙트 배포와 소스 코드 검증만 완료했습니다. 공개 지갑의 ONCHAINID 생성, Claim 발급, IdentityRegistry 등록은 아직 수행되지 않았습니다. 따라서 현재 isVerified == true인 mainnet 샘플 지갑을 공개하지 않으며, 독립적인 운영 초기화와 온체인 인수 검증이 완료된 후 이 절을 보완합니다.

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

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

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을 통해 자격을 조회합니다.
  • contains와 isVerified를 구분합니다.
  • 다중 자산 앱에서 잘못된 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 · 프로젝트 및 연동 개요로