@ignitionfi/fogo-stake-pool 1.0.2 → 1.1.0

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.esm.js CHANGED
@@ -438,6 +438,139 @@ const USER_STAKE_SEED_PREFIX = Buffer$1.from('user_stake');
438
438
  // for merges without a mismatch on credits observed
439
439
  const MINIMUM_ACTIVE_STAKE = 1000000;
440
440
 
441
+ /**
442
+ * BN-based layout for data decoding using buffer-layout.
443
+ * Used for decoding on-chain account data (StakePool, ValidatorList, etc.)
444
+ */
445
+ class BNDataLayout extends Layout {
446
+ constructor(span, signed, property) {
447
+ super(span, property);
448
+ this.blobLayout = blob(span);
449
+ this.signed = signed;
450
+ }
451
+ decode(b, offset = 0) {
452
+ const num = new BN(this.blobLayout.decode(b, offset), 10, 'le');
453
+ if (this.signed) {
454
+ return num.fromTwos(this.span * 8).clone();
455
+ }
456
+ return num;
457
+ }
458
+ encode(src, b, offset = 0) {
459
+ if (this.signed) {
460
+ src = src.toTwos(this.span * 8);
461
+ }
462
+ return this.blobLayout.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
463
+ }
464
+ }
465
+ /**
466
+ * Creates a u64 layout for data decoding (account layouts).
467
+ * Used in StakePoolLayout, ValidatorListLayout, etc.
468
+ */
469
+ function u64(property) {
470
+ return new BNDataLayout(8, false, property);
471
+ }
472
+ /**
473
+ * BN-based layout for 64-bit unsigned integers using @solana/buffer-layout.
474
+ * Used for encoding instruction data with support for values > MAX_SAFE_INTEGER.
475
+ */
476
+ class BNInstructionLayout extends BufferLayout.Layout {
477
+ constructor(span, signed, property) {
478
+ super(span, property);
479
+ this.blobLayout = BufferLayout.blob(span);
480
+ this.signed = signed;
481
+ }
482
+ decode(b, offset = 0) {
483
+ const num = new BN(this.blobLayout.decode(b, offset), 10, 'le');
484
+ if (this.signed) {
485
+ return num.fromTwos(this.span * 8).clone();
486
+ }
487
+ return num;
488
+ }
489
+ encode(src, b, offset = 0) {
490
+ if (this.signed) {
491
+ src = src.toTwos(this.span * 8);
492
+ }
493
+ return this.blobLayout.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
494
+ }
495
+ getSpan(_b, _offset) {
496
+ return this.span;
497
+ }
498
+ }
499
+ /**
500
+ * Creates a u64 layout for instruction encoding.
501
+ * Properly handles BN values larger than Number.MAX_SAFE_INTEGER.
502
+ * Compatible with @solana/buffer-layout.struct().
503
+ */
504
+ // eslint-disable-next-line ts/no-explicit-any
505
+ function u64Instruction(property) {
506
+ return new BNInstructionLayout(8, false, property);
507
+ }
508
+ class WrappedLayout extends Layout {
509
+ constructor(layout, decoder, encoder, property) {
510
+ super(layout.span, property);
511
+ this.layout = layout;
512
+ this.decoder = decoder;
513
+ this.encoder = encoder;
514
+ }
515
+ decode(b, offset) {
516
+ return this.decoder(this.layout.decode(b, offset));
517
+ }
518
+ encode(src, b, offset) {
519
+ return this.layout.encode(this.encoder(src), b, offset);
520
+ }
521
+ getSpan(b, offset) {
522
+ return this.layout.getSpan(b, offset);
523
+ }
524
+ }
525
+ function publicKey(property) {
526
+ return new WrappedLayout(blob(32), (b) => new PublicKey(b), (key) => key.toBuffer(), property);
527
+ }
528
+ class OptionLayout extends Layout {
529
+ constructor(layout, property) {
530
+ super(-1, property);
531
+ this.layout = layout;
532
+ this.discriminator = u8();
533
+ }
534
+ encode(src, b, offset = 0) {
535
+ if (src === null || src === undefined) {
536
+ return this.discriminator.encode(0, b, offset);
537
+ }
538
+ this.discriminator.encode(1, b, offset);
539
+ return this.layout.encode(src, b, offset + 1) + 1;
540
+ }
541
+ decode(b, offset = 0) {
542
+ const discriminator = this.discriminator.decode(b, offset);
543
+ if (discriminator === 0) {
544
+ return null;
545
+ }
546
+ else if (discriminator === 1) {
547
+ return this.layout.decode(b, offset + 1);
548
+ }
549
+ throw new Error(`Invalid option ${this.property}`);
550
+ }
551
+ getSpan(b, offset = 0) {
552
+ const discriminator = this.discriminator.decode(b, offset);
553
+ if (discriminator === 0) {
554
+ return 1;
555
+ }
556
+ else if (discriminator === 1) {
557
+ return this.layout.getSpan(b, offset + 1) + 1;
558
+ }
559
+ throw new Error(`Invalid option ${this.property}`);
560
+ }
561
+ }
562
+ function option(layout, property) {
563
+ return new OptionLayout(layout, property);
564
+ }
565
+ function vec(elementLayout, property) {
566
+ const length = u32('length');
567
+ const layout = struct([
568
+ length,
569
+ seq(elementLayout, offset(length, -length.span), 'values'),
570
+ ]);
571
+ return new WrappedLayout(layout, ({ values }) => values, values => ({ values }), property);
572
+ }
573
+
441
574
  /**
442
575
  * Populate a buffer of instruction data using an InstructionType
443
576
  * @internal
@@ -556,95 +689,6 @@ function findUserStakeProgramAddress(programId, userWallet, seed) {
556
689
  return publicKey;
557
690
  }
558
691
 
559
- class BNLayout extends Layout {
560
- constructor(span, signed, property) {
561
- super(span, property);
562
- this.blob = blob(span);
563
- this.signed = signed;
564
- }
565
- decode(b, offset = 0) {
566
- const num = new BN(this.blob.decode(b, offset), 10, 'le');
567
- if (this.signed) {
568
- return num.fromTwos(this.span * 8).clone();
569
- }
570
- return num;
571
- }
572
- encode(src, b, offset = 0) {
573
- if (this.signed) {
574
- src = src.toTwos(this.span * 8);
575
- }
576
- return this.blob.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
577
- }
578
- }
579
- function u64(property) {
580
- return new BNLayout(8, false, property);
581
- }
582
- class WrappedLayout extends Layout {
583
- constructor(layout, decoder, encoder, property) {
584
- super(layout.span, property);
585
- this.layout = layout;
586
- this.decoder = decoder;
587
- this.encoder = encoder;
588
- }
589
- decode(b, offset) {
590
- return this.decoder(this.layout.decode(b, offset));
591
- }
592
- encode(src, b, offset) {
593
- return this.layout.encode(this.encoder(src), b, offset);
594
- }
595
- getSpan(b, offset) {
596
- return this.layout.getSpan(b, offset);
597
- }
598
- }
599
- function publicKey(property) {
600
- return new WrappedLayout(blob(32), (b) => new PublicKey(b), (key) => key.toBuffer(), property);
601
- }
602
- class OptionLayout extends Layout {
603
- constructor(layout, property) {
604
- super(-1, property);
605
- this.layout = layout;
606
- this.discriminator = u8();
607
- }
608
- encode(src, b, offset = 0) {
609
- if (src === null || src === undefined) {
610
- return this.discriminator.encode(0, b, offset);
611
- }
612
- this.discriminator.encode(1, b, offset);
613
- return this.layout.encode(src, b, offset + 1) + 1;
614
- }
615
- decode(b, offset = 0) {
616
- const discriminator = this.discriminator.decode(b, offset);
617
- if (discriminator === 0) {
618
- return null;
619
- }
620
- else if (discriminator === 1) {
621
- return this.layout.decode(b, offset + 1);
622
- }
623
- throw new Error(`Invalid option ${this.property}`);
624
- }
625
- getSpan(b, offset = 0) {
626
- const discriminator = this.discriminator.decode(b, offset);
627
- if (discriminator === 0) {
628
- return 1;
629
- }
630
- else if (discriminator === 1) {
631
- return this.layout.getSpan(b, offset + 1) + 1;
632
- }
633
- throw new Error(`Invalid option ${this.property}`);
634
- }
635
- }
636
- function option(layout, property) {
637
- return new OptionLayout(layout, property);
638
- }
639
- function vec(elementLayout, property) {
640
- const length = u32('length');
641
- const layout = struct([
642
- length,
643
- seq(elementLayout, offset(length, -length.span), 'values'),
644
- ]);
645
- return new WrappedLayout(layout, ({ values }) => values, values => ({ values }), property);
646
- }
647
-
648
692
  const feeFields = [u64('denominator'), u64('numerator')];
649
693
  var AccountType;
650
694
  (function (AccountType) {
@@ -806,80 +850,84 @@ async function getValidatorListAccount(connection, pubkey) {
806
850
  },
807
851
  };
808
852
  }
809
- async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount, compareFn, skipFee) {
853
+ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount, compareFn, skipFee, allowPartial, prefetchedData) {
810
854
  var _a, _b;
811
855
  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');
856
+ // Use prefetched data if available, otherwise fetch from RPC
857
+ let validatorListData;
858
+ let minBalanceForRentExemption;
859
+ let stakeMinimumDelegation;
860
+ if (prefetchedData) {
861
+ validatorListData = prefetchedData.validatorListData;
862
+ minBalanceForRentExemption = prefetchedData.minBalanceForRentExemption;
863
+ stakeMinimumDelegation = prefetchedData.stakeMinimumDelegation;
816
864
  }
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 = [];
865
+ else {
866
+ const [validatorListAcc, rentExemption, stakeMinimumDelegationResponse] = await Promise.all([
867
+ connection.getAccountInfo(stakePool.validatorList),
868
+ connection.getMinimumBalanceForRentExemption(StakeProgram.space),
869
+ connection.getStakeMinimumDelegation(),
870
+ ]);
871
+ validatorListData = (_a = validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data) !== null && _a !== void 0 ? _a : null;
872
+ minBalanceForRentExemption = rentExemption;
873
+ stakeMinimumDelegation = Number(stakeMinimumDelegationResponse.value);
874
+ }
875
+ if (!validatorListData) {
876
+ throw new Error('No staked funds available for delayed unstake. Use instant unstake instead.');
877
+ }
878
+ const validatorList = ValidatorListLayout.decode(validatorListData);
879
+ if (!(validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators) || (validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators.length) === 0) {
880
+ throw new Error('No staked funds available for delayed unstake. Use instant unstake instead.');
881
+ }
882
+ // minBalance = rent + max(stake_minimum_delegation, MINIMUM_ACTIVE_STAKE)
883
+ const minimumDelegation = Math.max(stakeMinimumDelegation, MINIMUM_ACTIVE_STAKE);
884
+ const minBalance = new BN(minBalanceForRentExemption + minimumDelegation);
885
+ // Threshold for has_active_stake check (ceiling division for lamports_per_pool_token)
886
+ const lamportsPerPoolToken = stakePool.totalLamports
887
+ .add(stakePool.poolTokenSupply)
888
+ .sub(new BN(1))
889
+ .div(stakePool.poolTokenSupply);
890
+ const minimumLamportsWithTolerance = minBalance.add(lamportsPerPoolToken);
891
+ const hasActiveStake = validatorList.validators.some(v => v.status === ValidatorStakeInfoStatus.Active
892
+ && v.activeStakeLamports.gt(minimumLamportsWithTolerance));
893
+ const hasTransientStake = validatorList.validators.some(v => v.status === ValidatorStakeInfoStatus.Active
894
+ && v.transientStakeLamports.gt(minimumLamportsWithTolerance));
895
+ // ValidatorRemoval mode: no validator above threshold
896
+ const isValidatorRemovalMode = !hasActiveStake && !hasTransientStake;
897
+ let accounts = [];
821
898
  for (const validator of validatorList.validators) {
822
899
  if (validator.status !== ValidatorStakeInfoStatus.Active) {
823
900
  continue;
824
901
  }
825
902
  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({
903
+ // ValidatorRemoval: full balance available; Normal: leave minBalance
904
+ const availableActiveLamports = isValidatorRemovalMode
905
+ ? validator.activeStakeLamports
906
+ : validator.activeStakeLamports.sub(minBalance);
907
+ if (availableActiveLamports.gt(new BN(0))) {
908
+ const isPreferred = (_b = stakePool === null || stakePool === void 0 ? void 0 : stakePool.preferredWithdrawValidatorVoteAddress) === null || _b === void 0 ? void 0 : _b.equals(validator.voteAccountAddress);
909
+ accounts.push({
830
910
  type: isPreferred ? 'preferred' : 'active',
831
911
  voteAddress: validator.voteAccountAddress,
832
912
  stakeAddress: stakeAccountAddress,
913
+ lamports: availableActiveLamports,
833
914
  });
834
915
  }
835
- // Add transient stake account if validator list indicates it has stake
836
- if (validator.transientStakeLamports.gt(new BN(0))) {
916
+ const availableTransientLamports = isValidatorRemovalMode
917
+ ? validator.transientStakeLamports
918
+ : validator.transientStakeLamports.sub(minBalance);
919
+ if (availableTransientLamports.gt(new BN(0))) {
837
920
  const transientStakeAccountAddress = await findTransientStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress, validator.transientSeedSuffixStart);
838
- accountsToFetch.push({
921
+ accounts.push({
839
922
  type: 'transient',
840
923
  voteAddress: validator.voteAccountAddress,
841
924
  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,
925
+ lamports: availableTransientLamports,
868
926
  });
869
927
  }
870
928
  }
871
929
  // Sort from highest to lowest balance
872
930
  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
931
  // Prepare the list of accounts to withdraw from
884
932
  const withdrawFrom = [];
885
933
  let remainingAmount = new BN(amount);
@@ -888,23 +936,24 @@ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress,
888
936
  numerator: fee.denominator.sub(fee.numerator),
889
937
  denominator: fee.denominator,
890
938
  };
891
- for (const type of ['preferred', 'active', 'transient', 'reserve']) {
939
+ for (const type of ['preferred', 'active', 'transient']) {
892
940
  const filteredAccounts = accounts.filter(a => a.type === type);
893
941
  for (const { stakeAddress, voteAddress, lamports } of filteredAccounts) {
894
- if (lamports.lte(minBalance) && type === 'transient') {
895
- continue;
896
- }
897
942
  let availableForWithdrawal = calcPoolTokensForDeposit(stakePool, lamports);
898
943
  if (!skipFee && !inverseFee.numerator.isZero()) {
899
944
  availableForWithdrawal = availableForWithdrawal
900
945
  .mul(inverseFee.denominator)
901
946
  .div(inverseFee.numerator);
902
947
  }
948
+ // In ValidatorRemoval mode, must withdraw full validator balance (no partial)
949
+ // Skip if remaining amount is less than full validator balance
950
+ if (isValidatorRemovalMode && remainingAmount.lt(availableForWithdrawal)) {
951
+ continue;
952
+ }
903
953
  const poolAmount = BN.min(availableForWithdrawal, remainingAmount);
904
954
  if (poolAmount.lte(new BN(0))) {
905
955
  continue;
906
956
  }
907
- // Those accounts will be withdrawn completely with `claim` instruction
908
957
  withdrawFrom.push({ stakeAddress, voteAddress, poolAmount });
909
958
  remainingAmount = remainingAmount.sub(poolAmount);
910
959
  if (remainingAmount.isZero()) {
@@ -917,7 +966,23 @@ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress,
917
966
  }
918
967
  // Not enough stake to withdraw the specified amount
919
968
  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.`);
969
+ if (allowPartial) {
970
+ const delayedAmount = amount.sub(remainingAmount);
971
+ return {
972
+ withdrawAccounts: withdrawFrom,
973
+ delayedAmount,
974
+ remainingAmount,
975
+ };
976
+ }
977
+ const availableAmount = amount.sub(remainingAmount);
978
+ 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.`);
979
+ }
980
+ if (allowPartial) {
981
+ return {
982
+ withdrawAccounts: withdrawFrom,
983
+ delayedAmount: amount,
984
+ remainingAmount: new BN(0),
985
+ };
921
986
  }
922
987
  return withdrawFrom;
923
988
  }
@@ -966,11 +1031,50 @@ function arrayChunk(array, size) {
966
1031
  return result;
967
1032
  }
968
1033
 
1034
+ /**
1035
+ * Converts various numeric types to BN for safe large number handling.
1036
+ * @internal
1037
+ */
1038
+ function toBN(value) {
1039
+ if (BN.isBN(value)) {
1040
+ return value;
1041
+ }
1042
+ if (typeof value === 'bigint') {
1043
+ return new BN(value.toString());
1044
+ }
1045
+ if (typeof value === 'string') {
1046
+ // Validate string is a valid non-negative integer
1047
+ const trimmed = value.trim();
1048
+ if (!/^\d+$/.test(trimmed)) {
1049
+ throw new Error(`Invalid amount string: "${value}". Must be a non-negative integer.`);
1050
+ }
1051
+ return new BN(trimmed);
1052
+ }
1053
+ if (typeof value === 'number') {
1054
+ if (!Number.isFinite(value)) {
1055
+ throw new Error('Invalid amount: must be a finite number');
1056
+ }
1057
+ if (value < 0) {
1058
+ throw new Error('Invalid amount: must be non-negative');
1059
+ }
1060
+ if (!Number.isInteger(value)) {
1061
+ throw new Error('Invalid amount: must be an integer (lamports)');
1062
+ }
1063
+ // CRITICAL: Numbers > MAX_SAFE_INTEGER have already lost precision
1064
+ // We throw an error instead of silently corrupting data
1065
+ if (value > Number.MAX_SAFE_INTEGER) {
1066
+ throw new Error(`Amount ${value} exceeds Number.MAX_SAFE_INTEGER (9,007,199,254,740,991). ` +
1067
+ `Use BigInt or BN for large values to avoid precision loss.`);
1068
+ }
1069
+ return new BN(value);
1070
+ }
1071
+ throw new Error(`Invalid amount type: ${typeof value}`);
1072
+ }
969
1073
  // 'UpdateTokenMetadata' and 'CreateTokenMetadata' have dynamic layouts
970
1074
  const MOVE_STAKE_LAYOUT = BufferLayout.struct([
971
1075
  BufferLayout.u8('instruction'),
972
- BufferLayout.ns64('lamports'),
973
- BufferLayout.ns64('transientStakeSeed'),
1076
+ u64Instruction('lamports'),
1077
+ u64Instruction('transientStakeSeed'),
974
1078
  ]);
975
1079
  const UPDATE_VALIDATOR_LIST_BALANCE_LAYOUT = BufferLayout.struct([
976
1080
  BufferLayout.u8('instruction'),
@@ -1045,7 +1149,7 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1045
1149
  index: 10,
1046
1150
  layout: BufferLayout.struct([
1047
1151
  BufferLayout.u8('instruction'),
1048
- BufferLayout.ns64('poolTokens'),
1152
+ u64Instruction('poolTokens'),
1049
1153
  ]),
1050
1154
  },
1051
1155
  /// Deposit SOL directly into the pool's reserve account. The output is a "pool" token
@@ -1054,7 +1158,7 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1054
1158
  index: 14,
1055
1159
  layout: BufferLayout.struct([
1056
1160
  BufferLayout.u8('instruction'),
1057
- BufferLayout.ns64('lamports'),
1161
+ u64Instruction('lamports'),
1058
1162
  ]),
1059
1163
  },
1060
1164
  /// Withdraw SOL directly from the pool's reserve account. Fails if the
@@ -1063,25 +1167,25 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1063
1167
  index: 16,
1064
1168
  layout: BufferLayout.struct([
1065
1169
  BufferLayout.u8('instruction'),
1066
- BufferLayout.ns64('poolTokens'),
1170
+ u64Instruction('poolTokens'),
1067
1171
  ]),
1068
1172
  },
1069
1173
  IncreaseAdditionalValidatorStake: {
1070
1174
  index: 19,
1071
1175
  layout: BufferLayout.struct([
1072
1176
  BufferLayout.u8('instruction'),
1073
- BufferLayout.ns64('lamports'),
1074
- BufferLayout.ns64('transientStakeSeed'),
1075
- BufferLayout.ns64('ephemeralStakeSeed'),
1177
+ u64Instruction('lamports'),
1178
+ u64Instruction('transientStakeSeed'),
1179
+ u64Instruction('ephemeralStakeSeed'),
1076
1180
  ]),
1077
1181
  },
1078
1182
  DecreaseAdditionalValidatorStake: {
1079
1183
  index: 20,
1080
1184
  layout: BufferLayout.struct([
1081
1185
  BufferLayout.u8('instruction'),
1082
- BufferLayout.ns64('lamports'),
1083
- BufferLayout.ns64('transientStakeSeed'),
1084
- BufferLayout.ns64('ephemeralStakeSeed'),
1186
+ u64Instruction('lamports'),
1187
+ u64Instruction('transientStakeSeed'),
1188
+ u64Instruction('ephemeralStakeSeed'),
1085
1189
  ]),
1086
1190
  },
1087
1191
  DecreaseValidatorStakeWithReserve: {
@@ -1096,62 +1200,62 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1096
1200
  index: 23,
1097
1201
  layout: BufferLayout.struct([
1098
1202
  BufferLayout.u8('instruction'),
1099
- BufferLayout.ns64('lamports'),
1203
+ u64Instruction('lamports'),
1100
1204
  ]),
1101
1205
  },
1102
1206
  WithdrawStakeWithSlippage: {
1103
1207
  index: 24,
1104
1208
  layout: BufferLayout.struct([
1105
1209
  BufferLayout.u8('instruction'),
1106
- BufferLayout.ns64('poolTokensIn'),
1107
- BufferLayout.ns64('minimumLamportsOut'),
1210
+ u64Instruction('poolTokensIn'),
1211
+ u64Instruction('minimumLamportsOut'),
1108
1212
  ]),
1109
1213
  },
1110
1214
  DepositSolWithSlippage: {
1111
1215
  index: 25,
1112
1216
  layout: BufferLayout.struct([
1113
1217
  BufferLayout.u8('instruction'),
1114
- BufferLayout.ns64('lamports'),
1218
+ u64Instruction('lamports'),
1115
1219
  ]),
1116
1220
  },
1117
1221
  WithdrawSolWithSlippage: {
1118
1222
  index: 26,
1119
1223
  layout: BufferLayout.struct([
1120
1224
  BufferLayout.u8('instruction'),
1121
- BufferLayout.ns64('lamports'),
1225
+ u64Instruction('lamports'),
1122
1226
  ]),
1123
1227
  },
1124
1228
  DepositWsolWithSession: {
1125
1229
  index: 27,
1126
1230
  layout: BufferLayout.struct([
1127
1231
  BufferLayout.u8('instruction'),
1128
- BufferLayout.ns64('lamportsIn'),
1129
- BufferLayout.ns64('minimumPoolTokensOut'),
1232
+ u64Instruction('lamportsIn'),
1233
+ u64Instruction('minimumPoolTokensOut'),
1130
1234
  ]),
1131
1235
  },
1132
1236
  WithdrawWsolWithSession: {
1133
1237
  index: 28,
1134
1238
  layout: BufferLayout.struct([
1135
1239
  BufferLayout.u8('instruction'),
1136
- BufferLayout.ns64('poolTokensIn'),
1137
- BufferLayout.ns64('minimumLamportsOut'),
1240
+ u64Instruction('poolTokensIn'),
1241
+ u64Instruction('minimumLamportsOut'),
1138
1242
  ]),
1139
1243
  },
1140
1244
  WithdrawStakeWithSession: {
1141
1245
  index: 29,
1142
1246
  layout: BufferLayout.struct([
1143
1247
  BufferLayout.u8('instruction'),
1144
- BufferLayout.ns64('poolTokensIn'),
1145
- BufferLayout.ns64('minimumLamportsOut'),
1146
- BufferLayout.ns64('userStakeSeed'),
1248
+ u64Instruction('poolTokensIn'),
1249
+ u64Instruction('minimumLamportsOut'),
1250
+ u64Instruction('userStakeSeed'),
1147
1251
  ]),
1148
1252
  },
1149
1253
  WithdrawFromStakeAccountWithSession: {
1150
1254
  index: 30,
1151
1255
  layout: BufferLayout.struct([
1152
1256
  BufferLayout.u8('instruction'),
1153
- BufferLayout.ns64('lamports'),
1154
- BufferLayout.ns64('userStakeSeed'),
1257
+ u64Instruction('lamports'),
1258
+ u64Instruction('userStakeSeed'),
1155
1259
  ]),
1156
1260
  },
1157
1261
  });
@@ -1283,7 +1387,10 @@ class StakePoolInstruction {
1283
1387
  static increaseValidatorStake(params) {
1284
1388
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, transientStake, validatorStake, validatorVote, lamports, transientStakeSeed, } = params;
1285
1389
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.IncreaseValidatorStake;
1286
- const data = encodeData(type, { lamports, transientStakeSeed });
1390
+ const data = encodeData(type, {
1391
+ lamports: toBN(lamports),
1392
+ transientStakeSeed: toBN(transientStakeSeed),
1393
+ });
1287
1394
  const keys = [
1288
1395
  { pubkey: stakePool, isSigner: false, isWritable: false },
1289
1396
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1313,7 +1420,11 @@ class StakePoolInstruction {
1313
1420
  static increaseAdditionalValidatorStake(params) {
1314
1421
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, transientStake, validatorStake, validatorVote, lamports, transientStakeSeed, ephemeralStake, ephemeralStakeSeed, } = params;
1315
1422
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.IncreaseAdditionalValidatorStake;
1316
- const data = encodeData(type, { lamports, transientStakeSeed, ephemeralStakeSeed });
1423
+ const data = encodeData(type, {
1424
+ lamports: toBN(lamports),
1425
+ transientStakeSeed: toBN(transientStakeSeed),
1426
+ ephemeralStakeSeed: toBN(ephemeralStakeSeed),
1427
+ });
1317
1428
  const keys = [
1318
1429
  { pubkey: stakePool, isSigner: false, isWritable: false },
1319
1430
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1343,7 +1454,10 @@ class StakePoolInstruction {
1343
1454
  static decreaseValidatorStake(params) {
1344
1455
  const { programId, stakePool, staker, withdrawAuthority, validatorList, validatorStake, transientStake, lamports, transientStakeSeed, } = params;
1345
1456
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseValidatorStake;
1346
- const data = encodeData(type, { lamports, transientStakeSeed });
1457
+ const data = encodeData(type, {
1458
+ lamports: toBN(lamports),
1459
+ transientStakeSeed: toBN(transientStakeSeed),
1460
+ });
1347
1461
  const keys = [
1348
1462
  { pubkey: stakePool, isSigner: false, isWritable: false },
1349
1463
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1369,7 +1483,10 @@ class StakePoolInstruction {
1369
1483
  static decreaseValidatorStakeWithReserve(params) {
1370
1484
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, validatorStake, transientStake, lamports, transientStakeSeed, } = params;
1371
1485
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseValidatorStakeWithReserve;
1372
- const data = encodeData(type, { lamports, transientStakeSeed });
1486
+ const data = encodeData(type, {
1487
+ lamports: toBN(lamports),
1488
+ transientStakeSeed: toBN(transientStakeSeed),
1489
+ });
1373
1490
  const keys = [
1374
1491
  { pubkey: stakePool, isSigner: false, isWritable: false },
1375
1492
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1396,7 +1513,11 @@ class StakePoolInstruction {
1396
1513
  static decreaseAdditionalValidatorStake(params) {
1397
1514
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, validatorStake, transientStake, lamports, transientStakeSeed, ephemeralStakeSeed, ephemeralStake, } = params;
1398
1515
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseAdditionalValidatorStake;
1399
- const data = encodeData(type, { lamports, transientStakeSeed, ephemeralStakeSeed });
1516
+ const data = encodeData(type, {
1517
+ lamports: toBN(lamports),
1518
+ transientStakeSeed: toBN(transientStakeSeed),
1519
+ ephemeralStakeSeed: toBN(ephemeralStakeSeed),
1520
+ });
1400
1521
  const keys = [
1401
1522
  { pubkey: stakePool, isSigner: false, isWritable: false },
1402
1523
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1453,7 +1574,7 @@ class StakePoolInstruction {
1453
1574
  static depositSol(params) {
1454
1575
  const { programId, stakePool, withdrawAuthority, depositAuthority, reserveStake, fundingAccount, destinationPoolAccount, managerFeeAccount, referralPoolAccount, poolMint, lamports, } = params;
1455
1576
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DepositSol;
1456
- const data = encodeData(type, { lamports });
1577
+ const data = encodeData(type, { lamports: toBN(lamports) });
1457
1578
  const keys = [
1458
1579
  { pubkey: stakePool, isSigner: false, isWritable: true },
1459
1580
  { pubkey: withdrawAuthority, isSigner: false, isWritable: false },
@@ -1486,8 +1607,8 @@ class StakePoolInstruction {
1486
1607
  var _a;
1487
1608
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DepositWsolWithSession;
1488
1609
  const data = encodeData(type, {
1489
- lamportsIn: params.lamportsIn,
1490
- minimumPoolTokensOut: params.minimumPoolTokensOut,
1610
+ lamportsIn: toBN(params.lamportsIn),
1611
+ minimumPoolTokensOut: toBN(params.minimumPoolTokensOut),
1491
1612
  });
1492
1613
  const keys = [
1493
1614
  { pubkey: params.stakePool, isSigner: false, isWritable: true },
@@ -1529,7 +1650,7 @@ class StakePoolInstruction {
1529
1650
  static withdrawStake(params) {
1530
1651
  const { programId, stakePool, validatorList, withdrawAuthority, validatorStake, destinationStake, destinationStakeAuthority, sourceTransferAuthority, sourcePoolAccount, managerFeeAccount, poolMint, poolTokens, } = params;
1531
1652
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStake;
1532
- const data = encodeData(type, { poolTokens });
1653
+ const data = encodeData(type, { poolTokens: toBN(poolTokens) });
1533
1654
  const keys = [
1534
1655
  { pubkey: stakePool, isSigner: false, isWritable: true },
1535
1656
  { pubkey: validatorList, isSigner: false, isWritable: true },
@@ -1557,7 +1678,7 @@ class StakePoolInstruction {
1557
1678
  static withdrawSol(params) {
1558
1679
  const { programId, stakePool, withdrawAuthority, sourceTransferAuthority, sourcePoolAccount, reserveStake, destinationSystemAccount, managerFeeAccount, solWithdrawAuthority, poolMint, poolTokens, } = params;
1559
1680
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawSol;
1560
- const data = encodeData(type, { poolTokens });
1681
+ const data = encodeData(type, { poolTokens: toBN(poolTokens) });
1561
1682
  const keys = [
1562
1683
  { pubkey: stakePool, isSigner: false, isWritable: true },
1563
1684
  { pubkey: withdrawAuthority, isSigner: false, isWritable: false },
@@ -1592,8 +1713,8 @@ class StakePoolInstruction {
1592
1713
  static withdrawWsolWithSession(params) {
1593
1714
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawWsolWithSession;
1594
1715
  const data = encodeData(type, {
1595
- poolTokensIn: params.poolTokensIn,
1596
- minimumLamportsOut: params.minimumLamportsOut,
1716
+ poolTokensIn: toBN(params.poolTokensIn),
1717
+ minimumLamportsOut: toBN(params.minimumLamportsOut),
1597
1718
  });
1598
1719
  const keys = [
1599
1720
  { pubkey: params.stakePool, isSigner: false, isWritable: true },
@@ -1630,14 +1751,14 @@ class StakePoolInstruction {
1630
1751
  }
1631
1752
  /**
1632
1753
  * 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).
1754
+ * The stake account is created as a PDA and rent is funded from the reserve.
1634
1755
  */
1635
1756
  static withdrawStakeWithSession(params) {
1636
1757
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStakeWithSession;
1637
1758
  const data = encodeData(type, {
1638
- poolTokensIn: params.poolTokensIn,
1639
- minimumLamportsOut: params.minimumLamportsOut,
1640
- userStakeSeed: params.userStakeSeed,
1759
+ poolTokensIn: toBN(params.poolTokensIn),
1760
+ minimumLamportsOut: toBN(params.minimumLamportsOut),
1761
+ userStakeSeed: toBN(params.userStakeSeed),
1641
1762
  });
1642
1763
  const keys = [
1643
1764
  { pubkey: params.stakePool, isSigner: false, isWritable: true },
@@ -1645,17 +1766,19 @@ class StakePoolInstruction {
1645
1766
  { pubkey: params.withdrawAuthority, isSigner: false, isWritable: false },
1646
1767
  { pubkey: params.stakeToSplit, isSigner: false, isWritable: true },
1647
1768
  { 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)
1769
+ { pubkey: params.sessionSigner, isSigner: true, isWritable: false }, // user_stake_authority_info
1770
+ { pubkey: params.sessionSigner, isSigner: false, isWritable: false }, // user_transfer_authority_info (unused in session path)
1650
1771
  { pubkey: params.burnFromPool, isSigner: false, isWritable: true },
1651
1772
  { pubkey: params.managerFeeAccount, isSigner: false, isWritable: true },
1652
1773
  { pubkey: params.poolMint, isSigner: false, isWritable: true },
1653
1774
  { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
1654
1775
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1655
1776
  { pubkey: StakeProgram.programId, isSigner: false, isWritable: false },
1777
+ // Session-specific accounts
1656
1778
  { pubkey: params.programSigner, isSigner: false, isWritable: false },
1657
1779
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1658
- { pubkey: params.payer, isSigner: true, isWritable: true },
1780
+ { pubkey: params.reserveStake, isSigner: false, isWritable: true },
1781
+ { pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
1659
1782
  ];
1660
1783
  return new TransactionInstruction({
1661
1784
  programId: params.programId,
@@ -1670,8 +1793,8 @@ class StakePoolInstruction {
1670
1793
  static withdrawFromStakeAccountWithSession(params) {
1671
1794
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawFromStakeAccountWithSession;
1672
1795
  const data = encodeData(type, {
1673
- lamports: params.lamports,
1674
- userStakeSeed: params.userStakeSeed,
1796
+ lamports: toBN(params.lamports),
1797
+ userStakeSeed: toBN(params.userStakeSeed),
1675
1798
  });
1676
1799
  const keys = [
1677
1800
  { pubkey: params.userStakeAccount, isSigner: false, isWritable: true },
@@ -1960,10 +2083,18 @@ skipBalanceCheck = false) {
1960
2083
  if (!skipBalanceCheck) {
1961
2084
  const tokenAccountInfo = await connection.getTokenAccountBalance(wsolTokenAccount, 'confirmed');
1962
2085
  const wsolBalance = tokenAccountInfo
1963
- ? parseInt(tokenAccountInfo.value.amount)
1964
- : 0;
1965
- if (wsolBalance < lamports) {
1966
- throw new Error(`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(wsolBalance)} WSOL.`);
2086
+ ? BigInt(tokenAccountInfo.value.amount)
2087
+ : BigInt(0);
2088
+ // Convert lamports to BigInt for comparison
2089
+ const lamportsBigInt = typeof lamports === 'bigint'
2090
+ ? lamports
2091
+ : typeof lamports === 'string'
2092
+ ? BigInt(lamports)
2093
+ : BN.isBN(lamports)
2094
+ ? BigInt(lamports.toString())
2095
+ : BigInt(lamports);
2096
+ if (wsolBalance < lamportsBigInt) {
2097
+ throw new Error(`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(Number(wsolBalance))} WSOL.`);
1967
2098
  }
1968
2099
  }
1969
2100
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
@@ -2009,7 +2140,15 @@ skipBalanceCheck = false) {
2009
2140
  */
2010
2141
  async function depositSol(connection, stakePoolAddress, from, lamports, destinationTokenAccount, referrerTokenAccount, depositAuthority) {
2011
2142
  const fromBalance = await connection.getBalance(from, 'confirmed');
2012
- if (fromBalance < lamports) {
2143
+ // Convert lamports to BigInt for comparison
2144
+ const lamportsBigInt = typeof lamports === 'bigint'
2145
+ ? lamports
2146
+ : typeof lamports === 'string'
2147
+ ? BigInt(lamports)
2148
+ : BN.isBN(lamports)
2149
+ ? BigInt(lamports.toString())
2150
+ : BigInt(lamports);
2151
+ if (BigInt(fromBalance) < lamportsBigInt) {
2013
2152
  throw new Error(`Not enough SOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(fromBalance)} SOL.`);
2014
2153
  }
2015
2154
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
@@ -2023,7 +2162,7 @@ async function depositSol(connection, stakePoolAddress, from, lamports, destinat
2023
2162
  instructions.push(SystemProgram.transfer({
2024
2163
  fromPubkey: from,
2025
2164
  toPubkey: userSolTransfer.publicKey,
2026
- lamports,
2165
+ lamports: lamportsBigInt,
2027
2166
  }));
2028
2167
  // Create token account if not specified
2029
2168
  if (!destinationTokenAccount) {
@@ -2395,37 +2534,51 @@ async function getUserStakeAccounts(connection, programId, userPubkey, maxSeed =
2395
2534
  * Withdraws stake from a stake pool using a Fogo session.
2396
2535
  *
2397
2536
  * 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.
2537
+ * is funded from the reserve stake.
2399
2538
  *
2400
2539
  * @param connection - Solana connection
2401
2540
  * @param stakePoolAddress - The stake pool to withdraw from
2402
2541
  * @param signerOrSession - The session signer public key
2403
2542
  * @param userPubkey - User's wallet (used for PDA derivation and token ownership)
2404
- * @param payer - Payer for stake account rent (typically paymaster)
2405
2543
  * @param amount - Amount of pool tokens to withdraw
2406
2544
  * @param userStakeSeedStart - Starting seed for user stake PDA derivation (default: 0)
2407
2545
  * @param useReserve - Whether to withdraw from reserve (default: false)
2408
2546
  * @param voteAccountAddress - Optional specific validator to withdraw from
2409
2547
  * @param minimumLamportsOut - Minimum lamports to receive (slippage protection)
2410
2548
  * @param validatorComparator - Optional comparator for validator selection
2549
+ * @param allowPartial - If true, returns partial results instead of throwing when not enough stake available
2411
2550
  */
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);
2551
+ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, userStakeSeedStart = 0, useReserve = false, voteAccountAddress, minimumLamportsOut = 0, validatorComparator, allowPartial = false) {
2552
+ var _c;
2414
2553
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
2554
+ // First fetch: get stake pool to know other account addresses
2555
+ const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2415
2556
  const stakePool = stakePoolAccount.account.data;
2416
2557
  const poolTokens = solToLamports(amount);
2417
2558
  const poolAmount = new BN(poolTokens);
2418
2559
  const poolTokenAccount = getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey);
2419
- const tokenAccount = await getAccount(connection, poolTokenAccount);
2560
+ // Second fetch: get ALL remaining data in parallel
2561
+ const [tokenAccount, stakeAccountRentExemption, validatorListAcc, stakeMinimumDelegationResponse] = await Promise.all([
2562
+ getAccount(connection, poolTokenAccount),
2563
+ connection.getMinimumBalanceForRentExemption(StakeProgram.space),
2564
+ connection.getAccountInfo(stakePool.validatorList),
2565
+ connection.getStakeMinimumDelegation(),
2566
+ ]);
2567
+ // Pre-fetch data to avoid duplicate RPC calls in prepareWithdrawAccounts
2568
+ const prefetchedData = {
2569
+ validatorListData: (_c = validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data) !== null && _c !== void 0 ? _c : null,
2570
+ minBalanceForRentExemption: stakeAccountRentExemption,
2571
+ stakeMinimumDelegation: Number(stakeMinimumDelegationResponse.value),
2572
+ };
2420
2573
  if (tokenAccount.amount < poolTokens) {
2421
2574
  throw new Error(`Not enough token balance to withdraw ${amount} pool tokens.
2422
2575
  Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`);
2423
2576
  }
2424
2577
  const [programSigner] = PublicKey.findProgramAddressSync([Buffer.from('fogo_session_program_signer')], stakePoolProgramId);
2425
2578
  const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
2426
- const stakeAccountRentExemption = await connection.getMinimumBalanceForRentExemption(StakeProgram.space);
2427
2579
  // Determine which stake accounts to withdraw from
2428
2580
  const withdrawAccounts = [];
2581
+ let partialRemainingAmount;
2429
2582
  if (useReserve) {
2430
2583
  withdrawAccounts.push({
2431
2584
  stakeAddress: stakePool.reserveStake,
@@ -2456,7 +2609,14 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2456
2609
  }
2457
2610
  else {
2458
2611
  // Get the list of accounts to withdraw from automatically
2459
- withdrawAccounts.push(...(await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount))));
2612
+ if (allowPartial) {
2613
+ const result = await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount), true, prefetchedData);
2614
+ withdrawAccounts.push(...result.withdrawAccounts);
2615
+ partialRemainingAmount = result.remainingAmount;
2616
+ }
2617
+ else {
2618
+ withdrawAccounts.push(...(await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount), undefined, prefetchedData)));
2619
+ }
2460
2620
  }
2461
2621
  const instructions = [];
2462
2622
  const stakeAccountPubkeys = [];
@@ -2473,7 +2633,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2473
2633
  const stakeReceiverPubkey = findUserStakeProgramAddress(stakePoolProgramId, userPubkey, userStakeSeed);
2474
2634
  stakeAccountPubkeys.push(stakeReceiverPubkey);
2475
2635
  userStakeSeeds.push(userStakeSeed);
2476
- // The on-chain program creates the stake account PDA and rent is paid by payer.
2636
+ // The on-chain program creates the stake account PDA and rent is funded from reserve.
2477
2637
  instructions.push(StakePoolInstruction.withdrawStakeWithSession({
2478
2638
  programId: stakePoolProgramId,
2479
2639
  stakePool: stakePoolAddress,
@@ -2487,7 +2647,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2487
2647
  poolMint: stakePool.poolMint,
2488
2648
  tokenProgramId: stakePool.tokenProgramId,
2489
2649
  programSigner,
2490
- payer,
2650
+ reserveStake: stakePool.reserveStake,
2491
2651
  poolTokensIn: withdrawAccount.poolAmount.toNumber(),
2492
2652
  minimumLamportsOut,
2493
2653
  userStakeSeed,
@@ -2498,6 +2658,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2498
2658
  instructions,
2499
2659
  stakeAccountPubkeys,
2500
2660
  userStakeSeeds,
2661
+ remainingPoolTokens: partialRemainingAmount ? lamportsToSol(partialRemainingAmount) : 0,
2501
2662
  };
2502
2663
  }
2503
2664
  async function addValidatorToPool(connection, stakePoolAddress, validatorVote, seed) {