JavaScript에서 암호화폐 트랜잭션 보내기: ChainGate로 다루는 Bitcoin, Ethereum, Litecoin, Dogecoin과 그 외의 체인

하나의 SDK로 JavaScript에서 Bitcoin, Ethereum, Avalanche, Litecoin, Dogecoin, Bitcoin Cash를 보내는 실전 가이드 — ChainGate.

Bitcoin, Ethereum, Avalanche, Litecoin, Dogecoin and Bitcoin Cash — +6 chains, one JavaScript transfer() call

JavaScript에서 온체인 데이터를 읽는 일은 늘 일의 까다로운 절반이었습니다. 깔끔한 Bitcoin 잔액을 얻는 것 — 익스플로러처럼 확인된 잔액과 미확인 잔액을 함께 — 은 보통 자체 indexer를 운영하거나 흔들리는 공개 API에 계속 두드리는 일입니다. Ethereum이나 EVM 주소가 보유한 NFT와 토큰을 나열하는 것은 각각 고유한 스키마, rate limit, 빠진 필드를 가진 익스플로러 엔드포인트 서너 개를 짜깁기하는 일이고요. 그러다 정작 트랜잭션을 broadcast하려고 하면 다시 Bitcoin은 bitcoinjs-lib, EVM 쪽은 ethers나 viem, Dogecoin과 Litecoin은 반쯤 방치된 fork로 돌아오게 됩니다. ChainGate는 그 모두를 하나의 SDK로 커버합니다.

ChainGate는 그 일을 하나의 JavaScript SDK로 모읍니다. 멀티체인 지갑 SDK로 설계되어 있어, 같은 호출 형태로 Bitcoin, Ethereum, Avalanche(과 모든 EVM 체인), Litecoin, Dogecoin, Bitcoin Cash를 송신하고, 주소 잔액을 읽고, 임의의 주소에 있는 assets를 나열합니다. 같은 패키지가 Node.js와 브라우저에서 코드 변경 없이 동작합니다 — 모든 crypto 프리미티브가 네이티브로 함께 제공되어 polyfill이 필요 없습니다.

다음은 기본 다섯 줄짜리 송신 코드입니다. 가이드의 나머지는 같은 패턴을 각 체인에 적용해 보고, 그다음 broadcast 전에 fee 추정과 잔액 조회를 더합니다:

import { ChainGate, importWallet } from 'chaingate';

const cg = new ChainGate({ apiKey: 'your-api-key' });
const wallet = importWallet({ phrase: process.env.MNEMONIC });
const btc = cg.connect(cg.networks.bitcoin, wallet);

const tx = await btc.transfer(cg.networks.bitcoin.amount('0.001'), 'bc1qRecipient...');
await tx.signAndBroadcast();

요약

  • 하나의 SDK, 모든 체인. 동일한 transfer() API가 Bitcoin, Ethereum, Avalanche, Litecoin, Dogecoin, Bitcoin Cash, 그리고 EVM 호환 모든 네트워크를 커버합니다.
  • Node.js 22+와 최신 브라우저에서 수정 없이 동작합니다 — Buffer나 crypto polyfill 설정이 필요 없습니다.
  • fee, 잔액, broadcast가 모두 내장되어 있습니다 — 어느 것도 두 번째 라이브러리를 요구하지 않습니다.
  • 서명은 항상 로컬에서 이루어집니다. private key는 SDK 경계를 벗어나지 않습니다.

1. 셋업: ChainGate 설치와 초기화

패키지 매니저를 고르세요. SDK는 타입까지 포함된 단일 npm 패키지로 배포되어 — 한 번 설치하면 같은 import에서 Bitcoin, Ethereum 그리고 나머지를 송신할 수 있습니다:

# pick your package manager
npm install chaingate
pnpm add chaingate
yarn add chaingate

다음으로 무료 API key를 ChainGate 대시보드에서 받습니다. 가입은 약 1분이면 끝나고, 무료 tier로도 프로토타입을 프로덕션에 올리기에 충분합니다. 이 key로 client를 한 번만 초기화한 뒤 모든 체인에서 재사용하세요:

import { ChainGate } from 'chaingate';

const cg = new ChainGate({ apiKey: process.env.CHAINGATE_API_KEY });

Node.js와 브라우저에서 같은 코드. 패키지는 Node 22+, 최신 브라우저(Vite, Next.js, Webpack 5+), Bun에서 별도 설정 없이 동작하며, 서버에서 import 하는 그 Node.js 블록체인 라이브러리가 사용자 브라우저에서도 그대로 실행됩니다. Buffer와 randomBytes 같은 crypto 프리미티브가 SDK에 네이티브로 포함되어 있어 — polyfill 할 것도, 설정할 것도 없습니다.

TypeScript 타입은 패키지에 포함되어 있습니다. @types/chaingate 패키지는 존재하지 않습니다. 필요가 없기 때문이며 — 모든 공개 클래스, 메서드, 이벤트가 소스 단계에서 완전한 타입을 가집니다.

2. Bitcoin, Litecoin, Dogecoin, Bitcoin Cash 보내기

Bitcoin, Litecoin, Dogecoin, Bitcoin Cash는 같은 회계 모델인 UTXO를 공유합니다. 지갑 잔액은 그 주소들에 잠겨 있는 unspent transaction outputs의 합입니다. 송금하려면, 지갑은 금액을 충당할 만큼 UTXO를 골라 그것들을 input으로 소비하는 새 트랜잭션을 만들고, 수신인에게 한 개의 output을 보낸 뒤 거스름돈을 자기 자신에게 change output으로 돌리고, 킬로바이트당 satoshi(또는 체인별 등가물)로 fee를 지불합니다.

ChainGate는 모든 단계 — UTXO 선택, change output, fee tier, 서명 — 를 단 한 번의 transfer() 호출 뒤에서 처리합니다. 목적지 주소와 금액을 넘기면 broadcast 준비가 끝난 서명된 트랜잭션이 돌아옵니다.

Node.js와 브라우저에서 Bitcoin 보내기

기본 예제. 지갑을 Bitcoin 네트워크에 연결하고, transfer를 만들고, fee tier를 고른 다음 broadcast 합니다:

import { ChainGate, importWallet, NotEnoughFundsError } from 'chaingate';

const cg = new ChainGate({ apiKey: process.env.CHAINGATE_API_KEY });
const wallet = importWallet({ phrase: process.env.MNEMONIC });

const btc = cg.connect(cg.networks.bitcoin, wallet);
const amount = cg.networks.bitcoin.amount('0.001'); // 0.001 BTC

try {
  const tx = await btc.transfer(amount, 'bc1qRecipient...');
  tx.setFee(tx.recommendedFees().normal);

  const broadcast = await tx.signAndBroadcast();
  console.log('BTC txid:', broadcast.transactionId);
} catch (err) {
  if (err instanceof NotEnoughFundsError) {
    console.error('Not enough BTC for this transfer + fee');
  }
  throw err;
}

개발 중 testnet을 쓰려면 cg.networks.bitcoin을 cg.networks.bitcoinTestnet으로 바꾸기만 하면 됩니다 — 나머지 코드는 그대로입니다.

Node.js와 브라우저에서 Litecoin 보내기

코드는 같고 네트워크만 다릅니다. Bitcoin 스니펫과의 diff는 말 그대로 식별자 하나뿐입니다:

const ltc = cg.connect(cg.networks.litecoin, wallet);
const amount = cg.networks.litecoin.amount('0.1'); // 0.1 LTC

const tx = await ltc.transfer(amount, 'ltc1qRecipient...');
tx.setFee(tx.recommendedFees().normal);
await tx.signAndBroadcast();

Litecoin은 기본적으로 ltc1q… 형식의 SegWit 주소를 사용하고 블록을 2분 30초마다 확인합니다 — Bitcoin의 약 4배 속도 — 이 때문에 보안 깊이보다 settlement 속도가 더 중요한 소액 결제에서 인기입니다.

Node.js와 브라우저에서 Dogecoin 보내기

같은 diff, 다른 체인입니다:

const doge = cg.connect(cg.networks.dogecoin, wallet);
const amount = cg.networks.dogecoin.amount('100'); // 100 DOGE

const tx = await doge.transfer(amount, 'DRecipientAddress...');
tx.setFee(tx.recommendedFees().normal);
await tx.signAndBroadcast();

Dogecoin의 JavaScript 생태계는 UTXO 패밀리에서 뒤처져 있습니다. 인기 있던 bitcoinjs-lib 포크 대부분은 수년간 업데이트되지 않았고, 유지보수가 이뤄지는 소수 대안은 비공개입니다. ChainGate는 오늘 JavaScript에서 프로덕션 품질의 Dogecoin 지원을 제공하는 몇 안 되는 선택지 중 하나입니다.

Node.js와 브라우저에서 Bitcoin Cash 보내기

Bitcoin Cash는 기본적으로 CashAddr 주소를 사용하지만 SDK는 legacy 형식도 받습니다 — 둘 중 어느 쪽이든 transfer()에 붙여 넣어도 네트워크는 그대로 확인합니다:

const bch = cg.connect(cg.networks.bitcoincash, wallet);
const amount = cg.networks.bitcoincash.amount('0.05'); // 0.05 BCH

// CashAddr or legacy — both work
const tx = await bch.transfer(amount, 'bitcoincash:qpRecipient...');
tx.setFee(tx.recommendedFees().normal);
await tx.signAndBroadcast();

주소 검증은 양방향입니다. cg.networks.bitcoincash.isValidAddress()는 두 형식 모두에 대해 true를 돌려주므로, 보내기 전에 사용자 입력을 정규화할 필요가 없습니다.

브라우저에서 UTXO 보내기

위의 네 UTXO 스니펫은 Vite, Next.js, Webpack 5 번들 어디서든 수정 없이 동작합니다 — 같은 import가 클라이언트에서 작동하고, 같은 지갑이 API에 접근하지 않고 로컬에서 서명합니다.

보안에 관한 한 가지 주의: 운영용 private key를 프런트엔드에 직접 로딩하지 마세요. 서명을 서버에 두고 브라우저는 broadcast만 보내거나, 사용자가 통제하는 wallet 형태의 흐름으로부터 키를 받도록 하세요.

3. Ethereum과 EVM 체인 보내기

Ethereum의 계정 모델은 다릅니다 — UTXO는 없고, 잔액과 단조 증가 nonce를 가진 주소만 있습니다. ChainGate는 그 차이를 Bitcoin에서 썼던 것과 같은 transfer() 호출 뒤에 숨깁니다. 차이점(nonce 관리, EIP-1559 base fee + tip, gas limit)은 ChainGate가 계산해 줍니다:

const eth = cg.connect(cg.networks.ethereum, wallet);
const amount = cg.networks.ethereum.amount('0.01'); // 0.01 ETH

const tx = await eth.transfer(amount, '0xRecipient...');
tx.setFee(tx.recommendedFees().normal);

const broadcast = await tx.signAndBroadcast();
console.log('ETH txHash:', broadcast.transactionId);

Node.js나 브라우저에서 Avalanche 보내기

Avalanche의 C-Chain은 Ethereum과 함께 일급 네트워크로 연결되어 있습니다 — 같은 호출 형태, 같은 지갑, AVAX 단위 금액. Ethereum 스니펫과의 diff는 식별자 하나뿐입니다:

const avax = cg.connect(cg.networks.avalanche, wallet);
const amount = cg.networks.avalanche.amount('1'); // 1 AVAX

const tx = await avax.transfer(amount, '0xRecipient...');
tx.setFee(tx.recommendedFees().normal);
await tx.signAndBroadcast();

C-Chain은 약 2초 만에 블록을 확정하며 Ethereum과 같은 EIP-1559 fee 모델로 동작합니다. 그래서 AVAX 전송은 빠르게 정산되고 recommendedFees()의 출력도 같은 모양입니다. 이런 점이 Ethereum gas로는 사용자를 가격에서 밀어내는 고처리량 결제에서 Avalanche가 인기 있는 이유입니다.

같은 지갑, 같은 SDK, 모든 EVM 체인. 호스팅 RPC URL은 Polygon, Arbitrum, Base, BNB Chain, Sonic을 기본 제공합니다. 다른 EVM 네트워크 — Optimism, Linea, Scroll, Gnosis, 자체 노드, testnet — 는 JSON-RPC URL을 evmRpc()에 직접 넘기세요. Polygon에서 POL을 보내는 건 한 줄짜리 diff입니다:

const polygon = cg.networks.evmRpc({
  rpcUrl: cg.rpcUrls.polygon,
  chainId: 137,
  name: 'Polygon',
  symbol: 'POL',
});

const pol = cg.connect(polygon, wallet);
const amount = polygon.amount('5'); // 5 POL

const tx = await pol.transfer(amount, '0xRecipient...');
tx.setFee(tx.recommendedFees().normal);
await tx.signAndBroadcast();

nonce 관리는 자동입니다. SDK는 서명 전에 선택한 RPC에서 최신 pending nonce를 읽으므로 직접 추적할 필요가 없습니다. fee 구성은 지원 체인에서는 기본으로 EIP-1559(maxFeePerGas + maxPriorityFeePerGas)를 사용하고, 지원하지 않는 체인에서는 legacy gas pricing으로 폴백합니다.

evmRpc()에 넘기는 chainId는 RPC 엔드포인트 자신이 보고하는 값과 같아야 합니다. 다르면 SDK는 엉뚱한 네트워크용으로 서명하게 됩니다. EVM 체인 ID 목록에 Polygon, Arbitrum, Base, Optimism과 그 밖의 35개 네트워크 및 testnet의 번호가 있고, 아무 RPC URL에서나 값을 바로 읽어 오는 방법도 나와 있습니다.

노드가 브로드캐스트에 "nonce too low", "replacement transaction underpriced", "insufficient funds for gas * price + value"로 답한다면, Ethereum 트랜잭션 오류 가이드에서 각 메시지의 뜻과 어떤 오류가 gas를 소모하는지 설명합니다.

브라우저 지원은 UTXO 체인과 같습니다. 이 코드가 그대로 dApp의 'Send' 버튼 뒤에 들어갑니다.

4. 전송 전 fee 추정

broadcast 전에 fee를 항상 보여 주세요. 사용자는 깜짝을 싫어하고, 막힌 트랜잭션은 시간을 잡아먹으며, 'review and confirm' 단계는 한 방짜리 UI 흐름을 사용자가 정말로 신뢰하는 무언가로 바꿉니다.

ChainGate는 모든 체인에서 같은 recommendedFees() 메서드를 노출합니다 — 네 개의 tier(low, normal, high, maximum)와 각 tier에 필요한 숫자를 반환합니다. Bitcoin에서는 feePerKbSat 비율과 satoshi 단위 추정 총액을, Ethereum과 다른 EVM 체인에서는 EIP-1559의 maxFeePerGas와 maxPriorityFeePerGas를 wei bigint로, 그리고 추정 확인 시간 창을 함께 받습니다:

// Bitcoin — fee rate in sat/kB, plus the total estimated fee in satoshis
const btcTx = await btc.transfer(cg.networks.bitcoin.amount('0.01'), 'bc1q...');
const btcFees = btcTx.recommendedFees();

console.table({
  low:    { satPerKb: btcFees.low.feePerKbSat,    feeSat: btcFees.low.estimatedFeeSat,    enough: btcFees.low.enoughFunds },
  normal: { satPerKb: btcFees.normal.feePerKbSat, feeSat: btcFees.normal.estimatedFeeSat, enough: btcFees.normal.enoughFunds },
  high:   { satPerKb: btcFees.high.feePerKbSat,   feeSat: btcFees.high.estimatedFeeSat,   enough: btcFees.high.enoughFunds },
});

// Ethereum — EIP-1559 numbers in gwei
const ethTx = await eth.transfer(cg.networks.ethereum.amount('0.1'), '0x...');
const ethFees = ethTx.recommendedFees();
const toGwei = (wei) => Number(wei) / 1e9;

console.table({
  low:     { gwei: toGwei(ethFees.low.maxFeePerGas),     sec: ethFees.low.estimatedConfirmationSecs },
  normal:  { gwei: toGwei(ethFees.normal.maxFeePerGas),  sec: ethFees.normal.estimatedConfirmationSecs },
  high:    { gwei: toGwei(ethFees.high.maxFeePerGas),    sec: ethFees.high.estimatedConfirmationSecs },
  maximum: { gwei: toGwei(ethFees.maximum.maxFeePerGas), sec: ethFees.maximum.estimatedConfirmationSecs },
});

프로덕션에서 통하는 패턴: 트랜잭션을 만들고, recommendedFees()를 호출하고, 네 tier를 사용자에게 보여 선택하게 한 뒤, signAndBroadcast()를 호출합니다. 트랜잭션 객체는 추정과 broadcast 사이에 메모리에 그대로 남습니다 — 다시 만들 필요가 없습니다.

5. 전송 전 잔액 확인

잔액 확인은 어떤 송신 흐름이든 가장 앞에 있어야 합니다. 오타를 잡아내고, 실패할 트랜잭션에 gas를 낭비하는 일을 막아 주며, 사용자가 대조해 볼 수 있는 숫자를 줍니다. 모든 체인이 같은 한 줄을 노출합니다 — 주소를 넘기거나(또는 연결된 지갑을 사용하거나) 확인된 잔액을, 지원 체인에서는 미확인까지 분리해 받을 수 있습니다:

// Ethereum — confirmed balance + live USD price
const eth = cg.connect(cg.networks.ethereum, wallet);
const ethBalance = await eth.addressBalance();
const ethUsd = await ethBalance.confirmed.toCurrency('usd');
console.log(`${ethBalance.confirmed.base()} ETH ($${ethUsd})`);

// Bitcoin — confirmed and unconfirmed split
const btc = cg.connect(cg.networks.bitcoin, wallet);
const btcBalance = await btc.addressBalance();
console.log(`Confirmed:   ${btcBalance.confirmed.base()} BTC`);
console.log(`Unconfirmed: ${btcBalance.unconfirmed.base()} BTC`);

fee 추정과 결합하면 완전한 pre-flight는 잔액, fee, 그리고 송신의 세 호출입니다. 한 번 엮어 두면 앱의 모든 송신 흐름이 같은 사전 검증을 공짜로 누릴 수 있고, 서버의 Node.js에서 크립토 잔액을 확인하든 브라우저에서 확인하든 동일하게 동작합니다.

6. ChainGate vs bitcoinjs-lib, ethers 그리고 다른 대안

ChainGate는 그보다 앞선 모든 저수준 라이브러리를 대체하려는 게 아닙니다. 각각 명확한 역할이 있습니다. 한 체인 이상에서 트랜잭션을 보내야 할 때 trade-off는 다음과 같이 정리됩니다:

역량ChainGatebitcoinjs-libethers / viem체인별 자체 구현
지원 체인BTC, ETH, AVAX+EVM, LTC, DOGE, BCHBitcoin 전용EVM 전용라이브러리당 1
BTC 전송 줄 수~5~40+해당 없음다양
브라우저 준비예 — 즉시 사용polyfill 필요예다양
내장 fee 추정예아니요부분 지원아니요
TypeScript 타입First-class커뮤니티예다양

트랜잭션 형태에 대해 바이트 단위 통제가 필요하거나, 앱이 한 체인하고만 통신하고 번들 크기가 결정 요소라면 bitcoinjs-lib나 ethers를 그대로 쓰세요. 여러 체인을 동시에 다루거나, 데드라인을 쫓고 있거나, 블록체인별로 전문화하기 어려운 작은 팀을 운영하고 있다면 ChainGate로 옮길 만합니다.

7. 요금제와 무료 tier

무료 tier는 일부러 넉넉합니다 — 무료 포함 모든 요금제에 우리가 지원하는 모든 체인의 SDK 전 기능(잔액, assets 목록, fee 추정, 서명, broadcast)이 함께 제공됩니다. 프로토타입을 공개하거나, 개인 프로젝트를 굴리거나, 사내 도구를 프로덕션에 올리기에 충분합니다. 가입 시 신용카드를 요구하지 않습니다.

유료 tier는 rate limit을 올리고 priority support와 burst 용량을 더합니다. 업그레이드는 대시보드에서 한 번의 클릭입니다.

최신 수치와 업그레이드 경로는 요금 페이지에서 확인하세요.

8. 다음으로 갈 곳

모델은 손에 들어왔습니다: 하나의 SDK, 여러 체인, 그리고 각 체인에서 같은 다섯 줄짜리 모양. 여기서부터 문서는 나머지 영역 — 메시지 서명, ERC-20 전송, NFT 이동, 디코드된 트랜잭션 이력 — 을 다룹니다. GitHub 예제는 그대로 가져다 쓰기 좋은 엔드 투 엔드 레시피의 좋은 출발점입니다. 그리고 API 대시보드는 key를 받아 무언가를 출시하고 싶을 때 한 클릭 거리에 있습니다. fee 뒤의 메커니즘을 더 깊이 알고 싶다면, 자매 가이드인 Ethereum gas에서 recommendedFees()의 숫자가 실제로 어디서 오는지 풀어냅니다. signAndBroadcast()가 돌려주는 transactionId가 각 체인에서 어떤 모양인지, 블록 익스플로러에서 어떻게 확인하는지는 트랜잭션 해시 가이드에서 설명합니다.

자주 묻는 질문

ChainGate가 제 private key를 보관하나요?
아닙니다. 지갑 생성, key derivation, 서명은 모두 사용자 기기에서 로컬로 실행됩니다. ChainGate API는 읽기 호출과 이미 서명된 raw 트랜잭션의 broadcast만 처리하며, seed phrase와 private key는 SDK 경계 밖으로 나가지 않습니다.
ChainGate를 브라우저에서 쓸 수 있나요?

예 — 본 가이드의 모든 예제는 Vite, Next.js, Webpack 5 번들에서 수정 없이 동작합니다. SDK가 자체적으로 브라우저 호환 crypto를 함께 담고 있어 추가로 설치할 것이 없습니다.

어떤 네트워크를 지원하나요?

ChainGate는 Bitcoin, Bitcoin Testnet, Litecoin, Dogecoin, Bitcoin Cash, Ethereum, Avalanche에 대한 전체 API 지원과, Polygon, Arbitrum, Base, BNB Chain, Sonic에 대한 호스팅 JSON-RPC 엔드포인트, 그리고 다른 모든 EVM 체인에 대한 bring-your-own-RPC 지원을 제공합니다 — Optimism, Linea, Scroll, Gnosis, 각종 testnet, 자체 호스팅 노드까지 포함입니다. 전체 행렬은 문서에 있습니다.

testnet 트랜잭션도 보낼 수 있나요?

예 — testnet 전용 플래그는 없습니다. mainnet에서 쓰던 같은 cg.connect() 호출에 testnet 네트워크를 그대로 넘기면 됩니다. Bitcoin이라면 cg.networks.bitcoinTestnet입니다. EVM testnet이라면 그 testnet의 JSON-RPC URL과 chain ID를 cg.networks.evmRpc({ rpcUrl, chainId, name, symbol })에 넘기세요. 나머지 코드는 본 가이드의 mainnet 예제와 똑같습니다.

트랜잭션이 실패하면 어떻게 되나요?

SDK는 잡아서 처리할 수 있는 타입이 지정된 에러를 노출합니다. broadcast 이전의 문제는 대부분 복구 가능하며, 재시도 시 트랜잭션 객체가 새 nonce 또는 새 UTXO 선택으로 다시 만들어지므로 두 번째 시도는 보통 통과합니다.

ChainGate는 오픈소스인가요?

사용자의 머신에서 서명과 broadcast를 수행하는 JavaScript SDK는 MIT 라이선스 아래 GitHub에 공개되어 있습니다 — 소스를 읽고, vendor 하고, fork 해도 됩니다. 그 뒤에 있는 ChainGate API는 나머지를 모두 알아서 처리해 주는 매니지드 서비스입니다. 대부분의 팀은 오픈소스 SDK와 호스팅 API를 함께 쓰지만, 자체 인프라를 운영하고 싶다면 SDK를 사용자가 통제하는 임의의 RPC URL로 향하게 할 수 있습니다.

ChainGate에 왜 API key가 필요한가요?
API key는 ChainGate 백엔드 호출을 인증합니다. 백엔드는 SDK 혼자서 할 수 없는 부분 — 사용하는 체인으로의 JSON-RPC 요청 처리, UTXO와 잔액 조회, 서명된 트랜잭션의 broadcast — 을 담당합니다. 서명은 여전히 사용자의 머신에서 지갑으로 이뤄집니다. key는 ChainGate 인프라를 거치는 read와 broadcast 트래픽만 허용합니다. 무료 플랜은 대시보드에서 신용카드 없이 key를 발급할 수 있습니다.

ChainGate로 만들기

아이디어를 코드로 연결하세요 — ChainGate는 Ethereum과 20여 개 블록체인을 위한 지갑, 트랜잭션, 관리형 RPC를 하나의 라이브러리로 제공합니다.