@wireio/stake 0.2.2 → 0.2.4

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.
@@ -2,30 +2,65 @@ import {
2
2
  Commitment,
3
3
  Connection,
4
4
  ConnectionConfig,
5
+ LAMPORTS_PER_SOL,
5
6
  PublicKey as SolPubKey,
6
7
  Transaction,
8
+ TransactionInstruction,
7
9
  TransactionSignature,
8
10
  } from '@solana/web3.js';
9
- import { AnchorProvider, BN } from '@coral-xyz/anchor';
11
+ import { AnchorProvider } from '@coral-xyz/anchor';
10
12
  import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
11
- import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
13
+ import {
14
+ ASSOCIATED_TOKEN_PROGRAM_ID,
15
+ getAssociatedTokenAddressSync,
16
+ TOKEN_2022_PROGRAM_ID,
17
+ } from '@solana/spl-token';
18
+
19
+ import {
20
+ ChainID,
21
+ ExternalNetwork,
22
+ KeyType,
23
+ PublicKey,
24
+ SolChainID,
25
+ } from '@wireio/core';
12
26
 
13
- import { ChainID, ExternalNetwork, KeyType, PublicKey } from '@wireio/core';
14
- import { IStakingClient, Portfolio, StakerConfig } from '../../staker/types';
27
+ import {
28
+ IStakingClient,
29
+ Portfolio,
30
+ PurchaseAsset,
31
+ PurchaseQuote,
32
+ StakerConfig,
33
+ TrancheSnapshot,
34
+ } from '../../types';
15
35
 
16
36
  import { DepositClient } from './clients/deposit.client';
17
37
  import { DistributionClient } from './clients/distribution.client';
38
+ import { LeaderboardClient } from './clients/leaderboard.client';
39
+ import { OutpostClient } from './clients/outpost.client';
40
+ import { TokenClient } from './clients/token.client';
41
+
18
42
  import {
19
43
  deriveLiqsolMintPda,
20
44
  deriveReservePoolPda,
21
45
  deriveVaultPda,
22
46
  } from './constants';
47
+
48
+ import { buildSolanaTrancheSnapshot } from './utils';
23
49
  import { SolanaTransaction } from './types';
24
- import { LeaderboardClient } from './clients/leaderboard.client';
25
- import { OutpostClient } from './clients/outpost.client';
26
50
 
27
51
  const commitment: Commitment = 'confirmed';
28
52
 
53
+ /**
54
+ * Solana implementation of IStakingClient.
55
+ *
56
+ * Responsibilities:
57
+ * - Wire together liqSOL deposit/withdraw
58
+ * - Outpost stake/unstake
59
+ * - Prelaunch WIRE (pretokens) buy flows
60
+ * - Unified portfolio + tranche snapshot + buy quotes
61
+ *
62
+ * This class composes lower-level clients; it does not know about UI.
63
+ */
29
64
  export class SolanaStakingClient implements IStakingClient {
30
65
  public pubKey: PublicKey;
31
66
  public connection: Connection;
@@ -35,9 +70,15 @@ export class SolanaStakingClient implements IStakingClient {
35
70
  private distributionClient: DistributionClient;
36
71
  private leaderboardClient: LeaderboardClient;
37
72
  private outpostClient: OutpostClient;
73
+ private tokenClient: TokenClient;
74
+
75
+ get solPubKey(): SolPubKey {
76
+ return new SolPubKey(this.pubKey.data.array);
77
+ }
38
78
 
39
- get solPubKey(): SolPubKey { return new SolPubKey(this.pubKey.data.array); }
40
- get network() { return this.config.network; }
79
+ get network(): ExternalNetwork {
80
+ return this.config.network;
81
+ }
41
82
 
42
83
  constructor(private config: StakerConfig) {
43
84
  const adapter = config.provider as BaseSignerWalletAdapter;
@@ -51,7 +92,10 @@ export class SolanaStakingClient implements IStakingClient {
51
92
  }
52
93
 
53
94
  const opts: ConnectionConfig = { commitment };
54
- if (config.network.rpcUrls.length > 1 && config.network.rpcUrls[1].startsWith('ws')) {
95
+ if (
96
+ config.network.rpcUrls.length > 1 &&
97
+ config.network.rpcUrls[1].startsWith('ws')
98
+ ) {
55
99
  opts.wsEndpoint = config.network.rpcUrls[1];
56
100
  }
57
101
 
@@ -60,7 +104,9 @@ export class SolanaStakingClient implements IStakingClient {
60
104
  async signTransaction<T extends SolanaTransaction>(tx: T): Promise<T> {
61
105
  return adapter.signTransaction(tx);
62
106
  },
63
- async signAllTransactions<T extends SolanaTransaction>(txs: T[]): Promise<T[]> {
107
+ async signAllTransactions<T extends SolanaTransaction>(
108
+ txs: T[],
109
+ ): Promise<T[]> {
64
110
  return Promise.all(txs.map((tx) => adapter.signTransaction(tx)));
65
111
  },
66
112
  };
@@ -73,75 +119,155 @@ export class SolanaStakingClient implements IStakingClient {
73
119
  this.distributionClient = new DistributionClient(this.anchor);
74
120
  this.leaderboardClient = new LeaderboardClient(this.anchor);
75
121
  this.outpostClient = new OutpostClient(this.anchor);
122
+ this.tokenClient = new TokenClient(this.anchor);
123
+ this.tokenClient = new TokenClient(this.anchor);
76
124
  }
77
125
 
78
126
  // ---------------------------------------------------------------------
79
- // Public IStakingClient Interface Methods
127
+ // IStakingClient core methods
80
128
  // ---------------------------------------------------------------------
81
129
 
82
130
  /**
83
- * Deposit SOL into liqSOL protocol (liqsol_core deposit flow).
84
- * @param amountLamports Amount of SOL to deposit (smallest unit)
85
- * @return Transaction signature
131
+ * Deposit native SOL into liqSOL (liqsol_core::deposit).
132
+ * Handles tx build, sign, send, and confirmation.
86
133
  */
87
134
  async deposit(amountLamports: bigint): Promise<string> {
88
- if (amountLamports <= BigInt(0)) throw new Error('Deposit amount must be greater than zero.');
89
- // const amount = new BN(amountLamports.toString());
135
+ if (amountLamports <= BigInt(0)) {
136
+ throw new Error('Deposit amount must be greater than zero.');
137
+ }
138
+
90
139
  const tx = await this.depositClient.buildDepositTx(amountLamports);
91
- const { tx: prepared, blockhash, lastValidBlockHeight } = await this.prepareTx(tx);
140
+ const { tx: prepared, blockhash, lastValidBlockHeight } = await this.prepareTx(
141
+ tx,
142
+ );
92
143
  const signed = await this.signTransaction(prepared);
93
- const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
94
- return result.signature;
144
+ return await this.sendAndConfirmHttp(signed, {
145
+ blockhash,
146
+ lastValidBlockHeight,
147
+ });
95
148
  }
96
149
 
97
150
  /**
98
151
  * Withdraw SOL from liqSOL protocol.
99
- * (Wire up once you have DepositClient.buildWithdrawTx or equivalent.)
152
+ * NOTE: placeholder until a withdraw flow is implemented in DepositClient.
100
153
  */
101
- async withdraw(amountLamports: bigint): Promise<string> {
154
+ async withdraw(_amountLamports: bigint): Promise<string> {
102
155
  throw new Error('Withdraw method not yet implemented.');
103
156
  }
104
157
 
105
158
  /**
106
159
  * Stake liqSOL into Outpost (liqSOL -> pool).
107
- * Matches deposit flow: build -> prepare -> sign -> send/confirm.
108
- * @param amountLamports Amount of liqSOL to stake (smallest unit)
160
+ * Ensures user ATA exists, then stakes via outpostClient.
109
161
  */
110
162
  async stake(amountLamports: bigint): Promise<string> {
111
- if (amountLamports <= BigInt(0)) throw new Error('Stake amount must be greater than zero.');
112
- // const amount = new BN(amountLamports.toString());
113
- const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(this.solPubKey);
163
+ if (amountLamports <= BigInt(0)) {
164
+ throw new Error('Stake amount must be greater than zero.');
165
+ }
166
+
167
+ const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(
168
+ this.solPubKey,
169
+ );
114
170
  const stakeIx = await this.outpostClient.buildStakeLiqsolIx(amountLamports);
115
171
  const tx = new Transaction().add(...preIxs, stakeIx);
172
+
116
173
  const prepared = await this.prepareTx(tx);
117
174
  const signed = await this.signTransaction(prepared.tx);
118
- const result = await this.sendAndConfirmHttp(signed, prepared);
119
- return result.signature;
175
+ return await this.sendAndConfirmHttp(signed, prepared);
120
176
  }
121
177
 
122
178
  /**
123
179
  * Unstake liqSOL from Outpost (pool -> liqSOL).
124
- * Matches deposit flow: build -> prepare -> sign -> send/confirm.
125
- * @param amountLamports Amount of liqSOL principal to unstake (smallest unit)
180
+ * Mirrors stake() but calls withdrawStake.
126
181
  */
127
182
  async unstake(amountLamports: bigint): Promise<string> {
128
- if (amountLamports <= BigInt(0)) throw new Error('Unstake amount must be greater than zero.');
183
+ if (amountLamports <= BigInt(0)) {
184
+ throw new Error('Unstake amount must be greater than zero.');
185
+ }
186
+
129
187
  const user = this.solPubKey;
130
- // const amount = new BN(amountLamports.toString());
131
188
  const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
132
- const withdrawIx = await this.outpostClient.buildWithdrawStakeIx(amountLamports);
189
+ const withdrawIx =
190
+ await this.outpostClient.buildWithdrawStakeIx(amountLamports);
133
191
  const tx = new Transaction().add(...preIxs, withdrawIx);
192
+
193
+ const prepared = await this.prepareTx(tx);
194
+ const signed = await this.signTransaction(prepared.tx);
195
+ return await this.sendAndConfirmHttp(signed, prepared);
196
+ }
197
+
198
+ /**
199
+ * Buy prelaunch WIRE “pretokens” using a supported asset.
200
+ *
201
+ * - SOL: uses purchase_with_sol
202
+ * - LIQSOL: uses purchase_with_liqsol
203
+ * - YIELD: uses purchase_warrants_from_yield
204
+ *
205
+ * ETH / LIQETH are not valid on Solana.
206
+ */
207
+ async buy(amountLamports: bigint, purchaseAsset: PurchaseAsset): Promise<string> {
208
+ const user = this.solPubKey;
209
+ let ix: TransactionInstruction;
210
+ let preIxs: TransactionInstruction[] = [];
211
+
212
+ switch (purchaseAsset) {
213
+ case PurchaseAsset.SOL: {
214
+ if (!amountLamports || amountLamports <= BigInt(0)) {
215
+ throw new Error('SOL pretoken purchase requires a positive amount.');
216
+ }
217
+ ix = await this.tokenClient.buildPurchaseWithSolIx(
218
+ amountLamports,
219
+ user,
220
+ );
221
+ break;
222
+ }
223
+
224
+ case PurchaseAsset.LIQSOL: {
225
+ if (!amountLamports || amountLamports <= BigInt(0)) {
226
+ throw new Error(
227
+ 'liqSOL pretoken purchase requires a positive amount.',
228
+ );
229
+ }
230
+ preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
231
+ ix = await this.tokenClient.buildPurchaseWithLiqsolIx(
232
+ amountLamports,
233
+ user,
234
+ );
235
+ break;
236
+ }
237
+
238
+ case PurchaseAsset.YIELD: {
239
+ // Amount is ignored by the on-chain program; it consumes tracked yield.
240
+ ix = await this.tokenClient.buildPurchaseFromYieldIx(user);
241
+ break;
242
+ }
243
+
244
+ case PurchaseAsset.ETH:
245
+ case PurchaseAsset.LIQETH: {
246
+ throw new Error(
247
+ 'ETH / LIQETH pretoken purchases are not supported on Solana.',
248
+ );
249
+ }
250
+
251
+ default:
252
+ throw new Error(`Unsupported pretoken purchase asset: ${String(
253
+ purchaseAsset,
254
+ )}`);
255
+ }
256
+
257
+ const tx = new Transaction().add(...preIxs, ix);
134
258
  const prepared = await this.prepareTx(tx);
135
259
  const signed = await this.signTransaction(prepared.tx);
136
- const result = await this.sendAndConfirmHttp(signed, prepared);
137
- return result.signature;
260
+ return await this.sendAndConfirmHttp(signed, prepared);
138
261
  }
139
262
 
140
263
  /**
141
- * native = SOL in wallet
142
- * liq = liqSOL token balance (from Token-2022 ATA)
143
- * staked = Outpost staked liqSOL principal (from wireReceipt.stakedLiqsol)
144
- * tracked = liqSOL tracked balance (from Distribution.userRecord)
264
+ * Aggregate view of the user’s balances on Solana:
265
+ * - native: SOL wallet balance
266
+ * - liq: liqSOL token balance (Token-2022 ATA)
267
+ * - staked: Outpost-staked liqSOL principal
268
+ * - tracked: distribution program trackedBalance (liqSOL)
269
+ * - wire: total prelaunch WIRE shares (warrants/pretokens, 1e8)
270
+ * - extras: useful internal addresses and raw state for debugging/UX
145
271
  */
146
272
  async getPortfolio(): Promise<Portfolio> {
147
273
  const user = this.solPubKey;
@@ -158,26 +284,36 @@ export class SolanaStakingClient implements IStakingClient {
158
284
  ASSOCIATED_TOKEN_PROGRAM_ID,
159
285
  );
160
286
 
161
- // IMPORTANT: use the SAME read primitive the outpost tests rely on
162
- const [nativeLamports, actualBalResp, userRecord, snapshot] = await Promise.all([
163
- this.connection.getBalance(user, "confirmed"),
164
- this.connection.getTokenAccountBalance(userLiqsolAta, "confirmed").catch(() => null),
287
+ const [
288
+ nativeLamports,
289
+ actualBalResp,
290
+ userRecord,
291
+ snapshot,
292
+ ] = await Promise.all([
293
+ this.connection.getBalance(user, 'confirmed'),
294
+ this.connection
295
+ .getTokenAccountBalance(userLiqsolAta, 'confirmed')
296
+ .catch(() => null),
165
297
  this.distributionClient.getUserRecord(user).catch(() => null),
166
298
  this.outpostClient.getWireStateSnapshot(user).catch(() => null),
167
299
  ]);
168
300
 
169
301
  const LIQSOL_DECIMALS = 9;
170
302
 
171
- const actualAmountStr = actualBalResp?.value?.amount ?? "0";
172
-
303
+ const actualAmountStr = actualBalResp?.value?.amount ?? '0';
173
304
  const trackedAmountStr =
174
- userRecord?.trackedBalance ? userRecord.trackedBalance.toString() : "0";
305
+ userRecord?.trackedBalance?.toString() ?? '0';
175
306
 
176
- // Snapshot is canonical; receipt may be null if user never staked
177
307
  const wireReceipt = snapshot?.wireReceipt ?? null;
308
+ const userWarrantRecord = snapshot?.userWarrantRecord ?? null;
309
+ const trancheState = snapshot?.trancheState ?? null;
310
+ const globalState = snapshot?.globalState ?? null;
178
311
 
179
312
  const stakedAmountStr =
180
- wireReceipt?.stakedLiqsol ? wireReceipt.stakedLiqsol.toString() : "0";
313
+ wireReceipt?.stakedLiqsol?.toString() ?? '0';
314
+
315
+ const wireSharesStr =
316
+ userWarrantRecord?.totalWarrantsPurchased?.toString() ?? '0';
181
317
 
182
318
  return {
183
319
  native: {
@@ -196,6 +332,12 @@ export class SolanaStakingClient implements IStakingClient {
196
332
  symbol: "LiqSOL",
197
333
  decimals: LIQSOL_DECIMALS,
198
334
  },
335
+ wire: {
336
+ // Prelaunch pretokens / WIRE shares (1e8)
337
+ amount: BigInt(wireSharesStr),
338
+ symbol: "$WIRE",
339
+ decimals: 8,
340
+ },
199
341
  tracked: {
200
342
  amount: BigInt(trackedAmountStr),
201
343
  symbol: "LiqSOL",
@@ -206,51 +348,201 @@ export class SolanaStakingClient implements IStakingClient {
206
348
  reservePoolPDA: reservePoolPDA.toBase58(),
207
349
  vaultPDA: vaultPDA.toBase58(),
208
350
  wireReceipt,
351
+ userWarrantRecord,
352
+ globalIndex: globalState?.currentIndex?.toString(),
353
+ totalShares: globalState?.totalShares?.toString(),
354
+ currentTrancheNumber: trancheState?.currentTrancheNumber?.toString(),
355
+ currentTranchePriceUsd: trancheState?.currentTranchePriceUsd?.toString(), // 1e8 USD
209
356
  },
210
- chainID: this.network.chainId as ChainID,
357
+ chainID: this.network.chainId,
211
358
  };
212
359
  }
213
360
 
214
- // ---------------------------------------------------------------------
215
- // SOL-only extras
216
- // ---------------------------------------------------------------------
361
+ /**
362
+ * Unified, chain-agnostic tranche snapshot for Solana.
363
+ *
364
+ * Uses:
365
+ * - liqsol_core.globalState (currentIndex, totalShares, etc.)
366
+ * - liqsol_core.trancheState (price, supply, total sold, etc.)
367
+ * - Chainlink/PriceHistory for SOL/USD (via TokenClient.getSolPriceUsdSafe)
368
+ *
369
+ * windowBefore/windowAfter control how many ladder rows we precompute
370
+ * around the current tranche for UI, but you can pass nothing if you
371
+ * only need current tranche info.
372
+ */
373
+ async getTrancheSnapshot(options?: {
374
+ chainID?: ChainID;
375
+ windowBefore?: number;
376
+ windowAfter?: number;
377
+ }): Promise<TrancheSnapshot> {
378
+ const {
379
+ chainID = SolChainID.WireTestnet,
380
+ windowBefore,
381
+ windowAfter,
382
+ } = options ?? {};
383
+
384
+ // Canonical program state
385
+ const [globalState, trancheState] = await Promise.all([
386
+ this.tokenClient.fetchGlobalState(),
387
+ this.tokenClient.fetchTrancheState(),
388
+ ]);
217
389
 
218
- async getUserRecord() {
219
- return this.distributionClient.getUserRecord(this.solPubKey);
390
+ // Latest SOL/USD price (1e8) + timestamp from PriceHistory
391
+ const { price: solPriceUsd, timestamp } =
392
+ await this.tokenClient.getSolPriceUsdSafe();
393
+
394
+ return buildSolanaTrancheSnapshot({
395
+ chainID,
396
+ globalState,
397
+ trancheState,
398
+ solPriceUsd,
399
+ nativePriceTimestamp: timestamp,
400
+ ladderWindowBefore: windowBefore,
401
+ ladderWindowAfter: windowAfter,
402
+ });
220
403
  }
221
404
 
222
- getProtocolFee() {
223
- // TODO
405
+ /**
406
+ * Approximate prelaunch WIRE quote for a given amount & asset.
407
+ *
408
+ * Uses TrancheSnapshot + SOL/USD price for:
409
+ * - SOL: amount is lamports
410
+ * - LIQSOL: amount is liqSOL base units (decimals = 9)
411
+ * - YIELD: amount is treated as SOL lamports-equivalent of yield
412
+ *
413
+ * NOTE: On-chain rounding may differ slightly (this is UI-only).
414
+ */
415
+ async getBuyQuote(
416
+ amount: bigint,
417
+ asset: PurchaseAsset,
418
+ opts?: { chainID?: ChainID },
419
+ ): Promise<PurchaseQuote> {
420
+ // For non-YIELD purchases we require a positive amount.
421
+ if (asset !== PurchaseAsset.YIELD && amount <= BigInt(0)) {
422
+ throw new Error('amount must be > 0 for non-YIELD purchases');
423
+ }
424
+
425
+ const snapshot = await this.getTrancheSnapshot({
426
+ chainID: opts?.chainID,
427
+ });
428
+
429
+ const wirePriceUsd = snapshot.currentPriceUsd; // 1e8
430
+ const solPriceUsd = snapshot.nativePriceUsd; // 1e8
431
+
432
+ if (!wirePriceUsd || wirePriceUsd <= BigInt(0)) {
433
+ throw new Error('Invalid WIRE price in tranche snapshot');
434
+ }
435
+ if (!solPriceUsd || solPriceUsd <= BigInt(0)) {
436
+ throw new Error('No SOL/USD price available');
437
+ }
438
+
439
+ const ONE_E9 = BigInt(LAMPORTS_PER_SOL); // 1e9
440
+ const ONE_E8 = BigInt(100_000_000); // 1e8
441
+
442
+ let notionalUsd: bigint;
443
+
444
+ switch (asset) {
445
+ case PurchaseAsset.SOL: {
446
+ // lamports * solPriceUsd / 1e9 → 1e8 USD
447
+ notionalUsd = (amount * solPriceUsd) / ONE_E9;
448
+ break;
449
+ }
450
+
451
+ case PurchaseAsset.LIQSOL: {
452
+ // liqSOL also uses 9 decimals; use same conversion.
453
+ notionalUsd = (amount * solPriceUsd) / ONE_E9;
454
+ break;
455
+ }
456
+
457
+ case PurchaseAsset.YIELD: {
458
+ // Treat amount as lamports-equivalent of SOL yield (UI convention).
459
+ notionalUsd = (amount * solPriceUsd) / ONE_E9;
460
+ break;
461
+ }
462
+
463
+ case PurchaseAsset.ETH:
464
+ case PurchaseAsset.LIQETH:
465
+ throw new Error('getBuyQuote for ETH/LIQETH is not supported on Solana');
466
+
467
+ default:
468
+ throw new Error(`Unsupported purchase asset: ${String(asset)}`);
469
+ }
470
+
471
+ // WIRE shares (1e8) = (notionalUsd * 1e8) / wirePriceUsd
472
+ // Add a small bias to avoid truncating to 0 on tiny buys.
473
+ const numerator = notionalUsd * ONE_E8;
474
+ const wireShares =
475
+ numerator === BigInt(0)
476
+ ? BigInt(0)
477
+ : (numerator + wirePriceUsd - BigInt(1)) / wirePriceUsd;
478
+
479
+ return {
480
+ purchaseAsset: asset,
481
+ amountIn: amount,
482
+ wireShares,
483
+ wireDecimals: 8,
484
+ wirePriceUsd,
485
+ notionalUsd,
486
+ };
487
+ }
488
+
489
+ /**
490
+ * Convenience helper to fetch the distribution userRecord for the current user.
491
+ * Used by balance-correction flows and debugging.
492
+ */
493
+ async getUserRecord() {
494
+ return this.distributionClient.getUserRecord(this.solPubKey);
224
495
  }
225
496
 
497
+ /**
498
+ * Run the "correct & register" flow on Solana:
499
+ * - builds the minimal transaction (maybe multi-user) to reconcile liqSOL
500
+ * - signs and sends the transaction if it can succeed
501
+ */
226
502
  async correctBalance(amount?: bigint): Promise<string> {
227
503
  const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
228
504
  if (!build.canSucceed || !build.transaction) {
229
505
  throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
230
506
  }
231
507
 
232
- const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(build.transaction);
508
+ const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(
509
+ build.transaction,
510
+ );
233
511
  const signed = await this.signTransaction(tx);
234
- const result = await this.sendAndConfirmHttp(signed, { blockhash, lastValidBlockHeight });
235
- return result.signature;
512
+ const signature = await this.sendAndConfirmHttp(signed, {
513
+ blockhash,
514
+ lastValidBlockHeight,
515
+ });
516
+ return signature;
236
517
  }
237
518
 
238
519
  // ---------------------------------------------------------------------
239
520
  // Tx helpers
240
521
  // ---------------------------------------------------------------------
241
522
 
523
+ /**
524
+ * Send a signed transaction over HTTP RPC and wait for confirmation.
525
+ * Throws if the transaction fails.
526
+ */
242
527
  private async sendAndConfirmHttp(
243
528
  signed: SolanaTransaction,
244
529
  ctx: { blockhash: string; lastValidBlockHeight: number },
245
- ): Promise<TxResult> {
246
- const signature = await this.connection.sendRawTransaction(signed.serialize(), {
247
- skipPreflight: false,
248
- preflightCommitment: commitment,
249
- maxRetries: 3,
250
- });
530
+ ): Promise<string> {
531
+ const signature = await this.connection.sendRawTransaction(
532
+ signed.serialize(),
533
+ {
534
+ skipPreflight: false,
535
+ preflightCommitment: commitment,
536
+ maxRetries: 3,
537
+ },
538
+ );
251
539
 
252
540
  const conf = await this.connection.confirmTransaction(
253
- { signature, blockhash: ctx.blockhash, lastValidBlockHeight: ctx.lastValidBlockHeight },
541
+ {
542
+ signature,
543
+ blockhash: ctx.blockhash,
544
+ lastValidBlockHeight: ctx.lastValidBlockHeight,
545
+ },
254
546
  commitment,
255
547
  );
256
548
 
@@ -258,29 +550,35 @@ export class SolanaStakingClient implements IStakingClient {
258
550
  throw new Error(`Transaction failed: ${JSON.stringify(conf.value.err)}`);
259
551
  }
260
552
 
261
- return { signature, slot: conf.context.slot, confirmed: true };
553
+ return signature;
262
554
  }
263
555
 
556
+ /**
557
+ * Sign a single Solana transaction using the connected wallet adapter.
558
+ */
264
559
  async signTransaction(tx: SolanaTransaction): Promise<SolanaTransaction> {
265
560
  return this.anchor.wallet.signTransaction(tx);
266
561
  }
267
562
 
563
+ /**
564
+ * Generic "fire and forget" send helper if the caller already
565
+ * prepared and signed the transaction.
566
+ */
268
567
  async sendTransaction(signed: SolanaTransaction): Promise<TransactionSignature> {
269
568
  return this.anchor.sendAndConfirm(signed);
270
569
  }
271
570
 
571
+ /**
572
+ * Attach recent blockhash + fee payer to a transaction.
573
+ * Required before signing and sending.
574
+ */
272
575
  async prepareTx(
273
576
  tx: Transaction,
274
577
  ): Promise<{ tx: Transaction; blockhash: string; lastValidBlockHeight: number }> {
275
- const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('confirmed');
578
+ const { blockhash, lastValidBlockHeight } =
579
+ await this.connection.getLatestBlockhash('confirmed');
276
580
  tx.recentBlockhash = blockhash;
277
581
  tx.feePayer = this.solPubKey;
278
582
  return { tx, blockhash, lastValidBlockHeight };
279
583
  }
280
- }
281
-
282
- export interface TxResult {
283
- signature: string;
284
- slot: number;
285
- confirmed: boolean;
286
584
  }
@@ -131,4 +131,14 @@ export type WireReceipt = {
131
131
  purchasedWithSol: BN;
132
132
  purchasedSolShares: BN;
133
133
  bump: number;
134
- }
134
+ }
135
+
136
+
137
+ export type PriceHistory = {
138
+ windowSize: number;
139
+ prices: BN[];
140
+ count: number;
141
+ nextIndex: number;
142
+ bump: number;
143
+ }
144
+