개요

Viem은 이더리움 계열 체인과 상호작용하는 경량 Web3 클라이언트 라이브러리임 ethers나 web3.js와 같은 범용 라이브러리와 동일한 범주의 도구지만, 모듈 분리 구조와 타입 안전성, 빌드 사이즈, 성능에서 강점이 있음 프로덕션에서 자주 필요한 읽기와 쓰기 흐름을 중심으로, 설치부터 블록 조회, 컨트랙트 읽기, 컨트랙트 쓰기까지의 필수 개념과 실용 팁 정리

핵심 개념과 정의

  • Public Client

    • 퍼블릭 RPC를 통해 체인 데이터 읽기 전용 호출 수행하는 클라이언트
    • 블록, 트랜잭션, 로그 조회, read-only 컨트랙트 호출 담당
  • Wallet Client

    • 개인키를 보유한 계정에 대해 트랜잭션 서명과 전송을 수행하는 클라이언트
    • 시뮬레이션, 가스 추정, nonce 처리, write 컨트랙트 호출 담당
  • Transport

    • HTTP나 WebSocket 등 JSON-RPC 통신 방법 추상화 계층
    • 성능과 안정성에 영향을 주는 핵심 요소
  • Chain 구성

    • chain 객체에 chainId, 네트워크 이름, 기본 RPC 엔드포인트, 네이티브 통화 단위 등의 메타데이터 포함
    • 올바른 체인 설정이 시뮬레이션과 서명, 전송의 전제 조건
  • ABI 기반 타입 안전성

    • ABI를 바탕으로 함수 이름, 파라미터, 반환형에 대한 타입 추론 제공
    • 런타임 이전에 오타나 파라미터 타입 오류를 차단하는 효과
  • 데이터 타입

    • 금액, 블록 번호, 가스 등 정수 값은 BigInt 반환이 기본
    • 주소, 바이트 데이터는 0x 프리픽스 hex 문자열 사용

동작 원리와 구조

  • 읽기 흐름

    • Public Client가 RPC 호출을 통해 블록이나 상태를 조회함
    • 컨트랙트 읽기 시 getContract 또는 readContract 유틸을 사용하여 call 실행
  • 쓰기 흐름

    • Wallet Client가 계정을 보유하고 시뮬레이션으로 가스와 파라미터를 검증한 뒤 트랜잭션 전송
    • simulateContract 결과를 writeContract에 그대로 전달하는 패턴이 안전하고 권장됨
  • 분리의 이점

    • 읽기와 쓰기 책임이 분리되어 보안 경계와 테스트 용이성 향상
    • 서버 환경에서는 Public Client만 배포해 민감 키가 없는 조회 전용 API 제공 가능

설치와 환경 준비

  • 패키지 설치

    • npm install viem
  • 필수 준비물

    • 신뢰 가능한 RPC 엔드포인트 URL 준비
    • 테스트넷에서는 faucet로 ETH 확보
    • 환경 변수로 개인키, RPC URL 등 민감정보 관리 권장

사용법 1: 네트워크 연결과 블록 조회

아래 스니펫은 메인넷에 연결하고 최신 블록과 블록 번호를 조회하는 최소 예시임

import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(process.env.RPC_URL),
});

const run = async () => {
  const block = await publicClient.getBlock();
  const blockNumber = await publicClient.getBlockNumber();

  // block 해시, 타임스탬프, 가스 사용량 등 핵심 필드 사용
  console.log(block.hash, block.timestamp, block.gasUsed);
  console.log(blockNumber);
};

run();
  • blockNumber는 BigInt 반환값임
  • gas, 수수료, 블록 번호 등 정수 값 처리 시 숫자 오버플로 방지를 위해 BigInt 유지 또는 명시적 변환 필요
  • http 전송 대신 웹소켓 전송도 가능하나 서버 환경 안정성, 프록시 구성, 리밸런싱 전략 고려 필요

사용법 2: 컨트랙트 읽기와 쓰기

한 스니펫에서 읽기와 쓰기를 묶어 핵심 흐름만 정리함

import {
  getContract,
  createPublicClient,
  createWalletClient,
  http,
} from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const publicClient = createPublicClient({
  chain: sepolia,
  transport: http(process.env.RPC_URL),
});

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({
  account,
  chain: sepolia,
  transport: http(process.env.RPC_URL),
});

// 읽기 예시
const contract = getContract({
  address: "0xYourContractAddress",
  abi: YourAbi,
  client: publicClient,
});
const name = await contract.read.name();

// 쓰기 예시
const { request } = await publicClient.simulateContract({
  address: "0xYourContractAddress",
  abi: YourAbi,
  functionName: "store",
  args: [100n],
  account,
});
const txHash = await walletClient.writeContract(request);
console.log(name, txHash);
  • address에는 목표 컨트랙트 주소 입력
  • abi에는 실제 ABI 배열 주입
  • read 호출은 call을 사용하므로 가스 소모 없음
  • write 호출은 시뮬레이션으로 가스, 인자, 권한 문제를 사전 검증한 뒤 전송
  • txHash로 익스플로러에서 상태 확인 가능

동작 살펴보기와 실전 팁

  • getContract와 read

    • getContract는 ABI를 바탕으로 타입 안전한 메서드 집합 생성
    • contract.read.balanceOf(‘0x…’)처럼 각 함수가 자동 완성되며 잘못된 함수명이나 인자 타입을 컴파일 타임에 차단
  • simulateContract와 writeContract

    • 시뮬레이션 단계에서 revert 이유와 디코딩된 오류를 확인 가능
    • request에는 to, data, value, gas, maxFeePerGas 등 전송에 필요한 필드가 완성되어 포함됨
    • writeContract에 request를 그대로 넘겨 불일치 위험 최소화
  • 가스 수수료와 EIP-1559

    • 대부분 체인은 baseFee 기반의 maxFeePerGas, maxPriorityFeePerGas 조합 사용
    • 시뮬레이션 결과를 그대로 쓰거나, 트래픽이 많은 시간대에는 우선순위 수수료를 다소 상향해 확정 속도 확보
  • BigInt와 단위 처리

    • ERC-20 amount, 수수료 모두 BigInt로 다룸
    • parseUnits, formatUnits 유틸 사용으로 10진 문자열과 wei 단위 간 변환 안정화
  • 체인 불일치와 계정 네트워크

    • walletClient와 simulateContract의 chain은 동일해야 함
    • RPC URL이 다른 체인을 가리키면 nonce, 수수료, 시뮬레이션 결과가 어긋나 실패 가능

주의사항과 한계

  • 개인키 보호

    • PRIVATE_KEY는 서버 비밀 변수 관리 또는 HSM, KMS 연동 고려
    • 프런트엔드 환경에 개인키 하드코딩 금지
  • RPC 신뢰도와 레이트 리밋

    • 공용 RPC는 속도와 가용성이 낮을 수 있음
    • 상용 환경은 신뢰 가능한 제공자와 백업 엔드포인트 구성 권장
  • 재시도와 오류 처리

    • 네트워크 에러, 서버 오류, 체인 재구성 등 다양한 실패 케이스 존재
    • 지수 백오프, idempotency 고려, 트랜잭션 재전송 시 nonce 관리 필요
  • 테스트넷과 가스

    • 테스트넷은 faucet 지급 지연, 빈 블록, 비활성 노드 등 특성이 존재
    • 가스 부족, 체인 혼잡에 따른 확정 지연을 정상 동작 범위로 간주할 수 있어야 함
  • ABI 신뢰성

    • 오타 또는 잘못된 ABI는 런타임 revert를 유발함
    • 배포된 바이트코드와 ABI 매칭 검증 권장

베스트 프랙티스

  • 시뮬레이션 우선 전략

    • 모든 write 호출 전 simulateContract로 가스와 인자 검증
  • 환경 변수 분리

    • RPC_URL, PRIVATE_KEY, CONTRACT_ADDRESS를 환경 변수로 분리해 배포 환경별 설정 편의 확보
  • 블록 간 일관성

    • 이벤트나 상태를 조합해 읽을 때는 동일 블록 높이 기준을 유지하는 스냅샷 전략 고려
  • 멀티콜 활용

    • 여러 read를 묶어 왕복 비용 절감 가능
  • 로깅과 관찰성

    • txHash, nonce, gasUsed, effectiveFeePerGas, 블록 번호를 구조화 로그로 남겨 운영 이슈 분석 용이성 확보

간단 예시로 보는 검증 흐름

  • writeContract에서 반환된 txHash 확인
  • 체인 익스플로러에서 해당 txHash 조회로 블록 포함 여부, 리시트 상태, 이벤트 로그 검증
  • 컨트랙트의 view 함수로 최종 상태 교차 확인

이 흐름을 자동화해 배치나 백엔드 작업에서 신뢰도 있는 완료 판정 기준으로 사용 가능

마무리

Viem은 읽기와 쓰기 클라이언트 분리, 시뮬레이션 중심의 전송 흐름, 타입 안전한 ABI 접근이라는 명확한 철학을 가짐 이 기본기만 갖추면 블록 조회, 컨트랙트 읽기, 트랜잭션 생성이라는 세 가지 축을 빠르게 실무에 적용 가능 개인키와 RPC 관리, 시뮬레이션과 가스 전략, 오류 처리라는 운영 디테일을 함께 설계하면 프로덕션에서도 일관된 안정성을 확보할 수 있음

참고자료