@glowlabs-org/utils 0.2.155 → 0.2.157

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.
@@ -0,0 +1,547 @@
1
+ import {
2
+ type WalletClient,
3
+ type PublicClient,
4
+ type Address,
5
+ formatEther,
6
+ } from "viem";
7
+ import { REWARDS_KERNEL_ABI } from "../abis/rewardKernelABI";
8
+ import { getAddresses } from "../../constants/addresses";
9
+ import {
10
+ parseViemError,
11
+ waitForViemTransactionWithRetry,
12
+ } from "../../utils/transaction-utils";
13
+ import {
14
+ sentryAddBreadcrumb,
15
+ sentryCaptureException,
16
+ } from "../../utils/sentry";
17
+
18
+ export enum RewardsKernelError {
19
+ CONTRACT_NOT_AVAILABLE = "Contract not available",
20
+ SIGNER_NOT_AVAILABLE = "Signer not available",
21
+ UNKNOWN_ERROR = "Unknown error",
22
+ INVALID_PARAMETERS = "Invalid parameters",
23
+ ALREADY_CLAIMED = "Already claimed from this nonce",
24
+ NOT_FINALIZED = "Nonce not yet finalized",
25
+ NONCE_REJECTED = "Cannot claim from rejected nonce",
26
+ }
27
+
28
+ // Token and amount structure matching the contract
29
+ export interface TokenAndAmount {
30
+ token: `0x${string}`;
31
+ amount: bigint;
32
+ }
33
+
34
+ // Reward metadata structure
35
+ export interface RewardMeta {
36
+ merkleRoot: string;
37
+ pushTimestamp: number;
38
+ rejected: boolean;
39
+ }
40
+
41
+ // Parameters for claiming a payout
42
+ export interface ClaimPayoutParams {
43
+ nonce: bigint;
44
+ proof: `0x${string}`[]; // bytes32[] as hex strings
45
+ tokensAndAmounts: TokenAndAmount[];
46
+ from: `0x${string}`; // Address providing the tokens
47
+ to: `0x${string}`; // Address receiving the tokens
48
+ isGuardedToken: boolean[]; // Which tokens are guarded
49
+ toCounterfactual: boolean[]; // Whether to send to counterfactual wallet
50
+ }
51
+
52
+ // Type-guard style helper to ensure a wallet client exists
53
+ function assertWalletClient(
54
+ maybeWalletClient: WalletClient | undefined
55
+ ): asserts maybeWalletClient is WalletClient {
56
+ if (!maybeWalletClient) {
57
+ throw new Error(RewardsKernelError.SIGNER_NOT_AVAILABLE);
58
+ }
59
+ if (!maybeWalletClient.account) {
60
+ throw new Error("Wallet client must have an account");
61
+ }
62
+ }
63
+
64
+ export function useRewardsKernel(
65
+ walletClient: WalletClient | undefined,
66
+ publicClient: PublicClient | undefined,
67
+ CHAIN_ID: number
68
+ ) {
69
+ // Use dynamic addresses based on chain configuration
70
+ const ADDRESSES = getAddresses(CHAIN_ID);
71
+
72
+ // Framework-agnostic processing flag
73
+ let isProcessing = false;
74
+ const setIsProcessing = (value: boolean) => {
75
+ isProcessing = value;
76
+ };
77
+
78
+ // Helper to assert public client is available
79
+ function assertPublicClient(
80
+ maybePublicClient: PublicClient | undefined
81
+ ): asserts maybePublicClient is PublicClient {
82
+ if (!maybePublicClient) {
83
+ throw new Error("Public client not available");
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Claim payout from a finalized nonce
89
+ * @param params Parameters for claiming the payout
90
+ */
91
+ async function claimPayout(params: ClaimPayoutParams): Promise<string> {
92
+ assertWalletClient(walletClient);
93
+ assertPublicClient(publicClient);
94
+
95
+ try {
96
+ setIsProcessing(true);
97
+
98
+ sentryAddBreadcrumb({
99
+ category: "rewards-kernel",
100
+ message: "claimPayout.start",
101
+ level: "info",
102
+ data: {
103
+ chainId: walletClient?.chain?.id,
104
+ contract: ADDRESSES.REWARDS_KERNEL,
105
+ nonce: params.nonce.toString(),
106
+ to: params.to,
107
+ from: params.from,
108
+ tokensCount: params.tokensAndAmounts.length,
109
+ },
110
+ });
111
+
112
+ const {
113
+ nonce,
114
+ proof,
115
+ tokensAndAmounts,
116
+ from,
117
+ to,
118
+ isGuardedToken,
119
+ toCounterfactual,
120
+ } = params;
121
+
122
+ // Validate parameters
123
+ if (!proof || proof.length === 0) {
124
+ throw new Error("Merkle proof is required");
125
+ }
126
+
127
+ if (!tokensAndAmounts || tokensAndAmounts.length === 0) {
128
+ throw new Error("Tokens and amounts are required");
129
+ }
130
+
131
+ if (isGuardedToken.length !== tokensAndAmounts.length) {
132
+ throw new Error(
133
+ "isGuardedToken array length must match tokensAndAmounts"
134
+ );
135
+ }
136
+
137
+ if (toCounterfactual.length !== tokensAndAmounts.length) {
138
+ throw new Error(
139
+ "toCounterfactual array length must match tokensAndAmounts"
140
+ );
141
+ }
142
+
143
+ if (!from || !to) {
144
+ throw new Error(RewardsKernelError.INVALID_PARAMETERS);
145
+ }
146
+
147
+ // Check if already claimed
148
+ const owner = walletClient.account?.address;
149
+ if (!owner) {
150
+ throw new Error("No account found in wallet client");
151
+ }
152
+
153
+ const alreadyClaimed = await isClaimed(owner, nonce);
154
+ if (alreadyClaimed) {
155
+ throw new Error(RewardsKernelError.ALREADY_CLAIMED);
156
+ }
157
+
158
+ // Check if finalized
159
+ const finalized = await isFinalized(nonce);
160
+ if (!finalized) {
161
+ throw new Error(RewardsKernelError.NOT_FINALIZED);
162
+ }
163
+
164
+ // Run a simulation first to surface any revert reason
165
+ try {
166
+ await publicClient.simulateContract({
167
+ address: ADDRESSES.REWARDS_KERNEL as Address,
168
+ abi: REWARDS_KERNEL_ABI,
169
+ functionName: "claimPayout",
170
+ args: [
171
+ nonce,
172
+ proof as `0x${string}`[],
173
+ tokensAndAmounts as readonly {
174
+ token: `0x${string}`;
175
+ amount: bigint;
176
+ }[],
177
+ from as Address,
178
+ to as Address,
179
+ isGuardedToken,
180
+ toCounterfactual,
181
+ ],
182
+ account: walletClient.account!,
183
+ });
184
+ } catch (simulationError) {
185
+ sentryCaptureException(simulationError, {
186
+ action: "claimPayout.simulate",
187
+ chainId: walletClient?.chain?.id,
188
+ contract: ADDRESSES.REWARDS_KERNEL,
189
+ nonce: nonce.toString(),
190
+ });
191
+ throw new Error(parseViemError(simulationError));
192
+ }
193
+
194
+ // Execute the transaction
195
+ const hash = await walletClient.writeContract({
196
+ address: ADDRESSES.REWARDS_KERNEL as Address,
197
+ abi: REWARDS_KERNEL_ABI,
198
+ functionName: "claimPayout",
199
+ args: [
200
+ nonce,
201
+ proof as `0x${string}`[],
202
+ tokensAndAmounts as readonly {
203
+ token: `0x${string}`;
204
+ amount: bigint;
205
+ }[],
206
+ from as Address,
207
+ to as Address,
208
+ isGuardedToken,
209
+ toCounterfactual,
210
+ ],
211
+ chain: walletClient.chain,
212
+ account: walletClient.account!,
213
+ });
214
+
215
+ await waitForViemTransactionWithRetry(publicClient, hash);
216
+
217
+ return hash;
218
+ } catch (error) {
219
+ sentryCaptureException(error, {
220
+ action: "claimPayout",
221
+ chainId: walletClient?.chain?.id,
222
+ contract: ADDRESSES.REWARDS_KERNEL,
223
+ nonce: params.nonce.toString(),
224
+ from: params.from,
225
+ to: params.to,
226
+ });
227
+ throw new Error(parseViemError(error));
228
+ } finally {
229
+ setIsProcessing(false);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get reward metadata for a specific nonce
235
+ * @param nonce The nonce to query
236
+ */
237
+ async function getRewardMeta(nonce: bigint): Promise<RewardMeta> {
238
+ assertPublicClient(publicClient);
239
+
240
+ try {
241
+ const result = (await publicClient.readContract({
242
+ address: ADDRESSES.REWARDS_KERNEL as Address,
243
+ abi: REWARDS_KERNEL_ABI,
244
+ functionName: "getRewardMeta",
245
+ args: [nonce],
246
+ })) as any;
247
+
248
+ return {
249
+ merkleRoot: result[0],
250
+ pushTimestamp: Number(result[1]),
251
+ rejected: result[2],
252
+ };
253
+ } catch (error) {
254
+ throw new Error(parseViemError(error));
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Get the maximum claimable amount for a token at a specific nonce
260
+ * @param nonce The nonce to query
261
+ * @param token The token address
262
+ */
263
+ async function getMaxReward(
264
+ nonce: bigint,
265
+ token: `0x${string}`
266
+ ): Promise<bigint> {
267
+ assertPublicClient(publicClient);
268
+
269
+ try {
270
+ const result = (await publicClient.readContract({
271
+ address: ADDRESSES.REWARDS_KERNEL as Address,
272
+ abi: REWARDS_KERNEL_ABI,
273
+ functionName: "getMaxReward",
274
+ args: [nonce, token as Address],
275
+ })) as bigint;
276
+
277
+ return result;
278
+ } catch (error) {
279
+ throw new Error(parseViemError(error));
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Get the amount already claimed for a token at a specific nonce
285
+ * @param nonce The nonce to query
286
+ * @param token The token address
287
+ */
288
+ async function getAmountClaimed(
289
+ nonce: bigint,
290
+ token: `0x${string}`
291
+ ): Promise<bigint> {
292
+ assertPublicClient(publicClient);
293
+
294
+ try {
295
+ const result = (await publicClient.readContract({
296
+ address: ADDRESSES.REWARDS_KERNEL as Address,
297
+ abi: REWARDS_KERNEL_ABI,
298
+ functionName: "getAmountClaimed",
299
+ args: [nonce, token as Address],
300
+ })) as bigint;
301
+
302
+ return result;
303
+ } catch (error) {
304
+ throw new Error(parseViemError(error));
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Check if a nonce is finalized and claimable
310
+ * @param nonce The nonce to check
311
+ */
312
+ async function isFinalized(nonce: bigint): Promise<boolean> {
313
+ assertPublicClient(publicClient);
314
+
315
+ try {
316
+ const result = (await publicClient.readContract({
317
+ address: ADDRESSES.REWARDS_KERNEL as Address,
318
+ abi: REWARDS_KERNEL_ABI,
319
+ functionName: "isFinalized",
320
+ args: [nonce],
321
+ })) as boolean;
322
+
323
+ return result;
324
+ } catch (error) {
325
+ throw new Error(parseViemError(error));
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Check if a user has already claimed from a specific nonce
331
+ * @param user The user address
332
+ * @param nonce The nonce to check
333
+ */
334
+ async function isClaimed(
335
+ user: `0x${string}`,
336
+ nonce: bigint
337
+ ): Promise<boolean> {
338
+ assertPublicClient(publicClient);
339
+
340
+ try {
341
+ const result = (await publicClient.readContract({
342
+ address: ADDRESSES.REWARDS_KERNEL as Address,
343
+ abi: REWARDS_KERNEL_ABI,
344
+ functionName: "isClaimed",
345
+ args: [user as Address, nonce],
346
+ })) as boolean;
347
+
348
+ return result;
349
+ } catch (error) {
350
+ throw new Error(parseViemError(error));
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Get the next nonce that will be used for posting
356
+ */
357
+ async function getNextPostNonce(): Promise<bigint> {
358
+ assertPublicClient(publicClient);
359
+
360
+ try {
361
+ const result = (await publicClient.readContract({
362
+ address: ADDRESSES.REWARDS_KERNEL as Address,
363
+ abi: REWARDS_KERNEL_ABI,
364
+ functionName: "$nextPostNonce",
365
+ args: [],
366
+ })) as bigint;
367
+
368
+ return result;
369
+ } catch (error) {
370
+ throw new Error(parseViemError(error));
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Get the finality period in seconds
376
+ */
377
+ async function getFinality(): Promise<bigint> {
378
+ assertPublicClient(publicClient);
379
+
380
+ try {
381
+ const result = (await publicClient.readContract({
382
+ address: ADDRESSES.REWARDS_KERNEL as Address,
383
+ abi: REWARDS_KERNEL_ABI,
384
+ functionName: "FINALITY",
385
+ args: [],
386
+ })) as bigint;
387
+
388
+ return result;
389
+ } catch (error) {
390
+ throw new Error(parseViemError(error));
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Get the foundation multisig address
396
+ */
397
+ async function getFoundationMultisig(): Promise<string> {
398
+ assertPublicClient(publicClient);
399
+
400
+ try {
401
+ const result = (await publicClient.readContract({
402
+ address: ADDRESSES.REWARDS_KERNEL as Address,
403
+ abi: REWARDS_KERNEL_ABI,
404
+ functionName: "FOUNDATION_MULTISIG",
405
+ args: [],
406
+ })) as string;
407
+
408
+ return result;
409
+ } catch (error) {
410
+ throw new Error(parseViemError(error));
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Get the rejection multisig address
416
+ */
417
+ async function getRejectionMultisig(): Promise<string> {
418
+ assertPublicClient(publicClient);
419
+
420
+ try {
421
+ const result = (await publicClient.readContract({
422
+ address: ADDRESSES.REWARDS_KERNEL as Address,
423
+ abi: REWARDS_KERNEL_ABI,
424
+ functionName: "REJECTION_MULTISIG",
425
+ args: [],
426
+ })) as string;
427
+
428
+ return result;
429
+ } catch (error) {
430
+ throw new Error(parseViemError(error));
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Get the counterfactual holder factory address
436
+ */
437
+ async function getCFHFactory(): Promise<string> {
438
+ assertPublicClient(publicClient);
439
+
440
+ try {
441
+ const result = (await publicClient.readContract({
442
+ address: ADDRESSES.REWARDS_KERNEL as Address,
443
+ abi: REWARDS_KERNEL_ABI,
444
+ functionName: "CFH_FACTORY",
445
+ args: [],
446
+ })) as string;
447
+
448
+ return result;
449
+ } catch (error) {
450
+ throw new Error(parseViemError(error));
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Estimate gas for claiming a payout
456
+ * @param params Parameters for claiming the payout
457
+ * @param ethPriceInUSD Current ETH price in USD (for cost estimation)
458
+ */
459
+ async function estimateGasForClaimPayout(
460
+ params: ClaimPayoutParams,
461
+ ethPriceInUSD: number | null
462
+ ): Promise<string> {
463
+ assertWalletClient(walletClient);
464
+ assertPublicClient(publicClient);
465
+
466
+ try {
467
+ const {
468
+ nonce,
469
+ proof,
470
+ tokensAndAmounts,
471
+ from,
472
+ to,
473
+ isGuardedToken,
474
+ toCounterfactual,
475
+ } = params;
476
+
477
+ const gasPrice = await publicClient.getGasPrice();
478
+ if (!gasPrice) {
479
+ throw new Error("Could not fetch gas price to estimate cost.");
480
+ }
481
+
482
+ const estimatedGas = await publicClient.estimateContractGas({
483
+ address: ADDRESSES.REWARDS_KERNEL as Address,
484
+ abi: REWARDS_KERNEL_ABI,
485
+ functionName: "claimPayout",
486
+ args: [
487
+ nonce,
488
+ proof as `0x${string}`[],
489
+ tokensAndAmounts as readonly {
490
+ token: `0x${string}`;
491
+ amount: bigint;
492
+ }[],
493
+ from as Address,
494
+ to as Address,
495
+ isGuardedToken,
496
+ toCounterfactual,
497
+ ],
498
+ account: walletClient.account!,
499
+ });
500
+
501
+ const estimatedCost: bigint = estimatedGas * gasPrice;
502
+
503
+ if (ethPriceInUSD) {
504
+ const estimatedCostInEth = formatEther(estimatedCost);
505
+ const estimatedCostInUSD = (
506
+ parseFloat(estimatedCostInEth) * ethPriceInUSD
507
+ ).toFixed(2);
508
+ return estimatedCostInUSD;
509
+ } else {
510
+ throw new Error(
511
+ "Could not fetch the ETH price to calculate cost in USD."
512
+ );
513
+ }
514
+ } catch (error: any) {
515
+ throw new Error(parseViemError(error));
516
+ }
517
+ }
518
+
519
+ return {
520
+ // Core contract functions
521
+ claimPayout,
522
+
523
+ // View functions
524
+ getRewardMeta,
525
+ getMaxReward,
526
+ getAmountClaimed,
527
+ isFinalized,
528
+ isClaimed,
529
+ getNextPostNonce,
530
+ getFinality,
531
+ getFoundationMultisig,
532
+ getRejectionMultisig,
533
+ getCFHFactory,
534
+
535
+ // Gas estimation
536
+ estimateGasForClaimPayout,
537
+
538
+ // State
539
+ get isProcessing() {
540
+ return isProcessing;
541
+ },
542
+ addresses: ADDRESSES,
543
+
544
+ // Wallet client availability
545
+ isSignerAvailable: !!walletClient,
546
+ };
547
+ }
@@ -593,6 +593,8 @@ export interface WeeklyReward {
593
593
  paymentCurrency: PaymentCurrency;
594
594
  protocolDepositRewardsReceived: string; // Protocol deposit rewards received by wallet
595
595
  glowInflationTotal: string; // GLW inflation rewards (18 decimals)
596
+ v1MerkleRoot: string; // V1 merkle root for week's reward distribution (hex string)
597
+ v2MerkleRoot: string; // V2 merkle root for week's reward distribution (hex string)
596
598
  }
597
599
 
598
600
  export interface WeeklyRewardsSummary {