@wireio/stake 2.3.0 → 2.3.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 +115 -10
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +26 -0
- package/lib/stake.js +125 -10
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +115 -10
- package/lib/stake.m.js.map +1 -1
- package/package.json +1 -1
- package/src/networks/ethereum/ethereum.ts +9 -9
- package/src/networks/solana/clients/distribution.client.ts +114 -2
- package/src/networks/solana/solana.ts +26 -1
- package/src/types.ts +10 -1
package/package.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
}
|
|
@@ -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,
|
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
|
+
}
|