@ignitionfi/spl-stake-pool 1.1.23 → 1.1.25

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.
@@ -823,13 +823,16 @@ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress,
823
823
  continue;
824
824
  }
825
825
  const stakeAccountAddress = await findStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress);
826
- if (!validator.activeStakeLamports.isZero()) {
826
+ // For active stake accounts, subtract the minimum balance that must remain
827
+ // to allow for merges and maintain rent exemption
828
+ const availableActiveLamports = validator.activeStakeLamports.sub(minBalance);
829
+ if (availableActiveLamports.gt(new BN(0))) {
827
830
  const isPreferred = (_a = stakePool === null || stakePool === void 0 ? void 0 : stakePool.preferredWithdrawValidatorVoteAddress) === null || _a === void 0 ? void 0 : _a.equals(validator.voteAccountAddress);
828
831
  accounts.push({
829
832
  type: isPreferred ? 'preferred' : 'active',
830
833
  voteAddress: validator.voteAccountAddress,
831
834
  stakeAddress: stakeAccountAddress,
832
- lamports: validator.activeStakeLamports,
835
+ lamports: availableActiveLamports,
833
836
  });
834
837
  }
835
838
  const transientStakeLamports = validator.transientStakeLamports.sub(minBalance);
@@ -1120,6 +1123,14 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1120
1123
  BufferLayout.ns64('userStakeSeed'),
1121
1124
  ]),
1122
1125
  },
1126
+ WithdrawFromStakeAccountWithSession: {
1127
+ index: 30,
1128
+ layout: BufferLayout.struct([
1129
+ BufferLayout.u8('instruction'),
1130
+ BufferLayout.ns64('lamports'),
1131
+ BufferLayout.ns64('userStakeSeed'),
1132
+ ]),
1133
+ },
1123
1134
  });
1124
1135
  /**
1125
1136
  * Stake Pool Instruction class
@@ -1553,9 +1564,9 @@ class StakePoolInstruction {
1553
1564
  }
1554
1565
  /**
1555
1566
  * Creates a transaction instruction to withdraw WSOL from a stake pool using a session.
1567
+ * Rent for ATA creation (if needed) is paid from the withdrawal amount.
1556
1568
  */
1557
1569
  static withdrawWsolWithSession(params) {
1558
- var _a;
1559
1570
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawWsolWithSession;
1560
1571
  const data = encodeData(type, {
1561
1572
  poolTokensIn: params.poolTokensIn,
@@ -1576,7 +1587,6 @@ class StakePoolInstruction {
1576
1587
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1577
1588
  { pubkey: params.wsolMint, isSigner: false, isWritable: false },
1578
1589
  { pubkey: params.programSigner, isSigner: false, isWritable: true },
1579
- { pubkey: (_a = params.payer) !== null && _a !== void 0 ? _a : params.userTransferAuthority, isSigner: true, isWritable: true },
1580
1590
  { pubkey: params.userWallet, isSigner: false, isWritable: false },
1581
1591
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1582
1592
  ];
@@ -1597,6 +1607,7 @@ class StakePoolInstruction {
1597
1607
  }
1598
1608
  /**
1599
1609
  * Creates a transaction instruction to withdraw stake from a stake pool using a Fogo session.
1610
+ * The stake account is created as a PDA and rent is paid by the payer (typically paymaster).
1600
1611
  */
1601
1612
  static withdrawStakeWithSession(params) {
1602
1613
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStakeWithSession;
@@ -1620,8 +1631,8 @@ class StakePoolInstruction {
1620
1631
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1621
1632
  { pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
1622
1633
  { pubkey: params.programSigner, isSigner: false, isWritable: false },
1623
- { pubkey: params.payer, isSigner: true, isWritable: true },
1624
1634
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1635
+ { pubkey: params.payer, isSigner: true, isWritable: true },
1625
1636
  ];
1626
1637
  return new TransactionInstruction({
1627
1638
  programId: params.programId,
@@ -1629,6 +1640,43 @@ class StakePoolInstruction {
1629
1640
  data,
1630
1641
  });
1631
1642
  }
1643
+ /**
1644
+ * Creates a transaction instruction to withdraw SOL from a deactivated user stake account using a Fogo session.
1645
+ * The stake account must be fully deactivated (inactive).
1646
+ * User receives full stake balance (payer's rent contribution compensates for reduced split).
1647
+ */
1648
+ static withdrawFromStakeAccountWithSession(params) {
1649
+ // For u64::MAX (full withdrawal), we need to manually encode since buffer-layout doesn't handle bigint well
1650
+ const U64_MAX = BigInt('18446744073709551615');
1651
+ const isFullWithdrawal = params.lamports >= U64_MAX;
1652
+ // Manually create the instruction data buffer using Uint8Array for browser compatibility
1653
+ // Layout: u8 instruction (1) + u64 lamports (8) + u64 userStakeSeed (8) = 17 bytes
1654
+ const data = new Uint8Array(17);
1655
+ data[0] = 30; // instruction discriminator (WithdrawFromStakeAccountWithSession = 30)
1656
+ // Write lamports as u64 little-endian (bytes 1-8)
1657
+ const lamportsBigInt = isFullWithdrawal ? U64_MAX : params.lamports;
1658
+ for (let i = 0; i < 8; i++) {
1659
+ data[1 + i] = Number((lamportsBigInt >> BigInt(i * 8)) & BigInt(0xff));
1660
+ }
1661
+ // Write userStakeSeed as u64 little-endian (bytes 9-16)
1662
+ const seedBigInt = BigInt(params.userStakeSeed);
1663
+ for (let i = 0; i < 8; i++) {
1664
+ data[9 + i] = Number((seedBigInt >> BigInt(i * 8)) & BigInt(0xff));
1665
+ }
1666
+ // Account order matches Rust: stake_account, recipient, clock, stake_history, session_signer
1667
+ const keys = [
1668
+ { pubkey: params.userStakeAccount, isSigner: false, isWritable: true },
1669
+ { pubkey: params.userWallet, isSigner: false, isWritable: true },
1670
+ { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
1671
+ { pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
1672
+ { pubkey: params.sessionSigner, isSigner: true, isWritable: false },
1673
+ ];
1674
+ return new TransactionInstruction({
1675
+ programId: params.programId,
1676
+ keys,
1677
+ data: Buffer.from(data),
1678
+ });
1679
+ }
1632
1680
  /**
1633
1681
  * Creates an instruction to create metadata
1634
1682
  * using the mpl token metadata program for the pool token
@@ -1892,14 +1940,21 @@ async function depositStake(connection, stakePoolAddress, authorizedPubkey, vali
1892
1940
  /**
1893
1941
  * Creates instructions required to deposit sol to stake pool.
1894
1942
  */
1895
- async function depositWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, lamports, minimumPoolTokensOut = 0, destinationTokenAccount, referrerTokenAccount, depositAuthority, payer) {
1943
+ async function depositWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, lamports, minimumPoolTokensOut = 0, destinationTokenAccount, referrerTokenAccount, depositAuthority, payer,
1944
+ /**
1945
+ * Skip WSOL balance validation. Set to true when adding wrap instructions
1946
+ * in the same transaction that will fund the WSOL account before deposit.
1947
+ */
1948
+ skipBalanceCheck = false) {
1896
1949
  const wsolTokenAccount = getAssociatedTokenAddressSync(NATIVE_MINT, userPubkey);
1897
- const tokenAccountInfo = await connection.getTokenAccountBalance(wsolTokenAccount, 'confirmed');
1898
- const wsolBalance = tokenAccountInfo
1899
- ? parseInt(tokenAccountInfo.value.amount)
1900
- : 0;
1901
- if (wsolBalance < lamports) {
1902
- throw new Error(`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(wsolBalance)} WSOL.`);
1950
+ if (!skipBalanceCheck) {
1951
+ const tokenAccountInfo = await connection.getTokenAccountBalance(wsolTokenAccount, 'confirmed');
1952
+ const wsolBalance = tokenAccountInfo
1953
+ ? parseInt(tokenAccountInfo.value.amount)
1954
+ : 0;
1955
+ if (wsolBalance < lamports) {
1956
+ throw new Error(`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(wsolBalance)} WSOL.`);
1957
+ }
1903
1958
  }
1904
1959
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
1905
1960
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
@@ -2195,8 +2250,9 @@ async function withdrawSol(connection, stakePoolAddress, tokenOwner, solReceiver
2195
2250
  }
2196
2251
  /**
2197
2252
  * Creates instructions required to withdraw wSOL from a stake pool.
2253
+ * Rent for ATA creation (if needed) is paid from the withdrawal amount.
2198
2254
  */
2199
- async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, minimumLamportsOut = 0, solWithdrawAuthority, payer) {
2255
+ async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, minimumLamportsOut = 0, solWithdrawAuthority) {
2200
2256
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2201
2257
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
2202
2258
  const stakePool = stakePoolAccount.account.data;
@@ -2229,7 +2285,6 @@ async function withdrawWsolWithSession(connection, stakePoolAddress, signerOrSes
2229
2285
  wsolMint: NATIVE_MINT,
2230
2286
  programSigner,
2231
2287
  userWallet: userPubkey,
2232
- payer,
2233
2288
  poolTokensIn: poolTokens,
2234
2289
  minimumLamportsOut,
2235
2290
  }));
@@ -2260,17 +2315,83 @@ async function findNextUserStakeSeed(connection, programId, userPubkey, startSee
2260
2315
  }
2261
2316
  throw new Error(`No available user stake seed found between ${startSeed} and ${startSeed + maxSeed - 1}`);
2262
2317
  }
2318
+ /**
2319
+ * Fetches all user stake accounts created via WithdrawStakeWithSession.
2320
+ * These are PDAs derived from [b"user_stake", user_wallet, seed].
2321
+ *
2322
+ * @param connection - Solana connection
2323
+ * @param programId - The stake pool program ID
2324
+ * @param userPubkey - User's wallet address
2325
+ * @param maxSeed - Maximum seed to check (default: 100)
2326
+ * @returns Array of user stake accounts with their details
2327
+ */
2328
+ async function getUserStakeAccounts(connection, programId, userPubkey, maxSeed = 100) {
2329
+ var _c, _d;
2330
+ const stakeAccounts = [];
2331
+ const currentEpoch = (await connection.getEpochInfo()).epoch;
2332
+ for (let seed = 0; seed < maxSeed; seed++) {
2333
+ const pda = findUserStakeProgramAddress(programId, userPubkey, seed);
2334
+ const accountInfo = await connection.getAccountInfo(pda);
2335
+ if (!accountInfo) {
2336
+ continue; // Skip empty slots, there might be gaps
2337
+ }
2338
+ // Check if owned by stake program
2339
+ if (!accountInfo.owner.equals(StakeProgram.programId)) {
2340
+ continue;
2341
+ }
2342
+ // Parse stake account data
2343
+ const stakeAccount = {
2344
+ pubkey: pda,
2345
+ seed,
2346
+ lamports: accountInfo.lamports,
2347
+ state: 'inactive',
2348
+ };
2349
+ try {
2350
+ // Parse the stake account to get delegation info
2351
+ const parsedAccount = await connection.getParsedAccountInfo(pda);
2352
+ if (parsedAccount.value && 'parsed' in parsedAccount.value.data) {
2353
+ const parsed = parsedAccount.value.data.parsed;
2354
+ if (parsed.type === 'delegated' && ((_d = (_c = parsed.info) === null || _c === void 0 ? void 0 : _c.stake) === null || _d === void 0 ? void 0 : _d.delegation)) {
2355
+ const delegation = parsed.info.stake.delegation;
2356
+ stakeAccount.voter = new PublicKey(delegation.voter);
2357
+ stakeAccount.activationEpoch = Number(delegation.activationEpoch);
2358
+ stakeAccount.deactivationEpoch = Number(delegation.deactivationEpoch);
2359
+ // Determine state based on epochs
2360
+ const activationEpoch = stakeAccount.activationEpoch;
2361
+ const deactivationEpoch = stakeAccount.deactivationEpoch;
2362
+ if (deactivationEpoch !== undefined && deactivationEpoch < Number.MAX_SAFE_INTEGER && deactivationEpoch <= currentEpoch) {
2363
+ stakeAccount.state = 'inactive';
2364
+ }
2365
+ else if (deactivationEpoch !== undefined && deactivationEpoch < Number.MAX_SAFE_INTEGER) {
2366
+ stakeAccount.state = 'deactivating';
2367
+ }
2368
+ else if (activationEpoch !== undefined && activationEpoch <= currentEpoch) {
2369
+ stakeAccount.state = 'active';
2370
+ }
2371
+ else {
2372
+ stakeAccount.state = 'activating';
2373
+ }
2374
+ }
2375
+ }
2376
+ }
2377
+ catch {
2378
+ // If parsing fails, keep default 'inactive' state
2379
+ }
2380
+ stakeAccounts.push(stakeAccount);
2381
+ }
2382
+ return stakeAccounts;
2383
+ }
2263
2384
  /**
2264
2385
  * Withdraws stake from a stake pool using a Fogo session.
2265
2386
  *
2266
- * The on-chain program creates stake account PDAs, so no pre-creation step is needed.
2267
- * The paymaster payer provides rent for the stake account PDAs.
2387
+ * The on-chain program creates stake account PDAs. The rent for these accounts
2388
+ * is paid by the payer (typically the paymaster), not deducted from the user's withdrawal.
2268
2389
  *
2269
2390
  * @param connection - Solana connection
2270
2391
  * @param stakePoolAddress - The stake pool to withdraw from
2271
2392
  * @param signerOrSession - The session signer public key
2272
2393
  * @param userPubkey - User's wallet (used for PDA derivation and token ownership)
2273
- * @param payer - Payer for stake account rent (paymaster payer in session context)
2394
+ * @param payer - Payer for stake account rent (typically paymaster)
2274
2395
  * @param amount - Amount of pool tokens to withdraw
2275
2396
  * @param userStakeSeedStart - Starting seed for user stake PDA derivation (default: 0)
2276
2397
  * @param useReserve - Whether to withdraw from reserve (default: false)
@@ -2342,7 +2463,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2342
2463
  const stakeReceiverPubkey = findUserStakeProgramAddress(stakePoolProgramId, userPubkey, userStakeSeed);
2343
2464
  stakeAccountPubkeys.push(stakeReceiverPubkey);
2344
2465
  userStakeSeeds.push(userStakeSeed);
2345
- // The on-chain program will create the stake account PDA
2466
+ // The on-chain program creates the stake account PDA and rent is paid by payer.
2346
2467
  instructions.push(StakePoolInstruction.withdrawStakeWithSession({
2347
2468
  programId: stakePoolProgramId,
2348
2469
  stakePool: stakePoolAddress,
@@ -2731,5 +2852,5 @@ async function updatePoolTokenMetadata(connection, stakePoolAddress, name, symbo
2731
2852
  };
2732
2853
  }
2733
2854
 
2734
- export { DEVNET_STAKE_POOL_PROGRAM_ID, STAKE_POOL_INSTRUCTION_LAYOUTS, STAKE_POOL_PROGRAM_ID, StakePoolInstruction, StakePoolLayout, ValidatorListLayout, ValidatorStakeInfoLayout, addValidatorToPool, createPoolTokenMetadata, decreaseValidatorStake, depositSol, depositStake, depositWsolWithSession, findEphemeralStakeProgramAddress, findNextUserStakeSeed, findStakeProgramAddress, findTransientStakeProgramAddress, findUserStakeProgramAddress, findWithdrawAuthorityProgramAddress, findWsolTransientProgramAddress, getStakeAccount, getStakePoolAccount, getStakePoolAccounts, getStakePoolProgramId, increaseValidatorStake, removeValidatorFromPool, stakePoolInfo, tokenMetadataLayout, updatePoolTokenMetadata, updateStakePool, withdrawSol, withdrawStake, withdrawStakeWithSession, withdrawWsolWithSession };
2855
+ export { DEVNET_STAKE_POOL_PROGRAM_ID, STAKE_POOL_INSTRUCTION_LAYOUTS, STAKE_POOL_PROGRAM_ID, StakePoolInstruction, StakePoolLayout, ValidatorListLayout, ValidatorStakeInfoLayout, addValidatorToPool, createPoolTokenMetadata, decreaseValidatorStake, depositSol, depositStake, depositWsolWithSession, findEphemeralStakeProgramAddress, findNextUserStakeSeed, findStakeProgramAddress, findTransientStakeProgramAddress, findUserStakeProgramAddress, findWithdrawAuthorityProgramAddress, findWsolTransientProgramAddress, getStakeAccount, getStakePoolAccount, getStakePoolAccounts, getStakePoolProgramId, getUserStakeAccounts, increaseValidatorStake, removeValidatorFromPool, stakePoolInfo, tokenMetadataLayout, updatePoolTokenMetadata, updateStakePool, withdrawSol, withdrawStake, withdrawStakeWithSession, withdrawWsolWithSession };
2735
2856
  //# sourceMappingURL=index.browser.esm.js.map