0xtrails 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/analytics.d.ts +1 -0
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/{ccip-D6ToCrWc.js → ccip-BbfANth7.js} +1 -1
  4. package/dist/chains.d.ts.map +1 -1
  5. package/dist/config.d.ts +1 -2
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/constants.d.ts +2 -2
  8. package/dist/constants.d.ts.map +1 -1
  9. package/dist/gasless.d.ts +19 -7
  10. package/dist/gasless.d.ts.map +1 -1
  11. package/dist/{index-BqgeTLL8.js → index-WpIVoh3X.js} +27626 -26572
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +68 -68
  15. package/dist/indexerClient.d.ts +10 -0
  16. package/dist/indexerClient.d.ts.map +1 -1
  17. package/dist/intentEntrypoint.d.ts +40 -14
  18. package/dist/intentEntrypoint.d.ts.map +1 -1
  19. package/dist/intents.d.ts.map +1 -1
  20. package/dist/prepareSend.d.ts +11 -8
  21. package/dist/prepareSend.d.ts.map +1 -1
  22. package/dist/relayer.d.ts.map +1 -1
  23. package/dist/trails.d.ts.map +1 -1
  24. package/dist/trailsClient.d.ts.map +1 -1
  25. package/dist/trailsRouter.d.ts +22 -0
  26. package/dist/trailsRouter.d.ts.map +1 -0
  27. package/dist/transactions.d.ts +0 -1
  28. package/dist/transactions.d.ts.map +1 -1
  29. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  30. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  31. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  32. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  33. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  34. package/dist/widget/components/Earn.d.ts.map +1 -1
  35. package/dist/widget/components/FeeOption.d.ts +22 -0
  36. package/dist/widget/components/FeeOption.d.ts.map +1 -0
  37. package/dist/widget/components/FeeOptions.d.ts +13 -17
  38. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  39. package/dist/widget/components/Fund.d.ts.map +1 -1
  40. package/dist/widget/components/FundMethods.d.ts +1 -1
  41. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  42. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  43. package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
  44. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  45. package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
  46. package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
  47. package/dist/widget/components/NativeGasOption.d.ts +12 -0
  48. package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
  49. package/dist/widget/components/Pay.d.ts.map +1 -1
  50. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  51. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  52. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  53. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  54. package/dist/widget/hooks/useBack.d.ts +2 -0
  55. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  56. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  57. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  58. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  59. package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
  60. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
  61. package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
  62. package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
  63. package/dist/widget/hooks/useSendForm.d.ts +8 -13
  64. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  65. package/dist/widget/index.js +1 -1
  66. package/dist/widget/widget.d.ts.map +1 -1
  67. package/package.json +29 -28
  68. package/src/analytics.ts +6 -0
  69. package/src/chains.ts +10 -0
  70. package/src/config.ts +25 -10
  71. package/src/constants.ts +11 -10
  72. package/src/gasless.ts +162 -109
  73. package/src/index.ts +1 -1
  74. package/src/indexerClient.ts +73 -1
  75. package/src/intentEntrypoint.ts +66 -101
  76. package/src/intents.ts +0 -2
  77. package/src/prepareSend.ts +1409 -887
  78. package/src/relayer.ts +4 -3
  79. package/src/trails.ts +1 -3
  80. package/src/trailsClient.ts +4 -1
  81. package/src/{balanceInjector.ts → trailsRouter.ts} +14 -14
  82. package/src/transactions.ts +4 -54
  83. package/src/widget/compiled.css +1 -1
  84. package/src/widget/components/AccountSettings.tsx +7 -1
  85. package/src/widget/components/ClassicSwap.tsx +173 -175
  86. package/src/widget/components/ConfigDisplay.tsx +34 -1
  87. package/src/widget/components/ConnectWallet.tsx +168 -11
  88. package/src/widget/components/ConnectedWallets.tsx +184 -102
  89. package/src/widget/components/DebugToast.tsx +3 -3
  90. package/src/widget/components/Earn.tsx +4 -27
  91. package/src/widget/components/FeeOption.tsx +78 -0
  92. package/src/widget/components/FeeOptions.tsx +192 -127
  93. package/src/widget/components/Fund.tsx +18 -27
  94. package/src/widget/components/FundMethods.tsx +3 -3
  95. package/src/widget/components/FundSendForm.tsx +0 -33
  96. package/src/widget/components/MeshConnectExchanges.tsx +32 -3
  97. package/src/widget/components/MeshConnectFlow.tsx +23 -4
  98. package/src/widget/components/NativeGasOption.tsx +99 -0
  99. package/src/widget/components/Pay.tsx +36 -32
  100. package/src/widget/components/PaySendForm.tsx +0 -37
  101. package/src/widget/components/QuoteDetails.tsx +0 -29
  102. package/src/widget/components/TokenSelector.tsx +11 -0
  103. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  104. package/src/widget/components/UserPreferences.tsx +3 -4
  105. package/src/widget/hooks/useBack.tsx +4 -0
  106. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  107. package/src/widget/hooks/useDefaultTokenSelection.tsx +3 -7
  108. package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
  109. package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
  110. package/src/widget/hooks/useSendForm.ts +78 -23
  111. package/src/widget/widget.tsx +173 -111
  112. package/dist/balanceInjector.d.ts +0 -22
  113. package/dist/balanceInjector.d.ts.map +0 -1
@@ -1,6 +1,11 @@
1
1
  import React, { useEffect, useState } from "react"
2
2
  import { ChevronRight, LogOut } from "lucide-react"
3
- import { useAccount, useConnections, useSwitchAccount } from "wagmi"
3
+ import {
4
+ useAccount,
5
+ useConnections,
6
+ useSwitchAccount,
7
+ useConnectors,
8
+ } from "wagmi"
4
9
  import { AlignJustify, Wallet } from "lucide-react"
5
10
  import { ScreenHeader } from "./ScreenHeader.js"
6
11
  import { useWallets, wagmiConnectorToWalletId } from "../../wallets.js"
@@ -35,6 +40,7 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
35
40
  const { switchAccount } = useSwitchAccount()
36
41
  const { setCurrentScreen } = useCurrentScreen()
37
42
  const { wallets: allWallets } = useWallets()
43
+ const connectors = useConnectors()
38
44
  const [error, setError] = useState<string | null>(null)
39
45
 
40
46
  useEffect(() => {
@@ -96,6 +102,87 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
96
102
 
97
103
  const connectedWallets = getConnectedWallets()
98
104
 
105
+ // Get available browser wallets using EIP-6963 and injected providers
106
+ const getAvailableBrowserWallets = (): Array<{
107
+ connector: any
108
+ walletId: string
109
+ walletConfig: any
110
+ name: string
111
+ icon?: string
112
+ }> => {
113
+ const filteredConnectors = connectors
114
+ .filter((connector) => {
115
+ // EIP-6963 compliant wallets will have type "injected" and be ready when installed
116
+ // Filter for connectors that are:
117
+ // 1. Injected type (browser extensions, including EIP-6963)
118
+ // 2. Not WalletConnect (which is a protocol, not a browser wallet)
119
+ // 3. Not Privy wallet (should not appear in detected wallets)
120
+ // 4. Not already connected
121
+ const isInjected = connector.type === "injected"
122
+ const isNotWalletConnect = connector.id !== "walletConnect"
123
+ const isNotPrivy =
124
+ connector.id !== "privy" &&
125
+ !connector.name?.toLowerCase().includes("privy") &&
126
+ !connector.id?.toLowerCase().includes("privy")
127
+ const isNotAlreadyConnected = !connections.some(
128
+ (conn) => conn.connector.id === connector.id,
129
+ )
130
+
131
+ // Log for debugging EIP-6963 detection
132
+ if (isInjected && isNotWalletConnect) {
133
+ logger.console.log(
134
+ "[trails-sdk] Detected browser wallet via EIP-6963:",
135
+ {
136
+ id: connector.id,
137
+ name: connector.name,
138
+ type: connector.type,
139
+ uid: connector.uid,
140
+ hasIcon: !!connector.icon,
141
+ },
142
+ )
143
+ }
144
+
145
+ // Log all connectors for debugging
146
+ logger.console.log("[trails-sdk] All connector:", {
147
+ id: connector.id,
148
+ name: connector.name,
149
+ type: connector.type,
150
+ })
151
+
152
+ return (
153
+ isInjected &&
154
+ isNotWalletConnect &&
155
+ isNotPrivy &&
156
+ isNotAlreadyConnected
157
+ )
158
+ })
159
+ .map((connector) => {
160
+ const walletId = wagmiConnectorToWalletId(connector)
161
+ const walletConfig = allWallets.find((wallet) => wallet.id === walletId)
162
+
163
+ return {
164
+ connector,
165
+ walletId,
166
+ walletConfig,
167
+ name: connector.name,
168
+ // EIP-6963 providers may have additional metadata including icon
169
+ icon: connector.icon || walletConfig?.icon,
170
+ }
171
+ })
172
+
173
+ // Deduplicate by walletId, keeping the first occurrence
174
+ const seenWalletIds = new Set<string>()
175
+ return filteredConnectors.filter((wallet) => {
176
+ if (seenWalletIds.has(wallet.walletId)) {
177
+ return false
178
+ }
179
+ seenWalletIds.add(wallet.walletId)
180
+ return true
181
+ })
182
+ }
183
+
184
+ const allAvailableBrowserWallets = getAvailableBrowserWallets()
185
+
99
186
  // Handle switching to a different connected wallet
100
187
  const handleWalletSwitch = async (
101
188
  walletAddress: string,
@@ -160,6 +247,14 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
160
247
  return filteredWallets
161
248
  }, [lastClickedWallet, allWallets])
162
249
 
250
+ // Filter out browser wallets that are already in the main wallet options to avoid showing same wallet twice
251
+ const availableBrowserWallets = React.useMemo(() => {
252
+ const mainWalletIds = new Set(orderedWalletOptions.map((w) => w.id))
253
+ return allAvailableBrowserWallets.filter(
254
+ (wallet) => !mainWalletIds.has(wallet.walletId),
255
+ )
256
+ }, [allAvailableBrowserWallets, orderedWalletOptions])
257
+
163
258
  return (
164
259
  <div className="space-y-6">
165
260
  <ScreenHeader
@@ -171,14 +266,6 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
171
266
  {isConnected ? (
172
267
  <div className="space-y-4">
173
268
  <div className="flex flex-col gap-3">
174
- {error && (
175
- <div className="border rounded-lg p-4 bg-red-50 border-red-200 dark:bg-red-900/20 dark:border-red-800">
176
- <p className="text-sm break-words text-red-600 dark:text-red-200">
177
- {error}
178
- </p>
179
- </div>
180
- )}
181
-
182
269
  {/* Connected Wallets List */}
183
270
  {connectedWallets.map((wallet) => (
184
271
  <div key={wallet.address} className="space-y-2">
@@ -242,6 +329,39 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
242
329
  </div>
243
330
  ))}
244
331
 
332
+ {/* Available Browser Wallets Section */}
333
+ {availableBrowserWallets.length > 0 && (
334
+ <div className="pt-2 space-y-2">
335
+ {availableBrowserWallets.map((wallet) => (
336
+ <button
337
+ key={wallet.connector.uid || wallet.connector.id}
338
+ type="button"
339
+ onClick={() => handleWalletConnect(wallet.walletId)}
340
+ className="w-full flex items-center justify-between cursor-pointer font-semibold py-4 px-6 trails-border-radius-large-button transition-all duration-200 trails-bg-secondary trails-hover-bg trails-text-primary"
341
+ >
342
+ <div className="flex items-center space-x-3">
343
+ {typeof wallet.icon === "string" ? (
344
+ <img
345
+ src={wallet.icon}
346
+ alt={wallet.name}
347
+ className="h-6 w-6"
348
+ />
349
+ ) : (
350
+ <Wallet className="h-6 w-6 text-gray-600 dark:text-gray-400" />
351
+ )}
352
+ <div className="flex items-center space-x-2">
353
+ <span>{wallet.walletConfig?.name || wallet.name}</span>
354
+ <span className="text-xs bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 px-2 py-1 trails-border-radius-container font-normal">
355
+ Detected
356
+ </span>
357
+ </div>
358
+ </div>
359
+ <ChevronRight className="h-5 w-5 text-gray-400" />
360
+ </button>
361
+ ))}
362
+ </div>
363
+ )}
364
+
245
365
  {/* Connect Another Wallet Button */}
246
366
  <button
247
367
  type="button"
@@ -273,6 +393,36 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
273
393
  </div>
274
394
  ) : (
275
395
  <div className="flex flex-col gap-3">
396
+ {/* Available Browser Wallets Section - Not Connected */}
397
+ {availableBrowserWallets.length > 0 &&
398
+ availableBrowserWallets.map((wallet) => (
399
+ <button
400
+ key={wallet.connector.uid || wallet.connector.id}
401
+ type="button"
402
+ onClick={() => handleWalletConnect(wallet.walletId)}
403
+ className="w-full flex items-center justify-between cursor-pointer font-semibold py-4 px-6 trails-border-radius-large-button transition-all duration-200 trails-bg-secondary trails-hover-bg trails-text-primary"
404
+ >
405
+ <div className="flex items-center space-x-3">
406
+ {typeof wallet.icon === "string" ? (
407
+ <img
408
+ src={wallet.icon}
409
+ alt={wallet.name}
410
+ className="h-6 w-6"
411
+ />
412
+ ) : (
413
+ <Wallet className="h-6 w-6 text-gray-600 dark:text-gray-400" />
414
+ )}
415
+ <div className="flex items-center space-x-2">
416
+ <span>{wallet.walletConfig?.name || wallet.name}</span>
417
+ <span className="text-xs bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 px-2 py-1 trails-border-radius-container font-normal">
418
+ Detected
419
+ </span>
420
+ </div>
421
+ </div>
422
+ <ChevronRight className="h-5 w-5 text-gray-400" />
423
+ </button>
424
+ ))}
425
+
276
426
  {orderedWalletOptions.length > 0 ? (
277
427
  <>
278
428
  {orderedWalletOptions.map((wallet) => (
@@ -301,11 +451,18 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
301
451
  )}
302
452
  <div className="flex items-center space-x-2">
303
453
  <span>{walletConfig?.name || wallet.name}</span>
304
- {lastClickedWallet === wallet.id && (
454
+ {lastClickedWallet === wallet.id ? (
305
455
  <span className="text-xs bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 px-2 py-1 trails-border-radius-container font-normal">
306
456
  Recent
307
457
  </span>
308
- )}
458
+ ) : allAvailableBrowserWallets.some(
459
+ (detectedWallet) =>
460
+ detectedWallet.walletId === wallet.id,
461
+ ) ? (
462
+ <span className="text-xs bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 px-2 py-1 trails-border-radius-container font-normal">
463
+ Detected
464
+ </span>
465
+ ) : null}
309
466
  </div>
310
467
  </>
311
468
  )
@@ -1,10 +1,17 @@
1
1
  import { useState } from "react"
2
2
  import type React from "react"
3
- import { Wallet, Copy } from "lucide-react"
4
- import { useAccount, useConnections, useSwitchAccount } from "wagmi"
3
+ import { Wallet, Copy, X } from "lucide-react"
4
+ import {
5
+ useAccount,
6
+ useConnections,
7
+ useSwitchAccount,
8
+ useDisconnect,
9
+ } from "wagmi"
5
10
  import { useWallets, wagmiConnectorToWalletId } from "../../wallets.js"
6
11
  import { logger } from "../../logger.js"
7
12
  import { truncateAddress } from "../../utils.js"
13
+ import { useAccountTotalBalanceUsd } from "../../tokenBalances.js"
14
+ import { Identicon } from "./Identicon.js"
8
15
 
9
16
  export interface ConnectedWalletsProps {
10
17
  onWalletSwitch?: (address: string) => void
@@ -12,6 +19,153 @@ export interface ConnectedWalletsProps {
12
19
  className?: string
13
20
  }
14
21
 
22
+ interface WalletItemProps {
23
+ wallet: {
24
+ address: string
25
+ connector: any
26
+ walletConfig: any
27
+ walletId: string
28
+ isActive: boolean
29
+ }
30
+ onWalletSwitch: (address: string, connector: any) => void
31
+ onDisconnect: (connector: any, e: React.MouseEvent) => void
32
+ onCopyAddress: (address: string, e: React.MouseEvent) => void
33
+ copiedAddress: string | null
34
+ }
35
+
36
+ const WalletItem: React.FC<WalletItemProps> = ({
37
+ wallet,
38
+ onWalletSwitch,
39
+ onDisconnect,
40
+ onCopyAddress,
41
+ copiedAddress,
42
+ }) => {
43
+ const { totalBalanceUsdFormatted, isLoadingTotalBalanceUsd } =
44
+ useAccountTotalBalanceUsd(wallet.address)
45
+
46
+ return (
47
+ <div className="space-y-1">
48
+ {/* Wallet Button */}
49
+ <div
50
+ onClick={() => {
51
+ if (!wallet.isActive) {
52
+ onWalletSwitch(wallet.address, wallet.connector)
53
+ }
54
+ }}
55
+ onKeyDown={(e) => {
56
+ if ((e.key === "Enter" || e.key === " ") && !wallet.isActive) {
57
+ e.preventDefault()
58
+ onWalletSwitch(wallet.address, wallet.connector)
59
+ }
60
+ }}
61
+ tabIndex={wallet.isActive ? -1 : 0}
62
+ className={`group w-full flex items-center justify-between font-medium py-2 px-3 rounded-lg transition-all duration-200 ${
63
+ wallet.isActive
64
+ ? "bg-blue-50 dark:bg-blue-900/20 text-gray-700 dark:text-gray-300 cursor-default"
65
+ : "bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-300 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600"
66
+ }`}
67
+ >
68
+ <div className="flex items-center space-x-3">
69
+ {/* Identicon */}
70
+ <Identicon value={wallet.address} size={32} />
71
+ <div className="flex flex-col items-start space-y-1">
72
+ <div className="flex items-center space-x-2">
73
+ {/* Wallet Icon */}
74
+ {typeof wallet.walletConfig?.icon === "string" ? (
75
+ <img
76
+ src={wallet.walletConfig.icon}
77
+ alt={wallet.walletConfig.name}
78
+ className="h-4 w-4"
79
+ />
80
+ ) : (
81
+ <Wallet className="h-4 w-4 text-gray-600 dark:text-gray-400" />
82
+ )}
83
+ <span className="text-sm">
84
+ {wallet.walletConfig?.name ||
85
+ wallet.connector?.name ||
86
+ "Wallet"}
87
+ </span>
88
+ {!isLoadingTotalBalanceUsd && totalBalanceUsdFormatted && (
89
+ <span className="text-xs text-gray-400 dark:text-gray-500">
90
+ {totalBalanceUsdFormatted}
91
+ </span>
92
+ )}
93
+ </div>
94
+ <div className="flex items-center space-x-1">
95
+ <span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
96
+ {truncateAddress(wallet.address)}
97
+ </span>
98
+ <button
99
+ type="button"
100
+ onClick={(e) => {
101
+ e.preventDefault()
102
+ e.stopPropagation()
103
+ onCopyAddress(wallet.address, e)
104
+ }}
105
+ onMouseDown={(e) => e.stopPropagation()}
106
+ onMouseUp={(e) => e.stopPropagation()}
107
+ className={`p-0.5 rounded transition-all duration-200 cursor-pointer z-10 relative ${
108
+ copiedAddress === wallet.address
109
+ ? "bg-green-100 dark:bg-green-900/30"
110
+ : "hover:bg-gray-200 dark:hover:bg-gray-600"
111
+ }`}
112
+ title={
113
+ copiedAddress === wallet.address
114
+ ? "Copied!"
115
+ : "Copy full address"
116
+ }
117
+ >
118
+ {copiedAddress === wallet.address ? (
119
+ <svg
120
+ className="w-3 h-3 text-green-600 dark:text-green-400"
121
+ fill="none"
122
+ viewBox="0 0 24 24"
123
+ stroke="currentColor"
124
+ aria-label="Copied"
125
+ >
126
+ <title>Copied</title>
127
+ <path
128
+ strokeLinecap="round"
129
+ strokeLinejoin="round"
130
+ strokeWidth={2}
131
+ d="M5 13l4 4L19 7"
132
+ />
133
+ </svg>
134
+ ) : (
135
+ <Copy className="w-3 h-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" />
136
+ )}
137
+ </button>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <div className="flex items-center gap-2">
143
+ {wallet.isActive ? (
144
+ <span className="text-xs px-2 py-1 rounded font-medium bg-blue-500 text-white">
145
+ Active
146
+ </span>
147
+ ) : (
148
+ <span className="text-xs px-2 py-1 rounded font-medium bg-gray-200 text-gray-700 dark:bg-gray-600 dark:text-gray-300 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
149
+ Set as active
150
+ </span>
151
+ )}
152
+ <button
153
+ type="button"
154
+ onClick={(e) => onDisconnect(wallet.connector, e)}
155
+ onMouseDown={(e) => e.stopPropagation()}
156
+ onMouseUp={(e) => e.stopPropagation()}
157
+ className="p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 opacity-0 group-hover:opacity-100 transition-all duration-200 cursor-pointer"
158
+ title="Disconnect wallet"
159
+ aria-label="Disconnect wallet"
160
+ >
161
+ <X className="w-4 h-4 text-gray-500 dark:text-gray-400" />
162
+ </button>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ )
167
+ }
168
+
15
169
  export const ConnectedWallets: React.FC<ConnectedWalletsProps> = ({
16
170
  onWalletSwitch,
17
171
  showActiveWallet = true,
@@ -20,6 +174,7 @@ export const ConnectedWallets: React.FC<ConnectedWalletsProps> = ({
20
174
  const { address, connector } = useAccount()
21
175
  const connections = useConnections()
22
176
  const { switchAccount } = useSwitchAccount()
177
+ const { disconnect } = useDisconnect()
23
178
  const { wallets: allWallets } = useWallets()
24
179
  const [error, setError] = useState<string | null>(null)
25
180
  const [copiedAddress, setCopiedAddress] = useState<string | null>(null)
@@ -87,6 +242,25 @@ export const ConnectedWallets: React.FC<ConnectedWalletsProps> = ({
87
242
  }
88
243
  }
89
244
 
245
+ // Handle disconnecting a specific wallet
246
+ const handleDisconnect = async (
247
+ walletConnector: any,
248
+ e: React.MouseEvent,
249
+ ) => {
250
+ e.preventDefault()
251
+ e.stopPropagation()
252
+ try {
253
+ setError(null)
254
+ await disconnect({ connector: walletConnector })
255
+ logger.console.log("[trails-sdk] Disconnected wallet")
256
+ } catch (error) {
257
+ logger.console.error("[trails-sdk] Failed to disconnect wallet:", error)
258
+ setError(
259
+ error instanceof Error ? error.message : "Failed to disconnect wallet",
260
+ )
261
+ }
262
+ }
263
+
90
264
  // Handle copy to clipboard with success indication
91
265
  const handleCopyAddress = async (address: string, e: React.MouseEvent) => {
92
266
  e.preventDefault()
@@ -151,106 +325,14 @@ export const ConnectedWallets: React.FC<ConnectedWalletsProps> = ({
151
325
  }
152
326
 
153
327
  return (
154
- <div key={wallet.address} className="space-y-1">
155
- {/* Wallet Button */}
156
- <div
157
- onClick={() => {
158
- if (!wallet.isActive) {
159
- handleWalletSwitch(wallet.address, wallet.connector)
160
- }
161
- }}
162
- onKeyDown={(e) => {
163
- if ((e.key === "Enter" || e.key === " ") && !wallet.isActive) {
164
- e.preventDefault()
165
- handleWalletSwitch(wallet.address, wallet.connector)
166
- }
167
- }}
168
- tabIndex={wallet.isActive ? -1 : 0}
169
- className={`group w-full flex items-center justify-between font-medium py-2 px-3 rounded-lg transition-all duration-200 ${
170
- wallet.isActive
171
- ? "bg-blue-50 dark:bg-blue-900/20 text-gray-700 dark:text-gray-300 cursor-default"
172
- : "bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-300 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600"
173
- }`}
174
- >
175
- <div className="flex items-center space-x-3">
176
- {typeof wallet.walletConfig?.icon === "string" ? (
177
- <img
178
- src={wallet.walletConfig.icon}
179
- alt={wallet.walletConfig.name}
180
- className="h-5 w-5"
181
- />
182
- ) : (
183
- <Wallet className="h-5 w-5 text-gray-600 dark:text-gray-400" />
184
- )}
185
- <div className="flex flex-col items-start space-y-1">
186
- <div className="flex items-center space-x-2">
187
- <span className="text-sm">
188
- {wallet.walletConfig?.name ||
189
- wallet.connector?.name ||
190
- "Wallet"}
191
- </span>
192
- </div>
193
- <div className="flex items-center space-x-1">
194
- <span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
195
- {truncateAddress(wallet.address)}
196
- </span>
197
- <button
198
- type="button"
199
- onClick={(e) => {
200
- e.preventDefault()
201
- e.stopPropagation()
202
- handleCopyAddress(wallet.address, e)
203
- }}
204
- onMouseDown={(e) => e.stopPropagation()}
205
- onMouseUp={(e) => e.stopPropagation()}
206
- className={`p-0.5 rounded transition-all duration-200 cursor-pointer z-10 relative ${
207
- copiedAddress === wallet.address
208
- ? "bg-green-100 dark:bg-green-900/30"
209
- : "hover:bg-gray-200 dark:hover:bg-gray-600"
210
- }`}
211
- title={
212
- copiedAddress === wallet.address
213
- ? "Copied!"
214
- : "Copy full address"
215
- }
216
- >
217
- {copiedAddress === wallet.address ? (
218
- <svg
219
- className="w-3 h-3 text-green-600 dark:text-green-400"
220
- fill="none"
221
- viewBox="0 0 24 24"
222
- stroke="currentColor"
223
- aria-label="Copied"
224
- >
225
- <title>Copied</title>
226
- <path
227
- strokeLinecap="round"
228
- strokeLinejoin="round"
229
- strokeWidth={2}
230
- d="M5 13l4 4L19 7"
231
- />
232
- </svg>
233
- ) : (
234
- <Copy className="w-3 h-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" />
235
- )}
236
- </button>
237
- </div>
238
- </div>
239
- </div>
240
-
241
- <div className="flex items-center">
242
- {wallet.isActive ? (
243
- <span className="text-xs px-2 py-1 rounded font-medium bg-blue-500 text-white">
244
- Active
245
- </span>
246
- ) : (
247
- <span className="text-xs px-2 py-1 rounded font-medium bg-gray-200 text-gray-700 dark:bg-gray-600 dark:text-gray-300 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
248
- Set as active
249
- </span>
250
- )}
251
- </div>
252
- </div>
253
- </div>
328
+ <WalletItem
329
+ key={wallet.address}
330
+ wallet={wallet}
331
+ onWalletSwitch={handleWalletSwitch}
332
+ onDisconnect={handleDisconnect}
333
+ onCopyAddress={handleCopyAddress}
334
+ copiedAddress={copiedAddress}
335
+ />
254
336
  )
255
337
  })}
256
338
  </div>
@@ -38,21 +38,21 @@ export const DebugToast: React.FC = () => {
38
38
  <button
39
39
  type="button"
40
40
  onClick={handleShowInfo}
41
- className="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:hover:bg-blue-900/50 transition-colors"
41
+ className="px-2 py-1 text-xs font-medium rounded bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:hover:bg-blue-900/50 transition-colors cursor-pointer"
42
42
  >
43
43
  info
44
44
  </button>
45
45
  <button
46
46
  type="button"
47
47
  onClick={handleShowSuccess}
48
- className="px-2 py-1 text-xs font-medium rounded bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50 transition-colors"
48
+ className="px-2 py-1 text-xs font-medium rounded bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50 transition-colors cursor-pointer"
49
49
  >
50
50
  success
51
51
  </button>
52
52
  <button
53
53
  type="button"
54
54
  onClick={handleShowError}
55
- className="px-2 py-1 text-xs font-medium rounded bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50 transition-colors"
55
+ className="px-2 py-1 text-xs font-medium rounded bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50 transition-colors cursor-pointer"
56
56
  >
57
57
  error
58
58
  </button>
@@ -27,8 +27,6 @@ import { ChainList } from "./ChainList.js"
27
27
  import { QuoteDetails } from "./QuoteDetails.js"
28
28
  import { formatTvl } from "../../prices.js"
29
29
  import { getExplorerUrlForAddress } from "../../explorer.js"
30
- import { formatUsdAmountDisplay } from "../../tokenBalances.js"
31
- import { MINIMUM_USD_AMOUNT_FOR_SWAP } from "../../constants.js"
32
30
  import aaveLogo from "../assets/aave.svg"
33
31
  import morphoLogo from "../assets/morpho.svg"
34
32
  import type { PrepareSendQuote } from "../../prepareSend.js"
@@ -340,7 +338,10 @@ export const Earn: React.FC<EarnProps> = ({
340
338
  <div className="flex items-center space-x-2">
341
339
  {/* Amount Input */}
342
340
  <div className="flex-1">
343
- <div className="flex items-center justify-start">
341
+ <div
342
+ className="flex items-center justify-start cursor-text"
343
+ onClick={() => inputRef.current?.focus()}
344
+ >
344
345
  <div className="flex items-center">
345
346
  <input
346
347
  ref={inputRef}
@@ -623,30 +624,6 @@ export const Earn: React.FC<EarnProps> = ({
623
624
  </p>
624
625
  </div>
625
626
  </div>
626
- ) : prepareSendQuote?.minimumNotMet ? (
627
- <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
628
- <div className="flex items-center space-x-2">
629
- <svg
630
- className="w-4 h-4 text-amber-500 flex-shrink-0"
631
- fill="none"
632
- stroke="currentColor"
633
- viewBox="0 0 24 24"
634
- aria-hidden="true"
635
- >
636
- <path
637
- strokeLinecap="round"
638
- strokeLinejoin="round"
639
- strokeWidth={2}
640
- d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
641
- />
642
- </svg>
643
- <p className="text-sm text-amber-600 dark:text-amber-400">
644
- Please enter an amount above{" "}
645
- {formatUsdAmountDisplay(MINIMUM_USD_AMOUNT_FOR_SWAP)} otherwise
646
- transfer may fail
647
- </p>
648
- </div>
649
- </div>
650
627
  ) : null}
651
628
 
652
629
  <form onSubmit={handleSubmit}>