@ignitionfi/spl-stake-pool 1.1.22 → 1.1.24

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.
@@ -9,4 +9,5 @@ export declare const DEVNET_STAKE_POOL_PROGRAM_ID: PublicKey;
9
9
  export declare const MAX_VALIDATORS_TO_UPDATE = 4;
10
10
  export declare const EPHEMERAL_STAKE_SEED_PREFIX: Buffer<ArrayBuffer>;
11
11
  export declare const TRANSIENT_STAKE_SEED_PREFIX: Buffer<ArrayBuffer>;
12
+ export declare const USER_STAKE_SEED_PREFIX: Buffer<ArrayBuffer>;
12
13
  export declare const MINIMUM_ACTIVE_STAKE = 1000000000;
@@ -453,6 +453,8 @@ const MAX_VALIDATORS_TO_UPDATE = 4;
453
453
  const EPHEMERAL_STAKE_SEED_PREFIX = node_buffer.Buffer.from('ephemeral');
454
454
  // Seed used to derive transient stake accounts.
455
455
  const TRANSIENT_STAKE_SEED_PREFIX = node_buffer.Buffer.from('transient');
456
+ // Seed for user stake account created during session withdrawal
457
+ const USER_STAKE_SEED_PREFIX = node_buffer.Buffer.from('user_stake');
456
458
  // Minimum amount of staked SOL required in a validator stake account to allow
457
459
  // for merges without a mismatch on credits observed
458
460
  const MINIMUM_ACTIVE_STAKE = web3_js.LAMPORTS_PER_SOL;
@@ -561,6 +563,19 @@ function findMetadataAddress(stakePoolMintAddress) {
561
563
  const [publicKey] = web3_js.PublicKey.findProgramAddressSync([node_buffer.Buffer.from('metadata'), METADATA_PROGRAM_ID.toBuffer(), stakePoolMintAddress.toBuffer()], METADATA_PROGRAM_ID);
562
564
  return publicKey;
563
565
  }
566
+ /**
567
+ * Generates the user stake account PDA for session-based withdrawals.
568
+ * The PDA is derived from the user's wallet and a unique seed.
569
+ */
570
+ function findUserStakeProgramAddress(programId, userWallet, seed) {
571
+ const seedBN = typeof seed === 'number' ? new BN(seed) : seed;
572
+ const [publicKey] = web3_js.PublicKey.findProgramAddressSync([
573
+ USER_STAKE_SEED_PREFIX,
574
+ userWallet.toBuffer(),
575
+ seedBN.toArrayLike(node_buffer.Buffer, 'le', 8),
576
+ ], programId);
577
+ return publicKey;
578
+ }
564
579
 
565
580
  class BNLayout extends bufferLayout.Layout {
566
581
  constructor(span, signed, property) {
@@ -1123,6 +1138,7 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1123
1138
  BufferLayout__namespace.u8('instruction'),
1124
1139
  BufferLayout__namespace.ns64('poolTokensIn'),
1125
1140
  BufferLayout__namespace.ns64('minimumLamportsOut'),
1141
+ BufferLayout__namespace.ns64('userStakeSeed'),
1126
1142
  ]),
1127
1143
  },
1128
1144
  });
@@ -1558,9 +1574,9 @@ class StakePoolInstruction {
1558
1574
  }
1559
1575
  /**
1560
1576
  * Creates a transaction instruction to withdraw WSOL from a stake pool using a session.
1577
+ * Rent for ATA creation (if needed) is paid from the withdrawal amount.
1561
1578
  */
1562
1579
  static withdrawWsolWithSession(params) {
1563
- var _a;
1564
1580
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawWsolWithSession;
1565
1581
  const data = encodeData(type, {
1566
1582
  poolTokensIn: params.poolTokensIn,
@@ -1581,7 +1597,6 @@ class StakePoolInstruction {
1581
1597
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1582
1598
  { pubkey: params.wsolMint, isSigner: false, isWritable: false },
1583
1599
  { pubkey: params.programSigner, isSigner: false, isWritable: true },
1584
- { pubkey: (_a = params.payer) !== null && _a !== void 0 ? _a : params.userTransferAuthority, isSigner: true, isWritable: true },
1585
1600
  { pubkey: params.userWallet, isSigner: false, isWritable: false },
1586
1601
  { pubkey: web3_js.SystemProgram.programId, isSigner: false, isWritable: false },
1587
1602
  ];
@@ -1602,12 +1617,14 @@ class StakePoolInstruction {
1602
1617
  }
1603
1618
  /**
1604
1619
  * Creates a transaction instruction to withdraw stake from a stake pool using a Fogo session.
1620
+ * The stake account is created as a PDA and rent is paid from the withdrawal amount.
1605
1621
  */
1606
1622
  static withdrawStakeWithSession(params) {
1607
1623
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStakeWithSession;
1608
1624
  const data = encodeData(type, {
1609
1625
  poolTokensIn: params.poolTokensIn,
1610
1626
  minimumLamportsOut: params.minimumLamportsOut,
1627
+ userStakeSeed: params.userStakeSeed,
1611
1628
  });
1612
1629
  const keys = [
1613
1630
  { pubkey: params.stakePool, isSigner: false, isWritable: true },
@@ -1624,6 +1641,7 @@ class StakePoolInstruction {
1624
1641
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1625
1642
  { pubkey: web3_js.StakeProgram.programId, isSigner: false, isWritable: false },
1626
1643
  { pubkey: params.programSigner, isSigner: false, isWritable: false },
1644
+ { pubkey: web3_js.SystemProgram.programId, isSigner: false, isWritable: false },
1627
1645
  ];
1628
1646
  return new web3_js.TransactionInstruction({
1629
1647
  programId: params.programId,
@@ -2197,8 +2215,9 @@ async function withdrawSol(connection, stakePoolAddress, tokenOwner, solReceiver
2197
2215
  }
2198
2216
  /**
2199
2217
  * Creates instructions required to withdraw wSOL from a stake pool.
2218
+ * Rent for ATA creation (if needed) is paid from the withdrawal amount.
2200
2219
  */
2201
- async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, minimumLamportsOut = 0, solWithdrawAuthority, payer) {
2220
+ async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, minimumLamportsOut = 0, solWithdrawAuthority) {
2202
2221
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2203
2222
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
2204
2223
  const stakePool = stakePoolAccount.account.data;
@@ -2231,7 +2250,6 @@ async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSes
2231
2250
  wsolMint: splToken.NATIVE_MINT,
2232
2251
  programSigner,
2233
2252
  userWallet: userPubkey,
2234
- payer,
2235
2253
  poolTokensIn: poolTokens,
2236
2254
  minimumLamportsOut,
2237
2255
  }));
@@ -2241,10 +2259,45 @@ async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSes
2241
2259
  };
2242
2260
  }
2243
2261
  /**
2244
- * Creates instructions required to withdraw stake from a stake pool using a Fogo session.
2245
- * The withdrawn stake account will be authorized to the user's wallet.
2262
+ * Finds the next available seed for creating a user stake PDA.
2263
+ * Scans from startSeed until an unused PDA is found.
2264
+ *
2265
+ * @param connection - Solana connection
2266
+ * @param programId - The stake pool program ID
2267
+ * @param userPubkey - User's wallet (used for PDA derivation)
2268
+ * @param startSeed - Starting seed to search from (default: 0)
2269
+ * @param maxSeed - Maximum seed to check before giving up (default: 1000)
2270
+ * @returns The next available seed
2271
+ * @throws Error if no available seed found within maxSeed
2246
2272
  */
2247
- async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, payer, useReserve = false, voteAccountAddress, minimumLamportsOut = 0, validatorComparator) {
2273
+ async function findNextUserStakeSeed(connection, programId, userPubkey, startSeed = 0, maxSeed = 1000) {
2274
+ for (let seed = startSeed; seed < startSeed + maxSeed; seed++) {
2275
+ const pda = findUserStakeProgramAddress(programId, userPubkey, seed);
2276
+ const account = await connection.getAccountInfo(pda);
2277
+ if (!account) {
2278
+ return seed;
2279
+ }
2280
+ }
2281
+ throw new Error(`No available user stake seed found between ${startSeed} and ${startSeed + maxSeed - 1}`);
2282
+ }
2283
+ /**
2284
+ * Withdraws stake from a stake pool using a Fogo session.
2285
+ *
2286
+ * The on-chain program creates stake account PDAs, so no pre-creation step is needed.
2287
+ * Rent for the stake account PDAs is paid from the withdrawal amount.
2288
+ *
2289
+ * @param connection - Solana connection
2290
+ * @param stakePoolAddress - The stake pool to withdraw from
2291
+ * @param signerOrSession - The session signer public key
2292
+ * @param userPubkey - User's wallet (used for PDA derivation and token ownership)
2293
+ * @param amount - Amount of pool tokens to withdraw
2294
+ * @param userStakeSeedStart - Starting seed for user stake PDA derivation (default: 0)
2295
+ * @param useReserve - Whether to withdraw from reserve (default: false)
2296
+ * @param voteAccountAddress - Optional specific validator to withdraw from
2297
+ * @param minimumLamportsOut - Minimum lamports to receive (slippage protection)
2298
+ * @param validatorComparator - Optional comparator for validator selection
2299
+ */
2300
+ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, userStakeSeedStart = 0, useReserve = false, voteAccountAddress, minimumLamportsOut = 0, validatorComparator) {
2248
2301
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2249
2302
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
2250
2303
  const stakePool = stakePoolAccount.account.data;
@@ -2295,6 +2348,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2295
2348
  }
2296
2349
  const instructions = [];
2297
2350
  const stakeAccountPubkeys = [];
2351
+ const userStakeSeeds = [];
2298
2352
  // Max 5 accounts to prevent an error: "Transaction too large"
2299
2353
  const maxWithdrawAccounts = 5;
2300
2354
  let i = 0;
@@ -2302,26 +2356,12 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2302
2356
  if (i >= maxWithdrawAccounts) {
2303
2357
  break;
2304
2358
  }
2305
- // Create a deterministic stake account address using a seed
2306
- // This avoids needing the new account to sign (required for sessions)
2307
- // We use payer as base so only payer needs to sign (payer = basePubkey)
2308
- // The seed includes timestamp + random component to ensure uniqueness
2309
- const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2310
- const seed = `ws:${uniqueId}:${i}`.slice(0, 32); // Seed max 32 chars
2311
- const stakeReceiverPubkey = await web3_js.PublicKey.createWithSeed(payer, seed, web3_js.StakeProgram.programId);
2359
+ // Derive the stake account PDA for this withdrawal
2360
+ const userStakeSeed = userStakeSeedStart + i;
2361
+ const stakeReceiverPubkey = findUserStakeProgramAddress(stakePoolProgramId, userPubkey, userStakeSeed);
2312
2362
  stakeAccountPubkeys.push(stakeReceiverPubkey);
2313
- // Create the stake account using createAccountWithSeed
2314
- // Since basePubkey === fromPubkey (both are payer), only payer needs to sign
2315
- instructions.push(web3_js.SystemProgram.createAccountWithSeed({
2316
- fromPubkey: payer,
2317
- newAccountPubkey: stakeReceiverPubkey,
2318
- basePubkey: payer,
2319
- seed,
2320
- lamports: stakeAccountRentExemption,
2321
- space: web3_js.StakeProgram.space,
2322
- programId: web3_js.StakeProgram.programId,
2323
- }));
2324
- // Add the withdraw stake with session instruction
2363
+ userStakeSeeds.push(userStakeSeed);
2364
+ // The on-chain program will create the stake account PDA
2325
2365
  instructions.push(StakePoolInstruction.withdrawStakeWithSession({
2326
2366
  programId: stakePoolProgramId,
2327
2367
  stakePool: stakePoolAddress,
@@ -2337,12 +2377,14 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2337
2377
  programSigner,
2338
2378
  poolTokensIn: withdrawAccount.poolAmount.toNumber(),
2339
2379
  minimumLamportsOut,
2380
+ userStakeSeed,
2340
2381
  }));
2341
2382
  i++;
2342
2383
  }
2343
2384
  return {
2344
2385
  instructions,
2345
2386
  stakeAccountPubkeys,
2387
+ userStakeSeeds,
2346
2388
  };
2347
2389
  }
2348
2390
  async function addValidatorToPool(connection, stakePoolAddress, validatorVote, seed) {
@@ -2720,6 +2762,13 @@ exports.decreaseValidatorStake = decreaseValidatorStake;
2720
2762
  exports.depositSol = depositSol;
2721
2763
  exports.depositStake = depositStake;
2722
2764
  exports.depositWsolWithSession = depositWsolWithSession;
2765
+ exports.findEphemeralStakeProgramAddress = findEphemeralStakeProgramAddress;
2766
+ exports.findNextUserStakeSeed = findNextUserStakeSeed;
2767
+ exports.findStakeProgramAddress = findStakeProgramAddress;
2768
+ exports.findTransientStakeProgramAddress = findTransientStakeProgramAddress;
2769
+ exports.findUserStakeProgramAddress = findUserStakeProgramAddress;
2770
+ exports.findWithdrawAuthorityProgramAddress = findWithdrawAuthorityProgramAddress;
2771
+ exports.findWsolTransientProgramAddress = findWsolTransientProgramAddress;
2723
2772
  exports.getStakeAccount = getStakeAccount;
2724
2773
  exports.getStakePoolAccount = getStakePoolAccount;
2725
2774
  exports.getStakePoolAccounts = getStakePoolAccounts;