@ignitionfi/spl-stake-pool 1.1.19 → 1.1.21

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.
@@ -4,7 +4,7 @@ import { InstructionType } from './utils';
4
4
  /**
5
5
  * An enumeration of valid StakePoolInstructionType's
6
6
  */
7
- export type StakePoolInstructionType = 'IncreaseValidatorStake' | 'DecreaseValidatorStake' | 'UpdateValidatorListBalance' | 'UpdateStakePoolBalance' | 'CleanupRemovedValidatorEntries' | 'DepositStake' | 'DepositSol' | 'WithdrawStake' | 'WithdrawSol' | 'IncreaseAdditionalValidatorStake' | 'DecreaseAdditionalValidatorStake' | 'DecreaseValidatorStakeWithReserve' | 'Redelegate' | 'AddValidatorToPool' | 'RemoveValidatorFromPool' | 'DepositWsolWithSession' | 'WithdrawWsolWithSession';
7
+ export type StakePoolInstructionType = 'IncreaseValidatorStake' | 'DecreaseValidatorStake' | 'UpdateValidatorListBalance' | 'UpdateStakePoolBalance' | 'CleanupRemovedValidatorEntries' | 'DepositStake' | 'DepositSol' | 'WithdrawStake' | 'WithdrawSol' | 'IncreaseAdditionalValidatorStake' | 'DecreaseAdditionalValidatorStake' | 'DecreaseValidatorStakeWithReserve' | 'Redelegate' | 'AddValidatorToPool' | 'RemoveValidatorFromPool' | 'DepositWsolWithSession' | 'WithdrawWsolWithSession' | 'WithdrawStakeWithSession';
8
8
  export declare function tokenMetadataLayout(instruction: number, nameLength: number, symbolLength: number, uriLength: number): {
9
9
  index: number;
10
10
  layout: BufferLayout.Structure<any>;
@@ -155,9 +155,29 @@ export type WithdrawWsolWithSessionParams = {
155
155
  managerFeeAccount: PublicKey;
156
156
  poolMint: PublicKey;
157
157
  tokenProgramId: PublicKey;
158
- solWithdrawAuthority?: PublicKey;
159
158
  wsolMint: PublicKey;
160
159
  programSigner: PublicKey;
160
+ userWallet: PublicKey;
161
+ poolTokensIn: number;
162
+ minimumLamportsOut: number;
163
+ payer?: PublicKey;
164
+ solWithdrawAuthority?: PublicKey;
165
+ };
166
+ export type WithdrawStakeWithSessionParams = {
167
+ programId: PublicKey;
168
+ stakePool: PublicKey;
169
+ validatorList: PublicKey;
170
+ withdrawAuthority: PublicKey;
171
+ stakeToSplit: PublicKey;
172
+ stakeToReceive: PublicKey;
173
+ /** The session signer (user or session) - used as both stake authority and transfer authority */
174
+ sessionSigner: PublicKey;
175
+ burnFromPool: PublicKey;
176
+ managerFeeAccount: PublicKey;
177
+ poolMint: PublicKey;
178
+ tokenProgramId: PublicKey;
179
+ /** The program signer PDA derived from PROGRAM_SIGNER_SEED */
180
+ programSigner: PublicKey;
161
181
  poolTokensIn: number;
162
182
  minimumLamportsOut: number;
163
183
  };
@@ -304,6 +324,10 @@ export declare class StakePoolInstruction {
304
324
  * Creates a transaction instruction to withdraw WSOL from a stake pool using a session.
305
325
  */
306
326
  static withdrawWsolWithSession(params: WithdrawWsolWithSessionParams): TransactionInstruction;
327
+ /**
328
+ * Creates a transaction instruction to withdraw stake from a stake pool using a Fogo session.
329
+ */
330
+ static withdrawStakeWithSession(params: WithdrawStakeWithSessionParams): TransactionInstruction;
307
331
  /**
308
332
  * Creates an instruction to create metadata
309
333
  * using the mpl token metadata program for the pool token
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ignitionfi/spl-stake-pool",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "description": "Ignition Stake Pool SDK for FOGO",
5
5
  "contributors": [
6
6
  "Anza Maintainers <maintainers@anza.xyz>",
package/src/index.ts CHANGED
@@ -827,6 +827,7 @@ export async function withdrawWsolWithSession(
827
827
  amount: number,
828
828
  minimumLamportsOut: number = 0,
829
829
  solWithdrawAuthority?: PublicKey,
830
+ payer?: PublicKey,
830
831
  ) {
831
832
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress)
832
833
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
@@ -848,14 +849,8 @@ export async function withdrawWsolWithSession(
848
849
  const instructions: TransactionInstruction[] = []
849
850
  const signers: Signer[] = []
850
851
 
851
- instructions.push(
852
- createAssociatedTokenAccountIdempotentInstruction(
853
- signerOrSession,
854
- userWsolAccount,
855
- userPubkey,
856
- NATIVE_MINT,
857
- ),
858
- )
852
+ // The program handles wSOL ATA creation internally
853
+ // This prevents rent drain attacks where paymaster pays for ATA and user reclaims rent
859
854
 
860
855
  const [programSigner] = PublicKey.findProgramAddressSync(
861
856
  [Buffer.from('fogo_session_program_signer')],
@@ -882,6 +877,8 @@ export async function withdrawWsolWithSession(
882
877
  solWithdrawAuthority,
883
878
  wsolMint: NATIVE_MINT,
884
879
  programSigner,
880
+ userWallet: userPubkey,
881
+ payer,
885
882
  poolTokensIn: poolTokens,
886
883
  minimumLamportsOut,
887
884
  }),
@@ -893,6 +890,164 @@ export async function withdrawWsolWithSession(
893
890
  }
894
891
  }
895
892
 
893
+ /**
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.
896
+ */
897
+ export async function withdrawStakeWithSession(
898
+ connection: Connection,
899
+ stakePoolAddress: PublicKey,
900
+ signerOrSession: PublicKey,
901
+ userPubkey: PublicKey,
902
+ amount: number,
903
+ useReserve = false,
904
+ voteAccountAddress?: PublicKey,
905
+ minimumLamportsOut: number = 0,
906
+ payer?: PublicKey,
907
+ validatorComparator?: (_a: ValidatorAccount, _b: ValidatorAccount) => number,
908
+ ) {
909
+ const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress)
910
+ const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint)
911
+ const stakePool = stakePoolAccount.account.data
912
+ const poolTokens = solToLamports(amount)
913
+ const poolAmount = new BN(poolTokens)
914
+
915
+ const poolTokenAccount = getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey)
916
+ const tokenAccount = await getAccount(connection, poolTokenAccount)
917
+
918
+ if (tokenAccount.amount < poolTokens) {
919
+ throw new Error(
920
+ `Not enough token balance to withdraw ${amount} pool tokens.
921
+ Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`,
922
+ )
923
+ }
924
+
925
+ const [programSigner] = PublicKey.findProgramAddressSync(
926
+ [Buffer.from('fogo_session_program_signer')],
927
+ stakePoolProgramId,
928
+ )
929
+
930
+ const withdrawAuthority = await findWithdrawAuthorityProgramAddress(
931
+ stakePoolProgramId,
932
+ stakePoolAddress,
933
+ )
934
+
935
+ const stakeAccountRentExemption = await connection.getMinimumBalanceForRentExemption(StakeProgram.space)
936
+
937
+ // Determine which stake accounts to withdraw from
938
+ const withdrawAccounts: WithdrawAccount[] = []
939
+
940
+ if (useReserve) {
941
+ withdrawAccounts.push({
942
+ stakeAddress: stakePool.reserveStake,
943
+ voteAddress: undefined,
944
+ poolAmount,
945
+ })
946
+ } else if (voteAccountAddress) {
947
+ const stakeAccountAddress = await findStakeProgramAddress(
948
+ stakePoolProgramId,
949
+ voteAccountAddress,
950
+ stakePoolAddress,
951
+ )
952
+ const stakeAccount = await connection.getAccountInfo(stakeAccountAddress)
953
+ if (!stakeAccount) {
954
+ throw new Error(`Validator stake account not found for vote address ${voteAccountAddress.toBase58()}`)
955
+ }
956
+
957
+ const availableLamports = new BN(
958
+ stakeAccount.lamports - MINIMUM_ACTIVE_STAKE - stakeAccountRentExemption,
959
+ )
960
+ if (availableLamports.lt(new BN(0))) {
961
+ throw new Error('Invalid Stake Account')
962
+ }
963
+ const availableForWithdrawal = calcLamportsWithdrawAmount(
964
+ stakePool,
965
+ availableLamports,
966
+ )
967
+
968
+ if (availableForWithdrawal.lt(poolAmount)) {
969
+ throw new Error(
970
+ `Not enough lamports available for withdrawal from ${stakeAccountAddress},
971
+ ${poolAmount} asked, ${availableForWithdrawal} available.`,
972
+ )
973
+ }
974
+ withdrawAccounts.push({
975
+ stakeAddress: stakeAccountAddress,
976
+ voteAddress: voteAccountAddress,
977
+ poolAmount,
978
+ })
979
+ } else {
980
+ // Get the list of accounts to withdraw from automatically
981
+ withdrawAccounts.push(
982
+ ...(await prepareWithdrawAccounts(
983
+ connection,
984
+ stakePool,
985
+ stakePoolAddress,
986
+ poolAmount,
987
+ validatorComparator,
988
+ poolTokenAccount.equals(stakePool.managerFeeAccount),
989
+ )),
990
+ )
991
+ }
992
+
993
+ const instructions: TransactionInstruction[] = []
994
+ const signers: Signer[] = []
995
+ const stakeAccountPubkeys: PublicKey[] = []
996
+
997
+ // Max 5 accounts to prevent an error: "Transaction too large"
998
+ const maxWithdrawAccounts = 5
999
+ let i = 0
1000
+
1001
+ for (const withdrawAccount of withdrawAccounts) {
1002
+ if (i >= maxWithdrawAccounts) {
1003
+ break
1004
+ }
1005
+
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
+ }),
1020
+ )
1021
+
1022
+ // Add the withdraw stake with session instruction
1023
+ instructions.push(
1024
+ StakePoolInstruction.withdrawStakeWithSession({
1025
+ programId: stakePoolProgramId,
1026
+ stakePool: stakePoolAddress,
1027
+ validatorList: stakePool.validatorList,
1028
+ withdrawAuthority,
1029
+ stakeToSplit: withdrawAccount.stakeAddress,
1030
+ stakeToReceive: stakeReceiver.publicKey,
1031
+ sessionSigner: signerOrSession,
1032
+ burnFromPool: poolTokenAccount,
1033
+ managerFeeAccount: stakePool.managerFeeAccount,
1034
+ poolMint: stakePool.poolMint,
1035
+ tokenProgramId: stakePool.tokenProgramId,
1036
+ programSigner,
1037
+ poolTokensIn: withdrawAccount.poolAmount.toNumber(),
1038
+ minimumLamportsOut: solToLamports(minimumLamportsOut),
1039
+ }),
1040
+ )
1041
+ i++
1042
+ }
1043
+
1044
+ return {
1045
+ instructions,
1046
+ signers,
1047
+ stakeAccountPubkeys,
1048
+ }
1049
+ }
1050
+
896
1051
  export async function addValidatorToPool(
897
1052
  connection: Connection,
898
1053
  stakePoolAddress: PublicKey,
@@ -41,6 +41,7 @@ export type StakePoolInstructionType
41
41
  | 'RemoveValidatorFromPool'
42
42
  | 'DepositWsolWithSession'
43
43
  | 'WithdrawWsolWithSession'
44
+ | 'WithdrawStakeWithSession'
44
45
 
45
46
  // 'UpdateTokenMetadata' and 'CreateTokenMetadata' have dynamic layouts
46
47
 
@@ -193,7 +194,8 @@ export const STAKE_POOL_INSTRUCTION_LAYOUTS: {
193
194
  index: 24,
194
195
  layout: BufferLayout.struct<any>([
195
196
  BufferLayout.u8('instruction'),
196
- BufferLayout.ns64('lamports'),
197
+ BufferLayout.ns64('poolTokensIn'),
198
+ BufferLayout.ns64('minimumLamportsOut'),
197
199
  ]),
198
200
  },
199
201
  DepositSolWithSlippage: {
@@ -226,6 +228,14 @@ export const STAKE_POOL_INSTRUCTION_LAYOUTS: {
226
228
  BufferLayout.ns64('minimumLamportsOut'),
227
229
  ]),
228
230
  },
231
+ WithdrawStakeWithSession: {
232
+ index: 29,
233
+ layout: BufferLayout.struct<any>([
234
+ BufferLayout.u8('instruction'),
235
+ BufferLayout.ns64('poolTokensIn'),
236
+ BufferLayout.ns64('minimumLamportsOut'),
237
+ ]),
238
+ },
229
239
  })
230
240
 
231
241
  /**
@@ -382,9 +392,30 @@ export type WithdrawWsolWithSessionParams = {
382
392
  managerFeeAccount: PublicKey
383
393
  poolMint: PublicKey
384
394
  tokenProgramId: PublicKey
385
- solWithdrawAuthority?: PublicKey
386
395
  wsolMint: PublicKey
387
396
  programSigner: PublicKey
397
+ userWallet: PublicKey
398
+ poolTokensIn: number
399
+ minimumLamportsOut: number
400
+ payer?: PublicKey
401
+ solWithdrawAuthority?: PublicKey
402
+ }
403
+
404
+ export type WithdrawStakeWithSessionParams = {
405
+ programId: PublicKey
406
+ stakePool: PublicKey
407
+ validatorList: PublicKey
408
+ withdrawAuthority: PublicKey
409
+ stakeToSplit: PublicKey
410
+ stakeToReceive: PublicKey
411
+ /** The session signer (user or session) - used as both stake authority and transfer authority */
412
+ sessionSigner: PublicKey
413
+ burnFromPool: PublicKey
414
+ managerFeeAccount: PublicKey
415
+ poolMint: PublicKey
416
+ tokenProgramId: PublicKey
417
+ /** The program signer PDA derived from PROGRAM_SIGNER_SEED */
418
+ programSigner: PublicKey
388
419
  poolTokensIn: number
389
420
  minimumLamportsOut: number
390
421
  }
@@ -1138,6 +1169,9 @@ export class StakePoolInstruction {
1138
1169
 
1139
1170
  { pubkey: params.wsolMint, isSigner: false, isWritable: false },
1140
1171
  { pubkey: params.programSigner, isSigner: false, isWritable: true },
1172
+ { pubkey: params.payer ?? params.userTransferAuthority, isSigner: true, isWritable: true },
1173
+ { pubkey: params.userWallet, isSigner: false, isWritable: false },
1174
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1141
1175
  ]
1142
1176
 
1143
1177
  if (params.solWithdrawAuthority) {
@@ -1148,6 +1182,43 @@ export class StakePoolInstruction {
1148
1182
  })
1149
1183
  }
1150
1184
 
1185
+ // Associated Token Program must be last - only needed in transaction for CPI routing
1186
+ keys.push({ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false })
1187
+
1188
+ return new TransactionInstruction({
1189
+ programId: params.programId,
1190
+ keys,
1191
+ data,
1192
+ })
1193
+ }
1194
+
1195
+ /**
1196
+ * Creates a transaction instruction to withdraw stake from a stake pool using a Fogo session.
1197
+ */
1198
+ static withdrawStakeWithSession(params: WithdrawStakeWithSessionParams): TransactionInstruction {
1199
+ const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStakeWithSession
1200
+ const data = encodeData(type, {
1201
+ poolTokensIn: params.poolTokensIn,
1202
+ minimumLamportsOut: params.minimumLamportsOut,
1203
+ })
1204
+
1205
+ const keys = [
1206
+ { pubkey: params.stakePool, isSigner: false, isWritable: true },
1207
+ { pubkey: params.validatorList, isSigner: false, isWritable: true },
1208
+ { pubkey: params.withdrawAuthority, isSigner: false, isWritable: false },
1209
+ { pubkey: params.stakeToSplit, isSigner: false, isWritable: true },
1210
+ { pubkey: params.stakeToReceive, isSigner: false, isWritable: true },
1211
+ { pubkey: params.sessionSigner, isSigner: true, isWritable: false }, // user_stake_authority_info (signer_or_session)
1212
+ { pubkey: params.sessionSigner, isSigner: false, isWritable: false }, // user_transfer_authority_info (not used in session path)
1213
+ { pubkey: params.burnFromPool, isSigner: false, isWritable: true },
1214
+ { pubkey: params.managerFeeAccount, isSigner: false, isWritable: true },
1215
+ { pubkey: params.poolMint, isSigner: false, isWritable: true },
1216
+ { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
1217
+ { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1218
+ { pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
1219
+ { pubkey: params.programSigner, isSigner: false, isWritable: false },
1220
+ ]
1221
+
1151
1222
  return new TransactionInstruction({
1152
1223
  programId: params.programId,
1153
1224
  keys,