@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.
- package/lib/stake.browser.js +1678 -414
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +49 -9
- package/lib/stake.js +1821 -497
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +1678 -414
- package/lib/stake.m.js.map +1 -1
- package/package.json +1 -1
- package/src/assets/ethereum/ABI/outpost/Aggregator.sol/Aggregator.json +82 -0
- package/src/networks/ethereum/clients/deposit.client.ts +76 -2
- package/src/networks/ethereum/clients/pretoken.client.ts +130 -0
- package/src/networks/ethereum/clients/stake.client.ts +87 -24
- package/src/networks/ethereum/contract.ts +13 -0
- package/src/networks/ethereum/ethereum.ts +234 -88
- package/src/networks/ethereum/types.ts +2 -0
- package/src/networks/ethereum/utils.ts +314 -0
- package/src/staker/types.ts +62 -0
- package/src/types.ts +14 -1
|
@@ -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
|
-
|
|
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',
|