@wireio/stake 2.3.0 → 2.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.
Files changed (30) hide show
  1. package/lib/stake.browser.js +1224 -71
  2. package/lib/stake.browser.js.map +1 -1
  3. package/lib/stake.d.ts +1150 -163
  4. package/lib/stake.js +1234 -71
  5. package/lib/stake.js.map +1 -1
  6. package/lib/stake.m.js +1224 -71
  7. package/lib/stake.m.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/assets/solana/devnet/idl/liqsol_core.json +472 -27
  10. package/src/assets/solana/devnet/idl/liqsol_token.json +34 -0
  11. package/src/assets/solana/devnet/idl/transfer_hook.json +34 -0
  12. package/src/assets/solana/devnet/types/liqsol_core.ts +472 -27
  13. package/src/assets/solana/devnet/types/liqsol_token.ts +34 -0
  14. package/src/assets/solana/devnet/types/transfer_hook.ts +34 -0
  15. package/src/assets/solana/mainnet/idl/liqsol_core.json +472 -27
  16. package/src/assets/solana/mainnet/idl/liqsol_token.json +34 -0
  17. package/src/assets/solana/mainnet/idl/transfer_hook.json +34 -0
  18. package/src/assets/solana/mainnet/types/liqsol_core.ts +472 -27
  19. package/src/assets/solana/mainnet/types/liqsol_token.ts +34 -0
  20. package/src/assets/solana/mainnet/types/transfer_hook.ts +34 -0
  21. package/src/networks/ethereum/ethereum.ts +9 -9
  22. package/src/networks/solana/clients/distribution.client.ts +114 -2
  23. package/src/networks/solana/clients/outpost.client.ts +2 -0
  24. package/src/networks/solana/clients/token.client.ts +2 -0
  25. package/src/networks/solana/constants.ts +3 -0
  26. package/src/networks/solana/program.ts +7 -0
  27. package/src/networks/solana/solana.ts +26 -1
  28. package/src/networks/solana/types.ts +3 -3
  29. package/src/networks/solana/utils.ts +7 -1
  30. package/src/types.ts +10 -1
@@ -118,6 +118,40 @@ export type LiqsolToken = {
118
118
  }
119
119
  }
120
120
  ]
121
+ },
122
+ {
123
+ "name": "updateLiqsolMetadataPermissionless",
124
+ "discriminator": [
125
+ 21,
126
+ 255,
127
+ 26,
128
+ 184,
129
+ 136,
130
+ 110,
131
+ 214,
132
+ 33
133
+ ],
134
+ "accounts": [
135
+ {
136
+ "name": "payer",
137
+ "writable": true,
138
+ "signer": true
139
+ },
140
+ {
141
+ "name": "mintAuthority"
142
+ },
143
+ {
144
+ "name": "mint",
145
+ "writable": true
146
+ },
147
+ {
148
+ "name": "tokenProgram"
149
+ },
150
+ {
151
+ "name": "systemProgram"
152
+ }
153
+ ],
154
+ "args": []
121
155
  }
122
156
  ],
123
157
  "errors": [
@@ -100,6 +100,40 @@ export type TransferHook = {
100
100
  "type": "u64"
101
101
  }
102
102
  ]
103
+ },
104
+ {
105
+ "name": "updateExtraAccountMetaList",
106
+ "discriminator": [
107
+ 44,
108
+ 125,
109
+ 141,
110
+ 226,
111
+ 97,
112
+ 179,
113
+ 166,
114
+ 96
115
+ ],
116
+ "accounts": [
117
+ {
118
+ "name": "payer",
119
+ "writable": true,
120
+ "signer": true
121
+ },
122
+ {
123
+ "name": "extraAccountMetaList",
124
+ "writable": true
125
+ },
126
+ {
127
+ "name": "mint"
128
+ },
129
+ {
130
+ "name": "systemProgram"
131
+ },
132
+ {
133
+ "name": "liqsolCore"
134
+ }
135
+ ],
136
+ "args": []
103
137
  }
104
138
  ],
105
139
  "accounts": [
@@ -32,9 +32,9 @@ export class EthereumStakingClient implements IStakingClient {
32
32
  private receiptClient: ReceiptClient;
33
33
  private validatorClient: ValidatorClient;
34
34
 
35
-
36
35
  get contract() { return this.contractService.contract; }
37
36
  get network() { return this.config.network; }
37
+ get address() { return this.signer?.getAddress(); }
38
38
 
39
39
  constructor(private config: StakerConfig) {
40
40
  try {
@@ -95,7 +95,7 @@ export class EthereumStakingClient implements IStakingClient {
95
95
  async withdraw(amount: bigint): Promise<string> {
96
96
  this.ensureUser();
97
97
 
98
- const address = await this.signer!.getAddress();
98
+ const address = await this.address!;
99
99
  const amountWei = BigNumber.from(amount);
100
100
 
101
101
  const result = await this.convertClient.performWithdraw(address, amountWei)
@@ -109,7 +109,7 @@ export class EthereumStakingClient implements IStakingClient {
109
109
  */
110
110
  async getPendingWithdraws(): Promise<WithdrawReceipt[]> {
111
111
  this.ensureUser();
112
- const address = await this.signer!.getAddress();
112
+ const address = await this.address!;
113
113
 
114
114
  return await this.receiptClient.fetchWithdrawReceipts(address);
115
115
  }
@@ -136,7 +136,7 @@ export class EthereumStakingClient implements IStakingClient {
136
136
  async stake(amount: bigint): Promise<string> {
137
137
  this.ensureUser();
138
138
 
139
- const walletAddress = await this.signer!.getAddress();
139
+ const walletAddress = await this.address!;
140
140
  const amountWei = BigNumber.from(amount);
141
141
 
142
142
  const result = await this.stakeClient.performStake(amountWei, walletAddress);
@@ -168,7 +168,7 @@ export class EthereumStakingClient implements IStakingClient {
168
168
  async buy(amount: bigint): Promise<string> {
169
169
  this.ensureUser();
170
170
 
171
- const buyer = await this.signer!.getAddress();
171
+ const buyer = await this.address!;
172
172
 
173
173
  // ! Hoodi only - check if the mock aggregator price is stale, and if so, update it before submitting the buy request
174
174
  // const network = await this.provider.getNetwork();
@@ -208,7 +208,7 @@ export class EthereumStakingClient implements IStakingClient {
208
208
  try {
209
209
  if (!this.signer) return Promise.resolve(null);
210
210
 
211
- const walletAddress = await this.signer!.getAddress();
211
+ const walletAddress = await this.address!;
212
212
 
213
213
  // 1) Native ETH balance
214
214
  const nativeBalance = await this.provider.getBalance(walletAddress);
@@ -314,7 +314,7 @@ export class EthereumStakingClient implements IStakingClient {
314
314
  async fetchPrelaunchReceipts(address?: string): Promise<preLaunchReceipt[]> {
315
315
  this.ensureUser();
316
316
 
317
- if (address === undefined) address = await this.signer!.getAddress();
317
+ if (address === undefined) address = await this.address!;
318
318
 
319
319
  //default to stake receipts
320
320
  return await this.receiptClient.stakeReceipts(address);
@@ -323,7 +323,7 @@ export class EthereumStakingClient implements IStakingClient {
323
323
  async getOPPMessages(address?: string): Promise<OPPAssertion[]> {
324
324
  this.ensureUser();
325
325
 
326
- if (!address) address = await this.signer!.getAddress();
326
+ if (!address) address = await this.address!;
327
327
 
328
328
  return await this.oppClient.getMessages(address);
329
329
  }
@@ -460,7 +460,7 @@ export class EthereumStakingClient implements IStakingClient {
460
460
  }): Promise<bigint> {
461
461
  this.ensureUser();
462
462
 
463
- const walletAddress = await this.signer!.getAddress();
463
+ const walletAddress = await this.address!;
464
464
 
465
465
  // 1) Estimate a baseline gas usage using a simple self-transfer.
466
466
  // This is cheap and doesn't depend on your contract ABI at all.
@@ -1,5 +1,5 @@
1
1
  import { AnchorProvider, Program, BN } from '@coral-xyz/anchor';
2
- import { PublicKey } from '@solana/web3.js';
2
+ import { PublicKey, TransactionInstruction } from '@solana/web3.js';
3
3
 
4
4
  import { LiqsolCoreClientIdl, SolanaProgramService } from '../program';
5
5
  import type { LiqsolCore } from '../../../assets/solana/devnet/types/liqsol_core';
@@ -7,6 +7,8 @@ import type { DistributionState, DistributionUserRecord, GlobalConfig, PayRateEn
7
7
  import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
8
8
  import { ceilDiv } from '../utils';
9
9
 
10
+ const INDEX_SCALE_BN = new BN('1000000000000');
11
+
10
12
  /**
11
13
  * Distribution client – wraps the distribution portion of the liqsol_core
12
14
  * program in the *new* shares-only model.
@@ -39,6 +41,15 @@ export class DistributionClient {
39
41
  return this.provider.connection;
40
42
  }
41
43
 
44
+ private async getTokenBalance(ata: PublicKey): Promise<BN> {
45
+ try {
46
+ const bal = await this.connection.getTokenAccountBalance(ata);
47
+ return new BN(bal.value.amount);
48
+ } catch {
49
+ return new BN(0);
50
+ }
51
+ }
52
+
42
53
  /**
43
54
  * Fetch the global distribution state account.
44
55
  *
@@ -162,6 +173,107 @@ export class DistributionClient {
162
173
  return { shares: userShares, totalShares, ratio };
163
174
  }
164
175
 
176
+ /**
177
+ * Compute claimable liqSOL for a wallet:
178
+ * claimable = max(0, entitled - actual)
179
+ * entitled = floor(shares * syncedIndex / 1e12)
180
+ *
181
+ * `syncedIndex` mirrors on-chain `sync_index` behavior by incorporating any
182
+ * unsynced bucket delta (bucket_balance - last_bucket_balance).
183
+ */
184
+ async getClaimableLiqsol(user: PublicKey): Promise<BN> {
185
+ const liqsolMint = this.pgs.deriveLiqsolMintPda();
186
+ const bucketAuthority = this.pgs.deriveBucketAuthorityPda();
187
+
188
+ const userAta = getAssociatedTokenAddressSync(
189
+ liqsolMint,
190
+ user,
191
+ true,
192
+ TOKEN_2022_PROGRAM_ID,
193
+ );
194
+ const bucketTokenAccount = getAssociatedTokenAddressSync(
195
+ liqsolMint,
196
+ bucketAuthority,
197
+ true,
198
+ TOKEN_2022_PROGRAM_ID,
199
+ );
200
+
201
+ const [distributionState, userRecord, actualBalance, bucketBalance] = await Promise.all([
202
+ this.getDistributionState(),
203
+ this.getUserRecord(user),
204
+ this.getTokenBalance(userAta),
205
+ this.getTokenBalance(bucketTokenAccount),
206
+ ]);
207
+
208
+ if (!distributionState || !userRecord) {
209
+ return new BN(0);
210
+ }
211
+
212
+ let syncedIndex = new BN(distributionState.currentIndex.toString());
213
+ const totalShares = new BN(distributionState.totalShares.toString());
214
+ const lastBucketBalance = new BN(distributionState.lastBucketBalance.toString());
215
+
216
+ if (totalShares.gt(new BN(0)) && bucketBalance.gt(lastBucketBalance)) {
217
+ const delta = bucketBalance.sub(lastBucketBalance);
218
+ const indexDelta = delta.mul(INDEX_SCALE_BN).div(totalShares);
219
+ if (indexDelta.gt(new BN(0))) {
220
+ syncedIndex = syncedIndex.add(indexDelta);
221
+ }
222
+ }
223
+
224
+ const shares = new BN(userRecord.shares.toString());
225
+ const entitled = shares.mul(syncedIndex).div(INDEX_SCALE_BN);
226
+
227
+ if (entitled.lte(actualBalance)) {
228
+ return new BN(0);
229
+ }
230
+ return entitled.sub(actualBalance);
231
+ }
232
+
233
+ /**
234
+ * Build claim_rewards instruction for a wallet.
235
+ */
236
+ async buildClaimRewardsIx(user: PublicKey): Promise<TransactionInstruction> {
237
+ const liqsolMint = this.pgs.deriveLiqsolMintPda();
238
+ const distributionState = this.pgs.deriveDistributionStatePda();
239
+ const bucketAuthority = this.pgs.deriveBucketAuthorityPda();
240
+
241
+ const userAta = getAssociatedTokenAddressSync(
242
+ liqsolMint,
243
+ user,
244
+ true,
245
+ TOKEN_2022_PROGRAM_ID,
246
+ );
247
+ const bucketTokenAccount = getAssociatedTokenAddressSync(
248
+ liqsolMint,
249
+ bucketAuthority,
250
+ true,
251
+ TOKEN_2022_PROGRAM_ID,
252
+ );
253
+
254
+ const userRecord = this.pgs.deriveUserRecordPda(userAta);
255
+ const bucketUserRecord = this.pgs.deriveUserRecordPda(bucketTokenAccount);
256
+ const extraAccountMetaList = this.pgs.deriveExtraAccountMetaListPda(liqsolMint);
257
+
258
+ return this.program.methods
259
+ .claimRewards()
260
+ .accounts({
261
+ user,
262
+ userAta,
263
+ userRecord,
264
+ bucketUserRecord,
265
+ distributionState,
266
+ extraAccountMetaList,
267
+ liqsolCoreProgram: this.pgs.PROGRAM_IDS.LIQSOL_CORE,
268
+ transferHookProgram: this.pgs.PROGRAM_IDS.TRANSFER_HOOK,
269
+ liqsolMint,
270
+ bucketAuthority,
271
+ bucketTokenAccount,
272
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
273
+ })
274
+ .instruction();
275
+ }
276
+
165
277
  /**
166
278
  * Compute an average scaled pay rate over the most recent `windowSize`
167
279
  * valid entries in the pay-rate history circular buffer.
@@ -243,4 +355,4 @@ export class DistributionClient {
243
355
  // Same behavior as the dashboard: use a ceiling-like average
244
356
  return ceilDiv(sum, new BN(valid));
245
357
  }
246
- }
358
+ }
@@ -169,6 +169,7 @@ export class OutpostClient {
169
169
  tokenProgram: TOKEN_2022_PROGRAM_ID,
170
170
  associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
171
171
  systemProgram: SystemProgram.programId,
172
+ pretokenPurchaseHistory: a.pretokenPurchaseHistory,
172
173
  })
173
174
  .instruction();
174
175
  }
@@ -210,6 +211,7 @@ export class OutpostClient {
210
211
  tokenProgram: TOKEN_2022_PROGRAM_ID,
211
212
  systemProgram: SystemProgram.programId,
212
213
  associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
214
+ pretokenPurchaseHistory: a.pretokenPurchaseHistory,
213
215
  })
214
216
  .instruction();
215
217
  }
@@ -147,6 +147,8 @@ export class TokenClient {
147
147
  tokenProgram: TOKEN_2022_PROGRAM_ID,
148
148
  associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
149
149
  systemProgram: SystemProgram.programId,
150
+
151
+ pretokenPurchaseHistory: a.pretokenPurchaseHistory,
150
152
  })
151
153
  .instruction();
152
154
  }
@@ -109,6 +109,9 @@ export const PDA_SEEDS = {
109
109
  MINT_METADATA: 'mint_metadata',
110
110
  LIQ_RECEIPT_DATA: 'liq_receipt_data',
111
111
  WITHDRAW_MINT: 'mint',
112
+
113
+ // New
114
+ PRETOKEN_PURCHASE_HISTORY: 'pretoken_purchase_history',
112
115
  } as const;
113
116
 
114
117
  /**
@@ -263,6 +263,13 @@ export class SolanaProgramService {
263
263
  )[0];
264
264
  }
265
265
 
266
+ derivePretokenPurchaseHistoryPda(): PublicKey {
267
+ return PublicKey.findProgramAddressSync(
268
+ [Buffer.from(PDA_SEEDS.PRETOKEN_PURCHASE_HISTORY)],
269
+ this.ids.LIQSOL_CORE,
270
+ )[0];
271
+ }
272
+
266
273
  /**
267
274
  * NFT mint for withdrawal receipt, derived from nextReceiptId.
268
275
  */
@@ -298,6 +298,24 @@ export class SolanaStakingClient implements IStakingClient {
298
298
  }
299
299
  }
300
300
 
301
+ /**
302
+ * Claim accrued liqSOL distribution rewards (liqsol_core::claim_rewards).
303
+ */
304
+ async claimLiqsolRewards(): Promise<string> {
305
+ this.ensureUser();
306
+ const owner = this.squadsVaultPDA ?? this.anchor.wallet.publicKey;
307
+
308
+ try {
309
+ const ix = await this.distributionClient.buildClaimRewardsIx(owner);
310
+ return !!this.squadsX
311
+ ? await this.sendSquadsIxs(ix)
312
+ : await this.buildAndSendIx(ix);
313
+ } catch (err) {
314
+ console.log(`Failed to claim liqSOL rewards on Solana: ${err}`);
315
+ throw err;
316
+ }
317
+ }
318
+
301
319
  /**
302
320
  * Stake liqSOL into Outpost (liqSOL → pool) via liqsol_core::synd.
303
321
  */
@@ -391,12 +409,13 @@ export class SolanaStakingClient implements IStakingClient {
391
409
  // - nativeLamports: wallet SOL
392
410
  // - actualBalResp: liqSOL balance in user ATA
393
411
  // - snapshot: Outpost + pretokens + global index/shares
394
- const [nativeLamports, actualBalResp, snapshot] = await Promise.all([
412
+ const [nativeLamports, actualBalResp, snapshot, claimableLamports] = await Promise.all([
395
413
  this.connection.getBalance(user, 'confirmed'),
396
414
  this.connection
397
415
  .getTokenAccountBalance(userLiqsolAta, 'confirmed')
398
416
  .catch(() => null),
399
417
  this.outpostClient.fetchWireState(user).catch(() => null),
418
+ this.distributionClient.getClaimableLiqsol(user).catch(() => new BN(0)),
400
419
  ]);
401
420
 
402
421
  const LIQSOL_DECIMALS = 9;
@@ -472,6 +491,12 @@ export class SolanaStakingClient implements IStakingClient {
472
491
  decimals: LIQSOL_DECIMALS,
473
492
  ata: userLiqsolAta,
474
493
  },
494
+ claimable: {
495
+ amount: BigInt(claimableLamports.toString()),
496
+ symbol: 'LiqSOL',
497
+ decimals: LIQSOL_DECIMALS,
498
+ ata: userLiqsolAta,
499
+ },
475
500
  staked: {
476
501
  // liqSOL staked in Outpost via `synd`
477
502
  amount: stakedLiqsol,
@@ -238,10 +238,10 @@ export type GlobalState = {
238
238
  currentIndex: BN;
239
239
 
240
240
  /**
241
- * Last observed liqSOL pool balance
242
- * used for computing incremental yield.
241
+ * Replaced legacy `lastPoolLiqsolBalance`
242
+ * with expected balance based on total shares and current index.
243
243
  */
244
- lastPoolLiqsolBalance: BN;
244
+ expectedPoolBalance: BN;
245
245
 
246
246
  /**
247
247
  * Accumulated liqSOL yield available to the protocol
@@ -278,6 +278,9 @@ export interface OutpostAccounts {
278
278
 
279
279
  // Transfer hook extra accounts
280
280
  extraAccountMetaList: PublicKey;
281
+
282
+ // New
283
+ pretokenPurchaseHistory: PublicKey;
281
284
  }
282
285
 
283
286
  export async function buildOutpostAccounts(
@@ -325,6 +328,8 @@ export async function buildOutpostAccounts(
325
328
 
326
329
  const extraAccountMetaList = pgs.deriveExtraAccountMetaListPda(liqsolMint);
327
330
 
331
+ const pretokenPurchaseHistory = pgs.derivePretokenPurchaseHistoryPda();
332
+
328
333
  // Chainlink program feeds
329
334
  let chainLinkFeed = CHAINLINK_FEED;
330
335
  let chainLinkProgram = CHAINLINK_PROGRAM;
@@ -365,7 +370,8 @@ export async function buildOutpostAccounts(
365
370
  trancheState,
366
371
  chainLinkFeed,
367
372
  chainLinkProgram,
368
- extraAccountMetaList
373
+ extraAccountMetaList,
374
+ pretokenPurchaseHistory
369
375
  };
370
376
  }
371
377
  // -----------------------------------------------------------------------------
package/src/types.ts CHANGED
@@ -42,6 +42,9 @@ export interface IStakingClient {
42
42
 
43
43
  /** Claim a withdrawal receipt (burn NFT + receive ETH/SOL) via claim_withdraw. */
44
44
  claimWithdraw(tokenId: bigint): Promise<string>
45
+
46
+ /** Solana only: claim accrued liqSOL distribution rewards. */
47
+ claimLiqsolRewards?(): Promise<string>
45
48
 
46
49
  /** Enumerate withdrawal receipt NFTs held by the user (queued/ready/claimed). */
47
50
  getPendingWithdraws(): Promise<WithdrawReceipt[]>
@@ -105,6 +108,12 @@ export interface Portfolio {
105
108
  /** Liquid staking token balance (LiqETH, LiqSOL, etc.). */
106
109
  liq: BalanceView;
107
110
 
111
+ /**
112
+ * Solana-only liqSOL rewards currently claimable from the distribution bucket.
113
+ * Not populated on Ethereum.
114
+ */
115
+ claimable?: BalanceView;
116
+
108
117
  /**
109
118
  * Outpost-staked balance:
110
119
  * - On Solana: liqSOL staked via `synd` (stakedLiqsol, in lamports).
@@ -341,4 +350,4 @@ export interface WithdrawReceipt {
341
350
  /** Optional explicit contract address for ETH queue NFT. */
342
351
  contractAddress?: string;
343
352
  };
344
- }
353
+ }