@glowlabs-org/utils 0.2.140 → 0.2.142
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/dist/cjs/browser.d.ts +1 -0
- package/dist/cjs/browser.js +5 -1
- package/dist/cjs/browser.js.map +1 -1
- package/dist/cjs/{farms-router-Darq9ZsF.js → farms-router-CCva--xp.js} +205 -60
- package/dist/cjs/farms-router-CCva--xp.js.map +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/lib/types/index.d.ts +2 -1
- package/dist/cjs/utils/transaction-utils.d.ts +23 -0
- package/dist/esm/browser.d.ts +1 -0
- package/dist/esm/browser.js +2 -2
- package/dist/esm/{farms-router-B5g58fsT.js → farms-router-Cxbn5Hap.js} +202 -61
- package/dist/esm/farms-router-Cxbn5Hap.js.map +1 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/lib/types/index.d.ts +2 -1
- package/dist/esm/utils/transaction-utils.d.ts +23 -0
- package/package.json +1 -1
- package/src/browser.ts +1 -0
- package/src/lib/hooks/use-forwarder.ts +8 -30
- package/src/lib/hooks/use-offchain-fractions.ts +12 -49
- package/src/lib/types/index.ts +2 -1
- package/src/utils/transaction-utils.ts +285 -0
- package/dist/cjs/farms-router-Darq9ZsF.js.map +0 -1
- package/dist/esm/farms-router-B5g58fsT.js.map +0 -1
package/dist/esm/index.js
CHANGED
|
@@ -13,8 +13,8 @@ import { parseUnits, formatUnits } from 'viem';
|
|
|
13
13
|
import { MerkleTree } from 'merkletreejs';
|
|
14
14
|
import { solidityPackedKeccak256, keccak256 } from 'ethers';
|
|
15
15
|
import Decimal from 'decimal.js';
|
|
16
|
-
import { H as HUB_URL, U as USDG_WEIGHT_DECIMAL_PRECISION, G as GLOW_WEIGHT_DECIMAL_PRECISION, M as MAX_WEIGHT } from './farms-router-
|
|
17
|
-
export { C as ControlRouter, F as FarmsRouter, c as KICKSTARTER_STATUS, K as KickstarterRouter, O as OFF_CHAIN_PAYMENT_CURRENCIES, P as PAYMENT_CURRENCIES, b as REGIONS, R as RegionRouter, S as STAKING_DIRECTIONS, T as TRANSFER_TYPES, W as WalletsRouter, u as useForwarder, a as useOffchainFractions } from './farms-router-
|
|
16
|
+
import { H as HUB_URL, U as USDG_WEIGHT_DECIMAL_PRECISION, G as GLOW_WEIGHT_DECIMAL_PRECISION, M as MAX_WEIGHT } from './farms-router-Cxbn5Hap.js';
|
|
17
|
+
export { C as ControlRouter, F as FarmsRouter, c as KICKSTARTER_STATUS, K as KickstarterRouter, O as OFF_CHAIN_PAYMENT_CURRENCIES, P as PAYMENT_CURRENCIES, b as REGIONS, R as RegionRouter, S as STAKING_DIRECTIONS, T as TRANSFER_TYPES, W as WalletsRouter, u as useForwarder, a as useOffchainFractions } from './farms-router-Cxbn5Hap.js';
|
|
18
18
|
|
|
19
19
|
const GENESIS_TIMESTAMP = 1700352000;
|
|
20
20
|
|
|
@@ -370,6 +370,7 @@ export interface GlwRegionRewardsResponse {
|
|
|
370
370
|
export interface ControlWallet {
|
|
371
371
|
address: string;
|
|
372
372
|
controlBalance: string;
|
|
373
|
+
committedControl: string;
|
|
373
374
|
stakedControl: string;
|
|
374
375
|
createdAt: string;
|
|
375
376
|
}
|
|
@@ -409,11 +410,11 @@ export interface WalletFarmInfo {
|
|
|
409
410
|
export interface WalletDetails {
|
|
410
411
|
wallet: string;
|
|
411
412
|
controlBalance: string;
|
|
413
|
+
committedControl: string;
|
|
412
414
|
stakedControl: string;
|
|
413
415
|
createdAt: string;
|
|
414
416
|
regions: WalletRegionStakeTotal[];
|
|
415
417
|
ownedFarms: WalletFarmInfo[];
|
|
416
|
-
purchasedFarms: WalletFarmInfo[];
|
|
417
418
|
}
|
|
418
419
|
export interface EstimateRewardScoreParams {
|
|
419
420
|
userId: string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type PublicClient } from "viem";
|
|
2
|
+
import { type Signer } from "ethers";
|
|
3
|
+
export declare function parseViemError(error: unknown): string;
|
|
4
|
+
export declare function parseEthersError(error: unknown): string;
|
|
5
|
+
export interface TransactionRetryOptions {
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
enableLogging?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Enhanced transaction receipt handler with retry logic for viem
|
|
12
|
+
* @param publicClient The viem public client
|
|
13
|
+
* @param hash The transaction hash
|
|
14
|
+
* @param options Retry configuration options
|
|
15
|
+
*/
|
|
16
|
+
export declare function waitForViemTransactionWithRetry(publicClient: PublicClient, hash: `0x${string}`, options?: TransactionRetryOptions): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Enhanced transaction receipt handler with retry logic for ethers.js
|
|
19
|
+
* @param signer The ethers signer
|
|
20
|
+
* @param txHash The transaction hash
|
|
21
|
+
* @param options Retry configuration options
|
|
22
|
+
*/
|
|
23
|
+
export declare function waitForEthersTransactionWithRetry(signer: Signer, txHash: string, options?: TransactionRetryOptions): Promise<void>;
|
package/package.json
CHANGED
package/src/browser.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { ERC20_ABI } from "../abis/erc20.abi";
|
|
|
4
4
|
import { getAddresses } from "../../constants/addresses";
|
|
5
5
|
import { formatEther } from "viem";
|
|
6
6
|
import { PendingTransferType, TRANSFER_TYPES } from "../types";
|
|
7
|
+
import {
|
|
8
|
+
parseEthersError,
|
|
9
|
+
waitForEthersTransactionWithRetry,
|
|
10
|
+
} from "../../utils/transaction-utils";
|
|
7
11
|
|
|
8
12
|
export enum ForwarderError {
|
|
9
13
|
CONTRACT_NOT_AVAILABLE = "Contract not available",
|
|
@@ -31,32 +35,6 @@ export interface ForwardParams {
|
|
|
31
35
|
kickstarterId?: string;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
// Utility to extract the most useful revert reason from an ethers error object
|
|
35
|
-
function parseEthersError(error: unknown): string {
|
|
36
|
-
if (!error) return "Unknown error";
|
|
37
|
-
const possibleError: any = error;
|
|
38
|
-
|
|
39
|
-
// If the error originates from a callStatic it will often be found at `error?.error?.body`
|
|
40
|
-
if (possibleError?.error?.body) {
|
|
41
|
-
try {
|
|
42
|
-
const body = JSON.parse(possibleError.error.body);
|
|
43
|
-
// Hardhat style errors
|
|
44
|
-
if (body?.error?.message) return body.error.message as string;
|
|
45
|
-
} catch {}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Found on MetaMask/Alchemy shape errors
|
|
49
|
-
if (possibleError?.data?.message) return possibleError.data.message as string;
|
|
50
|
-
if (possibleError?.error?.message)
|
|
51
|
-
return possibleError.error.message as string;
|
|
52
|
-
|
|
53
|
-
// Standard ethers v5 message
|
|
54
|
-
if (possibleError?.reason) return possibleError.reason as string;
|
|
55
|
-
if (possibleError?.message) return possibleError.message as string;
|
|
56
|
-
|
|
57
|
-
return ForwarderError.UNKNOWN_ERROR;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
38
|
// Type-guard style helper to ensure a signer exists throughout the rest of the function.
|
|
61
39
|
function assertSigner(
|
|
62
40
|
maybeSigner: Signer | undefined
|
|
@@ -253,7 +231,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
|
|
|
253
231
|
ADDRESSES.FORWARDER,
|
|
254
232
|
amount
|
|
255
233
|
);
|
|
256
|
-
await approveTx.
|
|
234
|
+
await waitForEthersTransactionWithRetry(signer, approveTx.hash);
|
|
257
235
|
|
|
258
236
|
return true;
|
|
259
237
|
} catch (error) {
|
|
@@ -312,7 +290,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
|
|
|
312
290
|
ADDRESSES.FORWARDER,
|
|
313
291
|
MaxUint256
|
|
314
292
|
);
|
|
315
|
-
await approveTx.
|
|
293
|
+
await waitForEthersTransactionWithRetry(signer, approveTx.hash);
|
|
316
294
|
} catch (approveError) {
|
|
317
295
|
throw new Error(
|
|
318
296
|
parseEthersError(approveError) || "Token approval failed"
|
|
@@ -391,7 +369,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
|
|
|
391
369
|
message
|
|
392
370
|
);
|
|
393
371
|
}
|
|
394
|
-
await tx.
|
|
372
|
+
await waitForEthersTransactionWithRetry(signer, tx.hash);
|
|
395
373
|
|
|
396
374
|
return tx.hash;
|
|
397
375
|
} catch (txError: any) {
|
|
@@ -715,7 +693,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
|
|
|
715
693
|
|
|
716
694
|
// Try to call mint function (common for test tokens)
|
|
717
695
|
const tx = await usdcContract.mint(recipient, amount);
|
|
718
|
-
await tx.
|
|
696
|
+
await waitForEthersTransactionWithRetry(signer, tx.hash);
|
|
719
697
|
|
|
720
698
|
return tx.hash;
|
|
721
699
|
} catch (error: any) {
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
import { OFFCHAIN_FRACTIONS_ABI } from "../abis/offchainFractions";
|
|
8
8
|
import { ERC20_ABI } from "../abis/erc20.abi";
|
|
9
9
|
import { getAddresses } from "../../constants/addresses";
|
|
10
|
+
import {
|
|
11
|
+
parseViemError,
|
|
12
|
+
waitForViemTransactionWithRetry,
|
|
13
|
+
} from "../../utils/transaction-utils";
|
|
10
14
|
|
|
11
15
|
export enum OffchainFractionsError {
|
|
12
16
|
CONTRACT_NOT_AVAILABLE = "Contract not available",
|
|
@@ -63,31 +67,6 @@ export interface RefundDetails {
|
|
|
63
67
|
useCounterfactualAddress: boolean;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
// Utility to extract the most useful revert reason from a viem error object
|
|
67
|
-
function parseViemError(error: unknown): string {
|
|
68
|
-
if (!error) return "Unknown error";
|
|
69
|
-
|
|
70
|
-
// Check if it's a viem BaseError
|
|
71
|
-
if (error instanceof Error) {
|
|
72
|
-
// For contract revert errors
|
|
73
|
-
if ((error as any).cause?.reason) {
|
|
74
|
-
return (error as any).cause.reason;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// For viem's shortMessage
|
|
78
|
-
if ((error as any).shortMessage) {
|
|
79
|
-
return (error as any).shortMessage;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Fallback to regular message
|
|
83
|
-
if (error.message) {
|
|
84
|
-
return error.message;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return OffchainFractionsError.UNKNOWN_ERROR;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
70
|
// Type-guard style helper to ensure a wallet client exists throughout the rest of the function.
|
|
92
71
|
function assertWalletClient(
|
|
93
72
|
maybeWalletClient: WalletClient | undefined
|
|
@@ -195,9 +174,7 @@ export function useOffchainFractions(
|
|
|
195
174
|
account: walletClient.account!,
|
|
196
175
|
});
|
|
197
176
|
|
|
198
|
-
await publicClient
|
|
199
|
-
hash,
|
|
200
|
-
});
|
|
177
|
+
await waitForViemTransactionWithRetry(publicClient, hash);
|
|
201
178
|
|
|
202
179
|
return true;
|
|
203
180
|
} catch (error) {
|
|
@@ -286,9 +263,7 @@ export function useOffchainFractions(
|
|
|
286
263
|
account: walletClient.account!,
|
|
287
264
|
});
|
|
288
265
|
|
|
289
|
-
await publicClient
|
|
290
|
-
hash,
|
|
291
|
-
});
|
|
266
|
+
await waitForViemTransactionWithRetry(publicClient, hash);
|
|
292
267
|
|
|
293
268
|
return hash;
|
|
294
269
|
} catch (error) {
|
|
@@ -358,9 +333,7 @@ export function useOffchainFractions(
|
|
|
358
333
|
chain: walletClient.chain,
|
|
359
334
|
account: walletClient.account!,
|
|
360
335
|
});
|
|
361
|
-
await publicClient
|
|
362
|
-
hash: approveHash,
|
|
363
|
-
});
|
|
336
|
+
await waitForViemTransactionWithRetry(publicClient, approveHash);
|
|
364
337
|
}
|
|
365
338
|
|
|
366
339
|
// Run a simulation first to surface any revert reason
|
|
@@ -402,9 +375,7 @@ export function useOffchainFractions(
|
|
|
402
375
|
account: walletClient.account!,
|
|
403
376
|
});
|
|
404
377
|
|
|
405
|
-
await publicClient
|
|
406
|
-
hash,
|
|
407
|
-
});
|
|
378
|
+
await waitForViemTransactionWithRetry(publicClient, hash);
|
|
408
379
|
|
|
409
380
|
return hash;
|
|
410
381
|
} catch (error) {
|
|
@@ -495,9 +466,7 @@ export function useOffchainFractions(
|
|
|
495
466
|
account: walletClient.account!,
|
|
496
467
|
});
|
|
497
468
|
|
|
498
|
-
await publicClient
|
|
499
|
-
hash,
|
|
500
|
-
});
|
|
469
|
+
await waitForViemTransactionWithRetry(publicClient, hash);
|
|
501
470
|
|
|
502
471
|
return hash;
|
|
503
472
|
} catch (error) {
|
|
@@ -552,9 +521,7 @@ export function useOffchainFractions(
|
|
|
552
521
|
account: walletClient.account!,
|
|
553
522
|
});
|
|
554
523
|
|
|
555
|
-
await publicClient
|
|
556
|
-
hash,
|
|
557
|
-
});
|
|
524
|
+
await waitForViemTransactionWithRetry(publicClient, hash);
|
|
558
525
|
|
|
559
526
|
return hash;
|
|
560
527
|
} catch (error) {
|
|
@@ -748,9 +715,7 @@ export function useOffchainFractions(
|
|
|
748
715
|
account: walletClient.account!,
|
|
749
716
|
});
|
|
750
717
|
|
|
751
|
-
await publicClient
|
|
752
|
-
hash,
|
|
753
|
-
});
|
|
718
|
+
await waitForViemTransactionWithRetry(publicClient, hash);
|
|
754
719
|
|
|
755
720
|
return hash;
|
|
756
721
|
} catch (error) {
|
|
@@ -808,9 +773,7 @@ export function useOffchainFractions(
|
|
|
808
773
|
account: walletClient.account!,
|
|
809
774
|
});
|
|
810
775
|
|
|
811
|
-
await publicClient
|
|
812
|
-
hash,
|
|
813
|
-
});
|
|
776
|
+
await waitForViemTransactionWithRetry(publicClient, hash);
|
|
814
777
|
|
|
815
778
|
return hash;
|
|
816
779
|
} catch (error) {
|
package/src/lib/types/index.ts
CHANGED
|
@@ -455,6 +455,7 @@ export interface GlwRegionRewardsResponse {
|
|
|
455
455
|
export interface ControlWallet {
|
|
456
456
|
address: string;
|
|
457
457
|
controlBalance: string;
|
|
458
|
+
committedControl: string;
|
|
458
459
|
stakedControl: string;
|
|
459
460
|
createdAt: string; // ISO 8601
|
|
460
461
|
}
|
|
@@ -499,11 +500,11 @@ export interface WalletFarmInfo {
|
|
|
499
500
|
export interface WalletDetails {
|
|
500
501
|
wallet: string;
|
|
501
502
|
controlBalance: string;
|
|
503
|
+
committedControl: string;
|
|
502
504
|
stakedControl: string;
|
|
503
505
|
createdAt: string; // ISO 8601
|
|
504
506
|
regions: WalletRegionStakeTotal[];
|
|
505
507
|
ownedFarms: WalletFarmInfo[];
|
|
506
|
-
purchasedFarms: WalletFarmInfo[];
|
|
507
508
|
}
|
|
508
509
|
|
|
509
510
|
// ----------------------------- Farms Reward Score ---------------------------
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { type PublicClient } from "viem";
|
|
2
|
+
import { type Signer } from "ethers";
|
|
3
|
+
|
|
4
|
+
// Error parsing utilities
|
|
5
|
+
export function parseViemError(error: unknown): string {
|
|
6
|
+
if (!error) return "Unknown error";
|
|
7
|
+
|
|
8
|
+
// Check if it's a viem BaseError
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
// For contract revert errors
|
|
11
|
+
if ((error as any).cause?.reason) {
|
|
12
|
+
return (error as any).cause.reason;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// For viem's shortMessage
|
|
16
|
+
if ((error as any).shortMessage) {
|
|
17
|
+
return (error as any).shortMessage;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Fallback to regular message
|
|
21
|
+
if (error.message) {
|
|
22
|
+
return error.message;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return "Unknown error";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseEthersError(error: unknown): string {
|
|
30
|
+
if (!error) return "Unknown error";
|
|
31
|
+
const possibleError: any = error;
|
|
32
|
+
|
|
33
|
+
// If the error originates from a callStatic it will often be found at `error?.error?.body`
|
|
34
|
+
if (possibleError?.error?.body) {
|
|
35
|
+
try {
|
|
36
|
+
const body = JSON.parse(possibleError.error.body);
|
|
37
|
+
// Hardhat style errors
|
|
38
|
+
if (body?.error?.message) return body.error.message as string;
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Found on MetaMask/Alchemy shape errors
|
|
43
|
+
if (possibleError?.data?.message) return possibleError.data.message as string;
|
|
44
|
+
if (possibleError?.error?.message)
|
|
45
|
+
return possibleError.error.message as string;
|
|
46
|
+
|
|
47
|
+
// Standard ethers v5 message
|
|
48
|
+
if (possibleError?.reason) return possibleError.reason as string;
|
|
49
|
+
if (possibleError?.message) return possibleError.message as string;
|
|
50
|
+
|
|
51
|
+
return "Unknown error";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Transaction receipt utilities
|
|
55
|
+
export interface TransactionRetryOptions {
|
|
56
|
+
maxRetries?: number;
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
enableLogging?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DEFAULT_OPTIONS: Required<TransactionRetryOptions> = {
|
|
62
|
+
maxRetries: 3,
|
|
63
|
+
timeoutMs: 60000,
|
|
64
|
+
enableLogging: true,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Enhanced transaction receipt handler with retry logic for viem
|
|
69
|
+
* @param publicClient The viem public client
|
|
70
|
+
* @param hash The transaction hash
|
|
71
|
+
* @param options Retry configuration options
|
|
72
|
+
*/
|
|
73
|
+
export async function waitForViemTransactionWithRetry(
|
|
74
|
+
publicClient: PublicClient,
|
|
75
|
+
hash: `0x${string}`,
|
|
76
|
+
options: TransactionRetryOptions = {}
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const { maxRetries, timeoutMs, enableLogging } = {
|
|
79
|
+
...DEFAULT_OPTIONS,
|
|
80
|
+
...options,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
let lastError: Error | null = null;
|
|
84
|
+
|
|
85
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
86
|
+
try {
|
|
87
|
+
// Wait for transaction receipt with timeout
|
|
88
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
89
|
+
hash,
|
|
90
|
+
timeout: timeoutMs,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Check transaction status
|
|
94
|
+
if (receipt.status === "reverted") {
|
|
95
|
+
throw new Error(`Transaction ${hash} was reverted on-chain`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (enableLogging) {
|
|
99
|
+
console.log(
|
|
100
|
+
`Transaction ${hash} confirmed successfully in block ${receipt.blockNumber}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return; // Success, exit the function
|
|
104
|
+
} catch (error) {
|
|
105
|
+
lastError = error as Error;
|
|
106
|
+
|
|
107
|
+
// Check if it's a timeout or not found error
|
|
108
|
+
const errorMessage = parseViemError(error);
|
|
109
|
+
const isRetryableError =
|
|
110
|
+
errorMessage.includes("could not be found") ||
|
|
111
|
+
errorMessage.includes("timeout") ||
|
|
112
|
+
errorMessage.includes("timed out") ||
|
|
113
|
+
errorMessage.includes("receipt") ||
|
|
114
|
+
errorMessage.includes("not be processed");
|
|
115
|
+
|
|
116
|
+
if (!isRetryableError || attempt === maxRetries) {
|
|
117
|
+
// If it's not a retryable error or we've exhausted retries, throw
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Transaction failed after ${attempt} attempts: ${errorMessage}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Wait before retrying (exponential backoff)
|
|
124
|
+
const delay = Math.min(2000 * Math.pow(2, attempt - 1), 10000);
|
|
125
|
+
if (enableLogging) {
|
|
126
|
+
console.warn(
|
|
127
|
+
`Transaction receipt not found (attempt ${attempt}/${maxRetries}), retrying in ${delay}ms...`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
131
|
+
|
|
132
|
+
// Check transaction status before retrying
|
|
133
|
+
try {
|
|
134
|
+
const transaction = await publicClient.getTransaction({ hash });
|
|
135
|
+
if (!transaction) {
|
|
136
|
+
throw new Error("Transaction not found in mempool or blockchain");
|
|
137
|
+
}
|
|
138
|
+
if (enableLogging) {
|
|
139
|
+
console.log(
|
|
140
|
+
`Transaction ${hash} found in ${
|
|
141
|
+
transaction.blockNumber ? "block" : "mempool"
|
|
142
|
+
}, waiting for confirmation...`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
} catch (txError) {
|
|
146
|
+
if (enableLogging) {
|
|
147
|
+
console.warn(
|
|
148
|
+
`Could not fetch transaction ${hash}: ${parseViemError(txError)}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
// Continue with retry anyway in case it's just an RPC issue
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// This should never be reached, but just in case
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Transaction failed after ${maxRetries} attempts: ${parseViemError(
|
|
159
|
+
lastError
|
|
160
|
+
)}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Enhanced transaction receipt handler with retry logic for ethers.js
|
|
166
|
+
* @param signer The ethers signer
|
|
167
|
+
* @param txHash The transaction hash
|
|
168
|
+
* @param options Retry configuration options
|
|
169
|
+
*/
|
|
170
|
+
export async function waitForEthersTransactionWithRetry(
|
|
171
|
+
signer: Signer,
|
|
172
|
+
txHash: string,
|
|
173
|
+
options: TransactionRetryOptions = {}
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
const { maxRetries, timeoutMs, enableLogging } = {
|
|
176
|
+
...DEFAULT_OPTIONS,
|
|
177
|
+
...options,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
let lastError: Error | null = null;
|
|
181
|
+
|
|
182
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
183
|
+
try {
|
|
184
|
+
// Wait for transaction receipt with timeout
|
|
185
|
+
const provider = signer.provider;
|
|
186
|
+
if (!provider) {
|
|
187
|
+
throw new Error("Provider not available");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create a timeout promise
|
|
191
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
192
|
+
setTimeout(
|
|
193
|
+
() => reject(new Error("Transaction receipt timeout")),
|
|
194
|
+
timeoutMs
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Race between waiting for receipt and timeout
|
|
199
|
+
const receipt = await Promise.race([
|
|
200
|
+
provider.waitForTransaction(txHash, 1, timeoutMs),
|
|
201
|
+
timeoutPromise,
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
if (!receipt) {
|
|
205
|
+
throw new Error("Transaction receipt not found");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check transaction status (ethers.js uses status: 0 for failed, 1 for success)
|
|
209
|
+
if ((receipt as any).status === 0) {
|
|
210
|
+
throw new Error(`Transaction ${txHash} was reverted on-chain`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (enableLogging) {
|
|
214
|
+
console.log(
|
|
215
|
+
`Transaction ${txHash} confirmed successfully in block ${
|
|
216
|
+
(receipt as any).blockNumber
|
|
217
|
+
}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return; // Success, exit the function
|
|
221
|
+
} catch (error) {
|
|
222
|
+
lastError = error as Error;
|
|
223
|
+
|
|
224
|
+
// Check if it's a timeout or not found error
|
|
225
|
+
const errorMessage = parseEthersError(error);
|
|
226
|
+
const isRetryableError =
|
|
227
|
+
errorMessage.includes("could not be found") ||
|
|
228
|
+
errorMessage.includes("timeout") ||
|
|
229
|
+
errorMessage.includes("timed out") ||
|
|
230
|
+
errorMessage.includes("receipt") ||
|
|
231
|
+
errorMessage.includes("not be processed") ||
|
|
232
|
+
errorMessage.includes("Transaction receipt timeout");
|
|
233
|
+
|
|
234
|
+
if (!isRetryableError || attempt === maxRetries) {
|
|
235
|
+
// If it's not a retryable error or we've exhausted retries, throw
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Transaction failed after ${attempt} attempts: ${errorMessage}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Wait before retrying (exponential backoff)
|
|
242
|
+
const delay = Math.min(2000 * Math.pow(2, attempt - 1), 10000);
|
|
243
|
+
if (enableLogging) {
|
|
244
|
+
console.warn(
|
|
245
|
+
`Transaction receipt not found (attempt ${attempt}/${maxRetries}), retrying in ${delay}ms...`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
249
|
+
|
|
250
|
+
// Check transaction status before retrying
|
|
251
|
+
try {
|
|
252
|
+
const provider = signer.provider;
|
|
253
|
+
if (provider) {
|
|
254
|
+
const transaction = await provider.getTransaction(txHash);
|
|
255
|
+
if (!transaction) {
|
|
256
|
+
throw new Error("Transaction not found in mempool or blockchain");
|
|
257
|
+
}
|
|
258
|
+
if (enableLogging) {
|
|
259
|
+
console.log(
|
|
260
|
+
`Transaction ${txHash} found in ${
|
|
261
|
+
transaction.blockNumber ? "block" : "mempool"
|
|
262
|
+
}, waiting for confirmation...`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} catch (txError) {
|
|
267
|
+
if (enableLogging) {
|
|
268
|
+
console.warn(
|
|
269
|
+
`Could not fetch transaction ${txHash}: ${parseEthersError(
|
|
270
|
+
txError
|
|
271
|
+
)}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
// Continue with retry anyway in case it's just an RPC issue
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// This should never be reached, but just in case
|
|
280
|
+
throw new Error(
|
|
281
|
+
`Transaction failed after ${maxRetries} attempts: ${parseEthersError(
|
|
282
|
+
lastError
|
|
283
|
+
)}`
|
|
284
|
+
);
|
|
285
|
+
}
|