@wireio/stake 0.7.0 → 0.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wireio/stake",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "LIQ Staking Module for Wire Network",
5
5
  "homepage": "https://gitea.gitgo.app/Wire/sdk-stake",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -375,7 +375,7 @@ export class EthereumStakingClient implements IStakingClient {
375
375
  // let ethPriceUsd: bigint = BigInt(answer.toString());)
376
376
 
377
377
  let ethPriceUsdBn = await this.contract.EthUsdPriceConsumer.getPrice18Decimals();
378
- let ethPriceUsd: bigint = BigInt(ethPriceUsdBn.toString()) / 10n;
378
+ let ethPriceUsd: bigint = BigInt(ethPriceUsdBn.toString()) / BigInt(1e10);
379
379
 
380
380
  let nativePriceTimestamp: number = Number(updatedAt);
381
381
 
@@ -677,7 +677,7 @@ export class SolanaStakingClient implements IStakingClient {
677
677
  * in the wallet so that a MAX deposit (balance - buffer) will succeed.
678
678
  *
679
679
  * It accounts for:
680
- * - Runtime tx fees (via a dummy self-transfer)
680
+ * - Runtime tx fees (via cached dummy self-transfer fee)
681
681
  * - Protocol deposit fee (via getDepositFee(amount))
682
682
  *
683
683
  * Intended UI usage:
@@ -696,7 +696,7 @@ export class SolanaStakingClient implements IStakingClient {
696
696
  const payer = this.solPubKey;
697
697
 
698
698
  // -------------------------------------------------------------
699
- // 1) Current wallet balance
699
+ // 1) Current wallet balance (prefer caller override)
700
700
  // -------------------------------------------------------------
701
701
  const balanceLamports: bigint =
702
702
  options?.balanceOverrideLamports ??
@@ -707,32 +707,12 @@ export class SolanaStakingClient implements IStakingClient {
707
707
  }
708
708
 
709
709
  // -------------------------------------------------------------
710
- // 2) Estimate gas buffer via dummy self-transfer
710
+ // 2) Estimate gas buffer (using cached single-tx fee)
711
711
  // -------------------------------------------------------------
712
712
  let gasBuffer = BigInt(0);
713
713
 
714
714
  try {
715
- const dummyIx = SystemProgram.transfer({
716
- fromPubkey: payer,
717
- toPubkey: payer,
718
- lamports: 0,
719
- });
720
-
721
- const tx = new Transaction().add(dummyIx);
722
- const { blockhash } = await this.connection.getLatestBlockhash(
723
- commitment,
724
- );
725
- tx.recentBlockhash = blockhash;
726
- tx.feePayer = payer;
727
-
728
- const message = tx.compileMessage();
729
- const feeInfo = await this.connection.getFeeForMessage(
730
- message,
731
- commitment,
732
- );
733
-
734
- // Fallback to a conservative default if RPC doesn't give a value.
735
- const singleTxFeeLamports = BigInt(feeInfo.value ?? 5000);
715
+ const singleTxFeeLamports = await this.getSingleTxFeeLamports();
736
716
 
737
717
  const txCount = BigInt(options?.txCount ?? 2);
738
718
  const safetyMultiplier = options?.safetyMultiplier ?? 3;
@@ -752,12 +732,11 @@ export class SolanaStakingClient implements IStakingClient {
752
732
 
753
733
  gasBuffer = buf;
754
734
  } catch {
755
- // If fee estimation fails, fall back to zero gas buffer
756
- // (we will still leave room for protocol fee below).
735
+ // If fee estimation fails, just fall back to "no gas buffer".
757
736
  gasBuffer = BigInt(0);
758
737
  }
759
738
 
760
- // If fees alone eat the whole balance, just keep everything as buffer.
739
+ // If gas buffer alone eats the whole balance, we just keep everything.
761
740
  if (balanceLamports <= gasBuffer) {
762
741
  return balanceLamports;
763
742
  }
@@ -772,35 +751,43 @@ export class SolanaStakingClient implements IStakingClient {
772
751
  }
773
752
 
774
753
  // -------------------------------------------------------------
775
- // 4) Binary search for max principal p such that:
776
- // p + fee(p) <= spendable
777
- // Then buffer = balance - pEffective.
754
+ // 4) Approximate principal using linear fee model from ONE RPC call
755
+ //
756
+ // fee(a) k * a
757
+ // want: a + k*a <= spendable → a <= spendable / (1 + k)
758
+ //
759
+ // We estimate k from fee(spendable) / spendable.
778
760
  // -------------------------------------------------------------
779
- let lo = BigInt(0);
780
- let hi = spendable;
781
-
782
- for (let i = 0; i < 20 && hi - lo > BigInt(1); i++) {
783
- const mid = (lo + hi) / BigInt(2);
784
-
785
- let fee: bigint;
786
- try {
787
- fee = await this.getDepositFee(mid);
788
- } catch {
789
- // If fee lookup fails mid-search, fall back to gas-only buffer.
790
- return gasBuffer;
791
- }
761
+ let feeAtSpendable: bigint;
792
762
 
793
- if (mid + fee <= spendable) {
794
- lo = mid; // mid is affordable → try bigger
795
- } else {
796
- hi = mid; // mid too big go smaller
797
- }
763
+ try {
764
+ feeAtSpendable = await this.getDepositFee(spendable);
765
+ } catch {
766
+ // If protocol fee lookup fails, fall back to gas-only buffer
767
+ return gasBuffer;
798
768
  }
799
769
 
800
- // Tiny safety haircut to avoid off-by-one lamport issues.
801
- const fudge = BigInt(1_000); // ≈ 0.000001 SOL
770
+ // If there is effectively no protocol fee, keep only gas buffer.
771
+ if (feeAtSpendable <= BigInt(0)) {
772
+ return gasBuffer;
773
+ }
774
+
775
+ const s = spendable;
776
+ const f = feeAtSpendable;
777
+
778
+ // denom = s + f = s * (1 + k) (since f ≈ k*s)
779
+ const denom = s + f;
780
+ if (denom === BigInt(0)) {
781
+ return gasBuffer;
782
+ }
783
+
784
+ // a ≈ floor( s^2 / (s + f) )
785
+ let a = (s * s) / denom;
786
+
787
+ // Tiny safety haircut to avoid edge off-by-one lamports
788
+ const fudge = BigInt(10_000); // ≈ 0.00001 SOL
802
789
  let effectivePrincipal =
803
- lo > fudge ? lo - fudge : lo;
790
+ a > fudge ? a - fudge : a;
804
791
 
805
792
  if (effectivePrincipal < BigInt(0)) {
806
793
  effectivePrincipal = BigInt(0);
@@ -810,10 +797,46 @@ export class SolanaStakingClient implements IStakingClient {
810
797
  ? balanceLamports - effectivePrincipal
811
798
  : balanceLamports;
812
799
 
813
- // Ensure we never *under*-reserve gas
800
+ // Ensure we never under-reserve gas.
814
801
  return buffer < gasBuffer ? gasBuffer : buffer;
815
802
  }
816
803
 
804
+ private cachedTxFee?: { value: bigint; fetchedAt: number };
805
+ private static readonly FEE_CACHE_TTL_MS = 60_000; // 60s
806
+
807
+ private async getSingleTxFeeLamports(): Promise<bigint> {
808
+ const now = Date.now();
809
+
810
+ if (this.cachedTxFee && now - this.cachedTxFee.fetchedAt < SolanaStakingClient.FEE_CACHE_TTL_MS) {
811
+ return this.cachedTxFee.value;
812
+ }
813
+
814
+ const payer = this.solPubKey;
815
+
816
+ const dummyIx = SystemProgram.transfer({
817
+ fromPubkey: payer,
818
+ toPubkey: payer,
819
+ lamports: 0,
820
+ });
821
+
822
+ const tx = new Transaction().add(dummyIx);
823
+ const { blockhash } = await this.connection.getLatestBlockhash(commitment);
824
+ tx.recentBlockhash = blockhash;
825
+ tx.feePayer = payer;
826
+
827
+ const message = tx.compileMessage();
828
+ const feeInfo = await this.connection.getFeeForMessage(message, commitment);
829
+
830
+ const singleTxFeeLamports = BigInt(feeInfo.value ?? 5000);
831
+
832
+ this.cachedTxFee = {
833
+ value: singleTxFeeLamports,
834
+ fetchedAt: now,
835
+ };
836
+
837
+ return singleTxFeeLamports;
838
+ }
839
+
817
840
  // ---------------------------------------------------------------------
818
841
  // Tx helpers
819
842
  // ---------------------------------------------------------------------