이 가이드에서는 Foundry 프로젝트를 생성하고, WKRC 결제 컨트랙트를 작성하고, StableNet 테스트넷에 배포한 다음 Explorer에서 검증합니다.
소요 시간: 약 15분
도구: Foundry (forge, cast)
네트워크: StableNet 테스트넷 (Chain ID 8283)
사전 준비
시작 전에 아래 항목을 확인하세요:
Foundry 설치 — forge --version으로 확인하세요. 없다면 설치합니다:
curl -L https://foundry.paradigm.xyz | bash
foundryup
잔액이 있는 테스트넷 지갑. Faucet에서 무료 KRC를 받으세요:
Faucet 테스트넷 KRC 요청 — 회원가입 불필요.
StableNet은 27,600 Gwei 최소 Priority Fee를 강제합니다. 이 가이드의 배포 커맨드에는 해당 값이 포함되어 있습니다. 이 임계값 미만의 트랜잭션은 거절됩니다.
네트워크 정보
항목 값 Chain ID 8283RPC URL https://api.test.stablenet.networkExplorer explorer.stablenet.network Faucet faucet.stablenet.network WKRC 컨트랙트 0x0000000000000000000000000000000000001000최소 Gas Tip 27,600 Gwei
WKRC는 StableNet의 네이티브 코인으로, NativeCoinAdapter 시스템 컨트랙트를 통해 표준 ERC-20 인터페이스로 노출됩니다. 가스비에 사용되는 잔액과 WKRC.balanceOf()로 조회되는 잔액이 동일합니다.
단계별 가이드
프로젝트 생성
새 Foundry 프로젝트를 만들고 forge-std를 설치합니다: forge init stablenet-demo
cd stablenet-demo
프로젝트 구조: stablenet-demo/
├── src/ # 컨트랙트 소스 파일
├── script/ # 배포 스크립트
├── test/ # 테스트 파일
└── foundry.toml # Foundry 설정
foundry.toml 설정
foundry.toml을 열고 StableNet 테스트넷 RPC 엔드포인트를 추가합니다:[ profile . default ]
src = "src"
out = "out"
libs = [ "lib" ]
[ rpc_endpoints ]
stablenet_testnet = "https://api.test.stablenet.network"
WKRCPayment.sol 작성
기본 제공되는 src/Counter.sol을 삭제하고 src/WKRCPayment.sol을 만듭니다: // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20 ;
interface IERC20 {
function transferFrom ( address from , address to , uint256 amount ) external returns ( bool );
function allowance ( address owner , address spender ) external view returns ( uint256 );
}
/// @title WKRCPayment
/// @notice 승인된 발신자로부터 WKRC 결제를 수락합니다.
/// 발신자는 먼저 이 컨트랙트를 approve해야 합니다 (4단계 참고).
contract WKRCPayment {
/// @dev NativeCoinAdapter (WKRC) — StableNet의 고정 시스템 컨트랙트 주소.
IERC20 public constant WKRC =
IERC20 ( 0x0000000000000000000000000000000000001000 );
event Payment ( address indexed from , address indexed to , uint256 amount );
/// @notice msg.sender로부터 `amount`만큼 WKRC를 가져와 `to`에게 전송합니다.
/// 사전에 WKRC.approve(address(this), amount)가 필요합니다.
function pay ( address to , uint256 amount ) external {
require (WKRC. transferFrom ( msg.sender , to, amount), "WKRC transfer failed" );
emit Payment ( msg.sender , to, amount);
}
/// @notice `owner`가 이 컨트랙트에 승인한 WKRC 한도를 조회합니다.
function allowanceOf ( address owner ) external view returns ( uint256 ) {
return WKRC. allowance (owner, address ( this ));
}
}
컴파일로 오류가 없는지 확인합니다: 예상 출력: [⠒] Compiling...
[⠢] Compiling 1 files with 0.8.x
[⠆] Solc 0.8.x finished in Xs
Compiler run successful!
배포 스크립트 작성
script/DeployWKRCPayment.s.sol을 만듭니다:// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20 ;
import { Script , console } from "forge-std/Script.sol" ;
import { WKRCPayment } from "../src/WKRCPayment.sol" ;
contract DeployWKRCPayment is Script {
function run () external {
vm. startBroadcast ();
WKRCPayment payment = new WKRCPayment ();
console. log ( "WKRCPayment deployed at:" , address (payment));
vm. stopBroadcast ();
}
}
테스트넷에 배포
프라이빗 키를 내보내고 배포 스크립트를 실행합니다: export PRIVATE_KEY = 0xYourPrivateKey
forge script script/DeployWKRCPayment.s.sol \
--rpc-url https://api.test.stablenet.network \
--private-key $PRIVATE_KEY \
--priority-gas-price 27600000000000 \
--broadcast
PRIVATE_KEY를 버전 관리에 커밋하지 마세요. .env 파일을 사용하고 .gitignore에 추가하세요.
블록 포함까지 최대 1-2분 걸릴 수 있습니다. cast가 타임아웃되더라도 tx는 mempool에 있으며 곧 확정됩니다.
성공 시 출력 예시: Script ran successfully.
== Logs ==
WKRCPayment deployed at: 0xABC...
## Setting up 1 EVM.
Chain 8283
Estimated gas price: X gwei
Estimated total gas used for script: Y
✅ Hash: 0xDEF...
Contract Address: 0xABC...
Block: NNNN
Contract Address 를 저장해 두세요 — 다음 단계에서 필요합니다.
WKRC 승인 후 결제 전송
pay()를 호출하기 전에, 배포한 컨트랙트가 WKRC를 가져갈 수 있도록 지갑에서 먼저 approve해야 합니다. cast로 두 트랜잭션을 전송합니다:export CONTRACT = 0xABC ... # 배포한 WKRCPayment 주소
export RECIPIENT = 0xRecipientAddress
export AMOUNT = 1000000000000000000 # 1 WKRC (소수점 18자리)
export WKRC = 0x0000000000000000000000000000000000001000
# A단계: 컨트랙트가 1 WKRC를 사용할 수 있도록 approve
cast send $WKRC \
"approve(address,uint256)" $CONTRACT $AMOUNT \
--rpc-url https://api.test.stablenet.network \
--private-key $PRIVATE_KEY \
--priority-gas-price 27600000000000
# B단계: pay() 호출로 수신자에게 전송
cast send $CONTRACT \
"pay(address,uint256)" $RECIPIENT $AMOUNT \
--rpc-url https://api.test.stablenet.network \
--private-key $PRIVATE_KEY \
--priority-gas-price 27600000000000
--priority-gas-price를 27,600 Gwei 이상으로 높이지 마세요. gas estimation 단계에서 NativeCoinAdapter balance 부족 오류가 발생할 수 있습니다.
승인 한도가 소진됐는지 확인합니다: export YOUR_ADDRESS = 0x < 지갑-주소 >
cast call $CONTRACT \
"allowanceOf(address)(uint256)" $YOUR_ADDRESS \
--rpc-url https://api.test.stablenet.network
# 결과: 0
Explorer에서 검증
explorer.stablenet.network 에서 컨트랙트 주소를 검색합니다.다음 항목을 확인할 수 있어야 합니다:
배포 트랜잭션
WKRC 컨트랙트의 approve 트랜잭션
내 컨트랙트의 pay 호출, 로그에 Payment 이벤트 포함
Payment 이벤트가 확인되면 WKRC 전송이 온체인에 기록된 것입니다.
무슨 일이 일어났나요?
WKRC 는 StableNet의 KRW 연동 네이티브 코인입니다. 일반 ERC-20 토큰과 동일하게 approve, transferFrom, balanceOf가 표준 인터페이스로 작동합니다.
**WKRCPayment.pay()**는 ERC-20 transferFrom 패턴을 사용합니다. 사용자가 먼저 컨트랙트를 spender로 승인하면, 컨트랙트가 토큰을 가져갑니다. 온체인 결제 흐름의 표준 패턴입니다.
27,600 Gwei Priority Fee 는 네트워크의 GovValidator 거버넌스 컨트랙트가 강제합니다. 컨트랙트 배포를 포함한 모든 트랜잭션에 이 최솟값이 적용됩니다.
트러블슈팅
“replacement transaction underpriced” → --gas-price 80000000000000 --priority-gas-price 35000000000000 조합 사용
“transfer amount exceeds balance” → gas price를 낮춰 재시도
다음 단계
EVM 호환성 레퍼런스 지원되는 eth_* RPC 메서드, StableNet과 Ethereum의 차이점, 수수료 위임(tx type 0x16).
네트워크 정보 전체 네트워크 파라미터 — Chain ID, 시스템 컨트랙트 주소.
API 레퍼런스 curl 및 ethers.js 예제가 포함된 JSON-RPC 메서드.
Explorer 온체인에서 컨트랙트와 트랜잭션을 조회합니다.