0xtrails 0.1.13 → 0.2.0

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 (216) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +11 -2
  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/{proxyCaller.d.ts → balanceInjector.d.ts} +5 -4
  7. package/dist/balanceInjector.d.ts.map +1 -0
  8. package/dist/{ccip-D3gTQONK.js → ccip-D6ToCrWc.js} +12 -12
  9. package/dist/cctp.d.ts.map +1 -1
  10. package/dist/cctpqueue.d.ts +3 -3
  11. package/dist/cctpqueue.d.ts.map +1 -1
  12. package/dist/chains.d.ts.map +1 -1
  13. package/dist/config.d.ts +17 -3
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/constants.d.ts +5 -4
  16. package/dist/constants.d.ts.map +1 -1
  17. package/dist/contractUtils.d.ts +2 -0
  18. package/dist/contractUtils.d.ts.map +1 -1
  19. package/dist/customChains.d.ts +24 -0
  20. package/dist/customChains.d.ts.map +1 -0
  21. package/dist/{index-CnUM7lKf.js → index-BqgeTLL8.js} +34072 -30146
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +411 -400
  25. package/dist/intentEntrypoint.d.ts +96 -0
  26. package/dist/intentEntrypoint.d.ts.map +1 -0
  27. package/dist/intents.d.ts +5 -3
  28. package/dist/intents.d.ts.map +1 -1
  29. package/dist/metaTxnMonitor.d.ts.map +1 -1
  30. package/dist/morpho.d.ts.map +1 -1
  31. package/dist/pools.d.ts +3 -1
  32. package/dist/pools.d.ts.map +1 -1
  33. package/dist/prepareSend.d.ts +8 -2
  34. package/dist/prepareSend.d.ts.map +1 -1
  35. package/dist/prices.d.ts +1 -1
  36. package/dist/prices.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/toast.d.ts +9 -0
  40. package/dist/toast.d.ts.map +1 -0
  41. package/dist/tokenBalances.d.ts +6 -2
  42. package/dist/tokenBalances.d.ts.map +1 -1
  43. package/dist/tokens.d.ts.map +1 -1
  44. package/dist/trails.d.ts +6 -5
  45. package/dist/trails.d.ts.map +1 -1
  46. package/dist/trailsClient.d.ts +12 -0
  47. package/dist/trailsClient.d.ts.map +1 -0
  48. package/dist/transactions.d.ts +8 -0
  49. package/dist/transactions.d.ts.map +1 -1
  50. package/dist/wallets.d.ts.map +1 -1
  51. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  52. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  53. package/dist/widget/components/AccountSettings.d.ts +7 -0
  54. package/dist/widget/components/AccountSettings.d.ts.map +1 -0
  55. package/dist/widget/components/ChainList.d.ts +0 -1
  56. package/dist/widget/components/ChainList.d.ts.map +1 -1
  57. package/dist/widget/components/ClassicSwap.d.ts +46 -0
  58. package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
  59. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  60. package/dist/widget/components/ConnectedWallets.d.ts +9 -0
  61. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
  62. package/dist/widget/components/DebugMenu.d.ts.map +1 -1
  63. package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
  64. package/dist/widget/components/DebugToast.d.ts +3 -0
  65. package/dist/widget/components/DebugToast.d.ts.map +1 -0
  66. package/dist/widget/components/Earn.d.ts.map +1 -1
  67. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  68. package/dist/widget/components/Fund.d.ts +44 -0
  69. package/dist/widget/components/Fund.d.ts.map +1 -0
  70. package/dist/widget/components/Identicon.d.ts +9 -0
  71. package/dist/widget/components/Identicon.d.ts.map +1 -0
  72. package/dist/widget/components/Pay.d.ts +46 -0
  73. package/dist/widget/components/Pay.d.ts.map +1 -0
  74. package/dist/widget/components/Receive.d.ts.map +1 -1
  75. package/dist/widget/components/RecentTokens.d.ts.map +1 -1
  76. package/dist/widget/components/Recipients.d.ts +9 -0
  77. package/dist/widget/components/Recipients.d.ts.map +1 -0
  78. package/dist/widget/components/RefundWarning.d.ts +9 -0
  79. package/dist/widget/components/RefundWarning.d.ts.map +1 -0
  80. package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
  81. package/dist/widget/components/Swap.d.ts.map +1 -1
  82. package/dist/widget/components/SwapSettings.d.ts +1 -5
  83. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  84. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  85. package/dist/widget/components/ThemeSyncer.d.ts +6 -0
  86. package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
  87. package/dist/widget/components/Toast.d.ts +24 -0
  88. package/dist/widget/components/Toast.d.ts.map +1 -0
  89. package/dist/widget/components/TokenList.d.ts.map +1 -1
  90. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  91. package/dist/widget/components/TruncatedAddress.d.ts +2 -0
  92. package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
  93. package/dist/widget/components/UserPreferences.d.ts +7 -0
  94. package/dist/widget/components/UserPreferences.d.ts.map +1 -0
  95. package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
  96. package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
  97. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  98. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  99. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  100. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  101. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  102. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
  103. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
  104. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  105. package/dist/widget/hooks/usePayMessage.d.ts +34 -0
  106. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
  107. package/dist/widget/hooks/useRecipients.d.ts +17 -0
  108. package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
  109. package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
  110. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
  111. package/dist/widget/hooks/useSendForm.d.ts +2 -0
  112. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  113. package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
  114. package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
  115. package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
  116. package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
  117. package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
  118. package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
  119. package/dist/widget/hooks/useTheme.d.ts +14 -0
  120. package/dist/widget/hooks/useTheme.d.ts.map +1 -0
  121. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  122. package/dist/widget/index.js +2 -2
  123. package/dist/widget/widget.d.ts +9 -0
  124. package/dist/widget/widget.d.ts.map +1 -1
  125. package/package.json +29 -28
  126. package/src/aave.ts +6 -1
  127. package/src/analytics.ts +103 -53
  128. package/src/apiClient.ts +1 -1
  129. package/src/{proxyCaller.ts → balanceInjector.ts} +22 -17
  130. package/src/cctp.ts +6 -2
  131. package/src/cctpqueue.ts +7 -7
  132. package/src/chains.ts +8 -0
  133. package/src/config.ts +40 -9
  134. package/src/constants.ts +11 -8
  135. package/src/contractUtils.ts +33 -2
  136. package/src/customChains.ts +24 -0
  137. package/src/index.ts +11 -1
  138. package/src/intentEntrypoint.ts +253 -0
  139. package/src/intents.ts +87 -54
  140. package/src/metaTxnMonitor.ts +1 -0
  141. package/src/morpho.ts +13 -2
  142. package/src/pools.ts +68 -86
  143. package/src/prepareSend.ts +437 -207
  144. package/src/prices.ts +51 -7
  145. package/src/relaySdk.ts +6 -4
  146. package/src/relayer.ts +2 -0
  147. package/src/toast.ts +110 -0
  148. package/src/tokenBalances.ts +112 -20
  149. package/src/tokens.ts +70 -7
  150. package/src/trails.ts +80 -77
  151. package/src/trailsClient.ts +45 -0
  152. package/src/transactions.ts +27 -35
  153. package/src/umd.tsx +1 -1
  154. package/src/wallets.ts +2 -1
  155. package/src/widget/assets/sequence-logo.svg +15 -0
  156. package/src/widget/compiled.css +2 -2
  157. package/src/widget/components/AccountActionsDropdown.tsx +18 -159
  158. package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
  159. package/src/widget/components/AccountSettings.tsx +96 -0
  160. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  161. package/src/widget/components/ChainList.tsx +10 -20
  162. package/src/widget/components/ClassicSwap.tsx +923 -0
  163. package/src/widget/components/ConfigDisplay.tsx +8 -5
  164. package/src/widget/components/ConnectedWallets.tsx +260 -0
  165. package/src/widget/components/DebugMenu.tsx +2 -0
  166. package/src/widget/components/DebugScreensList.tsx +3 -0
  167. package/src/widget/components/DebugToast.tsx +63 -0
  168. package/src/widget/components/Earn.tsx +108 -116
  169. package/src/widget/components/EarnPools.tsx +2 -4
  170. package/src/widget/components/EarnPoolsFilters.tsx +6 -6
  171. package/src/widget/components/Fund.tsx +1245 -0
  172. package/src/widget/components/FundMethods.tsx +1 -1
  173. package/src/widget/components/FundSendForm.tsx +1 -1
  174. package/src/widget/components/Identicon.tsx +158 -0
  175. package/src/widget/components/Pay.tsx +1088 -0
  176. package/src/widget/components/PaySendForm.tsx +1 -1
  177. package/src/widget/components/QuoteDetails.tsx +1 -1
  178. package/src/widget/components/Receipt.tsx +1 -1
  179. package/src/widget/components/Receive.tsx +4 -2
  180. package/src/widget/components/RecentTokens.tsx +2 -1
  181. package/src/widget/components/Recipients.tsx +448 -0
  182. package/src/widget/components/RefundWarning.tsx +61 -0
  183. package/src/widget/components/ScreenHeader.tsx +1 -1
  184. package/src/widget/components/SimpleSwap.tsx +74 -58
  185. package/src/widget/components/Swap.tsx +35 -853
  186. package/src/widget/components/SwapSettings.tsx +5 -11
  187. package/src/widget/components/ThemeProvider.tsx +32 -0
  188. package/src/widget/components/ThemeSyncer.tsx +47 -0
  189. package/src/widget/components/Toast.tsx +315 -0
  190. package/src/widget/components/TokenList.tsx +2 -34
  191. package/src/widget/components/TokenSelector.tsx +3 -3
  192. package/src/widget/components/TransactionDetails.tsx +153 -13
  193. package/src/widget/components/TruncatedAddress.tsx +5 -1
  194. package/src/widget/components/UserPreferences.tsx +156 -0
  195. package/src/widget/components/WalletList.tsx +1 -1
  196. package/src/widget/hooks/useBalanceVisible.tsx +40 -2
  197. package/src/widget/hooks/useCheckout.ts +13 -0
  198. package/src/widget/hooks/useCurrentScreen.tsx +3 -0
  199. package/src/widget/hooks/useDebugScreens.ts +12 -2
  200. package/src/widget/hooks/useDefaultTokenSelection.tsx +475 -0
  201. package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
  202. package/src/widget/hooks/usePayMessage.tsx +370 -0
  203. package/src/widget/hooks/useRecipients.ts +168 -0
  204. package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
  205. package/src/widget/hooks/useSendForm.ts +179 -26
  206. package/src/widget/hooks/useSwapAmount.tsx +50 -0
  207. package/src/widget/hooks/useSwapSettings.tsx +100 -0
  208. package/src/widget/hooks/useTargetAmount.ts +23 -0
  209. package/src/widget/hooks/useTheme.tsx +80 -0
  210. package/src/widget/hooks/useTokenList.ts +20 -11
  211. package/src/widget/index.css +45 -21
  212. package/src/widget/widget.tsx +164 -68
  213. package/dist/address.d.ts +0 -2
  214. package/dist/address.d.ts.map +0 -1
  215. package/dist/proxyCaller.d.ts.map +0 -1
  216. package/src/address.ts +0 -6
@@ -754,7 +754,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
754
754
  {/* Warning Messages - Show only one at a time */}
755
755
  {isSameTokenWithoutCustomCalldata ? (
756
756
  <ErrorDisplay
757
- errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different destination token."
757
+ errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different origin token."
758
758
  severity="error"
759
759
  />
760
760
  ) : (
@@ -4,7 +4,7 @@ import type React from "react"
4
4
  import { getExplorerUrlForAddress } from "../../explorer.js"
5
5
  import type { PrepareSendQuote } from "../../prepareSend.js"
6
6
  import { useState, useEffect, useRef } from "react"
7
- import { truncateAddress } from "../../address.js"
7
+ import { truncateAddress } from "../../utils.js"
8
8
  import { PriceImpactWarning } from "./PriceImpactWarning.js"
9
9
  import { usePriceImpactWarning } from "../hooks/usePriceImpactWarning.js"
10
10
 
@@ -9,7 +9,7 @@ import { YellowWarningAnimation } from "./YellowWarningAnimation.js"
9
9
  import { getTxTimeDiff } from "../../transactions.js"
10
10
  import { QuoteDetails } from "./QuoteDetails.js"
11
11
  import type { PrepareSendQuote } from "../../prepareSend.js"
12
- import { truncateAddress } from "../../address.js"
12
+ import { truncateAddress } from "../../utils.js"
13
13
  import { formatElapsed } from "../../utils.js"
14
14
  import { ChainImage } from "./ChainImage.js"
15
15
  import { getChainInfo } from "../../chains.js"
@@ -4,7 +4,7 @@ import { QrCode } from "./QrCode.js"
4
4
  import { ScreenHeader } from "./ScreenHeader.js"
5
5
  import { getExplorerUrlForAddress } from "../../explorer.js"
6
6
  import { ExternalLink, Copy, Check } from "lucide-react"
7
- import { truncateAddress } from "../../address.js"
7
+ import { truncateAddress } from "../../utils.js"
8
8
  import { useResolveEnsName } from "../../ens.js"
9
9
 
10
10
  interface ReceiveProps {
@@ -60,6 +60,7 @@ export const Receive: React.FC<ReceiveProps> = ({
60
60
  onBack={onBack}
61
61
  headerContent="Receive"
62
62
  headerContentAlign="left"
63
+ showAccountActions={true}
63
64
  />
64
65
 
65
66
  <div className="flex flex-col justify-center min-h-full space-y-6 pt-8">
@@ -96,6 +97,7 @@ export const Receive: React.FC<ReceiveProps> = ({
96
97
  onBack={onBack}
97
98
  headerContent={`Pay ${ensName ? ensName : truncateAddress(resolvedAddress)}`}
98
99
  headerContentAlign="left"
100
+ showAccountActions={true}
99
101
  />
100
102
 
101
103
  <div className="flex flex-col justify-center min-h-full space-y-6 pt-2">
@@ -163,7 +165,7 @@ export const Receive: React.FC<ReceiveProps> = ({
163
165
  <button
164
166
  type="button"
165
167
  onClick={onPay}
166
- className="px-6 py-3 font-medium rounded-lg transition-colors duration-200 cursor-pointer bg-blue-500 hover:bg-blue-600 text-white"
168
+ 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"
167
169
  >
168
170
  Pay {ensName ? ensName : truncateAddress(resolvedAddress)}
169
171
  </button>
@@ -20,7 +20,8 @@ export const RecentTokens: React.FC<RecentTokensProps> = ({
20
20
  const isTokenSelected = (token: SupportedToken) => {
21
21
  if (!selectedToken) return false
22
22
  return (
23
- token.contractAddress === selectedToken.contractAddress &&
23
+ token.contractAddress.toLowerCase() ===
24
+ selectedToken.contractAddress.toLowerCase() &&
24
25
  token.chainId === selectedToken.chainId
25
26
  )
26
27
  }
@@ -0,0 +1,448 @@
1
+ import { useEffect, useState, useCallback } from "react"
2
+ import type React from "react"
3
+ import { isAddress } from "viem"
4
+ import { Copy, RotateCcw } from "lucide-react"
5
+ import { useAccount, useConnections } from "wagmi"
6
+ import { ScreenHeader } from "./ScreenHeader.js"
7
+ import { SearchInputField } from "./SearchInputField.js"
8
+ import { Identicon } from "./Identicon.js"
9
+ import { useCurrentScreen } from "../hooks/useCurrentScreen.js"
10
+ import { useSelectedRecipient } from "../hooks/useSelectedRecipient.js"
11
+ import { useRecipients, type RecentRecipient } from "../hooks/useRecipients.js"
12
+ import { useResolveEnsAddress, useResolveEnsName } from "../../ens.js"
13
+ import { truncateAddress } from "../../utils.js"
14
+ import { logger } from "../../logger.js"
15
+ import { useMode } from "../hooks/useMode.js"
16
+ import { useWallets, wagmiConnectorToWalletId } from "../../wallets.js"
17
+
18
+ interface RecipientsProps {
19
+ onBack?: () => void
20
+ selectedRecipient?: string
21
+ onRecipientSelect?: (address: string) => void
22
+ }
23
+
24
+ export const Recipients: React.FC<RecipientsProps> = ({
25
+ onBack,
26
+ selectedRecipient = "",
27
+ onRecipientSelect,
28
+ }) => {
29
+ const { mode } = useMode()
30
+ const { setCurrentScreen } = useCurrentScreen()
31
+ const { setSelectedRecipient } = useSelectedRecipient()
32
+ const { recentRecipients, addRecentRecipient, clearRecentRecipients } =
33
+ useRecipients()
34
+ const [recipientInput, setRecipientInput] = useState(selectedRecipient)
35
+ const [isValidAddress, setIsValidAddress] = useState(false)
36
+ const [copiedAddress, setCopiedAddress] = useState<string | null>(null)
37
+
38
+ // Connected wallets functionality
39
+ const { address } = useAccount()
40
+ const connections = useConnections()
41
+ const { wallets: allWallets } = useWallets()
42
+
43
+ // Use wagmi ENS resolution for both directions
44
+ const trimmedInput = recipientInput.trim()
45
+
46
+ // Forward resolution: ENS name → address
47
+ const { ensAddress, isLoading: isLoadingEnsAddress } = useResolveEnsAddress({
48
+ textInput: trimmedInput,
49
+ })
50
+
51
+ // Reverse resolution: address → ENS name
52
+ const { ensName, isLoading: isLoadingEnsName } = useResolveEnsName({
53
+ address: isAddress(trimmedInput) ? trimmedInput : "",
54
+ })
55
+
56
+ const isLoadingEns = isLoadingEnsAddress || isLoadingEnsName
57
+
58
+ // Get all connected wallets with their details
59
+ const getConnectedWallets = () => {
60
+ const walletMap = new Map()
61
+
62
+ connections.forEach((connection) => {
63
+ // Process each account in the connection
64
+ connection.accounts.forEach((account) => {
65
+ if (account && !walletMap.has(account)) {
66
+ const walletId = wagmiConnectorToWalletId(connection.connector)
67
+ const walletConfig = allWallets.find(
68
+ (wallet) => wallet.id === walletId,
69
+ )
70
+
71
+ walletMap.set(account, {
72
+ address: account,
73
+ connector: connection.connector,
74
+ walletConfig,
75
+ walletId,
76
+ isActive: account === address,
77
+ })
78
+ }
79
+ })
80
+ })
81
+
82
+ return Array.from(walletMap.values())
83
+ }
84
+
85
+ const connectedWallets = getConnectedWallets()
86
+
87
+ // Validate address and handle ENS resolution
88
+ useEffect(() => {
89
+ const validateAddress = async () => {
90
+ if (!recipientInput || recipientInput.trim() === "") {
91
+ setIsValidAddress(false)
92
+ return
93
+ }
94
+
95
+ const trimmedInput = recipientInput.trim()
96
+
97
+ // Check if it's a valid hex address
98
+ if (isAddress(trimmedInput)) {
99
+ setIsValidAddress(true)
100
+ // Add to recent recipients with reverse ENS resolution if available
101
+ if (ensName) {
102
+ await addRecentRecipient(trimmedInput, trimmedInput, ensName)
103
+ } else {
104
+ await addRecentRecipient(trimmedInput, trimmedInput)
105
+ }
106
+ return
107
+ }
108
+
109
+ // Check if it looks like an ENS name
110
+ if (trimmedInput.includes(".") && trimmedInput.endsWith(".eth")) {
111
+ // ENS validation will be handled by the wagmi hook
112
+ // If we have a resolved address, it's valid
113
+ if (ensAddress) {
114
+ setIsValidAddress(true)
115
+ await addRecentRecipient(trimmedInput, ensAddress, trimmedInput)
116
+ } else if (!isLoadingEns) {
117
+ // Not loading and no address means resolution failed or not implemented
118
+ setIsValidAddress(false)
119
+ }
120
+ } else {
121
+ setIsValidAddress(false)
122
+ }
123
+ }
124
+
125
+ const timeoutId = setTimeout(validateAddress, 300) // Debounce validation
126
+ return () => clearTimeout(timeoutId)
127
+ }, [recipientInput, ensAddress, ensName, isLoadingEns, addRecentRecipient])
128
+
129
+ const handleInputChange = useCallback((value: string) => {
130
+ setRecipientInput(value)
131
+ }, [])
132
+
133
+ // Handle recent recipient selection
134
+ const handleRecentRecipientSelect = useCallback(
135
+ (recipient: RecentRecipient) => {
136
+ logger.console.log("[trails-sdk] Selected recent recipient:", recipient)
137
+ setSelectedRecipient(recipient.address)
138
+ onRecipientSelect?.(recipient.address)
139
+ setCurrentScreen(mode === "pay" ? "send-form" : "fund-form")
140
+ },
141
+ [setSelectedRecipient, onRecipientSelect, setCurrentScreen, mode],
142
+ )
143
+
144
+ // Handle connected wallet selection
145
+ const handleConnectedWalletSelect = useCallback(
146
+ (walletAddress: string) => {
147
+ logger.console.log(
148
+ "[trails-sdk] Selected connected wallet:",
149
+ walletAddress,
150
+ )
151
+ setSelectedRecipient(walletAddress)
152
+ onRecipientSelect?.(walletAddress)
153
+ setCurrentScreen(mode === "pay" ? "send-form" : "fund-form")
154
+ },
155
+ [setSelectedRecipient, onRecipientSelect, setCurrentScreen, mode],
156
+ )
157
+
158
+ // Handle copy to clipboard with success indication
159
+ const handleCopyAddress = useCallback(
160
+ async (address: string, e: React.MouseEvent) => {
161
+ e.preventDefault()
162
+ e.stopPropagation()
163
+ try {
164
+ await navigator.clipboard.writeText(address)
165
+ setCopiedAddress(address)
166
+ setTimeout(() => setCopiedAddress(null), 2000) // Clear after 2 seconds
167
+ } catch (error) {
168
+ logger.console.error("[trails-sdk] Failed to copy address:", error)
169
+ }
170
+ },
171
+ [],
172
+ )
173
+
174
+ const getValidationMessage = () => {
175
+ if (!recipientInput || recipientInput.trim() === "") {
176
+ return null
177
+ }
178
+
179
+ if (isLoadingEns) {
180
+ return "Resolving ENS name..."
181
+ }
182
+
183
+ return null
184
+ }
185
+
186
+ const getValidationColor = () => {
187
+ if (isLoadingEns) {
188
+ return "text-blue-500"
189
+ }
190
+ if (isValidAddress) {
191
+ return "text-green-500"
192
+ }
193
+ if (recipientInput && recipientInput.trim() !== "") {
194
+ return "text-red-500"
195
+ }
196
+ return "text-gray-500"
197
+ }
198
+
199
+ return (
200
+ <div className="space-y-4">
201
+ <ScreenHeader
202
+ onBack={onBack}
203
+ headerContent="Recipient address"
204
+ headerContentAlign="left"
205
+ showAccountActions={true}
206
+ />
207
+
208
+ <div className="space-y-4">
209
+ {/* Input Section */}
210
+ <div className="space-y-2">
211
+ <SearchInputField
212
+ value={recipientInput}
213
+ onChange={handleInputChange}
214
+ placeholder="Enter wallet address"
215
+ autoFocus={true}
216
+ />
217
+
218
+ {/* Validation Message */}
219
+ {getValidationMessage() && (
220
+ <p className={`text-xs ${getValidationColor()} text-left`}>
221
+ {getValidationMessage()}
222
+ </p>
223
+ )}
224
+ </div>
225
+
226
+ {/* Recent Recipients */}
227
+ {recentRecipients.length > 0 && (
228
+ <div className="space-y-2">
229
+ <div className="text-sm font-medium text-gray-700 dark:text-gray-300 text-left">
230
+ Recents
231
+ </div>
232
+ <div className="space-y-1 max-h-48 overflow-y-auto trails-scrollbar">
233
+ {recentRecipients.map((recipient) => {
234
+ // Only highlight if search field contains a valid address that matches this recipient
235
+ const searchQuery = recipientInput.trim()
236
+ const isHighlighted =
237
+ searchQuery &&
238
+ isAddress(searchQuery) &&
239
+ recipient.address.toLowerCase() === searchQuery.toLowerCase()
240
+
241
+ return (
242
+ <button
243
+ key={recipient.address}
244
+ type="button"
245
+ className={`w-full py-2 px-4 flex items-center space-x-3 transition-all duration-200 trails-border-radius-list-button cursor-pointer group ${
246
+ isHighlighted
247
+ ? "trails-list-item-selected border border-blue-200 dark:border-blue-800"
248
+ : "trails-list-item"
249
+ }`}
250
+ onClick={() => handleRecentRecipientSelect(recipient)}
251
+ onKeyDown={(e) => {
252
+ if (e.key === "Enter" || e.key === " ") {
253
+ e.preventDefault()
254
+ handleRecentRecipientSelect(recipient)
255
+ }
256
+ }}
257
+ aria-label={`Select recipient ${recipient.ensName || recipient.address}`}
258
+ >
259
+ {/* Identicon */}
260
+ <div className="relative flex-shrink-0 mr-2">
261
+ <Identicon
262
+ value={recipient.address}
263
+ size={32}
264
+ className="flex-shrink-0"
265
+ />
266
+ </div>
267
+
268
+ <div className="flex-1 min-w-0 text-left">
269
+ <div className="flex items-start">
270
+ <div className="flex-1">
271
+ <div className="flex items-center">
272
+ <span className="text-sm font-medium text-gray-900 dark:text-white">
273
+ {truncateAddress(recipient.address, 8, 4)}
274
+ </span>
275
+ <button
276
+ type="button"
277
+ onClick={(e) =>
278
+ handleCopyAddress(recipient.address, e)
279
+ }
280
+ className={`ml-1 p-0.5 rounded opacity-0 group-hover:opacity-100 transition-all duration-200 cursor-pointer ${
281
+ copiedAddress === recipient.address
282
+ ? "bg-green-100 dark:bg-green-900/30 opacity-100"
283
+ : "hover:bg-gray-200 dark:hover:bg-gray-600"
284
+ }`}
285
+ title={
286
+ copiedAddress === recipient.address
287
+ ? "Copied!"
288
+ : "Copy address"
289
+ }
290
+ >
291
+ {copiedAddress === recipient.address ? (
292
+ <svg
293
+ className="w-3 h-3 text-green-600 dark:text-green-400"
294
+ fill="none"
295
+ viewBox="0 0 24 24"
296
+ stroke="currentColor"
297
+ aria-label="Copied"
298
+ >
299
+ <title>Copied</title>
300
+ <path
301
+ strokeLinecap="round"
302
+ strokeLinejoin="round"
303
+ strokeWidth={2}
304
+ d="M5 13l4 4L19 7"
305
+ />
306
+ </svg>
307
+ ) : (
308
+ <Copy className="w-3 h-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" />
309
+ )}
310
+ </button>
311
+ </div>
312
+ {recipient.ensName && (
313
+ <div className="text-xs text-gray-500 dark:text-gray-400">
314
+ {recipient.ensName}
315
+ </div>
316
+ )}
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </button>
321
+ )
322
+ })}
323
+ </div>
324
+ </div>
325
+ )}
326
+
327
+ {/* Connected Wallets - only show when no input */}
328
+ {connectedWallets.length > 0 && !recipientInput.trim() && (
329
+ <div className="space-y-2">
330
+ <div className="flex items-center gap-2">
331
+ <div className="text-sm font-medium text-gray-700 dark:text-gray-300 text-left">
332
+ Connected Wallets
333
+ </div>
334
+ </div>
335
+ <div className="space-y-1 max-h-48 overflow-y-auto trails-scrollbar">
336
+ {connectedWallets.map((wallet) => {
337
+ // Only highlight if search field contains a valid address that matches this wallet
338
+ const searchQuery = recipientInput.trim()
339
+ const isHighlighted =
340
+ searchQuery &&
341
+ isAddress(searchQuery) &&
342
+ wallet.address.toLowerCase() === searchQuery.toLowerCase()
343
+
344
+ return (
345
+ <button
346
+ key={wallet.address}
347
+ type="button"
348
+ className={`w-full py-2 px-4 flex items-center space-x-3 transition-all duration-200 trails-border-radius-list-button cursor-pointer group ${
349
+ isHighlighted
350
+ ? "trails-list-item-selected border border-blue-200 dark:border-blue-800"
351
+ : "trails-list-item"
352
+ }`}
353
+ onClick={() => handleConnectedWalletSelect(wallet.address)}
354
+ onKeyDown={(e) => {
355
+ if (e.key === "Enter" || e.key === " ") {
356
+ e.preventDefault()
357
+ handleConnectedWalletSelect(wallet.address)
358
+ }
359
+ }}
360
+ aria-label={`Select wallet ${wallet.walletConfig?.name || "Wallet"} - ${wallet.address}`}
361
+ >
362
+ {/* Identicon */}
363
+ <div className="relative flex-shrink-0 mr-2">
364
+ <Identicon
365
+ value={wallet.address}
366
+ size={32}
367
+ className="flex-shrink-0"
368
+ />
369
+ </div>
370
+
371
+ <div className="flex-1 min-w-0 text-left">
372
+ <div className="flex items-start">
373
+ <div className="flex-1">
374
+ <div className="flex items-center">
375
+ <span className="text-sm font-medium text-gray-900 dark:text-white">
376
+ {truncateAddress(wallet.address, 8, 4)}
377
+ </span>
378
+ <button
379
+ type="button"
380
+ onClick={(e) =>
381
+ handleCopyAddress(wallet.address, e)
382
+ }
383
+ className={`ml-1 p-0.5 rounded opacity-0 group-hover:opacity-100 transition-all duration-200 cursor-pointer ${
384
+ copiedAddress === wallet.address
385
+ ? "bg-green-100 dark:bg-green-900/30 opacity-100"
386
+ : "hover:bg-gray-200 dark:hover:bg-gray-600"
387
+ }`}
388
+ title={
389
+ copiedAddress === wallet.address
390
+ ? "Copied!"
391
+ : "Copy address"
392
+ }
393
+ >
394
+ {copiedAddress === wallet.address ? (
395
+ <svg
396
+ className="w-3 h-3 text-green-600 dark:text-green-400"
397
+ fill="none"
398
+ viewBox="0 0 24 24"
399
+ stroke="currentColor"
400
+ aria-label="Copied"
401
+ >
402
+ <title>Copied</title>
403
+ <path
404
+ strokeLinecap="round"
405
+ strokeLinejoin="round"
406
+ strokeWidth={2}
407
+ d="M5 13l4 4L19 7"
408
+ />
409
+ </svg>
410
+ ) : (
411
+ <Copy className="w-3 h-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" />
412
+ )}
413
+ </button>
414
+ </div>
415
+ <div className="text-xs text-gray-500 dark:text-gray-400">
416
+ {wallet.walletConfig?.name ||
417
+ wallet.connector?.name ||
418
+ "Wallet"}
419
+ </div>
420
+ </div>
421
+ </div>
422
+ </div>
423
+ </button>
424
+ )
425
+ })}
426
+ </div>
427
+ </div>
428
+ )}
429
+
430
+ {/* Clear Recents Button */}
431
+ {recentRecipients.length > 0 && (
432
+ <div className="pt-4">
433
+ <button
434
+ type="button"
435
+ onClick={clearRecentRecipients}
436
+ className="flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 trails-bg-secondary rounded-xl hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors cursor-pointer"
437
+ >
438
+ <RotateCcw className="w-3 h-3" />
439
+ Clear recents
440
+ </button>
441
+ </div>
442
+ )}
443
+ </div>
444
+ </div>
445
+ )
446
+ }
447
+
448
+ export default Recipients
@@ -0,0 +1,61 @@
1
+ import type React from "react"
2
+
3
+ interface RefundWarningProps {
4
+ fundMethod?: string
5
+ isSenderContractOnOrigin?: boolean
6
+ isSenderContractOnDestination?: boolean
7
+ }
8
+
9
+ export const RefundWarning: React.FC<RefundWarningProps> = ({
10
+ fundMethod,
11
+ isSenderContractOnOrigin,
12
+ isSenderContractOnDestination,
13
+ }) => {
14
+ // Determine if we should show the warning
15
+ const shouldShowWarning =
16
+ fundMethod === "exchange" ||
17
+ (isSenderContractOnOrigin && !isSenderContractOnDestination)
18
+
19
+ if (!shouldShowWarning) {
20
+ return null
21
+ }
22
+
23
+ // Determine the warning message
24
+ let warningMessage = ""
25
+ if (fundMethod === "exchange") {
26
+ warningMessage =
27
+ "Using exchange funding may result in loss of funds if the transaction fails and the sender address cannot accept refunds on the origin chain."
28
+ } else if (isSenderContractOnOrigin && !isSenderContractOnDestination) {
29
+ warningMessage =
30
+ "The sender is a contract address. Funds may be lost if the transaction fails and the contract cannot accept refunds on the destination chain."
31
+ }
32
+
33
+ return (
34
+ <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
35
+ <div className="flex items-start space-x-2">
36
+ <svg
37
+ className="w-4 h-4 text-amber-500 flex-shrink-0 mt-0.5"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ viewBox="0 0 24 24"
41
+ aria-hidden="true"
42
+ >
43
+ <path
44
+ strokeLinecap="round"
45
+ strokeLinejoin="round"
46
+ strokeWidth={2}
47
+ 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"
48
+ />
49
+ </svg>
50
+ <div>
51
+ <p className="text-sm font-medium text-amber-600 dark:text-amber-400">
52
+ Risk of Fund Loss
53
+ </p>
54
+ <p className="text-xs mt-1 text-amber-600 dark:text-amber-400">
55
+ {warningMessage}
56
+ </p>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ )
61
+ }
@@ -50,7 +50,7 @@ export const ScreenHeader: React.FC<ScreenHeaderProps> = ({
50
50
  {(rightSideContent || (isConnected && showAccountActions)) && (
51
51
  <div className="absolute right-0 top-1/2 -translate-y-1/2 -translate-x-0 flex items-center gap-2 z-10">
52
52
  {rightSideContent && (
53
- <div className="text-right max-w-[125px]">{rightSideContent}</div>
53
+ <div className="text-right max-w-[280px]">{rightSideContent}</div>
54
54
  )}
55
55
  {isConnected && showAccountActions && <AccountActionsDropdown />}
56
56
  </div>