@wireio/stake 0.2.4 → 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
+ }
@@ -0,0 +1,62 @@
1
+ import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
2
+ import { PublicKey as SolPubKey } from '@solana/web3.js';
3
+ import { ChainID, ExternalNetwork, PublicKey } from '@wireio/core';
4
+ import { ethers } from 'ethers';
5
+
6
+ export interface IStakingClient {
7
+ pubKey: PublicKey;
8
+ network: ExternalNetwork;
9
+
10
+ /** Amount is in the chain's smallest unit (lamports/wei, etc.) */
11
+ deposit(amount: bigint): Promise<string>;
12
+ withdraw(amount: bigint): Promise<string>;
13
+ stake(amount: bigint): Promise<string>;
14
+ unstake(amount: bigint): Promise<string>;
15
+
16
+ buy?(amount: bigint, purchaseAsset: PurchaseAsset): Promise<string>;
17
+
18
+ // REMOVED from shared client, SOLANA ONLY
19
+ /** Register any untracked LIQ staked tokens */
20
+ // register(): Promise<string>;
21
+
22
+ /** Fetch the portfolio for the LIQ stake user */
23
+ getPortfolio(): Promise<Portfolio>;
24
+ }
25
+
26
+ // Enum describing which asset is being used to buy pretoken
27
+ export enum PurchaseAsset {
28
+ SOL = "SOL",
29
+ LIQSOL = "LIQSOL",
30
+ ETH = "ETH",
31
+ LIQETH = "LIQETH",
32
+ YIELD = "YIELD",
33
+ }
34
+
35
+ export type StakerConfig = {
36
+ network: ExternalNetwork;
37
+ provider: BaseSignerWalletAdapter | ethers.providers.Web3Provider;
38
+ pubKey: PublicKey;
39
+ }
40
+
41
+ export interface Portfolio {
42
+ /** Native balance on chain: ETH, SOL */
43
+ native: BalanceView;
44
+ /** Actual Liquid balance of LiqETH, LiqSOL*/
45
+ liq: BalanceView;
46
+ /** Outpost Staked balance */
47
+ staked: BalanceView
48
+ /** SOL ONLY!
49
+ * Tracked liqSOL balance from distribution program */
50
+ tracked?: BalanceView;
51
+ /** Extra PDAs and account addresses */
52
+ extras?: Record<string, any>;
53
+ /** Chain ID of the network for which this portfolio is from */
54
+ chainID: ChainID;
55
+ }
56
+
57
+ export type BalanceView = {
58
+ amount: bigint; // raw on-chain integer value
59
+ decimals: number; // number of decimal places
60
+ symbol?: string; // optional token symbol identifier
61
+ ata?: SolPubKey; // associated token account address
62
+ };
package/src/types.ts CHANGED
@@ -64,6 +64,18 @@ export type BalanceView = {
64
64
  symbol: string; // optional token symbol identifier
65
65
  ata?: SolPubKey; // associated token account address
66
66
  };
67
+ export interface TrancheLadderItem {
68
+ /** On-chain tranche id, 0-based (0,1,2,...) */
69
+ id: number;
70
+ /** Total capacity for this tranche (pretokens, 1e8 scale) */
71
+ capacity: bigint;
72
+ /** Sold amount in this tranche (1e8 scale) */
73
+ sold: bigint;
74
+ /** Remaining = capacity - sold (1e8 scale) */
75
+ remaining: bigint;
76
+ /** Price for this tranche in USD (1e8 scale) */
77
+ priceUsd: bigint;
78
+ }
67
79
 
68
80
  export interface TrancheLadderItem {
69
81
  /** On-chain tranche id, 0-based (0,1,2,...) */
@@ -120,7 +132,8 @@ export interface TrancheSnapshot {
120
132
  ladder: TrancheLadderItem[];
121
133
  }
122
134
 
123
- /** Purchase asset selection used by staking client(s) */
135
+
136
+ // Enum describing which asset is being used to buy pretoken
124
137
  export enum PurchaseAsset {
125
138
  SOL = 'SOL',
126
139
  LIQSOL = 'LIQSOL',