@wireio/stake 0.4.1 → 0.4.3

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.
@@ -5,7 +5,7 @@
5
5
  * IDL can be found at `target/idl/liqsol_token.json`.
6
6
  */
7
7
  export type LiqsolToken = {
8
- "address": "6hiJ8PrSyYLr7H9MA9VYh9fvGoHxZ2TXdDAMzjeUVSXZ",
8
+ "address": "5MRFSLCkXPEzfR6gkcZCVttq9g83mMUHyTZ85Z3TSpvU",
9
9
  "metadata": {
10
10
  "name": "liqsolToken",
11
11
  "version": "0.1.0",
@@ -5,7 +5,7 @@
5
5
  * IDL can be found at `target/idl/transfer_hook.json`.
6
6
  */
7
7
  export type TransferHook = {
8
- "address": "EtqCLddkPNGjkgiH9rVUmK3M5X2FKFprQvgscBJ8g6hX",
8
+ "address": "HbBpH9opFW9gcYVanHLweKuHhWQ8P3Kcc1mbpAx1vojz",
9
9
  "metadata": {
10
10
  "name": "transferHook",
11
11
  "version": "0.1.0",
@@ -129,6 +129,11 @@ export type TransferHook = {
129
129
  {
130
130
  "code": 6002,
131
131
  "name": "tlvAccountResolutionError"
132
+ },
133
+ {
134
+ "code": 6003,
135
+ "name": "cannotTransferToBucket",
136
+ "msg": "Cannot transfer liqSOL directly to bucket - only protocol minting is allowed"
132
137
  }
133
138
  ],
134
139
  "types": [
@@ -5,7 +5,7 @@
5
5
  * IDL can be found at `target/idl/validator_leaderboard.json`.
6
6
  */
7
7
  export type ValidatorLeaderboard = {
8
- "address": "H6CWj2PgxCE9Z5dPi9nMrR2VxT1C4DQcqR1QjmLeon47",
8
+ "address": "5v7mWL1735qp2Th9B5WNf7spGynR5ZaxwyCYoQw13DP2",
9
9
  "metadata": {
10
10
  "name": "validatorLeaderboard",
11
11
  "version": "0.1.0",
@@ -124,6 +124,9 @@ export type ValidatorLeaderboard = {
124
124
  },
125
125
  {
126
126
  "name": "systemProgram"
127
+ },
128
+ {
129
+ "name": "clock"
127
130
  }
128
131
  ],
129
132
  "args": [
@@ -1,11 +1,11 @@
1
1
  import { BigNumber, ethers } from 'ethers';
2
2
  import { ChainID, EvmChainID, PublicKey as WirePubKey } from '@wireio/core';
3
- import {
3
+ import {
4
4
  IStakingClient,
5
5
  OPPAssertion,
6
- Portfolio,
7
- StakerConfig,
8
- TrancheSnapshot
6
+ Portfolio,
7
+ StakerConfig,
8
+ TrancheSnapshot
9
9
  } from '../../types';
10
10
  import { EthereumContractService } from './contract';
11
11
  import { preLaunchReceipt } from './types';
@@ -16,13 +16,10 @@ import { PretokenClient } from './clients/pretoken.client';
16
16
  import { OPPClient } from './clients/opp.client';
17
17
  import { ReceiptClient } from './clients/receipt.client';
18
18
 
19
-
20
-
21
-
22
19
  export class EthereumStakingClient implements IStakingClient {
20
+ private readonly provider: ethers.providers.Web3Provider | ethers.providers.JsonRpcProvider;
23
21
  public readonly pubKey?: WirePubKey;
24
- private readonly provider: ethers.providers.Web3Provider;
25
- private readonly signer: ethers.Signer;
22
+ private readonly signer?: ethers.Signer;
26
23
  private readonly contractService: EthereumContractService;
27
24
 
28
25
  private convertClient: ConvertClient;
@@ -37,29 +34,32 @@ export class EthereumStakingClient implements IStakingClient {
37
34
 
38
35
  constructor(private config: StakerConfig) {
39
36
  try {
40
- this.provider = config.provider as ethers.providers.Web3Provider;
41
- this.signer = this.provider.getSigner();
37
+ if (config.provider) {
38
+ this.provider = config.provider as ethers.providers.Web3Provider;
39
+ this.signer = this.provider.getSigner();
40
+ }
41
+ else {
42
+ this.provider = new ethers.providers.JsonRpcProvider(config.network.rpcUrls[0]);
43
+ }
42
44
  this.pubKey = config.pubKey;
43
-
45
+
44
46
  this.contractService = new EthereumContractService({
45
47
  provider: this.provider,
46
48
  signer: this.signer,
47
49
  });
48
-
50
+
49
51
  this.convertClient = new ConvertClient(this.contractService);
50
52
  this.pretokenClient = new PretokenClient(this.contractService);
51
53
  this.stakeClient = new StakeClient(this.contractService);
52
54
  this.oppClient = new OPPClient(this.contractService);
53
55
  this.receiptClient = new ReceiptClient(this.contractService);
54
- }
56
+ }
55
57
  catch (error) {
56
58
  // console.error('Error initializing EthereumStakingClient:', error);
57
59
  throw error;
58
- }
60
+ }
59
61
  }
60
62
 
61
-
62
-
63
63
  // ---------------------------------------------------------------------
64
64
  // Public IStakingClient Interface Methods
65
65
  // ---------------------------------------------------------------------
@@ -71,6 +71,8 @@ export class EthereumStakingClient implements IStakingClient {
71
71
  * @returns transaction hash
72
72
  */
73
73
  async deposit(amount: number | string | bigint | BigNumber): Promise<string> {
74
+ this.ensureUser();
75
+
74
76
  const amountWei = BigNumber.isBigNumber(amount)
75
77
  ? amount
76
78
  : BigNumber.from(amount);
@@ -79,15 +81,15 @@ export class EthereumStakingClient implements IStakingClient {
79
81
  return result.txHash;
80
82
  }
81
83
 
82
-
83
-
84
84
  /**
85
85
  * Withdraw native ETH from the liqETH protocol via the liqeth safeBurn function, which burns the LiqETH and adds the user to the withdrawal queue.
86
86
  * @param amount Amount in wei (or something convertible to BigNumber).
87
87
  * @returns transaction hash
88
88
  */
89
89
  async withdraw(amount: bigint): Promise<string> {
90
- const address = await this.signer.getAddress();
90
+ this.ensureUser();
91
+
92
+ const address = await this.signer!.getAddress();
91
93
  const amountWei = BigNumber.from(amount);
92
94
 
93
95
  const result = await this.convertClient.performWithdraw(address, amountWei)
@@ -101,7 +103,9 @@ export class EthereumStakingClient implements IStakingClient {
101
103
  * @returns transaction hash
102
104
  */
103
105
  async stake(amount: bigint): Promise<string> {
104
- const walletAddress = await this.signer.getAddress();
106
+ this.ensureUser();
107
+
108
+ const walletAddress = await this.signer!.getAddress();
105
109
  const amountWei = BigNumber.from(amount);
106
110
 
107
111
  const result = await this.stakeClient.performStake(amountWei, walletAddress);
@@ -122,15 +126,18 @@ export class EthereumStakingClient implements IStakingClient {
122
126
  * @returns the transaction hash
123
127
  */
124
128
  async unstakePrelaunch(tokenId: bigint, recipient: string): Promise<string> {
129
+ this.ensureUser();
130
+
125
131
  const tokenIdBigNum = BigNumber.from(tokenId)
126
132
  const result = await this.stakeClient.performWithdrawStake(tokenIdBigNum, recipient);
127
133
  return result.txHash;
128
134
  }
129
135
 
130
136
 
131
-
132
137
  async buy(amount: bigint): Promise<string> {
133
- const buyer = await this.signer.getAddress();
138
+ this.ensureUser();
139
+
140
+ const buyer = await this.signer!.getAddress();
134
141
 
135
142
  // ! Hoodi only - check if the mock aggregator price is stale, and if so, update it before submitting the buy request
136
143
  await this.updateMockAggregatorPrice();
@@ -140,46 +147,6 @@ export class EthereumStakingClient implements IStakingClient {
140
147
  }
141
148
 
142
149
 
143
- async getOPPMessages(address?: string): Promise<OPPAssertion[]> {
144
- if(!address) address = await this.signer.getAddress();
145
-
146
- return await this.oppClient.getMessages(address);
147
- }
148
-
149
-
150
- async getOPPStatus(): Promise<any> {
151
- return await this.oppClient.getStatus();
152
- }
153
-
154
-
155
-
156
- /**
157
- * ETH Prelaunch function to list the Stake ReceiptNFTs owned by a specific user
158
- * @param address address to query the receipts for
159
- * @returns array of receipts
160
- */
161
- async fetchPrelaunchReceipts(address?: string): Promise<preLaunchReceipt[]> {
162
- if(address === undefined) address = await this.signer.getAddress();
163
-
164
- //default to stake receipts
165
- return await this.receiptClient.stakeReceipts(address);
166
- }
167
-
168
-
169
-
170
- async getEthStats(): Promise<any> {
171
- let withdrawDelay = await this.contract.DepositManager.withdrawDelay();
172
- let minDeposit = await this.contract.DepositManager.minDeposit();
173
- let rewardCooldown = await this.contract.DepositManager.rewardCooldown();
174
-
175
- return {
176
- withdrawDelay,
177
- minDeposit,
178
- rewardCooldown,
179
- }
180
- }
181
-
182
-
183
150
  /**
184
151
  * Resolve the user's ETH + liqETH balances.
185
152
  *
@@ -188,7 +155,9 @@ export class EthereumStakingClient implements IStakingClient {
188
155
  * tracked = liqETH tracked balance (protocol/accounting view)
189
156
  */
190
157
  async getPortfolio(): Promise<Portfolio> {
191
- const walletAddress = await this.signer.getAddress();
158
+ this.ensureUser();
159
+
160
+ const walletAddress = await this.signer!.getAddress();
192
161
 
193
162
  // 1) Native ETH balance
194
163
  const nativeBalance = await this.provider.getBalance(walletAddress);
@@ -205,11 +174,48 @@ export class EthereumStakingClient implements IStakingClient {
205
174
  for (let r of stakeReceipts) {
206
175
  stakeBalanceBN = stakeBalanceBN.add(BigNumber.from(r.receipt.principal.amount));
207
176
  }
208
-
177
+ let stakeSharesBN = BigNumber.from(0);
178
+ for (let r of stakeReceipts) {
179
+ stakeSharesBN = stakeSharesBN.add(BigNumber.from(r.receipt.shares.amount));
180
+ }
209
181
 
210
182
  // 4) WIRE pretoken balance
211
183
  const wireBalance: ethers.BigNumber = await this.contract.Pretoken.balanceOf(walletAddress);
212
184
 
185
+
186
+ // 5) Calculate staking yield
187
+ let currentIndex = BigInt(0);
188
+ let totalShares = BigInt(0);
189
+ let userShares = BigInt(0);
190
+ const indexScale = BigInt(1e27);
191
+ try {
192
+ // These may throw if not implemented on contract
193
+ const [indexBn, totalSharesBn] = await Promise.all([
194
+ this.contract.Depositor.index().catch(() => BigNumber.from(0)),
195
+ this.contract.Depositor.totalShares().catch(() => BigNumber.from(0)),
196
+ ]);
197
+
198
+ const userSharesBn = stakeSharesBN;
199
+ currentIndex = BigInt(indexBn.toString());
200
+ totalShares = BigInt(totalSharesBn.toString());
201
+ userShares = BigInt(userSharesBn.toString());
202
+ } catch {}
203
+
204
+ // sharesToTokens(userShares, currentIndex) = userShares * currentIndex / indexScale
205
+ let estimatedClaim = BigInt(0);
206
+ let estimatedYield = BigInt(0);
207
+
208
+ // started work on estimating the user's personal APY - not necessary at the moment
209
+ // let estimatedAPY: number | null = null;
210
+ // if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
211
+ // estimatedClaim = (userShares * currentIndex) / indexScale;
212
+ // if (estimatedClaim > stakeBalanceBN.toBigInt()) {
213
+ // estimatedYield = estimatedClaim - stakeBalanceBN.toBigInt();
214
+ // }
215
+
216
+ // estimatedAPY = null;
217
+ // }
218
+
213
219
  const portfolio: Portfolio = {
214
220
  native: {
215
221
  amount: nativeBalance.toBigInt(),
@@ -231,14 +237,88 @@ export class EthereumStakingClient implements IStakingClient {
231
237
  decimals: 18,
232
238
  symbol: '$WIRE',
233
239
  },
240
+
241
+ yield: {
242
+ currentIndex,
243
+ indexScale,
244
+ totalShares,
245
+ userShares,
246
+ estimatedClaim,
247
+ estimatedYield,
248
+ },
234
249
  chainID: this.network.chainId
235
250
  }
236
251
  return portfolio;
237
252
  }
238
253
 
254
+ /**
255
+ * ETH Prelaunch function to list the Stake ReceiptNFTs owned by a specific user
256
+ * @param address address to query the receipts for
257
+ * @returns array of receipts
258
+ */
259
+ async fetchPrelaunchReceipts(address?: string): Promise<preLaunchReceipt[]> {
260
+ this.ensureUser();
261
+
262
+ if (address === undefined) address = await this.signer!.getAddress();
263
+
264
+ //default to stake receipts
265
+ return await this.receiptClient.stakeReceipts(address);
266
+ }
267
+
268
+ async getOPPMessages(address?: string): Promise<OPPAssertion[]> {
269
+ this.ensureUser();
270
+
271
+ if (!address) address = await this.signer!.getAddress();
272
+
273
+ return await this.oppClient.getMessages(address);
274
+ }
275
+
276
+ // Ensure that signer wallet is available for write operations
277
+ private ensureUser() {
278
+ if (!this.signer) {
279
+ throw new Error(
280
+ 'EthereumStakingClient: write operation requires a wallet-connected Web3 provider',
281
+ );
282
+ }
283
+ }
284
+
285
+
286
+ // ---------------------------------------------------------------------
287
+ // READ-ONLY Public Methods
288
+ // ---------------------------------------------------------------------
289
+
290
+ // Estimated total APY for staking yeild
291
+ async getSystemAPY(): Promise<number> {
292
+ const annualBpsBn = await this.contract.DepositManager.dailyRateBPS();
293
+ return annualBpsBn.toNumber() / 10000; // 0.04 for 4%
294
+ }
295
+
296
+ // Protocol fee charged for deposit from Native to LIQ
297
+ async getDepositFee(amountWei: bigint): Promise<bigint> {
298
+ const feeBn: BigNumber = await this.contract.DepositManager.procFee(amountWei);
299
+ return BigInt(feeBn.toString());
300
+ }
301
+
302
+ async getOPPStatus(): Promise<any> {
303
+ return await this.oppClient.getStatus();
304
+ }
305
+
306
+ async getEthStats(): Promise<any> {
307
+ let withdrawDelay = await this.contract.DepositManager.withdrawDelay();
308
+ let minDeposit = await this.contract.DepositManager.minDeposit();
309
+ let rewardCooldown = await this.contract.DepositManager.rewardCooldown();
310
+
311
+ return {
312
+ withdrawDelay,
313
+ minDeposit,
314
+ rewardCooldown,
315
+ }
316
+ }
239
317
 
240
318
  /**
241
319
  * Program-level prelaunch WIRE / tranche snapshot for Ethereum
320
+ *
321
+ * SUPPORTS READ-ONLY ACcESS
242
322
  */
243
323
  async getTrancheSnapshot(options?: {
244
324
  chainID?: ChainID;
@@ -275,25 +355,25 @@ export class EthereumStakingClient implements IStakingClient {
275
355
 
276
356
 
277
357
  // fetch price and timestamp from aggregator
278
- const [ roundId, answer, startedAt, updatedAt, answeredInRound ] = await this.contract.Aggregator.latestRoundData();
358
+ const [roundId, answer, startedAt, updatedAt, answeredInRound] = await this.contract.Aggregator.latestRoundData();
279
359
  let ethPriceUsd: bigint = BigInt(answer.toString());
280
360
  let nativePriceTimestamp: number = Number(updatedAt);
281
361
 
282
362
  // ! Placeholder from hoodi deployment - don't think this can be fetched dynamically
283
- const initialTrancheSupply = BigInt(60000) * BigInt(1e8);
363
+ const initialTrancheSupply = BigInt(60000) * BigInt(1e8);
284
364
 
285
365
  return buildEthereumTrancheSnapshot({
286
366
  chainID,
287
- totalSharesBn,
288
- indexBn,
367
+ totalSharesBn,
368
+ indexBn,
289
369
  trancheNumberBn,
290
- currentTrancheSupply,
291
- tranchePriceWadBn,
370
+ currentTrancheSupply,
371
+ tranchePriceWadBn,
292
372
  totalTrancheSupply,
293
373
  initialTrancheSupply,
294
374
  supplyGrowthBps,
295
375
  priceGrowthBps,
296
- minPriceUsd,
376
+ minPriceUsd,
297
377
  maxPriceUsd,
298
378
 
299
379
  ethPriceUsd,
@@ -303,16 +383,10 @@ export class EthereumStakingClient implements IStakingClient {
303
383
  });
304
384
  }
305
385
 
306
-
307
-
308
-
309
386
  // ---------------------------------------------------------------------
310
387
  // Internal ETH Staking client helper functions
311
388
  // ---------------------------------------------------------------------
312
389
 
313
-
314
-
315
-
316
390
  // ! This is a temporary measure for Hoodi testnet because there is no aggregator deployed
317
391
  private async updateMockAggregatorPrice() {
318
392
  const aggregator = this.contract.Aggregator;
@@ -328,7 +402,7 @@ export class EthereumStakingClient implements IStakingClient {
328
402
  // safety check - only run in non-production contexts
329
403
  const network = await this.provider.getNetwork();
330
404
  const chainId = network.chainId;
331
- const allowedTestChains = new Set([560048]);
405
+ const allowedTestChains = new Set([560048]);
332
406
  if (!allowedTestChains.has(chainId)) {
333
407
  console.warn(`MockAggregator is stale (${ageSec}s) but chainId ${chainId} is not a test/local network — skipping update.`);
334
408
  return;
@@ -359,10 +433,4 @@ export class EthereumStakingClient implements IStakingClient {
359
433
  console.log(`MockAggregator updated ${ageSec}s ago — no update needed`);
360
434
  }
361
435
  }
362
-
363
-
364
-
365
-
366
-
367
- // TODO: implement claimRewards, etc.
368
436
  }
@@ -40,6 +40,7 @@ import {
40
40
  deriveWithdrawMintMetadataPda,
41
41
  deriveWithdrawNftMintPda,
42
42
  deriveLiqReceiptDataPda,
43
+ deriveGlobalConfigPda,
43
44
  } from '../constants';
44
45
  import { WalletLike } from '../types';
45
46
 
@@ -86,6 +87,7 @@ export class DepositClient {
86
87
  const payoutState = derivePayoutStatePda();
87
88
  const bucketAuthority = deriveBucketAuthorityPda();
88
89
  const payRateHistory = derivePayRateHistoryPda();
90
+ const globalConfig = deriveGlobalConfigPda();
89
91
 
90
92
  // -------------------------------------------------------------
91
93
  // Token-2022 ATAs
@@ -129,27 +131,24 @@ export class DepositClient {
129
131
  associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
130
132
  liqsolProgram: PROGRAM_IDS.LIQSOL_TOKEN,
131
133
  stakeProgram: StakeProgram.programId,
132
-
133
134
  liqsolMint,
134
135
  userAta,
135
136
  liqsolMintAuthority,
136
137
  reservePool,
137
138
  vault,
138
139
  ephemeralStake,
139
-
140
140
  controllerState,
141
141
  payoutState,
142
142
  bucketAuthority,
143
143
  bucketTokenAccount,
144
-
145
144
  userRecord,
146
145
  distributionState,
147
146
  payRateHistory,
148
-
149
147
  instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
150
148
  clock: SYSVAR_CLOCK_PUBKEY,
151
149
  stakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
152
150
  rent: SYSVAR_RENT_PUBKEY,
151
+ globalConfig
153
152
  })
154
153
  .instruction();
155
154
 
@@ -203,6 +202,7 @@ export class DepositClient {
203
202
  const stakeAllocationState = deriveStakeAllocationStatePda();
204
203
  const stakeMetrics = deriveStakeMetricsPda();
205
204
  const maintenanceLedger = deriveMaintenanceLedgerPda();
205
+ const globalConfig = deriveGlobalConfigPda();
206
206
 
207
207
  // -------------------------------------------------------------
208
208
  // Need nextReceiptId from withdraw global state
@@ -265,6 +265,7 @@ export class DepositClient {
265
265
  associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
266
266
  systemProgram: SystemProgram.programId,
267
267
  rent: SYSVAR_RENT_PUBKEY,
268
+ globalConfig
268
269
  })
269
270
  .instruction();
270
271
 
@@ -36,6 +36,9 @@ export const {
36
36
  */
37
37
 
38
38
  export const PDA_SEEDS = {
39
+ // GLOBAL CONFIG
40
+ GLOBAL_CONFIG: 'global_config',
41
+
39
42
  // liqsol_core: deposit / stake controller
40
43
  DEPOSIT_AUTHORITY: 'deposit_authority',
41
44
  VAULT: 'vault',
@@ -86,8 +89,19 @@ export const PDA_SEEDS = {
86
89
  MINT_METADATA: 'mint_metadata',
87
90
  LIQ_RECEIPT_DATA: 'liq_receipt_data',
88
91
  WITHDRAW_MINT: 'mint',
92
+
93
+
94
+
89
95
  } as const;
90
96
 
97
+ // Global Config PDA
98
+ export const deriveGlobalConfigPda = () =>
99
+ PublicKey.findProgramAddressSync(
100
+ [Buffer.from(PDA_SEEDS.GLOBAL_CONFIG)],
101
+ LIQSOL_CORE,
102
+ )[0];
103
+
104
+
91
105
  /**
92
106
  * ---------------------------------------------------------------------------
93
107
  * CORE / DISTRIBUTION / DEPOSIT PDAS