@ignitionfi/spl-stake-pool 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,1523 @@
1
+ import {
2
+ createApproveInstruction,
3
+ createAssociatedTokenAccountIdempotentInstruction,
4
+ getAccount,
5
+ getAssociatedTokenAddressSync,
6
+ NATIVE_MINT,
7
+ } from '@solana/spl-token'
8
+ import {
9
+ AccountInfo,
10
+ Connection,
11
+ Keypair,
12
+ PublicKey,
13
+ Signer,
14
+ StakeAuthorizationLayout,
15
+ StakeProgram,
16
+ SystemProgram,
17
+ TransactionInstruction,
18
+ } from '@solana/web3.js'
19
+ import BN from 'bn.js'
20
+ import { create } from 'superstruct'
21
+ import {
22
+ DEVNET_STAKE_POOL_PROGRAM_ID,
23
+ MAX_VALIDATORS_TO_UPDATE,
24
+ MINIMUM_ACTIVE_STAKE,
25
+ STAKE_POOL_PROGRAM_ID,
26
+ } from './constants'
27
+ import { StakePoolInstruction } from './instructions'
28
+ import {
29
+ StakeAccount,
30
+ StakePool,
31
+ StakePoolLayout,
32
+ ValidatorList,
33
+ ValidatorListLayout,
34
+ ValidatorStakeInfo,
35
+ } from './layouts'
36
+ import {
37
+ arrayChunk,
38
+ calcLamportsWithdrawAmount,
39
+ findEphemeralStakeProgramAddress,
40
+ findMetadataAddress,
41
+ findStakeProgramAddress,
42
+ findTransientStakeProgramAddress,
43
+ findWithdrawAuthorityProgramAddress,
44
+ findWsolTransientProgramAddress,
45
+ getValidatorListAccount,
46
+ lamportsToSol,
47
+ newStakeAccount,
48
+ prepareWithdrawAccounts,
49
+ solToLamports,
50
+ ValidatorAccount,
51
+ } from './utils'
52
+
53
+ export {
54
+ DEVNET_STAKE_POOL_PROGRAM_ID,
55
+ STAKE_POOL_PROGRAM_ID,
56
+ } from './constants'
57
+ export * from './instructions'
58
+ export type {
59
+ AccountType,
60
+ StakePool,
61
+ ValidatorList,
62
+ ValidatorStakeInfo,
63
+ } from './layouts'
64
+ export {
65
+ StakePoolLayout,
66
+ ValidatorListLayout,
67
+ ValidatorStakeInfoLayout,
68
+ } from './layouts'
69
+
70
+ export interface ValidatorListAccount {
71
+ pubkey: PublicKey
72
+ account: AccountInfo<ValidatorList>
73
+ }
74
+
75
+ export interface StakePoolAccount {
76
+ pubkey: PublicKey
77
+ account: AccountInfo<StakePool>
78
+ }
79
+
80
+ export interface WithdrawAccount {
81
+ stakeAddress: PublicKey
82
+ voteAddress?: PublicKey
83
+ poolAmount: BN
84
+ }
85
+
86
+ /**
87
+ * Wrapper class for a stake pool.
88
+ * Each stake pool has a stake pool account and a validator list account.
89
+ */
90
+ export interface StakePoolAccounts {
91
+ stakePool: StakePoolAccount | undefined
92
+ validatorList: ValidatorListAccount | undefined
93
+ }
94
+
95
+ export function getStakePoolProgramId(rpcEndpoint: string): PublicKey {
96
+ if (rpcEndpoint.includes('devnet')) {
97
+ return DEVNET_STAKE_POOL_PROGRAM_ID
98
+ } else {
99
+ return STAKE_POOL_PROGRAM_ID
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Retrieves and deserializes a StakePool account using a web3js connection and the stake pool address.
105
+ * @param connection An active web3js connection.
106
+ * @param stakePoolAddress The public key (address) of the stake pool account.
107
+ */
108
+ export async function getStakePoolAccount(
109
+ connection: Connection,
110
+ stakePoolAddress: PublicKey,
111
+ ): Promise<StakePoolAccount> {
112
+ const account = await connection.getAccountInfo(stakePoolAddress)
113
+
114
+ if (!account) {
115
+ throw new Error('Invalid stake pool account')
116
+ }
117
+
118
+ return {
119
+ pubkey: stakePoolAddress,
120
+ account: {
121
+ data: StakePoolLayout.decode(account.data),
122
+ executable: account.executable,
123
+ lamports: account.lamports,
124
+ owner: account.owner,
125
+ },
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Retrieves and deserializes a Stake account using a web3js connection and the stake address.
131
+ * @param connection An active web3js connection.
132
+ * @param stakeAccount The public key (address) of the stake account.
133
+ */
134
+ export async function getStakeAccount(
135
+ connection: Connection,
136
+ stakeAccount: PublicKey,
137
+ ): Promise<StakeAccount> {
138
+ const result = (await connection.getParsedAccountInfo(stakeAccount)).value
139
+ if (!result || !('parsed' in result.data)) {
140
+ throw new Error('Invalid stake account')
141
+ }
142
+ const program = result.data.program
143
+ if (program !== 'stake') {
144
+ throw new Error('Not a stake account')
145
+ }
146
+ return create(result.data.parsed, StakeAccount)
147
+ }
148
+
149
+ /**
150
+ * Retrieves all StakePool and ValidatorList accounts that are running a particular StakePool program.
151
+ * @param connection An active web3js connection.
152
+ * @param stakePoolProgramAddress The public key (address) of the StakePool program.
153
+ */
154
+ export async function getStakePoolAccounts(
155
+ connection: Connection,
156
+ stakePoolProgramAddress: PublicKey,
157
+ ): Promise<
158
+ (StakePoolAccount | ValidatorListAccount | undefined)[] | undefined
159
+ > {
160
+ const response = await connection.getProgramAccounts(stakePoolProgramAddress)
161
+
162
+ return response
163
+ .map((a) => {
164
+ try {
165
+ if (a.account.data.readUInt8() === 1) {
166
+ const data = StakePoolLayout.decode(a.account.data)
167
+ return {
168
+ pubkey: a.pubkey,
169
+ account: {
170
+ data,
171
+ executable: a.account.executable,
172
+ lamports: a.account.lamports,
173
+ owner: a.account.owner,
174
+ },
175
+ }
176
+ } else if (a.account.data.readUInt8() === 2) {
177
+ const data = ValidatorListLayout.decode(a.account.data)
178
+ return {
179
+ pubkey: a.pubkey,
180
+ account: {
181
+ data,
182
+ executable: a.account.executable,
183
+ lamports: a.account.lamports,
184
+ owner: a.account.owner,
185
+ },
186
+ }
187
+ } else {
188
+ console.error(
189
+ `Could not decode. StakePoolAccount Enum is ${a.account.data.readUInt8()}, expected 1 or 2!`,
190
+ )
191
+ return undefined
192
+ }
193
+ } catch (error) {
194
+ console.error('Could not decode account. Error:', error)
195
+ return undefined
196
+ }
197
+ })
198
+ .filter(a => a !== undefined)
199
+ }
200
+
201
+ /**
202
+ * Creates instructions required to deposit stake to stake pool.
203
+ */
204
+ export async function depositStake(
205
+ connection: Connection,
206
+ stakePoolAddress: PublicKey,
207
+ authorizedPubkey: PublicKey,
208
+ validatorVote: PublicKey,
209
+ depositStake: PublicKey,
210
+ poolTokenReceiverAccount?: PublicKey,
211
+ ) {
212
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
213
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
214
+
215
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
216
+ stakePoolProgramId,
217
+ stakePoolAddress,
218
+ )
219
+
220
+ const validatorStake = await findStakeProgramAddress(
221
+ stakePoolProgramId,
222
+ validatorVote,
223
+ stakePoolAddress,
224
+ )
225
+
226
+ const instructions: TransactionInstruction[] = []
227
+ const signers: Signer[] = []
228
+
229
+ const poolMint = stakePool.account.data.poolMint
230
+
231
+ // Create token account if not specified
232
+ if (!poolTokenReceiverAccount) {
233
+ const associatedAddress = getAssociatedTokenAddressSync(
234
+ poolMint,
235
+ authorizedPubkey,
236
+ )
237
+ instructions.push(
238
+ createAssociatedTokenAccountIdempotentInstruction(
239
+ authorizedPubkey,
240
+ associatedAddress,
241
+ authorizedPubkey,
242
+ poolMint,
243
+ ),
244
+ )
245
+ poolTokenReceiverAccount = associatedAddress
246
+ }
247
+
248
+ instructions.push(
249
+ ...StakeProgram.authorize({
250
+ stakePubkey: depositStake,
251
+ authorizedPubkey,
252
+ newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
253
+ stakeAuthorizationType: StakeAuthorizationLayout.Staker,
254
+ }).instructions,
255
+ )
256
+
257
+ instructions.push(
258
+ ...StakeProgram.authorize({
259
+ stakePubkey: depositStake,
260
+ authorizedPubkey,
261
+ newAuthorizedPubkey: stakePool.account.data.stakeDepositAuthority,
262
+ stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
263
+ }).instructions,
264
+ )
265
+
266
+ instructions.push(
267
+ StakePoolInstruction.depositStake({
268
+ programId: stakePoolProgramId,
269
+ stakePool: stakePoolAddress,
270
+ validatorList: stakePool.account.data.validatorList,
271
+ depositAuthority: stakePool.account.data.stakeDepositAuthority,
272
+ reserveStake: stakePool.account.data.reserveStake,
273
+ managerFeeAccount: stakePool.account.data.managerFeeAccount,
274
+ referralPoolAccount: poolTokenReceiverAccount,
275
+ destinationPoolAccount: poolTokenReceiverAccount,
276
+ withdrawAuthority,
277
+ depositStake,
278
+ validatorStake,
279
+ poolMint,
280
+ }),
281
+ )
282
+
283
+ return {
284
+ instructions,
285
+ signers,
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Creates instructions required to deposit sol to stake pool.
291
+ */
292
+ export async function depositWsolWithSession(
293
+ connection: Connection,
294
+ stakePoolAddress: PublicKey,
295
+ signerOrSession: PublicKey,
296
+ userPubkey: PublicKey,
297
+ lamports: number,
298
+ destinationTokenAccount?: PublicKey,
299
+ referrerTokenAccount?: PublicKey,
300
+ depositAuthority?: PublicKey,
301
+ payer?: PublicKey,
302
+ ) {
303
+ const wsolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, userPubkey)
304
+
305
+ const tokenAccountInfo = await connection.getTokenAccountBalance(
306
+ wsolTokenAccount,
307
+ 'confirmed',
308
+ )
309
+ const wsolBalance = tokenAccountInfo
310
+ ? parseInt(tokenAccountInfo.value.amount)
311
+ : 0
312
+
313
+ if (wsolBalance < lamports) {
314
+ throw new Error(
315
+ `Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(
316
+ wsolBalance,
317
+ )} WSOL.`,
318
+ )
319
+ }
320
+
321
+ const stakePoolAccount = await getStakePoolAccount(
322
+ connection,
323
+ stakePoolAddress,
324
+ )
325
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
326
+ const stakePool = stakePoolAccount.account.data
327
+
328
+ const instructions: TransactionInstruction[] = []
329
+
330
+ // Create token account if not specified
331
+ if (!destinationTokenAccount) {
332
+ const associatedAddress = getAssociatedTokenAddressSync(
333
+ stakePool.poolMint,
334
+ userPubkey,
335
+ )
336
+ instructions.push(
337
+ createAssociatedTokenAccountIdempotentInstruction(
338
+ signerOrSession,
339
+ associatedAddress,
340
+ userPubkey,
341
+ stakePool.poolMint,
342
+ ),
343
+ )
344
+ destinationTokenAccount = associatedAddress
345
+ }
346
+
347
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
348
+ stakePoolProgramId,
349
+ stakePoolAddress,
350
+ )
351
+
352
+ const [programSigner] = PublicKey.findProgramAddressSync(
353
+ [Buffer.from('fogo_session_program_signer')],
354
+ stakePoolProgramId,
355
+ )
356
+
357
+ const wsolTransientAccount = findWsolTransientProgramAddress(
358
+ stakePoolProgramId,
359
+ userPubkey,
360
+ )
361
+
362
+ instructions.push(
363
+ StakePoolInstruction.depositWsolWithSession({
364
+ programId: stakePoolProgramId,
365
+ stakePool: stakePoolAddress,
366
+ reserveStake: stakePool.reserveStake,
367
+ fundingAccount: signerOrSession,
368
+ destinationPoolAccount: destinationTokenAccount,
369
+ managerFeeAccount: stakePool.managerFeeAccount,
370
+ referralPoolAccount: referrerTokenAccount ?? destinationTokenAccount,
371
+ poolMint: stakePool.poolMint,
372
+ lamports,
373
+ withdrawAuthority,
374
+ depositAuthority,
375
+
376
+ wsolMint: NATIVE_MINT,
377
+ wsolTokenAccount,
378
+ wsolTransientAccount,
379
+ tokenProgramId: stakePool.tokenProgramId,
380
+ programSigner,
381
+ payer,
382
+ }),
383
+ )
384
+ return {
385
+ instructions,
386
+ signers: [],
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Creates instructions required to deposit sol to stake pool.
392
+ */
393
+ export async function depositSol(
394
+ connection: Connection,
395
+ stakePoolAddress: PublicKey,
396
+ from: PublicKey,
397
+ lamports: number,
398
+ destinationTokenAccount?: PublicKey,
399
+ referrerTokenAccount?: PublicKey,
400
+ depositAuthority?: PublicKey,
401
+ ) {
402
+ const fromBalance = await connection.getBalance(from, 'confirmed')
403
+ if (fromBalance < lamports) {
404
+ throw new Error(
405
+ `Not enough SOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(
406
+ fromBalance,
407
+ )} SOL.`,
408
+ )
409
+ }
410
+
411
+ const stakePoolAccount = await getStakePoolAccount(
412
+ connection,
413
+ stakePoolAddress,
414
+ )
415
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
416
+ const stakePool = stakePoolAccount.account.data
417
+
418
+ // Ephemeral SOL account just to do the transfer
419
+ const userSolTransfer = new Keypair()
420
+ const signers: Signer[] = [userSolTransfer]
421
+ const instructions: TransactionInstruction[] = []
422
+
423
+ // Create the ephemeral SOL account
424
+ instructions.push(
425
+ SystemProgram.transfer({
426
+ fromPubkey: from,
427
+ toPubkey: userSolTransfer.publicKey,
428
+ lamports,
429
+ }),
430
+ )
431
+
432
+ // Create token account if not specified
433
+ if (!destinationTokenAccount) {
434
+ const associatedAddress = getAssociatedTokenAddressSync(
435
+ stakePool.poolMint,
436
+ from,
437
+ )
438
+ instructions.push(
439
+ createAssociatedTokenAccountIdempotentInstruction(
440
+ from,
441
+ associatedAddress,
442
+ from,
443
+ stakePool.poolMint,
444
+ ),
445
+ )
446
+ destinationTokenAccount = associatedAddress
447
+ }
448
+
449
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
450
+ stakePoolProgramId,
451
+ stakePoolAddress,
452
+ )
453
+
454
+ instructions.push(
455
+ StakePoolInstruction.depositSol({
456
+ programId: stakePoolProgramId,
457
+ stakePool: stakePoolAddress,
458
+ reserveStake: stakePool.reserveStake,
459
+ fundingAccount: userSolTransfer.publicKey,
460
+ destinationPoolAccount: destinationTokenAccount,
461
+ managerFeeAccount: stakePool.managerFeeAccount,
462
+ referralPoolAccount: referrerTokenAccount ?? destinationTokenAccount,
463
+ poolMint: stakePool.poolMint,
464
+ lamports,
465
+ withdrawAuthority,
466
+ depositAuthority,
467
+ }),
468
+ )
469
+
470
+ return {
471
+ instructions,
472
+ signers,
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Creates instructions required to withdraw stake from a stake pool.
478
+ */
479
+ export async function withdrawStake(
480
+ connection: Connection,
481
+ stakePoolAddress: PublicKey,
482
+ tokenOwner: PublicKey,
483
+ amount: number,
484
+ useReserve = false,
485
+ voteAccountAddress?: PublicKey,
486
+ stakeReceiver?: PublicKey,
487
+ poolTokenAccount?: PublicKey,
488
+ validatorComparator?: (_a: ValidatorAccount, _b: ValidatorAccount) => number,
489
+ ) {
490
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
491
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
492
+ const poolAmount = new BN(solToLamports(amount))
493
+
494
+ if (!poolTokenAccount) {
495
+ poolTokenAccount = getAssociatedTokenAddressSync(
496
+ stakePool.account.data.poolMint,
497
+ tokenOwner,
498
+ )
499
+ }
500
+
501
+ const tokenAccount = await getAccount(connection, poolTokenAccount)
502
+
503
+ // Check withdrawFrom balance
504
+ if (tokenAccount.amount < poolAmount.toNumber()) {
505
+ throw new Error(
506
+ `Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
507
+ Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
508
+ )
509
+ }
510
+
511
+ const stakeAccountRentExemption
512
+ = await connection.getMinimumBalanceForRentExemption(StakeProgram.space)
513
+
514
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
515
+ stakePoolProgramId,
516
+ stakePoolAddress,
517
+ )
518
+
519
+ let stakeReceiverAccount = null
520
+ if (stakeReceiver) {
521
+ stakeReceiverAccount = await getStakeAccount(connection, stakeReceiver)
522
+ }
523
+
524
+ const withdrawAccounts: WithdrawAccount[] = []
525
+
526
+ if (useReserve) {
527
+ withdrawAccounts.push({
528
+ stakeAddress: stakePool.account.data.reserveStake,
529
+ voteAddress: undefined,
530
+ poolAmount,
531
+ })
532
+ } else if (
533
+ stakeReceiverAccount
534
+ && stakeReceiverAccount?.type === 'delegated'
535
+ ) {
536
+ const voteAccount = stakeReceiverAccount.info?.stake?.delegation.voter
537
+ if (!voteAccount) {
538
+ throw new Error(`Invalid stake receiver ${stakeReceiver} delegation`)
539
+ }
540
+ const validatorListAccount = await connection.getAccountInfo(
541
+ stakePool.account.data.validatorList,
542
+ )
543
+ const validatorList = ValidatorListLayout.decode(
544
+ validatorListAccount?.data,
545
+ ) as ValidatorList
546
+ const isValidVoter = validatorList.validators.find(val =>
547
+ val.voteAccountAddress.equals(voteAccount),
548
+ )
549
+ if (voteAccountAddress && voteAccountAddress !== voteAccount) {
550
+ throw new Error(`Provided withdrawal vote account ${voteAccountAddress} does not match delegation on stake receiver account ${voteAccount},
551
+ remove this flag or provide a different stake account delegated to ${voteAccountAddress}`)
552
+ }
553
+ if (isValidVoter) {
554
+ const stakeAccountAddress = await findStakeProgramAddress(
555
+ stakePoolProgramId,
556
+ voteAccount,
557
+ stakePoolAddress,
558
+ )
559
+
560
+ const stakeAccount = await connection.getAccountInfo(stakeAccountAddress)
561
+ if (!stakeAccount) {
562
+ throw new Error(
563
+ `Preferred withdraw valdator's stake account is invalid`,
564
+ )
565
+ }
566
+
567
+ const availableForWithdrawal = calcLamportsWithdrawAmount(
568
+ stakePool.account.data,
569
+ new BN(
570
+ stakeAccount.lamports
571
+ - MINIMUM_ACTIVE_STAKE
572
+ - stakeAccountRentExemption,
573
+ ),
574
+ )
575
+
576
+ if (availableForWithdrawal.lt(poolAmount)) {
577
+ throw new Error(
578
+ `Not enough lamports available for withdrawal from ${stakeAccountAddress},
579
+ ${poolAmount} asked, ${availableForWithdrawal} available.`,
580
+ )
581
+ }
582
+ withdrawAccounts.push({
583
+ stakeAddress: stakeAccountAddress,
584
+ voteAddress: voteAccount,
585
+ poolAmount,
586
+ })
587
+ } else {
588
+ throw new Error(
589
+ `Provided stake account is delegated to a vote account ${voteAccount} which does not exist in the stake pool`,
590
+ )
591
+ }
592
+ } else if (voteAccountAddress) {
593
+ const stakeAccountAddress = await findStakeProgramAddress(
594
+ stakePoolProgramId,
595
+ voteAccountAddress,
596
+ stakePoolAddress,
597
+ )
598
+ const stakeAccount = await connection.getAccountInfo(stakeAccountAddress)
599
+ if (!stakeAccount) {
600
+ throw new Error('Invalid Stake Account')
601
+ }
602
+
603
+ const availableLamports = new BN(
604
+ stakeAccount.lamports - MINIMUM_ACTIVE_STAKE - stakeAccountRentExemption,
605
+ )
606
+ if (availableLamports.lt(new BN(0))) {
607
+ throw new Error('Invalid Stake Account')
608
+ }
609
+ const availableForWithdrawal = calcLamportsWithdrawAmount(
610
+ stakePool.account.data,
611
+ availableLamports,
612
+ )
613
+
614
+ if (availableForWithdrawal.lt(poolAmount)) {
615
+ // noinspection ExceptionCaughtLocallyJS
616
+ throw new Error(
617
+ `Not enough lamports available for withdrawal from ${stakeAccountAddress},
618
+ ${poolAmount} asked, ${availableForWithdrawal} available.`,
619
+ )
620
+ }
621
+ withdrawAccounts.push({
622
+ stakeAddress: stakeAccountAddress,
623
+ voteAddress: voteAccountAddress,
624
+ poolAmount,
625
+ })
626
+ } else {
627
+ // Get the list of accounts to withdraw from
628
+ withdrawAccounts.push(
629
+ ...(await prepareWithdrawAccounts(
630
+ connection,
631
+ stakePool.account.data,
632
+ stakePoolAddress,
633
+ poolAmount,
634
+ validatorComparator,
635
+ poolTokenAccount.equals(stakePool.account.data.managerFeeAccount),
636
+ )),
637
+ )
638
+ }
639
+
640
+ // Construct transaction to withdraw from withdrawAccounts account list
641
+ const instructions: TransactionInstruction[] = []
642
+ const userTransferAuthority = Keypair.generate()
643
+
644
+ const signers: Signer[] = [userTransferAuthority]
645
+
646
+ instructions.push(
647
+ createApproveInstruction(
648
+ poolTokenAccount,
649
+ userTransferAuthority.publicKey,
650
+ tokenOwner,
651
+ poolAmount.toNumber(),
652
+ ),
653
+ )
654
+
655
+ let totalRentFreeBalances = 0
656
+
657
+ // Max 5 accounts to prevent an error: "Transaction too large"
658
+ const maxWithdrawAccounts = 5
659
+ let i = 0
660
+
661
+ // Go through prepared accounts and withdraw/claim them
662
+ for (const withdrawAccount of withdrawAccounts) {
663
+ if (i > maxWithdrawAccounts) {
664
+ break
665
+ }
666
+ // Convert pool tokens amount to lamports
667
+ const solWithdrawAmount = calcLamportsWithdrawAmount(
668
+ stakePool.account.data,
669
+ withdrawAccount.poolAmount,
670
+ )
671
+
672
+ let infoMsg = `Withdrawing ◎${solWithdrawAmount},
673
+ from stake account ${withdrawAccount.stakeAddress?.toBase58()}`
674
+
675
+ if (withdrawAccount.voteAddress) {
676
+ infoMsg = `${infoMsg}, delegated to ${withdrawAccount.voteAddress?.toBase58()}`
677
+ }
678
+
679
+ console.info(infoMsg)
680
+ let stakeToReceive
681
+
682
+ if (
683
+ !stakeReceiver
684
+ || (stakeReceiverAccount && stakeReceiverAccount.type === 'delegated')
685
+ ) {
686
+ const stakeKeypair = newStakeAccount(
687
+ tokenOwner,
688
+ instructions,
689
+ stakeAccountRentExemption,
690
+ )
691
+ signers.push(stakeKeypair)
692
+ totalRentFreeBalances += stakeAccountRentExemption
693
+ stakeToReceive = stakeKeypair.publicKey
694
+ } else {
695
+ stakeToReceive = stakeReceiver
696
+ }
697
+
698
+ instructions.push(
699
+ StakePoolInstruction.withdrawStake({
700
+ programId: stakePoolProgramId,
701
+ stakePool: stakePoolAddress,
702
+ validatorList: stakePool.account.data.validatorList,
703
+ validatorStake: withdrawAccount.stakeAddress,
704
+ destinationStake: stakeToReceive,
705
+ destinationStakeAuthority: tokenOwner,
706
+ sourceTransferAuthority: userTransferAuthority.publicKey,
707
+ sourcePoolAccount: poolTokenAccount,
708
+ managerFeeAccount: stakePool.account.data.managerFeeAccount,
709
+ poolMint: stakePool.account.data.poolMint,
710
+ poolTokens: withdrawAccount.poolAmount.toNumber(),
711
+ withdrawAuthority,
712
+ }),
713
+ )
714
+ i++
715
+ }
716
+ if (
717
+ stakeReceiver
718
+ && stakeReceiverAccount
719
+ && stakeReceiverAccount.type === 'delegated'
720
+ ) {
721
+ signers.forEach((newStakeKeypair) => {
722
+ instructions.concat(
723
+ StakeProgram.merge({
724
+ stakePubkey: stakeReceiver,
725
+ sourceStakePubKey: newStakeKeypair.publicKey,
726
+ authorizedPubkey: tokenOwner,
727
+ }).instructions,
728
+ )
729
+ })
730
+ }
731
+
732
+ return {
733
+ instructions,
734
+ signers,
735
+ stakeReceiver,
736
+ totalRentFreeBalances,
737
+ }
738
+ }
739
+
740
+ /**
741
+ * Creates instructions required to withdraw SOL directly from a stake pool.
742
+ */
743
+ export async function withdrawSol(
744
+ connection: Connection,
745
+ stakePoolAddress: PublicKey,
746
+ tokenOwner: PublicKey,
747
+ solReceiver: PublicKey,
748
+ amount: number,
749
+ solWithdrawAuthority?: PublicKey,
750
+ ) {
751
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
752
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
753
+ const poolAmount = solToLamports(amount)
754
+
755
+ const poolTokenAccount = getAssociatedTokenAddressSync(
756
+ stakePool.account.data.poolMint,
757
+ tokenOwner,
758
+ )
759
+
760
+ const tokenAccount = await getAccount(connection, poolTokenAccount)
761
+
762
+ // Check withdrawFrom balance
763
+ if (tokenAccount.amount < poolAmount) {
764
+ throw new Error(
765
+ `Not enough token balance to withdraw ${lamportsToSol(poolAmount)} pool tokens.
766
+ Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
767
+ )
768
+ }
769
+
770
+ // Construct transaction to withdraw from withdrawAccounts account list
771
+ const instructions: TransactionInstruction[] = []
772
+ const userTransferAuthority = Keypair.generate()
773
+ const signers: Signer[] = [userTransferAuthority]
774
+
775
+ instructions.push(
776
+ createApproveInstruction(
777
+ poolTokenAccount,
778
+ userTransferAuthority.publicKey,
779
+ tokenOwner,
780
+ poolAmount,
781
+ ),
782
+ )
783
+
784
+ const poolWithdrawAuthority = await findWithdrawAuthorityProgramAddress(
785
+ stakePoolProgramId,
786
+ stakePoolAddress,
787
+ )
788
+
789
+ if (solWithdrawAuthority) {
790
+ const expectedSolWithdrawAuthority
791
+ = stakePool.account.data.solWithdrawAuthority
792
+ if (!expectedSolWithdrawAuthority) {
793
+ throw new Error(
794
+ 'SOL withdraw authority specified in arguments but stake pool has none',
795
+ )
796
+ }
797
+ if (
798
+ solWithdrawAuthority.toBase58() !== expectedSolWithdrawAuthority.toBase58()
799
+ ) {
800
+ throw new Error(
801
+ `Invalid deposit withdraw specified, expected ${expectedSolWithdrawAuthority.toBase58()}, received ${solWithdrawAuthority.toBase58()}`,
802
+ )
803
+ }
804
+ }
805
+
806
+ const withdrawTransaction = StakePoolInstruction.withdrawSol({
807
+ programId: stakePoolProgramId,
808
+ stakePool: stakePoolAddress,
809
+ withdrawAuthority: poolWithdrawAuthority,
810
+ reserveStake: stakePool.account.data.reserveStake,
811
+ sourcePoolAccount: poolTokenAccount,
812
+ sourceTransferAuthority: userTransferAuthority.publicKey,
813
+ destinationSystemAccount: solReceiver,
814
+ managerFeeAccount: stakePool.account.data.managerFeeAccount,
815
+ poolMint: stakePool.account.data.poolMint,
816
+ poolTokens: poolAmount,
817
+ solWithdrawAuthority,
818
+ })
819
+
820
+ instructions.push(withdrawTransaction)
821
+
822
+ return {
823
+ instructions,
824
+ signers,
825
+ }
826
+ }
827
+
828
+ /**
829
+ * Creates instructions required to withdraw wSOL from a stake pool.
830
+ */
831
+ export async function withdrawWsolWithSession(
832
+ connection: Connection,
833
+ stakePoolAddress: PublicKey,
834
+ signerOrSession: PublicKey,
835
+ userPubkey: PublicKey,
836
+ amount: number,
837
+ solWithdrawAuthority?: PublicKey,
838
+ ) {
839
+ const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress)
840
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
841
+ const stakePool = stakePoolAccount.account.data
842
+ const poolTokens = solToLamports(amount)
843
+
844
+ const poolTokenAccount = getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey)
845
+ const tokenAccount = await getAccount(connection, poolTokenAccount)
846
+
847
+ if (tokenAccount.amount < poolTokens) {
848
+ throw new Error(
849
+ `Not enough token balance to withdraw ${amount} pool tokens.
850
+ Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
851
+ )
852
+ }
853
+
854
+ const userWsolAccount = getAssociatedTokenAddressSync(NATIVE_MINT, userPubkey)
855
+
856
+ const instructions: TransactionInstruction[] = []
857
+ const signers: Signer[] = []
858
+
859
+ instructions.push(
860
+ createAssociatedTokenAccountIdempotentInstruction(
861
+ signerOrSession,
862
+ userWsolAccount,
863
+ userPubkey,
864
+ NATIVE_MINT,
865
+ ),
866
+ )
867
+
868
+ const [programSigner] = PublicKey.findProgramAddressSync(
869
+ [Buffer.from('fogo_session_program_signer')],
870
+ stakePoolProgramId,
871
+ )
872
+
873
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
874
+ stakePoolProgramId,
875
+ stakePoolAddress,
876
+ )
877
+
878
+ instructions.push(
879
+ StakePoolInstruction.withdrawWsolWithSession({
880
+ programId: stakePoolProgramId,
881
+ stakePool: stakePoolAddress,
882
+ withdrawAuthority,
883
+ userTransferAuthority: signerOrSession,
884
+ poolTokensFrom: poolTokenAccount,
885
+ reserveStake: stakePool.reserveStake,
886
+ userWsolAccount,
887
+ managerFeeAccount: stakePool.managerFeeAccount,
888
+ poolMint: stakePool.poolMint,
889
+ tokenProgramId: stakePool.tokenProgramId,
890
+ solWithdrawAuthority,
891
+ wsolMint: NATIVE_MINT,
892
+ programSigner,
893
+ poolTokens,
894
+ }),
895
+ )
896
+
897
+ return {
898
+ instructions,
899
+ signers,
900
+ }
901
+ }
902
+
903
+ export async function addValidatorToPool(
904
+ connection: Connection,
905
+ stakePoolAddress: PublicKey,
906
+ validatorVote: PublicKey,
907
+ seed?: number,
908
+ ) {
909
+ const stakePoolAccount = await getStakePoolAccount(
910
+ connection,
911
+ stakePoolAddress,
912
+ )
913
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
914
+ const stakePool = stakePoolAccount.account.data
915
+ const { reserveStake, staker, validatorList } = stakePool
916
+
917
+ const validatorListAccount = await getValidatorListAccount(
918
+ connection,
919
+ validatorList,
920
+ )
921
+
922
+ const validatorInfo = validatorListAccount.account.data.validators.find(
923
+ v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
924
+ )
925
+
926
+ if (validatorInfo) {
927
+ throw new Error('Vote account is already in validator list')
928
+ }
929
+
930
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
931
+ stakePoolProgramId,
932
+ stakePoolAddress,
933
+ )
934
+
935
+ const validatorStake = await findStakeProgramAddress(
936
+ stakePoolProgramId,
937
+ validatorVote,
938
+ stakePoolAddress,
939
+ seed,
940
+ )
941
+
942
+ const instructions: TransactionInstruction[] = [
943
+ StakePoolInstruction.addValidatorToPool({
944
+ programId: stakePoolProgramId,
945
+ stakePool: stakePoolAddress,
946
+ staker,
947
+ reserveStake,
948
+ withdrawAuthority,
949
+ validatorList,
950
+ validatorStake,
951
+ validatorVote,
952
+ }),
953
+ ]
954
+
955
+ return {
956
+ instructions,
957
+ }
958
+ }
959
+
960
+ export async function removeValidatorFromPool(
961
+ connection: Connection,
962
+ stakePoolAddress: PublicKey,
963
+ validatorVote: PublicKey,
964
+ seed?: number,
965
+ ) {
966
+ const stakePoolAccount = await getStakePoolAccount(
967
+ connection,
968
+ stakePoolAddress,
969
+ )
970
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
971
+ const stakePool = stakePoolAccount.account.data
972
+ const { staker, validatorList } = stakePool
973
+
974
+ const validatorListAccount = await getValidatorListAccount(
975
+ connection,
976
+ validatorList,
977
+ )
978
+
979
+ const validatorInfo = validatorListAccount.account.data.validators.find(
980
+ v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
981
+ )
982
+
983
+ if (!validatorInfo) {
984
+ throw new Error('Vote account is not already in validator list')
985
+ }
986
+
987
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
988
+ stakePoolProgramId,
989
+ stakePoolAddress,
990
+ )
991
+
992
+ const validatorStake = await findStakeProgramAddress(
993
+ stakePoolProgramId,
994
+ validatorVote,
995
+ stakePoolAddress,
996
+ seed,
997
+ )
998
+
999
+ const transientStakeSeed = validatorInfo.transientSeedSuffixStart
1000
+
1001
+ const transientStake = await findTransientStakeProgramAddress(
1002
+ stakePoolProgramId,
1003
+ validatorInfo.voteAccountAddress,
1004
+ stakePoolAddress,
1005
+ transientStakeSeed,
1006
+ )
1007
+
1008
+ const instructions: TransactionInstruction[] = [
1009
+ StakePoolInstruction.removeValidatorFromPool({
1010
+ programId: stakePoolProgramId,
1011
+ stakePool: stakePoolAddress,
1012
+ staker,
1013
+ withdrawAuthority,
1014
+ validatorList,
1015
+ validatorStake,
1016
+ transientStake,
1017
+ }),
1018
+ ]
1019
+
1020
+ return {
1021
+ instructions,
1022
+ }
1023
+ }
1024
+
1025
+ /**
1026
+ * Creates instructions required to increase validator stake.
1027
+ */
1028
+ export async function increaseValidatorStake(
1029
+ connection: Connection,
1030
+ stakePoolAddress: PublicKey,
1031
+ validatorVote: PublicKey,
1032
+ lamports: number,
1033
+ ephemeralStakeSeed?: number,
1034
+ ) {
1035
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
1036
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
1037
+
1038
+ const validatorList = await getValidatorListAccount(
1039
+ connection,
1040
+ stakePool.account.data.validatorList,
1041
+ )
1042
+
1043
+ const validatorInfo = validatorList.account.data.validators.find(
1044
+ v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
1045
+ )
1046
+
1047
+ if (!validatorInfo) {
1048
+ throw new Error('Vote account not found in validator list')
1049
+ }
1050
+
1051
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
1052
+ stakePoolProgramId,
1053
+ stakePoolAddress,
1054
+ )
1055
+
1056
+ // Bump transient seed suffix by one to avoid reuse when not using the increaseAdditionalStake instruction
1057
+ const transientStakeSeed
1058
+ = ephemeralStakeSeed === undefined
1059
+ ? validatorInfo.transientSeedSuffixStart.addn(1)
1060
+ : validatorInfo.transientSeedSuffixStart
1061
+
1062
+ const transientStake = await findTransientStakeProgramAddress(
1063
+ stakePoolProgramId,
1064
+ validatorInfo.voteAccountAddress,
1065
+ stakePoolAddress,
1066
+ transientStakeSeed,
1067
+ )
1068
+
1069
+ const validatorStake = await findStakeProgramAddress(
1070
+ stakePoolProgramId,
1071
+ validatorInfo.voteAccountAddress,
1072
+ stakePoolAddress,
1073
+ )
1074
+
1075
+ const instructions: TransactionInstruction[] = []
1076
+
1077
+ if (ephemeralStakeSeed !== undefined) {
1078
+ const ephemeralStake = await findEphemeralStakeProgramAddress(
1079
+ stakePoolProgramId,
1080
+ stakePoolAddress,
1081
+ new BN(ephemeralStakeSeed),
1082
+ )
1083
+ instructions.push(
1084
+ StakePoolInstruction.increaseAdditionalValidatorStake({
1085
+ programId: stakePoolProgramId,
1086
+ stakePool: stakePoolAddress,
1087
+ staker: stakePool.account.data.staker,
1088
+ validatorList: stakePool.account.data.validatorList,
1089
+ reserveStake: stakePool.account.data.reserveStake,
1090
+ transientStakeSeed: transientStakeSeed.toNumber(),
1091
+ withdrawAuthority,
1092
+ transientStake,
1093
+ validatorStake,
1094
+ validatorVote,
1095
+ lamports,
1096
+ ephemeralStake,
1097
+ ephemeralStakeSeed,
1098
+ }),
1099
+ )
1100
+ } else {
1101
+ instructions.push(
1102
+ StakePoolInstruction.increaseValidatorStake({
1103
+ programId: stakePoolProgramId,
1104
+ stakePool: stakePoolAddress,
1105
+ staker: stakePool.account.data.staker,
1106
+ validatorList: stakePool.account.data.validatorList,
1107
+ reserveStake: stakePool.account.data.reserveStake,
1108
+ transientStakeSeed: transientStakeSeed.toNumber(),
1109
+ withdrawAuthority,
1110
+ transientStake,
1111
+ validatorStake,
1112
+ validatorVote,
1113
+ lamports,
1114
+ }),
1115
+ )
1116
+ }
1117
+
1118
+ return {
1119
+ instructions,
1120
+ }
1121
+ }
1122
+
1123
+ /**
1124
+ * Creates instructions required to decrease validator stake.
1125
+ */
1126
+ export async function decreaseValidatorStake(
1127
+ connection: Connection,
1128
+ stakePoolAddress: PublicKey,
1129
+ validatorVote: PublicKey,
1130
+ lamports: number,
1131
+ ephemeralStakeSeed?: number,
1132
+ ) {
1133
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
1134
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
1135
+ const validatorList = await getValidatorListAccount(
1136
+ connection,
1137
+ stakePool.account.data.validatorList,
1138
+ )
1139
+
1140
+ const validatorInfo = validatorList.account.data.validators.find(
1141
+ v => v.voteAccountAddress.toBase58() === validatorVote.toBase58(),
1142
+ )
1143
+
1144
+ if (!validatorInfo) {
1145
+ throw new Error('Vote account not found in validator list')
1146
+ }
1147
+
1148
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
1149
+ stakePoolProgramId,
1150
+ stakePoolAddress,
1151
+ )
1152
+
1153
+ const validatorStake = await findStakeProgramAddress(
1154
+ stakePoolProgramId,
1155
+ validatorInfo.voteAccountAddress,
1156
+ stakePoolAddress,
1157
+ )
1158
+
1159
+ // Bump transient seed suffix by one to avoid reuse when not using the decreaseAdditionalStake instruction
1160
+ const transientStakeSeed
1161
+ = ephemeralStakeSeed === undefined
1162
+ ? validatorInfo.transientSeedSuffixStart.addn(1)
1163
+ : validatorInfo.transientSeedSuffixStart
1164
+
1165
+ const transientStake = await findTransientStakeProgramAddress(
1166
+ stakePoolProgramId,
1167
+ validatorInfo.voteAccountAddress,
1168
+ stakePoolAddress,
1169
+ transientStakeSeed,
1170
+ )
1171
+
1172
+ const instructions: TransactionInstruction[] = []
1173
+
1174
+ if (ephemeralStakeSeed !== undefined) {
1175
+ const ephemeralStake = await findEphemeralStakeProgramAddress(
1176
+ stakePoolProgramId,
1177
+ stakePoolAddress,
1178
+ new BN(ephemeralStakeSeed),
1179
+ )
1180
+ instructions.push(
1181
+ StakePoolInstruction.decreaseAdditionalValidatorStake({
1182
+ programId: stakePoolProgramId,
1183
+ stakePool: stakePoolAddress,
1184
+ staker: stakePool.account.data.staker,
1185
+ validatorList: stakePool.account.data.validatorList,
1186
+ reserveStake: stakePool.account.data.reserveStake,
1187
+ transientStakeSeed: transientStakeSeed.toNumber(),
1188
+ withdrawAuthority,
1189
+ validatorStake,
1190
+ transientStake,
1191
+ lamports,
1192
+ ephemeralStake,
1193
+ ephemeralStakeSeed,
1194
+ }),
1195
+ )
1196
+ } else {
1197
+ instructions.push(
1198
+ StakePoolInstruction.decreaseValidatorStakeWithReserve({
1199
+ programId: stakePoolProgramId,
1200
+ stakePool: stakePoolAddress,
1201
+ staker: stakePool.account.data.staker,
1202
+ validatorList: stakePool.account.data.validatorList,
1203
+ reserveStake: stakePool.account.data.reserveStake,
1204
+ transientStakeSeed: transientStakeSeed.toNumber(),
1205
+ withdrawAuthority,
1206
+ validatorStake,
1207
+ transientStake,
1208
+ lamports,
1209
+ }),
1210
+ )
1211
+ }
1212
+
1213
+ return {
1214
+ instructions,
1215
+ }
1216
+ }
1217
+
1218
+ /**
1219
+ * Creates instructions required to completely update a stake pool after epoch change.
1220
+ */
1221
+ export async function updateStakePool(
1222
+ connection: Connection,
1223
+ stakePool: StakePoolAccount,
1224
+ noMerge = false,
1225
+ ) {
1226
+ const stakePoolAddress = stakePool.pubkey
1227
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
1228
+
1229
+ const validatorList = await getValidatorListAccount(
1230
+ connection,
1231
+ stakePool.account.data.validatorList,
1232
+ )
1233
+
1234
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
1235
+ stakePoolProgramId,
1236
+ stakePoolAddress,
1237
+ )
1238
+
1239
+ const updateListInstructions: TransactionInstruction[] = []
1240
+ const instructions: TransactionInstruction[] = []
1241
+
1242
+ let startIndex = 0
1243
+ const validatorChunks: Array<ValidatorStakeInfo[]> = arrayChunk(
1244
+ validatorList.account.data.validators,
1245
+ MAX_VALIDATORS_TO_UPDATE,
1246
+ )
1247
+
1248
+ for (const validatorChunk of validatorChunks) {
1249
+ const validatorAndTransientStakePairs: PublicKey[] = []
1250
+
1251
+ for (const validator of validatorChunk) {
1252
+ const validatorStake = await findStakeProgramAddress(
1253
+ stakePoolProgramId,
1254
+ validator.voteAccountAddress,
1255
+ stakePoolAddress,
1256
+ )
1257
+ validatorAndTransientStakePairs.push(validatorStake)
1258
+
1259
+ const transientStake = await findTransientStakeProgramAddress(
1260
+ stakePoolProgramId,
1261
+ validator.voteAccountAddress,
1262
+ stakePoolAddress,
1263
+ validator.transientSeedSuffixStart,
1264
+ )
1265
+ validatorAndTransientStakePairs.push(transientStake)
1266
+ }
1267
+
1268
+ updateListInstructions.push(
1269
+ StakePoolInstruction.updateValidatorListBalance({
1270
+ programId: stakePoolProgramId,
1271
+ stakePool: stakePoolAddress,
1272
+ validatorList: stakePool.account.data.validatorList,
1273
+ reserveStake: stakePool.account.data.reserveStake,
1274
+ validatorAndTransientStakePairs,
1275
+ withdrawAuthority,
1276
+ startIndex,
1277
+ noMerge,
1278
+ }),
1279
+ )
1280
+ startIndex += MAX_VALIDATORS_TO_UPDATE
1281
+ }
1282
+
1283
+ instructions.push(
1284
+ StakePoolInstruction.updateStakePoolBalance({
1285
+ programId: stakePoolProgramId,
1286
+ stakePool: stakePoolAddress,
1287
+ validatorList: stakePool.account.data.validatorList,
1288
+ reserveStake: stakePool.account.data.reserveStake,
1289
+ managerFeeAccount: stakePool.account.data.managerFeeAccount,
1290
+ poolMint: stakePool.account.data.poolMint,
1291
+ withdrawAuthority,
1292
+ }),
1293
+ )
1294
+
1295
+ instructions.push(
1296
+ StakePoolInstruction.cleanupRemovedValidatorEntries({
1297
+ programId: stakePoolProgramId,
1298
+ stakePool: stakePoolAddress,
1299
+ validatorList: stakePool.account.data.validatorList,
1300
+ }),
1301
+ )
1302
+
1303
+ return {
1304
+ updateListInstructions,
1305
+ finalInstructions: instructions,
1306
+ }
1307
+ }
1308
+
1309
+ /**
1310
+ * Retrieves detailed information about the StakePool.
1311
+ */
1312
+ export async function stakePoolInfo(
1313
+ connection: Connection,
1314
+ stakePoolAddress: PublicKey,
1315
+ ) {
1316
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
1317
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
1318
+ const reserveAccountStakeAddress = stakePool.account.data.reserveStake
1319
+ const totalLamports = stakePool.account.data.totalLamports
1320
+ const lastUpdateEpoch = stakePool.account.data.lastUpdateEpoch
1321
+
1322
+ const validatorList = await getValidatorListAccount(
1323
+ connection,
1324
+ stakePool.account.data.validatorList,
1325
+ )
1326
+
1327
+ const maxNumberOfValidators = validatorList.account.data.maxValidators
1328
+ const currentNumberOfValidators
1329
+ = validatorList.account.data.validators.length
1330
+
1331
+ const epochInfo = await connection.getEpochInfo()
1332
+ const reserveStake = await connection.getAccountInfo(
1333
+ reserveAccountStakeAddress,
1334
+ )
1335
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
1336
+ stakePoolProgramId,
1337
+ stakePoolAddress,
1338
+ )
1339
+
1340
+ const minimumReserveStakeBalance
1341
+ = await connection.getMinimumBalanceForRentExemption(StakeProgram.space)
1342
+
1343
+ const stakeAccounts = await Promise.all(
1344
+ validatorList.account.data.validators.map(async (validator) => {
1345
+ const stakeAccountAddress = await findStakeProgramAddress(
1346
+ stakePoolProgramId,
1347
+ validator.voteAccountAddress,
1348
+ stakePoolAddress,
1349
+ )
1350
+ const transientStakeAccountAddress
1351
+ = await findTransientStakeProgramAddress(
1352
+ stakePoolProgramId,
1353
+ validator.voteAccountAddress,
1354
+ stakePoolAddress,
1355
+ validator.transientSeedSuffixStart,
1356
+ )
1357
+ const updateRequired = !validator.lastUpdateEpoch.eqn(epochInfo.epoch)
1358
+ return {
1359
+ voteAccountAddress: validator.voteAccountAddress.toBase58(),
1360
+ stakeAccountAddress: stakeAccountAddress.toBase58(),
1361
+ validatorActiveStakeLamports: validator.activeStakeLamports.toString(),
1362
+ validatorLastUpdateEpoch: validator.lastUpdateEpoch.toString(),
1363
+ validatorLamports: validator.activeStakeLamports
1364
+ .add(validator.transientStakeLamports)
1365
+ .toString(),
1366
+ validatorTransientStakeAccountAddress:
1367
+ transientStakeAccountAddress.toBase58(),
1368
+ validatorTransientStakeLamports:
1369
+ validator.transientStakeLamports.toString(),
1370
+ updateRequired,
1371
+ }
1372
+ }),
1373
+ )
1374
+
1375
+ const totalPoolTokens = lamportsToSol(stakePool.account.data.poolTokenSupply)
1376
+ const updateRequired = !lastUpdateEpoch.eqn(epochInfo.epoch)
1377
+
1378
+ return {
1379
+ address: stakePoolAddress.toBase58(),
1380
+ poolWithdrawAuthority: withdrawAuthority.toBase58(),
1381
+ manager: stakePool.account.data.manager.toBase58(),
1382
+ staker: stakePool.account.data.staker.toBase58(),
1383
+ stakeDepositAuthority:
1384
+ stakePool.account.data.stakeDepositAuthority.toBase58(),
1385
+ stakeWithdrawBumpSeed: stakePool.account.data.stakeWithdrawBumpSeed,
1386
+ maxValidators: maxNumberOfValidators,
1387
+ validatorList: validatorList.account.data.validators.map((validator) => {
1388
+ return {
1389
+ activeStakeLamports: validator.activeStakeLamports.toString(),
1390
+ transientStakeLamports: validator.transientStakeLamports.toString(),
1391
+ lastUpdateEpoch: validator.lastUpdateEpoch.toString(),
1392
+ transientSeedSuffixStart: validator.transientSeedSuffixStart.toString(),
1393
+ transientSeedSuffixEnd: validator.transientSeedSuffixEnd.toString(),
1394
+ status: validator.status.toString(),
1395
+ voteAccountAddress: validator.voteAccountAddress.toString(),
1396
+ }
1397
+ }), // CliStakePoolValidator
1398
+ validatorListStorageAccount:
1399
+ stakePool.account.data.validatorList.toBase58(),
1400
+ reserveStake: stakePool.account.data.reserveStake.toBase58(),
1401
+ poolMint: stakePool.account.data.poolMint.toBase58(),
1402
+ managerFeeAccount: stakePool.account.data.managerFeeAccount.toBase58(),
1403
+ tokenProgramId: stakePool.account.data.tokenProgramId.toBase58(),
1404
+ totalLamports: stakePool.account.data.totalLamports.toString(),
1405
+ poolTokenSupply: stakePool.account.data.poolTokenSupply.toString(),
1406
+ lastUpdateEpoch: stakePool.account.data.lastUpdateEpoch.toString(),
1407
+ lockup: stakePool.account.data.lockup, // pub lockup: CliStakePoolLockup
1408
+ epochFee: stakePool.account.data.epochFee,
1409
+ nextEpochFee: stakePool.account.data.nextEpochFee,
1410
+ preferredDepositValidatorVoteAddress:
1411
+ stakePool.account.data.preferredDepositValidatorVoteAddress,
1412
+ preferredWithdrawValidatorVoteAddress:
1413
+ stakePool.account.data.preferredWithdrawValidatorVoteAddress,
1414
+ stakeDepositFee: stakePool.account.data.stakeDepositFee,
1415
+ stakeWithdrawalFee: stakePool.account.data.stakeWithdrawalFee,
1416
+ // CliStakePool the same
1417
+ nextStakeWithdrawalFee: stakePool.account.data.nextStakeWithdrawalFee,
1418
+ stakeReferralFee: stakePool.account.data.stakeReferralFee,
1419
+ solDepositAuthority: stakePool.account.data.solDepositAuthority?.toBase58(),
1420
+ solDepositFee: stakePool.account.data.solDepositFee,
1421
+ solReferralFee: stakePool.account.data.solReferralFee,
1422
+ solWithdrawAuthority:
1423
+ stakePool.account.data.solWithdrawAuthority?.toBase58(),
1424
+ solWithdrawalFee: stakePool.account.data.solWithdrawalFee,
1425
+ nextSolWithdrawalFee: stakePool.account.data.nextSolWithdrawalFee,
1426
+ lastEpochPoolTokenSupply:
1427
+ stakePool.account.data.lastEpochPoolTokenSupply.toString(),
1428
+ lastEpochTotalLamports:
1429
+ stakePool.account.data.lastEpochTotalLamports.toString(),
1430
+ details: {
1431
+ reserveStakeLamports: reserveStake?.lamports,
1432
+ reserveAccountStakeAddress: reserveAccountStakeAddress.toBase58(),
1433
+ minimumReserveStakeBalance,
1434
+ stakeAccounts,
1435
+ totalLamports,
1436
+ totalPoolTokens,
1437
+ currentNumberOfValidators,
1438
+ maxNumberOfValidators,
1439
+ updateRequired,
1440
+ }, // CliStakePoolDetails
1441
+ }
1442
+ }
1443
+
1444
+ /**
1445
+ * Creates instructions required to create pool token metadata.
1446
+ */
1447
+ export async function createPoolTokenMetadata(
1448
+ connection: Connection,
1449
+ stakePoolAddress: PublicKey,
1450
+ payer: PublicKey,
1451
+ name: string,
1452
+ symbol: string,
1453
+ uri: string,
1454
+ ) {
1455
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
1456
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
1457
+
1458
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
1459
+ stakePoolProgramId,
1460
+ stakePoolAddress,
1461
+ )
1462
+ const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint)
1463
+ const manager = stakePool.account.data.manager
1464
+
1465
+ const instructions: TransactionInstruction[] = []
1466
+ instructions.push(
1467
+ StakePoolInstruction.createTokenMetadata({
1468
+ programId: stakePoolProgramId,
1469
+ stakePool: stakePoolAddress,
1470
+ poolMint: stakePool.account.data.poolMint,
1471
+ payer,
1472
+ manager,
1473
+ tokenMetadata,
1474
+ withdrawAuthority,
1475
+ name,
1476
+ symbol,
1477
+ uri,
1478
+ }),
1479
+ )
1480
+
1481
+ return {
1482
+ instructions,
1483
+ }
1484
+ }
1485
+
1486
+ /**
1487
+ * Creates instructions required to update pool token metadata.
1488
+ */
1489
+ export async function updatePoolTokenMetadata(
1490
+ connection: Connection,
1491
+ stakePoolAddress: PublicKey,
1492
+ name: string,
1493
+ symbol: string,
1494
+ uri: string,
1495
+ ) {
1496
+ const stakePool = await getStakePoolAccount(connection, stakePoolAddress)
1497
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
1498
+
1499
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
1500
+ stakePoolProgramId,
1501
+ stakePoolAddress,
1502
+ )
1503
+
1504
+ const tokenMetadata = findMetadataAddress(stakePool.account.data.poolMint)
1505
+
1506
+ const instructions: TransactionInstruction[] = []
1507
+ instructions.push(
1508
+ StakePoolInstruction.updateTokenMetadata({
1509
+ programId: stakePoolProgramId,
1510
+ stakePool: stakePoolAddress,
1511
+ manager: stakePool.account.data.manager,
1512
+ tokenMetadata,
1513
+ withdrawAuthority,
1514
+ name,
1515
+ symbol,
1516
+ uri,
1517
+ }),
1518
+ )
1519
+
1520
+ return {
1521
+ instructions,
1522
+ }
1523
+ }