런치 유형

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를 참조하세요.

빠른 시작

작동 방식

  1. 특정 수량의 토큰이 Launch Pool bucket에 할당됩니다
  2. 사용자는 예치 기간 동안 SOL을 예치합니다 (수수료와 함께 출금 가능)
  3. 기간이 종료되면 예치 지분에 따라 토큰이 비례적으로 배분됩니다

가격 발견

토큰 가격은 총 예치금에서 도출됩니다:

tokenPrice = totalDeposits / tokenAllocation
userTokens = (userDeposit / totalDeposits) * tokenAllocation

예시: 1,000,000 토큰 할당, 총 100 SOL 예치 = 토큰당 0.0001 SOL

라이프사이클

  1. 예치 기간 - 사용자가 정해진 기간 동안 SOL을 예치
  2. Transition - End behavior 실행 (예: 수집된 SOL을 다른 bucket으로 전송)
  3. 청구 기간 - 사용자가 예치 비중에 비례한 토큰을 청구

수수료

인스트럭션Solana
Deposit2%
Withdraw2%
Graduation5%

예치 수수료 예시: 사용자가 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을 조정합니다.

initializeV2.ts
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은 예치금을 수집하고 토큰을 비례적으로 배분합니다. 여기서 타이밍을 구성합니다.

addLaunchPoolBucket.ts
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을 받습니다.

addUnlockedBucket.ts
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이 구성되면 최종화하여 출시를 활성화합니다. 이 작업은 되돌릴 수 없습니다.

finalize.ts
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로 래핑해야 합니다.

wrapSol.ts
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)

예치

depositLaunchPool.ts
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% 수수료가 적용됩니다.

withdrawLaunchPool.ts
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가 닫힙니다.

토큰 청구

예치 기간이 종료되고 청구가 열린 후:

claimLaunchPool.ts
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으로 이동합니다.

transitionLaunchPool.ts
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예치 기간 종료 후 실행되는 자동 작업
TransitionEnd behavior를 처리하고 자금을 라우팅하는 명령어
Proportional Distribution총 예치금에서의 사용자 지분에 따른 토큰 할당
Quote Token사용자가 예치하는 토큰 (일반적으로 wSOL)
Base Token배포되는 토큰

다음 단계