런치 유형
본딩 커브 — 인덱싱 및 이벤트
Last updated April 9, 2026
폴링 없이 GPA 쿼리, 스왑별 이벤트 디코딩, 가격 및 상태 변화 추적을 통해 전체 Genesis 본딩 커브 라이프사이클을 인덱싱합니다.
요약
Genesis 프로그램은 모든 확인된 스왑에서 내부 명령어로 BondingCurveSwapEvent를 발생시킵니다. 인덱서는 GPA 쿼리와 라이프사이클 명령어 추적을 결합하여 모든 거래 후 계정을 가져오지 않고도 전체 커브 상태를 재구성할 수 있습니다.
- GPA 검색 — 프로그램 전체에서 모든
BondingCurveBucketV2계정 검색 - 스왑 이벤트 — 내부 명령어의 판별자 바이트
255; 방향, 금액, 수수료, 스왑 후 리저브 포함 - 이벤트에서 가격 계산 — 추가 RPC 호출 없이 이벤트 데이터에서 현재 가격 파생
- 라이프사이클 추적 — 토큰 생성부터 Raydium 졸업까지 여덟 가지 고유 이벤트
프로그램 ID: GNS1S5J5AspKXgpjz6SvKL66kPaKWAhaGRhCqPRxii2B
모든 본딩 커브 검색 (GPA)
GPA 빌더를 사용하여 프로그램의 모든 BondingCurveBucketV2 계정을 가져옵니다 — 대시보드, 애그리게이터, 인덱서에 유용합니다. 전체 계정 필드 참조는 고급 내부 구조를 참조하세요.
1import { getBondingCurveBucketV2GpaBuilder } from '@metaplex-foundation/genesis';
2
3const allCurves = await getBondingCurveBucketV2GpaBuilder(umi)
4 .whereField('discriminator', /* BondingCurveBucketV2 discriminator */)
5 .get();
6
7for (const curve of allCurves) {
8 console.log('Bucket PDA: ', curve.publicKey.toString());
9 console.log('Base token balance: ', curve.data.baseTokenBalance.toString());
10}
스왑 이벤트 디코딩
모든 확인된 스왑은 판별자 바이트 255로 BondingCurveSwapEvent를 내부 명령어로 발생시킵니다. 트랜잭션에서 디코딩하면 정확한 스왑 후 리저브 상태, 수수료 내역, 방향을 얻을 수 있습니다.
BondingCurveSwapEvent 필드
| 필드 | 유형 | 설명 |
|---|---|---|
swapDirection | SwapDirection | SwapDirection.Buy (SOL 입력, 토큰 출력) 또는 SwapDirection.Sell (토큰 입력, SOL 출력) |
quoteTokenAmount | bigint | 스왑의 SOL 금액 (매수 시 입력, 매도 시 총 출력), lamports 단위 |
baseTokenAmount | bigint | 스왑의 토큰 금액 (매수 시 출력, 매도 시 입력) |
fee | bigint | 부과된 프로토콜 수수료, lamports 단위 |
creatorFee | bigint | 부과된 창작자 수수료, lamports 단위 (창작자 수수료가 구성되지 않은 경우 0) |
baseTokenBalance | bigint | 스왑 후 baseTokenBalance |
quoteTokenDepositTotal | bigint | 스왑 후 quoteTokenDepositTotal |
virtualSol | bigint | 가상 SOL 리저브 (불변 — 계정 가져오기 없이 가격 계산에 유용) |
virtualTokens | bigint | 가상 토큰 리저브 (불변 — 위와 동일) |
blockTime | bigint | 스왑이 포함된 블록의 Unix 타임스탬프 |
확인된 트랜잭션에서 디코딩
1import { getBondingCurveSwapEventSerializer, SwapDirection } from '@metaplex-foundation/genesis';
2
3const GENESIS_PROGRAM_ID = 'GNS1S5J5AspKXgpjz6SvKL66kPaKWAhaGRhCqPRxii2B';
4const SWAP_EVENT_DISCRIMINATOR = 255;
5
6async function decodeSwapEvent(signature: string) {
7 const tx = await umi.rpc.getTransaction(signature, {
8 commitment: 'confirmed',
9 });
10
11 if (!tx) throw new Error('Transaction not found');
12
13 const serializer = getBondingCurveSwapEventSerializer();
14
15 for (const innerIx of tx.meta?.innerInstructions ?? []) {
16 for (const ix of innerIx.instructions) {
17 const programId = tx.transaction.message.accountKeys[ix.programIdIndex];
18
19 if (programId.toString() !== GENESIS_PROGRAM_ID) continue;
20
21 const data = ix.data; // Uint8Array
22 if (data[0] !== SWAP_EVENT_DISCRIMINATOR) continue;
23
24 // 판별자 바이트를 잘라낸 후 역직렬화합니다.
25 const [event] = serializer.deserialize(data.slice(1));
26
27 const isBuy = event.swapDirection === SwapDirection.Buy;
28 console.log('Direction: ', isBuy ? 'buy' : 'sell');
29 console.log('Quote token amount: ', event.quoteTokenAmount.toString(), 'lamports');
30 console.log('Base token amount: ', event.baseTokenAmount.toString());
31 console.log('Protocol fee: ', event.fee.toString(), 'lamports');
32 console.log('Creator fee: ', event.creatorFee.toString(), 'lamports');
33 console.log('Base balance: ', event.baseTokenBalance.toString());
34 console.log('Quote deposit total: ', event.quoteTokenDepositTotal.toString());
35
36 return event;
37 }
38 }
39
40 return null; // 이 트랜잭션에서 스왑 이벤트를 찾지 못했습니다.
41}
이벤트에서 현재 가격 추적
모든 거래 후 계정을 가져오는 대신, 각 BondingCurveSwapEvent에 포함된 스왑 후 리저브 상태에서 현재 가격을 파생합니다:
1function getPriceFromEvent(event: BondingCurveSwapEvent, bucket: BondingCurveBucketV2) {
2 // totalTokens = virtualTokens + 스왑 후 baseTokenBalance (이벤트에 포함)
3 const totalTokens = bucket.virtualTokens + event.baseTokenBalance;
4 // totalSol = virtualSol + 스왑 후 quoteTokenDepositTotal (이벤트에 포함)
5 const totalSol = bucket.virtualSol + event.quoteTokenDepositTotal;
6 // 가격: SOL당 토큰 (bigint로 기본 토큰 단위당 lamports)
7 return totalSol > 0n ? totalTokens / totalSol : 0n;
8}
virtualSol과 virtualTokens는 모든 BondingCurveSwapEvent에 포함되어 있습니다 — 이벤트에서 가격을 계산하기 위해 별도의 계정 가져오기가 필요하지 않습니다. 커브 생성 후에는 불변입니다.
라이프사이클 이벤트
Genesis 프로그램 명령어 및 내부 명령어 이벤트를 수신하여 본딩 커브의 전체 라이프사이클을 추적합니다. SDK를 사용하여 스왑 트랜잭션을 실행하는 방법은 본딩 커브 스왑 통합을 참조하세요.
| 이벤트 | 설명 | 주요 필드 |
|---|---|---|
| 토큰 생성 | SPL 토큰 민팅, genesis 계정 초기화 | baseMint, genesisAccount |
| 본딩 커브 추가 | BondingCurveBucketV2 계정 생성 | bucketPda, baseTokenAllocation, virtualSol, virtualTokens |
| 완료 | 런칭 구성 잠금, 버킷 활성화 | genesisAccount |
| 라이브 시작 | swapStartCondition 충족, 거래 시작 | bucketPda, 타임스탬프 |
| 스왑 | 매수 또는 매도 실행 | BondingCurveSwapEvent (판별자 255) |
| 소진 | baseTokenBalance === 0 | bucketPda, quoteTokenDepositTotal |
| 졸업 크랭크 | 유동성 마이그레이션 명령어 제출 | bucketPda, raydiumCpmmPool |
| 졸업 | Raydium CPMM 풀 자금 조달, 본딩 커브 종료 | cpmmPoolPda, 누적 SOL |
계정 판별자 및 PDA 파생
판별자
| 계정 | 판별자 | 설명 |
|---|---|---|
GenesisAccountV2 | 계정 유형별 고유 | 마스터 조정 계정 |
BondingCurveBucketV2 | 계정 유형별 고유 | 본딩 커브 AMM 상태 |
BondingCurveSwapEvent | 255 (내부 명령어) | 프로그램에서 발생하는 스왑별 이벤트 |
PDA 시드
| PDA | 시드 |
|---|---|
GenesisAccountV2 | ["genesis_account_v2", baseMint, genesisIndex (u8)] |
BondingCurveBucketV2 | ["bonding_curve_bucket_v2", genesisAccount, bucketIndex (u8)] |
Genesis SDK에서 findGenesisAccountV2Pda와 findBondingCurveBucketV2Pda를 사용하여 TypeScript에서 PDA를 파생하세요.
참고 사항
virtualSol과virtualTokens는 모든BondingCurveSwapEvent에 포함되어 있습니다 — 이벤트에서 가격을 계산하기 위해 별도의 계정 가져오기가 필요하지 않습니다; 커브 생성 후에는 불변입니다BondingCurveSwapEvent판별자는 항상 바이트255입니다 — 이 선행 바이트를 가진 Genesis 프로그램의 모든 내부 명령어는 스왑 이벤트입니다isSoldOut이true를 반환하고isGraduated가true를 반환하기 전 사이에, 커브는 소진되었지만 Raydium CPMM 풀에 아직 자금이 조달되지 않았습니다;isGraduated가 풀 존재를 확인할 때까지 사용자를 Raydium으로 리다이렉트하지 마세요isGraduated는 매번 호출 시 RPC 호출을 수행합니다 — 모든 렌더링에서 호출하는 대신 인덱서에서 결과를 캐시하세요
FAQ
BondingCurveSwapEvent를 어떻게 디코딩하나요?
Genesis 프로그램(GNS1S5J5AspKXgpjz6SvKL66kPaKWAhaGRhCqPRxii2B)에서 첫 번째 데이터 바이트가 255인 내부 명령어를 찾으세요. 그 바이트를 잘라내고 나머지를 getBondingCurveSwapEventSerializer().deserialize(data.slice(1))에 전달하세요. 반환된 객체에는 swapDirection, quoteTokenAmount, baseTokenAmount, fee, creatorFee, 스왑 후 리저브 상태(baseTokenBalance, quoteTokenDepositTotal, virtualSol, virtualTokens, blockTime)가 포함됩니다.
isSoldOut과 isGraduated의 차이는 무엇인가요?
isSoldOut은 동기 로컬 확인입니다 — baseTokenBalance가 0n이 되는 즉시 true를 반환합니다. isGraduated는 Raydium CPMM 풀이 온체인에서 생성되고 자금이 조달되었는지 확인하는 비동기 RPC 호출입니다. 소진과 졸업 사이에 isSoldOut이 true이지만 isGraduated가 false인 구간이 있습니다. isGraduated가 풀 존재를 확인할 때까지 사용자를 Raydium으로 리다이렉트하지 마세요.
