@wireio/stake 0.3.0 → 0.4.0
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 +3602 -1921
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +3265 -1358
- package/lib/stake.js +4369 -2728
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +3602 -1921
- package/lib/stake.m.js.map +1 -1
- package/package.json +3 -1
- package/src/assets/solana/idl/liqsol_core.json +2327 -887
- package/src/assets/solana/idl/liqsol_token.json +1 -1
- package/src/assets/solana/idl/transfer_hook.json +192 -0
- package/src/assets/solana/idl/validator_leaderboard.json +147 -4
- package/src/assets/solana/types/liqsol_core.ts +2327 -887
- package/src/assets/solana/types/liqsol_token.ts +1 -1
- package/src/assets/solana/types/transfer_hook.ts +198 -0
- package/src/assets/solana/types/validator_leaderboard.ts +147 -4
- package/src/networks/ethereum/ethereum.ts +0 -5
- package/src/networks/solana/clients/deposit.client.ts +154 -8
- package/src/networks/solana/clients/distribution.client.ts +72 -291
- package/src/networks/solana/clients/leaderboard.client.ts +59 -14
- package/src/networks/solana/clients/outpost.client.ts +188 -359
- package/src/networks/solana/clients/token.client.ts +85 -100
- package/src/networks/solana/constants.ts +155 -64
- package/src/networks/solana/solana.ts +273 -153
- package/src/networks/solana/types.ts +531 -71
- package/src/networks/solana/utils.ts +66 -49
- package/src/types.ts +108 -17
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Commitment,
|
|
3
|
+
ComputeBudgetProgram,
|
|
3
4
|
Connection,
|
|
4
5
|
ConnectionConfig,
|
|
5
|
-
LAMPORTS_PER_SOL,
|
|
6
6
|
PublicKey as SolPubKey,
|
|
7
7
|
Transaction,
|
|
8
|
-
TransactionInstruction,
|
|
9
8
|
TransactionSignature,
|
|
10
9
|
} from '@solana/web3.js';
|
|
11
10
|
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
@@ -41,6 +40,7 @@ import {
|
|
|
41
40
|
deriveLiqsolMintPda,
|
|
42
41
|
deriveReservePoolPda,
|
|
43
42
|
deriveVaultPda,
|
|
43
|
+
INDEX_SCALE,
|
|
44
44
|
} from './constants';
|
|
45
45
|
|
|
46
46
|
import { buildSolanaTrancheSnapshot } from './utils';
|
|
@@ -52,23 +52,23 @@ const commitment: Commitment = 'confirmed';
|
|
|
52
52
|
* Solana implementation of IStakingClient.
|
|
53
53
|
*
|
|
54
54
|
* Responsibilities:
|
|
55
|
-
* -
|
|
56
|
-
* -
|
|
57
|
-
* -
|
|
58
|
-
* - Unified portfolio + tranche snapshot +
|
|
55
|
+
* - liqSOL deposit (SOL -> liqSOL)
|
|
56
|
+
* - withdraw requests (liqSOL -> NFT receipt / encumbered SOL)
|
|
57
|
+
* - Pretoken (WIRE) buys via liqSOL
|
|
58
|
+
* - Unified portfolio + tranche snapshot + balance correction (later)
|
|
59
59
|
*
|
|
60
|
-
* This
|
|
60
|
+
* This composes lower-level clients; it does not know about UI.
|
|
61
61
|
*/
|
|
62
62
|
export class SolanaStakingClient implements IStakingClient {
|
|
63
|
-
public pubKey?: PublicKey;
|
|
63
|
+
public pubKey?: PublicKey; // Wire ED key (optional → read-only)
|
|
64
64
|
public connection: Connection;
|
|
65
65
|
public anchor: AnchorProvider;
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
public depositClient: DepositClient;
|
|
68
|
+
public distributionClient: DistributionClient;
|
|
69
|
+
public leaderboardClient: LeaderboardClient;
|
|
70
|
+
public outpostClient: OutpostClient;
|
|
71
|
+
public tokenClient: TokenClient;
|
|
72
72
|
|
|
73
73
|
get solPubKey(): SolPubKey {
|
|
74
74
|
if (!this.pubKey) throw new Error('pubKey is undefined');
|
|
@@ -80,20 +80,53 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
constructor(private config: StakerConfig) {
|
|
83
|
-
const adapter = config.provider as BaseSignerWalletAdapter;
|
|
84
|
-
|
|
85
|
-
if (!config.network.rpcUrls.length)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
const adapter = config.provider as BaseSignerWalletAdapter | undefined;
|
|
84
|
+
|
|
85
|
+
if (!config.network.rpcUrls.length) {
|
|
86
|
+
throw new Error('No RPC URLs provided');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -------------------------------------------------------------
|
|
90
|
+
// Resolve Solana wallet pubkey (or dummy in read-only mode)
|
|
91
|
+
// -------------------------------------------------------------
|
|
92
|
+
let solWalletPubkey: SolPubKey;
|
|
93
|
+
if (adapter?.publicKey) {
|
|
94
|
+
solWalletPubkey = adapter.publicKey;
|
|
95
|
+
} else {
|
|
96
|
+
// Zeroed pubkey when no wallet (read-only usage)
|
|
97
|
+
solWalletPubkey = new SolPubKey(new Uint8Array(32));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// -------------------------------------------------------------
|
|
101
|
+
// Resolve Wire pubKey
|
|
102
|
+
// -------------------------------------------------------------
|
|
103
|
+
if (config.pubKey) {
|
|
104
|
+
const wirePub = config.pubKey;
|
|
105
|
+
|
|
106
|
+
if (adapter?.publicKey) {
|
|
107
|
+
const derived = new PublicKey(
|
|
108
|
+
KeyType.ED,
|
|
109
|
+
adapter.publicKey.toBytes(),
|
|
110
|
+
);
|
|
111
|
+
if (!derived.equals(wirePub)) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"Passed-in pubKey doesn't match adapter.publicKey",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
94
118
|
this.pubKey = wirePub;
|
|
119
|
+
} else if (adapter?.publicKey) {
|
|
120
|
+
// Derive Wire pubKey from adapter when not explicitly passed
|
|
121
|
+
this.pubKey = new PublicKey(
|
|
122
|
+
KeyType.ED,
|
|
123
|
+
adapter.publicKey.toBytes(),
|
|
124
|
+
);
|
|
95
125
|
}
|
|
96
126
|
|
|
127
|
+
// -------------------------------------------------------------
|
|
128
|
+
// Connection + AnchorProvider
|
|
129
|
+
// -------------------------------------------------------------
|
|
97
130
|
const opts: ConnectionConfig = { commitment };
|
|
98
131
|
if (
|
|
99
132
|
config.network.rpcUrls.length > 1 &&
|
|
@@ -102,20 +135,47 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
102
135
|
opts.wsEndpoint = config.network.rpcUrls[1];
|
|
103
136
|
}
|
|
104
137
|
|
|
105
|
-
const anchorWallet =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
const anchorWallet = adapter
|
|
139
|
+
? {
|
|
140
|
+
publicKey: solWalletPubkey,
|
|
141
|
+
async signTransaction<T extends SolanaTransaction>(
|
|
142
|
+
tx: T,
|
|
143
|
+
): Promise<T> {
|
|
144
|
+
return adapter.signTransaction(tx);
|
|
145
|
+
},
|
|
146
|
+
async signAllTransactions<T extends SolanaTransaction>(
|
|
147
|
+
txs: T[],
|
|
148
|
+
): Promise<T[]> {
|
|
149
|
+
if (adapter.signAllTransactions) {
|
|
150
|
+
return adapter.signAllTransactions(txs);
|
|
151
|
+
}
|
|
152
|
+
return Promise.all(
|
|
153
|
+
txs.map((tx) => adapter.signTransaction(tx)),
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
: {
|
|
158
|
+
publicKey: solWalletPubkey,
|
|
159
|
+
async signTransaction<T extends SolanaTransaction>(
|
|
160
|
+
_tx: T,
|
|
161
|
+
): Promise<T> {
|
|
162
|
+
throw new Error(
|
|
163
|
+
'Wallet not connected: signTransaction not available',
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
async signAllTransactions<T extends SolanaTransaction>(
|
|
167
|
+
_txs: T[],
|
|
168
|
+
): Promise<T[]> {
|
|
169
|
+
throw new Error(
|
|
170
|
+
'Wallet not connected: signAllTransactions not available',
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
};
|
|
116
174
|
|
|
117
175
|
this.connection = new Connection(config.network.rpcUrls[0], opts);
|
|
118
|
-
this.anchor = new AnchorProvider(this.connection, anchorWallet, {
|
|
176
|
+
this.anchor = new AnchorProvider(this.connection, anchorWallet, {
|
|
177
|
+
commitment,
|
|
178
|
+
});
|
|
119
179
|
|
|
120
180
|
this.depositClient = new DepositClient(this.anchor);
|
|
121
181
|
this.distributionClient = new DistributionClient(this.anchor);
|
|
@@ -139,9 +199,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
139
199
|
}
|
|
140
200
|
|
|
141
201
|
const tx = await this.depositClient.buildDepositTx(amountLamports);
|
|
142
|
-
const { tx: prepared, blockhash, lastValidBlockHeight } =
|
|
143
|
-
tx
|
|
144
|
-
);
|
|
202
|
+
const { tx: prepared, blockhash, lastValidBlockHeight } =
|
|
203
|
+
await this.prepareTx(tx);
|
|
145
204
|
const signed = await this.signTransaction(prepared);
|
|
146
205
|
return await this.sendAndConfirmHttp(signed, {
|
|
147
206
|
blockhash,
|
|
@@ -150,89 +209,109 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
150
209
|
}
|
|
151
210
|
|
|
152
211
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
212
|
+
* Request a withdraw from liqSOL to SOL (liqsol_core::requestWithdraw).
|
|
213
|
+
*
|
|
214
|
+
* This:
|
|
215
|
+
* - burns liqSOL from the user ATA
|
|
216
|
+
* - mints an NFT withdrawal receipt
|
|
217
|
+
* - increases totalEncumberedFunds in global state
|
|
218
|
+
*
|
|
219
|
+
* Actual SOL payout happens later via the operator-side flow.
|
|
155
220
|
*/
|
|
156
|
-
async withdraw(
|
|
221
|
+
async withdraw(amountLamports: bigint): Promise<string> {
|
|
157
222
|
this.ensureWriteAccess();
|
|
158
|
-
|
|
223
|
+
if (amountLamports <= BigInt(0)) {
|
|
224
|
+
throw new Error('Withdraw amount must be greater than zero.');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const tx = await this.depositClient.buildWithdrawTx(amountLamports);
|
|
228
|
+
const { tx: prepared, blockhash, lastValidBlockHeight } =
|
|
229
|
+
await this.prepareTx(tx);
|
|
230
|
+
const signed = await this.signTransaction(prepared);
|
|
231
|
+
return await this.sendAndConfirmHttp(signed, {
|
|
232
|
+
blockhash,
|
|
233
|
+
lastValidBlockHeight,
|
|
234
|
+
});
|
|
159
235
|
}
|
|
160
236
|
|
|
161
237
|
/**
|
|
162
|
-
* Stake liqSOL into Outpost (liqSOL
|
|
163
|
-
* Ensures user ATA exists, then stakes via outpostClient.
|
|
238
|
+
* Stake liqSOL into Outpost (liqSOL → pool) via liqsol_core::synd.
|
|
164
239
|
*/
|
|
165
240
|
async stake(amountLamports: bigint): Promise<string> {
|
|
166
241
|
this.ensureWriteAccess();
|
|
167
|
-
|
|
242
|
+
|
|
243
|
+
if (!amountLamports || amountLamports <= BigInt(0)) {
|
|
168
244
|
throw new Error('Stake amount must be greater than zero.');
|
|
169
245
|
}
|
|
170
246
|
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const tx = new Transaction().add(...preIxs, stakeIx);
|
|
247
|
+
const user = this.solPubKey;
|
|
248
|
+
|
|
249
|
+
// Build the Outpost synd instruction
|
|
250
|
+
const ix = await this.outpostClient.buildStakeIx(amountLamports, user);
|
|
176
251
|
|
|
252
|
+
// Wrap in a transaction and send
|
|
253
|
+
const tx = new Transaction().add(ix);
|
|
177
254
|
const prepared = await this.prepareTx(tx);
|
|
178
255
|
const signed = await this.signTransaction(prepared.tx);
|
|
179
|
-
|
|
256
|
+
|
|
257
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
180
258
|
}
|
|
181
259
|
|
|
182
260
|
/**
|
|
183
|
-
* Unstake liqSOL from Outpost (pool
|
|
184
|
-
* Mirrors stake() but calls withdrawStake.
|
|
261
|
+
* Unstake liqSOL from Outpost (pool → liqSOL) via liqsol_core::desynd.
|
|
185
262
|
*/
|
|
186
263
|
async unstake(amountLamports: bigint): Promise<string> {
|
|
187
264
|
this.ensureWriteAccess();
|
|
188
|
-
|
|
265
|
+
|
|
266
|
+
if (!amountLamports || amountLamports <= BigInt(0)) {
|
|
189
267
|
throw new Error('Unstake amount must be greater than zero.');
|
|
190
268
|
}
|
|
191
269
|
|
|
192
270
|
const user = this.solPubKey;
|
|
193
|
-
const preIxs = await this.outpostClient.maybeBuildCreateUserAtaIx(user);
|
|
194
|
-
const withdrawIx =
|
|
195
|
-
await this.outpostClient.buildWithdrawStakeIx(amountLamports);
|
|
196
|
-
const tx = new Transaction().add(...preIxs, withdrawIx);
|
|
197
271
|
|
|
272
|
+
// Build the Outpost desynd instruction
|
|
273
|
+
const ix = await this.outpostClient.buildUnstakeIx(amountLamports, user);
|
|
274
|
+
|
|
275
|
+
// Wrap in a transaction and send
|
|
276
|
+
const tx = new Transaction().add(ix);
|
|
198
277
|
const prepared = await this.prepareTx(tx);
|
|
199
278
|
const signed = await this.signTransaction(prepared.tx);
|
|
200
|
-
|
|
279
|
+
|
|
280
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
201
281
|
}
|
|
202
282
|
|
|
203
283
|
/**
|
|
204
|
-
* Buy prelaunch WIRE “pretokens” using
|
|
205
|
-
*
|
|
206
|
-
* - SOL: uses purchase_with_sol
|
|
207
|
-
* - LIQSOL: uses purchase_with_liqsol
|
|
208
|
-
* - YIELD: uses purchase_warrants_from_yield
|
|
284
|
+
* Buy prelaunch WIRE “pretokens” using liqSOL.
|
|
209
285
|
*
|
|
210
|
-
*
|
|
286
|
+
* This delegates to TokenClient, which uses the `purchase`-style
|
|
287
|
+
* instruction under the new IDL (no more native-SOL purchase).
|
|
211
288
|
*/
|
|
212
289
|
async buy(amountLamports: bigint): Promise<string> {
|
|
213
290
|
this.ensureWriteAccess();
|
|
214
|
-
if (!amountLamports || amountLamports <= BigInt(0))
|
|
291
|
+
if (!amountLamports || amountLamports <= BigInt(0)) {
|
|
215
292
|
throw new Error('liqSOL pretoken purchase requires a positive amount.');
|
|
216
|
-
|
|
293
|
+
}
|
|
294
|
+
|
|
217
295
|
const user = this.solPubKey;
|
|
218
|
-
const
|
|
219
|
-
const ix = await this.tokenClient.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
296
|
+
const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
|
|
297
|
+
const ix = await this.tokenClient.buildPurchaseIx(amountLamports, user);
|
|
298
|
+
const tx = new Transaction().add(cuIx, ix);
|
|
299
|
+
const { tx: prepared, blockhash, lastValidBlockHeight } =
|
|
300
|
+
await this.prepareTx(tx);
|
|
301
|
+
const signed = await this.signTransaction(prepared);
|
|
302
|
+
return await this.sendAndConfirmHttp(signed, {
|
|
303
|
+
blockhash,
|
|
304
|
+
lastValidBlockHeight,
|
|
305
|
+
});
|
|
227
306
|
}
|
|
228
307
|
|
|
229
308
|
/**
|
|
230
309
|
* Aggregate view of the user’s balances on Solana:
|
|
231
310
|
* - native: SOL wallet balance
|
|
232
311
|
* - liq: liqSOL token balance (Token-2022 ATA)
|
|
233
|
-
* - staked:
|
|
234
|
-
* -
|
|
235
|
-
* -
|
|
312
|
+
* - staked: liqSOL staked in Outpost (synd/desynd) → outpostAccount.stakedLiqsol
|
|
313
|
+
* - wire: total WIRE pretokens purchased (1e8 scale)
|
|
314
|
+
* - yield: on-chain index/shares plus an estimated accrued liqSOL yield
|
|
236
315
|
* - extras: useful internal addresses and raw state for debugging/UX
|
|
237
316
|
*/
|
|
238
317
|
async getPortfolio(): Promise<Portfolio> {
|
|
@@ -252,75 +331,123 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
252
331
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
253
332
|
);
|
|
254
333
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
] = await Promise.all([
|
|
334
|
+
// NOTE:
|
|
335
|
+
// - nativeLamports: wallet SOL
|
|
336
|
+
// - actualBalResp: liqSOL balance in user ATA
|
|
337
|
+
// - snapshot: Outpost + pretokens + global index/shares
|
|
338
|
+
const [nativeLamports, actualBalResp, snapshot] = await Promise.all([
|
|
261
339
|
this.connection.getBalance(user, 'confirmed'),
|
|
262
340
|
this.connection
|
|
263
341
|
.getTokenAccountBalance(userLiqsolAta, 'confirmed')
|
|
264
342
|
.catch(() => null),
|
|
265
|
-
this.
|
|
266
|
-
this.outpostClient.getWireStateSnapshot(user).catch(() => null),
|
|
343
|
+
this.outpostClient.fetchWireState(user).catch(() => null),
|
|
267
344
|
]);
|
|
268
345
|
|
|
269
346
|
const LIQSOL_DECIMALS = 9;
|
|
270
347
|
|
|
271
348
|
const actualAmountStr = actualBalResp?.value?.amount ?? '0';
|
|
272
|
-
const trackedAmountStr =
|
|
273
|
-
userRecord?.trackedBalance?.toString() ?? '0';
|
|
274
349
|
|
|
275
|
-
const wireReceipt = snapshot?.wireReceipt ?? null;
|
|
276
|
-
const userWarrantRecord = snapshot?.userWarrantRecord ?? null;
|
|
277
|
-
const trancheState = snapshot?.trancheState ?? null;
|
|
278
350
|
const globalState = snapshot?.globalState ?? null;
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
351
|
+
const outpostAccount = snapshot?.outpostAccount ?? null;
|
|
352
|
+
const trancheState = snapshot?.trancheState ?? null;
|
|
353
|
+
const userPretokenRecord = snapshot?.userPretokenRecord ?? null;
|
|
354
|
+
|
|
355
|
+
// -----------------------------
|
|
356
|
+
// Staked liqSOL (Outpost)
|
|
357
|
+
// -----------------------------
|
|
358
|
+
// This is the liqSOL that has been syndicated into Outpost via `synd`.
|
|
359
|
+
// It lives on the outpost account as `stakedLiqsol`.
|
|
360
|
+
const stakedLiqsolStr =
|
|
361
|
+
outpostAccount?.stakedLiqsol?.toString?.() ?? '0';
|
|
362
|
+
|
|
363
|
+
// -----------------------------
|
|
364
|
+
// WIRE pretokens (1e8 scale)
|
|
365
|
+
// -----------------------------
|
|
366
|
+
// This is NOT stake — it’s the prelaunch WIRE position.
|
|
367
|
+
const wirePretokensStr =
|
|
368
|
+
userPretokenRecord?.totalPretokensPurchased?.toString?.() ??
|
|
369
|
+
'0';
|
|
370
|
+
|
|
371
|
+
// -----------------------------
|
|
372
|
+
// Yield view (index + shares)
|
|
373
|
+
// -----------------------------
|
|
374
|
+
// We expose:
|
|
375
|
+
// - currentIndex: globalState.currentIndex (1e12 scale)
|
|
376
|
+
// - totalShares: globalState.totalShares
|
|
377
|
+
// - userShares: outpostAccount.stakedShares
|
|
378
|
+
// - estimatedClaimLiqsol: floor(userShares * index / INDEX_SCALE)
|
|
379
|
+
// - estimatedYieldLiqsol: max(0, estimatedClaim - stakedLiqsol)
|
|
380
|
+
//
|
|
381
|
+
// This matches the capital-staking math:
|
|
382
|
+
// sharesToTokens(shares, index) = shares * index / INDEX_SCALE
|
|
383
|
+
const currentIndexStr =
|
|
384
|
+
globalState?.currentIndex?.toString?.() ?? '0';
|
|
385
|
+
const totalSharesStr =
|
|
386
|
+
globalState?.totalShares?.toString?.() ?? '0';
|
|
387
|
+
const userSharesStr =
|
|
388
|
+
outpostAccount?.stakedShares?.toString?.() ?? '0';
|
|
389
|
+
|
|
390
|
+
const stakedLiqsol = BigInt(stakedLiqsolStr);
|
|
391
|
+
const currentIndex = BigInt(currentIndexStr);
|
|
392
|
+
const totalShares = BigInt(totalSharesStr);
|
|
393
|
+
const userShares = BigInt(userSharesStr);
|
|
394
|
+
|
|
395
|
+
let estimatedClaimLiqsol = BigInt(0);
|
|
396
|
+
let estimatedYieldLiqsol = BigInt(0);
|
|
397
|
+
|
|
398
|
+
if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
|
|
399
|
+
// sharesToTokens(userShares, currentIndex)
|
|
400
|
+
estimatedClaimLiqsol = (userShares * currentIndex) / INDEX_SCALE;
|
|
401
|
+
|
|
402
|
+
if (estimatedClaimLiqsol > stakedLiqsol) {
|
|
403
|
+
estimatedYieldLiqsol = estimatedClaimLiqsol - stakedLiqsol;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
285
406
|
|
|
286
407
|
return {
|
|
287
408
|
native: {
|
|
288
409
|
amount: BigInt(nativeLamports),
|
|
289
|
-
symbol:
|
|
410
|
+
symbol: 'SOL',
|
|
290
411
|
decimals: 9,
|
|
291
412
|
},
|
|
292
413
|
liq: {
|
|
293
414
|
amount: BigInt(actualAmountStr),
|
|
294
|
-
symbol:
|
|
415
|
+
symbol: 'LiqSOL',
|
|
295
416
|
decimals: LIQSOL_DECIMALS,
|
|
296
417
|
ata: userLiqsolAta,
|
|
297
418
|
},
|
|
298
419
|
staked: {
|
|
299
|
-
|
|
300
|
-
|
|
420
|
+
// liqSOL staked in Outpost via `synd`
|
|
421
|
+
amount: stakedLiqsol,
|
|
422
|
+
symbol: 'LiqSOL',
|
|
301
423
|
decimals: LIQSOL_DECIMALS,
|
|
302
424
|
},
|
|
303
425
|
wire: {
|
|
304
|
-
// Prelaunch
|
|
305
|
-
amount: BigInt(
|
|
306
|
-
symbol:
|
|
426
|
+
// Prelaunch WIRE pretokens (1e8 scale)
|
|
427
|
+
amount: BigInt(wirePretokensStr),
|
|
428
|
+
symbol: '$WIRE',
|
|
307
429
|
decimals: 8,
|
|
308
430
|
},
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
431
|
+
yield: {
|
|
432
|
+
// Raw primitives so the frontend can display curves, charts, etc.
|
|
433
|
+
currentIndex,
|
|
434
|
+
indexScale: INDEX_SCALE,
|
|
435
|
+
totalShares,
|
|
436
|
+
userShares,
|
|
437
|
+
// liqSOL amounts (lamports) implied by index/shares
|
|
438
|
+
estimatedClaimLiqsol,
|
|
439
|
+
estimatedYieldLiqsol,
|
|
313
440
|
},
|
|
314
441
|
extras: {
|
|
315
442
|
userLiqsolAta: userLiqsolAta.toBase58(),
|
|
316
443
|
reservePoolPDA: reservePoolPDA.toBase58(),
|
|
317
444
|
vaultPDA: vaultPDA.toBase58(),
|
|
318
|
-
wireReceipt,
|
|
319
|
-
userWarrantRecord,
|
|
320
445
|
globalIndex: globalState?.currentIndex?.toString(),
|
|
321
446
|
totalShares: globalState?.totalShares?.toString(),
|
|
322
|
-
currentTrancheNumber:
|
|
323
|
-
|
|
447
|
+
currentTrancheNumber:
|
|
448
|
+
trancheState?.currentTrancheNumber?.toString(),
|
|
449
|
+
currentTranchePriceUsd:
|
|
450
|
+
trancheState?.currentTranchePriceUsd?.toString(), // 1e8 USD
|
|
324
451
|
},
|
|
325
452
|
chainID: this.network.chainId,
|
|
326
453
|
};
|
|
@@ -332,13 +459,9 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
332
459
|
* Uses:
|
|
333
460
|
* - liqsol_core.globalState (currentIndex, totalShares, etc.)
|
|
334
461
|
* - liqsol_core.trancheState (price, supply, total sold, etc.)
|
|
335
|
-
* - Chainlink
|
|
462
|
+
* - PriceHistory/Chainlink SOL/USD via TokenClient.getSolPriceUsdSafe()
|
|
336
463
|
*
|
|
337
|
-
*
|
|
338
|
-
* around the current tranche for UI, but you can pass nothing if you
|
|
339
|
-
* only need current tranche info.
|
|
340
|
-
*
|
|
341
|
-
* READ-ONLY allowed
|
|
464
|
+
* This is READ-ONLY and works even with no connected wallet.
|
|
342
465
|
*/
|
|
343
466
|
async getTrancheSnapshot(options?: {
|
|
344
467
|
chainID?: ChainID;
|
|
@@ -351,13 +474,11 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
351
474
|
windowAfter,
|
|
352
475
|
} = options ?? {};
|
|
353
476
|
|
|
354
|
-
// Canonical program state
|
|
355
477
|
const [globalState, trancheState] = await Promise.all([
|
|
356
478
|
this.tokenClient.fetchGlobalState(),
|
|
357
479
|
this.tokenClient.fetchTrancheState(),
|
|
358
480
|
]);
|
|
359
481
|
|
|
360
|
-
// Latest SOL/USD price (1e8) + timestamp from PriceHistory
|
|
361
482
|
const { price: solPriceUsd, timestamp } =
|
|
362
483
|
await this.tokenClient.getSolPriceUsdSafe();
|
|
363
484
|
|
|
@@ -381,29 +502,6 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
381
502
|
return this.distributionClient.getUserRecord(this.solPubKey);
|
|
382
503
|
}
|
|
383
504
|
|
|
384
|
-
/**
|
|
385
|
-
* Run the "correct & register" flow on Solana:
|
|
386
|
-
* - builds the minimal transaction (maybe multi-user) to reconcile liqSOL
|
|
387
|
-
* - signs and sends the transaction if it can succeed
|
|
388
|
-
*/
|
|
389
|
-
async correctBalance(amount?: bigint): Promise<string> {
|
|
390
|
-
this.ensureWriteAccess();
|
|
391
|
-
const build = await this.distributionClient.buildCorrectRegisterTx({ amount });
|
|
392
|
-
if (!build.canSucceed || !build.transaction) {
|
|
393
|
-
throw new Error(build.reason ?? 'Unable to build Correct&Register transaction');
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const { tx, blockhash, lastValidBlockHeight } = await this.prepareTx(
|
|
397
|
-
build.transaction,
|
|
398
|
-
);
|
|
399
|
-
const signed = await this.signTransaction(tx);
|
|
400
|
-
const signature = await this.sendAndConfirmHttp(signed, {
|
|
401
|
-
blockhash,
|
|
402
|
-
lastValidBlockHeight,
|
|
403
|
-
});
|
|
404
|
-
return signature;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
505
|
// ---------------------------------------------------------------------
|
|
408
506
|
// Tx helpers
|
|
409
507
|
// ---------------------------------------------------------------------
|
|
@@ -417,7 +515,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
417
515
|
ctx: { blockhash: string; lastValidBlockHeight: number },
|
|
418
516
|
): Promise<string> {
|
|
419
517
|
this.ensureWriteAccess();
|
|
420
|
-
|
|
518
|
+
|
|
519
|
+
const signature = await this.connection.sendRawTransaction(
|
|
421
520
|
signed.serialize(),
|
|
422
521
|
{
|
|
423
522
|
skipPreflight: false,
|
|
@@ -436,7 +535,9 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
436
535
|
);
|
|
437
536
|
|
|
438
537
|
if (conf.value.err) {
|
|
439
|
-
throw new Error(
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Transaction failed: ${JSON.stringify(conf.value.err)}`,
|
|
540
|
+
);
|
|
440
541
|
}
|
|
441
542
|
|
|
442
543
|
return signature;
|
|
@@ -445,7 +546,9 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
445
546
|
/**
|
|
446
547
|
* Sign a single Solana transaction using the connected wallet adapter.
|
|
447
548
|
*/
|
|
448
|
-
async signTransaction(
|
|
549
|
+
async signTransaction(
|
|
550
|
+
tx: SolanaTransaction,
|
|
551
|
+
): Promise<SolanaTransaction> {
|
|
449
552
|
this.ensureWriteAccess();
|
|
450
553
|
return this.anchor.wallet.signTransaction(tx);
|
|
451
554
|
}
|
|
@@ -454,7 +557,9 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
454
557
|
* Generic "fire and forget" send helper if the caller already
|
|
455
558
|
* prepared and signed the transaction.
|
|
456
559
|
*/
|
|
457
|
-
async sendTransaction(
|
|
560
|
+
async sendTransaction(
|
|
561
|
+
signed: SolanaTransaction,
|
|
562
|
+
): Promise<TransactionSignature> {
|
|
458
563
|
this.ensureWriteAccess();
|
|
459
564
|
return this.anchor.sendAndConfirm(signed);
|
|
460
565
|
}
|
|
@@ -465,7 +570,11 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
465
570
|
*/
|
|
466
571
|
async prepareTx(
|
|
467
572
|
tx: Transaction,
|
|
468
|
-
): Promise<{
|
|
573
|
+
): Promise<{
|
|
574
|
+
tx: Transaction;
|
|
575
|
+
blockhash: string;
|
|
576
|
+
lastValidBlockHeight: number;
|
|
577
|
+
}> {
|
|
469
578
|
const { blockhash, lastValidBlockHeight } =
|
|
470
579
|
await this.connection.getLatestBlockhash('confirmed');
|
|
471
580
|
tx.recentBlockhash = blockhash;
|
|
@@ -473,10 +582,21 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
473
582
|
return { tx, blockhash, lastValidBlockHeight };
|
|
474
583
|
}
|
|
475
584
|
|
|
585
|
+
/**
|
|
586
|
+
* Guard for all write operations (deposit/withdraw/stake/unstake/buy).
|
|
587
|
+
* Ensures we have a Wire pubKey and an Anchor wallet pubKey, and that they match.
|
|
588
|
+
*/
|
|
476
589
|
ensureWriteAccess() {
|
|
477
|
-
if (!this.pubKey || !this.anchor.wallet.publicKey)
|
|
590
|
+
if (!this.pubKey || !this.anchor.wallet.publicKey) {
|
|
478
591
|
throw new Error('User Authorization required: pubKey is undefined');
|
|
479
|
-
|
|
480
|
-
|
|
592
|
+
}
|
|
593
|
+
if (
|
|
594
|
+
this.solPubKey.toBase58() !==
|
|
595
|
+
this.anchor.wallet.publicKey.toBase58()
|
|
596
|
+
) {
|
|
597
|
+
throw new Error(
|
|
598
|
+
'Write access requires connected wallet to match pubKey',
|
|
599
|
+
);
|
|
600
|
+
}
|
|
481
601
|
}
|
|
482
602
|
}
|