@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.cjs.js CHANGED
@@ -459,6 +459,139 @@ const USER_STAKE_SEED_PREFIX = node_buffer.Buffer.from('user_stake');
459
459
  // for merges without a mismatch on credits observed
460
460
  const MINIMUM_ACTIVE_STAKE = 1000000;
461
461
 
462
+ /**
463
+ * BN-based layout for data decoding using buffer-layout.
464
+ * Used for decoding on-chain account data (StakePool, ValidatorList, etc.)
465
+ */
466
+ class BNDataLayout extends bufferLayout.Layout {
467
+ constructor(span, signed, property) {
468
+ super(span, property);
469
+ this.blobLayout = bufferLayout.blob(span);
470
+ this.signed = signed;
471
+ }
472
+ decode(b, offset = 0) {
473
+ const num = new BN(this.blobLayout.decode(b, offset), 10, 'le');
474
+ if (this.signed) {
475
+ return num.fromTwos(this.span * 8).clone();
476
+ }
477
+ return num;
478
+ }
479
+ encode(src, b, offset = 0) {
480
+ if (this.signed) {
481
+ src = src.toTwos(this.span * 8);
482
+ }
483
+ return this.blobLayout.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
484
+ }
485
+ }
486
+ /**
487
+ * Creates a u64 layout for data decoding (account layouts).
488
+ * Used in StakePoolLayout, ValidatorListLayout, etc.
489
+ */
490
+ function u64(property) {
491
+ return new BNDataLayout(8, false, property);
492
+ }
493
+ /**
494
+ * BN-based layout for 64-bit unsigned integers using @solana/buffer-layout.
495
+ * Used for encoding instruction data with support for values > MAX_SAFE_INTEGER.
496
+ */
497
+ class BNInstructionLayout extends BufferLayout__namespace.Layout {
498
+ constructor(span, signed, property) {
499
+ super(span, property);
500
+ this.blobLayout = BufferLayout__namespace.blob(span);
501
+ this.signed = signed;
502
+ }
503
+ decode(b, offset = 0) {
504
+ const num = new BN(this.blobLayout.decode(b, offset), 10, 'le');
505
+ if (this.signed) {
506
+ return num.fromTwos(this.span * 8).clone();
507
+ }
508
+ return num;
509
+ }
510
+ encode(src, b, offset = 0) {
511
+ if (this.signed) {
512
+ src = src.toTwos(this.span * 8);
513
+ }
514
+ return this.blobLayout.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
515
+ }
516
+ getSpan(_b, _offset) {
517
+ return this.span;
518
+ }
519
+ }
520
+ /**
521
+ * Creates a u64 layout for instruction encoding.
522
+ * Properly handles BN values larger than Number.MAX_SAFE_INTEGER.
523
+ * Compatible with @solana/buffer-layout.struct().
524
+ */
525
+ // eslint-disable-next-line ts/no-explicit-any
526
+ function u64Instruction(property) {
527
+ return new BNInstructionLayout(8, false, property);
528
+ }
529
+ class WrappedLayout extends bufferLayout.Layout {
530
+ constructor(layout, decoder, encoder, property) {
531
+ super(layout.span, property);
532
+ this.layout = layout;
533
+ this.decoder = decoder;
534
+ this.encoder = encoder;
535
+ }
536
+ decode(b, offset) {
537
+ return this.decoder(this.layout.decode(b, offset));
538
+ }
539
+ encode(src, b, offset) {
540
+ return this.layout.encode(this.encoder(src), b, offset);
541
+ }
542
+ getSpan(b, offset) {
543
+ return this.layout.getSpan(b, offset);
544
+ }
545
+ }
546
+ function publicKey(property) {
547
+ return new WrappedLayout(bufferLayout.blob(32), (b) => new web3_js.PublicKey(b), (key) => key.toBuffer(), property);
548
+ }
549
+ class OptionLayout extends bufferLayout.Layout {
550
+ constructor(layout, property) {
551
+ super(-1, property);
552
+ this.layout = layout;
553
+ this.discriminator = bufferLayout.u8();
554
+ }
555
+ encode(src, b, offset = 0) {
556
+ if (src === null || src === undefined) {
557
+ return this.discriminator.encode(0, b, offset);
558
+ }
559
+ this.discriminator.encode(1, b, offset);
560
+ return this.layout.encode(src, b, offset + 1) + 1;
561
+ }
562
+ decode(b, offset = 0) {
563
+ const discriminator = this.discriminator.decode(b, offset);
564
+ if (discriminator === 0) {
565
+ return null;
566
+ }
567
+ else if (discriminator === 1) {
568
+ return this.layout.decode(b, offset + 1);
569
+ }
570
+ throw new Error(`Invalid option ${this.property}`);
571
+ }
572
+ getSpan(b, offset = 0) {
573
+ const discriminator = this.discriminator.decode(b, offset);
574
+ if (discriminator === 0) {
575
+ return 1;
576
+ }
577
+ else if (discriminator === 1) {
578
+ return this.layout.getSpan(b, offset + 1) + 1;
579
+ }
580
+ throw new Error(`Invalid option ${this.property}`);
581
+ }
582
+ }
583
+ function option(layout, property) {
584
+ return new OptionLayout(layout, property);
585
+ }
586
+ function vec(elementLayout, property) {
587
+ const length = bufferLayout.u32('length');
588
+ const layout = bufferLayout.struct([
589
+ length,
590
+ bufferLayout.seq(elementLayout, bufferLayout.offset(length, -length.span), 'values'),
591
+ ]);
592
+ return new WrappedLayout(layout, ({ values }) => values, values => ({ values }), property);
593
+ }
594
+
462
595
  /**
463
596
  * Populate a buffer of instruction data using an InstructionType
464
597
  * @internal
@@ -577,95 +710,6 @@ function findUserStakeProgramAddress(programId, userWallet, seed) {
577
710
  return publicKey;
578
711
  }
579
712
 
580
- class BNLayout extends bufferLayout.Layout {
581
- constructor(span, signed, property) {
582
- super(span, property);
583
- this.blob = bufferLayout.blob(span);
584
- this.signed = signed;
585
- }
586
- decode(b, offset = 0) {
587
- const num = new BN(this.blob.decode(b, offset), 10, 'le');
588
- if (this.signed) {
589
- return num.fromTwos(this.span * 8).clone();
590
- }
591
- return num;
592
- }
593
- encode(src, b, offset = 0) {
594
- if (this.signed) {
595
- src = src.toTwos(this.span * 8);
596
- }
597
- return this.blob.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset);
598
- }
599
- }
600
- function u64(property) {
601
- return new BNLayout(8, false, property);
602
- }
603
- class WrappedLayout extends bufferLayout.Layout {
604
- constructor(layout, decoder, encoder, property) {
605
- super(layout.span, property);
606
- this.layout = layout;
607
- this.decoder = decoder;
608
- this.encoder = encoder;
609
- }
610
- decode(b, offset) {
611
- return this.decoder(this.layout.decode(b, offset));
612
- }
613
- encode(src, b, offset) {
614
- return this.layout.encode(this.encoder(src), b, offset);
615
- }
616
- getSpan(b, offset) {
617
- return this.layout.getSpan(b, offset);
618
- }
619
- }
620
- function publicKey(property) {
621
- return new WrappedLayout(bufferLayout.blob(32), (b) => new web3_js.PublicKey(b), (key) => key.toBuffer(), property);
622
- }
623
- class OptionLayout extends bufferLayout.Layout {
624
- constructor(layout, property) {
625
- super(-1, property);
626
- this.layout = layout;
627
- this.discriminator = bufferLayout.u8();
628
- }
629
- encode(src, b, offset = 0) {
630
- if (src === null || src === undefined) {
631
- return this.discriminator.encode(0, b, offset);
632
- }
633
- this.discriminator.encode(1, b, offset);
634
- return this.layout.encode(src, b, offset + 1) + 1;
635
- }
636
- decode(b, offset = 0) {
637
- const discriminator = this.discriminator.decode(b, offset);
638
- if (discriminator === 0) {
639
- return null;
640
- }
641
- else if (discriminator === 1) {
642
- return this.layout.decode(b, offset + 1);
643
- }
644
- throw new Error(`Invalid option ${this.property}`);
645
- }
646
- getSpan(b, offset = 0) {
647
- const discriminator = this.discriminator.decode(b, offset);
648
- if (discriminator === 0) {
649
- return 1;
650
- }
651
- else if (discriminator === 1) {
652
- return this.layout.getSpan(b, offset + 1) + 1;
653
- }
654
- throw new Error(`Invalid option ${this.property}`);
655
- }
656
- }
657
- function option(layout, property) {
658
- return new OptionLayout(layout, property);
659
- }
660
- function vec(elementLayout, property) {
661
- const length = bufferLayout.u32('length');
662
- const layout = bufferLayout.struct([
663
- length,
664
- bufferLayout.seq(elementLayout, bufferLayout.offset(length, -length.span), 'values'),
665
- ]);
666
- return new WrappedLayout(layout, ({ values }) => values, values => ({ values }), property);
667
- }
668
-
669
713
  const feeFields = [u64('denominator'), u64('numerator')];
670
714
  var AccountType;
671
715
  (function (AccountType) {
@@ -827,80 +871,84 @@ async function getValidatorListAccount(connection, pubkey) {
827
871
  },
828
872
  };
829
873
  }
830
- async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount, compareFn, skipFee) {
874
+ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, amount, compareFn, skipFee, allowPartial, prefetchedData) {
831
875
  var _a, _b;
832
876
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
833
- const validatorListAcc = await connection.getAccountInfo(stakePool.validatorList);
834
- const validatorList = ValidatorListLayout.decode(validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data);
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 accounts found');
877
+ // Use prefetched data if available, otherwise fetch from RPC
878
+ let validatorListData;
879
+ let minBalanceForRentExemption;
880
+ let stakeMinimumDelegation;
881
+ if (prefetchedData) {
882
+ validatorListData = prefetchedData.validatorListData;
883
+ minBalanceForRentExemption = prefetchedData.minBalanceForRentExemption;
884
+ stakeMinimumDelegation = prefetchedData.stakeMinimumDelegation;
837
885
  }
838
- const minBalanceForRentExemption = await connection.getMinimumBalanceForRentExemption(web3_js.StakeProgram.space);
839
- const minBalance = new BN(minBalanceForRentExemption + MINIMUM_ACTIVE_STAKE);
840
- // First, collect all stake account addresses we need to check
841
- const accountsToFetch = [];
886
+ else {
887
+ const [validatorListAcc, rentExemption, stakeMinimumDelegationResponse] = await Promise.all([
888
+ connection.getAccountInfo(stakePool.validatorList),
889
+ connection.getMinimumBalanceForRentExemption(web3_js.StakeProgram.space),
890
+ connection.getStakeMinimumDelegation(),
891
+ ]);
892
+ validatorListData = (_a = validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data) !== null && _a !== void 0 ? _a : null;
893
+ minBalanceForRentExemption = rentExemption;
894
+ stakeMinimumDelegation = Number(stakeMinimumDelegationResponse.value);
895
+ }
896
+ if (!validatorListData) {
897
+ throw new Error('No staked funds available for delayed unstake. Use instant unstake instead.');
898
+ }
899
+ const validatorList = ValidatorListLayout.decode(validatorListData);
900
+ if (!(validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators) || (validatorList === null || validatorList === void 0 ? void 0 : validatorList.validators.length) === 0) {
901
+ throw new Error('No staked funds available for delayed unstake. Use instant unstake instead.');
902
+ }
903
+ // minBalance = rent + max(stake_minimum_delegation, MINIMUM_ACTIVE_STAKE)
904
+ const minimumDelegation = Math.max(stakeMinimumDelegation, MINIMUM_ACTIVE_STAKE);
905
+ const minBalance = new BN(minBalanceForRentExemption + minimumDelegation);
906
+ // Threshold for has_active_stake check (ceiling division for lamports_per_pool_token)
907
+ const lamportsPerPoolToken = stakePool.totalLamports
908
+ .add(stakePool.poolTokenSupply)
909
+ .sub(new BN(1))
910
+ .div(stakePool.poolTokenSupply);
911
+ const minimumLamportsWithTolerance = minBalance.add(lamportsPerPoolToken);
912
+ const hasActiveStake = validatorList.validators.some(v => v.status === ValidatorStakeInfoStatus.Active
913
+ && v.activeStakeLamports.gt(minimumLamportsWithTolerance));
914
+ const hasTransientStake = validatorList.validators.some(v => v.status === ValidatorStakeInfoStatus.Active
915
+ && v.transientStakeLamports.gt(minimumLamportsWithTolerance));
916
+ // ValidatorRemoval mode: no validator above threshold
917
+ const isValidatorRemovalMode = !hasActiveStake && !hasTransientStake;
918
+ let accounts = [];
842
919
  for (const validator of validatorList.validators) {
843
920
  if (validator.status !== ValidatorStakeInfoStatus.Active) {
844
921
  continue;
845
922
  }
846
923
  const stakeAccountAddress = await findStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress);
847
- const isPreferred = (_a = stakePool === null || stakePool === void 0 ? void 0 : stakePool.preferredWithdrawValidatorVoteAddress) === null || _a === void 0 ? void 0 : _a.equals(validator.voteAccountAddress);
848
- // Add active stake account if validator list indicates it has stake
849
- if (validator.activeStakeLamports.gt(new BN(0))) {
850
- accountsToFetch.push({
924
+ // ValidatorRemoval: full balance available; Normal: leave minBalance
925
+ const availableActiveLamports = isValidatorRemovalMode
926
+ ? validator.activeStakeLamports
927
+ : validator.activeStakeLamports.sub(minBalance);
928
+ if (availableActiveLamports.gt(new BN(0))) {
929
+ const isPreferred = (_b = stakePool === null || stakePool === void 0 ? void 0 : stakePool.preferredWithdrawValidatorVoteAddress) === null || _b === void 0 ? void 0 : _b.equals(validator.voteAccountAddress);
930
+ accounts.push({
851
931
  type: isPreferred ? 'preferred' : 'active',
852
932
  voteAddress: validator.voteAccountAddress,
853
933
  stakeAddress: stakeAccountAddress,
934
+ lamports: availableActiveLamports,
854
935
  });
855
936
  }
856
- // Add transient stake account if validator list indicates it has stake
857
- if (validator.transientStakeLamports.gt(new BN(0))) {
937
+ const availableTransientLamports = isValidatorRemovalMode
938
+ ? validator.transientStakeLamports
939
+ : validator.transientStakeLamports.sub(minBalance);
940
+ if (availableTransientLamports.gt(new BN(0))) {
858
941
  const transientStakeAccountAddress = await findTransientStakeProgramAddress(stakePoolProgramId, validator.voteAccountAddress, stakePoolAddress, validator.transientSeedSuffixStart);
859
- accountsToFetch.push({
942
+ accounts.push({
860
943
  type: 'transient',
861
944
  voteAddress: validator.voteAccountAddress,
862
945
  stakeAddress: transientStakeAccountAddress,
863
- });
864
- }
865
- }
866
- // Fetch all stake accounts + reserve in one batch call
867
- const addressesToFetch = [
868
- ...accountsToFetch.map(a => a.stakeAddress),
869
- stakePool.reserveStake,
870
- ];
871
- const accountInfos = await connection.getMultipleAccountsInfo(addressesToFetch);
872
- // Build accounts list using actual on-chain balances
873
- let accounts = [];
874
- for (let i = 0; i < accountsToFetch.length; i++) {
875
- const { type, voteAddress, stakeAddress } = accountsToFetch[i];
876
- const accountInfo = accountInfos[i];
877
- if (!accountInfo) {
878
- continue;
879
- }
880
- // Use actual on-chain balance instead of validator list value
881
- const actualLamports = new BN(accountInfo.lamports);
882
- const availableLamports = actualLamports.sub(minBalance);
883
- if (availableLamports.gt(new BN(0))) {
884
- accounts.push({
885
- type,
886
- voteAddress,
887
- stakeAddress,
888
- lamports: availableLamports,
946
+ lamports: availableTransientLamports,
889
947
  });
890
948
  }
891
949
  }
892
950
  // Sort from highest to lowest balance
893
951
  accounts = accounts.sort(compareFn || ((a, b) => b.lamports.sub(a.lamports).toNumber()));
894
- // Add reserve stake using actual balance (last item in batch fetch)
895
- const reserveAccountInfo = accountInfos[accountInfos.length - 1];
896
- const reserveStakeBalance = new BN(((_b = reserveAccountInfo === null || reserveAccountInfo === void 0 ? void 0 : reserveAccountInfo.lamports) !== null && _b !== void 0 ? _b : 0) - minBalanceForRentExemption);
897
- if (reserveStakeBalance.gt(new BN(0))) {
898
- accounts.push({
899
- type: 'reserve',
900
- stakeAddress: stakePool.reserveStake,
901
- lamports: reserveStakeBalance,
902
- });
903
- }
904
952
  // Prepare the list of accounts to withdraw from
905
953
  const withdrawFrom = [];
906
954
  let remainingAmount = new BN(amount);
@@ -909,23 +957,24 @@ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress,
909
957
  numerator: fee.denominator.sub(fee.numerator),
910
958
  denominator: fee.denominator,
911
959
  };
912
- for (const type of ['preferred', 'active', 'transient', 'reserve']) {
960
+ for (const type of ['preferred', 'active', 'transient']) {
913
961
  const filteredAccounts = accounts.filter(a => a.type === type);
914
962
  for (const { stakeAddress, voteAddress, lamports } of filteredAccounts) {
915
- if (lamports.lte(minBalance) && type === 'transient') {
916
- continue;
917
- }
918
963
  let availableForWithdrawal = calcPoolTokensForDeposit(stakePool, lamports);
919
964
  if (!skipFee && !inverseFee.numerator.isZero()) {
920
965
  availableForWithdrawal = availableForWithdrawal
921
966
  .mul(inverseFee.denominator)
922
967
  .div(inverseFee.numerator);
923
968
  }
969
+ // In ValidatorRemoval mode, must withdraw full validator balance (no partial)
970
+ // Skip if remaining amount is less than full validator balance
971
+ if (isValidatorRemovalMode && remainingAmount.lt(availableForWithdrawal)) {
972
+ continue;
973
+ }
924
974
  const poolAmount = BN.min(availableForWithdrawal, remainingAmount);
925
975
  if (poolAmount.lte(new BN(0))) {
926
976
  continue;
927
977
  }
928
- // Those accounts will be withdrawn completely with `claim` instruction
929
978
  withdrawFrom.push({ stakeAddress, voteAddress, poolAmount });
930
979
  remainingAmount = remainingAmount.sub(poolAmount);
931
980
  if (remainingAmount.isZero()) {
@@ -938,7 +987,23 @@ async function prepareWithdrawAccounts(connection, stakePool, stakePoolAddress,
938
987
  }
939
988
  // Not enough stake to withdraw the specified amount
940
989
  if (remainingAmount.gt(new BN(0))) {
941
- throw new Error(`No stake accounts found in this pool with enough balance to withdraw ${lamportsToSol(amount)} pool tokens.`);
990
+ if (allowPartial) {
991
+ const delayedAmount = amount.sub(remainingAmount);
992
+ return {
993
+ withdrawAccounts: withdrawFrom,
994
+ delayedAmount,
995
+ remainingAmount,
996
+ };
997
+ }
998
+ const availableAmount = amount.sub(remainingAmount);
999
+ 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.`);
1000
+ }
1001
+ if (allowPartial) {
1002
+ return {
1003
+ withdrawAccounts: withdrawFrom,
1004
+ delayedAmount: amount,
1005
+ remainingAmount: new BN(0),
1006
+ };
942
1007
  }
943
1008
  return withdrawFrom;
944
1009
  }
@@ -987,11 +1052,50 @@ function arrayChunk(array, size) {
987
1052
  return result;
988
1053
  }
989
1054
 
1055
+ /**
1056
+ * Converts various numeric types to BN for safe large number handling.
1057
+ * @internal
1058
+ */
1059
+ function toBN(value) {
1060
+ if (BN.isBN(value)) {
1061
+ return value;
1062
+ }
1063
+ if (typeof value === 'bigint') {
1064
+ return new BN(value.toString());
1065
+ }
1066
+ if (typeof value === 'string') {
1067
+ // Validate string is a valid non-negative integer
1068
+ const trimmed = value.trim();
1069
+ if (!/^\d+$/.test(trimmed)) {
1070
+ throw new Error(`Invalid amount string: "${value}". Must be a non-negative integer.`);
1071
+ }
1072
+ return new BN(trimmed);
1073
+ }
1074
+ if (typeof value === 'number') {
1075
+ if (!Number.isFinite(value)) {
1076
+ throw new Error('Invalid amount: must be a finite number');
1077
+ }
1078
+ if (value < 0) {
1079
+ throw new Error('Invalid amount: must be non-negative');
1080
+ }
1081
+ if (!Number.isInteger(value)) {
1082
+ throw new Error('Invalid amount: must be an integer (lamports)');
1083
+ }
1084
+ // CRITICAL: Numbers > MAX_SAFE_INTEGER have already lost precision
1085
+ // We throw an error instead of silently corrupting data
1086
+ if (value > Number.MAX_SAFE_INTEGER) {
1087
+ throw new Error(`Amount ${value} exceeds Number.MAX_SAFE_INTEGER (9,007,199,254,740,991). ` +
1088
+ `Use BigInt or BN for large values to avoid precision loss.`);
1089
+ }
1090
+ return new BN(value);
1091
+ }
1092
+ throw new Error(`Invalid amount type: ${typeof value}`);
1093
+ }
990
1094
  // 'UpdateTokenMetadata' and 'CreateTokenMetadata' have dynamic layouts
991
1095
  const MOVE_STAKE_LAYOUT = BufferLayout__namespace.struct([
992
1096
  BufferLayout__namespace.u8('instruction'),
993
- BufferLayout__namespace.ns64('lamports'),
994
- BufferLayout__namespace.ns64('transientStakeSeed'),
1097
+ u64Instruction('lamports'),
1098
+ u64Instruction('transientStakeSeed'),
995
1099
  ]);
996
1100
  const UPDATE_VALIDATOR_LIST_BALANCE_LAYOUT = BufferLayout__namespace.struct([
997
1101
  BufferLayout__namespace.u8('instruction'),
@@ -1066,7 +1170,7 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1066
1170
  index: 10,
1067
1171
  layout: BufferLayout__namespace.struct([
1068
1172
  BufferLayout__namespace.u8('instruction'),
1069
- BufferLayout__namespace.ns64('poolTokens'),
1173
+ u64Instruction('poolTokens'),
1070
1174
  ]),
1071
1175
  },
1072
1176
  /// Deposit SOL directly into the pool's reserve account. The output is a "pool" token
@@ -1075,7 +1179,7 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1075
1179
  index: 14,
1076
1180
  layout: BufferLayout__namespace.struct([
1077
1181
  BufferLayout__namespace.u8('instruction'),
1078
- BufferLayout__namespace.ns64('lamports'),
1182
+ u64Instruction('lamports'),
1079
1183
  ]),
1080
1184
  },
1081
1185
  /// Withdraw SOL directly from the pool's reserve account. Fails if the
@@ -1084,25 +1188,25 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1084
1188
  index: 16,
1085
1189
  layout: BufferLayout__namespace.struct([
1086
1190
  BufferLayout__namespace.u8('instruction'),
1087
- BufferLayout__namespace.ns64('poolTokens'),
1191
+ u64Instruction('poolTokens'),
1088
1192
  ]),
1089
1193
  },
1090
1194
  IncreaseAdditionalValidatorStake: {
1091
1195
  index: 19,
1092
1196
  layout: BufferLayout__namespace.struct([
1093
1197
  BufferLayout__namespace.u8('instruction'),
1094
- BufferLayout__namespace.ns64('lamports'),
1095
- BufferLayout__namespace.ns64('transientStakeSeed'),
1096
- BufferLayout__namespace.ns64('ephemeralStakeSeed'),
1198
+ u64Instruction('lamports'),
1199
+ u64Instruction('transientStakeSeed'),
1200
+ u64Instruction('ephemeralStakeSeed'),
1097
1201
  ]),
1098
1202
  },
1099
1203
  DecreaseAdditionalValidatorStake: {
1100
1204
  index: 20,
1101
1205
  layout: BufferLayout__namespace.struct([
1102
1206
  BufferLayout__namespace.u8('instruction'),
1103
- BufferLayout__namespace.ns64('lamports'),
1104
- BufferLayout__namespace.ns64('transientStakeSeed'),
1105
- BufferLayout__namespace.ns64('ephemeralStakeSeed'),
1207
+ u64Instruction('lamports'),
1208
+ u64Instruction('transientStakeSeed'),
1209
+ u64Instruction('ephemeralStakeSeed'),
1106
1210
  ]),
1107
1211
  },
1108
1212
  DecreaseValidatorStakeWithReserve: {
@@ -1117,62 +1221,62 @@ const STAKE_POOL_INSTRUCTION_LAYOUTS = Object.freeze({
1117
1221
  index: 23,
1118
1222
  layout: BufferLayout__namespace.struct([
1119
1223
  BufferLayout__namespace.u8('instruction'),
1120
- BufferLayout__namespace.ns64('lamports'),
1224
+ u64Instruction('lamports'),
1121
1225
  ]),
1122
1226
  },
1123
1227
  WithdrawStakeWithSlippage: {
1124
1228
  index: 24,
1125
1229
  layout: BufferLayout__namespace.struct([
1126
1230
  BufferLayout__namespace.u8('instruction'),
1127
- BufferLayout__namespace.ns64('poolTokensIn'),
1128
- BufferLayout__namespace.ns64('minimumLamportsOut'),
1231
+ u64Instruction('poolTokensIn'),
1232
+ u64Instruction('minimumLamportsOut'),
1129
1233
  ]),
1130
1234
  },
1131
1235
  DepositSolWithSlippage: {
1132
1236
  index: 25,
1133
1237
  layout: BufferLayout__namespace.struct([
1134
1238
  BufferLayout__namespace.u8('instruction'),
1135
- BufferLayout__namespace.ns64('lamports'),
1239
+ u64Instruction('lamports'),
1136
1240
  ]),
1137
1241
  },
1138
1242
  WithdrawSolWithSlippage: {
1139
1243
  index: 26,
1140
1244
  layout: BufferLayout__namespace.struct([
1141
1245
  BufferLayout__namespace.u8('instruction'),
1142
- BufferLayout__namespace.ns64('lamports'),
1246
+ u64Instruction('lamports'),
1143
1247
  ]),
1144
1248
  },
1145
1249
  DepositWsolWithSession: {
1146
1250
  index: 27,
1147
1251
  layout: BufferLayout__namespace.struct([
1148
1252
  BufferLayout__namespace.u8('instruction'),
1149
- BufferLayout__namespace.ns64('lamportsIn'),
1150
- BufferLayout__namespace.ns64('minimumPoolTokensOut'),
1253
+ u64Instruction('lamportsIn'),
1254
+ u64Instruction('minimumPoolTokensOut'),
1151
1255
  ]),
1152
1256
  },
1153
1257
  WithdrawWsolWithSession: {
1154
1258
  index: 28,
1155
1259
  layout: BufferLayout__namespace.struct([
1156
1260
  BufferLayout__namespace.u8('instruction'),
1157
- BufferLayout__namespace.ns64('poolTokensIn'),
1158
- BufferLayout__namespace.ns64('minimumLamportsOut'),
1261
+ u64Instruction('poolTokensIn'),
1262
+ u64Instruction('minimumLamportsOut'),
1159
1263
  ]),
1160
1264
  },
1161
1265
  WithdrawStakeWithSession: {
1162
1266
  index: 29,
1163
1267
  layout: BufferLayout__namespace.struct([
1164
1268
  BufferLayout__namespace.u8('instruction'),
1165
- BufferLayout__namespace.ns64('poolTokensIn'),
1166
- BufferLayout__namespace.ns64('minimumLamportsOut'),
1167
- BufferLayout__namespace.ns64('userStakeSeed'),
1269
+ u64Instruction('poolTokensIn'),
1270
+ u64Instruction('minimumLamportsOut'),
1271
+ u64Instruction('userStakeSeed'),
1168
1272
  ]),
1169
1273
  },
1170
1274
  WithdrawFromStakeAccountWithSession: {
1171
1275
  index: 30,
1172
1276
  layout: BufferLayout__namespace.struct([
1173
1277
  BufferLayout__namespace.u8('instruction'),
1174
- BufferLayout__namespace.ns64('lamports'),
1175
- BufferLayout__namespace.ns64('userStakeSeed'),
1278
+ u64Instruction('lamports'),
1279
+ u64Instruction('userStakeSeed'),
1176
1280
  ]),
1177
1281
  },
1178
1282
  });
@@ -1304,7 +1408,10 @@ class StakePoolInstruction {
1304
1408
  static increaseValidatorStake(params) {
1305
1409
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, transientStake, validatorStake, validatorVote, lamports, transientStakeSeed, } = params;
1306
1410
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.IncreaseValidatorStake;
1307
- const data = encodeData(type, { lamports, transientStakeSeed });
1411
+ const data = encodeData(type, {
1412
+ lamports: toBN(lamports),
1413
+ transientStakeSeed: toBN(transientStakeSeed),
1414
+ });
1308
1415
  const keys = [
1309
1416
  { pubkey: stakePool, isSigner: false, isWritable: false },
1310
1417
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1334,7 +1441,11 @@ class StakePoolInstruction {
1334
1441
  static increaseAdditionalValidatorStake(params) {
1335
1442
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, transientStake, validatorStake, validatorVote, lamports, transientStakeSeed, ephemeralStake, ephemeralStakeSeed, } = params;
1336
1443
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.IncreaseAdditionalValidatorStake;
1337
- const data = encodeData(type, { lamports, transientStakeSeed, ephemeralStakeSeed });
1444
+ const data = encodeData(type, {
1445
+ lamports: toBN(lamports),
1446
+ transientStakeSeed: toBN(transientStakeSeed),
1447
+ ephemeralStakeSeed: toBN(ephemeralStakeSeed),
1448
+ });
1338
1449
  const keys = [
1339
1450
  { pubkey: stakePool, isSigner: false, isWritable: false },
1340
1451
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1364,7 +1475,10 @@ class StakePoolInstruction {
1364
1475
  static decreaseValidatorStake(params) {
1365
1476
  const { programId, stakePool, staker, withdrawAuthority, validatorList, validatorStake, transientStake, lamports, transientStakeSeed, } = params;
1366
1477
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseValidatorStake;
1367
- const data = encodeData(type, { lamports, transientStakeSeed });
1478
+ const data = encodeData(type, {
1479
+ lamports: toBN(lamports),
1480
+ transientStakeSeed: toBN(transientStakeSeed),
1481
+ });
1368
1482
  const keys = [
1369
1483
  { pubkey: stakePool, isSigner: false, isWritable: false },
1370
1484
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1390,7 +1504,10 @@ class StakePoolInstruction {
1390
1504
  static decreaseValidatorStakeWithReserve(params) {
1391
1505
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, validatorStake, transientStake, lamports, transientStakeSeed, } = params;
1392
1506
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseValidatorStakeWithReserve;
1393
- const data = encodeData(type, { lamports, transientStakeSeed });
1507
+ const data = encodeData(type, {
1508
+ lamports: toBN(lamports),
1509
+ transientStakeSeed: toBN(transientStakeSeed),
1510
+ });
1394
1511
  const keys = [
1395
1512
  { pubkey: stakePool, isSigner: false, isWritable: false },
1396
1513
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1417,7 +1534,11 @@ class StakePoolInstruction {
1417
1534
  static decreaseAdditionalValidatorStake(params) {
1418
1535
  const { programId, stakePool, staker, withdrawAuthority, validatorList, reserveStake, validatorStake, transientStake, lamports, transientStakeSeed, ephemeralStakeSeed, ephemeralStake, } = params;
1419
1536
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DecreaseAdditionalValidatorStake;
1420
- const data = encodeData(type, { lamports, transientStakeSeed, ephemeralStakeSeed });
1537
+ const data = encodeData(type, {
1538
+ lamports: toBN(lamports),
1539
+ transientStakeSeed: toBN(transientStakeSeed),
1540
+ ephemeralStakeSeed: toBN(ephemeralStakeSeed),
1541
+ });
1421
1542
  const keys = [
1422
1543
  { pubkey: stakePool, isSigner: false, isWritable: false },
1423
1544
  { pubkey: staker, isSigner: true, isWritable: false },
@@ -1474,7 +1595,7 @@ class StakePoolInstruction {
1474
1595
  static depositSol(params) {
1475
1596
  const { programId, stakePool, withdrawAuthority, depositAuthority, reserveStake, fundingAccount, destinationPoolAccount, managerFeeAccount, referralPoolAccount, poolMint, lamports, } = params;
1476
1597
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DepositSol;
1477
- const data = encodeData(type, { lamports });
1598
+ const data = encodeData(type, { lamports: toBN(lamports) });
1478
1599
  const keys = [
1479
1600
  { pubkey: stakePool, isSigner: false, isWritable: true },
1480
1601
  { pubkey: withdrawAuthority, isSigner: false, isWritable: false },
@@ -1507,8 +1628,8 @@ class StakePoolInstruction {
1507
1628
  var _a;
1508
1629
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.DepositWsolWithSession;
1509
1630
  const data = encodeData(type, {
1510
- lamportsIn: params.lamportsIn,
1511
- minimumPoolTokensOut: params.minimumPoolTokensOut,
1631
+ lamportsIn: toBN(params.lamportsIn),
1632
+ minimumPoolTokensOut: toBN(params.minimumPoolTokensOut),
1512
1633
  });
1513
1634
  const keys = [
1514
1635
  { pubkey: params.stakePool, isSigner: false, isWritable: true },
@@ -1550,7 +1671,7 @@ class StakePoolInstruction {
1550
1671
  static withdrawStake(params) {
1551
1672
  const { programId, stakePool, validatorList, withdrawAuthority, validatorStake, destinationStake, destinationStakeAuthority, sourceTransferAuthority, sourcePoolAccount, managerFeeAccount, poolMint, poolTokens, } = params;
1552
1673
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStake;
1553
- const data = encodeData(type, { poolTokens });
1674
+ const data = encodeData(type, { poolTokens: toBN(poolTokens) });
1554
1675
  const keys = [
1555
1676
  { pubkey: stakePool, isSigner: false, isWritable: true },
1556
1677
  { pubkey: validatorList, isSigner: false, isWritable: true },
@@ -1578,7 +1699,7 @@ class StakePoolInstruction {
1578
1699
  static withdrawSol(params) {
1579
1700
  const { programId, stakePool, withdrawAuthority, sourceTransferAuthority, sourcePoolAccount, reserveStake, destinationSystemAccount, managerFeeAccount, solWithdrawAuthority, poolMint, poolTokens, } = params;
1580
1701
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawSol;
1581
- const data = encodeData(type, { poolTokens });
1702
+ const data = encodeData(type, { poolTokens: toBN(poolTokens) });
1582
1703
  const keys = [
1583
1704
  { pubkey: stakePool, isSigner: false, isWritable: true },
1584
1705
  { pubkey: withdrawAuthority, isSigner: false, isWritable: false },
@@ -1613,8 +1734,8 @@ class StakePoolInstruction {
1613
1734
  static withdrawWsolWithSession(params) {
1614
1735
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawWsolWithSession;
1615
1736
  const data = encodeData(type, {
1616
- poolTokensIn: params.poolTokensIn,
1617
- minimumLamportsOut: params.minimumLamportsOut,
1737
+ poolTokensIn: toBN(params.poolTokensIn),
1738
+ minimumLamportsOut: toBN(params.minimumLamportsOut),
1618
1739
  });
1619
1740
  const keys = [
1620
1741
  { pubkey: params.stakePool, isSigner: false, isWritable: true },
@@ -1651,14 +1772,14 @@ class StakePoolInstruction {
1651
1772
  }
1652
1773
  /**
1653
1774
  * Creates a transaction instruction to withdraw stake from a stake pool using a Fogo session.
1654
- * The stake account is created as a PDA and rent is paid by the payer (typically paymaster).
1775
+ * The stake account is created as a PDA and rent is funded from the reserve.
1655
1776
  */
1656
1777
  static withdrawStakeWithSession(params) {
1657
1778
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawStakeWithSession;
1658
1779
  const data = encodeData(type, {
1659
- poolTokensIn: params.poolTokensIn,
1660
- minimumLamportsOut: params.minimumLamportsOut,
1661
- userStakeSeed: params.userStakeSeed,
1780
+ poolTokensIn: toBN(params.poolTokensIn),
1781
+ minimumLamportsOut: toBN(params.minimumLamportsOut),
1782
+ userStakeSeed: toBN(params.userStakeSeed),
1662
1783
  });
1663
1784
  const keys = [
1664
1785
  { pubkey: params.stakePool, isSigner: false, isWritable: true },
@@ -1666,17 +1787,19 @@ class StakePoolInstruction {
1666
1787
  { pubkey: params.withdrawAuthority, isSigner: false, isWritable: false },
1667
1788
  { pubkey: params.stakeToSplit, isSigner: false, isWritable: true },
1668
1789
  { pubkey: params.stakeToReceive, isSigner: false, isWritable: true },
1669
- { pubkey: params.sessionSigner, isSigner: true, isWritable: false }, // user_stake_authority_info (signer_or_session)
1670
- { pubkey: params.sessionSigner, isSigner: false, isWritable: false }, // user_transfer_authority_info (not used in session path)
1790
+ { pubkey: params.sessionSigner, isSigner: true, isWritable: false }, // user_stake_authority_info
1791
+ { pubkey: params.sessionSigner, isSigner: false, isWritable: false }, // user_transfer_authority_info (unused in session path)
1671
1792
  { pubkey: params.burnFromPool, isSigner: false, isWritable: true },
1672
1793
  { pubkey: params.managerFeeAccount, isSigner: false, isWritable: true },
1673
1794
  { pubkey: params.poolMint, isSigner: false, isWritable: true },
1674
1795
  { pubkey: web3_js.SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
1675
1796
  { pubkey: params.tokenProgramId, isSigner: false, isWritable: false },
1676
1797
  { pubkey: web3_js.StakeProgram.programId, isSigner: false, isWritable: false },
1798
+ // Session-specific accounts
1677
1799
  { pubkey: params.programSigner, isSigner: false, isWritable: false },
1678
1800
  { pubkey: web3_js.SystemProgram.programId, isSigner: false, isWritable: false },
1679
- { pubkey: params.payer, isSigner: true, isWritable: true },
1801
+ { pubkey: params.reserveStake, isSigner: false, isWritable: true },
1802
+ { pubkey: web3_js.SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false },
1680
1803
  ];
1681
1804
  return new web3_js.TransactionInstruction({
1682
1805
  programId: params.programId,
@@ -1691,8 +1814,8 @@ class StakePoolInstruction {
1691
1814
  static withdrawFromStakeAccountWithSession(params) {
1692
1815
  const type = STAKE_POOL_INSTRUCTION_LAYOUTS.WithdrawFromStakeAccountWithSession;
1693
1816
  const data = encodeData(type, {
1694
- lamports: params.lamports,
1695
- userStakeSeed: params.userStakeSeed,
1817
+ lamports: toBN(params.lamports),
1818
+ userStakeSeed: toBN(params.userStakeSeed),
1696
1819
  });
1697
1820
  const keys = [
1698
1821
  { pubkey: params.userStakeAccount, isSigner: false, isWritable: true },
@@ -1981,10 +2104,18 @@ skipBalanceCheck = false) {
1981
2104
  if (!skipBalanceCheck) {
1982
2105
  const tokenAccountInfo = await connection.getTokenAccountBalance(wsolTokenAccount, 'confirmed');
1983
2106
  const wsolBalance = tokenAccountInfo
1984
- ? parseInt(tokenAccountInfo.value.amount)
1985
- : 0;
1986
- if (wsolBalance < lamports) {
1987
- throw new Error(`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(wsolBalance)} WSOL.`);
2107
+ ? BigInt(tokenAccountInfo.value.amount)
2108
+ : BigInt(0);
2109
+ // Convert lamports to BigInt for comparison
2110
+ const lamportsBigInt = typeof lamports === 'bigint'
2111
+ ? lamports
2112
+ : typeof lamports === 'string'
2113
+ ? BigInt(lamports)
2114
+ : BN.isBN(lamports)
2115
+ ? BigInt(lamports.toString())
2116
+ : BigInt(lamports);
2117
+ if (wsolBalance < lamportsBigInt) {
2118
+ throw new Error(`Not enough WSOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(Number(wsolBalance))} WSOL.`);
1988
2119
  }
1989
2120
  }
1990
2121
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
@@ -2030,7 +2161,15 @@ skipBalanceCheck = false) {
2030
2161
  */
2031
2162
  async function depositSol(connection, stakePoolAddress, from, lamports, destinationTokenAccount, referrerTokenAccount, depositAuthority) {
2032
2163
  const fromBalance = await connection.getBalance(from, 'confirmed');
2033
- if (fromBalance < lamports) {
2164
+ // Convert lamports to BigInt for comparison
2165
+ const lamportsBigInt = typeof lamports === 'bigint'
2166
+ ? lamports
2167
+ : typeof lamports === 'string'
2168
+ ? BigInt(lamports)
2169
+ : BN.isBN(lamports)
2170
+ ? BigInt(lamports.toString())
2171
+ : BigInt(lamports);
2172
+ if (BigInt(fromBalance) < lamportsBigInt) {
2034
2173
  throw new Error(`Not enough SOL to deposit into pool. Maximum deposit amount is ${lamportsToSol(fromBalance)} SOL.`);
2035
2174
  }
2036
2175
  const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
@@ -2044,7 +2183,7 @@ async function depositSol(connection, stakePoolAddress, from, lamports, destinat
2044
2183
  instructions.push(web3_js.SystemProgram.transfer({
2045
2184
  fromPubkey: from,
2046
2185
  toPubkey: userSolTransfer.publicKey,
2047
- lamports,
2186
+ lamports: lamportsBigInt,
2048
2187
  }));
2049
2188
  // Create token account if not specified
2050
2189
  if (!destinationTokenAccount) {
@@ -2416,37 +2555,51 @@ async function getUserStakeAccounts(connection, programId, userPubkey, maxSeed =
2416
2555
  * Withdraws stake from a stake pool using a Fogo session.
2417
2556
  *
2418
2557
  * The on-chain program creates stake account PDAs. The rent for these accounts
2419
- * is paid by the payer (typically the paymaster), not deducted from the user's withdrawal.
2558
+ * is funded from the reserve stake.
2420
2559
  *
2421
2560
  * @param connection - Solana connection
2422
2561
  * @param stakePoolAddress - The stake pool to withdraw from
2423
2562
  * @param signerOrSession - The session signer public key
2424
2563
  * @param userPubkey - User's wallet (used for PDA derivation and token ownership)
2425
- * @param payer - Payer for stake account rent (typically paymaster)
2426
2564
  * @param amount - Amount of pool tokens to withdraw
2427
2565
  * @param userStakeSeedStart - Starting seed for user stake PDA derivation (default: 0)
2428
2566
  * @param useReserve - Whether to withdraw from reserve (default: false)
2429
2567
  * @param voteAccountAddress - Optional specific validator to withdraw from
2430
2568
  * @param minimumLamportsOut - Minimum lamports to receive (slippage protection)
2431
2569
  * @param validatorComparator - Optional comparator for validator selection
2570
+ * @param allowPartial - If true, returns partial results instead of throwing when not enough stake available
2432
2571
  */
2433
- async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, payer, amount, userStakeSeedStart = 0, useReserve = false, voteAccountAddress, minimumLamportsOut = 0, validatorComparator) {
2434
- const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2572
+ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSession, userPubkey, amount, userStakeSeedStart = 0, useReserve = false, voteAccountAddress, minimumLamportsOut = 0, validatorComparator, allowPartial = false) {
2573
+ var _c;
2435
2574
  const stakePoolProgramId = getStakePoolProgramId(connection.rpcEndpoint);
2575
+ // First fetch: get stake pool to know other account addresses
2576
+ const stakePoolAccount = await getStakePoolAccount(connection, stakePoolAddress);
2436
2577
  const stakePool = stakePoolAccount.account.data;
2437
2578
  const poolTokens = solToLamports(amount);
2438
2579
  const poolAmount = new BN(poolTokens);
2439
2580
  const poolTokenAccount = splToken.getAssociatedTokenAddressSync(stakePool.poolMint, userPubkey);
2440
- const tokenAccount = await splToken.getAccount(connection, poolTokenAccount);
2581
+ // Second fetch: get ALL remaining data in parallel
2582
+ const [tokenAccount, stakeAccountRentExemption, validatorListAcc, stakeMinimumDelegationResponse] = await Promise.all([
2583
+ splToken.getAccount(connection, poolTokenAccount),
2584
+ connection.getMinimumBalanceForRentExemption(web3_js.StakeProgram.space),
2585
+ connection.getAccountInfo(stakePool.validatorList),
2586
+ connection.getStakeMinimumDelegation(),
2587
+ ]);
2588
+ // Pre-fetch data to avoid duplicate RPC calls in prepareWithdrawAccounts
2589
+ const prefetchedData = {
2590
+ validatorListData: (_c = validatorListAcc === null || validatorListAcc === void 0 ? void 0 : validatorListAcc.data) !== null && _c !== void 0 ? _c : null,
2591
+ minBalanceForRentExemption: stakeAccountRentExemption,
2592
+ stakeMinimumDelegation: Number(stakeMinimumDelegationResponse.value),
2593
+ };
2441
2594
  if (tokenAccount.amount < poolTokens) {
2442
2595
  throw new Error(`Not enough token balance to withdraw ${amount} pool tokens.
2443
2596
  Maximum withdraw amount is ${lamportsToSol(tokenAccount.amount)} pool tokens.`);
2444
2597
  }
2445
2598
  const [programSigner] = web3_js.PublicKey.findProgramAddressSync([Buffer.from('fogo_session_program_signer')], stakePoolProgramId);
2446
2599
  const withdrawAuthority = await findWithdrawAuthorityProgramAddress(stakePoolProgramId, stakePoolAddress);
2447
- const stakeAccountRentExemption = await connection.getMinimumBalanceForRentExemption(web3_js.StakeProgram.space);
2448
2600
  // Determine which stake accounts to withdraw from
2449
2601
  const withdrawAccounts = [];
2602
+ let partialRemainingAmount;
2450
2603
  if (useReserve) {
2451
2604
  withdrawAccounts.push({
2452
2605
  stakeAddress: stakePool.reserveStake,
@@ -2477,7 +2630,14 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2477
2630
  }
2478
2631
  else {
2479
2632
  // Get the list of accounts to withdraw from automatically
2480
- withdrawAccounts.push(...(await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount))));
2633
+ if (allowPartial) {
2634
+ const result = await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount), true, prefetchedData);
2635
+ withdrawAccounts.push(...result.withdrawAccounts);
2636
+ partialRemainingAmount = result.remainingAmount;
2637
+ }
2638
+ else {
2639
+ withdrawAccounts.push(...(await prepareWithdrawAccounts(connection, stakePool, stakePoolAddress, poolAmount, validatorComparator, poolTokenAccount.equals(stakePool.managerFeeAccount), undefined, prefetchedData)));
2640
+ }
2481
2641
  }
2482
2642
  const instructions = [];
2483
2643
  const stakeAccountPubkeys = [];
@@ -2494,7 +2654,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2494
2654
  const stakeReceiverPubkey = findUserStakeProgramAddress(stakePoolProgramId, userPubkey, userStakeSeed);
2495
2655
  stakeAccountPubkeys.push(stakeReceiverPubkey);
2496
2656
  userStakeSeeds.push(userStakeSeed);
2497
- // The on-chain program creates the stake account PDA and rent is paid by payer.
2657
+ // The on-chain program creates the stake account PDA and rent is funded from reserve.
2498
2658
  instructions.push(StakePoolInstruction.withdrawStakeWithSession({
2499
2659
  programId: stakePoolProgramId,
2500
2660
  stakePool: stakePoolAddress,
@@ -2508,7 +2668,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2508
2668
  poolMint: stakePool.poolMint,
2509
2669
  tokenProgramId: stakePool.tokenProgramId,
2510
2670
  programSigner,
2511
- payer,
2671
+ reserveStake: stakePool.reserveStake,
2512
2672
  poolTokensIn: withdrawAccount.poolAmount.toNumber(),
2513
2673
  minimumLamportsOut,
2514
2674
  userStakeSeed,
@@ -2519,6 +2679,7 @@ async function withdrawStakeWithSession(connection, stakePoolAddress, signerOrSe
2519
2679
  instructions,
2520
2680
  stakeAccountPubkeys,
2521
2681
  userStakeSeeds,
2682
+ remainingPoolTokens: partialRemainingAmount ? lamportsToSol(partialRemainingAmount) : 0,
2522
2683
  };
2523
2684
  }
2524
2685
  async function addValidatorToPool(connection, stakePoolAddress, validatorVote, seed) {