메인 콘텐츠로 건너뛰기
이 가이드에서는 ethers.js 또는 viem을 StableNet 테스트넷에 연결하고, 잔액을 조회하고, 필수 priority fee를 포함한 트랜잭션을 전송하고, WKRC ERC-20 컨트랙트와 상호작용하는 방법을 안내합니다. 도구: ethers.js v6 또는 viem 네트워크: StableNet 테스트넷 (Chain ID 8283)

사전 준비

  • Node.js ≥ 18 설치
  • 잔액이 있는 테스트넷 지갑 — Faucet에서 무료 WKRC를 받으세요:

    Faucet

    테스트넷 WKRC 요청 — 회원가입 불필요.

    Explorer

    온체인에서 트랜잭션과 잔액을 조회합니다.
StableNet의 모든 트랜잭션에는 최소 27,600 Gwei (27600000000000 wei)의 maxPriorityFeePerGas가 필요합니다. 이 임계값 미만의 트랜잭션은 트랜잭션 풀에서 즉시 거절됩니다. maxFeePerGas도 반드시 설정해야 합니다 — 생략하면 0이 되어 거절됩니다.

네트워크 정보

항목
Chain ID8283
RPC URLhttps://api.test.stablenet.network
WKRC 컨트랙트0x0000000000000000000000000000000000001000
최소 maxPriorityFeePerGas27,600 Gwei (27600000000000 wei)
eth_maxPriorityFeePerGas시장 추정값이 아닌 거버넌스 강제값 반환
WKRC는 StableNet의 네이티브 가스 코인으로, NativeCoinAdapter 시스템 컨트랙트를 통해 표준 ERC-20 인터페이스로 노출됩니다. 가스 지불에 사용되는 잔액과 balanceOf()로 조회되는 잔액이 동일합니다 — 래핑이 필요 없습니다.

ethers.js

설치

npm install ethers dotenv

StableNet 연결

import { ethers } from "ethers";
import "dotenv/config";

const RPC_URL = "https://api.test.stablenet.network";

const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Verify connection
const network = await provider.getNetwork();
console.log("Chain ID:", network.chainId.toString()); // 8283
Node.js v20 이상에서는 dotenv 대신 --env-file=.env 플래그 사용을 권장합니다: node --env-file=.env your-script.js

잔액 조회

const ADDRESS = wallet.address;

// Native WKRC balance (same as gas balance)
const nativeBalance = await provider.getBalance(ADDRESS);
console.log("Native WKRC:", ethers.formatEther(nativeBalance));

// WKRC via NativeCoinAdapter ERC-20 interface
const WKRC_ADDRESS = "0x0000000000000000000000000000000000001000";
const ERC20_ABI = [
  "function balanceOf(address) view returns (uint256)",
  "function allowance(address owner, address spender) view returns (uint256)",
  "function approve(address spender, uint256 amount) returns (bool)",
  "function transfer(address to, uint256 amount) returns (bool)",
];

const wkrc = new ethers.Contract(WKRC_ADDRESS, ERC20_ABI, wallet);
const erc20Balance = await wkrc.balanceOf(ADDRESS);
console.log("WKRC (ERC-20):", ethers.formatEther(erc20Balance));
// Native balance and ERC-20 balance are always equal

트랜잭션 전송

// Always set both maxPriorityFeePerGas and maxFeePerGas
const tx = await wallet.sendTransaction({
  to: "0xRecipientAddress",
  value: ethers.parseEther("1.0"),
  maxPriorityFeePerGas: ethers.parseUnits("27600", "gwei"),
  maxFeePerGas: ethers.parseUnits("80000", "gwei"),
});

const receipt = await tx.wait();
console.log("Confirmed in block:", receipt.blockNumber);
tx.wait()는 트랜잭션이 포함될 때까지 eth_getTransactionReceipt를 폴링합니다. 블록 타임은 ~1초이지만 mempool 전파에 최대 2분이 걸릴 수 있습니다 (field-tested; actual time varies). 취소 후 재시도 시 nonce 충돌이 발생할 수 있습니다.

컨트랙트에 WKRC 승인

const CONTRACT_ADDRESS = "0xYourContractAddress";
const AMOUNT = ethers.parseEther("1.0");

// Approve the contract to spend 1 WKRC
const approveTx = await wkrc.approve(CONTRACT_ADDRESS, AMOUNT, {
  maxPriorityFeePerGas: ethers.parseUnits("27600", "gwei"),
  maxFeePerGas: ethers.parseUnits("80000", "gwei"),
});
await approveTx.wait();
console.log("Approval confirmed");

// Verify allowance
const allowance = await wkrc.allowance(wallet.address, CONTRACT_ADDRESS);
console.log("Allowance:", ethers.formatEther(allowance));

컨트랙트 함수 호출

const PAYMENT_ABI = [
  "function pay(address to, uint256 amount) external",
  "function allowanceOf(address owner) view returns (uint256)",
];

const paymentContract = new ethers.Contract(
  CONTRACT_ADDRESS,
  PAYMENT_ABI,
  wallet
);

// Read — no fee needed for view calls
const remaining = await paymentContract.allowanceOf(wallet.address);
console.log("Remaining allowance:", ethers.formatEther(remaining));

// Write — always include fee params
const payTx = await paymentContract.pay(
  "0xRecipientAddress",
  ethers.parseEther("1.0"),
  {
    maxPriorityFeePerGas: ethers.parseUnits("27600", "gwei"),
    maxFeePerGas: ethers.parseUnits("80000", "gwei"),
  }
);
await payTx.wait();
console.log("Payment confirmed");

강제 priority fee 조회

// Returns the governance-controlled minimum, not a market estimate
const tip = await provider.send("eth_maxPriorityFeePerGas", []);
console.log("Enforced tip:", BigInt(tip).toString(), "wei");
// e.g. 27600000000000

viem

설치

npm install viem dotenv

체인 및 클라이언트 정의

StableNet 테스트넷 체인 객체를 한 번만 정의하고 모든 클라이언트에서 재사용합니다:
import {
  createPublicClient,
  createWalletClient,
  http,
  parseEther,
  parseGwei,
  formatEther,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import "dotenv/config";

const stablenetTestnet = {
  id: 8283,
  name: "StableNet Testnet",
  nativeCurrency: { name: "WKRC", symbol: "WKRC", decimals: 18 },
  rpcUrls: {
    default: { http: ["https://api.test.stablenet.network"] },
  },
  blockExplorers: {
    default: {
      name: "StableNet Explorer",
      url: "https://explorer.stablenet.network",
    },
  },
};

const publicClient = createPublicClient({
  chain: stablenetTestnet,
  transport: http(),
});

const account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`);
const walletClient = createWalletClient({
  chain: stablenetTestnet,
  transport: http(),
  account,
});
Node.js v20 이상에서는 dotenv 대신 --env-file=.env 플래그 사용을 권장합니다: node --env-file=.env your-script.js

잔액 조회

const ADDRESS = account.address;

// Native WKRC balance
const nativeBalance = await publicClient.getBalance({ address: ADDRESS });
console.log("Native WKRC:", formatEther(nativeBalance));

// WKRC via NativeCoinAdapter ERC-20 interface
const WKRC_ADDRESS = "0x0000000000000000000000000000000000001000";
const ERC20_ABI = [
  {
    name: "balanceOf",
    type: "function",
    stateMutability: "view",
    inputs: [{ name: "account", type: "address" }],
    outputs: [{ name: "", type: "uint256" }],
  },
  {
    name: "allowance",
    type: "function",
    stateMutability: "view",
    inputs: [
      { name: "owner", type: "address" },
      { name: "spender", type: "address" },
    ],
    outputs: [{ name: "", type: "uint256" }],
  },
  {
    name: "approve",
    type: "function",
    stateMutability: "nonpayable",
    inputs: [
      { name: "spender", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    outputs: [{ name: "", type: "bool" }],
  },
];

const erc20Balance = await publicClient.readContract({
  address: WKRC_ADDRESS,
  abi: ERC20_ABI,
  functionName: "balanceOf",
  args: [ADDRESS],
});
console.log("WKRC (ERC-20):", formatEther(erc20Balance));

트랜잭션 전송

// Always set both maxPriorityFeePerGas and maxFeePerGas
const hash = await walletClient.sendTransaction({
  to: "0xRecipientAddress",
  value: parseEther("1.0"),
  maxPriorityFeePerGas: parseGwei("27600"),
  maxFeePerGas: parseGwei("80000"),
});

const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log("Confirmed in block:", receipt.blockNumber);

컨트랙트에 WKRC 승인

const CONTRACT_ADDRESS = "0xYourContractAddress";

// Approve
const approveHash = await walletClient.writeContract({
  address: WKRC_ADDRESS,
  abi: ERC20_ABI,
  functionName: "approve",
  args: [CONTRACT_ADDRESS, parseEther("1.0")],
  maxPriorityFeePerGas: parseGwei("27600"),
  maxFeePerGas: parseGwei("80000"),
});
await publicClient.waitForTransactionReceipt({ hash: approveHash });

// Verify allowance
const allowance = await publicClient.readContract({
  address: WKRC_ADDRESS,
  abi: ERC20_ABI,
  functionName: "allowance",
  args: [account.address, CONTRACT_ADDRESS],
});
console.log("Allowance:", formatEther(allowance));

컨트랙트 함수 호출

const PAYMENT_ABI = [
  {
    name: "pay",
    type: "function",
    stateMutability: "nonpayable",
    inputs: [
      { name: "to", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    outputs: [],
  },
  {
    name: "allowanceOf",
    type: "function",
    stateMutability: "view",
    inputs: [{ name: "owner", type: "address" }],
    outputs: [{ name: "", type: "uint256" }],
  },
];

// Read — no fee needed for view calls
const remaining = await publicClient.readContract({
  address: CONTRACT_ADDRESS,
  abi: PAYMENT_ABI,
  functionName: "allowanceOf",
  args: [account.address],
});
console.log("Remaining allowance:", formatEther(remaining));

// Write — always include fee params
const payHash = await walletClient.writeContract({
  address: CONTRACT_ADDRESS,
  abi: PAYMENT_ABI,
  functionName: "pay",
  args: ["0xRecipientAddress", parseEther("1.0")],
  maxPriorityFeePerGas: parseGwei("27600"),
  maxFeePerGas: parseGwei("80000"),
});
await publicClient.waitForTransactionReceipt({ hash: payHash });
console.log("Payment confirmed");

강제 priority fee 조회

// Returns the governance-controlled minimum, not a market estimate
const tip = await publicClient.request({
  method: "eth_maxPriorityFeePerGas",
});
console.log("Enforced tip:", BigInt(tip).toString(), "wei");
// e.g. 27600000000000

공통 주의사항

모든 트랜잭션에 priority fee 필수

GovValidator 거버넌스 컨트랙트는 네이티브 전송, 컨트랙트 배포, 컨트랙트 호출 등 모든 트랜잭션 타입에 maxPriorityFeePerGas 최솟값을 강제합니다. 생략하면 즉시 거절됩니다.
필드최솟값느릴 때 참고값
maxPriorityFeePerGas27,600 Gwei35,000 Gwei
maxFeePerGasmaxPriorityFeePerGas 이상80,000 Gwei
위의 35,000 Gwei / 80,000 Gwei 값은 StableNet 테스트넷 실측 기반 참고값입니다. 공식 프로토콜 스펙이 아닙니다 — 실제 mempool 상태에 따라 조정하세요.
StableNet의 eth_maxPriorityFeePerGas는 최근 블록 기반 추정값이 아닌 GovValidator의 거버넌스 강제값을 반환합니다. 프로그램적으로 현재 최솟값을 가져오는 데 사용할 수 있습니다.

WKRC는 네이티브이자 ERC-20

0x0000000000000000000000000000000000001000NativeCoinAdapter는 네이티브 가스 코인을 표준 ERC-20 토큰으로 노출합니다. 래핑이 필요 없습니다:
  • provider.getBalance() / publicClient.getBalance()balanceOf()와 동일한 값 반환
  • 표준 approve / transferFrom 패턴이 Ethereum과 동일하게 작동
  • WKRC 컨트랙트의 transfer()는 가스 지불에 사용되는 것과 동일한 잔액을 이동

트러블슈팅

  • 트랜잭션 거절 / “fee too low” — 모든 전송에 maxPriorityFeePerGas (최소 27,600 Gwei)와 maxFeePerGas를 함께 설정하세요.
  • maxFeePerGas가 0이 됨maxPriorityFeePerGas와 함께 항상 명시적으로 설정하세요. 생략하면 priority fee가 올바르더라도 거절됩니다.
  • 확정이 느린 경우maxPriorityFeePerGas: parseGwei("35000"), maxFeePerGas: parseGwei("80000")으로 높이세요 (실측 기반 참고값, 공식 스펙 아님).
  • tx.wait() / waitForTransactionReceipt 멈춤 — tx가 mempool에 있습니다. 최대 2분 기다리세요 (field-tested; actual time varies). 동일한 nonce로 취소 후 재시도하지 마세요.
  • “replacement transaction underpriced” — 동일한 nonce의 대기 중인 트랜잭션을 대체하려면 두 fee 필드를 모두 높이세요.

다음 단계

Foundry 빠른 시작

Foundry로 WKRC 결제 컨트랙트를 배포합니다 — approve 및 pay 흐름 포함.

Hardhat 가이드

Hardhat으로 컨트랙트를 배포하고 ethers.js로 호출합니다.

EVM 호환성 레퍼런스

지원 옵코드, 도구 설정 스니펫, 수수료 위임(tx type 0x16), 미지원 기능.

Explorer

온체인에서 트랜잭션과 컨트랙트를 조회합니다.

예제 컨트랙트

Foundry, Hardhat, ethers.js, viem 예제 모음.