@wireio/stake 2.0.0 → 2.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.
@@ -29,6 +29,25 @@ import type { LiqsolCore as LiqSolCoreDevnet } from '../../assets/solana/devnet/
29
29
  import type { LiqsolToken as LiqsolTokenDevnet } from '../../assets/solana/devnet/types/liqsol_token';
30
30
  import type { ValidatorLeaderboard as ValidatorLeaderboardDevnet } from '../../assets/solana/devnet/types/validator_leaderboard';
31
31
 
32
+ // “Client” IDLs = mainnet OR devnet, plus `address` field we add at runtime
33
+
34
+ export type LiqsolCoreClientIdl =
35
+ (LiqSolCoreMainnet | LiqSolCoreDevnet) & { address: string };
36
+
37
+ export type LiqsolTokenClientIdl =
38
+ (LiqsolTokenMainnet | LiqsolTokenDevnet) & { address: string };
39
+
40
+ export type ValidatorLeaderboardClientIdl =
41
+ (ValidatorLeaderboardMainnet | ValidatorLeaderboardDevnet) & { address: string };
42
+
43
+ export type SolanaProgramIdlMap = {
44
+ liqsolCore: LiqsolCoreClientIdl;
45
+ liqsolToken: LiqsolTokenClientIdl;
46
+ validatorLeaderboard: ValidatorLeaderboardClientIdl;
47
+ };
48
+
49
+ export type SolanaProgramName = keyof SolanaProgramIdlMap;
50
+
32
51
  type Entry<IDL> = {
33
52
  idl: IDL & { address: string };
34
53
  address: string;
@@ -65,9 +84,6 @@ const PROGRAMS_BY_CHAIN = {
65
84
  },
66
85
  } as const;
67
86
 
68
- export type SolanaProgramName =
69
- keyof (typeof PROGRAMS_BY_CHAIN)[SolChainID.Mainnet];
70
-
71
87
  export class SolanaProgramService {
72
88
  private readonly ids: SolanaProgramIds;
73
89
  private readonly programs: (typeof PROGRAMS_BY_CHAIN)[SolChainID.Mainnet];
@@ -90,15 +106,14 @@ export class SolanaProgramService {
90
106
  return this.ids;
91
107
  }
92
108
 
93
- /** Anchor Program getter */
109
+ // Generic getProgram, typed by name → union IDL
94
110
  getProgram<K extends SolanaProgramName>(
95
111
  name: K,
96
- ): Program<(typeof this.programs)[K]['idl']> {
112
+ ): Program<SolanaProgramIdlMap[K]> {
97
113
  const { idl, address } = this.programs[name];
98
- const idlWithAddr = { ...idl, address };
99
- return new Program(idlWithAddr, this.provider) as Program<
100
- (typeof this.programs)[K]['idl']
101
- >;
114
+ const idlWithAddr = { ...(idl as any), address } as SolanaProgramIdlMap[K];
115
+
116
+ return new Program(idlWithAddr, this.provider) as Program<SolanaProgramIdlMap[K]>;
102
117
  }
103
118
 
104
119
  listProgramNames(): SolanaProgramName[] {
@@ -4,12 +4,14 @@ import {
4
4
  Connection,
5
5
  ConnectionConfig,
6
6
  PerfSample,
7
+ SendTransactionError,
7
8
  PublicKey as SolPubKey,
8
9
  SystemProgram,
9
10
  Transaction,
10
11
  TransactionInstruction,
11
12
  TransactionMessage,
12
13
  TransactionSignature,
14
+ VersionedTransaction,
13
15
  } from '@solana/web3.js';
14
16
  import { AnchorProvider, BN } from '@coral-xyz/anchor';
15
17
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
@@ -343,15 +345,16 @@ export class SolanaStakingClient implements IStakingClient {
343
345
  // return sent2;
344
346
  // }
345
347
  // else {
346
- const ix = await this.depositClient.buildDepositTx(amountLamports)
347
- const tx = new Transaction().add(ix);
348
- const prepared = await this.prepareTx(tx);
349
- const signed = await this.signTransaction(prepared.tx);
348
+ const ix = await this.depositClient.buildDepositTx(amountLamports)
349
+ const tx = new Transaction().add(ix);
350
+ const prepared = await this.prepareTx(tx);
351
+ const signed = await this.signTransaction(prepared.tx);
350
352
 
351
- return this.sendAndConfirmHttp(signed, prepared);
353
+ return this.sendAndConfirmHttp(signed, prepared);
352
354
  // }
353
355
  } catch (err) {
354
- throw new Error(`Failed to deposit Solana: ${err}`);
356
+ console.log(`Failed to deposit Solana: ${err}`);
357
+ throw err;
355
358
  }
356
359
  }
357
360
 
@@ -385,7 +388,8 @@ export class SolanaStakingClient implements IStakingClient {
385
388
 
386
389
  return this.sendAndConfirmHttp(signed, prepared);
387
390
  } catch (err) {
388
- throw new Error(`Failed to withdraw Solana: ${err}`);
391
+ console.log(`Failed to withdraw Solana: ${err}`);
392
+ throw err;
389
393
  }
390
394
  }
391
395
 
@@ -415,7 +419,8 @@ export class SolanaStakingClient implements IStakingClient {
415
419
 
416
420
  return this.sendAndConfirmHttp(signed, prepared);
417
421
  } catch (err) {
418
- throw new Error(`Failed to stake Solana: ${err}`);
422
+ console.log(`Failed to stake Solana: ${err}`);
423
+ throw err;
419
424
  }
420
425
  }
421
426
 
@@ -446,7 +451,8 @@ export class SolanaStakingClient implements IStakingClient {
446
451
  return this.sendAndConfirmHttp(signed, prepared);
447
452
  }
448
453
  catch (err) {
449
- throw new Error(`Failed to unstake Solana: ${err}`);
454
+ console.log(`Failed to unstake Solana: ${err}`);
455
+ throw err;
450
456
  }
451
457
  }
452
458
 
@@ -474,7 +480,8 @@ export class SolanaStakingClient implements IStakingClient {
474
480
  return this.sendAndConfirmHttp(signed, prepared);
475
481
  }
476
482
  catch (err) {
477
- throw new Error(`Failed to buy liqSOL pretokens: ${err}`);
483
+ console.log(`Failed to buy liqSOL pretokens: ${err}`);
484
+ throw err;
478
485
  }
479
486
  }
480
487
 
@@ -627,7 +634,8 @@ export class SolanaStakingClient implements IStakingClient {
627
634
  };
628
635
  }
629
636
  catch (err) {
630
- throw new Error(`Failed to get Solana portfolio: ${err}`);
637
+ console.log(`Failed to get Solana portfolio: ${err}`);
638
+ throw err;
631
639
  }
632
640
  }
633
641
 
@@ -689,7 +697,8 @@ export class SolanaStakingClient implements IStakingClient {
689
697
  });
690
698
  }
691
699
  catch (err) {
692
- throw new Error(`Failed to build Solana tranche snapshot: ${err}`);
700
+ console.log(`Failed to build Solana tranche snapshot: ${err}`);
701
+ throw err;
693
702
  }
694
703
  }
695
704
 
@@ -711,7 +720,8 @@ export class SolanaStakingClient implements IStakingClient {
711
720
 
712
721
  return apyPercent;
713
722
  } catch (err) {
714
- throw new Error(`Failed to compute Solana system APY: ${err}`);
723
+ console.log(`Failed to compute Solana system APY: ${err}`);
724
+ throw err;
715
725
  }
716
726
  }
717
727
 
@@ -1026,38 +1036,107 @@ export class SolanaStakingClient implements IStakingClient {
1026
1036
  /**
1027
1037
  * Send a signed transaction over HTTP RPC and wait for confirmation.
1028
1038
  * Throws if the transaction fails.
1039
+ *
1040
+ * - Handles benign "already processed" preflight errors.
1041
+ * - Uses getSignatureStatuses() instead of confirmTransaction()
1042
+ * so we don't care about blockheight expiry.
1029
1043
  */
1030
1044
  private async sendAndConfirmHttp(
1031
1045
  signed: SolanaTransaction,
1032
- ctx: { blockhash: string; lastValidBlockHeight: number },
1046
+ _ctx: { blockhash: string; lastValidBlockHeight: number },
1033
1047
  ): Promise<string> {
1034
1048
  this.ensureUser();
1035
1049
 
1036
- const signature = await this.connection.sendRawTransaction(
1037
- signed.serialize(),
1038
- {
1050
+ const rawTx = signed.serialize();
1051
+ let signature: string;
1052
+
1053
+ // -------------------------------------------------------------
1054
+ // 1) Send the raw transaction
1055
+ // Handle "already been processed" as a soft success.
1056
+ // -------------------------------------------------------------
1057
+ try {
1058
+ signature = await this.connection.sendRawTransaction(rawTx, {
1039
1059
  skipPreflight: false,
1040
1060
  preflightCommitment: commitment,
1041
1061
  maxRetries: 3,
1042
- },
1043
- );
1062
+ });
1063
+ } catch (e: any) {
1064
+ const msg = e?.message ?? '';
1065
+
1066
+ const isSendTxError =
1067
+ e instanceof SendTransactionError || e?.name === 'SendTransactionError';
1068
+
1069
+ if (isSendTxError && msg.includes('already been processed')) {
1070
+ // Preflight is telling us this exact txid already landed.
1071
+ // Derive the signature from the signed tx and continue to confirmation.
1072
+ console.warn(
1073
+ 'sendRawTransaction preflight says "already been processed"; ' +
1074
+ 'treating as success and deriving signature from signed tx.',
1075
+ );
1044
1076
 
1045
- const conf = await this.connection.confirmTransaction(
1046
- {
1047
- signature,
1048
- blockhash: ctx.blockhash,
1049
- lastValidBlockHeight: ctx.lastValidBlockHeight,
1050
- },
1051
- commitment,
1052
- );
1077
+ // Assuming SolanaTransaction is a legacy Transaction here.
1078
+ const legacy = signed as unknown as Transaction;
1079
+ const first = legacy.signatures?.[0]?.signature;
1053
1080
 
1054
- if (conf.value.err) {
1055
- throw new Error(
1056
- `Transaction failed: ${JSON.stringify(conf.value.err)}`,
1057
- );
1081
+ if (!first) {
1082
+ // No way to recover the txid → rethrow
1083
+ throw e;
1084
+ }
1085
+
1086
+ signature = bs58.encode(first);
1087
+ } else {
1088
+ // Any other send error is a real failure
1089
+ throw e;
1090
+ }
1058
1091
  }
1059
1092
 
1060
- return signature;
1093
+ // -------------------------------------------------------------
1094
+ // 2) Poll getSignatureStatuses until confirmed/finalized or timeout
1095
+ // -------------------------------------------------------------
1096
+ const start = Date.now();
1097
+ const timeoutMs = 30_000; // tune this if you want longer for devnet
1098
+ const pollIntervalMs = 500; // 0.5s between polls
1099
+
1100
+ // eslint-disable-next-line no-constant-condition
1101
+ while (true) {
1102
+ const { value } = await this.connection.getSignatureStatuses(
1103
+ [signature],
1104
+ { searchTransactionHistory: true },
1105
+ );
1106
+ const status = value[0];
1107
+
1108
+ if (status) {
1109
+ if (status.err) {
1110
+ // On-chain error (instruction error, etc.)
1111
+ throw new Error(
1112
+ `Transaction failed: ${JSON.stringify(status.err)}`,
1113
+ );
1114
+ }
1115
+
1116
+ const confirmations = status.confirmations;
1117
+ const confirmationStatus = status.confirmationStatus;
1118
+
1119
+ const satisfied =
1120
+ confirmationStatus === 'confirmed' ||
1121
+ confirmationStatus === 'finalized' ||
1122
+ confirmations === null || // rooted
1123
+ (confirmations ?? 0) > 0; // at least 1 confirmation
1124
+
1125
+ if (satisfied) {
1126
+ // We consider this successfully confirmed
1127
+ return signature;
1128
+ }
1129
+ }
1130
+
1131
+ // Timeout guard so we don't spin forever on a tx that never lands
1132
+ if (Date.now() - start > timeoutMs) {
1133
+ throw new Error(
1134
+ `Transaction confirmation timed out for ${signature}`,
1135
+ );
1136
+ }
1137
+
1138
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
1139
+ }
1061
1140
  }
1062
1141
 
1063
1142
  /**