로컬에서 빌드하고 데브넷에서 테스트하는 것은 Solana 결제를 시작하는 좋은 방법입니다. 하지만 메인넷에 배포할 준비가 되었다면 메인넷의 특성을 알아야 합니다. 데브넷은 실수를 용인하지만 메인넷은 그렇지 않습니다. 이 가이드는 사용자에게 원활한 경험을 제공하기 위해 중요한 차이점을 다룹니다.
| 데브넷 | 메인넷 |
|---|---|
| 파우셋에서 무료 SOL 제공 | 실제 SOL 획득하여 수수료 지불 |
| 블록 공간 경쟁 낮음 | 우선순위 수수료가 중요함 |
| 트랜잭션이 쉽게 처리됨 | 트랜잭션 구성이 중요함 |
| 퍼블릭 RPC로 충분함 | 프로덕션 RPC 필요 |
| 데브넷 키페어 및 민트 | 다른 키와 토큰 민트—구성 업데이트 필요 |
RPC 인프라
퍼블릭 엔드포인트
(api.mainnet-beta.solana.com)는 SLA 없이 속도 제한이 있습니다. 개발에는
괜찮지만 프로덕션 결제 플로우에서는 실패할 것입니다—가동 시간 보장 없이 공유
API를 통해 결제 프로세서를 실행하려는 것과 같습니다.
프로덕션에서는 절대 퍼블릭 RPC를 사용하지 마세요
안정적이고 지연 시간이 짧은 액세스를 위해 프라이빗 RPC 제공업체를 사용하세요.
RPC 제공업체를 선택할 때 다음을 확인하세요:
- 안정성: 가동 시간 보장이 있는 SLA (99.9%+)
- 지연 시간: 사용자와의 지리적 근접성
- 기능: 트랜잭션 랜딩 기능, 인덱싱, 우선순위 수수료 API
RPC 제공업체의 전체 목록은 RPC 인프라 제공업체 가이드를 참조하세요.
중복 RPC 구성
모든 네트워크 서비스 제공업체와 마찬가지로 RPC 제공업체도 다운타임이나 성능 저하 기간을 경험할 수 있습니다. 애플리케이션의 복원력을 보장하려면 여러 RPC 제공업체를 사용하도록 애플리케이션을 구성해야 합니다.
Solana Kit은 RPC 전송을 사용자 정의할 수 있는 라이브러리를 제공하여 자체 중복 RPC 클라이언트를 구축할 수 있게 해줍니다. 다음은 중복 RPC 클라이언트를 구축하는 데 사용할 수 있는 예시입니다:
import { RpcTransport } from "@solana/rpc-spec";import { RpcResponse } from "@solana/rpc-spec-types";import { createHttpTransport } from "@solana/rpc-transport-http";// Create a transport for each RPC serverconst transports = [createHttpTransport({ url: "https://mainnet-beta.my-server-1.com" }),createHttpTransport({ url: "https://mainnet-beta.my-server-2.com" }),createHttpTransport({ url: "https://mainnet-beta.my-server-3.com" })];// Create a wrapper transport that distributes requests to themlet nextTransport = 0;async function roundRobinTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {const transport = transports[nextTransport];nextTransport = (nextTransport + 1) % transports.length;return await transport(...args);}
자체 라우팅 도구를 구축하지 않으려면 Iron Forge와 같은 타사 서비스를 활용하여 라우팅을 처리할 수 있습니다.
트랜잭션 랜딩
Devnet에서는 트랜잭션이 비교적 쉽게 랜딩됩니다. Mainnet에서는 블록 공간을 두고 경쟁해야 합니다. 트랜잭션이 블록에 포함될 가능성을 높이려면 트랜잭션을 적절하게 구성해야 합니다. 이는 다음을 의미합니다:
- 트랜잭션을 전송하기 전에 최신 블록해시 포함
- 경쟁력 있는 우선순위 수수료가 포함된 우선순위 수수료 명령어를 트랜잭션에 포함
- 트랜잭션에 필요한 예상 컴퓨팅 유닛을 기반으로 한 컴퓨팅 유닛 제한 명령어를 트랜잭션에 포함
또한 Jito Bundles와 같은 다른 도구를 고려하여 트랜잭션이 블록에 포함될 가능성을 높여야 합니다. 이러한 도구들을 더 자세히 살펴보겠습니다.
트랜잭션 전송 구성
Mainnet에서 트랜잭션을 전송할 때 최적의 랜딩률을 위해 다음 매개변수를 구성하세요:
블록해시 관리:
confirmed커밋먼트로 가져오기getLatestBlockhash에서 반환된lastValidBlockHeight를 저장—이는 트랜잭션이 만료되는 시점을 알려줍니다- 블록해시는 약 150개 블록(약 60-90초) 후에 만료됩니다
전송 옵션:
maxRetries: 0— 자동 RPC 재시도를 비활성화합니다. 필요할 때 블록해시를 새로 고칠 수 있도록 재시도를 직접 처리하세요.skipPreflight: true— 전송 전 시뮬레이션을 건너뜁니다. 이미 트랜잭션을 검증했고 최저 지연 시간을 원할 때 사용하세요. 개발 중에는 오류를 조기에 발견하기 위해false로 유지하세요.
import { createSolanaRpc } from "@solana/kit";const rpc = createSolanaRpc(process.env.RPC_URL!);// 1. Get blockhash with confirmed commitmentconst { value: latestBlockhash } = await rpc.getLatestBlockhash({ commitment: "confirmed" }).send();// 2. Build and sign your transaction with the blockhash// ... (transaction building code)// 3. Send with production settingsconst signature = await rpc.sendTransaction(encodedTransaction, {encoding: "base64",maxRetries: 0n, // Handle retries yourselfskipPreflight: true, // Skip simulation for speed (use false during dev)preflightCommitment: "confirmed"}).send();// 4. Track expiration using lastValidBlockHeightconst { lastValidBlockHeight } = latestBlockhash;// Stop retrying when current block height exceeds lastValidBlockHeight
우선순위 수수료 사용
모든 Solana 트랜잭션은 SOL로 지불되는 트랜잭션 수수료가 필요합니다. 트랜잭션 수수료는 기본 수수료와 우선순위 수수료의 두 부분으로 나뉩니다. 기본 수수료는 트랜잭션 처리에 대한 검증자 보상입니다. 우선순위 수수료는 현재 리더가 트랜잭션을 처리할 가능성을 높이기 위한 선택적 수수료입니다. 특급 배송처럼 생각하면 됩니다. 더 빠르고 안정적인 전달을 위해 더 많은 비용을 지불하는 것입니다.
수수료 작동 방식:
Total fee = Base fee (5,000 lamports per signature) + Priority feePriority fee = Compute units x Price per unit (micro-lamports per compute unit)
실제 비용:
- 간단한 USDC 전송: 정상 상태에서 약 $0.001-0.005
- 혼잡 시: 약 $0.01-0.05
- 최대 혼잡 시: 더 높게 급등 가능
샘플 구현:
@solana-program/compute-budget
패키지는 트랜잭션에 컴퓨트 유닛 가격(마이크로 램포트 단위) 명령을 쉽게
업데이트하거나 추가할 수 있는 헬퍼 함수를 제공합니다.
import { updateOrAppendSetComputeUnitPriceInstruction } from "@solana-program/compute-budget";const tx = pipe(createTransactionMessage({ version: 0 }),(m) => setTransactionMessageFeePayerSigner(payer, m),(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),(m) => appendTransactionMessageInstructions([myInstructions], m),(m) => updateOrAppendSetComputeUnitPriceInstruction(1000n as MicroLamports, m));
수수료 추정 가져오기: 대부분의 RPC 제공자는 우선순위 수수료 API를 제공합니다:
전체 수수료 메커니즘은 트랜잭션 수수료와 가이드 트랜잭션에 우선순위 수수료를 추가하는 방법을 참조하세요.
컴퓨트 유닛 최적화
Solana의 컴퓨트는 프로그램이 수행하는 작업량을 효과적으로 측정한 것입니다. 트랜잭션에서 사용할 수 있는 컴퓨트 양에는 제한이 있으며(현재 140만 컴퓨트 유닛), 블록당 계정당 사용할 수 있는 컴퓨트 양에도 제한이 있습니다(현재 1억 컴퓨트 유닛).
트랜잭션을 제출할 때 사용될 컴퓨트 양을 추정하고 그에 따라 컴퓨트 유닛 제한을 설정해야 합니다. 이는 트랜잭션을 위해 예약해야 하는 전체 용량의 양에 대한 요청입니다. 실제로 이는 트랜잭션에 필요한 컴퓨트 유닛을 적절히 추정하는 것이 트랜잭션을 블록에 포함시키는 데 중요하며(우선순위 수수료 관리에도 중요함을 의미합니다).
Solana JSON RPC API에는 트랜잭션에 필요한 컴퓨트 유닛을 추정하는 데 사용할 수
있는 simulatetransaction 메서드가
있으며, 여기에는 사용될 컴퓨트 유닛의 추정치가 포함됩니다.
@solana-program/compute-budget
패키지는 트랜잭션에 필요한 컴퓨트 유닛을 쉽게 추정할 수 있는 헬퍼 함수를
제공합니다(내부적으로 simulatetransaction 메서드를 사용합니다).
import {estimateComputeUnitLimitFactory,updateOrAppendSetComputeUnitLimitInstruction} from "@solana-program/compute-budget";const estimateComputeUnitLimit = estimateComputeUnitLimitFactory({ rpc });const computeUnitLimit = await estimateComputeUnitLimit(tx);const txWithComputeUnitLimit = updateOrAppendSetComputeUnitLimitInstruction(computeUnitLimit,tx);
프로덕션 환경에서 동일한 유형의 트랜잭션을 여러 번 반복하는 경우, 매번 컴퓨트 유닛을 추정하는 오버헤드를 피하기 위해 해당 트랜잭션 유형에 대한 컴퓨트 추정치를 캐싱하는 것을 고려해야 합니다.
Jito 번들
Jito 번들은 여러 트랜잭션의 원자적 실행을 관리하기 위한 도구입니다. 이는 팁과 함께 여러 트랜잭션을 Jito 네트워크에 전송함으로써 달성됩니다. 팁은 Jito 네트워크가 귀하의 트랜잭션을 블록에 포함하도록 인센티브를 제공하는 데 사용될 수 있습니다.
리소스:
재시도 전략
트랜잭션은 여러 가지 이유로 실패할 수 있습니다. 즉시 성공/실패를 반환하는 기존 결제 API와 달리, 블록체인 트랜잭션은 확인 추적이 필요합니다.
주요 개념:
- 블록해시 만료: 트랜잭션은 약 150개 블록(약 60-90초) 동안 유효합니다
- 멱등성: 동일하게 서명된 트랜잭션은 항상 동일한 서명을 생성하므로 재전송이 안전합니다
- 지수 백오프: 빠른 재시도로 네트워크에 과부하를 주지 않도록 합니다
import {createSolanaRpc,createSolanaRpcSubscriptions,sendAndConfirmTransactionFactory,isSolanaError,SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED} from "@solana/kit";const rpc = createSolanaRpc(process.env.RPC_URL!);const rpcSubscriptions = createSolanaRpcSubscriptions(process.env.RPC_WSS_URL!);const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({rpc,rpcSubscriptions});// Send with automatic confirmation tracking and block height monitoringtry {await sendAndConfirmTransaction(signedTransaction, {commitment: "confirmed",// Optional: abort after 75 secondsabortSignal: AbortSignal.timeout(75_000)});} catch (e) {if (isSolanaError(e, SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED)) {// Blockhash expired—rebuild transaction with fresh blockhash and retryrebuildAndRetryTransaction(); // implement your own logic for rebuilding and retrying the transaction}throw e;}
@solana/kit의 sendAndConfirmTransactionFactory는 확인 폴링과 블록 높이
추적을 자동으로 처리합니다. 트랜잭션의 블록해시가 만료되면
SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED를 발생시켜 새로운 블록해시로 트랜잭션을
재구성해야 함을 알립니다.
추가 자료
확인 수준 이해하기
Solana는 세 가지 확인 수준을 제공합니다. 전통적인 금융 용어로 표현하면:
| 수준 | Solana 정의 | 전통적인 금융 용어 | 사용 사례 |
|---|---|---|---|
processed | 블록에 포함되었으나 아직 투표되지 않음 | 승인 대기 중 | 실시간 UI 업데이트 |
confirmed | 절대 다수가 투표함 | 결제 완료 | 대부분의 결제 |
finalized | 루트화됨, 되돌릴 수 없음 | 정산 완료 | 고액 거래, 컴플라이언스 |
각 수준을 사용해야 하는 경우:
- UI 업데이트: 즉각적인 피드백을 위해
processed표시 ("결제 제출됨") - 사용자 계정 입금:
confirmed대기 (대부분의 트랜잭션에 안전함) - 실물 상품 배송:
finalized대기 - 대규모 출금:
finalized대기 - 컴플라이언스/감사: 항상
finalized상태 기록
트랜잭션 상태 확인에 대한 자세한 내용은 Solana와 상호작용하기를 참조하세요.
오류 처리
Solana Kit은 isSolanaError()를 통해 타입이 지정된 오류를 제공합니다. 문자열
매칭 대신 특정 오류 코드를 사용하세요:
import {isSolanaError,SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED,SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE,SOLANA_ERROR__TRANSACTION_ERROR__BLOCKHASH_NOT_FOUND,SOLANA_ERROR__INSTRUCTION_ERROR__INSUFFICIENT_FUNDS} from "@solana/kit";function handlePaymentError(error: unknown): {message: string;retryable: boolean;} {// Blockhash expired—rebuild and retryif (isSolanaError(error, SOLANA_ERROR__BLOCK_HEIGHT_EXCEEDED) ||isSolanaError(error, SOLANA_ERROR__TRANSACTION_ERROR__BLOCKHASH_NOT_FOUND)) {return { message: "Transaction expired—rebuilding", retryable: true };}// Insufficient SOL for feesif (isSolanaError(error,SOLANA_ERROR__TRANSACTION_ERROR__INSUFFICIENT_FUNDS_FOR_FEE)) {return { message: "Not enough SOL for fees", retryable: false };}// Insufficient token balanceif (isSolanaError(error, SOLANA_ERROR__INSTRUCTION_ERROR__INSUFFICIENT_FUNDS)) {return { message: "Insufficient balance", retryable: false };}// Unknown errorconsole.error("Payment error:", error);return { message: "Payment failed—please retry", retryable: true };}
일반적인 오류 코드:
| 오류 코드 | 원인 | 복구 방법 |
|---|---|---|
BLOCK_HEIGHT_EXCEEDED | 블록해시 만료 | 새로운 블록해시로 재구성 |
BLOCKHASH_NOT_FOUND | 블록해시를 찾을 수 없음 | 새로운 블록해시로 재구성 |
INSUFFICIENT_FUNDS_FOR_FEE | SOL 부족 | 수수료 지불자에게 자금 제공 또는 수수료 추상화 사용 |
INSUFFICIENT_FUNDS | 토큰 부족 | 사용자가 더 많은 잔액 필요 |
ACCOUNT_NOT_FOUND | 토큰 계정 누락 | 트랜잭션에서 ATA 생성 |
가스리스 트랜잭션
사용자는 네트워크 수수료를 위해 SOL을 획득하는 것이 아니라 스테이블코인으로 결제하기를 기대합니다. 가스리스 트랜잭션은 이 문제를 해결합니다. 마치 Venmo 사용자가 ACH 수수료에 대해 생각하지 않는 것과 유사합니다. 전체 구현은 수수료 추상화를 참조하세요.
보안
키 관리
- 프론트엔드 코드에서 개인 키를 절대 노출하지 마세요. 백엔드 서명, 하드웨어 지갑, 다중 서명 지갑 또는 키 관리 서비스를 사용하세요.
- 핫 월렛과 콜드 월렛을 분리하세요. 운영용 핫 월렛, 자금 보관용 콜드 월렛을 사용하세요.
- 모든 프로덕션 키를 백업하세요. 암호화된 백업을 여러 안전한 위치에 저장하세요. 키를 분실하면 영구적으로 액세스를 잃게 됩니다.
- devnet과 mainnet에 서로 다른 키를 사용하세요. devnet 키가 mainnet 키와 같아서는 안 됩니다. 환경 기반 구성을 사용하여 각 네트워크에 적합한 키가 로드되도록 하세요.
RPC 보안
RPC 엔드포인트를 API 키처럼 취급하세요. 추출되고 악용될 수 있는 프론트엔드 코드에 노출하지 마세요. 백엔드 프록시 또는 클라이언트 코드에 번들되지 않는 환경 변수를 사용하세요.
모니터링
프로덕션에서 다음 지표를 추적하세요:
| 지표 | 이유 |
|---|---|
| 트랜잭션 성공률 | 조기 문제 감지 |
| 확인 지연 시간 | 네트워크 상태 모니터링 |
| 우선순위 수수료 지출 | 비용 관리 |
| RPC 오류율 | 제공자 상태 |
다음에 대한 알림을 설정하세요:
- 자금 보관소에서 임계값을 초과하는 전송
- 실패한 트랜잭션 비율 급증
- 비정상적인 수신자 패턴
- RPC 오류율 증가
대규모 실시간 트랜잭션 모니터링은 인덱싱 가이드를 참조하세요.
주소 확인
모든 토큰과 프로그램은 메인넷에서 정확히 하나의 올바른 주소를 가집니다. USDC나 다른 스테이블코인을 모방한 위조 토큰이 흔합니다. 이들은 동일한 이름과 심볼을 가지지만 다른 민트 주소를 가집니다. 애플리케이션은 민트 주소를 하드코딩하거나 허용 목록에 추가해야 하며(요구사항에 따라), 신뢰할 수 없는 소스에서 동적으로 받아들여서는 안 됩니다.
환경 기반 구성: 데브넷과 메인넷은 종종 완전히 다른 토큰 민트를 사용합니다. 환경별로 올바른 주소를 로드하도록 애플리케이션 구성을 설정하세요. 메인넷 주소를 하드코딩하고 테스트 중에 교체하는 것을 잊거나, 더 나쁘게는 데브넷 주소를 프로덕션에 배포하지 마세요.
일반적인 스테이블코인 민트는 다음과 같습니다:
| 토큰 | 발행자 | 민트 주소 |
|---|---|---|
| USDC | Circle | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| USDT | Tether | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB |
| PYUSD | PayPal | 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo |
| USDG | Paxos | 2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH |
프로그램 주소도 중요합니다. 잘못된 프로그램으로 명령을 보내면 실패하거나, 더 나쁘게는 자금의 돌이킬 수 없는 손실을 초래할 수 있습니다. Token Program 주소는 다음과 같습니다:
| 프로그램 | 주소 |
|---|---|
| Token Program | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA |
| Token-2022 Program | TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb |
출시 전 체크리스트
- 수수료 및 rent를 위한 메인넷 SOL 확보
- 프로덕션 RPC 구성 완료(공개 엔드포인트 아님)
- 대체 RPC 엔드포인트 구성 완료
- 동적 가격 책정을 통한 우선순위 수수료 구현
- 재시도 로직이 블록해시 만료 처리
- 사용 사례에 적합한 확인 수준 설정
- 모든 일반적인 오류를 우아하게 처리
- 가스리스 구성 완료(해당되는 경우)
- 메인넷 토큰 주소 확인 완료(데브넷 민트 아님)
- 모든 키를 안전하게 백업
- 키 관리 검토 완료(프론트엔드에 키 없음)
- 트랜잭션 모니터링 및 알림 활성화
- 예상 볼륨에서 부하 테스트 완료
프로그램 배포
결제 인프라의 일부로 커스텀 Solana 프로그램을 배포하는 경우 추가로 고려해야 할 사항이 있습니다.
배포 전 준비
- Solana CLI 버전: 최신 버전의 Solana CLI를 사용하고 있는지 확인하세요.
- 프로그램 keypair: 프로그램은 devnet과 mainnet에서 서로 다른 주소를 갖게
됩니다(동일한 keypair를 재사용하지 않는 한). 애플리케이션 설정의 모든 참조를
업데이트하세요. 프로그램 keypair는 안전한 위치에 보관하세요(
cargo clean를 실행하면 프로그램 keypair가 삭제될 수 있습니다). - 계정 초기화: 프로그램에 관리자 계정, PDA 또는 기타 상태 계정이 필요한 경우, 사용자가 애플리케이션과 상호작용하기 전에 mainnet에서 이러한 계정을 생성해야 합니다. 프로그램에 필요한 연결 토큰 계정(ATA)도 마찬가지입니다.
배포 프로세스
- 버퍼 계정: 대용량 프로그램은 버퍼 계정을 통해 배포됩니다.
solana program deploy명령이 이를 자동으로 처리하지만, 배포는 원자적이지 않다는 점을 이해해야 합니다. 중단된 경우 버퍼 계정을 복구하거나 닫아야 할 수 있습니다. 프로그램 배포를 참조하세요. - 업그레이드 권한: 프로그램이 출시 후 업그레이드 가능해야 하는지 결정하세요. 불변성을 위해서는 배포 후 업그레이드 권한을 취소하세요. 유연성을 위해서는 업그레이드 권한 키를 적절하게 보호하세요.
- 렌트: 배포 지갑에 모든 프로그램 계정의 렌트 면제 최소 금액을 충당할 수 있는 충분한 SOL이 있는지 확인하세요.
- 검증: 프로그램을 검증하여 Solana 네트워크에 배포한 실행 가능한 프로그램이 리포지토리의 소스 코드와 일치하는지 확인하세요.
전체 프로그램 배포 가이드는 프로그램 배포를 참조하세요.
Is this page helpful?