@wireio/stake 0.2.2 → 0.2.4
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 +488 -92
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +267 -84
- package/lib/stake.js +596 -145
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +488 -92
- package/lib/stake.m.js.map +1 -1
- package/package.json +1 -1
- package/src/assets/solana/idl/liqsol_core.json +3 -53
- package/src/assets/solana/idl/liqsol_token.json +1 -1
- package/src/assets/solana/idl/validator_leaderboard.json +1 -1
- package/src/assets/solana/types/liqsol_core.ts +3 -53
- package/src/assets/solana/types/liqsol_token.ts +1 -1
- package/src/assets/solana/types/validator_leaderboard.ts +1 -1
- package/src/index.ts +4 -3
- package/src/networks/ethereum/ethereum.ts +25 -1
- package/src/networks/ethereum/types.ts +1 -1
- package/src/networks/solana/clients/token.client.ts +225 -0
- package/src/networks/solana/constants.ts +9 -0
- package/src/networks/solana/solana.ts +372 -74
- package/src/networks/solana/types.ts +11 -1
- package/src/networks/solana/utils.ts +223 -5
- package/src/{staker/staker.ts → staker.ts} +2 -2
- package/src/types.ts +143 -0
- package/src/staker/types.ts +0 -51
|
@@ -2,30 +2,65 @@ import {
|
|
|
2
2
|
Commitment,
|
|
3
3
|
Connection,
|
|
4
4
|
ConnectionConfig,
|
|
5
|
+
LAMPORTS_PER_SOL,
|
|
5
6
|
PublicKey as SolPubKey,
|
|
6
7
|
Transaction,
|
|
8
|
+
TransactionInstruction,
|
|
7
9
|
TransactionSignature,
|
|
8
10
|
} from '@solana/web3.js';
|
|
9
|
-
import { AnchorProvider
|
|
11
|
+
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
10
12
|
import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
|
|
11
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
15
|
+
getAssociatedTokenAddressSync,
|
|
16
|
+
TOKEN_2022_PROGRAM_ID,
|
|
17
|
+
} from '@solana/spl-token';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
ChainID,
|
|
21
|
+
ExternalNetwork,
|
|
22
|
+
KeyType,
|
|
23
|
+
PublicKey,
|
|
24
|
+
SolChainID,
|
|
25
|
+
} from '@wireio/core';
|
|
12
26
|
|
|
13
|
-
import {
|
|
14
|
-
|
|
27
|
+
import {
|
|
28
|
+
IStakingClient,
|
|
29
|
+
Portfolio,
|
|
30
|
+
PurchaseAsset,
|
|
31
|
+
PurchaseQuote,
|
|
32
|
+
StakerConfig,
|
|
33
|
+
TrancheSnapshot,
|
|
34
|
+
} from '../../types';
|
|
15
35
|
|
|
16
36
|
import { DepositClient } from './clients/deposit.client';
|
|
17
37
|
import { DistributionClient } from './clients/distribution.client';
|
|
38
|
+
import { LeaderboardClient } from './clients/leaderboard.client';
|
|
39
|
+
import { OutpostClient } from './clients/outpost.client';
|
|
40
|
+
import { TokenClient } from './clients/token.client';
|
|
41
|
+
|
|
18
42
|
import {
|
|
19
43
|
deriveLiqsolMintPda,
|
|
20
44
|
deriveReservePoolPda,
|
|
21
45
|
deriveVaultPda,
|
|
22
46
|
} from './constants';
|
|
47
|
+
|
|
48
|
+
import { buildSolanaTrancheSnapshot } from './utils';
|
|
23
49
|
import { SolanaTransaction } from './types';
|
|
24
|
-
import { LeaderboardClient } from './clients/leaderboard.client';
|
|
25
|
-
import { OutpostClient } from './clients/outpost.client';
|
|
26
50
|
|
|
27
51
|
const commitment: Commitment = 'confirmed';
|
|
28
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Solana implementation of IStakingClient.
|
|
55
|
+
*
|
|
56
|
+
* Responsibilities:
|
|
57
|
+
* - Wire together liqSOL deposit/withdraw
|
|
58
|
+
* - Outpost stake/unstake
|
|
59
|
+
* - Prelaunch WIRE (pretokens) buy flows
|
|
60
|
+
* - Unified portfolio + tranche snapshot + buy quotes
|
|
61
|
+
*
|
|
62
|
+
* This class composes lower-level clients; it does not know about UI.
|
|
63
|
+
*/
|
|
29
64
|
export class SolanaStakingClient implements IStakingClient {
|
|
30
65
|
public pubKey: PublicKey;
|
|
31
66
|
public connection: Connection;
|
|
@@ -35,9 +70,15 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
35
70
|
private distributionClient: DistributionClient;
|
|
36
71
|
private leaderboardClient: LeaderboardClient;
|
|
37
72
|
private outpostClient: OutpostClient;
|
|
73
|
+
private tokenClient: TokenClient;
|
|
74
|
+
|
|
75
|
+
get solPubKey(): SolPubKey {
|
|
76
|
+
return new SolPubKey(this.pubKey.data.array);
|
|
77
|
+
}
|
|
38
78
|
|
|
39
|
-
get
|
|
40
|
-
|
|
79
|
+
get network(): ExternalNetwork {
|
|
80
|
+
return this.config.network;
|
|
81
|
+
}
|
|
41
82
|
|
|
42
83
|
constructor(private config: StakerConfig) {
|
|
43
84
|
const adapter = config.provider as BaseSignerWalletAdapter;
|
|
@@ -51,7 +92,10 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
51
92
|
}
|
|
52
93
|
|
|
53
94
|
const opts: ConnectionConfig = { commitment };
|
|
54
|
-
if (
|
|
95
|
+
if (
|
|
96
|
+
config.network.rpcUrls.length > 1 &&
|
|
97
|
+
config.network.rpcUrls[1].startsWith('ws')
|
|
98
|
+
) {
|
|
55
99
|
opts.wsEndpoint = config.network.rpcUrls[1];
|
|
56
100
|
}
|
|
57
101
|
|
|
@@ -60,7 +104,9 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
60
104
|
async signTransaction<T extends SolanaTransaction>(tx: T): Promise<T> {
|
|
61
105
|
return adapter.signTransaction(tx);
|
|
62
106
|
},
|
|
63
|
-
async signAllTransactions<T extends SolanaTransaction>(
|
|
107
|
+
async signAllTransactions<T extends SolanaTransaction>(
|
|
108
|
+
txs: T[],
|
|
109
|
+
): Promise<T[]> {
|
|
64
110
|
return Promise.all(txs.map((tx) => adapter.signTransaction(tx)));
|
|
65
111
|
},
|
|
66
112
|
};
|
|
@@ -73,75 +119,155 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
73
119
|
this.distributionClient = new DistributionClient(this.anchor);
|
|
74
120
|
this.leaderboardClient = new LeaderboardClient(this.anchor);
|
|
75
121
|
this.outpostClient = new OutpostClient(this.anchor);
|
|
122
|
+
this.tokenClient = new TokenClient(this.anchor);
|
|
123
|
+
this.tokenClient = new TokenClient(this.anchor);
|
|
76
124
|
}
|
|
77
125
|
|
|
78
126
|
// ---------------------------------------------------------------------
|
|
79
|
-
//
|
|
127
|
+
// IStakingClient core methods
|
|
80
128
|
// ---------------------------------------------------------------------
|
|
81
129
|
|
|
82
130
|
/**
|
|
83
|
-
* Deposit SOL into liqSOL
|
|
84
|
-
*
|
|
85
|
-
* @return Transaction signature
|
|
131
|
+
* Deposit native SOL into liqSOL (liqsol_core::deposit).
|
|
132
|
+
* Handles tx build, sign, send, and confirmation.
|
|
86
133
|
*/
|
|
87
134
|
async deposit(amountLamports: bigint): Promise<string> {
|
|
88
|
-
if (amountLamports <= BigInt(0))
|
|
89
|
-
|
|
135
|
+
if (amountLamports <= BigInt(0)) {
|
|
136
|
+
throw new Error('Deposit amount must be greater than zero.');
|
|
137
|
+
}
|
|
138
|
+
|
|
90
139
|
const tx = await this.depositClient.buildDepositTx(amountLamports);
|
|
91
|
-
const { tx: prepared, blockhash, lastValidBlockHeight } = await this.prepareTx(
|
|
140
|
+
const { tx: prepared, blockhash, lastValidBlockHeight } = await this.prepareTx(
|
|
141
|
+
tx,
|
|
142
|
+
);
|
|
92
143
|
const signed = await this.signTransaction(prepared);
|
|
93
|
-
|
|
94
|
-
|
|
144
|
+
return await this.sendAndConfirmHttp(signed, {
|
|
145
|
+
blockhash,
|
|
146
|
+
lastValidBlockHeight,
|
|
147
|
+
});
|
|
95
148
|
}
|
|
96
149
|
|
|
97
150
|
/**
|
|
98
151
|
* Withdraw SOL from liqSOL protocol.
|
|
99
|
-
*
|
|
152
|
+
* NOTE: placeholder until a withdraw flow is implemented in DepositClient.
|
|
100
153
|
*/
|
|
101
|
-
async withdraw(
|
|
154
|
+
async withdraw(_amountLamports: bigint): Promise<string> {
|
|
102
155
|
throw new Error('Withdraw method not yet implemented.');
|
|
103
156
|
}
|
|
104
157
|
|
|
105
158
|
/**
|
|
106
159
|
* Stake liqSOL into Outpost (liqSOL -> pool).
|
|
107
|
-
*
|
|
108
|
-
* @param amountLamports Amount of liqSOL to stake (smallest unit)
|
|
160
|
+
* Ensures user ATA exists, then stakes via outpostClient.
|
|
109
161
|
*/
|
|
110
162
|
async stake(amountLamports: bigint): Promise<string> {
|
|
111
|
-
if (amountLamports <= BigInt(0))
|
|
112
|
-
|
|
113
|
-
|
|
163
|
+
if (amountLamports <= BigInt(0)) {
|
|
164
|
+
throw new Error('Stake amount must be greater than zero.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(
|
|
168
|
+
this.solPubKey,
|
|
169
|
+
);
|
|
114
170
|
const stakeIx = await this.outpostClient.buildStakeLiqsolIx(amountLamports);
|
|
115
171
|
const tx = new Transaction().add(...preIxs, stakeIx);
|
|
172
|
+
|
|
116
173
|
const prepared = await this.prepareTx(tx);
|
|
117
174
|
const signed = await this.signTransaction(prepared.tx);
|
|
118
|
-
|
|
119
|
-
return result.signature;
|
|
175
|
+
return await this.sendAndConfirmHttp(signed, prepared);
|
|
120
176
|
}
|
|
121
177
|
|
|
122
178
|
/**
|
|
123
179
|
* Unstake liqSOL from Outpost (pool -> liqSOL).
|
|
124
|
-
*
|
|
125
|
-
* @param amountLamports Amount of liqSOL principal to unstake (smallest unit)
|
|
180
|
+
* Mirrors stake() but calls withdrawStake.
|
|
126
181
|
*/
|
|
127
182
|
async unstake(amountLamports: bigint): Promise<string> {
|
|
128
|
-
if (amountLamports <= BigInt(0))
|
|
183
|
+
if (amountLamports <= BigInt(0)) {
|
|
184
|
+
throw new Error('Unstake amount must be greater than zero.');
|
|
185
|
+
}
|
|
186
|
+
|
|
129
187
|
const user = this.solPubKey;
|
|
130
|
-
// const amount = new BN(amountLamports.toString());
|
|
131
188
|
const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
|
|
132
|
-
const withdrawIx =
|
|
189
|
+
const withdrawIx =
|
|
190
|
+
await this.outpostClient.buildWithdrawStakeIx(amountLamports);
|
|
133
191
|
const tx = new Transaction().add(...preIxs, withdrawIx);
|
|
192
|
+
|
|
193
|
+
const prepared = await this.prepareTx(tx);
|
|
194
|
+
const signed = await this.signTransaction(prepared.tx);
|
|
195
|
+
return await this.sendAndConfirmHttp(signed, prepared);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Buy prelaunch WIRE “pretokens” using a supported asset.
|
|
200
|
+
*
|
|
201
|
+
* - SOL: uses purchase_with_sol
|
|
202
|
+
* - LIQSOL: uses purchase_with_liqsol
|
|
203
|
+
* - YIELD: uses purchase_warrants_from_yield
|
|
204
|
+
*
|
|
205
|
+
* ETH / LIQETH are not valid on Solana.
|
|
206
|
+
*/
|
|
207
|
+
async buy(amountLamports: bigint, purchaseAsset: PurchaseAsset): Promise<string> {
|
|
208
|
+
const user = this.solPubKey;
|
|
209
|
+
let ix: TransactionInstruction;
|
|
210
|
+
let preIxs: TransactionInstruction[] = [];
|
|
211
|
+
|
|
212
|
+
switch (purchaseAsset) {
|
|
213
|
+
case PurchaseAsset.SOL: {
|
|
214
|
+
if (!amountLamports || amountLamports <= BigInt(0)) {
|
|
215
|
+
throw new Error('SOL pretoken purchase requires a positive amount.');
|
|
216
|
+
}
|
|
217
|
+
ix = await this.tokenClient.buildPurchaseWithSolIx(
|
|
218
|
+
amountLamports,
|
|
219
|
+
user,
|
|
220
|
+
);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
case PurchaseAsset.LIQSOL: {
|
|
225
|
+
if (!amountLamports || amountLamports <= BigInt(0)) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
'liqSOL pretoken purchase requires a positive amount.',
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
|
|
231
|
+
ix = await this.tokenClient.buildPurchaseWithLiqsolIx(
|
|
232
|
+
amountLamports,
|
|
233
|
+
user,
|
|
234
|
+
);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case PurchaseAsset.YIELD: {
|
|
239
|
+
// Amount is ignored by the on-chain program; it consumes tracked yield.
|
|
240
|
+
ix = await this.tokenClient.buildPurchaseFromYieldIx(user);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
case PurchaseAsset.ETH:
|
|
245
|
+
case PurchaseAsset.LIQETH: {
|
|
246
|
+
throw new Error(
|
|
247
|
+
'ETH / LIQETH pretoken purchases are not supported on Solana.',
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
default:
|
|
252
|
+
throw new Error(`Unsupported pretoken purchase asset: ${String(
|
|
253
|
+
purchaseAsset,
|
|
254
|
+
)}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const tx = new Transaction().add(...preIxs, ix);
|
|
134
258
|
const prepared = await this.prepareTx(tx);
|
|
135
259
|
const signed = await this.signTransaction(prepared.tx);
|
|
136
|
-
|
|
137
|
-
return result.signature;
|
|
260
|
+
return await this.sendAndConfirmHttp(signed, prepared);
|
|
138
261
|
}
|
|
139
262
|
|
|
140
263
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
264
|
+
* Aggregate view of the user’s balances on Solana:
|
|
265
|
+
* - native: SOL wallet balance
|
|
266
|
+
* - liq: liqSOL token balance (Token-2022 ATA)
|
|
267
|
+
* - staked: Outpost-staked liqSOL principal
|
|
268
|
+
* - tracked: distribution program trackedBalance (liqSOL)
|
|
269
|
+
* - wire: total prelaunch WIRE shares (warrants/pretokens, 1e8)
|
|
270
|
+
* - extras: useful internal addresses and raw state for debugging/UX
|
|
145
271
|
*/
|
|
146
272
|
async getPortfolio(): Promise<Portfolio> {
|
|
147
273
|
const user = this.solPubKey;
|
|
@@ -158,26 +284,36 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
158
284
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
159
285
|
);
|
|
160
286
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
287
|
+
const [
|
|
288
|
+
nativeLamports,
|
|
289
|
+
actualBalResp,
|
|
290
|
+
userRecord,
|
|
291
|
+
snapshot,
|
|
292
|
+
] = await Promise.all([
|
|
293
|
+
this.connection.getBalance(user, 'confirmed'),
|
|
294
|
+
this.connection
|
|
295
|
+
.getTokenAccountBalance(userLiqsolAta, 'confirmed')
|
|
296
|
+
.catch(() => null),
|
|
165
297
|
this.distributionClient.getUserRecord(user).catch(() => null),
|
|
166
298
|
this.outpostClient.getWireStateSnapshot(user).catch(() => null),
|
|
167
299
|
]);
|
|
168
300
|
|
|
169
301
|
const LIQSOL_DECIMALS = 9;
|
|
170
302
|
|
|
171
|
-
const actualAmountStr = actualBalResp?.value?.amount ??
|
|
172
|
-
|
|
303
|
+
const actualAmountStr = actualBalResp?.value?.amount ?? '0';
|
|
173
304
|
const trackedAmountStr =
|
|
174
|
-
userRecord?.trackedBalance
|
|
305
|
+
userRecord?.trackedBalance?.toString() ?? '0';
|
|
175
306
|
|
|
176
|
-
// Snapshot is canonical; receipt may be null if user never staked
|
|
177
307
|
const wireReceipt = snapshot?.wireReceipt ?? null;
|
|
308
|
+
const userWarrantRecord = snapshot?.userWarrantRecord ?? null;
|
|
309
|
+
const trancheState = snapshot?.trancheState ?? null;
|
|
310
|
+
const globalState = snapshot?.globalState ?? null;
|
|
178
311
|
|
|
179
312
|
const stakedAmountStr =
|
|
180
|
-
wireReceipt?.stakedLiqsol
|
|
313
|
+
wireReceipt?.stakedLiqsol?.toString() ?? '0';
|
|
314
|
+
|
|
315
|
+
const wireSharesStr =
|
|
316
|
+
userWarrantRecord?.totalWarrantsPurchased?.toString() ?? '0';
|
|
181
317
|
|
|
182
318
|
return {
|
|
183
319
|
native: {
|
|
@@ -196,6 +332,12 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
196
332
|
symbol: "LiqSOL",
|
|
197
333
|
decimals: LIQSOL_DECIMALS,
|
|
198
334
|
},
|
|
335
|
+
wire: {
|
|
336
|
+
// Prelaunch pretokens / WIRE shares (1e8)
|
|
337
|
+
amount: BigInt(wireSharesStr),
|
|
338
|
+
symbol: "$WIRE",
|
|
339
|
+
decimals: 8,
|
|
340
|
+
},
|
|
199
341
|
tracked: {
|
|
200
342
|
amount: BigInt(trackedAmountStr),
|
|
201
343
|
symbol: "LiqSOL",
|
|
@@ -206,51 +348,201 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
206
348
|
reservePoolPDA: reservePoolPDA.toBase58(),
|
|
207
349
|
vaultPDA: vaultPDA.toBase58(),
|
|
208
350
|
wireReceipt,
|
|
351
|
+
userWarrantRecord,
|
|
352
|
+
globalIndex: globalState?.currentIndex?.toString(),
|
|
353
|
+
totalShares: globalState?.totalShares?.toString(),
|
|
354
|
+
currentTrancheNumber: trancheState?.currentTrancheNumber?.toString(),
|
|
355
|
+
currentTranchePriceUsd: trancheState?.currentTranchePriceUsd?.toString(), // 1e8 USD
|
|
209
356
|
},
|
|
210
|
-
chainID: this.network.chainId
|
|
357
|
+
chainID: this.network.chainId,
|
|
211
358
|
};
|
|
212
359
|
}
|
|
213
360
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Unified, chain-agnostic tranche snapshot for Solana.
|
|
363
|
+
*
|
|
364
|
+
* Uses:
|
|
365
|
+
* - liqsol_core.globalState (currentIndex, totalShares, etc.)
|
|
366
|
+
* - liqsol_core.trancheState (price, supply, total sold, etc.)
|
|
367
|
+
* - Chainlink/PriceHistory for SOL/USD (via TokenClient.getSolPriceUsdSafe)
|
|
368
|
+
*
|
|
369
|
+
* windowBefore/windowAfter control how many ladder rows we precompute
|
|
370
|
+
* around the current tranche for UI, but you can pass nothing if you
|
|
371
|
+
* only need current tranche info.
|
|
372
|
+
*/
|
|
373
|
+
async getTrancheSnapshot(options?: {
|
|
374
|
+
chainID?: ChainID;
|
|
375
|
+
windowBefore?: number;
|
|
376
|
+
windowAfter?: number;
|
|
377
|
+
}): Promise<TrancheSnapshot> {
|
|
378
|
+
const {
|
|
379
|
+
chainID = SolChainID.WireTestnet,
|
|
380
|
+
windowBefore,
|
|
381
|
+
windowAfter,
|
|
382
|
+
} = options ?? {};
|
|
383
|
+
|
|
384
|
+
// Canonical program state
|
|
385
|
+
const [globalState, trancheState] = await Promise.all([
|
|
386
|
+
this.tokenClient.fetchGlobalState(),
|
|
387
|
+
this.tokenClient.fetchTrancheState(),
|
|
388
|
+
]);
|
|
217
389
|
|
|
218
|
-
|
|
219
|
-
|
|
390
|
+
// Latest SOL/USD price (1e8) + timestamp from PriceHistory
|
|
391
|
+
const { price: solPriceUsd, timestamp } =
|
|
392
|
+
await this.tokenClient.getSolPriceUsdSafe();
|
|
393
|
+
|
|
394
|
+
return buildSolanaTrancheSnapshot({
|
|
395
|
+
chainID,
|
|
396
|
+
globalState,
|
|
397
|
+
trancheState,
|
|
398
|
+
solPriceUsd,
|
|
399
|
+
nativePriceTimestamp: timestamp,
|
|
400
|
+
ladderWindowBefore: windowBefore,
|
|
401
|
+
ladderWindowAfter: windowAfter,
|
|
402
|
+
});
|
|
220
403
|
}
|
|
221
404
|
|
|
222
|
-
|
|
223
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Approximate prelaunch WIRE quote for a given amount & asset.
|
|
407
|
+
*
|
|
408
|
+
* Uses TrancheSnapshot + SOL/USD price for:
|
|
409
|
+
* - SOL: amount is lamports
|
|
410
|
+
* - LIQSOL: amount is liqSOL base units (decimals = 9)
|
|
411
|
+
* - YIELD: amount is treated as SOL lamports-equivalent of yield
|
|
412
|
+
*
|
|
413
|
+
* NOTE: On-chain rounding may differ slightly (this is UI-only).
|
|
414
|
+
*/
|
|
415
|
+
async getBuyQuote(
|
|
416
|
+
amount: bigint,
|
|
417
|
+
asset: PurchaseAsset,
|
|
418
|
+
opts?: { chainID?: ChainID },
|
|
419
|
+
): Promise<PurchaseQuote> {
|
|
420
|
+
// For non-YIELD purchases we require a positive amount.
|
|
421
|
+
if (asset !== PurchaseAsset.YIELD && amount <= BigInt(0)) {
|
|
422
|
+
throw new Error('amount must be > 0 for non-YIELD purchases');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const snapshot = await this.getTrancheSnapshot({
|
|
426
|
+
chainID: opts?.chainID,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const wirePriceUsd = snapshot.currentPriceUsd; // 1e8
|
|
430
|
+
const solPriceUsd = snapshot.nativePriceUsd; // 1e8
|
|
431
|
+
|
|
432
|
+
if (!wirePriceUsd || wirePriceUsd <= BigInt(0)) {
|
|
433
|
+
throw new Error('Invalid WIRE price in tranche snapshot');
|
|
434
|
+
}
|
|
435
|
+
if (!solPriceUsd || solPriceUsd <= BigInt(0)) {
|
|
436
|
+
throw new Error('No SOL/USD price available');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const ONE_E9 = BigInt(LAMPORTS_PER_SOL); // 1e9
|
|
440
|
+
const ONE_E8 = BigInt(100_000_000); // 1e8
|
|
441
|
+
|
|
442
|
+
let notionalUsd: bigint;
|
|
443
|
+
|
|
444
|
+
switch (asset) {
|
|
445
|
+
case PurchaseAsset.SOL: {
|
|
446
|
+
// lamports * solPriceUsd / 1e9 → 1e8 USD
|
|
447
|
+
notionalUsd = (amount * solPriceUsd) / ONE_E9;
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
case PurchaseAsset.LIQSOL: {
|
|
452
|
+
// liqSOL also uses 9 decimals; use same conversion.
|
|
453
|
+
notionalUsd = (amount * solPriceUsd) / ONE_E9;
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
case PurchaseAsset.YIELD: {
|
|
458
|
+
// Treat amount as lamports-equivalent of SOL yield (UI convention).
|
|
459
|
+
notionalUsd = (amount * solPriceUsd) / ONE_E9;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
case PurchaseAsset.ETH:
|
|
464
|
+
case PurchaseAsset.LIQETH:
|
|
465
|
+
throw new Error('getBuyQuote for ETH/LIQETH is not supported on Solana');
|
|
466
|
+
|
|
467
|
+
default:
|
|
468
|
+
throw new Error(`Unsupported purchase asset: ${String(asset)}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// WIRE shares (1e8) = (notionalUsd * 1e8) / wirePriceUsd
|
|
472
|
+
// Add a small bias to avoid truncating to 0 on tiny buys.
|
|
473
|
+
const numerator = notionalUsd * ONE_E8;
|
|
474
|
+
const wireShares =
|
|
475
|
+
numerator === BigInt(0)
|
|
476
|
+
? BigInt(0)
|
|
477
|
+
: (numerator + wirePriceUsd - BigInt(1)) / wirePriceUsd;
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
purchaseAsset: asset,
|
|
481
|
+
amountIn: amount,
|
|
482
|
+
wireShares,
|
|
483
|
+
wireDecimals: 8,
|
|
484
|
+
wirePriceUsd,
|
|
485
|
+
notionalUsd,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Convenience helper to fetch the distribution userRecord for the current user.
|
|
491
|
+
* Used by balance-correction flows and debugging.
|
|
492
|
+
*/
|
|
493
|
+
async getUserRecord() {
|
|
494
|
+
return this.distributionClient.getUserRecord(this.solPubKey);
|
|
224
495
|
}
|
|
225
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Run the "correct & register" flow on Solana:
|
|
499
|
+
* - builds the minimal transaction (maybe multi-user) to reconcile liqSOL
|
|
500
|
+
* - signs and sends the transaction if it can succeed
|
|
501
|
+
*/
|
|
226
502
|
async correctBalance(amount?: bigint): Promise<string> {
|
|
227
503
|
const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
|
|
228
504
|
if (!build.canSucceed || !build.transaction) {
|
|
229
505
|
throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
|
|
230
506
|
}
|
|
231
507
|
|
|
232
|
-
const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(
|
|
508
|
+
const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(
|
|
509
|
+
build.transaction,
|
|
510
|
+
);
|
|
233
511
|
const signed = await this.signTransaction(tx);
|
|
234
|
-
const
|
|
235
|
-
|
|
512
|
+
const signature = await this.sendAndConfirmHttp(signed, {
|
|
513
|
+
blockhash,
|
|
514
|
+
lastValidBlockHeight,
|
|
515
|
+
});
|
|
516
|
+
return signature;
|
|
236
517
|
}
|
|
237
518
|
|
|
238
519
|
// ---------------------------------------------------------------------
|
|
239
520
|
// Tx helpers
|
|
240
521
|
// ---------------------------------------------------------------------
|
|
241
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Send a signed transaction over HTTP RPC and wait for confirmation.
|
|
525
|
+
* Throws if the transaction fails.
|
|
526
|
+
*/
|
|
242
527
|
private async sendAndConfirmHttp(
|
|
243
528
|
signed: SolanaTransaction,
|
|
244
529
|
ctx: { blockhash: string; lastValidBlockHeight: number },
|
|
245
|
-
): Promise<
|
|
246
|
-
const signature = await this.connection.sendRawTransaction(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
530
|
+
): Promise<string> {
|
|
531
|
+
const signature = await this.connection.sendRawTransaction(
|
|
532
|
+
signed.serialize(),
|
|
533
|
+
{
|
|
534
|
+
skipPreflight: false,
|
|
535
|
+
preflightCommitment: commitment,
|
|
536
|
+
maxRetries: 3,
|
|
537
|
+
},
|
|
538
|
+
);
|
|
251
539
|
|
|
252
540
|
const conf = await this.connection.confirmTransaction(
|
|
253
|
-
{
|
|
541
|
+
{
|
|
542
|
+
signature,
|
|
543
|
+
blockhash: ctx.blockhash,
|
|
544
|
+
lastValidBlockHeight: ctx.lastValidBlockHeight,
|
|
545
|
+
},
|
|
254
546
|
commitment,
|
|
255
547
|
);
|
|
256
548
|
|
|
@@ -258,29 +550,35 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
258
550
|
throw new Error(`Transaction failed: ${JSON.stringify(conf.value.err)}`);
|
|
259
551
|
}
|
|
260
552
|
|
|
261
|
-
return
|
|
553
|
+
return signature;
|
|
262
554
|
}
|
|
263
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Sign a single Solana transaction using the connected wallet adapter.
|
|
558
|
+
*/
|
|
264
559
|
async signTransaction(tx: SolanaTransaction): Promise<SolanaTransaction> {
|
|
265
560
|
return this.anchor.wallet.signTransaction(tx);
|
|
266
561
|
}
|
|
267
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Generic "fire and forget" send helper if the caller already
|
|
565
|
+
* prepared and signed the transaction.
|
|
566
|
+
*/
|
|
268
567
|
async sendTransaction(signed: SolanaTransaction): Promise<TransactionSignature> {
|
|
269
568
|
return this.anchor.sendAndConfirm(signed);
|
|
270
569
|
}
|
|
271
570
|
|
|
571
|
+
/**
|
|
572
|
+
* Attach recent blockhash + fee payer to a transaction.
|
|
573
|
+
* Required before signing and sending.
|
|
574
|
+
*/
|
|
272
575
|
async prepareTx(
|
|
273
576
|
tx: Transaction,
|
|
274
577
|
): Promise<{ tx: Transaction; blockhash: string; lastValidBlockHeight: number }> {
|
|
275
|
-
const { blockhash, lastValidBlockHeight } =
|
|
578
|
+
const { blockhash, lastValidBlockHeight } =
|
|
579
|
+
await this.connection.getLatestBlockhash('confirmed');
|
|
276
580
|
tx.recentBlockhash = blockhash;
|
|
277
581
|
tx.feePayer = this.solPubKey;
|
|
278
582
|
return { tx, blockhash, lastValidBlockHeight };
|
|
279
583
|
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export interface TxResult {
|
|
283
|
-
signature: string;
|
|
284
|
-
slot: number;
|
|
285
|
-
confirmed: boolean;
|
|
286
584
|
}
|
|
@@ -131,4 +131,14 @@ export type WireReceipt = {
|
|
|
131
131
|
purchasedWithSol: BN;
|
|
132
132
|
purchasedSolShares: BN;
|
|
133
133
|
bump: number;
|
|
134
|
-
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
export type PriceHistory = {
|
|
138
|
+
windowSize: number;
|
|
139
|
+
prices: BN[];
|
|
140
|
+
count: number;
|
|
141
|
+
nextIndex: number;
|
|
142
|
+
bump: number;
|
|
143
|
+
}
|
|
144
|
+
|