@glowlabs-org/utils 0.2.173 → 0.2.175

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/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 './calculate-farm-efficiency-DbFNiaxO.js';
17
- export { C as ControlRouter, F as FarmsRouter, e as KICKSTARTER_STATUS, K as KickstarterRouter, O as OFF_CHAIN_PAYMENT_CURRENCIES, P as PAYMENT_CURRENCIES, d as REGIONS, R as RegionRouter, S as STAKING_DIRECTIONS, T as TRANSFER_TYPES, W as WalletsRouter, f as calculateFarmEfficiency, c as configureSentry, u as useForwarder, a as useOffchainFractions, b as useRewardsKernel } from './calculate-farm-efficiency-DbFNiaxO.js';
16
+ import { H as HUB_URL, U as USDG_WEIGHT_DECIMAL_PRECISION, G as GLOW_WEIGHT_DECIMAL_PRECISION, M as MAX_WEIGHT } from './calculate-farm-efficiency-DvqnF04d.js';
17
+ export { C as ControlRouter, F as FarmsRouter, e as KICKSTARTER_STATUS, K as KickstarterRouter, O as OFF_CHAIN_PAYMENT_CURRENCIES, P as PAYMENT_CURRENCIES, d as REGIONS, R as RegionRouter, S as STAKING_DIRECTIONS, T as TRANSFER_TYPES, W as WalletsRouter, f as calculateFarmEfficiency, c as configureSentry, u as useForwarder, a as useOffchainFractions, b as useRewardsKernel } from './calculate-farm-efficiency-DvqnF04d.js';
18
18
 
19
19
  const GENESIS_TIMESTAMP = 1700352000;
20
20
 
@@ -1,4 +1,5 @@
1
1
  import { type Signer } from "ethers";
2
+ import { type PublicClient, type WalletClient } from "viem";
2
3
  import { PendingTransferType } from "../types";
3
4
  export declare enum ForwarderError {
4
5
  CONTRACT_NOT_AVAILABLE = "Contract not available",
@@ -19,7 +20,7 @@ export interface ForwardParams {
19
20
  regionId?: number;
20
21
  kickstarterId?: string;
21
22
  }
22
- export declare function useForwarder(signer: Signer | undefined, CHAIN_ID: number): {
23
+ export declare function useForwarder(signer: Signer | undefined, CHAIN_ID: number, publicClient?: PublicClient, walletClient?: WalletClient): {
23
24
  forwardTokens: (params: ForwardParams) => Promise<string>;
24
25
  payProtocolFeeAndMintGCTLAndStake: (amount: bigint, userAddress: string, applicationId: string, regionId?: number, currency?: Currency) => Promise<string>;
25
26
  sponsorProtocolFeeAndMintGCTLAndStake: (amount: bigint, userAddress: string, applicationId: string, currency?: Currency) => Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glowlabs-org/utils",
3
- "version": "0.2.173",
3
+ "version": "0.2.175",
4
4
  "description": "A library containing all typechain types and addresses relating to the glow guarded launch",
5
5
  "keywords": [],
6
6
  "author": "",
@@ -2,11 +2,18 @@ import { Contract, MaxUint256, type Signer } from "ethers";
2
2
  import { FORWARDER_ABI } from "../abis/forwarderABI";
3
3
  import { ERC20_ABI } from "../abis/erc20.abi";
4
4
  import { getAddresses } from "../../constants/addresses";
5
- import { formatEther } from "viem";
5
+ import {
6
+ type PublicClient,
7
+ type WalletClient,
8
+ type Address,
9
+ formatEther,
10
+ } from "viem";
6
11
  import { PendingTransferType, TRANSFER_TYPES } from "../types";
7
12
  import {
8
13
  parseEthersError,
14
+ parseViemError,
9
15
  waitForEthersTransactionWithRetry,
16
+ waitForViemTransactionWithRetry,
10
17
  } from "../../utils/transaction-utils";
11
18
  import {
12
19
  sentryAddBreadcrumb,
@@ -48,7 +55,24 @@ function assertSigner(
48
55
  }
49
56
  }
50
57
 
51
- export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
58
+ // Type-guard style helper to ensure a wallet client exists throughout the rest of the function.
59
+ function assertWalletClient(
60
+ maybeWalletClient: WalletClient | undefined
61
+ ): asserts maybeWalletClient is WalletClient {
62
+ if (!maybeWalletClient) {
63
+ throw new Error(ForwarderError.SIGNER_NOT_AVAILABLE);
64
+ }
65
+ if (!maybeWalletClient.account) {
66
+ throw new Error("Wallet client must have an account");
67
+ }
68
+ }
69
+
70
+ export function useForwarder(
71
+ signer: Signer | undefined,
72
+ CHAIN_ID: number,
73
+ publicClient?: PublicClient,
74
+ walletClient?: WalletClient
75
+ ) {
52
76
  // Use dynamic addresses based on chain configuration
53
77
  const ADDRESSES = getAddresses(CHAIN_ID);
54
78
 
@@ -58,7 +82,16 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
58
82
  isProcessing = value;
59
83
  };
60
84
 
61
- // Returns a contract instance for Forwarder
85
+ // Helper to assert public client is available
86
+ function assertPublicClient(
87
+ maybePublicClient: PublicClient | undefined
88
+ ): asserts maybePublicClient is PublicClient {
89
+ if (!maybePublicClient) {
90
+ throw new Error("Public client not available");
91
+ }
92
+ }
93
+
94
+ // Returns a contract instance for Forwarder (Legacy Ethers)
62
95
  function getForwarderContract() {
63
96
  assertSigner(signer);
64
97
  return new Contract(ADDRESSES.FORWARDER, FORWARDER_ABI, signer);
@@ -221,27 +254,64 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
221
254
  amount: bigint,
222
255
  currency: Currency = "USDC"
223
256
  ): Promise<boolean> {
224
- assertSigner(signer);
225
-
226
257
  try {
227
- const tokenContract = getTokenContract(currency);
228
- if (!tokenContract)
229
- throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
230
-
231
258
  setIsProcessing(true);
232
259
 
233
- // Approve only the specific amount needed
234
- const approveTx = await tokenContract.approve(
235
- ADDRESSES.FORWARDER,
236
- amount
237
- );
238
- await waitForEthersTransactionWithRetry(signer, approveTx.hash, {
239
- timeoutMs: 60000,
240
- pollIntervalMs: 2000,
241
- });
260
+ // Get token address
261
+ let tokenAddress: string;
262
+ switch (currency) {
263
+ case "USDC":
264
+ tokenAddress = ADDRESSES.USDC;
265
+ break;
266
+ case "GLW":
267
+ tokenAddress = ADDRESSES.GLW;
268
+ break;
269
+ case "USDG":
270
+ tokenAddress = ADDRESSES.USDG;
271
+ break;
272
+ default:
273
+ throw new Error(
274
+ `Currency ${currency} not yet supported. Only USDC, GLW, and USDG are currently supported.`
275
+ );
276
+ }
277
+
278
+ // If walletClient and publicClient are available, use Viem (better for mobile)
279
+ if (walletClient && publicClient) {
280
+ assertWalletClient(walletClient);
281
+ assertPublicClient(publicClient);
282
+
283
+ const hash = await walletClient.writeContract({
284
+ address: tokenAddress as Address,
285
+ abi: ERC20_ABI,
286
+ functionName: "approve",
287
+ args: [ADDRESSES.FORWARDER as Address, amount],
288
+ chain: walletClient.chain,
289
+ account: walletClient.account!,
290
+ });
291
+
292
+ await waitForViemTransactionWithRetry(publicClient, hash);
293
+ } else {
294
+ // Fallback to Ethers
295
+ assertSigner(signer);
296
+ const tokenContract = getTokenContract(currency);
297
+ if (!tokenContract)
298
+ throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
299
+
300
+ const approveTx = await tokenContract.approve(
301
+ ADDRESSES.FORWARDER,
302
+ amount
303
+ );
304
+ await waitForEthersTransactionWithRetry(signer, approveTx.hash, {
305
+ timeoutMs: 60000,
306
+ pollIntervalMs: 2000,
307
+ });
308
+ }
242
309
 
243
310
  return true;
244
311
  } catch (error) {
312
+ if (walletClient && publicClient) {
313
+ throw new Error(parseViemError(error));
314
+ }
245
315
  throw new Error(parseEthersError(error));
246
316
  } finally {
247
317
  setIsProcessing(false);
@@ -253,19 +323,10 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
253
323
  * @param params Forward parameters including type, amount, and required fields
254
324
  */
255
325
  async function forwardTokens(params: ForwardParams): Promise<string> {
256
- assertSigner(signer);
257
-
258
326
  try {
259
- const forwarderContract = getForwarderContract();
260
- if (!forwarderContract)
261
- throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
262
-
263
327
  setIsProcessing(true);
264
328
 
265
329
  const { amount, currency = "USDC" } = params;
266
- const tokenContract = getTokenContract(currency);
267
- if (!tokenContract)
268
- throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
269
330
 
270
331
  sentryAddBreadcrumb({
271
332
  category: "forwarder",
@@ -284,55 +345,19 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
284
345
  },
285
346
  });
286
347
 
287
- const owner = await signer.getAddress();
288
-
289
- // Construct the appropriate message for this forward type
290
- const message = constructForwardMessage(params);
291
-
292
- // Special handling: PayAuditFees can ONLY be USDC, and must call forward()
348
+ // Special handling checks
293
349
  const isAuditFees = params.type === TRANSFER_TYPES.PayAuditFees;
294
350
  if (isAuditFees && currency !== "USDC") {
295
351
  throw new Error("PayAuditFees only supports USDC");
296
352
  }
297
353
 
298
- // CommitKickstarter supports only USDC or USDG (GLW not allowed)
299
354
  const isCommitKickstarter =
300
355
  params.type === TRANSFER_TYPES.CommitKickstarter;
301
356
  if (isCommitKickstarter && currency === "GLW") {
302
357
  throw new Error("CommitKickstarter supports only USDC or USDG");
303
358
  }
304
359
 
305
- // Check allowance and approve if necessary
306
- const allowance: bigint = await tokenContract.allowance(
307
- owner,
308
- ADDRESSES.FORWARDER
309
- );
310
- console.log("allowance", allowance.toString());
311
- if (allowance < amount) {
312
- try {
313
- const approveTx = await tokenContract.approve(
314
- ADDRESSES.FORWARDER,
315
- MaxUint256
316
- );
317
- await waitForEthersTransactionWithRetry(signer, approveTx.hash, {
318
- timeoutMs: 60000,
319
- pollIntervalMs: 2000,
320
- });
321
- } catch (approveError) {
322
- sentryCaptureException(approveError, {
323
- action: "forwardTokens.approve",
324
- chainId: CHAIN_ID,
325
- spender: ADDRESSES.FORWARDER,
326
- amount: MaxUint256.toString(),
327
- currency,
328
- });
329
- throw new Error(
330
- parseEthersError(approveError) || "Token approval failed"
331
- );
332
- }
333
- }
334
-
335
- // Get the token address based on currency
360
+ // Get token address
336
361
  let tokenAddress: string;
337
362
  switch (currency) {
338
363
  case "USDC":
@@ -348,27 +373,192 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
348
373
  throw new Error(`Unsupported currency for forwarding: ${currency}`);
349
374
  }
350
375
 
351
- // Determine sendToCounterfactualWallet: false for audit fees (paid in USDC directly)
376
+ const message = constructForwardMessage(params);
352
377
  const sendToCounterfactualWallet = !isAuditFees;
353
378
 
354
- // Run a static call first to surface any revert reason (ethers v6)
355
- try {
379
+ // Handle transaction based on available client
380
+ if (walletClient && publicClient) {
381
+ // --- VIEM IMPLEMENTATION ---
382
+ assertWalletClient(walletClient);
383
+ assertPublicClient(publicClient);
384
+
385
+ const owner = walletClient.account!.address;
386
+
387
+ // Check allowance
388
+ const allowance = (await publicClient.readContract({
389
+ address: tokenAddress as Address,
390
+ abi: ERC20_ABI,
391
+ functionName: "allowance",
392
+ args: [owner as Address, ADDRESSES.FORWARDER as Address],
393
+ })) as bigint;
394
+
395
+ if (allowance < amount) {
396
+ try {
397
+ const approveHash = await walletClient.writeContract({
398
+ address: tokenAddress as Address,
399
+ abi: ERC20_ABI,
400
+ functionName: "approve",
401
+ args: [ADDRESSES.FORWARDER as Address, MaxUint256],
402
+ chain: walletClient.chain,
403
+ account: walletClient.account!,
404
+ });
405
+ await waitForViemTransactionWithRetry(publicClient, approveHash);
406
+ } catch (approveError) {
407
+ sentryCaptureException(approveError, {
408
+ action: "forwardTokens.approve",
409
+ chainId: CHAIN_ID,
410
+ spender: ADDRESSES.FORWARDER,
411
+ amount: MaxUint256.toString(),
412
+ currency,
413
+ });
414
+ throw new Error(
415
+ parseViemError(approveError) || "Token approval failed"
416
+ );
417
+ }
418
+ }
419
+
420
+ // Simulate
421
+ try {
422
+ if (!isAuditFees && currency === "USDC") {
423
+ await publicClient.simulateContract({
424
+ address: ADDRESSES.FORWARDER as Address,
425
+ abi: FORWARDER_ABI,
426
+ functionName: "swapUSDCAndForwardUSDG",
427
+ args: [
428
+ amount,
429
+ ADDRESSES.FOUNDATION_WALLET as Address,
430
+ sendToCounterfactualWallet,
431
+ message,
432
+ ],
433
+ account: walletClient.account!,
434
+ });
435
+ } else {
436
+ await publicClient.simulateContract({
437
+ address: ADDRESSES.FORWARDER as Address,
438
+ abi: FORWARDER_ABI,
439
+ functionName: "forward",
440
+ args: [
441
+ tokenAddress as Address,
442
+ (isAuditFees
443
+ ? ADDRESSES.AUDIT_FEE_WALLET
444
+ : ADDRESSES.FOUNDATION_WALLET) as Address,
445
+ amount,
446
+ sendToCounterfactualWallet,
447
+ message,
448
+ ],
449
+ account: walletClient.account!,
450
+ });
451
+ }
452
+ } catch (staticError) {
453
+ sentryCaptureException(staticError, {
454
+ action: "forwardTokens.simulate",
455
+ chainId: CHAIN_ID,
456
+ function:
457
+ !isAuditFees && currency === "USDC"
458
+ ? "swapUSDCAndForwardUSDG"
459
+ : "forward",
460
+ tokenAddress,
461
+ amount: amount.toString(),
462
+ currency,
463
+ isAuditFees,
464
+ });
465
+ throw new Error(parseViemError(staticError));
466
+ }
467
+
468
+ // Write
469
+ let hash;
356
470
  if (!isAuditFees && currency === "USDC") {
357
- await forwarderContract
358
- .getFunction("swapUSDCAndForwardUSDG")
359
- .staticCall(
471
+ hash = await walletClient.writeContract({
472
+ address: ADDRESSES.FORWARDER as Address,
473
+ abi: FORWARDER_ABI,
474
+ functionName: "swapUSDCAndForwardUSDG",
475
+ args: [
360
476
  amount,
361
- ADDRESSES.FOUNDATION_WALLET,
477
+ ADDRESSES.FOUNDATION_WALLET as Address,
362
478
  sendToCounterfactualWallet,
363
479
  message,
364
- {
365
- from: owner,
366
- }
367
- );
480
+ ],
481
+ chain: walletClient.chain,
482
+ account: walletClient.account!,
483
+ });
368
484
  } else {
369
- await forwarderContract
370
- .getFunction("forward")
371
- .staticCall(
485
+ hash = await walletClient.writeContract({
486
+ address: ADDRESSES.FORWARDER as Address,
487
+ abi: FORWARDER_ABI,
488
+ functionName: "forward",
489
+ args: [
490
+ tokenAddress as Address,
491
+ (isAuditFees
492
+ ? ADDRESSES.AUDIT_FEE_WALLET
493
+ : ADDRESSES.FOUNDATION_WALLET) as Address,
494
+ amount,
495
+ sendToCounterfactualWallet,
496
+ message,
497
+ ],
498
+ chain: walletClient.chain,
499
+ account: walletClient.account!,
500
+ });
501
+ }
502
+
503
+ await waitForViemTransactionWithRetry(publicClient, hash);
504
+ return hash;
505
+ } else {
506
+ // --- ETHERS IMPLEMENTATION (Legacy) ---
507
+ assertSigner(signer);
508
+ const forwarderContract = getForwarderContract();
509
+ if (!forwarderContract)
510
+ throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
511
+
512
+ const tokenContract = getTokenContract(currency);
513
+ if (!tokenContract)
514
+ throw new Error(ForwarderError.CONTRACT_NOT_AVAILABLE);
515
+
516
+ const owner = await signer.getAddress();
517
+
518
+ // Check allowance
519
+ const allowance: bigint = await tokenContract.allowance(
520
+ owner,
521
+ ADDRESSES.FORWARDER
522
+ );
523
+
524
+ if (allowance < amount) {
525
+ try {
526
+ const approveTx = await tokenContract.approve(
527
+ ADDRESSES.FORWARDER,
528
+ MaxUint256
529
+ );
530
+ await waitForEthersTransactionWithRetry(signer, approveTx.hash, {
531
+ timeoutMs: 60000,
532
+ pollIntervalMs: 2000,
533
+ });
534
+ } catch (approveError) {
535
+ sentryCaptureException(approveError, {
536
+ action: "forwardTokens.approve",
537
+ chainId: CHAIN_ID,
538
+ spender: ADDRESSES.FORWARDER,
539
+ amount: MaxUint256.toString(),
540
+ currency,
541
+ });
542
+ throw new Error(
543
+ parseEthersError(approveError) || "Token approval failed"
544
+ );
545
+ }
546
+ }
547
+
548
+ // Determine function and call static
549
+ try {
550
+ if (!isAuditFees && currency === "USDC") {
551
+ await forwarderContract
552
+ .getFunction("swapUSDCAndForwardUSDG")
553
+ .staticCall(
554
+ amount,
555
+ ADDRESSES.FOUNDATION_WALLET,
556
+ sendToCounterfactualWallet,
557
+ message,
558
+ { from: owner }
559
+ );
560
+ } else {
561
+ await forwarderContract.getFunction("forward").staticCall(
372
562
  tokenAddress,
373
563
  isAuditFees
374
564
  ? ADDRESSES.AUDIT_FEE_WALLET
@@ -378,49 +568,51 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
378
568
  message,
379
569
  { from: owner }
380
570
  );
571
+ }
572
+ } catch (staticError) {
573
+ sentryCaptureException(staticError, {
574
+ action: "forwardTokens.staticCall",
575
+ chainId: CHAIN_ID,
576
+ function:
577
+ !isAuditFees && currency === "USDC"
578
+ ? "swapUSDCAndForwardUSDG"
579
+ : "forward",
580
+ tokenAddress,
581
+ amount: amount.toString(),
582
+ currency,
583
+ isAuditFees,
584
+ });
585
+ throw new Error(parseEthersError(staticError));
381
586
  }
382
- } catch (staticError) {
383
- sentryCaptureException(staticError, {
384
- action: "forwardTokens.staticCall",
385
- chainId: CHAIN_ID,
386
- function:
387
- !isAuditFees && currency === "USDC"
388
- ? "swapUSDCAndForwardUSDG"
389
- : "forward",
390
- tokenAddress,
391
- amount: amount.toString(),
392
- currency,
393
- isAuditFees,
587
+
588
+ // Execute transaction
589
+ let tx;
590
+ if (!isAuditFees && currency === "USDC") {
591
+ tx = await forwarderContract.getFunction("swapUSDCAndForwardUSDG")(
592
+ amount,
593
+ ADDRESSES.FOUNDATION_WALLET,
594
+ sendToCounterfactualWallet,
595
+ message
596
+ );
597
+ } else {
598
+ tx = await forwarderContract.getFunction("forward")(
599
+ tokenAddress,
600
+ isAuditFees
601
+ ? ADDRESSES.AUDIT_FEE_WALLET
602
+ : ADDRESSES.FOUNDATION_WALLET,
603
+ amount,
604
+ sendToCounterfactualWallet,
605
+ message
606
+ );
607
+ }
608
+
609
+ await waitForEthersTransactionWithRetry(signer, tx.hash, {
610
+ timeoutMs: 60000,
611
+ pollIntervalMs: 2000,
394
612
  });
395
- throw new Error(parseEthersError(staticError));
396
- }
397
613
 
398
- // Execute the forward transaction
399
- let tx;
400
- if (!isAuditFees && currency === "USDC") {
401
- tx = await forwarderContract.getFunction("swapUSDCAndForwardUSDG")(
402
- amount,
403
- ADDRESSES.FOUNDATION_WALLET,
404
- sendToCounterfactualWallet,
405
- message
406
- );
407
- } else {
408
- tx = await forwarderContract.getFunction("forward")(
409
- tokenAddress,
410
- isAuditFees
411
- ? ADDRESSES.AUDIT_FEE_WALLET
412
- : ADDRESSES.FOUNDATION_WALLET,
413
- amount,
414
- sendToCounterfactualWallet,
415
- message
416
- );
614
+ return tx.hash;
417
615
  }
418
- await waitForEthersTransactionWithRetry(signer, tx.hash, {
419
- timeoutMs: 60000,
420
- pollIntervalMs: 2000,
421
- });
422
-
423
- return tx.hash;
424
616
  } catch (txError: any) {
425
617
  sentryCaptureException(txError, {
426
618
  action: "forwardTokens",
@@ -434,6 +626,9 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
434
626
  kickstarterId: params.kickstarterId,
435
627
  userAddress: params.userAddress,
436
628
  });
629
+ if (walletClient && publicClient) {
630
+ throw new Error(parseViemError(txError));
631
+ }
437
632
  throw new Error(parseEthersError(txError));
438
633
  } finally {
439
634
  setIsProcessing(false);
@@ -511,6 +706,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
511
706
  type: TRANSFER_TYPES.PayProtocolFee,
512
707
  currency,
513
708
  applicationId,
709
+ regionId: undefined, // Fix missing argument
514
710
  });
515
711
  }
516
712
 
@@ -543,7 +739,9 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
543
739
  regionId?: number,
544
740
  currency: Currency = "USDC"
545
741
  ): Promise<string> {
546
- assertSigner(signer);
742
+ // If using WalletClient, we don't strictly need a signer here anymore for forwardTokens
743
+ // But other methods might still rely on it.
744
+ if (!walletClient) assertSigner(signer);
547
745
 
548
746
  // GCTL minting only supports USDC and USDG
549
747
  if (currency === "GLW") {
@@ -569,7 +767,7 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
569
767
  userAddress: string,
570
768
  currency: Currency = "USDC"
571
769
  ): Promise<string> {
572
- assertSigner(signer);
770
+ if (!walletClient) assertSigner(signer);
573
771
 
574
772
  // GCTL minting only supports USDC and USDG
575
773
  if (currency === "GLW") {
@@ -754,10 +952,22 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
754
952
 
755
953
  // Try to call mint function (common for test tokens)
756
954
  const tx = await usdcContract.mint(recipient, amount);
757
- await waitForEthersTransactionWithRetry(signer, tx.hash, {
758
- timeoutMs: 60000,
759
- pollIntervalMs: 2000,
760
- });
955
+
956
+ if (publicClient) {
957
+ await waitForViemTransactionWithRetry(
958
+ publicClient,
959
+ tx.hash as `0x${string}`,
960
+ {
961
+ timeoutMs: 60000,
962
+ pollIntervalMs: 2000,
963
+ }
964
+ );
965
+ } else {
966
+ await waitForEthersTransactionWithRetry(signer, tx.hash, {
967
+ timeoutMs: 60000,
968
+ pollIntervalMs: 2000,
969
+ });
970
+ }
761
971
 
762
972
  return tx.hash;
763
973
  } catch (error: any) {
@@ -802,6 +1012,6 @@ export function useForwarder(signer: Signer | undefined, CHAIN_ID: number) {
802
1012
  addresses: ADDRESSES,
803
1013
 
804
1014
  // Signer availability
805
- isSignerAvailable: !!signer,
1015
+ isSignerAvailable: !!signer || !!walletClient,
806
1016
  };
807
1017
  }
@@ -172,16 +172,27 @@ export async function waitForEthersTransactionWithRetry(
172
172
  }
173
173
  } catch (error) {
174
174
  const errorMessage = parseEthersError(error);
175
- consecutiveErrors++;
176
- if (consecutiveErrors >= maxRetries) {
177
- throw new Error(
178
- `Transaction failed after ${consecutiveErrors} attempts: ${errorMessage}`
179
- );
180
- }
181
- if (enableLogging) {
182
- console.warn(
183
- `Error fetching receipt (attempt ${consecutiveErrors}/${maxRetries}), retrying in ${pollIntervalMs}ms...`
184
- );
175
+
176
+ // Treat not found/receipt missing as retryable without counting towards errors
177
+ const isNotFound =
178
+ errorMessage.toLowerCase().includes("not found") ||
179
+ errorMessage.toLowerCase().includes("could not be found") ||
180
+ errorMessage.toLowerCase().includes("receipt") ||
181
+ errorMessage.toLowerCase().includes("not confirmed") ||
182
+ errorMessage.toLowerCase().includes("transactionreceiptnotfound");
183
+
184
+ if (!isNotFound) {
185
+ consecutiveErrors++;
186
+ if (consecutiveErrors >= maxRetries) {
187
+ throw new Error(
188
+ `Transaction failed after ${consecutiveErrors} attempts: ${errorMessage}`
189
+ );
190
+ }
191
+ if (enableLogging) {
192
+ console.warn(
193
+ `Error fetching receipt (attempt ${consecutiveErrors}/${maxRetries}), retrying in ${pollIntervalMs}ms...`
194
+ );
195
+ }
185
196
  }
186
197
  }
187
198