Solana Web3.js v2와 viem 비교: 함수형 파이프라인이 바꾸는 Web3 프론트엔드 개발

삼코딩

@samcoding

Solana Web3.js v2와 viem 비교: 함수형 파이프라인이 바꾸는 Web3 프론트엔드 개발

Solana Web3.js v2와 viem 비교: 함수형 파이프라인이 바꾸는 Web3 프론트엔드 개발

Web3 프론트엔드 개발에서 무거운 객체지향 라이브러리의 시대가 저물고 있습니다. 이더리움 생태계의 viem이 과거 무거웠던 클래스 기반 아키텍처를 가볍고 조합 가능한 함수형으로 대체했다면, 솔라나 진영에서는 @solana/web3.js v2가 그 흐름을 이어받았습니다. 두 라이브러리는 번들 크기를 획기적으로 줄이고 코드의 예측 가능성을 높이기 위해 함수형 조합 방식을 전면에 내세웁니다. 이번 글에서는 두 라이브러리의 아키텍처 설계와 트랜잭션 빌딩 방식을 비교하며, 멀티체인 환경에서 우리가 실제로 빌드해야 할 프론트엔드 최적화 아키텍처를 정리해 보겠습니다.

모놀리식의 종말: 왜 함수형 아키텍처를 선택했을까?

Web3 프론트엔드 성능 최적화의 핵심은 빌드 시점에 사용하지 않는 코드를 완벽히 제거하는 트리 쉐이킹에 있습니다. 과거 솔라나 프론트엔드 개발의 중심이었던 Connection 클래스는 단 하나의 RPC 요청만 처리하려 해도 무거운 암호화 기능과 관련된 종속 패키지까지 강제로 브라우저 번들에 포함해야 하는 치명적인 한계가 있었습니다.

새롭게 개편된 Solana Kit는 이러한 객체지향 기반의 모놀리식 구조를 해체하고 라이브러리를 독립적인 미크로 패키지로 분할했습니다. RPC 요청, 트랜잭션 빌딩, 서명 시스템을 각각 독립된 모듈로 쪼개고 함수형 파이프라인 아키텍처를 도입한 결과, 프론트엔드 번들 크기를 최대 78%까지 줄이는 데 성공했습니다. 실제로 솔라나 익스플로러 개발팀은 이 마이그레이션만으로 번들 크기를 즉각 26% 절감하는 효과를 거두었습니다.

이러한 아키텍처 전환은 이더리움 생태계에서 viem이 거둔 성공 공식과 정확히 일치합니다. viem 역시 기존 ethers.js가 고수하던 거대한 클래스 구조를 걷어내고, 단순 온체인 조회를 처리하는 Public Client와 쓰기 및 서명을 담당하는 Wallet Client로 역할을 철저히 분리했습니다. 이처럼 필요한 클라이언트 기능만 독립적으로 조립하여 빌드 파일에 포함함으로써, viem은 35kb 미만의 극도로 가벼운 번들 크기를 유지하며 모던 웹 앱의 로딩 성능을 극대화하고 있습니다.

트랜잭션 빌딩 패턴: 함수형 파이프라인 대 선언적 시뮬레이션

트랜잭션을 생성하고 검증하는 코드 작성 스타일은 두 라이브러리의 설계 철학이 가장 극명하게 나뉘는 지점입니다. Solana Kit는 불변 객체와 함수형 파이프라인을 조합해 트랜잭션 메시지를 단계적으로 조립하는 반면, viem은 ABI 기반의 선언적 구조와 온체인 시뮬레이션을 결합하여 실행 안정성을 확보하는 방식을 취합니다.

Solana Kit는 기존의 무거운 클래스 메서드 대신 @solana/functional 패키지의 pipe 헬퍼를 도입했습니다. 이를 통해 트랜잭션을 점진적으로 완성해 나가는 빌더 패턴을 구현합니다. 구체적인 파이프라인 구축 단계는 다음과 같습니다.

  1. 메시지 초기화: createTransactionMessage({ version: 0 })를 실행하여 버전 정보가 주입된 빈 메시지 규격을 만듭니다.
  2. 수수료 대납자 지정: setTransactionMessageFeePayer를 사용하여 가스 비용을 지불할 계정을 매핑합니다.
  3. 수명 주기 설정: setTransactionMessageLifetimeUsingBlockhash를 체이닝하여 최신 블록해시를 적용하고 트랜잭션 만료 기준을 정의합니다.
  4. 명령어 추가: appendTransactionMessageInstruction을 통해 전송할 온체인 프로그램 명령어들을 하나씩 덧붙여 최종 트랜잭션을 완성합니다.

이 파이프라인의 각 단계는 기존 트랜잭션을 수정하는 대신, 가볍고 독립적인 불변 상태의 새로운 객체를 반환합니다. 덕분에 빌드 과정에서 쓰이지 않는 수수료 대납자 처리 코드나 블록해시 제어 모듈 등을 컴파일러가 완전히 잘라낼 수 있습니다.

반면 EVM 생태계의 viem은 복잡한 계약 상호작용의 위험성을 최소화하기 위해 선언적 인터페이스와 사전 안전장치에 집중합니다. 핵심은 퍼블릭 클라이언트가 제공하는 simulateContract 워크플로우에 있습니다.

개발자가 호출 대상 스마트 계약의 ABI, 주소, 함수 이름, 인자값을 선언적으로 채워 simulateContract를 호출하면, viem은 실제 전송 전에 RPC 노드에서 가상으로 계약을 시뮬레이션합니다. 이 과정에서 가스비가 충분한지, 런타임에 에러가 발생하지 않는지 미리 검증합니다. 시뮬레이션이 성공하면 내부적으로 최적의 가스 한도와 실행 스펙이 채워진 request 객체가 반환되며, 개발자는 이를 writeContract 메서드에 넘겨 즉시 가스 낭비 없는 안전한 트랜잭션을 보낼 수 있습니다.

typescript
// Solana Kit: 함수형 파이프라인을 통한 메시지 조립
const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayer(feePayerAddress, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) => appendTransactionMessageInstruction(transferInstruction, tx)
);

// viem: 선언적 시뮬레이션 기반 트랜잭션 안전 전송
const { request } = await publicClient.simulateContract({
  abi, address: contractAddress, functionName: 'mint', args: [1n]
});
const hash = await walletClient.writeContract(request);

이처럼 Solana Kit가 불변식 파이프라인을 통해 리소스 최적화에 힘을 실었다면, viem은 트랜잭션 서명 전 검증 단계를 프론트엔드 레벨로 끌어올려 트랜잭션 실패로 인한 유저 경험 저해를 원천 차단하는 데 목적을 둡니다.

서명 권한의 추상화: 고도로 세분화된 Signer 시스템 대 단일 Account 모델

트랜잭션에 서명하고 권한을 위임하는 아키텍처에서도 두 라이브러리는 정반대의 엔지니어링 철학을 보여줍니다. Solana Kit v2는 서명자의 역할과 권한 범위를 아주 미세하게 쪼갠 고도로 세분화된 Signer 시스템을 채택한 반면, viem은 서명 권한과 로직을 하나의 직관적인 Account 객체로 묶어 처리합니다.

Solana Kit v2의 가장 큰 특징은 서명자가 트랜잭션에 어떤 영향을 미칠 수 있는지를 명확히 제한하는 것입니다. 시스템은 개인키를 직접 보유한 KeyPairSigner, 트랜잭션에 수수료 대납 주소를 추가하는 등 서명 전 메시지를 수정할 수 있는 TransactionModifyingSigner, 완성된 트랜잭션을 네트워크에 송신하는 TransactionSendingSigner 등으로 권한을 완벽히 분리합니다. 이러한 미세 권한 분리는 멀티 시그나 가스비 대납처럼 여러 주체가 트랜잭션 빌딩에 참여하는 복잡한 파이프라인을 설계할 때 보안 사고를 예방하는 강력한 안전장치가 됩니다.

반면 viem은 지갑 주소와 서명 동작을 단일 Account 레이어로 단순화하여 개발자 경험을 극대화했습니다. privateKeyToAccount나 브라우저의 주입형 지갑 인터페이스를 통해 하나의 Account 인스턴스를 생성하면, 이를 WalletClient 자체에 바인딩하거나 개별 액션의 매개변수로 즉시 넘겨 사용할 수 있습니다.

typescript
// viem: 단일 Account 추상화를 활용한 직관적인 서명 및 전송
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0x...');
const client = createWalletClient({
  account,
  transport: http()
});

// 클라이언트 액션 실행 시 바인딩된 Account가 서명을 자동으로 처리
await client.sendTransaction({
  to: '0x...',
  value: 1000000000000000000n
});

EVM 환경에서는 트랜잭션 구조가 상대적으로 정형화되어 있어 이처럼 직관적인 단일 Account 바인딩 모델만으로도 대부분의 유스케이스를 우아하게 해결할 수 있습니다. 반면, 여러 계정의 서명 동의와 트랜잭션 변조가 빈번하게 일어나는 솔라나의 아키텍처에서는 Solana Kit v2처럼 역할에 따른 미세한 Signer 분리 모델이 코드의 안전성과 유연성을 확보하는 데 필수적입니다.

지갑 연동 표준: Solana Wallet Standard와 EIP-1193/6963

브라우저 확장 프로그램 지갑을 감지하고 호출하는 방식에서도 두 생태계는 각각의 연동 표준을 고도화하며 충돌 문제를 해결해 왔습니다.

솔라나 프론트엔드는 일관된 지갑 사양을 강제하는 솔라나 지갑 표준(Solana Wallet Standard)에 전적으로 의존합니다. 지갑 개발사가 인터페이스에 맞춰 solana:signTransaction이나 solana:signAndSendTransaction 같은 서명 메서드를 구현하면, 프론트엔드 개발자는 @solana/react 라이브러리의 훅을 통해 지갑을 감지합니다. 이때 useWalletAccountTransactionSendingSigner 같은 훅을 사용하면 브라우저 지갑 계정을 Solana Kit와 완벽히 호환되는 서명자 객체로 즉시 변환하여 사용할 수 있습니다.

반면 EVM 진영은 여러 지갑이 window.ethereum 주입 범위를 서로 덮어쓰며 충돌하던 EIP-1193 방식의 한계를 극복하기 위해 EIP-6963 표준을 채택했습니다. EIP-6963은 표준 이벤트를 활용해 브라우저에 설치된 다중 지갑을 충돌 없이 독립적으로 감지하도록 지원합니다. viem은 이를 custom 전송 방식을 구현한 createWalletClient 구조로 직접 다룰 수 있도록 돕고, 실무에서는 보통 Wagmi의 리액트 훅 세트와 결합하여 안정적인 지갑 연동 인터페이스를 제공합니다.

멀티체인 프론트엔드 엔지니어를 위한 프레임워크 선택 가이드

Solana Kit v2와 viem은 웹3 프론트엔드의 성능 저하와 무거운 번들 크기를 해결하기 위해 동일한 함수형 철학을 공유합니다. 계정 구조가 복잡하고 다중 명령을 정교하게 제어해야 하는 솔라나 환경에서는 Kit v2의 파이프라인 구조가, ABI 기반의 규격화된 스마트 계약 상호작용이 핵심인 EVM 환경에서는 viem의 클라이언트 조합 방식이 탁월한 선택입니다. 이제 기존의 무거운 클래스 기반 SDK를 걷어내고, 두 라이브러리가 제시하는 트리 쉐이킹 친화적인 아키텍처를 도입하여 더 빠르고 안정적인 dApp을 구축할 때입니다.


참고 링크