@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.
- package/lib/stake.browser.js +1919 -481
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +224 -92
- package/lib/stake.js +2101 -597
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +1919 -481
- 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/networks/solana/clients/token.client.ts +72 -98
- package/src/networks/solana/solana.ts +284 -184
- package/src/networks/solana/utils.ts +209 -5
- package/src/staker/types.ts +62 -0
- package/src/types.ts +80 -29
|
@@ -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 {
|
|
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 {
|
|
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
|
-
|
|
35
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
66
|
+
// Tranche snapshot / quoting
|
|
67
67
|
// ---------------------------------------------------------------------------
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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<
|
|
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
|
|
219
|
-
priceHistoryPda
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
console.log('PRICE HISTORY', history);
|
|
205
|
+
const { prices, nextIndex, count, windowSize } = history;
|
|
223
206
|
|
|
224
|
-
|
|
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
|
|
233
|
-
throw new Error(
|
|
212
|
+
if (!capacity) {
|
|
213
|
+
throw new Error('Price history capacity is zero – check account layout');
|
|
234
214
|
}
|
|
235
215
|
|
|
236
|
-
|
|
237
|
-
const
|
|
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(
|
|
245
|
-
throw new Error(
|
|
219
|
+
if (!BN.isBN(latest)) {
|
|
220
|
+
throw new Error('Latest price entry is not a BN – check IDL/decoder');
|
|
246
221
|
}
|
|
247
222
|
|
|
248
|
-
|
|
249
|
-
return priceUsd;
|
|
223
|
+
return BigInt(latest.toString());
|
|
250
224
|
}
|
|
251
225
|
}
|