0xtrails 0.1.2 → 0.1.3

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.
Files changed (103) hide show
  1. package/dist/analytics.d.ts +68 -1
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/{ccip-BmFTEOaB.js → ccip-CWd4g9uZ.js} +1 -1
  4. package/dist/chains.d.ts +9 -3
  5. package/dist/chains.d.ts.map +1 -1
  6. package/dist/ens.d.ts +7 -0
  7. package/dist/ens.d.ts.map +1 -0
  8. package/dist/error.d.ts +2 -0
  9. package/dist/error.d.ts.map +1 -1
  10. package/dist/{index-BPsVj7zK.js → index-BTUBzx4R.js} +23624 -21770
  11. package/dist/index.js +2 -2
  12. package/dist/lifi.d.ts +4 -0
  13. package/dist/lifi.d.ts.map +1 -0
  14. package/dist/mode.d.ts +1 -1
  15. package/dist/mode.d.ts.map +1 -1
  16. package/dist/prepareSend.d.ts +3 -1
  17. package/dist/prepareSend.d.ts.map +1 -1
  18. package/dist/prices.d.ts +2 -0
  19. package/dist/prices.d.ts.map +1 -1
  20. package/dist/relaySdk.d.ts.map +1 -1
  21. package/dist/relayer.d.ts.map +1 -1
  22. package/dist/tokenBalances.d.ts.map +1 -1
  23. package/dist/tokens.d.ts +2 -1
  24. package/dist/tokens.d.ts.map +1 -1
  25. package/dist/trails.d.ts +3 -3
  26. package/dist/trails.d.ts.map +1 -1
  27. package/dist/transactions.d.ts.map +1 -1
  28. package/dist/wallets.d.ts +247 -5
  29. package/dist/wallets.d.ts.map +1 -1
  30. package/dist/widget/components/ChainFilterDropdown.d.ts +2 -0
  31. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  32. package/dist/widget/components/ConnectWallet.d.ts +1 -0
  33. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  34. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -1
  35. package/dist/widget/components/FundSendForm.d.ts +2 -2
  36. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  37. package/dist/widget/components/PaySendForm.d.ts +2 -2
  38. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  39. package/dist/widget/components/QrCode.d.ts +1 -1
  40. package/dist/widget/components/QrCode.d.ts.map +1 -1
  41. package/dist/widget/components/RefundAddressInput.d.ts +13 -0
  42. package/dist/widget/components/RefundAddressInput.d.ts.map +1 -0
  43. package/dist/widget/components/Swap.d.ts +43 -0
  44. package/dist/widget/components/Swap.d.ts.map +1 -0
  45. package/dist/widget/components/TokenList.d.ts +0 -2
  46. package/dist/widget/components/TokenList.d.ts.map +1 -1
  47. package/dist/widget/components/TokenSelector.d.ts +26 -0
  48. package/dist/widget/components/TokenSelector.d.ts.map +1 -0
  49. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  50. package/dist/widget/components/WalletConnectionPending.d.ts +12 -0
  51. package/dist/widget/components/WalletConnectionPending.d.ts.map +1 -0
  52. package/dist/widget/components/WalletList.d.ts.map +1 -1
  53. package/dist/widget/hooks/useAmountUsd.d.ts +1 -3
  54. package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -1
  55. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  56. package/dist/widget/hooks/useSendForm.d.ts +6 -4
  57. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  58. package/dist/widget/hooks/useTokenList.d.ts +2 -3
  59. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  60. package/dist/widget/index.js +1 -1
  61. package/dist/widget/widget.d.ts.map +1 -1
  62. package/package.json +9 -6
  63. package/src/aave.ts +13 -13
  64. package/src/analytics.ts +87 -4
  65. package/src/chains.ts +45 -7
  66. package/src/constants.ts +4 -4
  67. package/src/ens.ts +17 -0
  68. package/src/error.ts +16 -1
  69. package/src/lifi.ts +58 -0
  70. package/src/mode.ts +1 -1
  71. package/src/morpho.ts +3 -3
  72. package/src/pools.ts +18 -18
  73. package/src/prepareSend.ts +35 -3
  74. package/src/prices.ts +21 -0
  75. package/src/relaySdk.ts +1 -0
  76. package/src/relayer.ts +8 -0
  77. package/src/tokenBalances.ts +3 -0
  78. package/src/tokens.ts +85 -19
  79. package/src/trails.ts +2 -2
  80. package/src/transactions.ts +1 -0
  81. package/src/wallets.ts +275 -35
  82. package/src/widget/compiled.css +1 -1
  83. package/src/widget/components/ChainFilterDropdown.tsx +42 -33
  84. package/src/widget/components/ChainImage.tsx +1 -1
  85. package/src/widget/components/ConnectWallet.tsx +92 -128
  86. package/src/widget/components/DebugScreensDropdown.tsx +3 -0
  87. package/src/widget/components/FundSendForm.tsx +17 -3
  88. package/src/widget/components/PaySendForm.tsx +16 -2
  89. package/src/widget/components/QRCodeDeposit.tsx +1 -1
  90. package/src/widget/components/QrCode.tsx +277 -16
  91. package/src/widget/components/Receipt.tsx +1 -1
  92. package/src/widget/components/RefundAddressInput.tsx +149 -0
  93. package/src/widget/components/Swap.tsx +648 -0
  94. package/src/widget/components/TokenList.tsx +27 -363
  95. package/src/widget/components/TokenSelector.tsx +405 -0
  96. package/src/widget/components/WalletConnect.tsx +9 -7
  97. package/src/widget/components/WalletConnectionPending.tsx +157 -0
  98. package/src/widget/components/WalletList.tsx +6 -5
  99. package/src/widget/hooks/useAmountUsd.ts +3 -8
  100. package/src/widget/hooks/useCheckout.ts +3 -2
  101. package/src/widget/hooks/useSendForm.ts +66 -32
  102. package/src/widget/hooks/useTokenList.ts +158 -106
  103. package/src/widget/widget.tsx +335 -72
@@ -15,7 +15,7 @@ import React, {
15
15
  } from "react"
16
16
  import { createPortal } from "react-dom"
17
17
  import type { Chain, WalletClient } from "viem"
18
- import { createWalletClient, custom, http, parseUnits } from "viem"
18
+ import { createWalletClient, custom, http, parseUnits, isAddress } from "viem"
19
19
  import * as viemChains from "viem/chains"
20
20
  import type { Connector } from "wagmi"
21
21
  import {
@@ -26,10 +26,7 @@ import {
26
26
  WagmiContext,
27
27
  WagmiProvider,
28
28
  } from "wagmi"
29
- import { injected, walletConnect, safe, baseAccount } from "wagmi/connectors"
30
- import { useAPIClient } from "../apiClient.js"
31
29
  import { getChainInfo } from "../chains.js"
32
- import { useIndexerGatewayClient } from "../indexerClient.js"
33
30
  import type { TransactionState } from "../transactions.js"
34
31
  import type { RelayerEnv, MetaTxnReceipt } from "../relayer.js"
35
32
  import type { Theme } from "../theme.js"
@@ -45,22 +42,25 @@ import TransferPending from "./components/TransferPendingVertical.js"
45
42
  import WalletConfirmation from "./components/WalletConfirmation.js"
46
43
  import QRCodeDeposit from "./components/QRCodeDeposit.js"
47
44
  import { ThemeProvider } from "./components/ThemeProvider.js"
48
- import {
49
- getWalletConnectProjectId,
50
- setWalletConnectProjectId,
51
- } from "../config.js"
45
+ import { setWalletConnectProjectId } from "../config.js"
52
46
  import { useAmountUsd } from "./hooks/useAmountUsd.js"
53
47
  import { useRecentTokens } from "./hooks/useRecentTokens.js"
54
48
  import { getWethAddress } from "../tokens.js"
55
49
  import css from "./compiled.css?inline"
56
- import { trackWalletConnected, trackWidgetScreen } from "../analytics.js"
50
+ import {
51
+ getSessionId,
52
+ trackWalletConnected,
53
+ trackWidgetScreen,
54
+ } from "../analytics.js"
57
55
  import type { PrepareSendQuote } from "../prepareSend.js"
58
56
  import { getNormalizedQuoteObject } from "../prepareSend.js"
59
57
  import type { SupportedToken } from "../tokens.js"
60
58
  import {
61
- getErrorString,
62
59
  getIsWalletRejectedError,
63
60
  getIsBalanceTooLowError,
61
+ getFullErrorMessage,
62
+ getIsApiError,
63
+ getIsRateLimitedError,
64
64
  } from "../error.js"
65
65
  import {
66
66
  setSequenceIndexerUrl,
@@ -73,11 +73,13 @@ import {
73
73
  setSlippageTolerance,
74
74
  } from "../config.js"
75
75
  import { FundSendForm } from "./components/FundSendForm.js"
76
+ import { Swap } from "./components/Swap.js"
76
77
  import type { MeshConnectProps } from "./components/MeshConnectIframe.js"
77
78
  import { MeshConnectFlow } from "./components/MeshConnectFlow.js"
78
79
  import WalletConnectScreen from "./components/WalletConnect.js"
79
80
  import FundMethods from "./components/FundMethods.js"
80
81
  import EarnPools from "./components/EarnPools.js"
82
+ import WalletConnectionPending from "./components/WalletConnectionPending.js"
81
83
  import type { Mode } from "../mode.js"
82
84
  import type { OnCompleteProps } from "./hooks/useSendForm.js"
83
85
  import type { Pool } from "../pools.js"
@@ -86,10 +88,24 @@ import { AaveProvider, AaveClient } from "@aave/react"
86
88
  import { encodeFunctionData } from "viem"
87
89
  import { cssObjectToString } from "../cssUtils.js"
88
90
  import { useCheckout } from "./hooks/useCheckout.js"
89
- import { WALLET_CONFIGS, walletConnectConnector } from "../wallets.js"
91
+ import { useWallets, walletConnectConnector, connectors } from "../wallets.js"
92
+ import { isValidNumeric, isValidInteger } from "../prices.js"
90
93
 
91
94
  export const aaveClient = AaveClient.create()
92
95
 
96
+ // Validate toToken - must be "ETH", "USDC", or a valid hex address
97
+ const isValidToToToken = (toToken: string | null | undefined) => {
98
+ if (toToken === null || toToken === undefined || toToken === "") {
99
+ return true // Empty values are considered valid
100
+ }
101
+ const token = String(toToken).trim()
102
+ if (token === "ETH" || token === "USDC") {
103
+ return true
104
+ }
105
+
106
+ return isAddress(token)
107
+ }
108
+
93
109
  type Screen =
94
110
  | "connect"
95
111
  | "tokens"
@@ -97,6 +113,7 @@ type Screen =
97
113
  | "fund-form"
98
114
  | "fund-methods"
99
115
  | "earn-pools"
116
+ | "swap"
100
117
  | "wallet-confirmation"
101
118
  | "qr-code-deposit"
102
119
  | "pending"
@@ -104,6 +121,7 @@ type Screen =
104
121
  | "mesh-connect"
105
122
  | "wallet-connect"
106
123
  | "wallet-list"
124
+ | "wallet-connection-pending"
107
125
 
108
126
  export const defaultWalletOptions = ["injected", "walletconnect"]
109
127
 
@@ -183,7 +201,7 @@ const useWalletManager = (
183
201
  const [walletClient, setWalletClient] = useState<WalletClient | null>(null)
184
202
 
185
203
  useEffect(() => {
186
- const connect = async () => {
204
+ const connectWallet = async () => {
187
205
  try {
188
206
  if (!connector) {
189
207
  return
@@ -208,7 +226,7 @@ const useWalletManager = (
208
226
  console.error("[trails-sdk] Failed to connect wallet", error)
209
227
  }
210
228
  }
211
- connect().catch(console.error)
229
+ connectWallet().catch(console.error)
212
230
  }, [address, chainId, connector])
213
231
 
214
232
  return walletClient
@@ -243,7 +261,7 @@ const useTransactionState = (
243
261
  onOriginConfirmation({
244
262
  txHash: originTxHash,
245
263
  chainId: originChainId,
246
- sessionId: Date.now().toString(),
264
+ sessionId: getSessionId(),
247
265
  })
248
266
  }
249
267
  }, [originTxHash, onOriginConfirmation, originChainId])
@@ -253,7 +271,7 @@ const useTransactionState = (
253
271
  onDestinationConfirmation({
254
272
  txHash: destinationTxHash,
255
273
  chainId: destinationChainId,
256
- sessionId: Date.now().toString(),
274
+ sessionId: getSessionId(),
257
275
  })
258
276
  }
259
277
  }, [destinationTxHash, onDestinationConfirmation, destinationChainId])
@@ -308,9 +326,6 @@ const useTransactionState = (
308
326
  const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
309
327
  (
310
328
  {
311
- appId: sequenceProjectAccessKey,
312
- sequenceIndexerUrl,
313
- sequenceApiUrl,
314
329
  toAddress,
315
330
  toAmount,
316
331
  toChainId,
@@ -335,20 +350,37 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
335
350
  ref,
336
351
  ) => {
337
352
  const { address, isConnected, chainId, connector } = useAccount()
338
- const { disconnect } = useDisconnect()
353
+ const { disconnectAsync } = useDisconnect()
339
354
  const { recentTokens, addRecentToken } = useRecentTokens(address)
355
+ const { wallets: allWallets } = useWallets()
340
356
  const [isModalOpen, setIsModalOpen] = useState(false)
341
357
  const [currentScreen, setCurrentScreen] = useState<Screen>(
342
- isConnected ? "tokens" : "connect",
358
+ isConnected ? (mode === "swap" ? "swap" : "tokens") : "connect",
343
359
  )
344
360
  const [selectedToken, setSelectedToken] = useState<Token | null>(null)
345
- const [selectedFundMethod, setSelectedFundMethod] = useState<string | null>(
346
- null,
347
- )
361
+ const [selectedFundMethod, setSelectedFundMethod] =
362
+ useState<string>("wallet")
348
363
  const [selectedPool, setSelectedPool] = useState<Pool | null>(null)
349
364
  const [selectedWalletId, setSelectedWalletId] = useState<string | null>(
350
- null,
365
+ () => {
366
+ // Initialize from localStorage if available
367
+ if (typeof window !== "undefined") {
368
+ try {
369
+ return localStorage.getItem("trails-last-wallet") || null
370
+ } catch (error) {
371
+ console.error(
372
+ "[trails-sdk] Failed to read from localStorage:",
373
+ error,
374
+ )
375
+ return null
376
+ }
377
+ }
378
+ return null
379
+ },
351
380
  )
381
+ const [isConnecting, setIsConnecting] = useState(false)
382
+ const [showWalletConnectionRetry, setShowWalletConnectionRetry] =
383
+ useState(false)
352
384
  const [generatedCalldata, setGeneratedCalldata] = useState<
353
385
  string | undefined
354
386
  >(undefined)
@@ -364,10 +396,105 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
364
396
  const [totalCompletionSeconds, setTotalCompletionSeconds] = useState<
365
397
  number | null
366
398
  >(null)
367
- const { connect } = useConnect()
399
+ const { connectAsync } = useConnect()
400
+
401
+ // Validate widget props
402
+ useEffect(() => {
403
+ const isValidToAmount = isValidNumeric(toAmount)
404
+ const isValidToChainId = isValidInteger(toChainId)
405
+ const isValidToToken = isValidToToToken(toToken)
406
+ const isValidToAddress = toAddress ? isAddress(toAddress) : true
407
+
408
+ if (
409
+ isValidToAmount &&
410
+ isValidToChainId &&
411
+ isValidToToken &&
412
+ isValidToAddress
413
+ ) {
414
+ setError(null)
415
+ return
416
+ }
417
+
418
+ // Validate toAmount - must be numeric if provided
419
+ if (!isValidToAmount) {
420
+ console.error(
421
+ "[trails-sdk] Invalid toAmount prop: must be numeric. Received:",
422
+ toAmount,
423
+ )
424
+ setError("Invalid toAmount: must be a numeric value")
425
+ return
426
+ }
427
+
428
+ // Validate toChainId - must be numeric if provided
429
+ if (!isValidToChainId) {
430
+ console.error(
431
+ "[trails-sdk] Invalid toChainId prop: must be numeric. Received:",
432
+ toChainId,
433
+ )
434
+ setError("Invalid toChainId: must be a numeric value")
435
+ return
436
+ }
437
+
438
+ // Validate toToken - must be "ETH", "USDC", or a valid hex address
439
+ if (!isValidToToken) {
440
+ console.error(
441
+ "[trails-sdk] Invalid toToken prop: must be 'ETH', 'USDC', or a valid hex address. Received:",
442
+ toToken,
443
+ )
444
+ setError(
445
+ "Invalid toToken: must be 'ETH', 'USDC', or a valid hex address",
446
+ )
447
+ return
448
+ }
449
+
450
+ if (!isValidToAddress) {
451
+ console.error(
452
+ "[trails-sdk] Invalid toAddress prop: must be a valid hex address. Received:",
453
+ toAddress,
454
+ )
455
+ setError("Invalid toAddress: must be a valid hex address")
456
+ }
457
+
458
+ if (mode === "fund") {
459
+ if (toAmount) {
460
+ setError("toAmount is not allowed in fund mode")
461
+ return
462
+ }
463
+ }
464
+
465
+ if (mode === "swap") {
466
+ if (toAmount) {
467
+ setError("toAmount is not allowed in swap mode")
468
+ return
469
+ }
470
+ if (toChainId) {
471
+ setError("toChainId is not allowed in swap mode")
472
+ return
473
+ }
474
+ if (toToken) {
475
+ setError("toToken is not allowed in swap mode")
476
+ }
477
+ if (toAddress) {
478
+ setError("toAddress is not allowed in swap mode")
479
+ return
480
+ }
481
+ }
482
+ }, [toAmount, toChainId, toToken, mode, toAddress])
368
483
 
369
484
  const walletClient = useWalletManager(address, chainId, connector)
370
485
 
486
+ // Function to save wallet ID to localStorage
487
+ const saveLastClickedWallet = (walletId: string) => {
488
+ setSelectedWalletId(walletId)
489
+ if (typeof window !== "undefined") {
490
+ try {
491
+ localStorage.setItem("trails-last-wallet", walletId)
492
+ } catch (error) {
493
+ console.error("[trails-sdk] Failed to save to localStorage:", error)
494
+ }
495
+ }
496
+ }
497
+
371
498
  const [meshConnectProps, setMeshConnectProps] =
372
499
  useState<Partial<MeshConnectProps> | null>(null)
373
500
 
@@ -434,7 +561,7 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
434
561
 
435
562
  if (matchingPool) {
436
563
  console.log(
437
- "[trails-sdk] Auto-selected pool for toAddress:",
564
+ `[trails-sdk] Auto-selected pool for ${mode} mode toAddress:`,
438
565
  toAddress,
439
566
  "toChainId:",
440
567
  targetChainId,
@@ -445,7 +572,7 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
445
572
  setSelectedPool(matchingPool)
446
573
  } else {
447
574
  console.log(
448
- "[trails-sdk] No matching pool found for toAddress:",
575
+ `[trails-sdk] No matching pool found for ${mode} mode toAddress:`,
449
576
  toAddress,
450
577
  "toChainId:",
451
578
  targetChainId,
@@ -489,24 +616,43 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
489
616
  // Update screen based on connection state and mode
490
617
  useEffect(() => {
491
618
  if (isConnected) {
492
- if (currentScreen === "connect" || currentScreen === "wallet-list") {
619
+ if (
620
+ currentScreen === "connect" ||
621
+ currentScreen === "wallet-list" ||
622
+ currentScreen === "wallet-connection-pending"
623
+ ) {
493
624
  if (!alreadyRedirectedToTokens) {
494
625
  setAlreadyRedirectedToTokens(true)
495
- setCurrentScreen("tokens")
626
+ // For swap mode, go directly to swap screen, otherwise go to tokens
627
+ if (mode === "swap") {
628
+ setCurrentScreen("swap")
629
+ } else {
630
+ setCurrentScreen("tokens")
631
+ }
496
632
  }
497
633
  }
498
634
  } else {
499
635
  if (
500
636
  currentScreen !== "connect" &&
501
637
  currentScreen !== "wallet-connect" &&
502
- currentScreen !== "wallet-list"
638
+ currentScreen !== "wallet-list" &&
639
+ currentScreen !== "wallet-connection-pending"
503
640
  ) {
504
641
  setTimeout(() => {
505
642
  setCurrentScreen("connect")
506
643
  }, 0)
507
644
  }
508
645
  }
509
- }, [isConnected, currentScreen, alreadyRedirectedToTokens])
646
+ }, [isConnected, currentScreen, alreadyRedirectedToTokens, mode])
647
+
648
+ useEffect(() => {
649
+ if (
650
+ currentScreen === "wallet-connection-pending" &&
651
+ alreadyRedirectedToTokens
652
+ ) {
653
+ setAlreadyRedirectedToTokens(false)
654
+ }
655
+ }, [currentScreen, alreadyRedirectedToTokens])
510
656
 
511
657
  // Auto-detect mode changes and switch screens accordingly
512
658
  useEffect(() => {
@@ -532,6 +678,7 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
532
678
  if (!address || !chainId || !connector?.name) {
533
679
  return
534
680
  }
681
+
535
682
  trackWalletConnected({
536
683
  walletType: connector?.name || "",
537
684
  address,
@@ -550,34 +697,34 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
550
697
  }
551
698
  }, [selectedPool, mode, generatedCalldata])
552
699
 
553
- const indexerGatewayClient = useIndexerGatewayClient({
554
- indexerGatewayUrl: sequenceIndexerUrl || undefined,
555
- projectAccessKey: sequenceProjectAccessKey,
556
- })
557
-
558
- const apiClient = useAPIClient({
559
- apiUrl: sequenceApiUrl || undefined,
560
- projectAccessKey: sequenceProjectAccessKey,
561
- })
562
-
563
700
  const handleWalletConnect = async (walletId: string) => {
564
701
  try {
565
702
  setError(null)
703
+ setIsConnecting(true)
566
704
 
567
705
  // Handle special case for wallet-list screen
568
706
  if (walletId === "wallet-list") {
569
707
  setCurrentScreen("wallet-list")
708
+ setIsConnecting(false)
570
709
  return
571
710
  }
572
711
 
573
- const config = WALLET_CONFIGS[walletId as keyof typeof WALLET_CONFIGS]
712
+ const config = allWallets.find((w) => w.id === walletId)
574
713
  if (!config) {
575
714
  setError(`No configuration found for wallet: ${walletId}`)
715
+ setIsConnecting(false)
576
716
  return
577
717
  }
578
- console.log("[trails-sdk] Connecting to wallet", walletId)
718
+ console.log("[trails-sdk] Initiating connection to wallet", walletId)
579
719
  if (config.connector !== walletConnectConnector) {
580
- await connect({ connector: config.connector() })
720
+ console.log(
721
+ "[trails-sdk] Initiating connection to walletId",
722
+ walletId,
723
+ )
724
+ await connectAsync({ connector: config.connector })
725
+ console.log(`[trails-sdk] Successfully connected to ${config.name}`)
726
+ // Set the last clicked wallet after successful connection
727
+ saveLastClickedWallet(walletId)
581
728
  } else if (config.connector === walletConnectConnector) {
582
729
  // Store the current connector as previous before switching to WalletConnect
583
730
  if (connector && connector.name !== "WalletConnect") {
@@ -585,14 +732,16 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
585
732
  }
586
733
  // Route to dedicated WalletConnect screen where we show our own QR
587
734
  setCurrentScreen("wallet-connect")
735
+ setIsConnecting(false)
588
736
  return
589
737
  }
590
- console.log(`[trails-sdk] Connected to ${config.name}`)
738
+ setIsConnecting(false)
591
739
  } catch (error) {
592
740
  console.error("[trails-sdk] Failed to connect:", error)
593
741
  setError(
594
742
  error instanceof Error ? error.message : "Failed to connect wallet",
595
743
  )
744
+ setIsConnecting(false)
596
745
  }
597
746
  }
598
747
 
@@ -600,7 +749,7 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
600
749
  setError(null)
601
750
 
602
751
  try {
603
- await disconnect()
752
+ await disconnectAsync()
604
753
  setAlreadyRedirectedToTokens(false)
605
754
  } catch (error) {
606
755
  console.error("[trails-sdk] Failed to disconnect:", error)
@@ -610,15 +759,19 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
610
759
  }
611
760
 
612
761
  const handleContinue = () => {
613
- setCurrentScreen("tokens")
762
+ if (mode === "swap") {
763
+ setCurrentScreen("swap")
764
+ } else {
765
+ setCurrentScreen("tokens")
766
+ }
614
767
  }
615
768
 
616
769
  const getAvailableWallets = (): WalletOption[] => {
617
770
  const requestedWallets = walletOptions || defaultWalletOptions
618
771
  const availableWallets = requestedWallets
619
- .filter((id) => WALLET_CONFIGS[id as keyof typeof WALLET_CONFIGS])
772
+ .filter((id) => allWallets.find((w) => w.id === id))
620
773
  .map((id) => {
621
- const config = WALLET_CONFIGS[id as keyof typeof WALLET_CONFIGS]
774
+ const config = allWallets.find((w) => w.id === id)
622
775
  if (!config) return null
623
776
  return {
624
777
  id: config.id,
@@ -647,6 +800,8 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
647
800
  // Go to earn-pools for pool selection when no specific destination is set
648
801
  setCurrentScreen("earn-pools")
649
802
  }
803
+ } else if (mode === "swap") {
804
+ setCurrentScreen("swap")
650
805
  } else {
651
806
  setCurrentScreen(mode === "fund" ? "fund-form" : "send-form")
652
807
  }
@@ -696,15 +851,22 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
696
851
  }
697
852
 
698
853
  const handleSendAnother = () => {
699
- setCurrentScreen("tokens")
854
+ if (mode === "swap") {
855
+ setCurrentScreen("swap")
856
+ } else {
857
+ setCurrentScreen("tokens")
858
+ }
700
859
  resetState()
701
860
  }
702
861
 
703
862
  const resetState = useCallback(() => {
704
- setSelectedFundMethod(null)
863
+ setSelectedFundMethod("wallet")
705
864
  setCurrentScreen("connect")
706
865
  setSelectedToken(null)
707
866
  setSelectedPool(null)
867
+ setSelectedWalletId(null)
868
+ setIsConnecting(false)
869
+ setShowWalletConnectionRetry(false)
708
870
  setGeneratedCalldata(undefined)
709
871
  setOriginTxHash("")
710
872
  setOriginChainId(null)
@@ -755,6 +917,10 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
755
917
  setCurrentScreen("tokens")
756
918
  setSelectedToken(null)
757
919
  break
920
+ case "swap":
921
+ setCurrentScreen("connect")
922
+ setSelectedToken(null)
923
+ break
758
924
  case "send-form":
759
925
  if (mode === "earn" && !toAddress && !toChainId) {
760
926
  setCurrentScreen("earn-pools")
@@ -770,6 +936,9 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
770
936
  case "wallet-confirmation":
771
937
  setCurrentScreen(mode === "fund" ? "fund-form" : "send-form")
772
938
  break
939
+ case "qr-code-deposit":
940
+ setCurrentScreen(mode === "fund" ? "fund-form" : "send-form")
941
+ break
773
942
  case "receipt":
774
943
  setCurrentScreen("tokens")
775
944
  setSelectedToken(null)
@@ -783,10 +952,18 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
783
952
  break
784
953
  case "wallet-connect":
785
954
  setCurrentScreen("wallet-list")
955
+ setSelectedWalletId(null)
786
956
  break
787
957
  case "wallet-list":
788
958
  setCurrentScreen("connect")
789
959
  break
960
+ case "wallet-connection-pending":
961
+ setCurrentScreen("wallet-list")
962
+ setSelectedWalletId(null)
963
+ setError(null)
964
+ setIsConnecting(false)
965
+ setShowWalletConnectionRetry(false)
966
+ break
790
967
  default:
791
968
  break
792
969
  }
@@ -1240,12 +1417,26 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1240
1417
  case "wallet-list":
1241
1418
  setCurrentScreen("wallet-list")
1242
1419
  break
1420
+ case "wallet-connection-pending":
1421
+ setSelectedWalletId("metamask")
1422
+ setCurrentScreen("wallet-connection-pending")
1423
+ break
1424
+ case "wallet-connection-pending-retry":
1425
+ setSelectedWalletId("metamask")
1426
+ setShowWalletConnectionRetry(true)
1427
+ setCurrentScreen("wallet-connection-pending")
1428
+ break
1243
1429
  case "fund-methods":
1244
1430
  setCurrentScreen("fund-methods")
1245
1431
  break
1246
1432
  case "earn-pools":
1247
1433
  setCurrentScreen("earn-pools")
1248
1434
  break
1435
+ case "swap":
1436
+ setSelectedToken(null)
1437
+ setTransactionStates([])
1438
+ setCurrentScreen("swap")
1439
+ break
1249
1440
  }
1250
1441
  }
1251
1442
 
@@ -1266,7 +1457,7 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1266
1457
  )
1267
1458
 
1268
1459
  try {
1269
- await connect({ connector: previousConnector })
1460
+ await connectAsync({ connector: previousConnector })
1270
1461
  } catch (error) {
1271
1462
  console.error(
1272
1463
  "[trails-sdk] Failed to reconnect to previous wallet:",
@@ -1430,12 +1621,17 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1430
1621
  if (error) {
1431
1622
  console.error("[trails-sdk] Error sending transaction", error)
1432
1623
  }
1433
- const errorMessage = getErrorString(error)
1624
+ const errorMessage = getFullErrorMessage(error)
1434
1625
  const isRejected = getIsWalletRejectedError(error)
1435
1626
  const isBalanceTooLow = getIsBalanceTooLowError(error)
1436
- if (isRejected) {
1627
+ const isApiError = getIsApiError(error)
1628
+ const isRateLimited = getIsRateLimitedError(error)
1629
+
1630
+ if (isRateLimited) {
1631
+ // no-op
1632
+ } else if (isRejected) {
1437
1633
  setShowWalletConfirmRetry(true)
1438
- } else if (isBalanceTooLow) {
1634
+ } else if (isBalanceTooLow || isApiError) {
1439
1635
  setShowWalletConfirmRetry(true)
1440
1636
  setError(errorMessage)
1441
1637
  } else {
@@ -1489,7 +1685,6 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1489
1685
  amount: toAmount,
1490
1686
  token: toToken,
1491
1687
  chainId: Number(toChainId),
1492
- apiClient: apiClient,
1493
1688
  })
1494
1689
 
1495
1690
  const renderScreenContent = () => {
@@ -1497,11 +1692,27 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1497
1692
  case "connect":
1498
1693
  return (
1499
1694
  <ConnectWallet
1500
- onConnect={handleWalletConnect}
1695
+ onConnect={(walletId) => {
1696
+ if (
1697
+ allWallets.find((w) => w.id === walletId)?.connector ===
1698
+ walletConnectConnector
1699
+ ) {
1700
+ saveLastClickedWallet(walletId)
1701
+ setCurrentScreen("wallet-connect")
1702
+ } else {
1703
+ saveLastClickedWallet(walletId)
1704
+ setCurrentScreen("wallet-connection-pending")
1705
+ // Auto-trigger connection
1706
+ setTimeout(() => {
1707
+ handleWalletConnect(walletId)
1708
+ }, 100)
1709
+ }
1710
+ }}
1501
1711
  onDisconnect={handleWalletDisconnect}
1502
1712
  onContinue={handleContinue}
1503
1713
  walletOptions={getAvailableWallets()}
1504
1714
  onError={handleConnectError}
1715
+ lastClickedWallet={selectedWalletId}
1505
1716
  />
1506
1717
  )
1507
1718
  case "fund-methods":
@@ -1525,7 +1736,6 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1525
1736
  <TokenList
1526
1737
  onContinue={handleTokenSelect}
1527
1738
  onBack={handleBack}
1528
- indexerGatewayClient={indexerGatewayClient}
1529
1739
  targetAmountUsd={targetAmountUsd}
1530
1740
  targetAmountUsdFormatted={targetAmountUsdFormatted}
1531
1741
  onError={handleTokenListError}
@@ -1694,15 +1904,26 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1694
1904
  onBack={handleBack}
1695
1905
  onWalletSelect={(walletId) => {
1696
1906
  if (
1697
- WALLET_CONFIGS[walletId]?.connector === walletConnectConnector
1907
+ allWallets.find((w) => w.id === walletId)?.connector ===
1908
+ walletConnectConnector
1698
1909
  ) {
1699
- setSelectedWalletId(walletId)
1910
+ saveLastClickedWallet(walletId)
1700
1911
  setCurrentScreen("wallet-connect")
1701
1912
  } else {
1702
- handleWalletConnect(walletId)
1913
+ saveLastClickedWallet(walletId)
1914
+ setCurrentScreen("wallet-connection-pending")
1915
+ // Auto-trigger connection
1916
+ setTimeout(() => {
1917
+ handleWalletConnect(walletId)
1918
+ }, 100)
1703
1919
  }
1704
1920
  }}
1705
- walletOptions={Object.values(WALLET_CONFIGS)}
1921
+ walletOptions={allWallets.map((wallet) => ({
1922
+ id: wallet.id,
1923
+ name: wallet.name,
1924
+ icon: wallet.icon,
1925
+ connector: wallet.connector,
1926
+ }))}
1706
1927
  />
1707
1928
  )
1708
1929
  case "earn-pools":
@@ -1716,6 +1937,55 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1716
1937
  }}
1717
1938
  />
1718
1939
  )
1940
+ case "swap":
1941
+ return walletClient?.account ? (
1942
+ <Swap
1943
+ onSend={handleOnSend}
1944
+ onBack={handleBack}
1945
+ onWaitingForWalletConfirm={handleWaitingForWalletConfirm}
1946
+ onConfirm={() => setCurrentScreen("pending")}
1947
+ onComplete={handleTransferComplete}
1948
+ selectedToken={selectedToken}
1949
+ account={walletClient.account}
1950
+ toRecipient={toAddress || undefined}
1951
+ toAmount={toAmount || undefined}
1952
+ toChainId={toChainId ? Number(toChainId) : undefined}
1953
+ toToken={toToken || undefined}
1954
+ toCalldata={toCalldata || undefined}
1955
+ walletClient={walletClient}
1956
+ onTransactionStateChange={handleTransactionStateChange}
1957
+ onError={handleSendError}
1958
+ paymasterUrls={paymasterUrls}
1959
+ gasless={gasless}
1960
+ setWalletConfirmRetryHandler={setWalletConfirmRetryHandler}
1961
+ quoteProvider={quoteProvider}
1962
+ fundMethod={selectedFundMethod}
1963
+ onNavigateToMeshConnect={handleNavigateToMeshConnect}
1964
+ onAmountUpdate={undefined}
1965
+ mode={mode}
1966
+ checkoutOnHandlers={checkoutOnHandlers}
1967
+ />
1968
+ ) : (
1969
+ <div className="text-center p-4 rounded-lg text-gray-600 bg-gray-50 dark:text-gray-300 dark:bg-gray-800">
1970
+ Please connect wallet
1971
+ </div>
1972
+ )
1973
+ case "wallet-connection-pending":
1974
+ return (
1975
+ <WalletConnectionPending
1976
+ onBack={handleBack}
1977
+ onRetry={() => {
1978
+ if (selectedWalletId) {
1979
+ setShowWalletConnectionRetry(false)
1980
+ handleWalletConnect(selectedWalletId)
1981
+ }
1982
+ }}
1983
+ selectedWalletId={selectedWalletId || ""}
1984
+ isConnecting={isConnecting}
1985
+ error={error}
1986
+ showRetry={showWalletConnectionRetry}
1987
+ />
1988
+ )
1719
1989
  default:
1720
1990
  return null
1721
1991
  }
@@ -1781,7 +2051,8 @@ const WidgetInner = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1781
2051
  onClick={() => setIsModalOpen(true)}
1782
2052
  className="trails-modal-button cursor-pointer font-semibold py-3 px-6 trails-font"
1783
2053
  >
1784
- {buttonText || (mode === "fund" ? "Fund" : "Pay")}
2054
+ {buttonText ||
2055
+ (mode === "fund" ? "Fund" : mode === "swap" ? "Swap" : "Pay")}
1785
2056
  </motion.button>
1786
2057
  ) : (
1787
2058
  <motion.div
@@ -1858,15 +2129,7 @@ export const TrailsWidget = forwardRef<TrailsWidgetRef, TrailsWidgetProps>(
1858
2129
 
1859
2130
  return createConfig({
1860
2131
  ...baseConfig,
1861
- connectors: [
1862
- injected(),
1863
- walletConnect({
1864
- projectId: getWalletConnectProjectId(),
1865
- showQrModal: false,
1866
- }),
1867
- safe(),
1868
- baseAccount(),
1869
- ],
2132
+ connectors: connectors,
1870
2133
  })
1871
2134
  }, [])
1872
2135