런치 유형
Launch Pool
Last updated October 12, 2026
Launch Pool은 솔라나에서 공정한 토큰 출시를 위한 유기적인 가격 발견을 제공합니다. 기존 중앙화 토큰 판매와는 다른 온체인 토큰 배포 메커니즘으로 — 사용자는 일정 기간 동안 SOL을 예치하고 총 예치금에서의 지분에 비례하여 SPL 토큰을 받습니다. 스나이핑 없음, 프론트러닝 없음, 모두에게 공정한 배분.
학습 내용
이 가이드에서 다루는 내용:
- Launch Pool 가격 책정 및 배분 작동 방식
- 예치 및 청구 기간 설정
- 자금 수집을 위한 End behavior 구성
- 사용자 작업: 예치, 출금, 청구
요약
Launch Pool은 정해진 기간 동안 예치금을 받은 후 토큰을 비례적으로 배분하는 크라우드세일 스타일의 토큰 출시 메커니즘입니다. 최종 토큰 가격은 총 예치금을 토큰 할당량으로 나누어 결정되며, 토큰 생성 이벤트(TGE)를 위한 투명한 온체인 가격 발견을 가능하게 합니다.
- 사용자는 예치 기간 동안 SOL을 예치합니다 (0% 수수료 적용)
- 예치 기간 동안 출금 가능 (0% 수수료)
- 토큰 배분은 예치 지분에 비례
- 선택적 소프트 캡이 런칭이 보유하는 금액을 제한하고 초과분을 비례적으로 환불
- End behavior가 수집된 SOL을 Treasury bucket으로 라우팅
Launch Pool은 예치금으로부터 가격을 발견합니다. 미리 정해진 고정 가격이 필요하면 Presale을, 입찰 기반 청산이 필요하면 Uniform Price Auction을 사용하세요. 유동성 풀 생성은 Launch Pool bucket이 아니라 Raydium 졸업 bucket이 처리합니다.
빠른 시작
Launch Pool을 예치 및 청구 기간과 함께 설정하는 방법을 보여줍니다. 사용자용 앱을 구축하려면 사용자 작업을 참조하세요.
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import { mplToolbox } from '@metaplex-foundation/mpl-toolbox';
import {
genesis,
initializeV2,
findGenesisAccountV2Pda,
addLaunchPoolBucketV2,
findLaunchPoolBucketV2Pda,
addUnlockedBucketV2,
findUnlockedBucketV2Pda,
finalizeV2,
} from '@metaplex-foundation/genesis';
import { generateSigner, publicKey } from '@metaplex-foundation/umi';
async function setupLaunchPool() {
const umi = createUmi('https://api.mainnet-beta.solana.com')
.use(mplToolbox())
.use(genesis());
// umi.use(keypairIdentity(yourKeypair));
const baseMint = generateSigner(umi);
const TOTAL_SUPPLY = 1_000_000_000_000_000n; // 1 million tokens (9 decimals)
// 1. Initialize
const [genesisAccount] = findGenesisAccountV2Pda(umi, {
baseMint: baseMint.publicKey,
genesisIndex: 0,
});
await initializeV2(umi, {
baseMint,
fundingMode: 0,
totalSupplyBaseToken: TOTAL_SUPPLY,
name: 'My Token',
symbol: 'MTK',
uri: 'https://example.com/metadata.json',
}).sendAndConfirm(umi);
// 2. Define timing
const now = BigInt(Math.floor(Date.now() / 1000));
const depositStart = now + 60n;
const depositEnd = now + 86400n; // 24 hours
const claimStart = depositEnd + 1n;
const claimEnd = claimStart + 604800n; // 1 week
// 3. Derive bucket PDAs
const [launchPoolBucket] = findLaunchPoolBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 });
const [unlockedBucket] = findUnlockedBucketV2Pda(umi, { genesisAccount, bucketIndex: 0 });
// 4. Add Launch Pool bucket
await addLaunchPoolBucketV2(umi, {
genesisAccount,
baseMint: baseMint.publicKey,
baseTokenAllocation: TOTAL_SUPPLY,
depositStartCondition: {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: depositStart,
triggeredTimestamp: null,
},
depositEndCondition: {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: depositEnd,
triggeredTimestamp: null,
},
claimStartCondition: {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: claimStart,
triggeredTimestamp: null,
},
claimEndCondition: {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: claimEnd,
triggeredTimestamp: null,
},
minimumDepositAmount: null,
softCap: null, // required since genesis 0.42.0; pass null for no soft cap
endBehaviors: [
{
__kind: 'SendQuoteTokenPercentage',
padding: Array(4).fill(0),
destinationBucket: publicKey(unlockedBucket),
percentageBps: 10000, // 100%
processed: false,
},
],
}).sendAndConfirm(umi);
// 5. Add Unlocked bucket (receives SOL after transition)
await addUnlockedBucketV2(umi, {
genesisAccount,
baseMint: baseMint.publicKey,
baseTokenAllocation: 0n,
recipient: umi.identity.publicKey,
claimStartCondition: {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: claimStart,
triggeredTimestamp: null,
},
claimEndCondition: {
__kind: 'TimeAbsolute',
padding: Array(47).fill(0),
time: claimEnd,
triggeredTimestamp: null,
},
}).sendAndConfirm(umi);
// 6. Finalize
await finalizeV2(umi, {
baseMint: baseMint.publicKey,
genesisAccount,
}).sendAndConfirm(umi);
console.log('Launch Pool active!');
console.log('Token:', baseMint.publicKey);
console.log('Genesis:', genesisAccount);
}
setupLaunchPool().catch(console.error);
작동 방식
- 특정 수량의 토큰이 Launch Pool bucket에 할당됩니다
- 사용자는 예치 기간 동안 SOL을 예치합니다 (수수료와 함께 출금 가능)
- 기간이 종료되면 예치 지분에 따라 토큰이 비례적으로 배분됩니다
가격 발견
토큰 가격은 총 예치금에서 도출됩니다:
tokenPrice = totalDeposits / tokenAllocation
userTokens = (userDeposit / totalDeposits) * tokenAllocation
예시: 1,000,000 토큰 할당, 총 100 SOL 예치 = 토큰당 0.0001 SOL
가격 발견은 기본적으로 상한이 없습니다 — 풀이 많이 청약될수록 암묵적 가격이 높아집니다. 소프트 캡을 설정하면 가격에 상한을 둘 수 있습니다.
라이프사이클
- 예치 기간 - 사용자가 정해진 기간 동안 SOL을 예치
triggerBehaviorsV2- End behavior 실행 (예: 수집된 SOL을 다른 bucket으로 전송)- 청구 기간 - 사용자가 예치 비중에 비례한 토큰을 청구
- 환불 기간 (조건부) - 소프트 캡을 초과했거나 최소 quote 토큰 임계값에 미달한 경우, 예치자가
refundLaunchPoolV2를 호출
Launch Pool 소프트 캡
소프트 캡은 Launch Pool이 보유하는 quote 토큰의 상한이며, addLaunchPoolBucketV2의 softCap 확장으로 설정합니다. 상한을 초과하는 예치금도 예치 기간 동안 계속 받아들여지고 런칭은 성공하며, 초과분 quote 토큰은 예치 기간이 끝나면 비례적으로 환불됩니다.
소프트 캡이 없으면 가격 발견에 상한이 없습니다. 모든 예치금이 보유되고 청약이 늘어날수록 암묵적 토큰 가격이 상승합니다. 소프트 캡은 런칭이 모금하는 최대 금액을 고정하므로, 예치금이 얼마나 초과하든 실효 가격은 softCap / baseTokenAllocation입니다.
| 속성 | 소프트 캡이 설정된 경우의 동작 |
|---|---|
| 상한을 초과하는 예치금 | 예치 기간 동안 받아들여짐 — 상한에서 예치가 거부되지 않음 |
| 런칭 결과 | 성공 — 소프트 캡은 상한일 뿐 실패 조건이 아님 |
| Base 토큰 배분 | 전체 baseTokenAllocation이 모든 예치금에 비례하여 배분됨 |
| 초과 quote 토큰 | 예치 기간 종료 후 refundLaunchPoolV2를 통해 비례적으로 환불됨 |
| End behavior가 보는 수익금 | 소프트 캡으로 제한되어 SendQuoteTokenPercentage가 전달하는 금액은 최대 softCap |
| Raydium 졸업 시작 가격 | 원시 예치 총액이 아닌 상한이 적용된 수익금에서 산출 |
소프트 캡은 모금액의 상한이지 하한이 아닙니다. 하한은 별개의 확장인 minimumQuoteTokenThreshold입니다 — 소프트 캡과 최소 quote 토큰 임계값 함께 사용하기를 참조하세요.
Launch Pool Bucket에 소프트 캡 설정하기
bucket을 추가할 때 addLaunchPoolBucketV2에 softCap 값을 전달합니다. 금액은 quote 토큰의 최소 단위(wSOL의 경우 lamports)로 표시합니다.
import { sol } from '@metaplex-foundation/umi';
await addLaunchPoolBucketV2(umi, {
genesisAccount,
baseMint: baseMint.publicKey,
baseTokenAllocation: TOTAL_SUPPLY,
// ...time conditions and end behaviors...
// Floor: the launch fails below this and everyone can take a full refund.
minimumQuoteTokenThreshold: { amount: sol(10).basisPoints },
// Ceiling: the launch keeps at most this much; the rest is refunded pro-rata.
softCap: { amount: sol(100).basisPoints },
}).sendAndConfirm(umi);
softCap은 @metaplex-foundation/genesis 0.42.0의 addLaunchPoolBucketV2에서 필수 인수입니다. 다른 Launch Pool 확장과 달리 기본값이 없으므로, 상한을 원하지 않을 때는 softCap: null을 명시적으로 전달하세요.
소프트 캡은 확장을 설정할 때와 finalizeV2 시점에 다시 검증됩니다:
| 규칙 | 위반 시 오류 |
|---|---|
softCap.amount는 0보다 커야 함 | InvalidSoftCap (221) |
softCap.amount는 minimumQuoteTokenThreshold.amount 이상이어야 함 | SoftCapBelowThreshold (222) |
확장은 finalizeV2 전에만 추가하거나 제거할 수 있음 | 계정이 이미 최종화되었다는 이유로 거부됨 |
소프트 캡은 LaunchPoolV2ExtensionType의 SoftCap 멤버와 함께 addLaunchPoolBucketV2Extensions 및 removeLaunchPoolBucketV2Extensions를 사용하여 기존 bucket에도 설정하거나 해제할 수 있지만, Genesis Account가 최종화되기 전에만 가능합니다.
초과 청약과 비례 환불 계산
quoteTokenDepositTotal이 softCap.amount보다 엄격히 클 때 Launch Pool은 초과 청약된 상태입니다. 이때 각 예치금은 상한에 포함되는 충당(filled) 부분과 환불 가능한 초과(excess) 부분으로 나뉩니다:
filled_i = ceil(deposit_i * softCap / totalDeposits)
excess_i = deposit_i - filled_i
tokens_i = (weighted_i / weightedQuoteTokenTotal) * baseTokenAllocation
filled는 올림 처리되어 모든 충당 부분의 합이 항상 소프트 캡 이상이 됩니다. 이렇게 하면 환불과 졸업 중 어느 쪽을 먼저 크랭크하더라도 상한이 적용된 졸업 전송에 대해 bucket의 지급 능력이 유지됩니다. 각 예치자의 반올림은 1 최소 단위 미만이므로 bucket에 남는 먼지(dust)는 총 최대 depositCount - 1 최소 단위입니다.
토큰 배분은 상한의 영향을 받지 않습니다. 초과분을 환불해도 예치자의 가중 기여도는 제거되지 않으므로, 모든 예치자는 여전히 전체 예치금에 비례하여 토큰을 받습니다.
계산 예시 — 1,000,000 토큰 할당, 100 SOL 소프트 캡, 150 SOL 예치:
| 예치자 | 예치금 | 충당 (보유) | 환불 | 받는 토큰 |
|---|---|---|---|---|
| Alice | 50 SOL | ~33.33 SOL | ~16.67 SOL | 333,333 (1/3) |
| Bob | 100 SOL | ~66.67 SOL | ~33.33 SOL | 666,667 (2/3) |
| 합계 | 150 SOL | 100 SOL | 50 SOL | 1,000,000 |
실효 가격은 상한이 없는 예치 총액이 의미했을 토큰당 0.00015 SOL이 아니라 토큰당 0.0001 SOL(100 SOL / 1,000,000)입니다. 온체인 값은 filled를 올림한 lamports 단위로 계산되므로 실제 수치는 위의 반올림된 SOL 금액과 몇 lamports 차이가 납니다.
refundLaunchPoolV2로 초과 예치금 환불하기
refundLaunchPoolV2는 초과 청약된 예치 기간이 끝난 후 예치자의 초과 quote 토큰을 반환합니다. amount 인수는 없으며, 프로그램이 예치금, 소프트 캡, bucket의 예치 총액으로부터 환불 가능 금액을 계산합니다.
1import {
2 genesis,
3 refundLaunchPoolV2,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi('https://api.mainnet-beta.solana.com')
9 .use(mplToolbox())
10 .use(genesis())
11
12// umi.use(keypairIdentity(yourKeypair));
13
14// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
15// Only valid after the deposit window closed AND either the
16// minimumQuoteTokenThreshold was missed or the softCap was exceeded.
17
18// The program computes the refundable amount, so there is no amount argument.
19// A missed threshold refunds the full deposit; an exceeded soft cap refunds
20// only the excess, leaving the depositor's token allocation intact.
21await refundLaunchPoolV2(umi, {
22 genesisAccount,
23 bucket: launchPoolBucket,
24 baseMint: baseMint.publicKey,
25 recipient: umi.identity.publicKey,
26}).sendAndConfirm(umi)
환불 경로의 주요 특성:
- 크랭크는 누구나 실행할 수 있습니다.
payer만 서명하면 됩니다. 예치자도 서명하면 비어 있는 base 토큰 계정이 닫힙니다. - 환불에는 수수료와 페널티가 없습니다. 예치 및 출금 페널티 스케줄은 환불에 영향을 주지 않습니다.
- 청구 순서는 상관없습니다. 초과분 환불은
claimLaunchPoolV2전후 어느 쪽에서도 가능하며, 두 순서 모두 동일한 최종 상태로 수렴합니다. - 예치금당 환불은 한 번입니다. 두 번째 호출은
DepositAlreadyRefunded를 반환합니다. - 환불은 예치 기간이 끝난 후에만 가능합니다. 더 일찍 호출하면
LaunchPoolNotEnded를 반환합니다. - 환불은 하한 미달 또는 상한 초과 시에만 가능합니다. 둘 다 해당하지 않으면
LaunchPoolThresholdMet를 반환합니다.
소프트 캡과 최소 quote 토큰 임계값 함께 사용하기
softCap과 minimumQuoteTokenThreshold는 Launch Pool을 서로 반대 방향에서 제한하는 독립적인 확장이며, refundLaunchPoolV2가 둘 다 처리합니다. 하한에 미달하면 그것이 우선하며 환불은 전액 환불입니다.
| 구성 | 하한 미만 예치 | 하한과 상한 사이 예치 | 상한 초과 예치 |
|---|---|---|---|
| 둘 다 미설정 | 런칭 성공, 환불 없음 | 런칭 성공, 환불 없음 | 런칭 성공, 환불 없음 |
| 하한만 설정 | 런칭 실패, 전액 환불 | 런칭 성공, 환불 없음 | 런칭 성공, 환불 없음 |
| 상한만 설정 | 런칭 성공, 환불 없음 | 런칭 성공, 환불 없음 | 런칭 성공, 초과분 비례 환불 |
| 하한과 상한 모두 설정 | 런칭 실패, 전액 환불 | 런칭 성공, 환불 없음 | 런칭 성공, 초과분 비례 환불 |
전액 환불은 예치자의 가중 기여도를 bucket에서 제거하며 청구 이후에는 할 수 없습니다 — 하한 미달이라면 애초에 청구가 불가능했기 때문입니다. 초과분 환불은 가중 기여도를 그대로 두므로 비례 청구 공식이 올바르게 유지됩니다.
수수료
| 인스트럭션 | Solana |
|---|---|
| User deposit fee | 0% |
| User withdraw fee | 0% |
| Creator withdraw fee | 5%* |
* This fee only applies when creators withdraw liquidity
예치 시 0% 사용자 예치 수수료를 제외한 금액만 예치 계정 잔액에 반영됩니다.
설정 가이드
사전 요구 사항
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 addLaunchPoolBucketV2,
3 findLaunchPoolBucketV2Pda,
4 findUnlockedBucketV2Pda,
5 genesis,
6} from '@metaplex-foundation/genesis'
7import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
8import { 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 // Required since @metaplex-foundation/genesis 0.42.0: pass null for no soft cap,
63 // or { amount: sol(100).basisPoints } to cap the quote tokens kept
64 softCap: null,
65
66 // Where collected SOL goes after transition
67 endBehaviors: [
68 {
69 __kind: 'SendQuoteTokenPercentage',
70 padding: Array(4).fill(0),
71 destinationBucket: publicKey(unlockedBucket),
72 percentageBps: 10000, // 100%
73 processed: false,
74 },
75 ],
76}).sendAndConfirm(umi)
3. Unlocked Bucket 추가
Unlocked bucket은 triggerBehaviorsV2 실행 후 Launch Pool에서 SOL을 받습니다.
1import {
2 addUnlockedBucketV2,
3 genesis,
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, 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)
같은 사용자의 여러 예치금은 단일 예치 계정에 누적됩니다.
출금
사용자는 예치 기간 동안 출금할 수 있습니다. 0% 수수료가 적용됩니다.
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 claimLaunchPoolV2,
3 genesis,
4} from '@metaplex-foundation/genesis'
5import { mplToolbox } from '@metaplex-foundation/mpl-toolbox'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi('https://api.mainnet-beta.solana.com')
9 .use(mplToolbox())
10 .use(genesis())
11
12// umi.use(keypairIdentity(yourKeypair));
13
14// Assumes genesisAccount, launchPoolBucket, and baseMint from previous steps.
15
16await claimLaunchPoolV2(umi, {
17 genesisAccount,
18 bucket: launchPoolBucket,
19 baseMint: baseMint.publicKey,
20 recipient: umi.identity.publicKey,
21}).sendAndConfirm(umi)
토큰 할당: userTokens = (userDeposit / totalDeposits) * bucketTokenAllocation
예치금 환불
환불은 두 가지 경우에 가능합니다. 런칭이 minimumQuoteTokenThreshold에 미달한 경우(전액 환불) 또는 softCap을 초과한 경우(초과분만 환불)입니다. 두 경우 모두 같은 명령어를 사용합니다 — refundLaunchPoolV2로 초과 예치금 환불하기를 참조하세요.
관리자 작업
triggerBehaviorsV2 실행
예치가 종료된 후 triggerBehaviorsV2를 실행하여 수집된 SOL을 Unlocked bucket으로 이동합니다.
1import {
2 genesis,
3 triggerBehaviorsV2,
4 WRAPPED_SOL_MINT,
5} from '@metaplex-foundation/genesis'
6import {
7 findAssociatedTokenPda,
8 mplToolbox,
9} from '@metaplex-foundation/mpl-toolbox'
10import { publicKey } 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 triggerBehaviorsV2(umi, {
27 genesisAccount,
28 primaryBucket: launchPoolBucket,
29 baseMint,
30})
31 .addRemainingAccounts([
32 { pubkey: publicKey(unlockedBucket), isSigner: false, isWritable: true },
33 { pubkey: publicKey(unlockedBucketQuoteTokenAccount), isSigner: false, isWritable: true },
34 ])
35 .sendAndConfirm(umi)
이것이 중요한 이유: triggerBehaviorsV2를 실행하지 않으면 수집된 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,
},
]
Launch Pool 확장
확장은 Launch Pool bucket에 구성하는 선택적 가드입니다. 모두 addLaunchPoolBucketV2로 설정하거나, finalizeV2 전에 addLaunchPoolBucketV2Extensions 및 removeLaunchPoolBucketV2Extensions로 개별적으로 추가하고 제거할 수 있습니다.
| 확장 | 타입 | 목적 |
|---|---|---|
softCap | { amount: bigint } | 보유하는 quote 토큰의 상한; 초과분은 비례 환불 |
minimumQuoteTokenThreshold | { amount: bigint } | 이 값 미만이면 런칭이 실패하고 전액 환불이 열리는 하한 |
minimumDepositAmount | { amount: bigint } | 예치당 최소 quote 토큰 |
depositLimit | { limit: bigint } | 계정당 최대 quote 토큰 |
allowlist | Allowlist | 허용 목록에 있는 지갑으로 예치를 제한 |
claimSchedule | ClaimSchedule | 청구된 base 토큰을 시간에 따라 베스팅 |
bonusSchedule | LinearBpsScheduleV2 | 시간 가중 예치 보너스 |
depositPenalty | LinearBpsScheduleV2 | 시간 가중 예치 페널티 |
withdrawPenalty | LinearBpsScheduleV2 | 시간 가중 출금 페널티 |
backendSigner | BackendSigner | 사용자 작업에 백엔드 공동 서명자를 요구 |
일반 오류
아래 오류는 잘못된 소프트 캡 구성과, refundLaunchPoolV2가 환불 요청을 거부하는 Launch Pool 상태를 다룹니다.
| 오류 | 코드 | 원인 |
|---|---|---|
InvalidSoftCap | 221 | softCap.amount가 0입니다 — { amount: 0n } 대신 softCap: null을 전달하세요 |
SoftCapBelowThreshold | 222 | softCap.amount가 minimumQuoteTokenThreshold.amount보다 작습니다 |
LaunchPoolNotEnded | — | 예치 기간이 닫히기 전에 refundLaunchPoolV2를 호출했습니다 |
LaunchPoolThresholdMet | 173 | 하한을 충족했고 상한을 초과하지 않았는데 환불을 요청했습니다 |
DepositAlreadyRefunded | — | 같은 예치금에 대해 refundLaunchPoolV2를 두 번 호출했습니다 |
DepositAlreadyClaimed | — | 예치자가 이미 토큰을 청구한 후 전액 환불을 요청했습니다 |
상태 조회
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);
// Soft cap state (Option<SoftCap>)
console.log('Soft cap:', bucket.extensions.softCap);
console.log('Floor:', bucket.extensions.minimumQuoteTokenThreshold);
예치 상태:
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);
console.log('Refunded:', deposit.refunded);
}
참고 사항
- Launch Pool 사용자 예치·출금 수수료는 위 수수료를 참고하세요.
- 같은 사용자의 여러 예치금은 하나의 예치 계정에 누적됩니다
- 사용자가 전체 잔액을 출금하면 예치 PDA가 닫힙니다
- End behavior를 처리하려면 예치 종료 후
triggerBehaviorsV2가 실행되어야 합니다 - 사용자는 예치하려면 wSOL (래핑된 SOL)이 있어야 합니다
softCap은@metaplex-foundation/genesis0.42.0의addLaunchPoolBucketV2에서 필수 인수입니다 — 상한을 원하지 않으면softCap: null을 전달하세요softCap을 포함한 Launch Pool 확장은finalizeV2전에만 추가하거나 제거할 수 있습니다- 소프트 캡은 Genesis 프로그램과 JavaScript SDK에서 지원됩니다.
mplxCLI는 아직 소프트 캡 플래그를 제공하지 않습니다 - 초과 청약된 Launch Pool은 각 예치자의 충당 부분이 올림되기 때문에 bucket에 최대
depositCount - 1최소 단위의 반올림 먼지가 남습니다 quoteTokenDepositTotal과depositCount는 환불 후에도 이력 기록으로 보존되며,refundCount는 처리된 환불 수를 추적합니다
FAQ
Launch Pool에서 토큰 가격은 어떻게 결정되나요?
가격은 총 예치금을 기반으로 유기적으로 발견됩니다. 최종 가격은 총 예치된 SOL을 할당된 토큰으로 나눈 값입니다. 예치금이 많을수록 토큰당 암묵적 가격이 높아집니다.
사용자가 예치금을 출금할 수 있나요?
네, 사용자는 예치 기간 동안 출금할 수 있습니다. 시스템 악용을 방지하기 위해 0% 출금 수수료가 적용됩니다.
여러 번 예치하면 어떻게 되나요?
같은 지갑에서의 여러 예치금은 단일 예치 계정에 누적됩니다. 총 지분은 합산된 예치금을 기준으로 합니다.
사용자는 언제 토큰을 청구할 수 있나요?
예치 기간이 끝나고 청구 기간이 열린 후(claimStartCondition으로 정의됨)에 가능합니다. End behavior를 처리하기 위해 먼저 triggerBehaviorsV2가 실행되어야 합니다.
Launch Pool과 Presale의 차이점은 무엇인가요?
Launch Pool은 비례 배분과 함께 예치금을 기반으로 유기적으로 가격을 발견합니다. Presale은 미리 정해진 고정 가격으로 상한까지 선착순 할당합니다.
Launch Pool 소프트 캡이란 무엇인가요?
소프트 캡은 Launch Pool이 보유하는 quote 토큰의 상한이며 softCap 확장으로 설정합니다. 상한을 초과하는 예치금도 계속 받아들여지고 런칭은 그대로 성공하며, 초과분은 예치 기간이 끝난 후 비례적으로 환불됩니다.
소프트 캡과 최소 quote 토큰 임계값(minimum quote token threshold)의 차이점은 무엇인가요?
소프트 캡은 모금액의 상한이며 런칭을 실패시키지 않습니다. minimumQuoteTokenThreshold는 하한으로, 총 예치금이 이에 미달하면 런칭이 실패하고 모든 예치자가 전액 환불을 받을 수 있습니다. 두 확장은 별개이며 함께 사용할 수 있습니다.
Launch Pool이 초과 청약되면 예치자가 받는 토큰이 줄어드나요?
아니요. 전체 base 토큰 할당량은 모든 예치금에 비례하여 그대로 배분됩니다. 초과 청약 시에는 토큰 할당량을 줄이는 대신 초과 quote 토큰을 환불하므로, 실효 가격은 softCap / baseTokenAllocation으로 제한됩니다.
소프트 캡이 Raydium 졸업 시작 가격을 변경하나요?
네. Launch Pool이 초과 청약된 경우 졸업 시작 가격은 원시 예치 총액이 아닌 상한이 적용된 수익금에서 산출되므로, SendQuoteTokenPercentage가 실제로 전달하는 금액과 일치합니다.
용어집
| 용어 | 정의 |
|---|---|
| Launch Pool | 종료 시 가격이 발견되는 예치 기반 배포 |
| Deposit Window | 사용자가 SOL을 예치하고 출금할 수 있는 기간 |
| Claim Window | 사용자가 비례 토큰을 청구할 수 있는 기간 |
| End Behavior | 예치 기간 종료 후 실행되는 자동 작업 |
triggerBehaviorsV2 | End behavior를 처리하고 자금을 라우팅하는 명령어 |
| Proportional Distribution | 총 예치금에서의 사용자 지분에 따른 토큰 할당 |
| Quote Token | 사용자가 예치하는 토큰 (일반적으로 wSOL) |
| Base Token | 배포되는 토큰 |
| Soft Cap | Launch Pool이 보유하는 quote 토큰의 상한; 초과분은 비례 환불 |
| Minimum Quote Token Threshold | 이 값 미만이면 Launch Pool이 실패하고 전액 환불이 열리는 하한 |
| Oversubscription | 총 예치금이 설정된 소프트 캡을 초과하는 상태 |
| Filled Portion | 소프트 캡에 포함되어 런칭이 보유하는 예치금의 일부 |
| Excess Refund | 토큰 할당량은 유지한 채 소프트 캡을 초과한 예치금 부분을 반환하는 것 |
다음 단계
- Presale - 고정 가격 토큰 판매
- Uniform Price Auction - 입찰 기반 토큰 오퍼링
- 토큰 출시하기 - 엔드투엔드 토큰 출시 가이드
- Metaplex API - API를 통한 런치 및 토큰 세일 데이터 조회
