@ignitionfi/spl-stake-pool 1.1.21 → 1.1.23

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.
@@ -169,6 +169,7 @@ export type WithdrawStakeWithSessionParams = {
169
169
  validatorList: PublicKey;
170
170
  withdrawAuthority: PublicKey;
171
171
  stakeToSplit: PublicKey;
172
+ /** The stake account PDA that will receive the withdrawn stake (derived from user wallet + seed) */
172
173
  stakeToReceive: PublicKey;
173
174
  /** The session signer (user or session) - used as both stake authority and transfer authority */
174
175
  sessionSigner: PublicKey;
@@ -178,8 +179,12 @@ export type WithdrawStakeWithSessionParams = {
178
179
  tokenProgramId: PublicKey;
179
180
  /** The program signer PDA derived from PROGRAM_SIGNER_SEED */
180
181
  programSigner: PublicKey;
182
+ /** Payer for the stake account rent (paymaster payer) */
183
+ payer: PublicKey;
181
184
  poolTokensIn: number;
182
185
  minimumLamportsOut: number;
186
+ /** Seed used to derive the user stake PDA */
187
+ userStakeSeed: number;
183
188
  };
184
189
  /**
185
190
  * Deposit SOL directly into the pool's reserve account. The output is a "pool" token
@@ -24,3 +24,8 @@ export declare function findEphemeralStakeProgramAddress(programId: PublicKey, s
24
24
  * Generates the metadata program address for the stake pool
25
25
  */
26
26
  export declare function findMetadataAddress(stakePoolMintAddress: PublicKey): PublicKey;
27
+ /**
28
+ * Generates the user stake account PDA for session-based withdrawals.
29
+ * The PDA is derived from the user's wallet and a unique seed.
30
+ */
31
+ export declare function findUserStakeProgramAddress(programId: PublicKey, userWallet: PublicKey, seed: BN | number): PublicKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ignitionfi/spl-stake-pool",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "Ignition Stake Pool SDK for FOGO",
5
5
  "contributors": [
6
6
  "Anza Maintainers <maintainers@anza.xyz>",
package/src/constants.ts CHANGED
@@ -24,6 +24,9 @@ export const EPHEMERAL_STAKE_SEED_PREFIX = Buffer.from('ephemeral')
24
24
  // Seed used to derive transient stake accounts.
25
25
  export const TRANSIENT_STAKE_SEED_PREFIX = Buffer.from('transient')
26
26
 
27
+ // Seed for user stake account created during session withdrawal
28
+ export const USER_STAKE_SEED_PREFIX = Buffer.from('user_stake')
29
+
27
30
  // Minimum amount of staked SOL required in a validator stake account to allow
28
31
  // for merges without a mismatch on credits observed
29
32
  export const MINIMUM_ACTIVE_STAKE = LAMPORTS_PER_SOL
package/src/index.ts CHANGED
@@ -40,6 +40,7 @@ import {
40
40
  findMetadataAddress,
41
41
  findStakeProgramAddress,
42
42
  findTransientStakeProgramAddress,
43
+ findUserStakeProgramAddress,
43
44
  findWithdrawAuthorityProgramAddress,
44
45
  findWsolTransientProgramAddress,
45
46
  getValidatorListAccount,
@@ -66,6 +67,14 @@ export {
66
67
  ValidatorListLayout,
67
68
  ValidatorStakeInfoLayout,
68
69
  } from './layouts'
70
+ export {
71
+ findUserStakeProgramAddress,
72
+ findWithdrawAuthorityProgramAddress,
73
+ findStakeProgramAddress,
74
+ findTransientStakeProgramAddress,
75
+ findEphemeralStakeProgramAddress,
76
+ findWsolTransientProgramAddress,
77
+ } from './utils'
69
78
 
70
79
  export interface ValidatorListAccount {
71
80
  pubkey: PublicKey
@@ -891,19 +900,63 @@ export async function withdrawWsolWithSession(
891
900
  }
892
901
 
893
902
  /**
894
- * Creates instructions required to withdraw stake from a stake pool using a Fogo session.
895
- * The withdrawn stake account will be authorized to the user's wallet.
903
+ * Finds the next available seed for creating a user stake PDA.
904
+ * Scans from startSeed until an unused PDA is found.
905
+ *
906
+ * @param connection - Solana connection
907
+ * @param programId - The stake pool program ID
908
+ * @param userPubkey - User's wallet (used for PDA derivation)
909
+ * @param startSeed - Starting seed to search from (default: 0)
910
+ * @param maxSeed - Maximum seed to check before giving up (default: 1000)
911
+ * @returns The next available seed
912
+ * @throws Error if no available seed found within maxSeed
913
+ */
914
+ export async function findNextUserStakeSeed(
915
+ connection: Connection,
916
+ programId: PublicKey,
917
+ userPubkey: PublicKey,
918
+ startSeed: number = 0,
919
+ maxSeed: number = 1000,
920
+ ): Promise<number> {
921
+ for (let seed = startSeed; seed < startSeed + maxSeed; seed++) {
922
+ const pda = findUserStakeProgramAddress(programId, userPubkey, seed)
923
+ const account = await connection.getAccountInfo(pda)
924
+ if (!account) {
925
+ return seed
926
+ }
927
+ }
928
+ throw new Error(`No available user stake seed found between ${startSeed} and ${startSeed + maxSeed - 1}`)
929
+ }
930
+
931
+ /**
932
+ * Withdraws stake from a stake pool using a Fogo session.
933
+ *
934
+ * The on-chain program creates stake account PDAs, so no pre-creation step is needed.
935
+ * The paymaster payer provides rent for the stake account PDAs.
936
+ *
937
+ * @param connection - Solana connection
938
+ * @param stakePoolAddress - The stake pool to withdraw from
939
+ * @param signerOrSession - The session signer public key
940
+ * @param userPubkey - User's wallet (used for PDA derivation and token ownership)
941
+ * @param payer - Payer for stake account rent (paymaster payer in session context)
942
+ * @param amount - Amount of pool tokens to withdraw
943
+ * @param userStakeSeedStart - Starting seed for user stake PDA derivation (default: 0)
944
+ * @param useReserve - Whether to withdraw from reserve (default: false)
945
+ * @param voteAccountAddress - Optional specific validator to withdraw from
946
+ * @param minimumLamportsOut - Minimum lamports to receive (slippage protection)
947
+ * @param validatorComparator - Optional comparator for validator selection
896
948
  */
897
949
  export async function withdrawStakeWithSession(
898
950
  connection: Connection,
899
951
  stakePoolAddress: PublicKey,
900
952
  signerOrSession: PublicKey,
901
953
  userPubkey: PublicKey,
954
+ payer: PublicKey,
902
955
  amount: number,
956
+ userStakeSeedStart: number = 0,
903
957
  useReserve = false,
904
958
  voteAccountAddress?: PublicKey,
905
959
  minimumLamportsOut: number = 0,
906
- payer?: PublicKey,
907
960
  validatorComparator?: (_a: ValidatorAccount, _b: ValidatorAccount) => number,
908
961
  ) {
909
962
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress)
@@ -991,8 +1044,8 @@ export async function withdrawStakeWithSession(
991
1044
  }
992
1045
 
993
1046
  const instructions: TransactionInstruction[] = []
994
- const signers: Signer[] = []
995
1047
  const stakeAccountPubkeys: PublicKey[] = []
1048
+ const userStakeSeeds: number[] = []
996
1049
 
997
1050
  // Max 5 accounts to prevent an error: "Transaction too large"
998
1051
  const maxWithdrawAccounts = 5
@@ -1003,23 +1056,18 @@ export async function withdrawStakeWithSession(
1003
1056
  break
1004
1057
  }
1005
1058
 
1006
- // Create a new stake account to receive the withdrawal
1007
- const stakeReceiver = Keypair.generate()
1008
- signers.push(stakeReceiver)
1009
- stakeAccountPubkeys.push(stakeReceiver.publicKey)
1010
-
1011
- // Create the stake account that will receive the split stake
1012
- instructions.push(
1013
- SystemProgram.createAccount({
1014
- fromPubkey: payer ?? userPubkey,
1015
- newAccountPubkey: stakeReceiver.publicKey,
1016
- lamports: stakeAccountRentExemption,
1017
- space: StakeProgram.space,
1018
- programId: StakeProgram.programId,
1019
- }),
1059
+ // Derive the stake account PDA for this withdrawal
1060
+ const userStakeSeed = userStakeSeedStart + i
1061
+ const stakeReceiverPubkey = findUserStakeProgramAddress(
1062
+ stakePoolProgramId,
1063
+ userPubkey,
1064
+ userStakeSeed,
1020
1065
  )
1021
1066
 
1022
- // Add the withdraw stake with session instruction
1067
+ stakeAccountPubkeys.push(stakeReceiverPubkey)
1068
+ userStakeSeeds.push(userStakeSeed)
1069
+
1070
+ // The on-chain program will create the stake account PDA
1023
1071
  instructions.push(
1024
1072
  StakePoolInstruction.withdrawStakeWithSession({
1025
1073
  programId: stakePoolProgramId,
@@ -1027,15 +1075,17 @@ export async function withdrawStakeWithSession(
1027
1075
  validatorList: stakePool.validatorList,
1028
1076
  withdrawAuthority,
1029
1077
  stakeToSplit: withdrawAccount.stakeAddress,
1030
- stakeToReceive: stakeReceiver.publicKey,
1078
+ stakeToReceive: stakeReceiverPubkey,
1031
1079
  sessionSigner: signerOrSession,
1032
1080
  burnFromPool: poolTokenAccount,
1033
1081
  managerFeeAccount: stakePool.managerFeeAccount,
1034
1082
  poolMint: stakePool.poolMint,
1035
1083
  tokenProgramId: stakePool.tokenProgramId,
1036
1084
  programSigner,
1085
+ payer,
1037
1086
  poolTokensIn: withdrawAccount.poolAmount.toNumber(),
1038
- minimumLamportsOut: solToLamports(minimumLamportsOut),
1087
+ minimumLamportsOut,
1088
+ userStakeSeed,
1039
1089
  }),
1040
1090
  )
1041
1091
  i++
@@ -1043,8 +1093,8 @@ export async function withdrawStakeWithSession(
1043
1093
 
1044
1094
  return {
1045
1095
  instructions,
1046
- signers,
1047
1096
  stakeAccountPubkeys,
1097
+ userStakeSeeds,
1048
1098
  }
1049
1099
  }
1050
1100
 
@@ -234,6 +234,7 @@ export const STAKE_POOL_INSTRUCTION_LAYOUTS: {
234
234
  BufferLayout.u8('instruction'),
235
235
  BufferLayout.ns64('poolTokensIn'),
236
236
  BufferLayout.ns64('minimumLamportsOut'),
237
+ BufferLayout.ns64('userStakeSeed'),
237
238
  ]),
238
239
  },
239
240
  })
@@ -407,6 +408,7 @@ export type WithdrawStakeWithSessionParams = {
407
408
  validatorList: PublicKey
408
409
  withdrawAuthority: PublicKey
409
410
  stakeToSplit: PublicKey
411
+ /** The stake account PDA that will receive the withdrawn stake (derived from user wallet + seed) */
410
412
  stakeToReceive: PublicKey
411
413
  /** The session signer (user or session) - used as both stake authority and transfer authority */
412
414
  sessionSigner: PublicKey
@@ -416,8 +418,12 @@ export type WithdrawStakeWithSessionParams = {
416
418
  tokenProgramId: PublicKey
417
419
  /** The program signer PDA derived from PROGRAM_SIGNER_SEED */
418
420
  programSigner: PublicKey
421
+ /** Payer for the stake account rent (paymaster payer) */
422
+ payer: PublicKey
419
423
  poolTokensIn: number
420
424
  minimumLamportsOut: number
425
+ /** Seed used to derive the user stake PDA */
426
+ userStakeSeed: number
421
427
  }
422
428
 
423
429
  /**
@@ -1200,6 +1206,7 @@ export class StakePoolInstruction {
1200
1206
  const data = encodeData(type, {
1201
1207
  poolTokensIn: params.poolTokensIn,
1202
1208
  minimumLamportsOut: params.minimumLamportsOut,
1209
+ userStakeSeed: params.userStakeSeed,
1203
1210
  })
1204
1211
 
1205
1212
  const keys = [
@@ -1217,6 +1224,8 @@ export class StakePoolInstruction {
1217
1224
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1218
1225
  { pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
1219
1226
  { pubkey: params.programSigner, isSigner: false, isWritable: false },
1227
+ { pubkey: params.payer, isSigner: true, isWritable: true },
1228
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1220
1229
  ]
1221
1230
 
1222
1231
  return new TransactionInstruction({
@@ -5,6 +5,7 @@ import {
5
5
  EPHEMERAL_STAKE_SEED_PREFIX,
6
6
  METADATA_PROGRAM_ID,
7
7
  TRANSIENT_STAKE_SEED_PREFIX,
8
+ USER_STAKE_SEED_PREFIX,
8
9
  } from '../constants'
9
10
 
10
11
  /**
@@ -101,3 +102,24 @@ export function findMetadataAddress(stakePoolMintAddress: PublicKey) {
101
102
  )
102
103
  return publicKey
103
104
  }
105
+
106
+ /**
107
+ * Generates the user stake account PDA for session-based withdrawals.
108
+ * The PDA is derived from the user's wallet and a unique seed.
109
+ */
110
+ export function findUserStakeProgramAddress(
111
+ programId: PublicKey,
112
+ userWallet: PublicKey,
113
+ seed: BN | number,
114
+ ) {
115
+ const seedBN = typeof seed === 'number' ? new BN(seed) : seed
116
+ const [publicKey] = PublicKey.findProgramAddressSync(
117
+ [
118
+ USER_STAKE_SEED_PREFIX,
119
+ userWallet.toBuffer(),
120
+ seedBN.toArrayLike(Buffer, 'le', 8),
121
+ ],
122
+ programId,
123
+ )
124
+ return publicKey
125
+ }