@wireio/stake 0.9.1 → 0.9.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wireio/stake",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "LIQ Staking Module for Wire Network",
5
5
  "homepage": "https://gitea.gitgo.app/Wire/sdk-stake",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -33,7 +33,7 @@
33
33
  "lint:fix": "eslint 'src/**/*.{js,ts}' --fix"
34
34
  },
35
35
  "peerDependencies": {
36
- "@wireio/core": ">=0.1.0 <0.3.0"
36
+ "@wireio/core": ">=0.1.0 <0.4.0"
37
37
  },
38
38
  "dependencies": {
39
39
  "@coral-xyz/anchor": "^0.31.1",
@@ -64,7 +64,7 @@
64
64
  "@types/node": "^18.19.0",
65
65
  "@typescript-eslint/eslint-plugin": "^5.60.0",
66
66
  "@typescript-eslint/parser": "^5.60.0",
67
- "@wireio/core": "^0.2.9",
67
+ "@wireio/core": "^0.3.0",
68
68
  "assert": "^2.0.0",
69
69
  "chai": "^4.3.6",
70
70
  "esbuild": "^0.25.8",
@@ -1,5 +1,5 @@
1
1
  import { BigNumber, ethers } from "ethers";
2
- import { DepositEvent, DepositResult, SharesBurnedEvent } from "../types";
2
+ import { ClaimedEvent, DepositEvent, DepositResult, SharesBurnedEvent } from "../types";
3
3
  import { EthereumContractService } from "../contract";
4
4
  import { formatContractErrors } from "../utils";
5
5
 
@@ -114,6 +114,34 @@ export class ConvertClient {
114
114
  }
115
115
 
116
116
 
117
+ public async claimWithdraw(tokenId: BigNumber): Promise<any> {
118
+ let tx, receipt;
119
+ try {
120
+ tx = await this.contract.DepositManager.claim(tokenId);
121
+ receipt = await tx.wait(1);
122
+ } catch (err: any) {
123
+ let errorObj = formatContractErrors(err);
124
+ throw new Error(errorObj.name ?? errorObj.raw)
125
+ }
126
+
127
+ // Parse Claimed event if present
128
+ let event: ClaimedEvent | undefined;
129
+ const ev = receipt.events?.find((e) => e.event === 'Claimed');
130
+
131
+ if (ev && ev.args) {
132
+ const { sender, ethAmount } = ev.args;
133
+ event = {
134
+ sender,
135
+ ethAmount: BigNumber.from(ethAmount),
136
+ };
137
+ }
138
+
139
+ return {
140
+ txHash: tx.hash,
141
+ receipt,
142
+ event,
143
+ };
144
+ }
117
145
 
118
146
  /*
119
147
  // OLD - this was replaced with LiqEth.safeBurn() on 1/13/26
@@ -1,5 +1,5 @@
1
1
  import { BigNumber } from "ethers";
2
- import { preLaunchReceipt } from "../types";
2
+ import { preLaunchReceipt, WithdrawReceipt } from "../types";
3
3
  import { EthereumContractService } from "../contract";
4
4
  import { ReceiptNFTKind } from "../../../types";
5
5
 
@@ -45,7 +45,7 @@ export class ReceiptClient {
45
45
  const receiptContract = this.contract.ReceiptNFT;
46
46
 
47
47
  // first figure out which tokenIds this address owns, from events
48
- const tokenIds = await this.getOwnedTokenIdsFor(address);
48
+ const tokenIds = await this.getOwnedReceiptNFTsFor(address);
49
49
 
50
50
  const results: preLaunchReceipt[] = [];
51
51
 
@@ -90,7 +90,7 @@ export class ReceiptClient {
90
90
 
91
91
 
92
92
 
93
- private async getOwnedTokenIdsFor(
93
+ private async getOwnedReceiptNFTsFor(
94
94
  owner: string,
95
95
  fromBlock = 0,
96
96
  toBlock: number | string = "latest"
@@ -132,5 +132,83 @@ export class ReceiptClient {
132
132
  }
133
133
 
134
134
 
135
+ /**
136
+ *
137
+ * @param address (string) to fetch receipts for
138
+ * @returns preLaunchReceipt[]
139
+ */
140
+ async fetchWithdrawReceipts(address: string): Promise<WithdrawReceipt[]> {
141
+ // first figure out which tokenIds this address owns, from events
142
+ const tokenIds = await this.getOwnedWithdrawReceiptsFor(address);
143
+ const results: WithdrawReceipt[] = [];
144
+
145
+ // next fetch on-chain token data just for those ids
146
+ for (const idBN of tokenIds) {
147
+ try {
148
+ const receiptData = await this.contract.WithdrawalQueue.info(idBN);
149
+
150
+ results.push({
151
+ tokenId: idBN.toBigInt(),
152
+ receipt: {
153
+ ethAmount: receiptData.ethAmount,
154
+ ethBalance: {
155
+ amount: receiptData.ethAmount.toBigInt(),
156
+ decimals: 18,
157
+ symbol: "ETH"
158
+ },
159
+ readyAt: new Date(Number(receiptData.readyAt.toString()) * 1000).valueOf(),
160
+ }
161
+ });
162
+ } catch (err) {
163
+ // in case of any mismatch or race, just skip this id
164
+ console.warn(`Failed to load receipt for tokenId=${idBN.toString()}`, err);
165
+ continue;
166
+ }
167
+ }
168
+
169
+ return results;
170
+ }
171
+
172
+
173
+
174
+ private async getOwnedWithdrawReceiptsFor(
175
+ owner: string,
176
+ fromBlock = 0,
177
+ toBlock: number | string = "latest"
178
+ ): Promise<BigNumber[]> {
179
+ const contract = this.contract.WithdrawalQueue;
180
+
181
+ // Logs where address received tokens
182
+ const toLogs = await contract.queryFilter(
183
+ contract.filters.Transfer(null, owner),
184
+ fromBlock,
185
+ toBlock
186
+ );
187
+
188
+ // Logs where address sent tokens (including burns from owner → 0)
189
+ const fromLogs = await contract.queryFilter(
190
+ contract.filters.Transfer(owner, null),
191
+ fromBlock,
192
+ toBlock
193
+ );
135
194
 
195
+ const owned = new Set<string>();
196
+
197
+ // Add all received tokenIds
198
+ for (const e of toLogs) {
199
+ const tokenId = e.args?.tokenId;
200
+ if (!tokenId) continue;
201
+ owned.add(tokenId.toString());
202
+ }
203
+
204
+ // Remove all sent tokenIds
205
+ for (const e of fromLogs) {
206
+ const tokenId = e.args?.tokenId;
207
+ if (!tokenId) continue;
208
+ owned.delete(tokenId.toString());
209
+ }
210
+
211
+ // Convert to BigNumbers
212
+ return Array.from(owned).map((id) => BigNumber.from(id));
213
+ }
136
214
  }
@@ -45,6 +45,7 @@ export class StakeClient {
45
45
  throw new Error("Error - Depositor is in a paused state");
46
46
  }
47
47
 
48
+ if (bal.lt(amountWei)) throw new Error("Insufficient LiqETH balance");
48
49
 
49
50
  // if allowance is less than the requested stake amount, request permission to spend LiqEth
50
51
  if (allowance.lt(amountWei)) {
@@ -8,7 +8,7 @@ import {
8
8
  TrancheSnapshot
9
9
  } from '../../types';
10
10
  import { EthereumContractService } from './contract';
11
- import { preLaunchReceipt } from './types';
11
+ import { preLaunchReceipt, WithdrawReceipt } from './types';
12
12
  import { buildEthereumTrancheSnapshot } from './utils';
13
13
  import { ConvertClient } from './clients/convert.client';
14
14
  import { StakeClient } from './clients/stake.client';
@@ -98,6 +98,31 @@ export class EthereumStakingClient implements IStakingClient {
98
98
  return result.txHash;
99
99
  }
100
100
 
101
+ /**
102
+ * Withdraw native ETH from the liqETH protocol via the liqeth safeBurn function, which burns the LiqETH and adds the user to the withdrawal queue.
103
+ * @param amount Amount in wei (or something convertible to BigNumber).
104
+ * @returns transaction hash
105
+ */
106
+ async loadPendingWithdraws(): Promise<WithdrawReceipt[]> {
107
+ this.ensureUser();
108
+ const address = await this.signer!.getAddress();
109
+
110
+ return await this.receiptClient.fetchWithdrawReceipts(address);
111
+ }
112
+
113
+ /**
114
+ * Withdraw native ETH from the liqETH protocol via the liqeth safeBurn function, which burns the LiqETH and adds the user to the withdrawal queue.
115
+ * @param amount Amount in wei (or something convertible to BigNumber).
116
+ * @returns transaction hash
117
+ */
118
+ async claimWithdraw(tokenId: bigint): Promise<string> {
119
+ this.ensureUser();
120
+
121
+ const tokenIdBigNum = BigNumber.from(tokenId)
122
+ const result = await this.convertClient.claimWithdraw(tokenIdBigNum)
123
+ return result.txHash;
124
+ }
125
+
101
126
 
102
127
  /**
103
128
  * Stake liqETH via DepositManager.
@@ -213,17 +238,14 @@ export class EthereumStakingClient implements IStakingClient {
213
238
  // sharesToTokens(userShares, currentIndex) = userShares * currentIndex / indexScale
214
239
  let estimatedClaim = BigInt(0);
215
240
  let estimatedYield = BigInt(0);
241
+
242
+ if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
243
+ estimatedClaim = (userShares * currentIndex) / indexScale;
244
+ if (estimatedClaim > stakeBalanceBN.toBigInt()) {
245
+ estimatedYield = estimatedClaim - stakeBalanceBN.toBigInt();
246
+ }
247
+ }
216
248
 
217
- // started work on estimating the user's personal APY - not necessary at the moment
218
- // let estimatedAPY: number | null = null;
219
- // if (userShares > BigInt(0) && currentIndex > BigInt(0)) {
220
- // estimatedClaim = (userShares * currentIndex) / indexScale;
221
- // if (estimatedClaim > stakeBalanceBN.toBigInt()) {
222
- // estimatedYield = estimatedClaim - stakeBalanceBN.toBigInt();
223
- // }
224
-
225
- // estimatedAPY = null;
226
- // }
227
249
 
228
250
  const portfolio: Portfolio = {
229
251
  native: {
@@ -110,4 +110,19 @@ export interface preLaunchReceipt {
110
110
  shares: BalanceView,
111
111
  timestamp: string,
112
112
  }
113
+ }
114
+
115
+ export interface ClaimedEvent {
116
+ sender: string;
117
+ ethAmount: BigNumber;
118
+ }
119
+
120
+
121
+ export interface WithdrawReceipt {
122
+ tokenId: bigint;
123
+ receipt: {
124
+ ethAmount: BigNumber;
125
+ ethBalance: BalanceView;
126
+ readyAt: number;
127
+ }
113
128
  }