06 · 환경 구성 및 인수 검증
공개 환경 연결에 필요한 네트워크 파라미터, 데모 자산, 조회 예제 및 인수 체크리스트를 제공합니다.
6.1 환경 정보
| 항목 | 테스트 환경 | 메인넷 데모 환경 |
|---|---|---|
| 용도 | 개발, 연동, 인수 | BOT Mainnet 기능 검증 |
| 네트워크 | Bohr Testnet | BOT Mainnet |
| 상태 | 사용 가능 | 데모 Suite 배포 완료 |
| Chain ID | 968 | 677 |
| RPC | https://rpc.bohr.life | https://rpc.botchain.ai |
| 탐색기 | https://scan.bohr.life | https://scan.botchain.ai |
| 네이티브 gas 자산 | Test BOT | BOT |
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 |
| Decimals | 6 |
| 자격 Topic | KYC_APPROVED |
| Topic 값 | 7151541257903274562366177585585886223835338030660388602590463654390840817671 |
| Claim scheme | 1 |
| 허용 국가 코드 | 156, 702 |
6.3.1 Bohr Testnet 주소
| 컨트랙트 | 주소 |
|---|---|
Token | 0x922835859623d6F3b99a2742D585E093bBA0a740 |
IdentityRegistry | 0x17e33C394Ab783D6786011d01681D8Ad540C5F53 |
IdentityRegistryStorage | 0xB834C4FEEADC24A9c5B873e8887CD9b54C40E98D |
ClaimTopicsRegistry | 0xbf8adD07A8722d06c218A3A7DD4Bb44d3a90948A |
TrustedIssuersRegistry | 0x94ffe710F64AB382274A53C8b10003a3287E621D |
ModularCompliance | 0x1E2e2a848b611C121C86F77C703B8DD32DCdE37A |
| Token ONCHAINID | 0x551676B9061b5324676BBbB5587433454EE54521 |
| 공유 컨트랙트 | 주소 |
|---|---|
ONCHAINID Gateway | 0x3B0FE2CD501534cbcbD1F97ec823Da535fc0224f |
IdFactory | 0xb4E03AB303f4c42dbced87b65e1d9f5788EAb369 |
ClaimIssuer | 0x67EC753196D6Cd1B65a29b6ff9c1bE41E554a5a7 |
TREXGateway | 0x82e1Fa8022BA1dcFC05061E0A1CE828C189108A9 |
| Country Allowlist | 0x532bFc29B0D9a281084203303a8E687259D57149 |
6.3.2 BOT Mainnet 데모 주소
| 컨트랙트 | 주소 |
|---|---|
Token | 0x9115A020821fDFdf265Eea8788a500d17557c370 |
IdentityRegistry | 0x7752dd107CDec2dF5feD585BCC70457c5312021a |
IdentityRegistryStorage | 0xD54fD1381c970F19366cb7c50072295045ff5516 |
ClaimTopicsRegistry | 0x645E33c8c67D3Ea478F87b70d76711E110953FB9 |
TrustedIssuersRegistry | 0x4d96f4Bb96795a3EFeb3Cea43459E2ee4C5ad439 |
ModularCompliance | 0xB8A4b2aFd8318e61cDbaEC07559B49bE6bBE20D5 |
| Token ONCHAINID | 0x3677B0F87892B10F1341DB3348dFEcb9CE039146 |
| 공유 플랫폼 | 주소 |
|---|---|
ONCHAINID Gateway | 0xa6B4E53128915E0f980DD455De9F70B6Dc61E1ee |
IdFactory | 0x124ED389600870D1a5f86f657896Add3E623ca9e |
ClaimIssuer | 0x3950eB42733b3eF6744E8788C741788CCF6c84b3 |
TREX ImplementationAuthority | 0x3E19c7f1e6E53125fF205591fc516e25541d21ff |
ONCHAINID ImplementationAuthority | 0x992CCAC1A33EFdb58b9cfD49CF510d5db15Fc249 |
IAFactory | 0xf3BdD013eb6c49C23f5b8E70bE5F6C8c98c76028 |
TREXFactory | 0xf622AbFDf470912CCb86A54d52D2430951a3e956 |
TREXGateway | 0x3c209A13fc9E91B060C7672874895E117a1A3Ab5 |
| Country Allowlist | 0xA41A626bcBF57B3695539f6516E3D482dCFAEDEC |
BOT Mainnet 데모 Suite의 Country Allowlist 모듈은 프로덕션 감사를 완료하지 않았습니다. 기술 기능 검증 전용이며 실제 자산을 직접 수용해서는 안 됩니다.
Bohr Testnet JSON · BOT Mainnet JSON
6.4 공개 조회 지갑
| 샘플 | 지갑 | Testnet ONCHAINID | 국가 | 자격 |
|---|---|---|---|---|
| Alice | 0xb450970Eece71044acF7cB7f53Ee51e3BC2EA922 | 0xfAB41B1DfD098F439D75cEA0cb48f70B46052544 | 156 | true |
| Bob | 0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F | 0xd3b816e7494e918297E3553d65488a7E0682C6A3 | 702 | true |
| Cathy | 0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe78 | 0xb7522A6649a20A0820E292D8D0f35aB792812011 | 156 | true |
개인 키는 제공되지 않습니다. write 작업에는 본인 지갑을 사용하십시오.
| 샘플 | 지갑 | BOT Mainnet ONCHAINID | 국가 | isVerified |
|---|---|---|---|---|
| Alice | 0xb450970Eece71044acF7cB7f53Ee51e3BC2EA922 | 0x95d0ae16d03A26BA6a0dA844938C5c50e16e407C | 156 | true |
| Bob | 0xfc996C5378fe0cd03Fd4296075abc07B16b2B12F | 0x3F5059a5eadc24c27809f481e97Bef4834DB4272 | 702 | true |
| Cathy | 0x2b981b025397AE1AFca6f8DC7A2f5C7A868dEe78 | 0x29E0cb659BEd6baF5a6Dc19C50cB41dbBdd41194 | 156 | true |
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,
});
공개 테스트 지갑에서는 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 |
addClaim | ONCHAINID 관리 지갑 | test Claim 서명 |
registerIdentity | test IR Agent | 발행자 또는 플랫폼 지원 |
mint / burn | test Token Agent | 발행자 또는 플랫폼 지원 |
transfer | Token 보유자 | 양측의 자격 및 규칙 충족 |
일반 개발자는 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에서 실패하면 네트워크, 컨트랙트 주소, 자산 자격 요건, 컴플라이언스 규칙을 비교하십시오. 동일한 구성이라고 가정하지 마십시오.