@wireio/stake 0.3.1 → 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/lib/stake.browser.js +12887 -10017
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +3305 -1364
- package/lib/stake.js +16298 -13436
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +12887 -10017
- package/lib/stake.m.js.map +1 -1
- package/package.json +3 -1
- package/src/assets/solana/idl/liqsol_core.json +2327 -887
- package/src/assets/solana/idl/liqsol_token.json +1 -1
- package/src/assets/solana/idl/transfer_hook.json +192 -0
- package/src/assets/solana/idl/validator_leaderboard.json +147 -4
- package/src/assets/solana/types/liqsol_core.ts +2327 -887
- package/src/assets/solana/types/liqsol_token.ts +1 -1
- package/src/assets/solana/types/transfer_hook.ts +198 -0
- package/src/assets/solana/types/validator_leaderboard.ts +147 -4
- package/src/networks/ethereum/clients/{deposit.client.ts → convert.client.ts} +36 -4
- package/src/networks/ethereum/clients/opp.client.ts +390 -0
- package/src/networks/ethereum/clients/pretoken.client.ts +88 -49
- package/src/networks/ethereum/clients/receipt.client.ts +129 -0
- package/src/networks/ethereum/clients/stake.client.ts +1 -148
- package/src/networks/ethereum/contract.ts +7 -4
- package/src/networks/ethereum/ethereum.ts +44 -70
- package/src/networks/ethereum/types.ts +1 -0
- package/src/networks/ethereum/utils.ts +1 -1
- package/src/networks/solana/clients/deposit.client.ts +154 -8
- package/src/networks/solana/clients/distribution.client.ts +72 -291
- package/src/networks/solana/clients/leaderboard.client.ts +59 -14
- package/src/networks/solana/clients/outpost.client.ts +188 -359
- package/src/networks/solana/clients/token.client.ts +85 -100
- package/src/networks/solana/constants.ts +155 -64
- package/src/networks/solana/solana.ts +273 -154
- package/src/networks/solana/types.ts +532 -71
- package/src/networks/solana/utils.ts +68 -51
- package/src/types.ts +161 -17
- package/src/networks/ethereum/clients/liq.client.ts +0 -47
|
@@ -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:
|
|
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
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { BigNumber } from "ethers";
|
|
2
|
+
import { preLaunchReceipt } from "../types";
|
|
3
|
+
import { EthereumContractService } from "../contract";
|
|
4
|
+
import { ReceiptNFTKind } from "../../../types";
|
|
5
|
+
|
|
6
|
+
export class ReceiptClient {
|
|
7
|
+
|
|
8
|
+
private readonly contractService: EthereumContractService;
|
|
9
|
+
|
|
10
|
+
get contract() { return this.contractService.contract; }
|
|
11
|
+
|
|
12
|
+
constructor(contract: EthereumContractService) {
|
|
13
|
+
this.contractService = contract;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async allReceipts(address: string): Promise<preLaunchReceipt[]> {
|
|
19
|
+
return this.fetchPreLaunchReceipts(address);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async stakeReceipts(address: string): Promise<preLaunchReceipt[]> {
|
|
23
|
+
return this.fetchPreLaunchReceipts(address, ReceiptNFTKind.STAKE);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async pretokenReceipts(address: string): Promise<preLaunchReceipt[]> {
|
|
27
|
+
return this.fetchPreLaunchReceipts(address, ReceiptNFTKind.PRETOKEN_PURCHASE);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param address (string) to fetch receipts for
|
|
35
|
+
* @returns preLaunchReceipt[]
|
|
36
|
+
*/
|
|
37
|
+
async fetchPreLaunchReceipts(address: string, type?: ReceiptNFTKind): Promise<preLaunchReceipt[]> {
|
|
38
|
+
const receiptContract = this.contract.ReceiptNFT;
|
|
39
|
+
|
|
40
|
+
// first figure out which tokenIds this address owns, from events
|
|
41
|
+
const tokenIds = await this.getOwnedTokenIdsFor(address);
|
|
42
|
+
|
|
43
|
+
const results: preLaunchReceipt[] = [];
|
|
44
|
+
|
|
45
|
+
// next fetch on-chain receipt data just for those ids
|
|
46
|
+
for (const idBN of tokenIds) {
|
|
47
|
+
try {
|
|
48
|
+
const receiptData = await receiptContract.getReceipt(idBN);
|
|
49
|
+
|
|
50
|
+
//skip any receipt not of the requested type
|
|
51
|
+
if(type !== undefined && receiptData.kind !== type) continue;
|
|
52
|
+
|
|
53
|
+
results.push({
|
|
54
|
+
tokenId: idBN.toBigInt(),
|
|
55
|
+
receipt: {
|
|
56
|
+
account: receiptData.account,
|
|
57
|
+
currency: receiptData.currency,
|
|
58
|
+
kind: receiptData.kind,
|
|
59
|
+
indexAtMint: receiptData.indexAtMint.toBigInt(),
|
|
60
|
+
principal: {
|
|
61
|
+
amount: receiptData.principal.toBigInt(),
|
|
62
|
+
decimals: 18,
|
|
63
|
+
symbol: "LiqETH"
|
|
64
|
+
},
|
|
65
|
+
shares: {
|
|
66
|
+
amount: receiptData.shares.toBigInt(),
|
|
67
|
+
decimals: 18,
|
|
68
|
+
symbol: "LiqETH"
|
|
69
|
+
},
|
|
70
|
+
timestamp: new Date(Number(receiptData.timestamp.toString()) * 1000).toLocaleString(),
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// in case of any mismatch or race, just skip this id
|
|
75
|
+
console.warn(`Failed to load receipt for tokenId=${idBN.toString()}`, err);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return results;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
private async getOwnedTokenIdsFor(
|
|
87
|
+
owner: string,
|
|
88
|
+
fromBlock = 0,
|
|
89
|
+
toBlock: number | string = "latest"
|
|
90
|
+
): Promise<BigNumber[]> {
|
|
91
|
+
const receiptContract = this.contract.ReceiptNFT;
|
|
92
|
+
|
|
93
|
+
// Logs where address received tokens
|
|
94
|
+
const toLogs = await receiptContract.queryFilter(
|
|
95
|
+
receiptContract.filters.Transfer(null, owner),
|
|
96
|
+
fromBlock,
|
|
97
|
+
toBlock
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Logs where address sent tokens (including burns from owner → 0)
|
|
101
|
+
const fromLogs = await receiptContract.queryFilter(
|
|
102
|
+
receiptContract.filters.Transfer(owner, null),
|
|
103
|
+
fromBlock,
|
|
104
|
+
toBlock
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const owned = new Set<string>();
|
|
108
|
+
|
|
109
|
+
// Add all received tokenIds
|
|
110
|
+
for (const e of toLogs) {
|
|
111
|
+
const tokenId = e.args?.tokenId;
|
|
112
|
+
if (!tokenId) continue;
|
|
113
|
+
owned.add(tokenId.toString());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Remove all sent tokenIds
|
|
117
|
+
for (const e of fromLogs) {
|
|
118
|
+
const tokenId = e.args?.tokenId;
|
|
119
|
+
if (!tokenId) continue;
|
|
120
|
+
owned.delete(tokenId.toString());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Convert to BigNumbers
|
|
124
|
+
return Array.from(owned).map((id) => BigNumber.from(id));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
}
|