11import {
2- safeFetchGenesisAccountV2 ,
2+ findBondingCurveBucketV2Pda ,
33 findLaunchPoolBucketV2Pda ,
4- safeFetchLaunchPoolBucketV2 ,
54 findPresaleBucketV2Pda ,
6- safeFetchPresaleBucketV2 ,
75 findUnlockedBucketV2Pda ,
6+ getCurrentPrice ,
7+ getCurrentPriceComponents ,
8+ getCurrentPriceQuotePerBase ,
9+ getFillPercentage ,
10+ isFirstBuyPending ,
11+ isSoldOut ,
12+ isSwappable ,
13+ safeFetchBondingCurveBucketV2 ,
14+ safeFetchGenesisAccountV2 ,
15+ safeFetchLaunchPoolBucketV2 ,
16+ safeFetchPresaleBucketV2 ,
817 safeFetchUnlockedBucketV2 ,
918} from '@metaplex-foundation/genesis'
1019import { publicKey } from '@metaplex-foundation/umi'
@@ -21,44 +30,49 @@ function formatCondition(condition: { __kind: string; time?: bigint }): string {
2130 if ( timestamp === 0 ) return 'Not set'
2231 return new Date ( timestamp * 1000 ) . toISOString ( )
2332 }
33+
2434 return `${ condition . __kind } `
2535}
2636
2737export default class BucketFetch extends BaseCommand < typeof BucketFetch > {
28- static override description = `Fetch a Genesis bucket by genesis address and bucket index.
29-
30- This command retrieves and displays information about a bucket in a Genesis account.
31- Supports Launch Pool, Presale, and Unlocked bucket types.`
32-
33- static override examples = [
34- '$ mplx genesis bucket fetch GenesisAddress... --bucketIndex 0' ,
35- '$ mplx genesis bucket fetch GenesisAddress... -b 1 --type presale' ,
36- '$ mplx genesis bucket fetch GenesisAddress... -b 2 --type unlocked' ,
37- ]
38-
39- static override usage = 'genesis bucket fetch [GENESIS] [FLAGS]'
40-
4138 static override args = {
4239 genesis : Args . string ( {
4340 description : 'The Genesis account address' ,
4441 required : true ,
4542 } ) ,
4643 }
4744
45+ static override description = `Fetch a Genesis bucket by genesis address and bucket index.
46+
47+ This command retrieves and displays information about a bucket in a Genesis account.
48+ Supports Launch Pool, Presale, Unlocked, and Bonding Curve bucket types.
49+
50+ When --type is not specified, the command auto-detects the bucket type by trying
51+ all known types at the given index.`
52+
53+ static override examples = [
54+ '$ mplx genesis bucket fetch GenesisAddress...' ,
55+ '$ mplx genesis bucket fetch GenesisAddress... -b 1' ,
56+ '$ mplx genesis bucket fetch GenesisAddress... --type presale -b 0' ,
57+ '$ mplx genesis bucket fetch GenesisAddress... --type bonding-curve' ,
58+ ]
59+
4860 static override flags = {
4961 bucketIndex : Flags . integer ( {
5062 char : 'b' ,
51- description : 'Index of the bucket to fetch' ,
5263 default : 0 ,
64+ description : 'Index of the bucket to fetch' ,
5365 } ) ,
5466 type : Flags . option ( {
5567 char : 't' ,
56- description : 'Type of bucket to fetch' ,
57- default : 'launch-pool' ,
58- options : [ 'launch-pool' , 'presale' , 'unlocked' ] as const ,
68+ description : 'Type of bucket to fetch (auto-detected if not specified) ' ,
69+ options : [ 'launch-pool' , 'presale' , 'unlocked' , 'bonding-curve' ] as const ,
70+ required : false ,
5971 } ) ( ) ,
6072 }
6173
74+ static override usage = 'genesis bucket fetch [GENESIS] [FLAGS]'
75+
6276 public async run ( ) : Promise < unknown > {
6377 const { args, flags } = await this . parse ( BucketFetch )
6478 const spinner = ora ( 'Fetching bucket...' ) . start ( )
@@ -77,24 +91,170 @@ Supports Launch Pool, Presale, and Unlocked bucket types.`
7791
7892 spinner . text = 'Fetching bucket details...'
7993
80- if ( flags . type === 'presale' ) {
81- return await this . fetchPresaleBucket ( genesisAddress , flags . bucketIndex , spinner )
82- } else if ( flags . type === 'unlocked' ) {
83- return await this . fetchUnlockedBucket ( genesisAddress , flags . bucketIndex , spinner )
84- } else {
85- return await this . fetchLaunchPoolBucket ( genesisAddress , flags . bucketIndex , spinner )
94+ if ( flags . type ) {
95+ // Explicit type specified
96+ if ( flags . type === 'presale' ) {
97+ return await this . fetchPresaleBucket ( genesisAddress , flags . bucketIndex , spinner )
98+ }
99+
100+ if ( flags . type === 'unlocked' ) {
101+ return await this . fetchUnlockedBucket ( genesisAddress , flags . bucketIndex , spinner )
102+ }
103+
104+ if ( flags . type === 'bonding-curve' ) {
105+ return await this . fetchBondingCurveBucket ( genesisAddress , flags . bucketIndex , spinner )
106+ }
107+
108+ return await this . fetchLaunchPoolBucket ( genesisAddress , flags . bucketIndex , spinner )
109+
86110 }
87111
112+ // Auto-detect: try all bucket types at this index
113+ spinner . text = 'Detecting bucket type...'
114+ return await this . fetchAutoDetect ( genesisAddress , flags . bucketIndex , spinner )
115+
88116 } catch ( error ) {
89117 spinner . fail ( 'Failed to fetch bucket' )
90118 throw error
91119 }
92120 }
93121
122+ private async fetchAutoDetect ( genesisAddress : ReturnType < typeof publicKey > , bucketIndex : number , spinner : ReturnType < typeof ora > ) : Promise < unknown > {
123+ // Probe all bucket types at this index in parallel
124+ const [ bc , lp , pre , ul ] = await Promise . all ( [
125+ safeFetchBondingCurveBucketV2 ( this . context . umi , findBondingCurveBucketV2Pda ( this . context . umi , { bucketIndex, genesisAccount : genesisAddress } ) [ 0 ] ) ,
126+ safeFetchLaunchPoolBucketV2 ( this . context . umi , findLaunchPoolBucketV2Pda ( this . context . umi , { bucketIndex, genesisAccount : genesisAddress } ) [ 0 ] ) ,
127+ safeFetchPresaleBucketV2 ( this . context . umi , findPresaleBucketV2Pda ( this . context . umi , { bucketIndex, genesisAccount : genesisAddress } ) [ 0 ] ) ,
128+ safeFetchUnlockedBucketV2 ( this . context . umi , findUnlockedBucketV2Pda ( this . context . umi , { bucketIndex, genesisAccount : genesisAddress } ) [ 0 ] ) ,
129+ ] )
130+
131+ const found : { fn : ( ) => Promise < unknown > ; type : string } [ ] = [ ]
132+ if ( bc ) found . push ( { fn : ( ) => this . fetchBondingCurveBucket ( genesisAddress , bucketIndex , spinner ) , type : 'bonding-curve' } )
133+ if ( lp ) found . push ( { fn : ( ) => this . fetchLaunchPoolBucket ( genesisAddress , bucketIndex , spinner ) , type : 'launch-pool' } )
134+ if ( pre ) found . push ( { fn : ( ) => this . fetchPresaleBucket ( genesisAddress , bucketIndex , spinner ) , type : 'presale' } )
135+ if ( ul ) found . push ( { fn : ( ) => this . fetchUnlockedBucket ( genesisAddress , bucketIndex , spinner ) , type : 'unlocked' } )
136+
137+ if ( found . length === 0 ) {
138+ spinner . fail ( 'Bucket not found' )
139+ this . error ( `No bucket found at index ${ bucketIndex } . Tried all bucket types (launch-pool, presale, unlocked, bonding-curve).` )
140+ }
141+
142+ if ( found . length === 1 ) {
143+ return found [ 0 ] . fn ( )
144+ }
145+
146+ // Multiple bucket types at the same index — show all
147+ spinner . succeed ( `Found ${ found . length } buckets at index ${ bucketIndex } ` )
148+ const results : unknown [ ] = await Promise . all ( found . map ( async ( { fn } ) => {
149+ const result = await fn ( )
150+ this . log ( '' )
151+ return result
152+ } ) )
153+
154+ return results
155+ }
156+
157+ private async fetchBondingCurveBucket ( genesisAddress : ReturnType < typeof publicKey > , bucketIndex : number , spinner : ReturnType < typeof ora > ) : Promise < unknown > {
158+ const [ bucketPda ] = findBondingCurveBucketV2Pda ( this . context . umi , {
159+ bucketIndex,
160+ genesisAccount : genesisAddress ,
161+ } )
162+
163+ const bucket = await safeFetchBondingCurveBucketV2 ( this . context . umi , bucketPda )
164+
165+ if ( ! bucket ) {
166+ spinner . fail ( 'Bucket not found' )
167+ this . error ( `Bonding curve bucket not found at index ${ bucketIndex } . It may be a different bucket type or not exist.` )
168+ }
169+
170+ spinner . succeed ( 'Bucket fetched successfully!' )
171+
172+ const price = getCurrentPrice ( bucket )
173+ const priceQuotePerBase = getCurrentPriceQuotePerBase ( bucket )
174+ const { baseReserves, quoteReserves } = getCurrentPriceComponents ( bucket )
175+ const firstBuyPending = isFirstBuyPending ( bucket )
176+ const swappable = isSwappable ( bucket )
177+ const soldOut = isSoldOut ( bucket )
178+ const fillPct = getFillPercentage ( bucket )
179+
180+ this . log ( '' )
181+ this . logSuccess ( `Bonding Curve Bucket` )
182+ this . log ( '' )
183+ this . log ( 'Bucket Details:' )
184+ this . log ( ` Address: ${ bucketPda } ` )
185+ this . log ( ` Type: ${ KEY_TYPES [ bucket . key ] || 'BondingCurveV2' } ` )
186+ this . log ( ` Genesis Account: ${ bucket . bucket . genesis } ` )
187+ this . log ( ` Bucket Index: ${ bucket . bucket . bucketIndex } ` )
188+ this . log ( '' )
189+ this . log ( 'Allocation:' )
190+ this . log ( ` Base Token Allocation: ${ bucket . bucket . baseTokenAllocation . toString ( ) } ` )
191+ this . log ( ` Base Token Balance: ${ bucket . bucket . baseTokenBalance . toString ( ) } ` )
192+ this . log ( ` Quote Token Deposit Total: ${ bucket . quoteTokenDepositTotal . toString ( ) } ` )
193+ this . log ( '' )
194+ this . log ( 'Pricing:' )
195+ this . log ( ` Current Price (tokens per quote): ${ price . toString ( ) } ` )
196+ this . log ( ` Current Price (quote per token): ${ priceQuotePerBase . toString ( ) } ` )
197+ this . log ( ` Base Reserves: ${ baseReserves . toString ( ) } ` )
198+ this . log ( ` Quote Reserves: ${ quoteReserves . toString ( ) } ` )
199+ this . log ( '' )
200+ this . log ( 'Constant Product Params:' )
201+ this . log ( ` Virtual SOL: ${ bucket . constantProductParams . virtualSol . toString ( ) } ` )
202+ this . log ( ` Virtual Tokens: ${ bucket . constantProductParams . virtualTokens . toString ( ) } ` )
203+ this . log ( '' )
204+ this . log ( 'Status:' )
205+ this . log ( ` First Buy Pending: ${ firstBuyPending ? 'Yes' : 'No' } ` )
206+ this . log ( ` Swappable: ${ swappable ? 'Yes' : 'No' } ` )
207+ this . log ( ` Sold Out: ${ soldOut ? 'Yes' : 'No' } ` )
208+ this . log ( ` Fill Percentage: ${ fillPct . toFixed ( 2 ) } %` )
209+ this . log ( '' )
210+ this . log ( 'Conditions:' )
211+ this . log ( ` Swap Start: ${ formatCondition ( bucket . swapStartCondition ) } ` )
212+ this . log ( ` Swap End: ${ formatCondition ( bucket . swapEndCondition ) } ` )
213+ this . log ( '' )
214+ this . log ( 'Fees:' )
215+ this . log ( ` Deposit Fee: ${ bucket . depositFee . toString ( ) } ` )
216+ this . log ( ` Withdraw Fee: ${ bucket . withdrawFee . toString ( ) } ` )
217+ this . log ( ` Creator Fee Accrued: ${ bucket . creatorFeeAccrued . toString ( ) } ` )
218+ this . log ( ` Creator Fee Claimed: ${ bucket . creatorFeeClaimed . toString ( ) } ` )
219+ this . log ( '' )
220+ this . log ( 'View on Explorer:' )
221+ this . log (
222+ generateExplorerUrl (
223+ this . context . explorer ,
224+ this . context . chain ,
225+ bucketPda ,
226+ 'account'
227+ )
228+ )
229+
230+ return {
231+ address : bucketPda . toString ( ) ,
232+ baseReserves : baseReserves . toString ( ) ,
233+ baseTokenAllocation : bucket . bucket . baseTokenAllocation . toString ( ) ,
234+ baseTokenBalance : bucket . bucket . baseTokenBalance . toString ( ) ,
235+ bucketIndex : bucket . bucket . bucketIndex ,
236+ creatorFeeAccrued : bucket . creatorFeeAccrued . toString ( ) ,
237+ creatorFeeClaimed : bucket . creatorFeeClaimed . toString ( ) ,
238+ currentPrice : price . toString ( ) ,
239+ currentPriceQuotePerBase : priceQuotePerBase . toString ( ) ,
240+ explorer : generateExplorerUrl ( this . context . explorer , this . context . chain , bucketPda , 'account' ) ,
241+ fillPercentage : fillPct ,
242+ firstBuyPending,
243+ genesisAccount : bucket . bucket . genesis . toString ( ) ,
244+ quoteReserves : quoteReserves . toString ( ) ,
245+ quoteTokenDepositTotal : bucket . quoteTokenDepositTotal . toString ( ) ,
246+ soldOut,
247+ swappable,
248+ type : 'bonding-curve' ,
249+ virtualSol : bucket . constantProductParams . virtualSol . toString ( ) ,
250+ virtualTokens : bucket . constantProductParams . virtualTokens . toString ( ) ,
251+ }
252+ }
253+
94254 private async fetchLaunchPoolBucket ( genesisAddress : ReturnType < typeof publicKey > , bucketIndex : number , spinner : ReturnType < typeof ora > ) : Promise < unknown > {
95255 const [ bucketPda ] = findLaunchPoolBucketV2Pda ( this . context . umi , {
96- genesisAccount : genesisAddress ,
97256 bucketIndex,
257+ genesisAccount : genesisAddress ,
98258 } )
99259
100260 const bucket = await safeFetchLaunchPoolBucketV2 ( this . context . umi , bucketPda )
@@ -149,20 +309,20 @@ Supports Launch Pool, Presale, and Unlocked bucket types.`
149309 )
150310
151311 return {
152- type : 'launch-pool' ,
153312 address : bucketPda . toString ( ) ,
154- genesisAccount : bucket . bucket . genesis . toString ( ) ,
155- bucketIndex : bucket . bucket . bucketIndex ,
156313 baseTokenAllocation : bucket . bucket . baseTokenAllocation . toString ( ) ,
157314 baseTokenBalance : bucket . bucket . baseTokenBalance . toString ( ) ,
315+ bucketIndex : bucket . bucket . bucketIndex ,
158316 explorer : generateExplorerUrl ( this . context . explorer , this . context . chain , bucketPda , 'account' ) ,
317+ genesisAccount : bucket . bucket . genesis . toString ( ) ,
318+ type : 'launch-pool' ,
159319 }
160320 }
161321
162322 private async fetchPresaleBucket ( genesisAddress : ReturnType < typeof publicKey > , bucketIndex : number , spinner : ReturnType < typeof ora > ) : Promise < unknown > {
163323 const [ bucketPda ] = findPresaleBucketV2Pda ( this . context . umi , {
164- genesisAccount : genesisAddress ,
165324 bucketIndex,
325+ genesisAccount : genesisAddress ,
166326 } )
167327
168328 const bucket = await safeFetchPresaleBucketV2 ( this . context . umi , bucketPda )
@@ -216,21 +376,21 @@ Supports Launch Pool, Presale, and Unlocked bucket types.`
216376 )
217377
218378 return {
219- type : 'presale' ,
220379 address : bucketPda . toString ( ) ,
221- genesisAccount : bucket . bucket . genesis . toString ( ) ,
222- bucketIndex : bucket . bucket . bucketIndex ,
380+ allocationQuoteTokenCap : bucket . allocationQuoteTokenCap . toString ( ) ,
223381 baseTokenAllocation : bucket . bucket . baseTokenAllocation . toString ( ) ,
224382 baseTokenBalance : bucket . bucket . baseTokenBalance . toString ( ) ,
225- allocationQuoteTokenCap : bucket . allocationQuoteTokenCap . toString ( ) ,
383+ bucketIndex : bucket . bucket . bucketIndex ,
226384 explorer : generateExplorerUrl ( this . context . explorer , this . context . chain , bucketPda , 'account' ) ,
385+ genesisAccount : bucket . bucket . genesis . toString ( ) ,
386+ type : 'presale' ,
227387 }
228388 }
229389
230390 private async fetchUnlockedBucket ( genesisAddress : ReturnType < typeof publicKey > , bucketIndex : number , spinner : ReturnType < typeof ora > ) : Promise < unknown > {
231391 const [ bucketPda ] = findUnlockedBucketV2Pda ( this . context . umi , {
232- genesisAccount : genesisAddress ,
233392 bucketIndex,
393+ genesisAccount : genesisAddress ,
234394 } )
235395
236396 const bucket = await safeFetchUnlockedBucketV2 ( this . context . umi , bucketPda )
@@ -274,15 +434,15 @@ Supports Launch Pool, Presale, and Unlocked bucket types.`
274434 )
275435
276436 return {
277- type : 'unlocked' ,
278437 address : bucketPda . toString ( ) ,
279- genesisAccount : bucket . bucket . genesis . toString ( ) ,
280- bucketIndex : bucket . bucket . bucketIndex ,
281438 baseTokenAllocation : bucket . bucket . baseTokenAllocation . toString ( ) ,
282439 baseTokenBalance : bucket . bucket . baseTokenBalance . toString ( ) ,
283- recipient : bucket . recipient . toString ( ) ,
440+ bucketIndex : bucket . bucket . bucketIndex ,
284441 claimed : bucket . claimed ,
285442 explorer : generateExplorerUrl ( this . context . explorer , this . context . chain , bucketPda , 'account' ) ,
443+ genesisAccount : bucket . bucket . genesis . toString ( ) ,
444+ recipient : bucket . recipient . toString ( ) ,
445+ type : 'unlocked' ,
286446 }
287447 }
288448}
0 commit comments