@wireio/stake 0.7.3 → 0.9.1
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 +628 -816
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +196 -549
- package/lib/stake.js +661 -816
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +628 -816
- package/lib/stake.m.js.map +1 -1
- package/package.json +2 -1
- package/src/assets/solana/idl/liqsol_core.json +114 -377
- package/src/assets/solana/idl/validator_leaderboard.json +0 -146
- package/src/assets/solana/types/liqsol_core.ts +114 -377
- package/src/assets/solana/types/validator_leaderboard.ts +0 -146
- package/src/networks/ethereum/clients/opp.client.ts +20 -21
- package/src/networks/ethereum/clients/receipt.client.ts +8 -1
- package/src/networks/ethereum/contract.ts +40 -5
- package/src/networks/ethereum/ethereum.ts +148 -139
- package/src/networks/ethereum/types.ts +1 -2
- package/src/networks/solana/clients/deposit.client.ts +260 -9
- package/src/networks/solana/clients/distribution.client.ts +1 -1
- package/src/networks/solana/clients/outpost.client.ts +1 -1
- package/src/networks/solana/constants.ts +1 -1
- package/src/networks/solana/solana.ts +439 -233
- package/src/networks/solana/types.ts +4 -4
- package/src/staker.ts +2 -2
- package/src/types.ts +8 -0
|
@@ -7,17 +7,23 @@ import {
|
|
|
7
7
|
PublicKey as SolPubKey,
|
|
8
8
|
SystemProgram,
|
|
9
9
|
Transaction,
|
|
10
|
+
TransactionInstruction,
|
|
11
|
+
TransactionMessage,
|
|
10
12
|
TransactionSignature,
|
|
11
13
|
} from '@solana/web3.js';
|
|
12
14
|
import { AnchorProvider, BN } from '@coral-xyz/anchor';
|
|
13
15
|
import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
|
|
14
16
|
import {
|
|
15
17
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
18
|
+
createAssociatedTokenAccountInstruction,
|
|
16
19
|
getAssociatedTokenAddressSync,
|
|
17
20
|
TOKEN_2022_PROGRAM_ID,
|
|
18
21
|
} from '@solana/spl-token';
|
|
19
22
|
|
|
23
|
+
import * as multisig from "@sqds/multisig";
|
|
24
|
+
|
|
20
25
|
import {
|
|
26
|
+
Base58,
|
|
21
27
|
ChainID,
|
|
22
28
|
ExternalNetwork,
|
|
23
29
|
KeyType,
|
|
@@ -28,6 +34,7 @@ import {
|
|
|
28
34
|
import {
|
|
29
35
|
IStakingClient,
|
|
30
36
|
Portfolio,
|
|
37
|
+
SquadsXConfig,
|
|
31
38
|
StakerConfig,
|
|
32
39
|
TrancheSnapshot,
|
|
33
40
|
} from '../../types';
|
|
@@ -50,6 +57,7 @@ import {
|
|
|
50
57
|
import { buildSolanaTrancheSnapshot, ceilDiv } from './utils';
|
|
51
58
|
import { GlobalConfig, PayRateEntry, SolanaTransaction } from './types';
|
|
52
59
|
import { SolanaProgramService } from './program';
|
|
60
|
+
import bs58 from 'bs58';
|
|
53
61
|
|
|
54
62
|
const commitment: Commitment = 'confirmed';
|
|
55
63
|
|
|
@@ -78,6 +86,9 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
78
86
|
public tokenClient: TokenClient;
|
|
79
87
|
public program: SolanaProgramService
|
|
80
88
|
|
|
89
|
+
private smartAccount?: SolPubKey; // PDA (off-curve)
|
|
90
|
+
private signer?: SolPubKey; // on-curve signer
|
|
91
|
+
|
|
81
92
|
get solPubKey(): SolPubKey {
|
|
82
93
|
if (!this.pubKey) throw new Error('pubKey is undefined');
|
|
83
94
|
return new SolPubKey(this.pubKey.data.array);
|
|
@@ -87,6 +98,18 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
87
98
|
return this.config.network;
|
|
88
99
|
}
|
|
89
100
|
|
|
101
|
+
get feePayer(): SolPubKey {
|
|
102
|
+
if (this.signer) return this.signer;
|
|
103
|
+
// fallback for normal wallets
|
|
104
|
+
if (this.anchor.wallet.publicKey) return this.anchor.wallet.publicKey;
|
|
105
|
+
throw new Error('No signing authority available');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
get squadsX(): SquadsXConfig | null {
|
|
109
|
+
const config = this.config.extras?.squadsX;
|
|
110
|
+
return config ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
90
113
|
constructor(private config: StakerConfig) {
|
|
91
114
|
const adapter = config.provider as BaseSignerWalletAdapter | undefined;
|
|
92
115
|
|
|
@@ -197,6 +220,83 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
197
220
|
// IStakingClient core methods
|
|
198
221
|
// ---------------------------------------------------------------------
|
|
199
222
|
|
|
223
|
+
|
|
224
|
+
async createVaultLiqsolAtaOneShot(params: {
|
|
225
|
+
connection: Connection;
|
|
226
|
+
payer: SolPubKey; // user's wallet pubkey (signer)
|
|
227
|
+
vaultPda: SolPubKey; // squads vault PDA (off-curve owner)
|
|
228
|
+
}): Promise<{ tx: Transaction; vaultAta: SolPubKey } | null> {
|
|
229
|
+
const { connection, payer, vaultPda } = params;
|
|
230
|
+
|
|
231
|
+
const liqsolMint = deriveLiqsolMintPda();
|
|
232
|
+
|
|
233
|
+
const vaultAta = getAssociatedTokenAddressSync(
|
|
234
|
+
liqsolMint,
|
|
235
|
+
vaultPda,
|
|
236
|
+
true, // allowOwnerOffCurve
|
|
237
|
+
TOKEN_2022_PROGRAM_ID,
|
|
238
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// If it already exists, just no-op
|
|
242
|
+
const info = await connection.getAccountInfo(vaultAta, "confirmed");
|
|
243
|
+
console.log('info?', info);
|
|
244
|
+
|
|
245
|
+
if (info) return null;
|
|
246
|
+
|
|
247
|
+
const ix = createAssociatedTokenAccountInstruction(
|
|
248
|
+
payer, // payer = user
|
|
249
|
+
vaultAta, // ata address
|
|
250
|
+
vaultPda, // owner = vault
|
|
251
|
+
liqsolMint,
|
|
252
|
+
TOKEN_2022_PROGRAM_ID,
|
|
253
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const tx = new Transaction().add(ix);
|
|
257
|
+
return { tx, vaultAta };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private async prepSquadsIxs(ix: TransactionInstruction): Promise<TransactionInstruction[]> {
|
|
261
|
+
if (!this.squadsX) throw new Error('Attempting to wrap Squads instruction without SquadsX config');
|
|
262
|
+
|
|
263
|
+
const multisigPda = this.squadsMultisigPDA!;
|
|
264
|
+
const vaultPda = this.squadsVaultPDA!;
|
|
265
|
+
const vaultIndex = this.squadsX?.vaultIndex ?? 0;
|
|
266
|
+
const creator = this.solPubKey;
|
|
267
|
+
|
|
268
|
+
// compute next transactionIndex
|
|
269
|
+
const ms = await multisig.accounts.Multisig.fromAccountAddress(this.connection, multisigPda);
|
|
270
|
+
const current = BigInt(ms.transactionIndex?.toString() ?? 0);
|
|
271
|
+
const transactionIndex = current + BigInt(1);
|
|
272
|
+
|
|
273
|
+
// inner message uses vault as payer
|
|
274
|
+
// const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 });
|
|
275
|
+
const { blockhash } = await this.connection.getLatestBlockhash("confirmed");
|
|
276
|
+
const transactionMessage = new TransactionMessage({
|
|
277
|
+
payerKey: vaultPda,
|
|
278
|
+
recentBlockhash: blockhash,
|
|
279
|
+
instructions: [ix],
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const createVaultTxIx = await multisig.instructions.vaultTransactionCreate({
|
|
283
|
+
multisigPda,
|
|
284
|
+
transactionIndex,
|
|
285
|
+
creator,
|
|
286
|
+
vaultIndex,
|
|
287
|
+
transactionMessage,
|
|
288
|
+
ephemeralSigners: 0,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const createProposalIx = await multisig.instructions.proposalCreate({
|
|
292
|
+
multisigPda,
|
|
293
|
+
transactionIndex,
|
|
294
|
+
creator,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
return [createVaultTxIx, createProposalIx];
|
|
298
|
+
}
|
|
299
|
+
|
|
200
300
|
/**
|
|
201
301
|
* Deposit native SOL into liqSOL (liqsol_core::deposit).
|
|
202
302
|
* Handles tx build, sign, send, and confirmation.
|
|
@@ -207,14 +307,55 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
207
307
|
throw new Error('Deposit amount must be greater than zero.');
|
|
208
308
|
}
|
|
209
309
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
310
|
+
try {
|
|
311
|
+
const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
|
|
312
|
+
// console.log('amountLamports', amountLamports);
|
|
313
|
+
|
|
314
|
+
if (!!this.squadsX) {
|
|
315
|
+
|
|
316
|
+
const createVaultTx = await this.createVaultLiqsolAtaOneShot({
|
|
317
|
+
connection: this.connection,
|
|
318
|
+
payer: this.solPubKey,
|
|
319
|
+
vaultPda: this.squadsVaultPDA!,
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
if (createVaultTx !== null) {
|
|
323
|
+
console.log('need to create vault ata first...');
|
|
324
|
+
const tx0 = new Transaction().add(createVaultTx.tx);
|
|
325
|
+
const prepared0 = await this.prepareTx(tx0);
|
|
326
|
+
const signed0 = await this.signTransaction(prepared0.tx);
|
|
327
|
+
const sent0 = await this.sendAndConfirmHttp(signed0, prepared0);
|
|
328
|
+
console.log('create Vault ATA', sent0);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const ix = await this.depositClient.buildDepositTx(amountLamports, this.squadsVaultPDA!)
|
|
332
|
+
const squadIxs = await this.prepSquadsIxs(ix)
|
|
333
|
+
|
|
334
|
+
const tx1 = new Transaction().add(cuIx, squadIxs[0]);
|
|
335
|
+
const prepared1 = await this.prepareTx(tx1);
|
|
336
|
+
const signed1 = await this.signTransaction(prepared1.tx);
|
|
337
|
+
const sent1 = await this.sendAndConfirmHttp(signed1, prepared1);
|
|
338
|
+
console.log('SENT 1', sent1);
|
|
339
|
+
|
|
340
|
+
const tx2 = new Transaction().add(cuIx, squadIxs[1]);
|
|
341
|
+
const prepared2 = await this.prepareTx(tx2);
|
|
342
|
+
const signed2 = await this.signTransaction(prepared2.tx);
|
|
343
|
+
const sent2 = await this.sendAndConfirmHttp(signed2, prepared2);
|
|
344
|
+
console.log('SENT 2', sent2);
|
|
345
|
+
|
|
346
|
+
return sent2;
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
const ix = await this.depositClient.buildDepositTx(amountLamports)
|
|
350
|
+
const tx = new Transaction().add(ix);
|
|
351
|
+
const prepared = await this.prepareTx(tx);
|
|
352
|
+
const signed = await this.signTransaction(prepared.tx);
|
|
353
|
+
|
|
354
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
throw new Error(`Failed to deposit Solana: ${err}`);
|
|
358
|
+
}
|
|
218
359
|
}
|
|
219
360
|
|
|
220
361
|
/**
|
|
@@ -233,14 +374,22 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
233
374
|
throw new Error('Withdraw amount must be greater than zero.');
|
|
234
375
|
}
|
|
235
376
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
377
|
+
try {
|
|
378
|
+
// Build compute budget increase instruction
|
|
379
|
+
const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
|
|
380
|
+
|
|
381
|
+
// Build the Outpost synd instruction
|
|
382
|
+
const ix = await this.depositClient.buildWithdrawTx(amountLamports);
|
|
383
|
+
|
|
384
|
+
// Wrap in a transaction and send
|
|
385
|
+
const tx = new Transaction().add(cuIx, ix);
|
|
386
|
+
const prepared = await this.prepareTx(tx);
|
|
387
|
+
const signed = await this.signTransaction(prepared.tx);
|
|
388
|
+
|
|
389
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
throw new Error(`Failed to withdraw Solana: ${err}`);
|
|
392
|
+
}
|
|
244
393
|
}
|
|
245
394
|
|
|
246
395
|
/**
|
|
@@ -253,20 +402,24 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
253
402
|
throw new Error('Stake amount must be greater than zero.');
|
|
254
403
|
}
|
|
255
404
|
|
|
256
|
-
|
|
405
|
+
try {
|
|
406
|
+
const user = this.solPubKey;
|
|
257
407
|
|
|
258
|
-
|
|
259
|
-
|
|
408
|
+
// Build compute budget increase instruction
|
|
409
|
+
const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
|
|
260
410
|
|
|
261
|
-
|
|
262
|
-
|
|
411
|
+
// Build the Outpost synd instruction
|
|
412
|
+
const ix = await this.outpostClient.buildStakeIx(amountLamports, user);
|
|
263
413
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
414
|
+
// Wrap in a transaction and send
|
|
415
|
+
const tx = new Transaction().add(cuIx, ix);
|
|
416
|
+
const prepared = await this.prepareTx(tx);
|
|
417
|
+
const signed = await this.signTransaction(prepared.tx);
|
|
268
418
|
|
|
269
|
-
|
|
419
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
throw new Error(`Failed to stake Solana: ${err}`);
|
|
422
|
+
}
|
|
270
423
|
}
|
|
271
424
|
|
|
272
425
|
/**
|
|
@@ -279,20 +432,25 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
279
432
|
throw new Error('Unstake amount must be greater than zero.');
|
|
280
433
|
}
|
|
281
434
|
|
|
282
|
-
|
|
435
|
+
try {
|
|
436
|
+
const user = this.solPubKey;
|
|
283
437
|
|
|
284
|
-
|
|
285
|
-
|
|
438
|
+
// Build compute budget increase instruction
|
|
439
|
+
const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
|
|
286
440
|
|
|
287
|
-
|
|
288
|
-
|
|
441
|
+
// Build the Outpost desynd instruction
|
|
442
|
+
const ix = await this.outpostClient.buildUnstakeIx(amountLamports, user);
|
|
289
443
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
444
|
+
// Wrap in a transaction and send
|
|
445
|
+
const tx = new Transaction().add(cuIx, ix);
|
|
446
|
+
const prepared = await this.prepareTx(tx);
|
|
447
|
+
const signed = await this.signTransaction(prepared.tx);
|
|
294
448
|
|
|
295
|
-
|
|
449
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
450
|
+
}
|
|
451
|
+
catch (err) {
|
|
452
|
+
throw new Error(`Failed to unstake Solana: ${err}`);
|
|
453
|
+
}
|
|
296
454
|
}
|
|
297
455
|
|
|
298
456
|
/**
|
|
@@ -307,17 +465,20 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
307
465
|
throw new Error('liqSOL pretoken purchase requires a positive amount.');
|
|
308
466
|
}
|
|
309
467
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
468
|
+
try {
|
|
469
|
+
const user = this.solPubKey;
|
|
470
|
+
const cuIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 });
|
|
471
|
+
const ix = await this.tokenClient.buildPurchaseIx(amountLamports, user);
|
|
472
|
+
|
|
473
|
+
const tx = new Transaction().add(cuIx, ix);
|
|
474
|
+
const prepared = await this.prepareTx(tx);
|
|
475
|
+
const signed = await this.signTransaction(prepared.tx);
|
|
476
|
+
|
|
477
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
478
|
+
}
|
|
479
|
+
catch (err) {
|
|
480
|
+
throw new Error(`Failed to buy liqSOL pretokens: ${err}`);
|
|
481
|
+
}
|
|
321
482
|
}
|
|
322
483
|
|
|
323
484
|
/**
|
|
@@ -332,140 +493,147 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
332
493
|
async getPortfolio(): Promise<Portfolio> {
|
|
333
494
|
if (!this.pubKey) throw new Error('User pubKey is undefined');
|
|
334
495
|
|
|
335
|
-
|
|
496
|
+
try {
|
|
497
|
+
const user = !!this.squadsX ? this.squadsVaultPDA! : this.solPubKey;
|
|
336
498
|
|
|
337
|
-
|
|
338
|
-
const vaultPDA = deriveVaultPda();
|
|
339
|
-
const liqsolMint = deriveLiqsolMintPda();
|
|
499
|
+
console.log('get portfolio for user', user.toBase58());
|
|
340
500
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
false,
|
|
345
|
-
TOKEN_2022_PROGRAM_ID,
|
|
346
|
-
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
347
|
-
);
|
|
501
|
+
const reservePoolPDA = deriveReservePoolPda();
|
|
502
|
+
const vaultPDA = deriveVaultPda();
|
|
503
|
+
const liqsolMint = deriveLiqsolMintPda();
|
|
348
504
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
.getTokenAccountBalance(userLiqsolAta, 'confirmed')
|
|
357
|
-
.catch(() => null),
|
|
358
|
-
this.outpostClient.fetchWireState(user).catch(() => null),
|
|
359
|
-
]);
|
|
505
|
+
const userLiqsolAta = getAssociatedTokenAddressSync(
|
|
506
|
+
liqsolMint,
|
|
507
|
+
user,
|
|
508
|
+
true, //set to true to allow off curve (e.g. PDA for squadsx wallet)
|
|
509
|
+
TOKEN_2022_PROGRAM_ID,
|
|
510
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
511
|
+
);
|
|
360
512
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
513
|
+
// NOTE:
|
|
514
|
+
// - nativeLamports: wallet SOL
|
|
515
|
+
// - actualBalResp: liqSOL balance in user ATA
|
|
516
|
+
// - snapshot: Outpost + pretokens + global index/shares
|
|
517
|
+
const [nativeLamports, actualBalResp, snapshot] = await Promise.all([
|
|
518
|
+
this.connection.getBalance(user, 'confirmed'),
|
|
519
|
+
this.connection
|
|
520
|
+
.getTokenAccountBalance(userLiqsolAta, 'confirmed')
|
|
521
|
+
.catch(() => null),
|
|
522
|
+
this.outpostClient.fetchWireState(user).catch(() => null),
|
|
523
|
+
]);
|
|
524
|
+
|
|
525
|
+
const LIQSOL_DECIMALS = 9;
|
|
526
|
+
|
|
527
|
+
const actualAmountStr = actualBalResp?.value?.amount ?? '0';
|
|
528
|
+
|
|
529
|
+
const globalState = snapshot?.globalState ?? null;
|
|
530
|
+
const outpostAccount = snapshot?.outpostAccount ?? null;
|
|
531
|
+
const trancheState = snapshot?.trancheState ?? null;
|
|
532
|
+
const userPretokenRecord = snapshot?.userPretokenRecord ?? null;
|
|
533
|
+
|
|
534
|
+
// -----------------------------
|
|
535
|
+
// Staked liqSOL (Outpost)
|
|
536
|
+
// -----------------------------
|
|
537
|
+
// This is the liqSOL that has been syndicated into Outpost via `synd`.
|
|
538
|
+
// It lives on the outpost account as `stakedLiqsol`.
|
|
539
|
+
const stakedLiqsolStr =
|
|
540
|
+
outpostAccount?.stakedLiqsol?.toString?.() ?? '0';
|
|
541
|
+
|
|
542
|
+
// -----------------------------
|
|
543
|
+
// WIRE pretokens (1e8 scale)
|
|
544
|
+
// -----------------------------
|
|
545
|
+
// This is NOT stake — it’s the prelaunch WIRE position.
|
|
546
|
+
const wirePretokensStr =
|
|
547
|
+
userPretokenRecord?.totalPretokensPurchased?.toString?.() ??
|
|
548
|
+
'0';
|
|
549
|
+
|
|
550
|
+
// -----------------------------
|
|
551
|
+
// Yield view (index + shares)
|
|
552
|
+
// -----------------------------
|
|
553
|
+
// We expose:
|
|
554
|
+
// - currentIndex: globalState.currentIndex (1e12 scale)
|
|
555
|
+
// - totalShares: globalState.totalShares
|
|
556
|
+
// - userShares: outpostAccount.stakedShares
|
|
557
|
+
// - estimatedClaimLiqsol: floor(userShares * index / INDEX_SCALE)
|
|
558
|
+
// - estimatedYield: max(0, estimatedClaim - stakedLiqsol)
|
|
559
|
+
//
|
|
560
|
+
// This matches the capital-staking math:
|
|
561
|
+
// sharesToTokens(shares, index) = shares * index / INDEX_SCALE
|
|
562
|
+
const currentIndexStr =
|
|
563
|
+
globalState?.currentIndex?.toString?.() ?? '0';
|
|
564
|
+
const totalSharesStr =
|
|
565
|
+
globalState?.totalShares?.toString?.() ?? '0';
|
|
566
|
+
const userSharesStr =
|
|
567
|
+
outpostAccount?.stakedShares?.toString?.() ?? '0';
|
|
568
|
+
|
|
569
|
+
const stakedLiqsol = BigInt(stakedLiqsolStr);
|
|
570
|
+
const currentIndex = BigInt(currentIndexStr);
|
|
571
|
+
const totalShares = BigInt(totalSharesStr);
|
|
572
|
+
const userShares = BigInt(userSharesStr);
|
|
573
|
+
|
|
574
|
+
let estimatedClaim = BigInt(0);
|
|
575
|
+
let estimatedYield = BigInt(0);
|
|
576
|
+
|
|
577
|
+
if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
|
|
578
|
+
// sharesToTokens(userShares, currentIndex)
|
|
579
|
+
estimatedClaim = (userShares * currentIndex) / INDEX_SCALE;
|
|
580
|
+
|
|
581
|
+
if (estimatedClaim > stakedLiqsol) {
|
|
582
|
+
estimatedYield = estimatedClaim - stakedLiqsol;
|
|
583
|
+
}
|
|
419
584
|
}
|
|
420
|
-
}
|
|
421
585
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
586
|
+
return {
|
|
587
|
+
native: {
|
|
588
|
+
amount: BigInt(nativeLamports),
|
|
589
|
+
symbol: 'SOL',
|
|
590
|
+
decimals: 9,
|
|
591
|
+
},
|
|
592
|
+
liq: {
|
|
593
|
+
amount: BigInt(actualAmountStr),
|
|
594
|
+
symbol: 'LiqSOL',
|
|
595
|
+
decimals: LIQSOL_DECIMALS,
|
|
596
|
+
ata: userLiqsolAta,
|
|
597
|
+
},
|
|
598
|
+
staked: {
|
|
599
|
+
// liqSOL staked in Outpost via `synd`
|
|
600
|
+
amount: stakedLiqsol,
|
|
601
|
+
symbol: 'LiqSOL',
|
|
602
|
+
decimals: LIQSOL_DECIMALS,
|
|
603
|
+
},
|
|
604
|
+
wire: {
|
|
605
|
+
// Prelaunch WIRE pretokens (1e8 scale)
|
|
606
|
+
amount: BigInt(wirePretokensStr),
|
|
607
|
+
symbol: '$WIRE',
|
|
608
|
+
decimals: 8,
|
|
609
|
+
},
|
|
610
|
+
yield: {
|
|
611
|
+
// Raw primitives so the frontend can display curves, charts, etc.
|
|
612
|
+
currentIndex,
|
|
613
|
+
indexScale: INDEX_SCALE,
|
|
614
|
+
totalShares,
|
|
615
|
+
userShares,
|
|
616
|
+
// liqSOL amounts (lamports) implied by index/shares
|
|
617
|
+
estimatedClaim,
|
|
618
|
+
estimatedYield,
|
|
619
|
+
},
|
|
620
|
+
extras: {
|
|
621
|
+
userLiqsolAta: userLiqsolAta.toBase58(),
|
|
622
|
+
reservePoolPDA: reservePoolPDA.toBase58(),
|
|
623
|
+
vaultPDA: vaultPDA.toBase58(),
|
|
624
|
+
globalIndex: globalState?.currentIndex?.toString(),
|
|
625
|
+
totalShares: globalState?.totalShares?.toString(),
|
|
626
|
+
currentTrancheNumber:
|
|
627
|
+
trancheState?.currentTrancheNumber?.toString(),
|
|
628
|
+
currentTranchePriceUsd:
|
|
629
|
+
trancheState?.currentTranchePriceUsd?.toString(), // 1e8 USD
|
|
630
|
+
},
|
|
631
|
+
chainID: this.network.chainId,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
throw new Error(`Failed to get Solana portfolio: ${err}`);
|
|
636
|
+
}
|
|
469
637
|
}
|
|
470
638
|
|
|
471
639
|
/**
|
|
@@ -477,6 +645,21 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
477
645
|
return this.distributionClient.getUserRecord(this.solPubKey);
|
|
478
646
|
}
|
|
479
647
|
|
|
648
|
+
// ---------------------------------------------------------------------
|
|
649
|
+
// SquadsX Helpers
|
|
650
|
+
// ---------------------------------------------------------------------
|
|
651
|
+
|
|
652
|
+
get squadsMultisigPDA(): SolPubKey | null {
|
|
653
|
+
if (!this.squadsX) return null;
|
|
654
|
+
return new SolPubKey(this.squadsX.multisigPDA);
|
|
655
|
+
}
|
|
656
|
+
get squadsVaultPDA(): SolPubKey | null {
|
|
657
|
+
if (!this.squadsX || !this.squadsMultisigPDA) return null;
|
|
658
|
+
const multisigPda = this.squadsMultisigPDA;
|
|
659
|
+
const index = this.squadsX.vaultIndex ?? 0;
|
|
660
|
+
const pda = multisig.getVaultPda({ multisigPda, index });
|
|
661
|
+
return pda[0];
|
|
662
|
+
}
|
|
480
663
|
|
|
481
664
|
// ---------------------------------------------------------------------
|
|
482
665
|
// READ-ONLY Public Methods
|
|
@@ -497,29 +680,34 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
497
680
|
windowBefore?: number;
|
|
498
681
|
windowAfter?: number;
|
|
499
682
|
}): Promise<TrancheSnapshot> {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
683
|
+
try {
|
|
684
|
+
const {
|
|
685
|
+
chainID = SolChainID.WireTestnet,
|
|
686
|
+
windowBefore,
|
|
687
|
+
windowAfter,
|
|
688
|
+
} = options ?? {};
|
|
689
|
+
|
|
690
|
+
const [globalState, trancheState] = await Promise.all([
|
|
691
|
+
this.tokenClient.fetchGlobalState(),
|
|
692
|
+
this.tokenClient.fetchTrancheState(),
|
|
693
|
+
]);
|
|
694
|
+
|
|
695
|
+
const { price: solPriceUsd, timestamp } =
|
|
696
|
+
await this.tokenClient.getSolPriceUsdSafe();
|
|
697
|
+
|
|
698
|
+
return buildSolanaTrancheSnapshot({
|
|
699
|
+
chainID,
|
|
700
|
+
globalState,
|
|
701
|
+
trancheState,
|
|
702
|
+
solPriceUsd,
|
|
703
|
+
nativePriceTimestamp: timestamp,
|
|
704
|
+
ladderWindowBefore: windowBefore,
|
|
705
|
+
ladderWindowAfter: windowAfter,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
catch (err) {
|
|
709
|
+
throw new Error(`Failed to build Solana tranche snapshot: ${err}`);
|
|
710
|
+
}
|
|
523
711
|
}
|
|
524
712
|
|
|
525
713
|
/**
|
|
@@ -528,16 +716,20 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
528
716
|
* cluster-derived epochs-per-year.
|
|
529
717
|
*/
|
|
530
718
|
async getSystemAPY(): Promise<number> {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
719
|
+
try {
|
|
720
|
+
// 1) Per-epoch rate (decimal) from on-chain stakeMetrics
|
|
721
|
+
const ratePerEpoch = await this.getEpochRateDecimalFromProgram();
|
|
722
|
+
// 2) Live epochs-per-year estimate from cluster
|
|
723
|
+
const epochsPerYear = await this.getEpochsPerYearFromCluster();
|
|
724
|
+
// 3) Compound: (1 + r)^N - 1
|
|
725
|
+
const apyDecimal = Math.pow(1 + ratePerEpoch, epochsPerYear) - 1;
|
|
726
|
+
// 4) Convert to percent
|
|
727
|
+
const apyPercent = apyDecimal * 100;
|
|
728
|
+
|
|
729
|
+
return apyPercent;
|
|
730
|
+
} catch (err) {
|
|
731
|
+
throw new Error(`Failed to compute Solana system APY: ${err}`);
|
|
732
|
+
}
|
|
541
733
|
}
|
|
542
734
|
|
|
543
735
|
/**
|
|
@@ -546,18 +738,24 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
546
738
|
* de-scaled using PAY_RATE_SCALE_FACTOR (1e12).
|
|
547
739
|
*/
|
|
548
740
|
private async getEpochRateDecimalFromProgram(): Promise<number> {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
741
|
+
try {
|
|
742
|
+
|
|
743
|
+
const liqSolCoreProgram = this.program.getProgram('liqsolCore');
|
|
744
|
+
const stakeMetricsPda = deriveStakeMetricsPda();
|
|
745
|
+
const stakeMetrics =
|
|
746
|
+
await liqSolCoreProgram.account.stakeMetrics.fetch(stakeMetricsPda);
|
|
553
747
|
|
|
554
|
-
|
|
555
|
-
|
|
748
|
+
// solSystemPayRate is stored on-chain with PAY_RATE_SCALE_FACTOR (1e12)
|
|
749
|
+
const raw = BigInt(stakeMetrics.solSystemPayRate.toString());
|
|
556
750
|
|
|
557
|
-
|
|
558
|
-
|
|
751
|
+
// Convert to JS number in **decimal per epoch** units
|
|
752
|
+
const rateDecimal = Number(raw) / Number(PAY_RATE_SCALE_FACTOR);
|
|
559
753
|
|
|
560
|
-
|
|
754
|
+
return rateDecimal;
|
|
755
|
+
}
|
|
756
|
+
catch (err) {
|
|
757
|
+
throw new Error(`Failed to read stakeMetrics from program: ${err}`);
|
|
758
|
+
}
|
|
561
759
|
}
|
|
562
760
|
|
|
563
761
|
// Simple cache so we don’t hammer RPC
|
|
@@ -642,7 +840,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
642
840
|
return BigInt(0);
|
|
643
841
|
}
|
|
644
842
|
|
|
645
|
-
const [avgPayRate, globalConfig]: [BN, GlobalConfig] = await Promise.all([
|
|
843
|
+
const [avgPayRate, globalConfig]: [BN, GlobalConfig | null] = await Promise.all([
|
|
646
844
|
this.distributionClient.getAverageScaledPayRate(windowSize),
|
|
647
845
|
this.distributionClient.getGlobalConfig(),
|
|
648
846
|
]);
|
|
@@ -693,14 +891,12 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
693
891
|
}): Promise<bigint> {
|
|
694
892
|
this.ensureUser();
|
|
695
893
|
|
|
696
|
-
const payer = this.solPubKey;
|
|
697
|
-
|
|
698
894
|
// -------------------------------------------------------------
|
|
699
895
|
// 1) Current wallet balance (prefer caller override)
|
|
700
896
|
// -------------------------------------------------------------
|
|
701
897
|
const balanceLamports: bigint =
|
|
702
898
|
options?.balanceOverrideLamports ??
|
|
703
|
-
BigInt(await this.connection.getBalance(
|
|
899
|
+
BigInt(await this.connection.getBalance(this.feePayer, commitment));
|
|
704
900
|
|
|
705
901
|
if (balanceLamports <= BigInt(0)) {
|
|
706
902
|
return BigInt(0);
|
|
@@ -811,7 +1007,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
811
1007
|
return this.cachedTxFee.value;
|
|
812
1008
|
}
|
|
813
1009
|
|
|
814
|
-
const payer = this.
|
|
1010
|
+
const payer = this.feePayer;
|
|
815
1011
|
|
|
816
1012
|
const dummyIx = SystemProgram.transfer({
|
|
817
1013
|
fromPubkey: payer,
|
|
@@ -913,7 +1109,7 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
913
1109
|
const { blockhash, lastValidBlockHeight } =
|
|
914
1110
|
await this.connection.getLatestBlockhash('confirmed');
|
|
915
1111
|
tx.recentBlockhash = blockhash;
|
|
916
|
-
tx.feePayer = this.
|
|
1112
|
+
tx.feePayer = this.feePayer;
|
|
917
1113
|
return { tx, blockhash, lastValidBlockHeight };
|
|
918
1114
|
}
|
|
919
1115
|
|
|
@@ -922,17 +1118,27 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
922
1118
|
* Ensures we have a Wire pubKey and an Anchor wallet pubKey, and that they match.
|
|
923
1119
|
*/
|
|
924
1120
|
ensureUser() {
|
|
925
|
-
if (!this.pubKey
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
) {
|
|
932
|
-
throw new Error(
|
|
933
|
-
'Write access requires connected wallet to match pubKey',
|
|
934
|
-
);
|
|
1121
|
+
if (!this.pubKey) throw new Error('User pubKey is undefined');
|
|
1122
|
+
|
|
1123
|
+
const wallet = this.anchor?.wallet as any;
|
|
1124
|
+
const pk = wallet?.publicKey as SolPubKey | undefined;
|
|
1125
|
+
|
|
1126
|
+
if (!pk) throw new Error('Wallet not connected');
|
|
1127
|
+
if (typeof wallet.signTransaction !== 'function') {
|
|
1128
|
+
throw new Error('Wallet does not support signTransaction');
|
|
935
1129
|
}
|
|
1130
|
+
|
|
1131
|
+
// if (!this.pubKey || !this.anchor.wallet.publicKey) {
|
|
1132
|
+
// throw new Error('User Authorization required: pubKey is undefined');
|
|
1133
|
+
// }
|
|
1134
|
+
// if (
|
|
1135
|
+
// this.solPubKey.toBase58() !==
|
|
1136
|
+
// this.anchor.wallet.publicKey.toBase58()
|
|
1137
|
+
// ) {
|
|
1138
|
+
// throw new Error(
|
|
1139
|
+
// 'Write access requires connected wallet to match pubKey',
|
|
1140
|
+
// );
|
|
1141
|
+
// }
|
|
936
1142
|
}
|
|
937
1143
|
|
|
938
1144
|
}
|