@wireio/stake 0.4.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wireio/stake",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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",
@@ -1,10 +1,9 @@
1
- import { BigNumber, ethers, Signer } from "ethers";
2
- import { DepositEvent, DepositResult, WithdrawRequestedEvent, WithdrawResult } from "../types";
1
+ import { BigNumber, ethers } from "ethers";
2
+ import { DepositEvent, DepositResult, SharesBurnedEvent } from "../types";
3
3
  import { EthereumContractService } from "../contract";
4
- import { ChainID } from "@wireio/core";
5
4
  import { formatContractErrors } from "../utils";
6
5
 
7
- export class DepositClient {
6
+ export class ConvertClient {
8
7
 
9
8
  private readonly contractService: EthereumContractService;
10
9
 
@@ -84,7 +83,39 @@ export class DepositClient {
84
83
  }
85
84
 
86
85
 
86
+ public async performWithdraw(signerAddress: string, amountWei: BigNumber): Promise<any> {
87
+ let tx, receipt;
88
+ try {
89
+ tx = await this.contract.LiqEth.safeBurn(signerAddress, amountWei);
90
+ receipt = await tx.wait(1);
91
+ } catch (err: any) {
92
+ let errorObj = formatContractErrors(err);
93
+ throw new Error(errorObj.name ?? errorObj.raw)
94
+ }
95
+
96
+ // Parse SharesBurned event if present
97
+ let event: SharesBurnedEvent | undefined;
98
+ const ev = receipt.events?.find((e) => e.event === 'SharesBurned');
87
99
 
100
+ if (ev && ev.args) {
101
+ const { from, shares, tokenValue } = ev.args;
102
+ event = {
103
+ from,
104
+ shares: BigNumber.from(shares),
105
+ tokenValue: BigNumber.from(tokenValue),
106
+ };
107
+ }
108
+
109
+ return {
110
+ txHash: tx.hash,
111
+ receipt,
112
+ event,
113
+ };
114
+ }
115
+
116
+
117
+
118
+ /*
88
119
  // OLD - this was replaced with LiqEth.safeBurn() on 1/13/26
89
120
  async requestWithdraw(amountWei: BigNumber, signer: Signer, chainId: ChainID): Promise<WithdrawResult> {
90
121
  // deadline is a period of time in the future that the signature is valid for
@@ -153,6 +184,7 @@ export class DepositClient {
153
184
  withdrawRequested,
154
185
  } as WithdrawResult;
155
186
  }
187
+ */
156
188
 
157
189
 
158
190
 
@@ -0,0 +1,390 @@
1
+ import { ethers } from "ethers";
2
+ import { EthereumContractService } from "../contract";
3
+ import { OPPAssertion } from "../../../types";
4
+
5
+
6
+ export class OPPClient {
7
+
8
+
9
+ private readonly decoders: Record<number, Decoder> = {
10
+ 2001: (d) => this.decode2001(d),
11
+ 2002: (d) => this.decode2002(d),
12
+
13
+ 3001: (d) => this.decodeStake3001(d),
14
+ 3002: (d) => this.decodeUnstake3002(d),
15
+ // 3003 not implemented
16
+ 3004: (d) => this.decodeLiqPretoken3004(d),
17
+ 3005: (d) => this.decodePretoken3005(d),
18
+ 3006: (d) => this.decodeYieldPretoken3006(d),
19
+ };
20
+
21
+ private readonly contractService: EthereumContractService;
22
+
23
+ get contract() { return this.contractService.contract; }
24
+
25
+ constructor(contract: EthereumContractService) {
26
+ this.contractService = contract;
27
+ }
28
+
29
+
30
+
31
+ /**
32
+ * Read OPP / Outpost state used by the Depositor to decide whether staking is allowed.
33
+ * Returns various data
34
+ */
35
+ async getStatus(): Promise<any> {
36
+ const depositor = this.contract.Depositor;
37
+ const opp = this.contract.OPP;
38
+
39
+
40
+ const oppAddress: string = await depositor.oppAddress();
41
+ const oppInboundAddress: string = await depositor.oppInboundAddress();
42
+ const prevEpochSent = await opp.prevEpochSent();
43
+
44
+
45
+ const inbound = this.contractService.getReadOnly('OPPInbound');
46
+
47
+ // Query useful getters
48
+ const nextEpochBN: any = await inbound.nextEpochNum();
49
+ const pendingEpochRaw: any = await inbound.pendingEpoch();
50
+ const pendingMessageCount: any = await inbound.pendingMessageCount();
51
+ const previousEpochHash: string = await inbound.previousEpochHash();
52
+ const nextEpochNum = (nextEpochBN && typeof nextEpochBN.toNumber === 'function') ? nextEpochBN.toNumber() : Number(nextEpochBN || 0);
53
+
54
+ const pendingEpoch = (pendingEpochRaw && pendingEpochRaw.epochNumber !== undefined)
55
+ ? {
56
+ epochNumber: (pendingEpochRaw.epochNumber && typeof pendingEpochRaw.epochNumber.toNumber === 'function') ? pendingEpochRaw.epochNumber.toNumber() : Number(pendingEpochRaw.epochNumber || 0),
57
+ timestamp: (pendingEpochRaw.timestamp && typeof pendingEpochRaw.timestamp.toNumber === 'function') ? pendingEpochRaw.timestamp.toNumber() : Number(pendingEpochRaw.timestamp || 0),
58
+ prevEpochHash: pendingEpochRaw.prevEpochHash,
59
+ merkleRoot: pendingEpochRaw.merkleRoot,
60
+ firstMessageID: pendingEpochRaw.firstMessageID,
61
+ lastMessageID: pendingEpochRaw.lastMessageID,
62
+ }
63
+ : null;
64
+
65
+ const pendingMessagesBN = pendingMessageCount;
66
+ const pendingMessages = (pendingMessagesBN && typeof pendingMessagesBN.toString === 'function') ? pendingMessagesBN.toString() : String(pendingMessagesBN || '0');
67
+
68
+ const hasPendingMessages = (pendingMessagesBN && typeof pendingMessagesBN.gt === 'function') ? pendingMessagesBN.gt(0) : (Number(pendingMessages) > 0);
69
+
70
+ return {
71
+ oppAddress,
72
+ prevEpochSent,
73
+ oppInboundAddress,
74
+ nextEpochNum,
75
+ pendingEpoch,
76
+ pendingMessageCount: pendingMessages,
77
+ previousEpochHash,
78
+ hasPendingMessages,
79
+ raw: {
80
+ nextEpochBN,
81
+ pendingEpochRaw,
82
+ pendingMessageCount: pendingMessagesBN,
83
+ },
84
+ };
85
+ }
86
+
87
+
88
+
89
+
90
+ /**
91
+ * Fetches all OPPMessage events and flattens all assertions into a single OPPAssertion[] array.
92
+ */
93
+ async getMessages(address: string): Promise<OPPAssertion[]> {
94
+ const oppMessageFilter = this.contract.OPP.filters.OPPMessage();
95
+ const events = await this.contract.OPP.queryFilter(oppMessageFilter, 0, "latest");
96
+ const allAssertions: OPPAssertion[] = [];
97
+ for (const event of events) {
98
+ const assertions = await this.extractAssertionsFromEvent(event);
99
+ allAssertions.push(...assertions);
100
+ }
101
+
102
+ // ! Current implementation is not ideal - no current way to filter OPP Messages by a single user
103
+ const normalized = address ? address.toLowerCase() : null;
104
+ const filtered = allAssertions.filter(a =>
105
+ (a.from && a.from.toLowerCase() === normalized) ||
106
+ (a.to && a.to.toLowerCase() === normalized)
107
+ );
108
+ return filtered.reverse();
109
+ }
110
+
111
+
112
+
113
+
114
+
115
+ /**
116
+ * Extracts all OPPAssertions from a single OPPMessage event, attaching event metadata to each.
117
+ */
118
+ private async extractAssertionsFromEvent(event: any): Promise<OPPAssertion[]> {
119
+ const header = event.args.header;
120
+ const payloadAssertions = event.args.payload.assertions;
121
+
122
+ const timestamp = header.timestamp ? Number(header.timestamp) : null;
123
+ const from = event.address || null;
124
+ const txHash = event.transactionHash;
125
+ const chain = 'ETH';
126
+
127
+ const assertionList: OPPAssertion[] = [];
128
+
129
+ if (payloadAssertions && payloadAssertions.length > 0) {
130
+ for (let a of payloadAssertions) {
131
+ const assertionType = a[0] as number;
132
+ const assertionData = a[1] as string;
133
+
134
+
135
+ let type: OPPAssertion["type"] = "unknown";
136
+ let data: any = {};
137
+ let to: string | null = null;
138
+
139
+ const decoder = this.decoders[assertionType];
140
+
141
+ if (decoder) {
142
+ try {
143
+ const decoded = decoder(assertionData);
144
+ type = decoded.type;
145
+ data = decoded.data;
146
+ to = decoded.to ?? null;
147
+ } catch (e: any) {
148
+ // catch a bad assertion so it doesn't fail the entire function
149
+ type = "unknown";
150
+ data = {
151
+ decodeError: e?.message ?? String(e),
152
+ assertionType,
153
+ rawHex: assertionData,
154
+ };
155
+ }
156
+ } else {
157
+ // Unknown assertion type
158
+ data = { assertionType, rawHex: assertionData };
159
+ }
160
+
161
+ assertionList.push({
162
+ type,
163
+ data,
164
+ chain,
165
+ timestamp,
166
+ from,
167
+ to,
168
+ txHash,
169
+ raw: event,
170
+ });
171
+ }
172
+ }
173
+ return assertionList;
174
+ }
175
+
176
+
177
+
178
+ private decode2001(assertionData: string): DecoderResult {
179
+ const hex = this.strip0x(assertionData);
180
+
181
+ let cursor = 0;
182
+ const r1 = this.readAddress(hex, cursor); cursor = r1.next;
183
+ const r2 = this.readAddress(hex, cursor); cursor = r2.next;
184
+ const r3 = this.readBytes32(hex, cursor); cursor = r3.next;
185
+ const r4 = this.readUint256LE(hex, cursor); cursor = r4.next;
186
+ const r5 = this.readUint64LE(hex, cursor); cursor = r5.next;
187
+
188
+ const data = {
189
+ actor: r1.addr,
190
+ owner: r2.addr,
191
+ bondLevelId: r3.value,
192
+ tokenId: r4.value,
193
+ bondedAt: r5.value,
194
+ };
195
+
196
+ return { type: "bonded_actor", data, to: r1.addr };
197
+ }
198
+ private decode2002(assertionData: string): DecoderResult {
199
+ const hex = this.strip0x(assertionData);
200
+
201
+ let cursor = 0;
202
+ const r1 = this.readAddress(hex, cursor); cursor = r1.next;
203
+ const r2 = this.readBytes32(hex, cursor); cursor = r2.next;
204
+ const r3 = this.readUint256LE(hex, cursor); cursor = r3.next;
205
+ const r4 = this.readUint64LE(hex, cursor); cursor = r4.next;
206
+
207
+ const data = {
208
+ actor: r1.addr,
209
+ bondLevelId: r2.value,
210
+ tokenId: r3.value,
211
+ unbondedAt: r4.value,
212
+ };
213
+
214
+ return { type: "unbonded_actor", data, to: r1.addr };
215
+ }
216
+
217
+
218
+ private decodeStake3001(assertionData: string): DecoderResult {
219
+ const { addr, nums } = this.decodeAddrPlusU256(assertionData, 3);
220
+ const [principal, shares, indexAtMint] = nums;
221
+
222
+ return {
223
+ type: "stake",
224
+ data: {
225
+ staker: addr,
226
+ principal: ethers.utils.formatUnits(principal, 18),
227
+ shares: ethers.utils.formatUnits(shares, 18),
228
+ indexAtMint
229
+ },
230
+ to: addr,
231
+ };
232
+ }
233
+ private decodeUnstake3002(assertionData: string): DecoderResult {
234
+ const { addr, nums } = this.decodeAddrPlusU256(assertionData, 4);
235
+ const [unstakeAmount, shares, indexAtBurn, tokenId] = nums;
236
+
237
+ return {
238
+ type: "unstake",
239
+ data: {
240
+ unstaker: addr,
241
+ unstakeAmount: ethers.utils.formatUnits(unstakeAmount, 18),
242
+ shares,
243
+ indexAtBurn,
244
+ tokenId
245
+ },
246
+ to: addr,
247
+ };
248
+ }
249
+ private decodeLiqPretoken3004(assertionData: string): DecoderResult {
250
+ const { addr, nums } = this.decodeAddrPlusU256(assertionData, 3);
251
+ const [principal, shares, indexAtMint] = nums;
252
+
253
+ return {
254
+ type: "liq_pretoken_purchase",
255
+ data: {
256
+ purchaser: addr,
257
+ principal: ethers.utils.formatUnits(principal, 18),
258
+ shares: ethers.utils.formatUnits(shares, 18),
259
+ indexAtMint
260
+ },
261
+ to: addr,
262
+ };
263
+ }
264
+ private decodePretoken3005(assertionData: string): DecoderResult {
265
+ const { addr, nums } = this.decodeAddrPlusU256(assertionData, 3);
266
+ const [ethInWei, usdValue, pretokensOut] = nums;
267
+
268
+ return {
269
+ type: "pretoken_purchase",
270
+ data: {
271
+ buyer: addr,
272
+ ethInWei,
273
+ ethIn: ethers.utils.formatUnits(ethInWei, 18),
274
+ usdValue: ethers.utils.formatUnits(usdValue, 18),
275
+ pretokensOut: ethers.utils.formatUnits(pretokensOut, 18),
276
+ },
277
+ to: addr,
278
+ };
279
+ }
280
+ private decodeYieldPretoken3006(assertionData: string): DecoderResult {
281
+ const { addr, nums } = this.decodeAddrPlusU256(assertionData, 2);
282
+ const [principal, indexAtMint] = nums;
283
+
284
+ return {
285
+ type: "yield_pretoken_purchase",
286
+ data: { purchaser: addr, principal, indexAtMint },
287
+ to: addr,
288
+ };
289
+ }
290
+
291
+
292
+
293
+
294
+ /**
295
+ *
296
+ * @param hex string to check
297
+ * @returns the passed string, without a prefixed 0x if it existed
298
+ */
299
+ private strip0x(hex: string): string {
300
+ return hex.startsWith("0x") ? hex.slice(2) : hex;
301
+ }
302
+
303
+ private decodeAddrPlusU256(assertionData: string, count: number): { addr: string; nums: string[] } {
304
+ const hex = this.strip0x(assertionData);
305
+ let cursor = 0;
306
+
307
+ const { addr, next } = this.readAddress(hex, cursor);
308
+ cursor = next;
309
+
310
+ const nums: string[] = [];
311
+ for (let i = 0; i < count; i++) {
312
+ const r = this.readUint256LE(hex, cursor);
313
+ nums.push(r.value);
314
+ cursor = r.next;
315
+ }
316
+ return { addr, nums };
317
+ }
318
+
319
+ private uint256FromLE(hexNo0x: string): ethers.BigNumber {
320
+ if (hexNo0x.length !== 64) {
321
+ throw new Error(`uint256 LE must be 32 bytes (64 hex chars), got ${hexNo0x.length}`);
322
+ }
323
+ const bytes = ethers.utils.arrayify("0x" + hexNo0x);
324
+ const reversed = Uint8Array.from(bytes).reverse(); // little -> big endian
325
+ return ethers.BigNumber.from(ethers.utils.hexlify(reversed));
326
+ }
327
+
328
+
329
+ /**
330
+ * Parses "0x14 + <addr>" and returns addr + next cursor (in hex chars, excluding 0x)
331
+ * @param payload full payload
332
+ * @param cursor place to start
333
+ * @returns string
334
+ */
335
+ private readAddress(payload: string, cursor: number): { addr: string; next: number } {
336
+ const lenByteHex = payload.slice(cursor, cursor + 2);
337
+ const len = parseInt(lenByteHex, 16);
338
+ if (!Number.isFinite(len) || len <= 0) throw new Error(`Bad address len byte: ${lenByteHex}`);
339
+ const start = cursor + 2;
340
+ const end = start + len * 2;
341
+ const addrHex = payload.slice(start, end);
342
+ if (addrHex.length !== len * 2) throw new Error(`Truncated address bytes`);
343
+ return { addr: ethers.utils.getAddress("0x" + addrHex), next: end };
344
+ }
345
+
346
+ /**
347
+ * Reads 32-byte uint256 little-endian from payload at cursor (hex chars, excluding 0x)
348
+ * @param payload
349
+ * @param cursor
350
+ * @returns
351
+ */
352
+ private readUint256LE(payload: string, cursor: number): { value: string; next: number } {
353
+ const word = payload.slice(cursor, cursor + 64);
354
+ if (word.length !== 64) throw new Error(`Truncated uint256 at ${cursor}`);
355
+ return { value: this.uint256FromLE(word).toString(), next: cursor + 64 };
356
+ }
357
+
358
+ /**
359
+ * Reads 8-byte uint64 little-endian from payload at cursor (hex chars, excluding 0x)
360
+ * @param payload
361
+ * @param cursor
362
+ * @returns
363
+ */
364
+ private readUint64LE(payload: string, cursor: number): { value: number; next: number } {
365
+ const word = payload.slice(cursor, cursor + 16);
366
+ if (word.length !== 16) throw new Error(`Truncated uint64 at ${cursor}`);
367
+ const bytes = ethers.utils.arrayify("0x" + word);
368
+ const reversed = Uint8Array.from(bytes).reverse();
369
+ const bn = ethers.BigNumber.from(ethers.utils.hexlify(reversed));
370
+ return { value: bn.toNumber(), next: cursor + 16 };
371
+ }
372
+
373
+ /**
374
+ * Reads bytes32 from payload at cursor (hex chars, excluding 0x)
375
+ * @param payload
376
+ * @param cursor
377
+ * @returns
378
+ */
379
+ private readBytes32(payload: string, cursor: number): { value: string; next: number } {
380
+ const word = payload.slice(cursor, cursor + 64);
381
+ if (word.length !== 64) throw new Error(`Truncated bytes32 at ${cursor}`);
382
+ return { value: "0x" + word, next: cursor + 64 };
383
+ }
384
+
385
+
386
+ }
387
+
388
+
389
+ type DecoderResult = { type: OPPAssertion["type"]; data: any; to?: string | null };
390
+ type Decoder = (assertionData: string) => DecoderResult;
@@ -13,61 +13,50 @@ export class PretokenClient {
13
13
 
14
14
 
15
15
 
16
- /**
17
- * Purchase pretokens by sending ETH to the Depositor.purchasePretokensWithETH(buyer) payable function.
18
- * Returns txHash, receipt and parsed PurchasedWithETH event (if present).
19
- */
20
- // async purchasePretokensWithETH(amountWei: BigNumber, buyer: string): Promise<any> {
21
- // // attempt a simulation of the purchase call
22
- // try {
23
- // await this.contract.Depositor.callStatic.purchasePretokensWithETH(buyer, { value: amountWei });
24
- // } catch (err: any) {
25
- // let errorObj = formatContractErrors(err);
26
- // throw new Error(errorObj.name ?? errorObj.raw)
27
- // }
28
-
29
- // // attempt the real purchase call
30
- // let tx, receipt;
31
- // try {
32
- // tx = await this.contract.Depositor.purchasePretokensWithETH(buyer, { value: amountWei });
33
- // receipt = await tx.wait(1);
34
- // } catch (err: any) {
35
- // let errorObj = formatContractErrors(err);
36
- // throw new Error(errorObj.name ?? errorObj.raw)
37
- // }
38
-
39
-
40
- // // Attempt to parse PurchasedWithETH event
41
- // let purchased: any | undefined;
42
- // const ev = receipt.events?.find((e) => e.event === 'PurchasedWithETH' || e.event === 'PurchasedWithETH(address,uint256,uint256,uint256)');
43
-
44
- // if (ev && ev.args) {
45
- // // event signature: PurchasedWithETH(address indexed user, uint256 ethIn, uint256 shares, uint256 tokenId)
46
- // const { user, ethIn, shares, tokenId } = ev.args as any;
47
-
48
- // purchased = {
49
- // buyer: user,
50
- // amount: BigNumber.from(ethIn),
51
- // shares: BigNumber.from(shares),
52
- // tokenId: BigNumber.from(tokenId),
53
- // };
54
- // }
55
-
56
- // return {
57
- // txHash: tx.hash,
58
- // receipt,
59
- // purchased,
60
- // };
61
- // }
62
-
63
16
  /**
64
17
  * Purchase pretokens by transferring liqETH into the pool (ERC-20) and calling Depositor.purchasePretokensWithLiqETH(amountLiq, buyer).
65
18
  * Returns txHash, receipt and parsed PurchasedWithLiqETH event (if present).
66
19
  */
67
- async purchasePretokensWithLiqETH(amountLiq: BigNumber, buyer: string): Promise<any> {
20
+ async purchasePretokensWithLiqETH(amountLiq: bigint, buyer: string): Promise<any> {
21
+
22
+ const bal = await this.contract.LiqEth.balanceOf(buyer);
23
+ const paused = await this.contract.Depositor.paused();
24
+ if(paused) {
25
+ throw new Error("Error - Depositor is in a paused state");
26
+ }
27
+
28
+ // if current liq balance is less than the requested buy amount, throw an error
29
+ if (bal.lt(amountLiq)) {
30
+ throw new Error(`Balance insufficient for pre-token purchase`);
31
+ }
32
+
33
+ //check that the contract has allowance for the token
34
+ const depositorAddr = this.contract.Depositor.address;
35
+ const allowance = await this.contract.LiqEth.allowance(buyer, depositorAddr);
36
+
37
+ // if allowance is less than the requested stake amount, request permission to spend LiqEth
38
+ if (allowance.lt(amountLiq)) {
39
+ const liqWrite = this.contractService.getWrite('LiqEth');
40
+
41
+ // currently requested unlimited amount - potentially change to only request up to the current amount?
42
+ const approveAmount = ethers.constants.MaxUint256;
43
+
44
+ console.warn(`allowance insufficient (${allowance.toString()} < ${amountLiq.toString()}); sending approve(${depositorAddr}, ${approveAmount.toString()})`);
45
+
46
+ const approveTx = await liqWrite.approve(depositorAddr, approveAmount);
47
+ await approveTx.wait(1);
48
+
49
+ // re-read allowance to ensure approval succeeded
50
+ const newAllowance = await this.contract.LiqEth.allowance(buyer, depositorAddr);
51
+ if (newAllowance.lt(amountLiq)) {
52
+ throw new Error('Approval failed or allowance still insufficient after approve');
53
+ }
54
+ }
55
+
68
56
  // attempt a simulation of the purchase call
69
57
  try {
70
- await this.contract.Depositor.callStatic.purchasePretokensWithLiqETH(amountLiq, buyer);
58
+ const amountLiqBN = BigNumber.from(amountLiq)
59
+ await this.contract.Depositor.callStatic.purchasePretokensWithLiqETH(amountLiqBN, buyer);
71
60
  } catch (err: any) {
72
61
  let errorObj = formatContractErrors(err);
73
62
 
@@ -124,4 +113,54 @@ export class PretokenClient {
124
113
 
125
114
 
126
115
 
116
+
117
+ /**
118
+ * Purchase pretokens by sending ETH to the Depositor.purchasePretokensWithETH(buyer) payable function.
119
+ * Returns txHash, receipt and parsed PurchasedWithETH event (if present).
120
+ async purchasePretokensWithETH(amountWei: BigNumber, buyer: string): Promise<any> {
121
+ // attempt a simulation of the purchase call
122
+ try {
123
+ await this.contract.Depositor.callStatic.purchasePretokensWithETH(buyer, { value: amountWei });
124
+ } catch (err: any) {
125
+ let errorObj = formatContractErrors(err);
126
+ throw new Error(errorObj.name ?? errorObj.raw)
127
+ }
128
+
129
+ // attempt the real purchase call
130
+ let tx, receipt;
131
+ try {
132
+ tx = await this.contract.Depositor.purchasePretokensWithETH(buyer, { value: amountWei });
133
+ receipt = await tx.wait(1);
134
+ } catch (err: any) {
135
+ let errorObj = formatContractErrors(err);
136
+ throw new Error(errorObj.name ?? errorObj.raw)
137
+ }
138
+
139
+
140
+ // Attempt to parse PurchasedWithETH event
141
+ let purchased: any | undefined;
142
+ const ev = receipt.events?.find((e) => e.event === 'PurchasedWithETH' || e.event === 'PurchasedWithETH(address,uint256,uint256,uint256)');
143
+
144
+ if (ev && ev.args) {
145
+ // event signature: PurchasedWithETH(address indexed user, uint256 ethIn, uint256 shares, uint256 tokenId)
146
+ const { user, ethIn, shares, tokenId } = ev.args as any;
147
+
148
+ purchased = {
149
+ buyer: user,
150
+ amount: BigNumber.from(ethIn),
151
+ shares: BigNumber.from(shares),
152
+ tokenId: BigNumber.from(tokenId),
153
+ };
154
+ }
155
+
156
+ return {
157
+ txHash: tx.hash,
158
+ receipt,
159
+ purchased,
160
+ };
161
+ }
162
+ */
163
+
164
+
165
+
127
166
  }