@wireio/stake 0.2.3 → 0.2.5

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.
@@ -0,0 +1,314 @@
1
+
2
+ import { ethers } from "ethers";
3
+ import { TrancheLadderItem, TrancheSnapshot } from "../../types";
4
+ import { ChainID } from "@wireio/core";
5
+
6
+
7
+ const BPS = BigInt(10_000);
8
+
9
+ export interface CustomContractError {
10
+ name?: string;
11
+ signature?: string;
12
+ args?: any[];
13
+ data?: any;
14
+ method?: string;
15
+ code?: any;
16
+ raw: string;
17
+ }
18
+
19
+
20
+
21
+ export function formatContractErrors(err: any): CustomContractError {
22
+ // Extract custom error details if present
23
+ if (err.errorName && err.errorArgs) {
24
+ const errorObj: CustomContractError = {
25
+ name: err.errorName,
26
+ signature: err.errorSignature,
27
+ args: err.errorArgs.map((arg: any) =>
28
+ arg && arg._isBigNumber ? ethers.BigNumber.from(arg).toString() : arg
29
+ ),
30
+ data: err.data,
31
+ method: err.method,
32
+ code: err.code,
33
+ raw: err,
34
+ };
35
+ console.error("Custom contract error:", errorObj);
36
+ return errorObj;
37
+ } else {
38
+ console.error("Contract Error:", err);
39
+ return {
40
+ raw: typeof err === 'string' ? err : (err?.message || String(err))
41
+ };
42
+ }
43
+ }
44
+
45
+
46
+
47
+ /**
48
+ * Finalize an OPP epoch. Optionally accept a gasLimit override (ethers BigNumberish).
49
+ * If gasLimit is not provided, the method will attempt estimateGas.finalizeEpoch()
50
+ * and pad it (1.2x). If estimateGas fails, a conservative fallback is used.
51
+ */
52
+ export async function sendOPPFinalize(opp: ethers.Contract, gasLimit?: ethers.BigNumberish) {
53
+ const overrides: any = {};
54
+ try {
55
+ if (gasLimit === undefined) {
56
+ try {
57
+ const estimated = await opp.estimateGas.finalizeEpoch();
58
+ const padded = ethers.BigNumber.from(estimated).mul(12).div(10); // 1.2x
59
+ overrides.gasLimit = padded;
60
+ console.log('sendFinalize: estimated gas', estimated.toString(), 'padded to', overrides.gasLimit.toString());
61
+ } catch (estErr) {
62
+ // estimateGas can throw UNPREDICTABLE_GAS_LIMIT; fall back to a safe default
63
+ console.warn('sendFinalize: estimateGas.finalizeEpoch() failed, falling back to hardcoded gasLimit', estErr);
64
+ overrides.gasLimit = ethers.BigNumber.from(8000000);
65
+ }
66
+ } else {
67
+ overrides.gasLimit = ethers.BigNumber.from(gasLimit);
68
+ console.log('sendFinalize: using provided gasLimit override', overrides.gasLimit.toString());
69
+ }
70
+
71
+ const tx = await opp.finalizeEpoch(overrides); // submit tx with overrides
72
+ console.log('sendFinalize tx hash:', tx.hash, 'overrides:', overrides);
73
+
74
+ const receipt = await tx.wait(); // wait for mined
75
+ console.log('sendFinalize tx mined, block:', receipt.blockNumber);
76
+ return receipt;
77
+ } catch (err: any) {
78
+ // Verbose error logging to help debugging reverts / provider issues
79
+ console.error('sendFinalize() failed:', err?.message || err);
80
+
81
+ // Try to extract common raw payload locations used by ethers errors
82
+ const raw = err?.error?.data || err?.data || err?.body || err?.receipt || err?.transaction || err;
83
+ console.error('sendFinalize raw payload:', raw);
84
+
85
+ // Try parsing with contractService if available
86
+ try {
87
+ const parsed = formatContractErrors(raw);
88
+ console.error('sendFinalize parsed error:', parsed.name, parsed.args);
89
+ } catch (parseErr) {
90
+ // Fallback: decode Error(string) ABI encoded revert (0x08c379a0)
91
+ try {
92
+ const hex = (typeof raw === 'string') ? raw : (raw && raw.data) ? raw.data : null;
93
+ if (hex && typeof hex === 'string' && hex.startsWith('0x08c379a0')) {
94
+ const reason = ethers.utils.defaultAbiCoder.decode(['string'], '0x' + hex.slice(10))[0];
95
+ console.error('sendFinalize revert reason:', reason);
96
+ } else {
97
+ console.error('sendFinalize: unable to decode revert payload (not standard Error(string))');
98
+ }
99
+ } catch (fallbackErr) {
100
+ console.error('sendFinalize: fallback decode failed:', fallbackErr);
101
+ }
102
+ }
103
+
104
+ // If there is a receipt, print it for extra context
105
+ if (err?.receipt) console.error('sendFinalize receipt:', err.receipt);
106
+ if (err?.transaction) console.error('sendFinalize transaction object:', err.transaction);
107
+
108
+ // Re-throw so callers can handle it as well
109
+ throw err;
110
+ }
111
+ }
112
+
113
+
114
+
115
+ /**
116
+ * Apply one forward growth step: value * (BPS + growthBps) / BPS.
117
+ * Simple integer round-half-up.
118
+ */
119
+ function growOnce(value: bigint, growthBps: number): bigint {
120
+ const g = BigInt(growthBps);
121
+ return (value * (BPS + g) + BPS / BigInt(2)) / BPS;
122
+ }
123
+
124
+
125
+ /**
126
+ * Apply one backward step: value * BPS / (BPS + growthBps).
127
+ * Also integer round-half-up.
128
+ */
129
+ function shrinkOnce(value: bigint, growthBps: number): bigint {
130
+ const g = BigInt(growthBps);
131
+ return (value * BPS + (BPS + g) / BigInt(2)) / (BPS + g);
132
+ }
133
+
134
+
135
+ /**
136
+ * Calculate the full supply for a given tranche using BigInt math.
137
+ * trancheNumber is 1-based (tranche 1 = startSupply)
138
+ */
139
+ function getTrancheSize(startSupply: bigint, supplyGrowthBps: number, trancheNumber: number): bigint {
140
+ let supply = startSupply;
141
+ for (let i = 1; i < trancheNumber; i++) {
142
+ supply = (supply * (BPS + BigInt(supplyGrowthBps)) + BPS / BigInt(2)) / BPS;
143
+ }
144
+ return supply;
145
+ }
146
+
147
+
148
+ /**
149
+ * Build a local tranche ladder around the current tranche
150
+ * using only on-chain config + current state.
151
+ *
152
+ */
153
+ export function buildEthereumTrancheLadder(options: {
154
+ currentTranche: number;
155
+ totalTrancheSupply: bigint,
156
+ initialTrancheSupply: bigint;
157
+ currentTrancheSupply: bigint;
158
+ currentPriceUsd: bigint;
159
+ supplyGrowthBps: number;
160
+ priceGrowthBps: number;
161
+ windowBefore?: number;
162
+ windowAfter?: number;
163
+ }): TrancheLadderItem[] {
164
+ const {
165
+ currentTranche,
166
+ initialTrancheSupply,
167
+ currentTrancheSupply,
168
+ currentPriceUsd,
169
+ supplyGrowthBps,
170
+ priceGrowthBps,
171
+ windowBefore = 5,
172
+ windowAfter = 5,
173
+ } = options;
174
+
175
+ const startId = Math.max(1, currentTranche - windowBefore);
176
+ const endId = currentTranche + windowAfter;
177
+
178
+ console.error('loading eth tranche ladder - ', currentTranche, currentTrancheSupply);
179
+ console.log('startId', startId)
180
+
181
+
182
+ //calculate total tranche size (e.g. 60,600 on tranche 2)
183
+ const currentTrancheSize = getTrancheSize(initialTrancheSupply, supplyGrowthBps, currentTranche);
184
+
185
+ const capacity = new Map<number, bigint>();
186
+ const price = new Map<number, bigint>();
187
+
188
+ // Seed current
189
+ capacity.set(currentTranche, currentTrancheSize);
190
+ price.set(currentTranche, currentPriceUsd);
191
+
192
+ // Forward (future tranches)
193
+ for (let id = currentTranche + 1; id <= endId; id++) {
194
+ const prevCap = capacity.get(id - 1)!;
195
+ const prevPrice = price.get(id - 1)!;
196
+ capacity.set(id, growOnce(prevCap, supplyGrowthBps));
197
+ price.set(id, growOnce(prevPrice, priceGrowthBps));
198
+ }
199
+
200
+ // Backward (past tranches)
201
+ for (let id = currentTranche - 1; id >= startId; id--) {
202
+ const nextCap = capacity.get(id + 1)!;
203
+ const nextPrice = price.get(id + 1)!;
204
+ capacity.set(id, shrinkOnce(nextCap, supplyGrowthBps));
205
+ price.set(id, shrinkOnce(nextPrice, priceGrowthBps));
206
+ }
207
+
208
+ const ladder: TrancheLadderItem[] = [];
209
+ for (let id = startId; id <= endId; id++) {
210
+ const cap = capacity.get(id)!;
211
+ let sold: bigint;
212
+ if (id < currentTranche) {
213
+ sold = cap;
214
+ } else if (id === currentTranche) {
215
+ sold = cap - currentTrancheSupply;
216
+ } else {
217
+ sold = BigInt(0);
218
+ }
219
+
220
+ ladder.push({
221
+ id,
222
+ capacity: cap,
223
+ sold,
224
+ remaining: cap - sold,
225
+ priceUsd: price.get(id)!,
226
+ });
227
+ }
228
+
229
+ return ladder;
230
+ }
231
+
232
+
233
+ /**
234
+ * Turn raw liqsol_core accounts into a chain-agnostic TrancheSnapshot for SOL.
235
+ * All math stays here; TokenClient just wires accounts + connection.
236
+ */
237
+ export async function buildEthereumTrancheSnapshot(options: {
238
+ chainID: ChainID;
239
+ totalSharesBn;
240
+ indexBn;
241
+ trancheNumberBn;
242
+ currentTrancheSupply;
243
+ tranchePriceWadBn;
244
+ totalTrancheSupply;
245
+ initialTrancheSupply;
246
+ supplyGrowthBps;
247
+ priceGrowthBps;
248
+ minPriceUsd;
249
+ maxPriceUsd;
250
+
251
+ ethPriceUsd?: bigint;
252
+ nativePriceTimestamp?: number;
253
+ ladderWindowBefore?: number;
254
+ ladderWindowAfter?: number;
255
+ }): Promise<TrancheSnapshot> {
256
+ const {
257
+ chainID,
258
+ ethPriceUsd,
259
+ nativePriceTimestamp,
260
+ ladderWindowBefore,
261
+ ladderWindowAfter,
262
+
263
+ totalSharesBn,
264
+ indexBn,
265
+ trancheNumberBn,
266
+ currentTrancheSupply,
267
+ tranchePriceWadBn,
268
+ totalTrancheSupply,
269
+ initialTrancheSupply,
270
+ supplyGrowthBps,
271
+ priceGrowthBps,
272
+ minPriceUsd,
273
+ maxPriceUsd,
274
+ } = options;
275
+
276
+
277
+ // convert default BigNumber to bigint for hub to handle, and partially convert from 1e18 to 1e8 for the hub
278
+ const totalShares = BigInt(totalSharesBn.toString()) / BigInt(1e10);
279
+ const currentIndex = BigInt(indexBn.toString()); // RAY (1e27)
280
+ const currentTranche = Number(trancheNumberBn.toString());
281
+ const currentPriceUsd = BigInt(tranchePriceWadBn.toString()) / BigInt(1e10); // 1e18 WAD
282
+
283
+
284
+ const ladder = buildEthereumTrancheLadder({
285
+ currentTranche,
286
+ totalTrancheSupply,
287
+ initialTrancheSupply,
288
+ currentTrancheSupply,
289
+ currentPriceUsd,
290
+ supplyGrowthBps,
291
+ priceGrowthBps,
292
+ windowBefore: ladderWindowBefore,
293
+ windowAfter: ladderWindowAfter,
294
+ });
295
+
296
+
297
+ return {
298
+ chainID,
299
+ currentIndex,
300
+ totalShares,
301
+ currentTranche,
302
+ currentPriceUsd,
303
+ minPriceUsd,
304
+ maxPriceUsd,
305
+ supplyGrowthBps,
306
+ priceGrowthBps,
307
+ currentTrancheSupply,
308
+ initialTrancheSupply,
309
+ totalWarrantsSold: totalTrancheSupply,
310
+ nativePriceUsd: ethPriceUsd,
311
+ nativePriceTimestamp,
312
+ ladder,
313
+ };
314
+ }
@@ -1,54 +1,54 @@
1
1
  import { AnchorProvider, BN, Program } from '@coral-xyz/anchor';
2
- import type { TransactionInstruction } from '@solana/web3.js';
2
+ import type { TransactionInstruction, Connection } from '@solana/web3.js';
3
3
  import { PublicKey, SystemProgram } from '@solana/web3.js';
4
4
  import {
5
5
  TOKEN_2022_PROGRAM_ID,
6
6
  ASSOCIATED_TOKEN_PROGRAM_ID,
7
7
  } from '@solana/spl-token';
8
8
 
9
- import { buildOutpostAccounts, type OutpostAccounts } from '../utils';
9
+ import { buildOutpostAccounts, type OutpostAccounts, buildSolanaTrancheSnapshot } from '../utils';
10
10
  import { SolanaProgramService } from '../program';
11
11
  import { LiqsolCore } from '../../../assets/solana/types/liqsol_core';
12
- import { GlobalState, PriceHistory, TrancheState, UserWarrantRecord, WalletLike, WireReceipt } from '../types';
12
+ import {
13
+ GlobalState,
14
+ PriceHistory,
15
+ TrancheState,
16
+ UserWarrantRecord,
17
+ WalletLike,
18
+ WireReceipt,
19
+ } from '../types';
13
20
  import { derivePriceHistoryPda } from '../constants';
21
+ import type { } from '../types';
22
+ import { ChainID, SolChainID } from '@wireio/core';
23
+ import { PurchaseAsset, PurchaseQuote, TrancheSnapshot } from '../../../types';
14
24
 
15
- /**
16
- * Client for interacting with the Pretoken (Outpost) program on Solana.
17
- *
18
- * Provides account fetching and instruction building for pretoken operations.
19
- * Does NOT send or confirm transactions; keeps SDK composable.
20
- *
21
- * TODO: Update to $WIRE Token implementation Post-Launch
22
- */
23
25
  export class TokenClient {
24
26
  private readonly program: Program<LiqsolCore>;
25
27
 
26
- get wallet(): WalletLike { return this.provider.wallet; }
28
+ get wallet(): WalletLike {
29
+ return this.provider.wallet;
30
+ }
27
31
 
28
32
  constructor(private readonly provider: AnchorProvider) {
29
33
  const svc = new SolanaProgramService(provider);
30
34
  this.program = svc.getProgram('liqsolCore');
31
35
  }
32
36
 
33
- /**
34
- * Single source of truth for outpost/pretoken accounts.
35
- * Uses your existing PDA + ATA derivations in buildOutpostAccounts().
36
- */
37
+ // ---------------------------------------------------------------------------
38
+ // Account helpers
39
+ // ---------------------------------------------------------------------------
40
+
37
41
  async getAccounts(user: PublicKey): Promise<OutpostAccounts> {
38
42
  return buildOutpostAccounts(this.provider.connection, user);
39
43
  }
40
44
 
41
- /**
42
- * Lightweight, UI-friendly snapshot fetchers.
43
- * (No decoding assumptions beyond what Anchor already provides.)
44
- */
45
45
  async fetchGlobalState(): Promise<GlobalState> {
46
- const { globalState } = await this.getAccounts(this.provider.wallet.publicKey);
46
+ const { globalState } = await this.getAccounts(this.wallet.publicKey);
47
47
  return this.program.account.globalState.fetch(globalState);
48
48
  }
49
49
 
50
50
  async fetchTrancheState(): Promise<TrancheState> {
51
- const { trancheState } = await this.getAccounts(this.provider.wallet.publicKey);
51
+ const { trancheState } = await this.getAccounts(this.wallet.publicKey);
52
52
  return this.program.account.trancheState.fetch(trancheState);
53
53
  }
54
54
 
@@ -63,28 +63,26 @@ export class TokenClient {
63
63
  }
64
64
 
65
65
  // ---------------------------------------------------------------------------
66
- // Instruction builders (no send, no confirmation: SDK stays composable)
66
+ // Tranche snapshot / quoting
67
67
  // ---------------------------------------------------------------------------
68
68
 
69
- /**
70
- * purchase_with_sol(amount u64)
71
- *
72
- * amountLamports is bigint to match your SDK convention.
73
- */
74
- async buildPurchaseWithSolIx(amountLamports: bigint, user = this.wallet.publicKey): Promise<TransactionInstruction> {
69
+ // ---------------------------------------------------------------------------
70
+ // Instruction builders (no send / confirm)
71
+ // ---------------------------------------------------------------------------
72
+
73
+ async buildPurchaseWithSolIx(
74
+ amountLamports: bigint,
75
+ user = this.wallet.publicKey,
76
+ ): Promise<TransactionInstruction> {
75
77
  const a = await this.getAccounts(user);
76
78
 
77
79
  return this.program.methods
78
80
  .purchaseWithSol(new BN(amountLamports.toString()))
79
81
  .accounts({
80
- // signer
81
82
  user: a.user,
82
-
83
- // core state
84
83
  liqsolMint: a.liqsolMint,
85
84
  globalState: a.globalState,
86
85
 
87
- // liqSOL pool + distribution plumbing
88
86
  poolAuthority: a.poolAuthority,
89
87
  liqsolPoolAta: a.liqsolPoolAta,
90
88
  liqsolPoolUserRecord: a.poolUserRecord,
@@ -94,67 +92,53 @@ export class TokenClient {
94
92
  bucketTokenAccount: a.bucketTokenAccount,
95
93
  solBucket: a.solBucket,
96
94
 
97
- // IMPORTANT: IDL name (not wireReceipt)
95
+ // IDL name is warrantDepositRecord (wireReceipt PDA)
98
96
  warrantDepositRecord: a.wireReceipt,
99
97
 
100
- // pretoken state
101
98
  trancheState: a.trancheState,
102
99
  userWarrantRecord: a.userWarrantRecord,
103
100
 
104
- // Chainlink (IDL names are chainlinkFeed / chainlinkProgram in old utils)
105
101
  chainlinkFeed: a.chainLinkFeed,
106
102
  chainlinkProgram: a.chainLinkProgram,
107
103
 
108
- // programs
109
104
  tokenProgram: TOKEN_2022_PROGRAM_ID,
110
105
  systemProgram: SystemProgram.programId,
111
106
  })
112
107
  .instruction();
113
108
  }
114
109
 
115
- /**
116
- * purchase_with_liqsol(amount u64)
117
- *
118
- * amount is liqSOL *raw base units* (Token-2022 amount).
119
- */
120
- async buildPurchaseWithLiqsolIx(amountLamports: bigint, user = this.wallet.publicKey): Promise<TransactionInstruction> {
110
+ async buildPurchaseWithLiqsolIx(
111
+ amountLamports: bigint,
112
+ user = this.wallet.publicKey,
113
+ ): Promise<TransactionInstruction> {
121
114
  const a = await this.getAccounts(user);
122
115
 
123
116
  return this.program.methods
124
117
  .purchaseWithLiqsol(new BN(amountLamports.toString()))
125
118
  .accounts({
126
- // signer
127
119
  user: a.user,
128
-
129
- // core state
130
120
  liqsolMint: a.liqsolMint,
131
121
  globalState: a.globalState,
132
122
 
133
- // token movement
134
- buyerAta: a.userAta, // Token-2022 ATA for user
123
+ buyerAta: a.userAta,
135
124
  poolAuthority: a.poolAuthority,
136
125
  liqsolPoolAta: a.liqsolPoolAta,
137
126
 
138
- // IMPORTANT: IDL name (not wireReceipt)
139
127
  warrantDepositRecord: a.wireReceipt,
140
128
 
141
- // distribution plumbing (per old utils)
142
- liqsolPoolUserRecord: a.poolUserRecord, // pool user_record PDA
129
+ liqsolPoolUserRecord: a.poolUserRecord,
143
130
  distributionState: a.distributionState,
144
131
  payRateHistory: a.payRateHistory,
145
132
  bucketAuthority: a.bucketAuthority,
146
133
  bucketTokenAccount: a.bucketTokenAccount,
147
134
  solBucket: a.solBucket,
148
135
 
149
- // pretoken state
150
136
  trancheState: a.trancheState,
151
137
  userWarrantRecord: a.userWarrantRecord,
152
138
 
153
- // Chainlink
154
139
  chainlinkFeed: a.chainLinkFeed,
155
140
  chainlinkProgram: a.chainLinkProgram,
156
141
 
157
- // programs
158
142
  tokenProgram: TOKEN_2022_PROGRAM_ID,
159
143
  associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
160
144
  systemProgram: SystemProgram.programId,
@@ -162,21 +146,15 @@ export class TokenClient {
162
146
  .instruction();
163
147
  }
164
148
 
165
- /**
166
- * purchase_warrants_from_yield()
167
- *
168
- * No amount arg; it consumes tracked yield according to on-chain rules.
169
- */
170
- async buildPurchaseFromYieldIx(user = this.wallet.publicKey): Promise<TransactionInstruction> {
149
+ async buildPurchaseFromYieldIx(
150
+ user = this.wallet.publicKey,
151
+ ): Promise<TransactionInstruction> {
171
152
  const a = await this.getAccounts(user);
172
153
 
173
154
  return this.program.methods
174
155
  .purchaseWarrantsFromYield()
175
156
  .accounts({
176
- // signer
177
157
  user: a.user,
178
-
179
- // core state
180
158
  globalState: a.globalState,
181
159
  liqsolMint: a.liqsolMint,
182
160
  poolAuthority: a.poolAuthority,
@@ -184,68 +162,64 @@ export class TokenClient {
184
162
  solBucket: a.solBucket,
185
163
 
186
164
  liqsolPoolUserRecord: a.poolUserRecord,
187
-
188
165
  distributionState: a.distributionState,
189
166
  payRateHistory: a.payRateHistory,
190
167
  bucketAuthority: a.bucketAuthority,
191
168
  bucketTokenAccount: a.bucketTokenAccount,
192
169
 
193
- // programs
194
- tokenProgram: TOKEN_2022_PROGRAM_ID,
195
- systemProgram: SystemProgram.programId,
196
-
197
- // pretoken state + chainlink (per old utils)
198
170
  trancheState: a.trancheState,
199
171
  userWarrantRecord: a.userWarrantRecord,
200
172
  chainlinkFeed: a.chainLinkFeed,
201
173
  chainlinkProgram: a.chainLinkProgram,
174
+
175
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
176
+ systemProgram: SystemProgram.programId,
202
177
  })
203
178
  .instruction();
204
179
  }
205
180
 
181
+ // ---------------------------------------------------------------------------
182
+ // Price helpers (SOL/USD via priceHistory account)
183
+ // ---------------------------------------------------------------------------
206
184
 
207
- // HELPERS
185
+ async getSolPriceUsdSafe(): Promise<{ price?: bigint; timestamp?: number }> {
186
+ try {
187
+ const price = await this.getSolPriceUsd();
188
+ // current priceHistory account has no timestamp; keep optional
189
+ return { price, timestamp: undefined };
190
+ } catch {
191
+ return { price: undefined, timestamp: undefined };
192
+ }
193
+ }
208
194
 
209
195
  /**
210
- * Fetch the SOL price in 1e8 USD units from liqsol_core.priceHistory.
211
- *
212
- * This uses the same ring-buffer semantics as the on-chain program:
213
- * the latest price is the entry just before `nextIndex`.
196
+ * Fetch latest SOL/USD price (1e8 scale) from liqsol_core.priceHistory.
197
+ * Uses the ring-buffer semantics from earlier SDK code.
214
198
  */
215
- async getSolPriceUsd(): Promise<BN> {
199
+ async getSolPriceUsd(): Promise<bigint> {
216
200
  const priceHistoryPda = derivePriceHistoryPda();
201
+ const history = (await this.program.account.priceHistory.fetch(
202
+ priceHistoryPda,
203
+ )) as PriceHistory;
217
204
 
218
- const history : PriceHistory = await this.program.account.priceHistory.fetch(
219
- priceHistoryPda
220
- );
221
-
222
- console.log('PRICE HISTORY', history);
205
+ const { prices, nextIndex, count, windowSize } = history;
223
206
 
224
- const { windowSize, prices, nextIndex, count } = history;
225
-
226
- if (!prices || prices.length === 0 || count === 0) {
227
- throw new Error("Price history is empty – no SOL price available");
207
+ if (!prices || prices.length === 0 || !count) {
208
+ throw new Error('Price history is empty – no SOL price available');
228
209
  }
229
210
 
230
- // Use the actual buffer length as capacity (should match windowSize)
231
211
  const capacity = prices.length || windowSize;
232
- if (capacity === 0) {
233
- throw new Error("Price history capacity is zero – check account layout");
212
+ if (!capacity) {
213
+ throw new Error('Price history capacity is zero – check account layout');
234
214
  }
235
215
 
236
- // Last written slot in the ring buffer
237
- const lastIndex =
238
- nextIndex === 0
239
- ? capacity - 1
240
- : nextIndex - 1;
241
-
242
- const priceUsd = prices[lastIndex];
216
+ const lastIndex = nextIndex === 0 ? capacity - 1 : nextIndex - 1;
217
+ const latest = prices[lastIndex];
243
218
 
244
- if (!BN.isBN(priceUsd)) {
245
- throw new Error("Latest price entry is not a BN – check IDL/decoder");
219
+ if (!BN.isBN(latest)) {
220
+ throw new Error('Latest price entry is not a BN – check IDL/decoder');
246
221
  }
247
222
 
248
- // priceUsd is already 1e8-scaled USD (same scale used in warrant pricing)
249
- return priceUsd;
223
+ return BigInt(latest.toString());
250
224
  }
251
225
  }