로컬에서 빌드하고 데브넷에서 테스트하는 것은 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/kit는 트랜잭션의 리소스 한도를
추정하고 한 번에 메시지에 설정하는 헬퍼 함수를 제공합니다 (내부적으로
simulatetransaction 메서드를 사용). 버전 1 트랜잭션의 경우 이 헬퍼들은 로드된
계정 데이터 크기 한도도 함께 추정합니다.
import {estimateResourceLimitsFactory,estimateAndSetResourceLimitsFactory} from "@solana/kit";const estimateResourceLimits = estimateResourceLimitsFactory({ rpc });const estimateAndSetResourceLimits = estimateAndSetResourceLimitsFactory(estimateResourceLimits);const txWithResourceLimits = await estimateAndSetResourceLimits(tx);
kit 플러그인 클라이언트로 트랜잭션을 빌드하고 전송하는 경우, 일반적으로 이
단계는 필요하지 않습니다 — 클라이언트가 전송 시(.sendTransaction()) 컴퓨팅
예산 명령어를 자동으로 추가합니다. 위의 수동 흐름은 @solana/kit로 트랜잭션을
직접 조립할 때 사용합니다.
프로덕션 환경에서 동일한 유형의 트랜잭션을 여러 번 반복하는 경우, 매번 컴퓨팅 유닛을 추정하는 오버헤드를 줄이기 위해 해당 트랜잭션 유형의 컴퓨팅 추정값을 캐싱하는 것을 고려하세요.
Jito 번들
Jito 번들은 여러 트랜잭션의 원자적 실행을 관리하는 도구입니다. 팁과 함께 여러 트랜잭션을 Jito 네트워크에 전송함으로써 이를 구현합니다. 팁은 Jito 네트워크가 귀하의 트랜잭션을 블록에 포함하도록 인센티브를 제공하는 데 사용할 수 있습니다.
리소스:
재시도 전략
트랜잭션은 다양한 이유로 실패할 수 있습니다. 즉시 성공/실패를 반환하는 전통적인 결제 API와 달리, 블록체인 트랜잭션은 확인 추적이 필요합니다.
핵심 개념:
- 블록해시 만료: 트랜잭션은 약 150개 블록(
6090초) 동안 유효합니다 - 멱등성: 동일하게 서명된 트랜잭션은 항상 동일한 서명을 생성하므로 재제출이 안전합니다
- 지수 백오프: 빠른 재시도로 네트워크에 과부하를 주지 않도록 합니다
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;}
sendAndConfirmTransactionFactory는 @solana/kit에서 확인 폴링과 블록 높이
추적을 자동으로 처리합니다. 트랜잭션의 블록해시가 만료되면
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 | token account 없음 | 트랜잭션에서 ATA 생성 |
가스리스 트랜잭션
사용자들은 네트워크 수수료를 위해 SOL을 획득하는 대신 스테이블코인으로 결제하기를 원합니다. 가스리스 트랜잭션은 이 문제를 해결합니다—Venmo 사용자들이 ACH 수수료를 신경 쓰지 않는 것과 유사합니다. 전체 구현 방법은 수수료 추상화 를 참조하세요.
보안
키 관리
- 프론트엔드 코드에 개인 키를 절대 노출하지 마세요. 백엔드 서명, 하드웨어 지갑, 멀티시그 지갑 또는 키 관리 서비스를 사용하세요.
- 핫 월렛과 콜드 월렛을 분리하세요. 핫 월렛은 운영용으로, 콜드 월렛은 자산 보관용으로 사용합니다.
- 모든 프로덕션 키를 백업하세요. 여러 안전한 장소에 암호화된 백업을 보관하세요. 키를 분실하면 영구적으로 접근 권한을 잃게 됩니다.
- 개발넷과 메인넷에 서로 다른 키를 사용하세요. 개발넷 키를 메인넷 키로 사용해서는 안 됩니다. 환경 기반 설정을 사용하여 각 네트워크에 올바른 키가 로드되도록 하세요.
서명 인프라
프로덕션 백엔드 서명에는 Keychain을 사용하세요—단일 인터페이스를 통해 다양한 키 관리 백엔드를 추상화하는 통합 서명 라이브러리입니다: Memory, Vault, Privy, Turnkey, AWS KMS, Fireblocks, GCP KMS, CDP, Para, Dfns, Crossmint, Openfort, Utila를 지원합니다. 이를 통해 로컬 개발 시에는 인메모리 키를 사용하고, 애플리케이션 코드 변경 없이 프로덕션 수준의 백엔드로 전환할 수 있습니다.
어떤 백엔드가 적합한지 모르겠다면 백엔드 선택 가이드를 참조하세요. Keychain은 Rust와 TypeScript 모두에서 사용 가능합니다.
RPC 보안
RPC 엔드포인트는 API 키처럼 취급하세요. 추출되어 악용될 수 있는 프론트엔드 코드에 노출하지 마세요. 클라이언트 코드에 번들되지 않는 백엔드 프록시나 환경 변수를 사용하세요.
모니터링
프로덕션에서 다음 지표를 추적하세요:
| 지표 | 이유 |
|---|---|
| 트랜잭션 성공률 | 조기 문제 감지 |
| 확인 지연 시간 | 네트워크 상태 모니터링 |
| 우선순위 수수료 지출 | 비용 관리 |
| RPC 오류율 | 제공자 상태 |
다음에 대한 알림을 설정하세요:
- 자금 관리 계정에서 임계값 초과 전송
- 실패한 트랜잭션 비율 급증
- 비정상적인 수신자 패턴
- RPC 오류율 증가
대규모 실시간 트랜잭션 모니터링에 대해서는 인덱싱 가이드를 참조하세요.
주소 확인
모든 토큰과 프로그램은 메인넷에서 정확히 하나의 올바른 주소를 갖습니다. USDC 또는 다른 스테이블코인을 모방한 스푸핑된 토큰이 흔합니다. 이들은 동일한 이름과 심볼을 가지지만 다른 민트 주소를 갖습니다. 귀하의 애플리케이션은 민트 주소를 하드코딩하거나 허용 목록에 추가해야 하며(요구사항에 따라), 신뢰할 수 없는 소스로부터 동적으로 받아들여서는 안 됩니다.
환경 기반 구성: Devnet과 Mainnet은 종종 완전히 다른 토큰 민트를 사용합니다. 환경별로 올바른 주소를 로드하도록 애플리케이션 설정을 구성하세요. 메인넷 주소를 하드코딩한 후 테스트 중에 변경하는 것을 잊거나, 더 나쁘게는 프로덕션에 devnet 주소를 배포하는 일이 없도록 주의하세요.
일반적인 스테이블코인 민트는 다음과 같습니다:
| 토큰 | 발행자 | 민트 주소 |
|---|---|---|
| USDC | Circle | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| USDT | Tether | Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB |
| PYUSD | PayPal | 2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo |
| USDG | Paxos | 2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH |
프로그램 주소도 중요합니다. 잘못된 프로그램으로 인스트럭션을 전송하면 실패하거나, 더 심각하게는 자금의 복구 불가능한 손실을 초래할 수 있습니다. Token Program 주소는 다음과 같습니다:
| 프로그램 | 주소 |
|---|---|
| Token Program | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA |
| Token-2022 Program | TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb |
올바른 주소를 허용 목록에 추가하면 위조된 토큰으로부터 보호할 수 있지만, 잘못된 종류의 계정으로 전송하는 것은 방지하지 못합니다. 수신자 주소는 지갑, token account, 민트, 또는 프로그램일 수 있으며, 각각 다른 처리 방식이 필요합니다. 지갑 이외의 대상으로 전송된 네이티브 SOL은 발신자의 통제를 벗어나게 됩니다 — 민트나 프로그램의 경우 즉시 소실되고, token account의 경우 계정 소유자만 복구할 수 있으며 — 사용자에게 경고하는 트랜잭션 실패 알림도 발생하지 않습니다.
전송 전 수신자 주소 검증
서명하기 전에 모든 수신자 주소를 분류하세요. 지갑, token account, 민트, 프로그램을 구분하는 전체 결정 트리와 참조 코드는 주소 검증을 참고하세요.
출시 전 체크리스트
- 수수료 및 rent 용 메인넷 SOL 확보
- 프로덕션 RPC 구성 완료 (공개 엔드포인트 사용 금지)
- 대체 RPC 엔드포인트 구성 완료
- 동적 가격 책정을 통한 우선순위 수수료 구현
- 재시도 로직이 블록해시 만료를 처리하도록 구현
- 사용 사례에 적합한 확인 수준 설정
- 모든 일반 오류 정상 처리
- 가스리스 구성 완료 (해당하는 경우)
- 메인넷 토큰 주소 검증 완료 (devnet 민트 사용 금지)
- 전송 전 수신자 주소 검증 (지갑 vs token account vs 민트 vs 프로그램)
- 모든 키 안전하게 백업 완료
- 키 관리 검토 완료 (프론트엔드에 키 저장 금지)
- 트랜잭션 모니터링 및 알림 활성화
- 예상 트래픽 규모에서 부하 테스트 완료
프로그램 배포
결제 인프라의 일부로 커스텀 Solana 프로그램을 배포하는 경우, 추가적으로 고려해야 할 사항이 있습니다.
배포 전 준비
- Solana CLI 버전: 최신 버전의 Solana CLI를 사용하고 있는지 확인하세요.
- 프로그램 keypair: 프로그램은 메인넷에서 데브넷과 다른 주소를 갖게
됩니다(동일한 keypair를 재사용하는 경우 제외). 애플리케이션 설정의 모든 참조를
업데이트하세요. 프로그램 keypair는 안전한 위치에 보관하세요(
cargo clean실행 시 프로그램 keypair가 삭제될 수 있습니다). - 계정 초기화: 프로그램에 관리자 계정, PDA 또는 기타 상태 계정이 필요한 경우, 사용자가 애플리케이션과 상호작용하기 전에 메인넷에서 해당 계정들이 생성되어 있는지 확인하세요. 프로그램에 필요한 Associated Token Accounts (ATAs)도 마찬가지입니다.
배포 프로세스
- 버퍼 계정: 대용량 프로그램은 버퍼 계정을 통해 배포됩니다.
solana program deploy명령이 이를 자동으로 처리하지만, 배포가 원자적이지 않다는 점을 이해해야 합니다. 중단될 경우 버퍼 계정을 복구하거나 닫아야 할 수 있습니다. 프로그램 배포를 참조하세요. - 업그레이드 권한: 프로그램이 출시 후 업그레이드 가능해야 하는지 결정하세요. 불변성을 원한다면 배포 후 업그레이드 권한을 철회하세요. 유연성을 원한다면 업그레이드 권한 키를 적절히 보호하세요.
- 렌트: 배포 지갑에 모든 program account의 렌트 면제 최소 금액을 충당할 수 있는 충분한 SOL이 있는지 확인하세요.
- 검증: 프로그램을 검증하여 Solana 네트워크에 배포한 실행 가능한 프로그램이 리포지토리의 소스 코드와 일치하는지 확인하세요.
완전한 프로그램 배포 가이드는 프로그램 배포를 참조하세요.
Is this page helpful?