Hello world contract와 상호작용하기
이 튜토리얼에서는 Midnight Preprod 네트워크에 배포된 hello world contract와 상호작용하는 CLI를 구축합니다. contract circuit 호출, public state 읽기, DUST 잔액 관리 방법을 다룹니다.
Prerequisites
시작하기 전에 다음을 준비하세요:
deployment.json에 contract address가 저장된 배포 완료 hello world contract. hello world contract 배포하기 튜토리얼을 먼저 완료해야 합니다.- 64자리 wallet seed
npm install로 모든 의존성이 설치된 상태- proof server 실행 중. 설정 방법은 proof server 가이드를 참조하세요.
- 배포 과정에서 생성한
src/utils.ts파일
hello world contract 배포하기 튜토리얼을 완료했다면 프로젝트 구조가 다음과 같아야 합니다:
my-midnight-contract/
├── contracts/
│ └── managed/
│ └── hello-world/
│ ├── compiler/
│ ├── contract/
│ ├── keys/
│ └── zkir/
│ └── hello-world.compact
├── src/
│ ├── deploy.ts
│ └── utils.ts
├── deployment.json
├── package.json
└── tsconfig.json
Add CLI script to package.json
CLI 애플리케이션 실행을 위한 script를 추가합니다.
package.json의 scripts 섹션을 다음과 같이 업데이트하세요:
{
"scripts": {
"compile": "compact compile contracts/hello-world.compact contracts/managed/hello-world",
"build": "tsc",
"deploy": "tsx src/deploy.ts",
"cli": "tsx src/cli.ts"
}
}
CLI 애플리케이션을 실행하는 cli script가 추가됩니다.
Create the CLI application file
컨트랙트 작업을 위한 대화형 인터페이스를 제공하는 CLI 파일을 작성합니다.
src/cli.ts를 생성하고 다음 코드를 추가하세요:
import { createInterface } from 'node:readline/promises';
import { stdin, stdout } from 'node:process';
import * as fs from 'node:fs';
import * as Rx from 'rxjs';
// Midnight SDK import
import { findDeployedContract } from '@midnight-ntwrk/midnight-js-contracts';
// utils.ts 파일의 공유 유틸리티
import {
createWallet,
createProviders,
compiledContract,
HelloWorld
} from './utils.js';
각 import의 역할:
- readline: 대화형 CLI 생성
- fs: 배포 설정 파일 읽기
- Rx (RxJS): 지갑 동기화를 위한 반응형 상태 관리
- findDeployedContract: 기존 배포된 컨트랙트에 연결
- 공유 유틸리티: 배포 시 사용한 지갑 및 provider 설정 재사용
Implement contract connection logic
cli.ts에 배포 정보 로드, 지갑 생성, 배포된 컨트랙트 연결을 담당하는 메인 함수를 추가하세요.
// ─── 메인 CLI 스크립트 ───────────────────────────────────────────────────────────
async function main() {
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ Hello World Contract CLI (Preprod) ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
// deployment.json 확인
if (!fs.existsSync('deployment.json')) {
console.error('No deployment.json found! Run `npm run deploy` first.\n');
process.exit(1);
}
const deployment = JSON.parse(fs.readFileSync('deployment.json', 'utf-8'));
console.log(` Contract: ${deployment.contractAddress}\n`);
const rl = createInterface({ input: stdin, output: stdout });
try {
// wallet seed(지갑 시드) 입력 받기
const seed = await rl.question(' Enter your wallet seed: ');
console.log('\n Connecting to Midnight Preprod...');
const walletCtx = await createWallet(seed.trim());
console.log(' Syncing wallet...');
await Rx.firstValueFrom(
walletCtx.wallet.state().pipe(
Rx.throttleTime(5000),
Rx.filter((s) => s.isSynced)
)
);
console.log(' Setting up providers...');
const providers = await createProviders(walletCtx);
console.log(' Joining contract...');
const contract = await findDeployedContract(providers, {
contractAddress: deployment.contractAddress,
compiledContract,
privateStateId: 'helloWorldState',
initialPrivateState: {},
});
console.log(' Connected!\n');
이 코드가 수행하는 작업:
deployment.json에 컨트랙트 정보가 있는지 확인- wallet seed를 입력받아 지갑 접근 복원
utils.ts의 공유 유틸리티로 지갑 인스턴스 생성- 지갑을 Preprod와 동기화하여 최신 블록체인 상태 확보
- 컨트랙트 상호작용에 필요한 provider 설정
findDeployedContract로 배포된 컨트랙트에 연결. 이 함수는:- 지정된 주소에 컨트랙트 존재 여부 확인
- 컨트랙트의 현재 상태 로드
- 로컬 private state 저장소 설정
- circuit 호출이 준비된 컨트랙트 인스턴스 반환
Build the interactive menu
사용자에게 옵션을 표시하고 선택을 처리하는 메인 메뉴 루프를 만듭니다.
cli.ts의 메인 함수에 이어서 추가하세요:
// 메인 메뉴 루프
let running = true;
while (running) {
const dust = (
await Rx.firstValueFrom(
walletCtx.wallet.state().pipe(Rx.filter((s) => s.isSynced))
)
).dust.walletBalance(new Date());
console.log('─────────────────────────────────────────────────────────────────');
console.log(` DUST: ${dust.toLocaleString()}`);
console.log('─────────────────────────────────────────────────────────────────');
const choice = await rl.question(
' [1] Store a message\n [2] Read current message\n [3] Exit\n > '
);
switch (choice.trim()) {
case '1':
try {
const message = await rl.question('\n Enter message: ');
console.log(' Storing message (this may take 20-30 seconds)...\n');
const tx = await contract.callTx.storeMessage(message);
console.log(` ✅ Message stored!`);
console.log(` Transaction: ${tx.public.txId}`);
console.log(` Block: ${tx.public.blockHeight}\n`);
} catch (e) {
console.error(
` ❌ Error: ${e instanceof Error ? e.message : e}\n`
);
}
break;
case '2':
try {
console.log('\n Reading message from blockchain...');
const state = await providers.publicDataProvider.queryContractState(
deployment.contractAddress
);
if (state) {
const ledgerState = HelloWorld.ledger(state.data);
console.log(
` Current message: "${ledgerState.message || '(empty)'}"\n`
);
} else {
console.log(' No message found.\n');
}
} catch (e) {
console.error(
` ❌ Error: ${e instanceof Error ? e.message : e}\n`
);
}
break;
case '3':
running = false;
break;
}
}
await walletCtx.wallet.stop();
console.log('\n Goodbye!\n');
} finally {
rl.close();
}
}
main().catch(console.error);
CLI 애플리케이션의 메인 진입점입니다. 배포 정보를 로드하고, 지갑을 생성하고, 컨트랙트에 연결한 뒤 메인 메뉴 루프에서 사용자 입력을 처리합니다.
Run the CLI application
이제 배포된 컨트랙트와 상호작용할 CLI를 실행할 수 있습니다.
다음 명령어로 CLI를 시작하세요:
npm run cli
CLI가 다음 순서로 진행됩니다:
- 배포 시 사용한 wallet seed를 입력합니다.
- 지갑이 Preprod와 동기화될 때까지 대기합니다 (보통 수 초 소요).
- 메뉴에 표시되는 DUST 잔액을 확인합니다.
- 원하는 작업을 선택합니다.
Store message example session
╔══════════════════════════════════════════════════════════╗
║ Hello World Contract CLI (Preprod) ║
╚══════════════════════════════════════ ════════════════════╝
Contract: 0x1234567890abcdef...
Enter your wallet seed: ********************************
Connecting to Midnight Preprod...
Syncing wallet...
Setting up providers...
Joining contract...
Connected!
─────────────────────────────────────────────────────────────────
DUST: 1,500,000
─────────────────────────────────────────────────────── ──────────
[1] Store a message
[2] Read current message
[3] Exit
> 1
Enter message: Welcome to the Midnight Network!
Storing message (this may take 20-30 seconds)...
✅ Message stored!
Transaction: 0xabcd1234...
Block: 123456
Read message example session
─────────────────────────────────────────────────────────────────
DUST: 1,450,000
─────────────────────────────────────────────────────────────────
[1] Store a message
[2] Read current message
[3] Exit
> 2
Reading message from blockchain...
Current message: "Welcome to the Midnight Network!"
Troubleshooting
자주 발생하는 문제와 해결 방법입니다.
Contract not found error
"No deployment.json found" 메시지가 표시되면:
npm run deploy가 성공적으로 완료되었는지 확인하세요.- 프로젝트 루트에
deployment.json이 있는지 확인하세요. - 올바른 디렉토리에서 CLI를 실행하고 있는지 확인하세요.
Insufficient DUST
DUST 부족으로 메시지 저장에 실패하면:
- 메뉴에서 DUST 잔액을 확인하세요.
- 최근 자금을 입금했다면 DUST 생성까지 잠시 기다리세요.
- tNight token이 있는지 확인하세요. DUST는 tNight token에서 생성됩니다.
- Preprod faucet에서 추가 tNight token을 받으세요.
Invalid seed error
wallet seed가 거부되면:
- 올바른 64자리 16진수 seed인지 확인하세요.
- 오타나 불필요한 공백이 없는지 확인하세요.
- 컨트랙트 배포 시 사용한 동일한 seed인지 확인하세요.
Next steps
CLI로 배포된 컨트랙트와 상호작용할 수 있게 되었습니다. 다음으로 DApp용 웹 인터페이스를 구축해 보세요. 컨트랙트의 GUI를 만들려면 React wallet connector 가이드를 참조하세요.