런치 유형
Launch Pool
Last updated January 31, 2026
Launch Pool은 토큰 출시를 위한 유기적인 가격 발견을 제공합니다. 사용자는 일정 기간 동안 예치하고 총 예치금에서의 지분에 비례하여 토큰을 받습니다 - 스나이핑 없음, 프론트러닝 없음, 모두에게 공정한 배분.
학습 내용
이 가이드에서 다루는 내용:
- Launch Pool 가격 책정 및 배분 작동 방식
- 예치 및 청구 기간 설정
- 자금 수집을 위한 End behavior 구성
- 사용자 작업: 예치, 출금, 청구
요약
Launch Pool은 정해진 기간 동안 예치금을 받은 후 토큰을 비례적으로 배분합니다. 최종 토큰 가격은 총 예치금을 토큰 할당량으로 나누어 결정됩니다.
- 사용자는 예치 기간 동안 SOL을 예치합니다 (2% 수수료 적용)
- 예치 기간 동안 출금 가능 (2% 수수료)
- 토큰 배분은 예치 지분에 비례
- End behavior가 수집된 SOL을 Treasury bucket으로 라우팅
범위 외
고정 가격 판매는 Presale을, 입찰 기반 경매는 Uniform Price Auction을, 유동성 풀 생성은 Raydium/Orca를 참조하세요.
빠른 시작
작동 방식
- 특정 수량의 토큰이 Launch Pool bucket에 할당됩니다
- 사용자는 예치 기간 동안 SOL을 예치합니다 (수수료와 함께 출금 가능)
- 기간이 종료되면 예치 지분에 따라 토큰이 비례적으로 배분됩니다
가격 발견
토큰 가격은 총 예치금에서 도출됩니다:
tokenPrice = totalDeposits / tokenAllocation
userTokens = (userDeposit / totalDeposits) * tokenAllocation
예시: 1,000,000 토큰 할당, 총 100 SOL 예치 = 토큰당 0.0001 SOL
라이프사이클
- 예치 기간 - 사용자가 정해진 기간 동안 SOL을 예치
- Transition - End behavior 실행 (예: 수집된 SOL을 다른 bucket으로 전송)
- 청구 기간 - 사용자가 예치 비중에 비례한 토큰을 청구
수수료
| 인스트럭션 | Solana |
|---|---|
| Deposit | 2% |
| Withdraw | 2% |
| Graduation | 5% |
예치 수수료 예시: 사용자가 10 SOL을 예치하면 9.8 SOL이 사용자의 예치 계정에 적립됩니다.
설정 가이드
사전 요구 사항
npm install @metaplex-foundation/genesis @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/mpl-toolbox
1. Genesis Account 초기화
Genesis Account는 토큰을 생성하고 모든 배포 bucket을 조정합니다.
1import {
2 findGenesisAccountV2Pda,
3 genesis,
4 initializeV2,
5} from '@metaplex-foundation/genesis'
6import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
7import { generateSigner, keypairIdentity } from '@metaplex-foundation/umi'
8import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
9
10const umi = createUmi('https://api.mainnet-beta.solana.com')
11 .use(mplToolbox())
12 .use(genesis())
13
14// umi.use(keypairIdentity(yourKeypair));
15
16const baseMint = generateSigner(umi)
17const TOTAL_SUPPLY = 1_000_000_000_000_000n // 1 million tokens (9 decimals)
18
19// Store this account address for later or recreate it when needed.
20const [genesisAccount] = findGenesisAccountV2Pda(umi, {
21 baseMint: baseMint.publicKey,
22 genesisIndex: 0,
23})
24
25await initializeV2(umi, {
26 baseMint,
27 fundingMode: 0,
28 totalSupplyBaseToken: TOTAL_SUPPLY,
29 name: 'My Token',
30 symbol: 'MTK',
31 uri: 'https://example.com/metadata.json',
32}).sendAndConfirm(umi)
totalSupplyBaseToken은 모든 bucket 할당량의 합과 같아야 합니다.
2. Launch Pool Bucket 추가
Launch Pool bucket은 예치금을 수집하고 토큰을 비례적으로 배분합니다. 여기서 타이밍을 구성합니다.
1import {
2 genesis,
3 addLaunchPoolBucketV2,
4 findLaunchPoolBucketV2Pda,
5 findUnlockedBucketV2Pda,
6} from '@metaplex-foundation/genesis'
7import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
8import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
9import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
10
11const umi = createUmi('https://api.mainnet-beta.solana.com')
12 .use(mplToolbox())
13 .use(genesis())
14
15// umi.use(keypairIdentity(yourKeypair));
16
17// Assumes genesisAccount, baseMint, and TOTAL_SUPPLY from the Initialize step.
18
19const [launchPoolBucket] = findLaunchPoolBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 })
20const [unlockedBucket] = findUnlockedBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 })
21
22const now = BigInt(Math.floor(Date.now() / 1000))
23const depositStart = now
24const depositEnd = now + 86400n // 24 hours
25const claimStart = depositEnd + 1n
26const claimEnd = claimStart + 604800n // 1 week
27
28await addLaunchPoolBucketV2(umi, {
29 genesisAccount,
30 baseMint: baseMint.publicKey,
31 baseTokenAllocation: TOTAL_SUPPLY,
32
33 // Timing
34 depositStartCondition: {
35 __kind: 'TimeAbsolute',
36 padding: Array(47).fill(0),
37 time: depositStart,
38 triggeredTimestamp: null,
39 },
40 depositEndCondition: {
41 __kind: 'TimeAbsolute',
42 padding: Array(47).fill(0),
43 time: depositEnd,
44 triggeredTimestamp: null,
45 },
46 claimStartCondition: {
47 __kind: 'TimeAbsolute',
48 padding: Array(47).fill(0),
49 time: claimStart,
50 triggeredTimestamp: null,
51 },
52 claimEndCondition: {
53 __kind: 'TimeAbsolute',
54 padding: Array(47).fill(0),
55 time: claimEnd,
56 triggeredTimestamp: null,
57 },
58
59 // Optional: Minimum deposit
60 minimumDepositAmount: null, // or { amount: sol(0.1).basisPoints }
61
62 // Where collected SOL goes after transition
63 endBehaviors: [
64 {
65 __kind: 'SendQuoteTokenPercentage',
66 padding: Array(4).fill(0),
67 destinationBucket: publicKey(unlockedBucket),
68 percentageBps: 10000, // 100%
69 processed: false,
70 },
71 ],
72}).sendAndConfirm(umi)
3. Unlocked Bucket 추가
Unlocked bucket은 Transition 후 Launch Pool에서 SOL을 받습니다.
1import {
2 addUnlockedBucketV2,
3 genesis,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { generateSigner, keypairIdentity } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com')
10 .use(mplToolbox())
11 .use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair));
14
15// Assumes genesisAccount, baseMint, claimStart, and claimEnd from previous steps.
16
17await addUnlockedBucketV2(umi, {
18 genesisAccount,
19 baseMint: baseMint.publicKey,
20 baseTokenAllocation: 0n,
21 recipient: umi.identity.publicKey,
22 claimStartCondition: {
23 __kind: 'TimeAbsolute',
24 padding: Array(47).fill(0),
25 time: claimStart,
26 triggeredTimestamp: null,
27 },
28 claimEndCondition: {
29 __kind: 'TimeAbsolute',
30 padding: Array(47).fill(0),
31 time: claimEnd,
32 triggeredTimestamp: null,
33 },
34 backendSigner: null,
35}).sendAndConfirm(umi)
4. 최종화
모든 bucket이 구성되면 최종화하여 출시를 활성화합니다. 이 작업은 되돌릴 수 없습니다.
1import {
2 genesis,
3 finalizeV2,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { keypairIdentity } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com')
10 .use(mplToolbox())
11 .use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair));
14
15// Assumes genesisAccount and baseMint from the Initialize step.
16
17await finalizeV2(umi, {
18 baseMint: baseMint.publicKey,
19 genesisAccount,
20}).sendAndConfirm(umi)
사용자 작업
SOL 래핑
사용자는 예치하기 전에 SOL을 wSOL로 래핑해야 합니다.
1import {
2 findAssociatedTokenPda,
3 createTokenIfMissing,
4 transferSol,
5 syncNative,
6 mplToolbox,
7} from '@metaplex-foundation/mpl-toolbox'
8import { WRAPPED_SOL_MINT, genesis } from '@metaplex-foundation/genesis'
9import { keypairIdentity, publicKey, sol } from '@metaplex-foundation/umi'
10import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
11
12const umi = createUmi('https://api.mainnet-beta.solana.com')
13 .use(mplToolbox())
14 .use(genesis())
15
16// umi.use(keypairIdentity(yourKeypair));
17
18const userWsolAccount = findAssociatedTokenPda(umi, {
19 owner: umi.identity.publicKey,
20 mint: WRAPPED_SOL_MINT,
21})
22
23await createTokenIfMissing(umi, {
24 mint: WRAPPED_SOL_MINT,
25 owner: umi.identity.publicKey,
26 token: userWsolAccount,
27})
28 .add(
29 transferSol(umi, {
30 destination: publicKey(userWsolAccount),
31 amount: sol(10),
32 })
33 )
34 .add(syncNative(umi, { account: userWsolAccount }))
35 .sendAndConfirm(umi)
예치
1import {
2 genesis,
3 depositLaunchPoolV2,
4 findLaunchPoolDepositV2Pda,
5 fetchLaunchPoolDepositV2,
6} from '@metaplex-foundation/genesis'
7import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
8import { keypairIdentity, sol } from '@metaplex-foundation/umi'
9import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
10
11const umi = createUmi('https://api.mainnet-beta.solana.com')
12 .use(mplToolbox())
13 .use(genesis())
14
15// umi.use(keypairIdentity(yourKeypair));
16
17// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
18
19await depositLaunchPoolV2(umi, {
20 genesisAccount,
21 bucket: launchPoolBucket,
22 baseMint: baseMint.publicKey,
23 amountQuoteToken: sol(10).basisPoints,
24}).sendAndConfirm(umi)
25
26// Verify
27const [depositPda] = findLaunchPoolDepositV2Pda(umi, {
28 bucket: launchPoolBucket,
29 recipient: umi.identity.publicKey,
30})
31const deposit = await fetchLaunchPoolDepositV2(umi, depositPda)
32
33console.log('Deposited (after fee):', deposit.amountQuoteToken)
같은 사용자의 여러 예치금은 단일 예치 계정에 누적됩니다.
출금
사용자는 예치 기간 동안 출금할 수 있습니다. 2% 수수료가 적용됩니다.
1import {
2 genesis,
3 withdrawLaunchPoolV2,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { keypairIdentity, sol } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com')
10 .use(mplToolbox())
11 .use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair));
14
15// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
16
17await withdrawLaunchPoolV2(umi, {
18 genesisAccount,
19 bucket: launchPoolBucket,
20 baseMint: baseMint.publicKey,
21 amountQuoteToken: sol(3).basisPoints,
22}).sendAndConfirm(umi)
사용자가 전체 잔액을 출금하면 예치 PDA가 닫힙니다.
토큰 청구
예치 기간이 종료되고 청구가 열린 후:
1import {
2 genesis,
3 claimLaunchPoolV2,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { keypairIdentity } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com')
10 .use(mplToolbox())
11 .use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair));
14
15// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
16
17await claimLaunchPoolV2(umi, {
18 genesisAccount,
19 bucket: launchPoolBucket,
20 baseMint: baseMint.publicKey,
21 recipient: umi.identity.publicKey,
22}).sendAndConfirm(umi)
토큰 할당: userTokens = (userDeposit / totalDeposits) * bucketTokenAllocation
관리자 작업
Transition 실행
예치가 종료된 후 Transition을 실행하여 수집된 SOL을 Unlocked bucket으로 이동합니다.
1import {
2 genesis,
3 transitionV2,
4 WRAPPED_SOL_MINT,
5} from '@metaplex-foundation/genesis'
6import {
7 findAssociatedTokenPda,
8 mplToolbox,
9} from '@metaplex-foundation/mpl-toolbox'
10import { keypairIdentity } from '@metaplex-foundation/umi'
11import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
12
13const umi = createUmi('https://api.mainnet-beta.solana.com')
14 .use(mplToolbox())
15 .use(genesis())
16
17// umi.use(keypairIdentity(yourKeypair));
18
19// Assumes genesisAccount, launchPoolBucket, unlockedBucket, and baseMint from previous steps.
20
21const [unlockedBucketQuoteTokenAccount] = findAssociatedTokenPda(umi, {
22 owner: unlockedBucket,
23 mint: WRAPPED_SOL_MINT,
24})
25
26await transitionV2(umi, {
27 genesisAccount,
28 primaryBucket: launchPoolBucket,
29 baseMint: baseMint.publicKey,
30})
31 .addRemainingAccounts([
32 { pubkey: unlockedBucket, isSigner: false, isWritable: true },
33 { pubkey: unlockedBucketQuoteTokenAccount, isSigner: false, isWritable: true },
34 ])
35 .sendAndConfirm(umi)
이것이 중요한 이유: Transition 없이는 수집된 SOL이 Launch Pool bucket에 잠겨 있습니다. 사용자는 여전히 토큰을 청구할 수 있지만, 팀은 모금된 자금에 접근할 수 없습니다.
레퍼런스
시간 조건
네 가지 조건이 Launch Pool 타이밍을 제어합니다:
| 조건 | 목적 |
|---|---|
depositStartCondition | 예치 시작 시점 |
depositEndCondition | 예치 종료 시점 |
claimStartCondition | 청구 시작 시점 |
claimEndCondition | 청구 종료 시점 |
Unix 타임스탬프와 함께 TimeAbsolute을 사용합니다:
const condition = {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
triggeredTimestamp: null,
};
End Behavior
예치 기간 후 수집된 SOL에 어떤 일이 발생하는지 정의합니다:
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(unlockedBucket),
percentageBps: 10000, // 100% = 10000 basis points
processed: false,
},
]
여러 bucket에 자금을 분할할 수 있습니다:
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(treasuryBucket),
percentageBps: 2000, // 20%
processed: false,
},
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(liquidityBucket),
percentageBps: 8000, // 80%
processed: false,
},
]
상태 조회
Bucket 상태:
import { fetchLaunchPoolBucketV2 } from '@metaplex-foundation/genesis';
const bucket = await fetchLaunchPoolBucketV2(umi, launchPoolBucket);
console.log('Total deposits:', bucket.quoteTokenDepositTotal);
console.log('Deposit count:', bucket.depositCount);
console.log('Claim count:', bucket.claimCount);
console.log('Token allocation:', bucket.bucket.baseTokenAllocation);
예치 상태:
import { fetchLaunchPoolDepositV2, safeFetchLaunchPoolDepositV2 } from '@metaplex-foundation/genesis';
const deposit = await fetchLaunchPoolDepositV2(umi, depositPda); // throws if not found
const maybeDeposit = await safeFetchLaunchPoolDepositV2(umi, depositPda); // returns null
if (deposit) {
console.log('Amount deposited:', deposit.amountQuoteToken);
console.log('Claimed:', deposit.claimed);
}
참고 사항
- 2% 프로토콜 수수료가 예치와 출금 모두에 적용됩니다
- 같은 사용자의 여러 예치금은 하나의 예치 계정에 누적됩니다
- 사용자가 전체 잔액을 출금하면 예치 PDA가 닫힙니다
- End behavior를 처리하려면 예치 종료 후 Transition이 실행되어야 합니다
- 사용자는 예치하려면 wSOL (래핑된 SOL)이 있어야 합니다
FAQ
Launch Pool에서 토큰 가격은 어떻게 결정되나요?
가격은 총 예치금을 기반으로 유기적으로 발견됩니다. 최종 가격은 총 예치된 SOL을 할당된 토큰으로 나눈 값입니다. 예치금이 많을수록 토큰당 암묵적 가격이 높아집니다.
사용자가 예치금을 출금할 수 있나요?
네, 사용자는 예치 기간 동안 출금할 수 있습니다. 시스템 악용을 방지하기 위해 2% 출금 수수료가 적용됩니다.
여러 번 예치하면 어떻게 되나요?
같은 지갑에서의 여러 예치금은 단일 예치 계정에 누적됩니다. 총 지분은 합산된 예치금을 기준으로 합니다.
사용자는 언제 토큰을 청구할 수 있나요?
예치 기간이 끝나고 청구 기간이 열린 후(claimStartCondition으로 정의됨)에 가능합니다. End behavior를 처리하기 위해 먼저 Transition이 실행되어야 합니다.
Launch Pool과 Presale의 차이점은 무엇인가요?
Launch Pool은 비례 배분과 함께 예치금을 기반으로 유기적으로 가격을 발견합니다. Presale은 미리 정해진 고정 가격으로 상한까지 선착순 할당합니다.
용어집
| 용어 | 정의 |
|---|---|
| Launch Pool | 종료 시 가격이 발견되는 예치 기반 배포 |
| Deposit Window | 사용자가 SOL을 예치하고 출금할 수 있는 기간 |
| Claim Window | 사용자가 비례 토큰을 청구할 수 있는 기간 |
| End Behavior | 예치 기간 종료 후 실행되는 자동 작업 |
| Transition | End behavior를 처리하고 자금을 라우팅하는 명령어 |
| Proportional Distribution | 총 예치금에서의 사용자 지분에 따른 토큰 할당 |
| Quote Token | 사용자가 예치하는 토큰 (일반적으로 wSOL) |
| Base Token | 배포되는 토큰 |
다음 단계
- Presale - 고정 가격 토큰 판매
- Uniform Price Auction - 입찰 기반 할당
- Aggregation API - API를 통한 출시 데이터 조회
