@ignitionfi/fogo-stake-pool 1.0.1 → 1.0.3

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/dist/index.d.ts CHANGED
@@ -144,24 +144,25 @@ export declare function getUserStakeAccounts(connection: Connection, programId:
144
144
  * Withdraws stake from a stake pool using a Fogo session.
145
145
  *
146
146
  * The on-chain program creates stake account PDAs. The rent for these accounts
147
- * is paid by the payer (typically the paymaster), not deducted from the user's withdrawal.
147
+ * is funded from the reserve stake.
148
148
  *
149
149
  * @param connection - Solana connection
150
150
  * @param stakePoolAddress - The stake pool to withdraw from
151
151
  * @param signerOrSession - The session signer public key
152
152
  * @param userPubkey - User's wallet (used for PDA derivation and token ownership)
153
- * @param payer - Payer for stake account rent (typically paymaster)
154
153
  * @param amount - Amount of pool tokens to withdraw
155
154
  * @param userStakeSeedStart - Starting seed for user stake PDA derivation (default: 0)
156
155
  * @param useReserve - Whether to withdraw from reserve (default: false)
157
156
  * @param voteAccountAddress - Optional specific validator to withdraw from
158
157
  * @param minimumLamportsOut - Minimum lamports to receive (slippage protection)
159
158
  * @param validatorComparator - Optional comparator for validator selection
159
+ * @param allowPartial - If true, returns partial results instead of throwing when not enough stake available
160
160
  */
161
- export declare function withdrawStakeWithSession(connection: Connection, stakePoolAddress: PublicKey, signerOrSession: PublicKey, userPubkey: PublicKey, payer: PublicKey, amount: number, userStakeSeedStart?: number, useReserve?: boolean, voteAccountAddress?: PublicKey, minimumLamportsOut?: number, validatorComparator?: (_a: ValidatorAccount, _b: ValidatorAccount) => number): Promise<{
161
+ export declare function withdrawStakeWithSession(connection: Connection, stakePoolAddress: PublicKey, signerOrSession: PublicKey, userPubkey: PublicKey, amount: number, userStakeSeedStart?: number, useReserve?: boolean, voteAccountAddress?: PublicKey, minimumLamportsOut?: number, validatorComparator?: (_a: ValidatorAccount, _b: ValidatorAccount) => number, allowPartial?: boolean): Promise<{
162
162
  instructions: TransactionInstruction[];
163
163
  stakeAccountPubkeys: PublicKey[];
164
164
  userStakeSeeds: number[];
165
+ remainingPoolTokens: number;
165
166
  }>;
166
167
  export declare function addValidatorToPool(connection: Connection, stakePoolAddress: PublicKey, validatorVote: PublicKey, seed?: number): Promise<{
167
168
  instructions: TransactionInstruction[];
package/dist/index.esm.js CHANGED
@@ -436,7 +436,7 @@ const TRANSIENT_STAKE_SEED_PREFIX = Buffer$1.from('transient');
436
436
  const USER_STAKE_SEED_PREFIX = Buffer$1.from('user_stake');
437
437
  // Minimum amount of staked SOL required in a validator stake account to allow
438
438
  // for merges without a mismatch on credits observed
439
- const MINIMUM_ACTIVE_STAKE = LAMPORTS_PER_SOL;
439
+ const MINIMUM_ACTIVE_STAKE = 1000000;
440
440
 
441
441
  /**
442
442
  * Populate a buffer of instruction data using an InstructionType
@@ -806,80 +806,84 @@ async function getValidatorListAccount(connection, pubkey) {
806
806
  },
807
807
  };
808
808
  }
809
- async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount, compareFn, skipFee) {
809
+ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount, compareFn, skipFee, allowPartial, prefetchedData) {
810
810
  var _a, _b;
811
811
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
812
- const validatorListAcc = await connection.getAccountInfo(stakePool.validatorList);
813
- const validatorList = ValidatorListLayout.decode(validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data);
814
- if (!(validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators) || (validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators.length) === 0) {
815
- throw new Error('No accounts found');
812
+ // Use prefetched data if available, otherwise fetch from RPC
813
+ let validatorListData;
814
+ let minBalanceForRentExemption;
815
+ let stakeMinimumDelegation;
816
+ if (prefetchedData) {
817
+ validatorListData = prefetchedData.validatorListData;
818
+ minBalanceForRentExemption = prefetchedData.minBalanceForRentExemption;
819
+ stakeMinimumDelegation = prefetchedData.stakeMinimumDelegation;
816
820
  }
817
- const minBalanceForRentExemption = await connection.getMinimumBalanceForRentExemption(StakeProgram.space);
818
- const minBalance = new BN(minBalanceForRentExemption + MINIMUM_ACTIVE_STAKE);
819
- // First, collect all stake account addresses we need to check
820
- const accountsToFetch = [];
821
+ else {
822
+ const [validatorListAcc, rentExemption, stakeMinimumDelegationResponse] = await Promise.all([
823
+ connection.getAccountInfo(stakePool.validatorList),
824
+ connection.getMinimumBalanceForRentExemption(StakeProgram.space),
825
+ connection.getStakeMinimumDelegation(),
826
+ ]);
827
+ validatorListData = (_a = validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data) !== null && _a !== void 0 ? _a : null;
828
+ minBalanceForRentExemption = rentExemption;
829
+ stakeMinimumDelegation = Number(stakeMinimumDelegationResponse.value);
830
+ }
831
+ if (!validatorListData) {
832
+ throw new Error('No staked funds available for delayed unstake. Use instant unstake instead.');
833
+ }
834
+ const validatorList = ValidatorListLayout.decode(validatorListData);
835
+ if (!(validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators) || (validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators.length) === 0) {
836
+ throw new Error('No staked funds available for delayed unstake. Use instant unstake instead.');
837
+ }
838
+ // minBalance = rent + max(stake_minimum_delegation, MINIMUM_ACTIVE_STAKE)
839
+ const minimumDelegation = Math.max(stakeMinimumDelegation, MINIMUM_ACTIVE_STAKE);
840
+ const minBalance = new BN(minBalanceForRentExemption + minimumDelegation);
841
+ // Threshold for has_active_stake check (ceiling division for lamports_per_pool_token)
842
+ const lamportsPerPoolToken = stakePool.totalLamports
843
+ .add(stakePool.poolTokenSupply)
844
+ .sub(new BN(1))
845
+ .div(stakePool.poolTokenSupply);
846
+ const minimumLamportsWithTolerance = minBalance.add(lamportsPerPoolToken);
847
+ const hasActiveStake = validatorList.validators.some(v => v.status === ValidatorStakeInfoStatus.Active
848
+ && v.activeStakeLamports.gt(minimumLamportsWithTolerance));
849
+ const hasTransientStake = validatorList.validators.some(v => v.status === ValidatorStakeInfoStatus.Active
850
+ && v.transientStakeLamports.gt(minimumLamportsWithTolerance));
851
+ // ValidatorRemoval mode: no validator above threshold
852
+ const isValidatorRemovalMode = !hasActiveStake && !hasTransientStake;
853
+ let accounts = [];
821
854
  for (const validator of validatorList.validators) {
822
855
  if (validator.status !== ValidatorStakeInfoStatus.Active) {
823
856
  continue;
824
857
  }
825
858
  const stakeAccountAddress = await findStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress);
826
- const isPreferred = (_a = stakePool === null || stakePool === void 0 ? void 0 : stakePool.preferredWithdrawValidatorVoteAddress) === null || _a === void 0 ? void 0 : _a.equals(validator.voteAccountAddress);
827
- // Add active stake account if validator list indicates it has stake
828
- if (validator.activeStakeLamports.gt(new BN(0))) {
829
- accountsToFetch.push({
859
+ // ValidatorRemoval: full balance available; Normal: leave minBalance
860
+ const availableActiveLamports = isValidatorRemovalMode
861
+ ? validator.activeStakeLamports
862
+ : validator.activeStakeLamports.sub(minBalance);
863
+ if (availableActiveLamports.gt(new BN(0))) {
864
+ const isPreferred = (_b = stakePool === null || stakePool === void 0 ? void 0 : stakePool.preferredWithdrawValidatorVoteAddress) === null || _b === void 0 ? void 0 : _b.equals(validator.voteAccountAddress);
865
+ accounts.push({
830
866
  type: isPreferred ? 'preferred' : 'active',
831
867
  voteAddress: validator.voteAccountAddress,
832
868
  stakeAddress: stakeAccountAddress,
869
+ lamports: availableActiveLamports,
833
870
  });
834
871
  }
835
- // Add transient stake account if validator list indicates it has stake
836
- if (validator.transientStakeLamports.gt(new BN(0))) {
872
+ const availableTransientLamports = isValidatorRemovalMode
873
+ ? validator.transientStakeLamports
874
+ : validator.transientStakeLamports.sub(minBalance);
875
+ if (availableTransientLamports.gt(new BN(0))) {
837
876
  const transientStakeAccountAddress = await findTransientStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress, validator.transientSeedSuffixStart);
838
- accountsToFetch.push({
877
+ accounts.push({
839
878
  type: 'transient',
840
879
  voteAddress: validator.voteAccountAddress,
841
880
  stakeAddress: transientStakeAccountAddress,
842
- });
843
- }
844
- }
845
- // Fetch all stake accounts + reserve in one batch call
846
- const addressesToFetch = [
847
- ...accountsToFetch.map(a => a.stakeAddress),
848
- stakePool.reserveStake,
849
- ];
850
- const accountInfos = await connection.getMultipleAccountsInfo(addressesToFetch);
851
- // Build accounts list using actual on-chain balances
852
- let accounts = [];
853
- for (let i = 0; i < accountsToFetch.length; i++) {
854
- const { type, voteAddress, stakeAddress } = accountsToFetch[i];
855
- const accountInfo = accountInfos[i];
856
- if (!accountInfo) {
857
- continue;
858
- }
859
- // Use actual on-chain balance instead of validator list value
860
- const actualLamports = new BN(accountInfo.lamports);
861
- const availableLamports = actualLamports.sub(minBalance);
862
- if (availableLamports.gt(new BN(0))) {
863
- accounts.push({
864
- type,
865
- voteAddress,
866
- stakeAddress,
867
- lamports: availableLamports,
881
+ lamports: availableTransientLamports,
868
882
  });
869
883
  }
870
884
  }
871
885
  // Sort from highest to lowest balance
872
886
  accounts = accounts.sort(compareFn || ((a, b) => b.lamports.sub(a.lamports).toNumber()));
873
- // Add reserve stake using actual balance (last item in batch fetch)
874
- const reserveAccountInfo = accountInfos[accountInfos.length - 1];
875
- const reserveStakeBalance = new BN(((_b = reserveAccountInfo === null || reserveAccountInfo === void 0 ? void 0 : reserveAccountInfo.lamports) !== null && _b !== void 0 ? _b : 0) - minBalanceForRentExemption);
876
- if (reserveStakeBalance.gt(new BN(0))) {
877
- accounts.push({
878
- type: 'reserve',
879
- stakeAddress: stakePool.reserveStake,
880
- lamports: reserveStakeBalance,
881
- });
882
- }
883
887
  // Prepare the list of accounts to withdraw from
884
888
  const withdrawFrom = [];
885
889
  let remainingAmount = new BN(amount);
@@ -888,23 +892,24 @@ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress,
888
892
  numerator: fee.denominator.sub(fee.numerator),
889
893
  denominator: fee.denominator,
890
894
  };
891
- for (const type of ['preferred', 'active', 'transient', 'reserve']) {
895
+ for (const type of ['preferred', 'active', 'transient']) {
892
896
  const filteredAccounts = accounts.filter(a => a.type === type);
893
897
  for (const { stakeAddress, voteAddress, lamports } of filteredAccounts) {
894
- if (lamports.lte(minBalance) && type === 'transient') {
895
- continue;
896
- }
897
898
  let availableForWithdrawal = calcPoolTokensForDeposit(stakePool, lamports);
898
899
  if (!skipFee && !inverseFee.numerator.isZero()) {
899
900
  availableForWithdrawal = availableForWithdrawal
900
901
  .mul(inverseFee.denominator)
901
902
  .div(inverseFee.numerator);
902
903
  }
904
+ // In ValidatorRemoval mode, must withdraw full validator balance (no partial)
905
+ // Skip if remaining amount is less than full validator balance
906
+ if (isValidatorRemovalMode && remainingAmount.lt(availableForWithdrawal)) {
907
+ continue;
908
+ }
903
909
  const poolAmount = BN.min(availableForWithdrawal, remainingAmount);
904
910
  if (poolAmount.lte(new BN(0))) {
905
911
  continue;
906
912
  }
907
- // Those accounts will be withdrawn completely with `claim` instruction
908
913
  withdrawFrom.push({ stakeAddress, voteAddress, poolAmount });
909
914
  remainingAmount = remainingAmount.sub(poolAmount);
910
915
  if (remainingAmount.isZero()) {
@@ -917,7 +922,23 @@ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress,
917
922
  }
918
923
  // Not enough stake to withdraw the specified amount
919
924
  if (remainingAmount.gt(new BN(0))) {
920
- throw new Error(`No stake accounts found in this pool with enough balance to withdraw ${lamportsToSol(amount)} pool tokens.`);
925
+ if (allowPartial) {
926
+ const delayedAmount = amount.sub(remainingAmount);
927
+ return {
928
+ withdrawAccounts: withdrawFrom,
929
+ delayedAmount,
930
+ remainingAmount,
931
+ };
932
+ }
933
+ const availableAmount = amount.sub(remainingAmount);
934
+ throw new Error(`Not enough staked funds for delayed unstake. Requested ${lamportsToSol(amount)} iFOGO, but only ${lamportsToSol(availableAmount)} available. Use instant unstake for the remaining amount.`);
935
+ }
936
+ if (allowPartial) {
937
+ return {
938
+ withdrawAccounts: withdrawFrom,
939
+ delayedAmount: amount,
940
+ remainingAmount: new BN(0),
941
+ };
921
942
  }
922
943
  return withdrawFrom;
923
944
  }
@@ -1630,7 +1651,7 @@ class StakePoolInstruction {
1630
1651
  }
1631
1652
  /**
1632
1653
  * Creates a transaction instruction to withdraw stake from a stake pool using a Fogo session.
1633
- * The stake account is created as a PDA and rent is paid by the payer (typically paymaster).
1654
+ * The stake account is created as a PDA and rent is funded from the reserve.
1634
1655
  */
1635
1656
  static withdrawStakeWithSession(params) {
1636
1657
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStakeWithSession;
@@ -1645,17 +1666,19 @@ class StakePoolInstruction {
1645
1666
  { pubkey: params.withdrawAuthority, isSigner: false, isWritable: false },
1646
1667
  { pubkey: params.stakeToSplit, isSigner: false, isWritable: true },
1647
1668
  { pubkey: params.stakeToReceive, isSigner: false, isWritable: true },
1648
- { pubkey: params.sessionSigner, isSigner: true, isWritable: false }, // user_stake_authority_info (signer_or_session)
1649
- { pubkey: params.sessionSigner, isSigner: false, isWritable: false }, // user_transfer_authority_info (not used in session path)
1669
+ { pubkey: params.sessionSigner, isSigner: true, isWritable: false }, // user_stake_authority_info
1670
+ { pubkey: params.sessionSigner, isSigner: false, isWritable: false }, // user_transfer_authority_info (unused in session path)
1650
1671
  { pubkey: params.burnFromPool, isSigner: false, isWritable: true },
1651
1672
  { pubkey: params.managerFeeAccount, isSigner: false, isWritable: true },
1652
1673
  { pubkey: params.poolMint, isSigner: false, isWritable: true },
1653
1674
  { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
1654
1675
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1655
1676
  { pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
1677
+ // Session-specific accounts
1656
1678
  { pubkey: params.programSigner, isSigner: false, isWritable: false },
1657
1679
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1658
- { pubkey: params.payer, isSigner: true, isWritable: true },
1680
+ { pubkey: params.reserveStake, isSigner: false, isWritable: true },
1681
+ { pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
1659
1682
  ];
1660
1683
  return new TransactionInstruction({
1661
1684
  programId: params.programId,
@@ -2395,37 +2418,51 @@ async function getUserStakeAccounts(connection, programId, userPubkey, maxSeed =
2395
2418
  * Withdraws stake from a stake pool using a Fogo session.
2396
2419
  *
2397
2420
  * The on-chain program creates stake account PDAs. The rent for these accounts
2398
- * is paid by the payer (typically the paymaster), not deducted from the user's withdrawal.
2421
+ * is funded from the reserve stake.
2399
2422
  *
2400
2423
  * @param connection - Solana connection
2401
2424
  * @param stakePoolAddress - The stake pool to withdraw from
2402
2425
  * @param signerOrSession - The session signer public key
2403
2426
  * @param userPubkey - User's wallet (used for PDA derivation and token ownership)
2404
- * @param payer - Payer for stake account rent (typically paymaster)
2405
2427
  * @param amount - Amount of pool tokens to withdraw
2406
2428
  * @param userStakeSeedStart - Starting seed for user stake PDA derivation (default: 0)
2407
2429
  * @param useReserve - Whether to withdraw from reserve (default: false)
2408
2430
  * @param voteAccountAddress - Optional specific validator to withdraw from
2409
2431
  * @param minimumLamportsOut - Minimum lamports to receive (slippage protection)
2410
2432
  * @param validatorComparator - Optional comparator for validator selection
2433
+ * @param allowPartial - If true, returns partial results instead of throwing when not enough stake available
2411
2434
  */
2412
- async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, payer, amount, userStakeSeedStart = 0, useReserve = false, voteAccountAddress, minimumLamportsOut = 0, validatorComparator) {
2413
- const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2435
+ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, userStakeSeedStart = 0, useReserve = false, voteAccountAddress, minimumLamportsOut = 0, validatorComparator, allowPartial = false) {
2436
+ var _c;
2414
2437
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
2438
+ // First fetch: get stake pool to know other account addresses
2439
+ const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2415
2440
  const stakePool = stakePoolAccount.account.data;
2416
2441
  const poolTokens = solToLamports(amount);
2417
2442
  const poolAmount = new BN(poolTokens);
2418
2443
  const poolTokenAccount = getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey);
2419
- const tokenAccount = await getAccount(connection, poolTokenAccount);
2444
+ // Second fetch: get ALL remaining data in parallel
2445
+ const [tokenAccount, stakeAccountRentExemption, validatorListAcc, stakeMinimumDelegationResponse] = await Promise.all([
2446
+ getAccount(connection, poolTokenAccount),
2447
+ connection.getMinimumBalanceForRentExemption(StakeProgram.space),
2448
+ connection.getAccountInfo(stakePool.validatorList),
2449
+ connection.getStakeMinimumDelegation(),
2450
+ ]);
2451
+ // Pre-fetch data to avoid duplicate RPC calls in prepareWithdrawAccounts
2452
+ const prefetchedData = {
2453
+ validatorListData: (_c = validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data) !== null && _c !== void 0 ? _c : null,
2454
+ minBalanceForRentExemption: stakeAccountRentExemption,
2455
+ stakeMinimumDelegation: Number(stakeMinimumDelegationResponse.value),
2456
+ };
2420
2457
  if (tokenAccount.amount < poolTokens) {
2421
2458
  throw new Error(`Not enough token balance to withdraw ${amount} pool tokens.
2422
2459
  Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`);
2423
2460
  }
2424
2461
  const [programSigner] = PublicKey.findProgramAddressSync([Buffer.from('fogo_session_program_signer')], stakePoolProgramId);
2425
2462
  const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
2426
- const stakeAccountRentExemption = await connection.getMinimumBalanceForRentExemption(StakeProgram.space);
2427
2463
  // Determine which stake accounts to withdraw from
2428
2464
  const withdrawAccounts = [];
2465
+ let partialRemainingAmount;
2429
2466
  if (useReserve) {
2430
2467
  withdrawAccounts.push({
2431
2468
  stakeAddress: stakePool.reserveStake,
@@ -2456,7 +2493,14 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2456
2493
  }
2457
2494
  else {
2458
2495
  // Get the list of accounts to withdraw from automatically
2459
- withdrawAccounts.push(...(await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount))));
2496
+ if (allowPartial) {
2497
+ const result = await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount), true, prefetchedData);
2498
+ withdrawAccounts.push(...result.withdrawAccounts);
2499
+ partialRemainingAmount = result.remainingAmount;
2500
+ }
2501
+ else {
2502
+ withdrawAccounts.push(...(await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount), undefined, prefetchedData)));
2503
+ }
2460
2504
  }
2461
2505
  const instructions = [];
2462
2506
  const stakeAccountPubkeys = [];
@@ -2473,7 +2517,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2473
2517
  const stakeReceiverPubkey = findUserStakeProgramAddress(stakePoolProgramId, userPubkey, userStakeSeed);
2474
2518
  stakeAccountPubkeys.push(stakeReceiverPubkey);
2475
2519
  userStakeSeeds.push(userStakeSeed);
2476
- // The on-chain program creates the stake account PDA and rent is paid by payer.
2520
+ // The on-chain program creates the stake account PDA and rent is funded from reserve.
2477
2521
  instructions.push(StakePoolInstruction.withdrawStakeWithSession({
2478
2522
  programId: stakePoolProgramId,
2479
2523
  stakePool: stakePoolAddress,
@@ -2487,7 +2531,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2487
2531
  poolMint: stakePool.poolMint,
2488
2532
  tokenProgramId: stakePool.tokenProgramId,
2489
2533
  programSigner,
2490
- payer,
2534
+ reserveStake: stakePool.reserveStake,
2491
2535
  poolTokensIn: withdrawAccount.poolAmount.toNumber(),
2492
2536
  minimumLamportsOut,
2493
2537
  userStakeSeed,
@@ -2498,6 +2542,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2498
2542
  instructions,
2499
2543
  stakeAccountPubkeys,
2500
2544
  userStakeSeeds,
2545
+ remainingPoolTokens: partialRemainingAmount ? lamportsToSol(partialRemainingAmount) : 0,
2501
2546
  };
2502
2547
  }
2503
2548
  async function addValidatorToPool(connection, stakePoolAddress, validatorVote, seed) {