0xtrails 0.1.2 → 0.1.4

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 (158) hide show
  1. package/dist/address.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +86 -1
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/apiClient.d.ts +1 -1
  5. package/dist/apiClient.d.ts.map +1 -1
  6. package/dist/{ccip-BmFTEOaB.js → ccip-dLSEJjCf.js} +55 -55
  7. package/dist/cctpqueue.d.ts +1 -1
  8. package/dist/cctpqueue.d.ts.map +1 -1
  9. package/dist/chains.d.ts +9 -3
  10. package/dist/chains.d.ts.map +1 -1
  11. package/dist/constants.d.ts +1 -0
  12. package/dist/constants.d.ts.map +1 -1
  13. package/dist/decoders.d.ts +58 -0
  14. package/dist/decoders.d.ts.map +1 -0
  15. package/dist/ens.d.ts +13 -0
  16. package/dist/ens.d.ts.map +1 -0
  17. package/dist/error.d.ts +9 -0
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/{index-BPsVj7zK.js → index-BXbaLmtt.js} +28779 -25738
  20. package/dist/index.js +2 -2
  21. package/dist/intents.d.ts +4 -4
  22. package/dist/intents.d.ts.map +1 -1
  23. package/dist/lifi.d.ts +4 -0
  24. package/dist/lifi.d.ts.map +1 -0
  25. package/dist/metaTxns.d.ts +1 -1
  26. package/dist/metaTxns.d.ts.map +1 -1
  27. package/dist/mode.d.ts +1 -1
  28. package/dist/mode.d.ts.map +1 -1
  29. package/dist/preconditions.d.ts +1 -1
  30. package/dist/preconditions.d.ts.map +1 -1
  31. package/dist/prepareSend.d.ts +32 -24
  32. package/dist/prepareSend.d.ts.map +1 -1
  33. package/dist/prices.d.ts +3 -1
  34. package/dist/prices.d.ts.map +1 -1
  35. package/dist/proxyCaller.d.ts +0 -1
  36. package/dist/proxyCaller.d.ts.map +1 -1
  37. package/dist/relaySdk.d.ts.map +1 -1
  38. package/dist/relayer.d.ts.map +1 -1
  39. package/dist/tokenBalances.d.ts +1 -1
  40. package/dist/tokenBalances.d.ts.map +1 -1
  41. package/dist/tokens.d.ts +2 -1
  42. package/dist/tokens.d.ts.map +1 -1
  43. package/dist/trails.d.ts +4 -4
  44. package/dist/trails.d.ts.map +1 -1
  45. package/dist/transactions.d.ts +4 -0
  46. package/dist/transactions.d.ts.map +1 -1
  47. package/dist/utils.d.ts +6 -0
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/wallets.d.ts +247 -5
  50. package/dist/wallets.d.ts.map +1 -1
  51. package/dist/widget/components/ChainFilterDropdown.d.ts +2 -0
  52. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  53. package/dist/widget/components/ConnectWallet.d.ts +1 -0
  54. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  55. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -1
  56. package/dist/widget/components/ErrorDisplay.d.ts +9 -0
  57. package/dist/widget/components/ErrorDisplay.d.ts.map +1 -0
  58. package/dist/widget/components/FundSendForm.d.ts +2 -2
  59. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  60. package/dist/widget/components/OriginTransferInformation.d.ts +10 -0
  61. package/dist/widget/components/OriginTransferInformation.d.ts.map +1 -0
  62. package/dist/widget/components/PaySendForm.d.ts +2 -2
  63. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  64. package/dist/widget/components/QrCode.d.ts +1 -1
  65. package/dist/widget/components/QrCode.d.ts.map +1 -1
  66. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  67. package/dist/widget/components/Receipt.d.ts.map +1 -1
  68. package/dist/widget/components/Receive.d.ts +12 -0
  69. package/dist/widget/components/Receive.d.ts.map +1 -0
  70. package/dist/widget/components/RefundAddressInput.d.ts +13 -0
  71. package/dist/widget/components/RefundAddressInput.d.ts.map +1 -0
  72. package/dist/widget/components/Swap.d.ts +47 -0
  73. package/dist/widget/components/Swap.d.ts.map +1 -0
  74. package/dist/widget/components/SwapDisplay.d.ts +9 -0
  75. package/dist/widget/components/SwapDisplay.d.ts.map +1 -0
  76. package/dist/widget/components/TokenList.d.ts +0 -2
  77. package/dist/widget/components/TokenList.d.ts.map +1 -1
  78. package/dist/widget/components/TokenSelector.d.ts +26 -0
  79. package/dist/widget/components/TokenSelector.d.ts.map +1 -0
  80. package/dist/widget/components/TransferPendingVertical.d.ts +2 -0
  81. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  82. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  83. package/dist/widget/components/WalletConnectionPending.d.ts +12 -0
  84. package/dist/widget/components/WalletConnectionPending.d.ts.map +1 -0
  85. package/dist/widget/components/WalletList.d.ts.map +1 -1
  86. package/dist/widget/components/YellowWarningAnimation.d.ts +2 -0
  87. package/dist/widget/components/YellowWarningAnimation.d.ts.map +1 -0
  88. package/dist/widget/hooks/useAmountUsd.d.ts +1 -3
  89. package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -1
  90. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  91. package/dist/widget/hooks/useDebugScreens.d.ts +22 -0
  92. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -0
  93. package/dist/widget/hooks/useSendForm.d.ts +12 -6
  94. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  95. package/dist/widget/hooks/useTokenList.d.ts +2 -3
  96. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  97. package/dist/widget/index.js +1 -1
  98. package/dist/widget/widget.d.ts.map +1 -1
  99. package/package.json +19 -15
  100. package/src/aave.ts +13 -13
  101. package/src/address.ts +3 -0
  102. package/src/analytics.ts +192 -8
  103. package/src/apiClient.ts +1 -1
  104. package/src/cctpqueue.ts +1 -1
  105. package/src/chains.ts +45 -7
  106. package/src/constants.ts +7 -4
  107. package/src/decoders.ts +310 -0
  108. package/src/ens.ts +32 -0
  109. package/src/error.ts +101 -1
  110. package/src/intents.ts +10 -2
  111. package/src/lifi.ts +58 -0
  112. package/src/metaTxns.ts +1 -1
  113. package/src/mode.ts +1 -1
  114. package/src/morpho.ts +3 -3
  115. package/src/pools.ts +18 -18
  116. package/src/preconditions.ts +1 -1
  117. package/src/prepareSend.ts +463 -113
  118. package/src/prices.ts +26 -1
  119. package/src/proxyCaller.ts +2 -14
  120. package/src/relaySdk.ts +1 -0
  121. package/src/relayer.ts +8 -0
  122. package/src/tokenBalances.ts +24 -17
  123. package/src/tokens.ts +147 -22
  124. package/src/trails.ts +4 -4
  125. package/src/transactions.ts +35 -17
  126. package/src/utils.ts +28 -0
  127. package/src/wallets.ts +275 -35
  128. package/src/widget/compiled.css +2 -2
  129. package/src/widget/components/ChainFilterDropdown.tsx +42 -33
  130. package/src/widget/components/ChainImage.tsx +1 -1
  131. package/src/widget/components/ConnectWallet.tsx +92 -128
  132. package/src/widget/components/DebugScreensDropdown.tsx +6 -0
  133. package/src/widget/components/ErrorDisplay.tsx +150 -0
  134. package/src/widget/components/FundSendForm.tsx +78 -11
  135. package/src/widget/components/OriginTransferInformation.tsx +59 -0
  136. package/src/widget/components/PaySendForm.tsx +80 -13
  137. package/src/widget/components/QRCodeDeposit.tsx +6 -6
  138. package/src/widget/components/QrCode.tsx +278 -17
  139. package/src/widget/components/QuoteDetails.tsx +93 -25
  140. package/src/widget/components/Receipt.tsx +296 -103
  141. package/src/widget/components/Receive.tsx +146 -0
  142. package/src/widget/components/RecentTokens.tsx +1 -1
  143. package/src/widget/components/RefundAddressInput.tsx +149 -0
  144. package/src/widget/components/Swap.tsx +769 -0
  145. package/src/widget/components/SwapDisplay.tsx +68 -0
  146. package/src/widget/components/TokenList.tsx +27 -363
  147. package/src/widget/components/TokenSelector.tsx +405 -0
  148. package/src/widget/components/TransferPendingVertical.tsx +162 -112
  149. package/src/widget/components/WalletConnect.tsx +9 -7
  150. package/src/widget/components/WalletConnectionPending.tsx +157 -0
  151. package/src/widget/components/WalletList.tsx +6 -5
  152. package/src/widget/components/YellowWarningAnimation.tsx +146 -0
  153. package/src/widget/hooks/useAmountUsd.ts +3 -8
  154. package/src/widget/hooks/useCheckout.ts +3 -2
  155. package/src/widget/hooks/useDebugScreens.ts +583 -0
  156. package/src/widget/hooks/useSendForm.ts +111 -35
  157. package/src/widget/hooks/useTokenList.ts +155 -122
  158. package/src/widget/widget.tsx +503 -523
@@ -1,10 +1,9 @@
1
1
  import React, { useEffect, useState } from "react"
2
2
  import { ChevronRight, Copy, LogOut } from "lucide-react"
3
3
  import { useAccount, useDisconnect } from "wagmi"
4
- import MetaMaskFox from "../assets/MetaMask-icon-fox.svg"
5
- import WalletConnectLogoWhite from "../assets/WalletConnect-logo-white.svg"
6
4
  import { AlignJustify, Wallet } from "lucide-react"
7
5
  import { ScreenHeader } from "./ScreenHeader.js"
6
+ import { useWallets, wagmiConnectorToWalletId } from "../../wallets.js"
8
7
 
9
8
  export interface WalletOption {
10
9
  id: string
@@ -18,6 +17,7 @@ export interface ConnectWalletProps {
18
17
  onContinue: () => void
19
18
  onError: (error: Error) => void
20
19
  walletOptions: WalletOption[]
20
+ lastClickedWallet?: string | null
21
21
  }
22
22
 
23
23
  export const ConnectWallet: React.FC<ConnectWalletProps> = ({
@@ -25,25 +25,12 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
25
25
  onDisconnect,
26
26
  onContinue,
27
27
  onError,
28
- walletOptions,
28
+ lastClickedWallet,
29
29
  }) => {
30
30
  const { isConnected, address, connector } = useAccount()
31
31
  const { disconnect } = useDisconnect()
32
+ const { wallets: allWallets } = useWallets()
32
33
  const [error, setError] = useState<string | null>(null)
33
- const [lastClickedWallet, setLastClickedWallet] = useState<string | null>(
34
- () => {
35
- // Initialize from localStorage if available
36
- if (typeof window !== "undefined") {
37
- try {
38
- return localStorage.getItem("trails-last-wallet") || null
39
- } catch (error) {
40
- console.error("[trails-sdk] Failed to read from localStorage:", error)
41
- return null
42
- }
43
- }
44
- return null
45
- },
46
- )
47
34
  const [showCopied, setShowCopied] = useState(false)
48
35
 
49
36
  useEffect(() => {
@@ -55,15 +42,6 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
55
42
  }, [error, onError])
56
43
 
57
44
  const handleWalletConnect = (walletId: string) => {
58
- setLastClickedWallet(walletId)
59
- // Save to localStorage
60
- if (typeof window !== "undefined") {
61
- try {
62
- localStorage.setItem("trails-last-wallet", walletId)
63
- } catch (error) {
64
- console.error("[trails-sdk] Failed to save to localStorage:", error)
65
- }
66
- }
67
45
  onConnect(walletId)
68
46
  }
69
47
 
@@ -92,24 +70,48 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
92
70
  }
93
71
  }
94
72
 
95
- // Get the wallet type for styling the continue button
96
- const getWalletType = () => {
97
- if (!connector?.name) return "injected"
98
- const name = connector.name.toLowerCase()
99
- if (name.includes("metamask")) return "injected"
100
- if (name.includes("walletconnect")) return "walletconnect"
101
- return "injected"
73
+ // Get the wallet config for the current connector
74
+ const getCurrentWalletConfig = () => {
75
+ if (!connector?.id) return null
76
+ const walletId = wagmiConnectorToWalletId(connector)
77
+ return allWallets.find((wallet) => wallet.id === walletId) || null
102
78
  }
103
79
 
104
- // Reorder wallet options to put the recent one first
80
+ // Create wallet options from allWallets and always prepend recent wallet
105
81
  const orderedWalletOptions = React.useMemo(() => {
106
- if (!lastClickedWallet) return walletOptions
82
+ // Convert allWallets to wallet options format
83
+ const walletOptionsFromAllWallets = allWallets.map((wallet) => ({
84
+ id: wallet.id,
85
+ name: wallet.name,
86
+ connector: () => wallet.connector?.connect?.(),
87
+ }))
107
88
 
108
- const recentWallet = walletOptions.find((w) => w.id === lastClickedWallet)
109
- const otherWallets = walletOptions.filter((w) => w.id !== lastClickedWallet)
89
+ const alwaysShowWallets = ["metamask", "walletconnect"]
90
+ const filteredWallets = walletOptionsFromAllWallets
91
+ .filter((wallet) => alwaysShowWallets.includes(wallet.id))
92
+ .sort((a, b) => {
93
+ const aIndex = alwaysShowWallets.indexOf(a.id)
94
+ const bIndex = alwaysShowWallets.indexOf(b.id)
95
+ return aIndex - bIndex
96
+ })
97
+
98
+ // Always prepend recent wallet if it exists, regardless of allowed list
99
+ if (lastClickedWallet) {
100
+ console.log("lastClickedWallet", lastClickedWallet)
101
+ const recentWallet = walletOptionsFromAllWallets.find(
102
+ (w) => w.id === lastClickedWallet,
103
+ )
104
+ const otherWallets = filteredWallets.filter(
105
+ (w) => w.id !== lastClickedWallet,
106
+ )
107
+
108
+ if (recentWallet) {
109
+ return [recentWallet, ...otherWallets]
110
+ }
111
+ }
110
112
 
111
- return recentWallet ? [recentWallet, ...otherWallets] : walletOptions
112
- }, [walletOptions, lastClickedWallet])
113
+ return filteredWallets
114
+ }, [lastClickedWallet, allWallets])
113
115
 
114
116
  return (
115
117
  <div className="space-y-6">
@@ -136,48 +138,30 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
136
138
  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"
137
139
  >
138
140
  <div className="flex items-center space-x-3">
139
- {getWalletType() === "injected" &&
140
- window.ethereum?.isMetaMask ? (
141
- <>
142
- <img
143
- src={MetaMaskFox}
144
- alt="MetaMask Fox"
145
- className="h-6 w-6"
146
- />
147
- <div className="flex items-center space-x-2">
148
- <span>MetaMask</span>
149
- <span className="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 trails-border-radius-container font-normal">
150
- Connected
151
- </span>
152
- </div>
153
- </>
154
- ) : getWalletType() === "walletconnect" ? (
155
- <>
156
- <div className="h-6 w-6 bg-[#3B99FC] rounded flex items-center justify-center">
157
- <img
158
- src={WalletConnectLogoWhite}
159
- alt="WalletConnect"
160
- className="h-4 w-4"
161
- />
162
- </div>
163
- <div className="flex items-center space-x-2">
164
- <span>WalletConnect</span>
165
- <span className="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 trails-border-radius-container font-normal">
166
- Connected
167
- </span>
168
- </div>
169
- </>
170
- ) : (
171
- <>
172
- <Wallet className="h-6 w-6 text-gray-600 dark:text-gray-400" />
173
- <div className="flex items-center space-x-2">
174
- <span>{connector?.name || "Wallet"}</span>
175
- <span className="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 trails-border-radius-container font-normal">
176
- Connected
177
- </span>
178
- </div>
179
- </>
180
- )}
141
+ {(() => {
142
+ const walletConfig = getCurrentWalletConfig()
143
+ return (
144
+ <>
145
+ {typeof walletConfig?.icon === "string" ? (
146
+ <img
147
+ src={walletConfig?.icon}
148
+ alt={walletConfig?.name}
149
+ className="h-6 w-6"
150
+ />
151
+ ) : (
152
+ <Wallet className="h-6 w-6 text-gray-600 dark:text-gray-400" />
153
+ )}
154
+ <div className="flex items-center space-x-2">
155
+ <span>
156
+ {walletConfig?.name || connector?.name || "Wallet"}
157
+ </span>
158
+ <span className="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 trails-border-radius-container font-normal">
159
+ Connected
160
+ </span>
161
+ </div>
162
+ </>
163
+ )
164
+ })()}
181
165
  </div>
182
166
 
183
167
  <ChevronRight className="h-5 w-5 text-gray-400" />
@@ -246,64 +230,44 @@ export const ConnectWallet: React.FC<ConnectWalletProps> = ({
246
230
  <button
247
231
  type="button"
248
232
  key={wallet.id}
233
+ data-wallet-id={wallet.id}
249
234
  onClick={() => handleWalletConnect(wallet.id)}
250
235
  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`}
251
236
  >
252
237
  <div className="flex items-center space-x-3">
253
- {wallet.id === "injected" && window.ethereum?.isMetaMask ? (
254
- <>
255
- <img
256
- src={MetaMaskFox}
257
- alt="MetaMask Fox"
258
- className="h-6 w-6"
259
- />
260
- <div className="flex items-center space-x-2">
261
- <span>MetaMask</span>
262
- {lastClickedWallet === wallet.id && (
263
- <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">
264
- Recent
265
- </span>
266
- )}
267
- </div>
268
- </>
269
- ) : wallet.id === "walletconnect" ? (
270
- <>
271
- <div className="h-6 w-6 bg-[#3B99FC] rounded flex items-center justify-center">
272
- <img
273
- src={WalletConnectLogoWhite}
274
- alt="WalletConnect"
275
- className="h-4 w-4"
276
- />
277
- </div>
278
- <div className="flex items-center space-x-2">
279
- <span>WalletConnect</span>
280
- {lastClickedWallet === wallet.id && (
281
- <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">
282
- Recent
283
- </span>
284
- )}
285
- </div>
286
- </>
287
- ) : (
288
- <>
289
- <Wallet className="h-6 w-6 text-gray-600 dark:text-gray-400" />
290
- <div className="flex items-center space-x-2">
291
- <span>{wallet.name}</span>
292
- {lastClickedWallet === wallet.id && (
293
- <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">
294
- Recent
295
- </span>
238
+ {(() => {
239
+ const walletConfig = allWallets.find(
240
+ (w) => w.id === wallet.id,
241
+ )
242
+ return (
243
+ <>
244
+ {typeof walletConfig?.icon === "string" ? (
245
+ <img
246
+ src={walletConfig?.icon}
247
+ alt={walletConfig?.name}
248
+ className="h-6 w-6"
249
+ />
250
+ ) : (
251
+ <Wallet className="h-6 w-6 text-gray-600 dark:text-gray-400" />
296
252
  )}
297
- </div>
298
- </>
299
- )}
253
+ <div className="flex items-center space-x-2">
254
+ <span>{walletConfig?.name || wallet.name}</span>
255
+ {lastClickedWallet === wallet.id && (
256
+ <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">
257
+ Recent
258
+ </span>
259
+ )}
260
+ </div>
261
+ </>
262
+ )
263
+ })()}
300
264
  </div>
301
265
  <ChevronRight className="h-5 w-5 text-gray-400" />
302
266
  </button>
303
267
  ))}
304
268
 
305
269
  {/* More Wallets Link - only show if more than 1 wallet option */}
306
- {orderedWalletOptions.length > 1 && (
270
+ {orderedWalletOptions.length > 0 && (
307
271
  <button
308
272
  type="button"
309
273
  onClick={() => onConnect("wallet-list")}
@@ -13,10 +13,14 @@ const SCREENS = [
13
13
  "Mesh Connect",
14
14
  "Wallet Connect",
15
15
  "Wallet List",
16
+ "Wallet Connection Pending",
17
+ "Wallet Connection Pending Retry",
16
18
  "Tokens",
17
19
  "Send Form",
18
20
  "Fund Form",
19
21
  "Earn Pools",
22
+ "Swap",
23
+ "Receive",
20
24
  "Wallet Confirmation",
21
25
  "Wallet Confirmation Retry",
22
26
  "Pending 1-item-0-confirmed",
@@ -28,8 +32,10 @@ const SCREENS = [
28
32
  "Pending 3-item-1-confirmed",
29
33
  "Pending 3-item-2-confirmed",
30
34
  "Pending 3-item-3-confirmed",
35
+ "Pending 3-item-2-confirmed-with-error",
31
36
  "Receipt",
32
37
  "Receipt Failed",
38
+ "Receipt Refunded",
33
39
  ] as const
34
40
 
35
41
  export const DebugScreensDropdown: React.FC<DebugScreensDropdownProps> = ({
@@ -0,0 +1,150 @@
1
+ import type React from "react"
2
+ import { useState, useEffect } from "react"
3
+ import { getPrettifiedErrorMessage } from "../../error.js"
4
+
5
+ interface ErrorDisplayProps {
6
+ errorPrettified?: string | null
7
+ error?: string | null
8
+ severity?: "warning" | "error" | "info"
9
+ }
10
+
11
+ export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
12
+ errorPrettified,
13
+ error,
14
+ severity = "error",
15
+ }) => {
16
+ const [showFullError, setShowFullError] = useState(false)
17
+ const [computedErrorPrettified, setComputedErrorPrettified] = useState<
18
+ string | null
19
+ >(errorPrettified || null)
20
+
21
+ // If errorPrettified doesn't exist but error does, try to get a prettified version
22
+ useEffect(() => {
23
+ if (!errorPrettified && error) {
24
+ const prettified = getPrettifiedErrorMessage(error)
25
+ if (prettified) {
26
+ setComputedErrorPrettified(prettified)
27
+ }
28
+ } else {
29
+ setComputedErrorPrettified(errorPrettified || null)
30
+ }
31
+ }, [errorPrettified, error])
32
+
33
+ // Get colors based on severity
34
+ const getSeverityColors = () => {
35
+ switch (severity) {
36
+ case "error":
37
+ return {
38
+ bg: "bg-red-500/10",
39
+ border: "border-red-500/30",
40
+ icon: "text-red-500",
41
+ text: "text-red-600 dark:text-red-400",
42
+ button:
43
+ "text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300",
44
+ expandable:
45
+ "bg-red-500/5 border-red-500/20 text-red-700 dark:text-red-300",
46
+ }
47
+ case "info":
48
+ return {
49
+ bg: "bg-blue-500/10",
50
+ border: "border-blue-500/30",
51
+ icon: "text-blue-500",
52
+ text: "text-blue-600 dark:text-blue-400",
53
+ button:
54
+ "text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300",
55
+ expandable:
56
+ "bg-blue-500/5 border-blue-500/20 text-blue-700 dark:text-blue-300",
57
+ }
58
+ case "warning":
59
+ return {
60
+ bg: "bg-amber-500/10",
61
+ border: "border-amber-500/30",
62
+ icon: "text-amber-500",
63
+ text: "text-amber-600 dark:text-amber-400",
64
+ button:
65
+ "text-amber-500 hover:text-amber-600 dark:text-amber-400 dark:hover:text-amber-300",
66
+ expandable:
67
+ "bg-amber-500/5 border-amber-500/20 text-amber-700 dark:text-amber-300",
68
+ }
69
+ default:
70
+ return {
71
+ bg: "bg-amber-500/10",
72
+ border: "border-amber-500/30",
73
+ icon: "text-amber-500",
74
+ text: "text-amber-600 dark:text-amber-400",
75
+ button:
76
+ "text-amber-500 hover:text-amber-600 dark:text-amber-400 dark:hover:text-amber-300",
77
+ expandable:
78
+ "bg-amber-500/5 border-amber-500/20 text-amber-700 dark:text-amber-300",
79
+ }
80
+ }
81
+ }
82
+
83
+ const colors = getSeverityColors()
84
+
85
+ // Only show if we have a prettified error (either provided or computed)
86
+ if (!computedErrorPrettified) {
87
+ return null
88
+ }
89
+
90
+ return (
91
+ <div
92
+ className={`px-2 py-3 rounded-lg ${colors.bg} border border-solid ${colors.border}`}
93
+ >
94
+ <div className="flex items-start space-x-2">
95
+ <svg
96
+ className={`w-4 h-4 ${colors.icon} flex-shrink-0 mt-0.5`}
97
+ fill="none"
98
+ stroke="currentColor"
99
+ viewBox="0 0 24 24"
100
+ aria-hidden="true"
101
+ >
102
+ <path
103
+ strokeLinecap="round"
104
+ strokeLinejoin="round"
105
+ strokeWidth={2}
106
+ 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"
107
+ />
108
+ </svg>
109
+ <div className="flex-1 min-w-0">
110
+ <p className={`text-sm ${colors.text}`}>{computedErrorPrettified}</p>
111
+ {error && error !== computedErrorPrettified && (
112
+ <div className="mt-2">
113
+ <button
114
+ type="button"
115
+ onClick={() => setShowFullError(!showFullError)}
116
+ className={`flex items-center gap-1 text-xs ${colors.button} transition-colors cursor-pointer`}
117
+ aria-label="Show full error details"
118
+ >
119
+ <span>{showFullError ? "Hide" : "Show"} full error</span>
120
+ <svg
121
+ className={`w-3 h-3 transition-transform duration-300 ease-out ${
122
+ showFullError ? "rotate-180" : ""
123
+ }`}
124
+ fill="none"
125
+ stroke="currentColor"
126
+ viewBox="0 0 24 24"
127
+ aria-hidden="true"
128
+ >
129
+ <path
130
+ strokeLinecap="round"
131
+ strokeLinejoin="round"
132
+ strokeWidth={2}
133
+ d="M19 9l-7 7-7-7"
134
+ />
135
+ </svg>
136
+ </button>
137
+ {showFullError && (
138
+ <div
139
+ className={`mt-2 p-2 ${colors.expandable} rounded text-xs font-mono max-h-[200px] overflow-y-auto overflow-x-auto`}
140
+ >
141
+ {error}
142
+ </div>
143
+ )}
144
+ </div>
145
+ )}
146
+ </div>
147
+ </div>
148
+ </div>
149
+ )
150
+ }
@@ -1,4 +1,4 @@
1
- import { ChevronDown, Loader2 } from "lucide-react"
1
+ import { ChevronDown, Loader2, RefreshCcw } from "lucide-react"
2
2
  import type React from "react"
3
3
  import { useEffect, useRef, useState, useCallback, useMemo } from "react"
4
4
  import type { Account, WalletClient } from "viem"
@@ -11,10 +11,12 @@ import { ChainImage } from "./ChainImage.js"
11
11
  import { TokenImage } from "./TokenImage.js"
12
12
  import { QuoteDetails } from "./QuoteDetails.js"
13
13
  import { TruncatedAddress } from "./TruncatedAddress.js"
14
+ // import { RefundAddressInput } from "./RefundAddressInput.js"
14
15
  import { TradeType } from "../../prepareSend.js"
15
16
  import type { PrepareSendQuote } from "../../prepareSend.js"
16
17
  import { formatAmount, formatUsdAmountDisplay } from "../../tokenBalances.js"
17
18
  import { ScreenHeader } from "./ScreenHeader.js"
19
+ import { ErrorDisplay } from "./ErrorDisplay.js"
18
20
 
19
21
  interface FundSendFormProps {
20
22
  selectedToken: Token
@@ -36,8 +38,8 @@ interface FundSendFormProps {
36
38
  gasless?: boolean
37
39
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
38
40
  quoteProvider?: string
39
- fundMethod?: string | null
40
- mode?: "pay" | "fund" | "earn"
41
+ fundMethod?: string
42
+ mode?: "pay" | "fund" | "earn" | "swap" | "receive"
41
43
  onNavigateToMeshConnect?: (
42
44
  props: {
43
45
  toTokenSymbol: string
@@ -77,6 +79,9 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
77
79
  }) => {
78
80
  // Local state for fund-specific functionality
79
81
  const [isInputTypeUsd, setIsInputTypeUsd] = useState(false)
82
+ const [refetchTrigger, setRefetchTrigger] = useState(0)
83
+ // const [isRefundAddressOpen, setIsRefundAddressOpen] = useState(false)
84
+ // const [refundAddress, setRefundAddress] = useState<string>(account.address)
80
85
 
81
86
  const [tokenAmountForBackend, setTokenAmountForBackend] = useState("")
82
87
  const [inputDisplayValue, setInputDisplayValue] = useState("")
@@ -119,6 +124,9 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
119
124
  setSelectedDestinationChain,
120
125
  setSelectedDestToken,
121
126
  prepareSendQuote,
127
+ quoteError,
128
+ quoteErrorPrettified,
129
+ isSameTokenWithoutCustomCalldata,
122
130
  } = useSendForm({
123
131
  account,
124
132
  // Don't pass toAmount for fund form - user enters input amount
@@ -126,6 +134,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
126
134
  toChainId,
127
135
  toToken,
128
136
  toCalldata,
137
+ // refundAddress,
129
138
  walletClient,
130
139
  onTransactionStateChange,
131
140
  onError,
@@ -143,6 +152,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
143
152
  mode,
144
153
  onNavigateToMeshConnect,
145
154
  checkoutOnHandlers,
155
+ refetchTrigger,
146
156
  })
147
157
 
148
158
  // Get source token price for USD conversions
@@ -340,6 +350,11 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
340
350
  }, 0)
341
351
  }, [tokenAmountForBackend, isInputTypeUsd, sourceTokenPrice])
342
352
 
353
+ // Handle manual quote refetch
354
+ const handleRefetchQuote = useCallback(() => {
355
+ setRefetchTrigger((prev) => prev + 1)
356
+ }, [])
357
+
343
358
  // Dynamic font size based on input length
344
359
  const inputStyles = useMemo(() => {
345
360
  const inputLength = displayAmount.length
@@ -479,7 +494,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
479
494
 
480
495
  {/* Percentage Buttons */}
481
496
  <div className="flex space-x-1 justify-center">
482
- {[25, 50, 100].map((percentage) => (
497
+ {[25, 50, 75, 100].map((percentage) => (
483
498
  <button
484
499
  key={percentage}
485
500
  type="button"
@@ -497,7 +512,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
497
512
  <div className="mb-4">
498
513
  <label
499
514
  htmlFor="destination-chain"
500
- className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300"
515
+ className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
501
516
  >
502
517
  Destination Chain
503
518
  </label>
@@ -560,7 +575,7 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
560
575
  <div className="mb-4">
561
576
  <label
562
577
  htmlFor="token"
563
- className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300"
578
+ className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
564
579
  >
565
580
  Receive Token
566
581
  </label>
@@ -626,10 +641,37 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
626
641
 
627
642
  {/* Receive Section */}
628
643
  <div className="space-y-1">
629
- <div
630
- className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
631
- >
632
- Receive
644
+ <div className="flex items-center justify-between">
645
+ <div
646
+ className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
647
+ >
648
+ Receive
649
+ </div>
650
+ <button
651
+ type="button"
652
+ onClick={handleRefetchQuote}
653
+ disabled={
654
+ isLoadingQuote ||
655
+ !tokenAmountForBackend ||
656
+ !selectedDestToken ||
657
+ !selectedDestinationChain ||
658
+ !isValidRecipient
659
+ }
660
+ className={`p-2 rounded-md transition-colors cursor-pointer ${
661
+ isLoadingQuote ||
662
+ !tokenAmountForBackend ||
663
+ !selectedDestToken ||
664
+ !selectedDestinationChain ||
665
+ !isValidRecipient
666
+ ? "opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-500"
667
+ : "text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200"
668
+ }`}
669
+ title="Refetch quote"
670
+ >
671
+ <RefreshCcw
672
+ className={`h-4 w-4 ${isLoadingQuote ? "animate-spin" : ""}`}
673
+ />
674
+ </button>
633
675
  </div>
634
676
 
635
677
  <div className="p-2">
@@ -688,7 +730,29 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
688
730
  </div>
689
731
  )}
690
732
 
733
+ {/* Refund Address Input */}
734
+ {/* <RefundAddressInput
735
+ account={account}
736
+ isOpen={isRefundAddressOpen}
737
+ onToggle={() => setIsRefundAddressOpen(!isRefundAddressOpen)}
738
+ refundAddress={refundAddress}
739
+ onRefundAddressChange={setRefundAddress}
740
+ chainId={selectedDestinationChain.id}
741
+ /> */}
742
+
691
743
  {/* Warning Messages - Show only one at a time */}
744
+ {isSameTokenWithoutCustomCalldata ? (
745
+ <ErrorDisplay
746
+ errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different destination token."
747
+ severity="error"
748
+ />
749
+ ) : (
750
+ <ErrorDisplay
751
+ errorPrettified={quoteErrorPrettified}
752
+ error={quoteError}
753
+ severity="warning"
754
+ />
755
+ )}
692
756
  {prepareSendQuote?.noSufficientBalance ? (
693
757
  <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
694
758
  <div className="flex items-center space-x-2">
@@ -746,7 +810,8 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
746
810
  !isValidRecipient ||
747
811
  buttonText === "No quote available" ||
748
812
  buttonText === "Getting quote..." ||
749
- prepareSendQuote?.noSufficientBalance
813
+ prepareSendQuote?.noSufficientBalance ||
814
+ isSameTokenWithoutCustomCalldata
750
815
  }
751
816
  className={`w-full font-semibold py-4 px-4 trails-border-radius-button transition-colors bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white disabled:text-gray-500 disabled:cursor-not-allowed cursor-pointer relative`}
752
817
  >
@@ -755,6 +820,8 @@ export const FundSendForm: React.FC<FundSendFormProps> = ({
755
820
  <Loader2 className="w-5 h-5 animate-spin mr-2 text-white dark:text-gray-400" />
756
821
  <span>{buttonText}</span>
757
822
  </div>
823
+ ) : isSameTokenWithoutCustomCalldata ? (
824
+ "Select Different Tokens"
758
825
  ) : prepareSendQuote?.noSufficientBalance ? (
759
826
  "Insufficient Balance"
760
827
  ) : !tokenAmountForBackend ||