@wireio/stake 0.3.0 → 0.4.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.
@@ -1,11 +1,10 @@
1
1
  import {
2
2
  Commitment,
3
+ ComputeBudgetProgram,
3
4
  Connection,
4
5
  ConnectionConfig,
5
- LAMPORTS_PER_SOL,
6
6
  PublicKey as SolPubKey,
7
7
  Transaction,
8
- TransactionInstruction,
9
8
  TransactionSignature,
10
9
  } from '@solana/web3.js';
11
10
  import { AnchorProvider } from '@coral-xyz/anchor';
@@ -41,6 +40,7 @@ import {
41
40
  deriveLiqsolMintPda,
42
41
  deriveReservePoolPda,
43
42
  deriveVaultPda,
43
+ INDEX_SCALE,
44
44
  } from './constants';
45
45
 
46
46
  import { buildSolanaTrancheSnapshot } from './utils';
@@ -52,23 +52,23 @@ const commitment: Commitment = 'confirmed';
52
52
  * Solana implementation of IStakingClient.
53
53
  *
54
54
  * Responsibilities:
55
- * - Wire together liqSOL deposit/withdraw
56
- * - Outpost stake/unstake
57
- * - Prelaunch WIRE (pretokens) buy flows
58
- * - Unified portfolio + tranche snapshot + buy quotes
55
+ * - liqSOL deposit (SOL -> liqSOL)
56
+ * - withdraw requests (liqSOL -> NFT receipt / encumbered SOL)
57
+ * - Pretoken (WIRE) buys via liqSOL
58
+ * - Unified portfolio + tranche snapshot + balance correction (later)
59
59
  *
60
- * This class composes lower-level clients; it does not know about UI.
60
+ * This composes lower-level clients; it does not know about UI.
61
61
  */
62
62
  export class SolanaStakingClient implements IStakingClient {
63
- public pubKey?: PublicKey;
63
+ public pubKey?: PublicKey; // Wire ED key (optional → read-only)
64
64
  public connection: Connection;
65
65
  public anchor: AnchorProvider;
66
66
 
67
- private depositClient: DepositClient;
68
- private distributionClient: DistributionClient;
69
- private leaderboardClient: LeaderboardClient;
70
- private outpostClient: OutpostClient;
71
- private tokenClient: TokenClient;
67
+ public depositClient: DepositClient;
68
+ public distributionClient: DistributionClient;
69
+ public leaderboardClient: LeaderboardClient;
70
+ public outpostClient: OutpostClient;
71
+ public tokenClient: TokenClient;
72
72
 
73
73
  get solPubKey(): SolPubKey {
74
74
  if (!this.pubKey) throw new Error('pubKey is undefined');
@@ -80,20 +80,53 @@ export class SolanaStakingClient implements IStakingClient {
80
80
  }
81
81
 
82
82
  constructor(private config: StakerConfig) {
83
- const adapter = config.provider as BaseSignerWalletAdapter;
84
- // if (!adapter?.publicKey) throw new Error('Solana wallet adapter not connected');
85
- if (!config.network.rpcUrls.length) throw new Error('No RPC URLs provided');
86
-
87
- const publicKey = adapter.publicKey || new SolPubKey(new Uint8Array(32))
88
- if (config.pubKey){
89
- // If pubKey provided, ensure it matches the adapter's pubkey
90
- const wirePub = new PublicKey(KeyType.ED, publicKey.toBytes());
91
- if (!wirePub.equals(config.pubKey))
92
- throw new Error("Passed-in pubKey doesn't match adapter.publicKey");
93
- // All good; assign it
83
+ const adapter = config.provider as BaseSignerWalletAdapter | undefined;
84
+
85
+ if (!config.network.rpcUrls.length) {
86
+ throw new Error('No RPC URLs provided');
87
+ }
88
+
89
+ // -------------------------------------------------------------
90
+ // Resolve Solana wallet pubkey (or dummy in read-only mode)
91
+ // -------------------------------------------------------------
92
+ let solWalletPubkey: SolPubKey;
93
+ if (adapter?.publicKey) {
94
+ solWalletPubkey = adapter.publicKey;
95
+ } else {
96
+ // Zeroed pubkey when no wallet (read-only usage)
97
+ solWalletPubkey = new SolPubKey(new Uint8Array(32));
98
+ }
99
+
100
+ // -------------------------------------------------------------
101
+ // Resolve Wire pubKey
102
+ // -------------------------------------------------------------
103
+ if (config.pubKey) {
104
+ const wirePub = config.pubKey;
105
+
106
+ if (adapter?.publicKey) {
107
+ const derived = new PublicKey(
108
+ KeyType.ED,
109
+ adapter.publicKey.toBytes(),
110
+ );
111
+ if (!derived.equals(wirePub)) {
112
+ throw new Error(
113
+ "Passed-in pubKey doesn't match adapter.publicKey",
114
+ );
115
+ }
116
+ }
117
+
94
118
  this.pubKey = wirePub;
119
+ } else if (adapter?.publicKey) {
120
+ // Derive Wire pubKey from adapter when not explicitly passed
121
+ this.pubKey = new PublicKey(
122
+ KeyType.ED,
123
+ adapter.publicKey.toBytes(),
124
+ );
95
125
  }
96
126
 
127
+ // -------------------------------------------------------------
128
+ // Connection + AnchorProvider
129
+ // -------------------------------------------------------------
97
130
  const opts: ConnectionConfig = { commitment };
98
131
  if (
99
132
  config.network.rpcUrls.length > 1 &&
@@ -102,20 +135,47 @@ export class SolanaStakingClient implements IStakingClient {
102
135
  opts.wsEndpoint = config.network.rpcUrls[1];
103
136
  }
104
137
 
105
- const anchorWallet = {
106
- publicKey,
107
- async signTransaction<T extends SolanaTransaction>(tx: T): Promise<T> {
108
- return adapter.signTransaction(tx);
109
- },
110
- async signAllTransactions<T extends SolanaTransaction>(
111
- txs: T[],
112
- ): Promise<T[]> {
113
- return Promise.all(txs.map((tx) => adapter.signTransaction(tx)));
114
- },
115
- };
138
+ const anchorWallet = adapter
139
+ ? {
140
+ publicKey: solWalletPubkey,
141
+ async signTransaction<T extends SolanaTransaction>(
142
+ tx: T,
143
+ ): Promise<T> {
144
+ return adapter.signTransaction(tx);
145
+ },
146
+ async signAllTransactions<T extends SolanaTransaction>(
147
+ txs: T[],
148
+ ): Promise<T[]> {
149
+ if (adapter.signAllTransactions) {
150
+ return adapter.signAllTransactions(txs);
151
+ }
152
+ return Promise.all(
153
+ txs.map((tx) => adapter.signTransaction(tx)),
154
+ );
155
+ },
156
+ }
157
+ : {
158
+ publicKey: solWalletPubkey,
159
+ async signTransaction<T extends SolanaTransaction>(
160
+ _tx: T,
161
+ ): Promise<T> {
162
+ throw new Error(
163
+ 'Wallet not connected: signTransaction not available',
164
+ );
165
+ },
166
+ async signAllTransactions<T extends SolanaTransaction>(
167
+ _txs: T[],
168
+ ): Promise<T[]> {
169
+ throw new Error(
170
+ 'Wallet not connected: signAllTransactions not available',
171
+ );
172
+ },
173
+ };
116
174
 
117
175
  this.connection = new Connection(config.network.rpcUrls[0], opts);
118
- this.anchor = new AnchorProvider(this.connection, anchorWallet, { commitment });
176
+ this.anchor = new AnchorProvider(this.connection, anchorWallet, {
177
+ commitment,
178
+ });
119
179
 
120
180
  this.depositClient = new DepositClient(this.anchor);
121
181
  this.distributionClient = new DistributionClient(this.anchor);
@@ -139,9 +199,8 @@ export class SolanaStakingClient implements IStakingClient {
139
199
  }
140
200
 
141
201
  const tx = await this.depositClient.buildDepositTx(amountLamports);
142
- const { tx: prepared, blockhash, lastValidBlockHeight } = await this.prepareTx(
143
- tx,
144
- );
202
+ const { tx: prepared, blockhash, lastValidBlockHeight } =
203
+ await this.prepareTx(tx);
145
204
  const signed = await this.signTransaction(prepared);
146
205
  return await this.sendAndConfirmHttp(signed, {
147
206
  blockhash,
@@ -150,89 +209,109 @@ export class SolanaStakingClient implements IStakingClient {
150
209
  }
151
210
 
152
211
  /**
153
- * Withdraw SOL from liqSOL protocol.
154
- * NOTE: placeholder until a withdraw flow is implemented in DepositClient.
212
+ * Request a withdraw from liqSOL to SOL (liqsol_core::requestWithdraw).
213
+ *
214
+ * This:
215
+ * - burns liqSOL from the user ATA
216
+ * - mints an NFT withdrawal receipt
217
+ * - increases totalEncumberedFunds in global state
218
+ *
219
+ * Actual SOL payout happens later via the operator-side flow.
155
220
  */
156
- async withdraw(_amountLamports: bigint): Promise<string> {
221
+ async withdraw(amountLamports: bigint): Promise<string> {
157
222
  this.ensureWriteAccess();
158
- throw new Error('Withdraw method not yet implemented.');
223
+ if (amountLamports <= BigInt(0)) {
224
+ throw new Error('Withdraw amount must be greater than zero.');
225
+ }
226
+
227
+ const tx = await this.depositClient.buildWithdrawTx(amountLamports);
228
+ const { tx: prepared, blockhash, lastValidBlockHeight } =
229
+ await this.prepareTx(tx);
230
+ const signed = await this.signTransaction(prepared);
231
+ return await this.sendAndConfirmHttp(signed, {
232
+ blockhash,
233
+ lastValidBlockHeight,
234
+ });
159
235
  }
160
236
 
161
237
  /**
162
- * Stake liqSOL into Outpost (liqSOL -> pool).
163
- * Ensures user ATA exists, then stakes via outpostClient.
238
+ * Stake liqSOL into Outpost (liqSOL pool) via liqsol_core::synd.
164
239
  */
165
240
  async stake(amountLamports: bigint): Promise<string> {
166
241
  this.ensureWriteAccess();
167
- if (amountLamports <= BigInt(0)) {
242
+
243
+ if (!amountLamports || amountLamports <= BigInt(0)) {
168
244
  throw new Error('Stake amount must be greater than zero.');
169
245
  }
170
246
 
171
- const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(
172
- this.solPubKey,
173
- );
174
- const stakeIx = await this.outpostClient.buildStakeLiqsolIx(amountLamports);
175
- const tx = new Transaction().add(...preIxs, stakeIx);
247
+ const user = this.solPubKey;
248
+
249
+ // Build the Outpost synd instruction
250
+ const ix = await this.outpostClient.buildStakeIx(amountLamports, user);
176
251
 
252
+ // Wrap in a transaction and send
253
+ const tx = new Transaction().add(ix);
177
254
  const prepared = await this.prepareTx(tx);
178
255
  const signed = await this.signTransaction(prepared.tx);
179
- return await this.sendAndConfirmHttp(signed, prepared);
256
+
257
+ return this.sendAndConfirmHttp(signed, prepared);
180
258
  }
181
259
 
182
260
  /**
183
- * Unstake liqSOL from Outpost (pool -> liqSOL).
184
- * Mirrors stake() but calls withdrawStake.
261
+ * Unstake liqSOL from Outpost (pool liqSOL) via liqsol_core::desynd.
185
262
  */
186
263
  async unstake(amountLamports: bigint): Promise<string> {
187
264
  this.ensureWriteAccess();
188
- if (amountLamports <= BigInt(0)) {
265
+
266
+ if (!amountLamports || amountLamports <= BigInt(0)) {
189
267
  throw new Error('Unstake amount must be greater than zero.');
190
268
  }
191
269
 
192
270
  const user = this.solPubKey;
193
- const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
194
- const withdrawIx =
195
- await this.outpostClient.buildWithdrawStakeIx(amountLamports);
196
- const tx = new Transaction().add(...preIxs, withdrawIx);
197
271
 
272
+ // Build the Outpost desynd instruction
273
+ const ix = await this.outpostClient.buildUnstakeIx(amountLamports, user);
274
+
275
+ // Wrap in a transaction and send
276
+ const tx = new Transaction().add(ix);
198
277
  const prepared = await this.prepareTx(tx);
199
278
  const signed = await this.signTransaction(prepared.tx);
200
- return await this.sendAndConfirmHttp(signed, prepared);
279
+
280
+ return this.sendAndConfirmHttp(signed, prepared);
201
281
  }
202
282
 
203
283
  /**
204
- * Buy prelaunch WIRE “pretokens” using a supported asset.
205
- *
206
- * - SOL: uses purchase_with_sol
207
- * - LIQSOL: uses purchase_with_liqsol
208
- * - YIELD: uses purchase_warrants_from_yield
284
+ * Buy prelaunch WIRE “pretokens” using liqSOL.
209
285
  *
210
- * ETH / LIQETH are not valid on Solana.
286
+ * This delegates to TokenClient, which uses the `purchase`-style
287
+ * instruction under the new IDL (no more native-SOL purchase).
211
288
  */
212
289
  async buy(amountLamports: bigint): Promise<string> {
213
290
  this.ensureWriteAccess();
214
- if (!amountLamports || amountLamports <= BigInt(0))
291
+ if (!amountLamports || amountLamports <= BigInt(0)) {
215
292
  throw new Error('liqSOL pretoken purchase requires a positive amount.');
216
-
293
+ }
294
+
217
295
  const user = this.solPubKey;
218
- const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
219
- const ix = await this.tokenClient.buildPurchaseWithLiqsolIx(
220
- amountLamports,
221
- user,
222
- );
223
- const tx = new Transaction().add(...preIxs, ix);
224
- const prepared = await this.prepareTx(tx);
225
- const signed = await this.signTransaction(prepared.tx);
226
- return await this.sendAndConfirmHttp(signed, prepared);
296
+ const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
297
+ const ix = await this.tokenClient.buildPurchaseIx(amountLamports, user);
298
+ const tx = new Transaction().add(cuIx, ix);
299
+ const { tx: prepared, blockhash, lastValidBlockHeight } =
300
+ await this.prepareTx(tx);
301
+ const signed = await this.signTransaction(prepared);
302
+ return await this.sendAndConfirmHttp(signed, {
303
+ blockhash,
304
+ lastValidBlockHeight,
305
+ });
227
306
  }
228
307
 
229
308
  /**
230
309
  * Aggregate view of the user’s balances on Solana:
231
310
  * - native: SOL wallet balance
232
311
  * - liq: liqSOL token balance (Token-2022 ATA)
233
- * - staked: Outpost-staked liqSOL principal
234
- * - tracked: distribution program trackedBalance (liqSOL)
235
- * - wire: total prelaunch WIRE shares (warrants/pretokens, 1e8)
312
+ * - staked: liqSOL staked in Outpost (synd/desynd) → outpostAccount.stakedLiqsol
313
+ * - wire: total WIRE pretokens purchased (1e8 scale)
314
+ * - yield: on-chain index/shares plus an estimated accrued liqSOL yield
236
315
  * - extras: useful internal addresses and raw state for debugging/UX
237
316
  */
238
317
  async getPortfolio(): Promise<Portfolio> {
@@ -252,75 +331,123 @@ export class SolanaStakingClient implements IStakingClient {
252
331
  ASSOCIATED_TOKEN_PROGRAM_ID,
253
332
  );
254
333
 
255
- const [
256
- nativeLamports,
257
- actualBalResp,
258
- userRecord,
259
- snapshot,
260
- ] = await Promise.all([
334
+ // NOTE:
335
+ // - nativeLamports: wallet SOL
336
+ // - actualBalResp: liqSOL balance in user ATA
337
+ // - snapshot: Outpost + pretokens + global index/shares
338
+ const [nativeLamports, actualBalResp, snapshot] = await Promise.all([
261
339
  this.connection.getBalance(user, 'confirmed'),
262
340
  this.connection
263
341
  .getTokenAccountBalance(userLiqsolAta, 'confirmed')
264
342
  .catch(() => null),
265
- this.distributionClient.getUserRecord(user).catch(() => null),
266
- this.outpostClient.getWireStateSnapshot(user).catch(() => null),
343
+ this.outpostClient.fetchWireState(user).catch(() => null),
267
344
  ]);
268
345
 
269
346
  const LIQSOL_DECIMALS = 9;
270
347
 
271
348
  const actualAmountStr = actualBalResp?.value?.amount ?? '0';
272
- const trackedAmountStr =
273
- userRecord?.trackedBalance?.toString() ?? '0';
274
349
 
275
- const wireReceipt = snapshot?.wireReceipt ?? null;
276
- const userWarrantRecord = snapshot?.userWarrantRecord ?? null;
277
- const trancheState = snapshot?.trancheState ?? null;
278
350
  const globalState = snapshot?.globalState ?? null;
279
-
280
- const stakedAmountStr =
281
- wireReceipt?.stakedLiqsol?.toString() ?? '0';
282
-
283
- const wireSharesStr =
284
- userWarrantRecord?.totalPretokensPurchased?.toString() ?? '0';
351
+ const outpostAccount = snapshot?.outpostAccount ?? null;
352
+ const trancheState = snapshot?.trancheState ?? null;
353
+ const userPretokenRecord = snapshot?.userPretokenRecord ?? null;
354
+
355
+ // -----------------------------
356
+ // Staked liqSOL (Outpost)
357
+ // -----------------------------
358
+ // This is the liqSOL that has been syndicated into Outpost via `synd`.
359
+ // It lives on the outpost account as `stakedLiqsol`.
360
+ const stakedLiqsolStr =
361
+ outpostAccount?.stakedLiqsol?.toString?.() ?? '0';
362
+
363
+ // -----------------------------
364
+ // WIRE pretokens (1e8 scale)
365
+ // -----------------------------
366
+ // This is NOT stake — it’s the prelaunch WIRE position.
367
+ const wirePretokensStr =
368
+ userPretokenRecord?.totalPretokensPurchased?.toString?.() ??
369
+ '0';
370
+
371
+ // -----------------------------
372
+ // Yield view (index + shares)
373
+ // -----------------------------
374
+ // We expose:
375
+ // - currentIndex: globalState.currentIndex (1e12 scale)
376
+ // - totalShares: globalState.totalShares
377
+ // - userShares: outpostAccount.stakedShares
378
+ // - estimatedClaimLiqsol: floor(userShares * index / INDEX_SCALE)
379
+ // - estimatedYieldLiqsol: max(0, estimatedClaim - stakedLiqsol)
380
+ //
381
+ // This matches the capital-staking math:
382
+ // sharesToTokens(shares, index) = shares * index / INDEX_SCALE
383
+ const currentIndexStr =
384
+ globalState?.currentIndex?.toString?.() ?? '0';
385
+ const totalSharesStr =
386
+ globalState?.totalShares?.toString?.() ?? '0';
387
+ const userSharesStr =
388
+ outpostAccount?.stakedShares?.toString?.() ?? '0';
389
+
390
+ const stakedLiqsol = BigInt(stakedLiqsolStr);
391
+ const currentIndex = BigInt(currentIndexStr);
392
+ const totalShares = BigInt(totalSharesStr);
393
+ const userShares = BigInt(userSharesStr);
394
+
395
+ let estimatedClaimLiqsol = BigInt(0);
396
+ let estimatedYieldLiqsol = BigInt(0);
397
+
398
+ if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
399
+ // sharesToTokens(userShares, currentIndex)
400
+ estimatedClaimLiqsol = (userShares * currentIndex) / INDEX_SCALE;
401
+
402
+ if (estimatedClaimLiqsol > stakedLiqsol) {
403
+ estimatedYieldLiqsol = estimatedClaimLiqsol - stakedLiqsol;
404
+ }
405
+ }
285
406
 
286
407
  return {
287
408
  native: {
288
409
  amount: BigInt(nativeLamports),
289
- symbol: "SOL",
410
+ symbol: 'SOL',
290
411
  decimals: 9,
291
412
  },
292
413
  liq: {
293
414
  amount: BigInt(actualAmountStr),
294
- symbol: "LiqSOL",
415
+ symbol: 'LiqSOL',
295
416
  decimals: LIQSOL_DECIMALS,
296
417
  ata: userLiqsolAta,
297
418
  },
298
419
  staked: {
299
- amount: BigInt(stakedAmountStr),
300
- symbol: "LiqSOL",
420
+ // liqSOL staked in Outpost via `synd`
421
+ amount: stakedLiqsol,
422
+ symbol: 'LiqSOL',
301
423
  decimals: LIQSOL_DECIMALS,
302
424
  },
303
425
  wire: {
304
- // Prelaunch pretokens / WIRE shares (1e8)
305
- amount: BigInt(wireSharesStr),
306
- symbol: "$WIRE",
426
+ // Prelaunch WIRE pretokens (1e8 scale)
427
+ amount: BigInt(wirePretokensStr),
428
+ symbol: '$WIRE',
307
429
  decimals: 8,
308
430
  },
309
- tracked: {
310
- amount: BigInt(trackedAmountStr),
311
- symbol: "LiqSOL",
312
- decimals: LIQSOL_DECIMALS,
431
+ yield: {
432
+ // Raw primitives so the frontend can display curves, charts, etc.
433
+ currentIndex,
434
+ indexScale: INDEX_SCALE,
435
+ totalShares,
436
+ userShares,
437
+ // liqSOL amounts (lamports) implied by index/shares
438
+ estimatedClaimLiqsol,
439
+ estimatedYieldLiqsol,
313
440
  },
314
441
  extras: {
315
442
  userLiqsolAta: userLiqsolAta.toBase58(),
316
443
  reservePoolPDA: reservePoolPDA.toBase58(),
317
444
  vaultPDA: vaultPDA.toBase58(),
318
- wireReceipt,
319
- userWarrantRecord,
320
445
  globalIndex: globalState?.currentIndex?.toString(),
321
446
  totalShares: globalState?.totalShares?.toString(),
322
- currentTrancheNumber: trancheState?.currentTrancheNumber?.toString(),
323
- currentTranchePriceUsd: trancheState?.currentTranchePriceUsd?.toString(), // 1e8 USD
447
+ currentTrancheNumber:
448
+ trancheState?.currentTrancheNumber?.toString(),
449
+ currentTranchePriceUsd:
450
+ trancheState?.currentTranchePriceUsd?.toString(), // 1e8 USD
324
451
  },
325
452
  chainID: this.network.chainId,
326
453
  };
@@ -332,13 +459,9 @@ export class SolanaStakingClient implements IStakingClient {
332
459
  * Uses:
333
460
  * - liqsol_core.globalState (currentIndex, totalShares, etc.)
334
461
  * - liqsol_core.trancheState (price, supply, total sold, etc.)
335
- * - Chainlink/PriceHistory for SOL/USD (via TokenClient.getSolPriceUsdSafe)
462
+ * - PriceHistory/Chainlink SOL/USD via TokenClient.getSolPriceUsdSafe()
336
463
  *
337
- * windowBefore/windowAfter control how many ladder rows we precompute
338
- * around the current tranche for UI, but you can pass nothing if you
339
- * only need current tranche info.
340
- *
341
- * READ-ONLY allowed
464
+ * This is READ-ONLY and works even with no connected wallet.
342
465
  */
343
466
  async getTrancheSnapshot(options?: {
344
467
  chainID?: ChainID;
@@ -351,13 +474,11 @@ export class SolanaStakingClient implements IStakingClient {
351
474
  windowAfter,
352
475
  } = options ?? {};
353
476
 
354
- // Canonical program state
355
477
  const [globalState, trancheState] = await Promise.all([
356
478
  this.tokenClient.fetchGlobalState(),
357
479
  this.tokenClient.fetchTrancheState(),
358
480
  ]);
359
481
 
360
- // Latest SOL/USD price (1e8) + timestamp from PriceHistory
361
482
  const { price: solPriceUsd, timestamp } =
362
483
  await this.tokenClient.getSolPriceUsdSafe();
363
484
 
@@ -381,29 +502,6 @@ export class SolanaStakingClient implements IStakingClient {
381
502
  return this.distributionClient.getUserRecord(this.solPubKey);
382
503
  }
383
504
 
384
- /**
385
- * Run the "correct & register" flow on Solana:
386
- * - builds the minimal transaction (maybe multi-user) to reconcile liqSOL
387
- * - signs and sends the transaction if it can succeed
388
- */
389
- async correctBalance(amount?: bigint): Promise<string> {
390
- this.ensureWriteAccess();
391
- const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
392
- if (!build.canSucceed || !build.transaction) {
393
- throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
394
- }
395
-
396
- const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(
397
- build.transaction,
398
- );
399
- const signed = await this.signTransaction(tx);
400
- const signature = await this.sendAndConfirmHttp(signed, {
401
- blockhash,
402
- lastValidBlockHeight,
403
- });
404
- return signature;
405
- }
406
-
407
505
  // ---------------------------------------------------------------------
408
506
  // Tx helpers
409
507
  // ---------------------------------------------------------------------
@@ -417,7 +515,8 @@ export class SolanaStakingClient implements IStakingClient {
417
515
  ctx: { blockhash: string; lastValidBlockHeight: number },
418
516
  ): Promise<string> {
419
517
  this.ensureWriteAccess();
420
- const signature = await this.connection.sendRawTransaction(
518
+
519
+ const signature = await this.connection.sendRawTransaction(
421
520
  signed.serialize(),
422
521
  {
423
522
  skipPreflight: false,
@@ -436,7 +535,9 @@ export class SolanaStakingClient implements IStakingClient {
436
535
  );
437
536
 
438
537
  if (conf.value.err) {
439
- throw new Error(`Transaction failed: ${JSON.stringify(conf.value.err)}`);
538
+ throw new Error(
539
+ `Transaction failed: ${JSON.stringify(conf.value.err)}`,
540
+ );
440
541
  }
441
542
 
442
543
  return signature;
@@ -445,7 +546,9 @@ export class SolanaStakingClient implements IStakingClient {
445
546
  /**
446
547
  * Sign a single Solana transaction using the connected wallet adapter.
447
548
  */
448
- async signTransaction(tx: SolanaTransaction): Promise<SolanaTransaction> {
549
+ async signTransaction(
550
+ tx: SolanaTransaction,
551
+ ): Promise<SolanaTransaction> {
449
552
  this.ensureWriteAccess();
450
553
  return this.anchor.wallet.signTransaction(tx);
451
554
  }
@@ -454,7 +557,9 @@ export class SolanaStakingClient implements IStakingClient {
454
557
  * Generic "fire and forget" send helper if the caller already
455
558
  * prepared and signed the transaction.
456
559
  */
457
- async sendTransaction(signed: SolanaTransaction): Promise<TransactionSignature> {
560
+ async sendTransaction(
561
+ signed: SolanaTransaction,
562
+ ): Promise<TransactionSignature> {
458
563
  this.ensureWriteAccess();
459
564
  return this.anchor.sendAndConfirm(signed);
460
565
  }
@@ -465,7 +570,11 @@ export class SolanaStakingClient implements IStakingClient {
465
570
  */
466
571
  async prepareTx(
467
572
  tx: Transaction,
468
- ): Promise<{ tx: Transaction; blockhash: string; lastValidBlockHeight: number }> {
573
+ ): Promise<{
574
+ tx: Transaction;
575
+ blockhash: string;
576
+ lastValidBlockHeight: number;
577
+ }> {
469
578
  const { blockhash, lastValidBlockHeight } =
470
579
  await this.connection.getLatestBlockhash('confirmed');
471
580
  tx.recentBlockhash = blockhash;
@@ -473,10 +582,21 @@ export class SolanaStakingClient implements IStakingClient {
473
582
  return { tx, blockhash, lastValidBlockHeight };
474
583
  }
475
584
 
585
+ /**
586
+ * Guard for all write operations (deposit/withdraw/stake/unstake/buy).
587
+ * Ensures we have a Wire pubKey and an Anchor wallet pubKey, and that they match.
588
+ */
476
589
  ensureWriteAccess() {
477
- if (!this.pubKey || !this.anchor.wallet.publicKey)
590
+ if (!this.pubKey || !this.anchor.wallet.publicKey) {
478
591
  throw new Error('User Authorization required: pubKey is undefined');
479
- if (this.solPubKey.toBase58() !== this.anchor.wallet.publicKey.toBase58())
480
- throw new Error('Write access requires connected wallet to match pubKey');
592
+ }
593
+ if (
594
+ this.solPubKey.toBase58() !==
595
+ this.anchor.wallet.publicKey.toBase58()
596
+ ) {
597
+ throw new Error(
598
+ 'Write access requires connected wallet to match pubKey',
599
+ );
600
+ }
481
601
  }
482
602
  }