0xtrails 0.1.13 → 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 (256) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +12 -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/{ccip-D3gTQONK.js → ccip-BbfANth7.js} +12 -12
  7. package/dist/cctp.d.ts.map +1 -1
  8. package/dist/cctpqueue.d.ts +3 -3
  9. package/dist/cctpqueue.d.ts.map +1 -1
  10. package/dist/chains.d.ts.map +1 -1
  11. package/dist/config.d.ts +18 -5
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/constants.d.ts +6 -5
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/contractUtils.d.ts +2 -0
  16. package/dist/contractUtils.d.ts.map +1 -1
  17. package/dist/customChains.d.ts +24 -0
  18. package/dist/customChains.d.ts.map +1 -0
  19. package/dist/gasless.d.ts +19 -7
  20. package/dist/gasless.d.ts.map +1 -1
  21. package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +405 -394
  25. package/dist/indexerClient.d.ts +10 -0
  26. package/dist/indexerClient.d.ts.map +1 -1
  27. package/dist/intentEntrypoint.d.ts +122 -0
  28. package/dist/intentEntrypoint.d.ts.map +1 -0
  29. package/dist/intents.d.ts +5 -3
  30. package/dist/intents.d.ts.map +1 -1
  31. package/dist/metaTxnMonitor.d.ts.map +1 -1
  32. package/dist/morpho.d.ts.map +1 -1
  33. package/dist/pools.d.ts +3 -1
  34. package/dist/pools.d.ts.map +1 -1
  35. package/dist/prepareSend.d.ts +18 -9
  36. package/dist/prepareSend.d.ts.map +1 -1
  37. package/dist/prices.d.ts +1 -1
  38. package/dist/prices.d.ts.map +1 -1
  39. package/dist/relaySdk.d.ts.map +1 -1
  40. package/dist/relayer.d.ts.map +1 -1
  41. package/dist/toast.d.ts +9 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/tokenBalances.d.ts +6 -2
  44. package/dist/tokenBalances.d.ts.map +1 -1
  45. package/dist/tokens.d.ts.map +1 -1
  46. package/dist/trails.d.ts +6 -5
  47. package/dist/trails.d.ts.map +1 -1
  48. package/dist/trailsClient.d.ts +12 -0
  49. package/dist/trailsClient.d.ts.map +1 -0
  50. package/dist/trailsRouter.d.ts +22 -0
  51. package/dist/trailsRouter.d.ts.map +1 -0
  52. package/dist/transactions.d.ts +8 -1
  53. package/dist/transactions.d.ts.map +1 -1
  54. package/dist/wallets.d.ts.map +1 -1
  55. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  56. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  57. package/dist/widget/components/AccountSettings.d.ts +7 -0
  58. package/dist/widget/components/AccountSettings.d.ts.map +1 -0
  59. package/dist/widget/components/ChainList.d.ts +0 -1
  60. package/dist/widget/components/ChainList.d.ts.map +1 -1
  61. package/dist/widget/components/ClassicSwap.d.ts +46 -0
  62. package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
  63. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  64. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  65. package/dist/widget/components/ConnectedWallets.d.ts +9 -0
  66. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
  67. package/dist/widget/components/DebugMenu.d.ts.map +1 -1
  68. package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
  69. package/dist/widget/components/DebugToast.d.ts +3 -0
  70. package/dist/widget/components/DebugToast.d.ts.map +1 -0
  71. package/dist/widget/components/Earn.d.ts.map +1 -1
  72. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  73. package/dist/widget/components/FeeOption.d.ts +22 -0
  74. package/dist/widget/components/FeeOption.d.ts.map +1 -0
  75. package/dist/widget/components/FeeOptions.d.ts +13 -17
  76. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  77. package/dist/widget/components/Fund.d.ts +44 -0
  78. package/dist/widget/components/Fund.d.ts.map +1 -0
  79. package/dist/widget/components/FundMethods.d.ts +1 -1
  80. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  81. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  82. package/dist/widget/components/Identicon.d.ts +9 -0
  83. package/dist/widget/components/Identicon.d.ts.map +1 -0
  84. package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
  85. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  86. package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
  87. package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
  88. package/dist/widget/components/NativeGasOption.d.ts +12 -0
  89. package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
  90. package/dist/widget/components/Pay.d.ts +46 -0
  91. package/dist/widget/components/Pay.d.ts.map +1 -0
  92. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  93. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  94. package/dist/widget/components/Receive.d.ts.map +1 -1
  95. package/dist/widget/components/RecentTokens.d.ts.map +1 -1
  96. package/dist/widget/components/Recipients.d.ts +9 -0
  97. package/dist/widget/components/Recipients.d.ts.map +1 -0
  98. package/dist/widget/components/RefundWarning.d.ts +9 -0
  99. package/dist/widget/components/RefundWarning.d.ts.map +1 -0
  100. package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
  101. package/dist/widget/components/Swap.d.ts.map +1 -1
  102. package/dist/widget/components/SwapSettings.d.ts +1 -5
  103. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  104. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  105. package/dist/widget/components/ThemeSyncer.d.ts +6 -0
  106. package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
  107. package/dist/widget/components/Toast.d.ts +24 -0
  108. package/dist/widget/components/Toast.d.ts.map +1 -0
  109. package/dist/widget/components/TokenList.d.ts.map +1 -1
  110. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  111. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  112. package/dist/widget/components/TruncatedAddress.d.ts +2 -0
  113. package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
  114. package/dist/widget/components/UserPreferences.d.ts +7 -0
  115. package/dist/widget/components/UserPreferences.d.ts.map +1 -0
  116. package/dist/widget/hooks/useBack.d.ts +2 -0
  117. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  118. package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
  119. package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
  120. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  121. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  122. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  123. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  124. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  125. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
  126. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
  127. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  128. package/dist/widget/hooks/usePayMessage.d.ts +34 -0
  129. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
  130. package/dist/widget/hooks/useRecipients.d.ts +17 -0
  131. package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
  132. package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
  133. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
  134. package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
  135. package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
  136. package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
  137. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
  138. package/dist/widget/hooks/useSendForm.d.ts +10 -13
  139. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  140. package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
  141. package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
  142. package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
  143. package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
  144. package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
  145. package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
  146. package/dist/widget/hooks/useTheme.d.ts +14 -0
  147. package/dist/widget/hooks/useTheme.d.ts.map +1 -0
  148. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  149. package/dist/widget/index.js +2 -2
  150. package/dist/widget/widget.d.ts +9 -0
  151. package/dist/widget/widget.d.ts.map +1 -1
  152. package/package.json +38 -36
  153. package/src/aave.ts +6 -1
  154. package/src/analytics.ts +109 -53
  155. package/src/apiClient.ts +1 -1
  156. package/src/cctp.ts +6 -2
  157. package/src/cctpqueue.ts +7 -7
  158. package/src/chains.ts +18 -0
  159. package/src/config.ts +63 -17
  160. package/src/constants.ts +20 -16
  161. package/src/contractUtils.ts +33 -2
  162. package/src/customChains.ts +24 -0
  163. package/src/gasless.ts +162 -109
  164. package/src/index.ts +11 -1
  165. package/src/indexerClient.ts +73 -1
  166. package/src/intentEntrypoint.ts +218 -0
  167. package/src/intents.ts +85 -54
  168. package/src/metaTxnMonitor.ts +1 -0
  169. package/src/morpho.ts +13 -2
  170. package/src/pools.ts +68 -86
  171. package/src/prepareSend.ts +1719 -967
  172. package/src/prices.ts +51 -7
  173. package/src/relaySdk.ts +6 -4
  174. package/src/relayer.ts +6 -3
  175. package/src/toast.ts +110 -0
  176. package/src/tokenBalances.ts +112 -20
  177. package/src/tokens.ts +70 -7
  178. package/src/trails.ts +81 -80
  179. package/src/trailsClient.ts +48 -0
  180. package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
  181. package/src/transactions.ts +30 -88
  182. package/src/umd.tsx +1 -1
  183. package/src/wallets.ts +2 -1
  184. package/src/widget/assets/sequence-logo.svg +15 -0
  185. package/src/widget/compiled.css +2 -2
  186. package/src/widget/components/AccountActionsDropdown.tsx +18 -159
  187. package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
  188. package/src/widget/components/AccountSettings.tsx +102 -0
  189. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  190. package/src/widget/components/ChainList.tsx +10 -20
  191. package/src/widget/components/ClassicSwap.tsx +921 -0
  192. package/src/widget/components/ConfigDisplay.tsx +41 -5
  193. package/src/widget/components/ConnectWallet.tsx +168 -11
  194. package/src/widget/components/ConnectedWallets.tsx +342 -0
  195. package/src/widget/components/DebugMenu.tsx +2 -0
  196. package/src/widget/components/DebugScreensList.tsx +3 -0
  197. package/src/widget/components/DebugToast.tsx +63 -0
  198. package/src/widget/components/Earn.tsx +112 -143
  199. package/src/widget/components/EarnPools.tsx +2 -4
  200. package/src/widget/components/EarnPoolsFilters.tsx +6 -6
  201. package/src/widget/components/FeeOption.tsx +78 -0
  202. package/src/widget/components/FeeOptions.tsx +192 -127
  203. package/src/widget/components/Fund.tsx +1236 -0
  204. package/src/widget/components/FundMethods.tsx +4 -4
  205. package/src/widget/components/FundSendForm.tsx +1 -34
  206. package/src/widget/components/Identicon.tsx +158 -0
  207. package/src/widget/components/MeshConnectExchanges.tsx +32 -3
  208. package/src/widget/components/MeshConnectFlow.tsx +23 -4
  209. package/src/widget/components/NativeGasOption.tsx +99 -0
  210. package/src/widget/components/Pay.tsx +1092 -0
  211. package/src/widget/components/PaySendForm.tsx +1 -38
  212. package/src/widget/components/QuoteDetails.tsx +1 -30
  213. package/src/widget/components/Receipt.tsx +1 -1
  214. package/src/widget/components/Receive.tsx +4 -2
  215. package/src/widget/components/RecentTokens.tsx +2 -1
  216. package/src/widget/components/Recipients.tsx +448 -0
  217. package/src/widget/components/RefundWarning.tsx +61 -0
  218. package/src/widget/components/ScreenHeader.tsx +1 -1
  219. package/src/widget/components/SimpleSwap.tsx +74 -58
  220. package/src/widget/components/Swap.tsx +35 -853
  221. package/src/widget/components/SwapSettings.tsx +5 -11
  222. package/src/widget/components/ThemeProvider.tsx +32 -0
  223. package/src/widget/components/ThemeSyncer.tsx +47 -0
  224. package/src/widget/components/Toast.tsx +315 -0
  225. package/src/widget/components/TokenList.tsx +2 -34
  226. package/src/widget/components/TokenSelector.tsx +14 -3
  227. package/src/widget/components/TransactionDetails.tsx +153 -13
  228. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  229. package/src/widget/components/TruncatedAddress.tsx +5 -1
  230. package/src/widget/components/UserPreferences.tsx +155 -0
  231. package/src/widget/components/WalletList.tsx +1 -1
  232. package/src/widget/hooks/useBack.tsx +4 -0
  233. package/src/widget/hooks/useBalanceVisible.tsx +40 -2
  234. package/src/widget/hooks/useCheckout.ts +13 -0
  235. package/src/widget/hooks/useCurrentScreen.tsx +4 -0
  236. package/src/widget/hooks/useDebugScreens.ts +12 -2
  237. package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -0
  238. package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
  239. package/src/widget/hooks/usePayMessage.tsx +370 -0
  240. package/src/widget/hooks/useRecipients.ts +168 -0
  241. package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
  242. package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
  243. package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
  244. package/src/widget/hooks/useSendForm.ts +257 -49
  245. package/src/widget/hooks/useSwapAmount.tsx +50 -0
  246. package/src/widget/hooks/useSwapSettings.tsx +100 -0
  247. package/src/widget/hooks/useTargetAmount.ts +23 -0
  248. package/src/widget/hooks/useTheme.tsx +80 -0
  249. package/src/widget/hooks/useTokenList.ts +20 -11
  250. package/src/widget/index.css +45 -21
  251. package/src/widget/widget.tsx +294 -136
  252. package/dist/address.d.ts +0 -2
  253. package/dist/address.d.ts.map +0 -1
  254. package/dist/proxyCaller.d.ts +0 -21
  255. package/dist/proxyCaller.d.ts.map +0 -1
  256. package/src/address.ts +0 -6
@@ -0,0 +1,1092 @@
1
+ import { ChevronRight, Search, Loader2, ChevronDown } from "lucide-react"
2
+ import { useEffect, useState, useMemo, useRef, useCallback } from "react"
3
+ import type React from "react"
4
+ import type { Account, WalletClient } from "viem"
5
+ import type { TransactionState } from "../../transactions.js"
6
+ import type { OnCompleteProps } from "../hooks/useSendForm.js"
7
+ import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
8
+ import { useSendForm } from "../hooks/useSendForm.js"
9
+ import { TradeType } from "../../prepareSend.js"
10
+ import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
11
+ import { useDestinationSelectedToken } from "../hooks/useDestinationSelectedToken.js"
12
+ import { useSelectedRecipient } from "../hooks/useSelectedRecipient.js"
13
+ import { useSwapAmount } from "../hooks/useSwapAmount.js"
14
+ import { useTokenList } from "../hooks/useTokenList.js"
15
+ import { useMode } from "../hooks/useMode.js"
16
+ import { useCurrentScreen } from "../hooks/useCurrentScreen.js"
17
+ import { useDefaultTokenSelection } from "../hooks/useDefaultTokenSelection.js" // Context version
18
+ import { usePayMessage } from "../hooks/usePayMessage.js"
19
+ import { useTargetAmount } from "../hooks/useTargetAmount.js"
20
+ import { TokenImage } from "./TokenImage.js"
21
+ import { Identicon } from "./Identicon.js"
22
+ import { TokenSelector } from "./TokenSelector.js"
23
+ import { ScreenHeader } from "./ScreenHeader.js"
24
+ import { ChainList } from "./ChainList.js"
25
+ import { QuoteDetails } from "./QuoteDetails.js"
26
+ import { ErrorDisplay } from "./ErrorDisplay.js"
27
+ import { getChainInfo } from "../../chains.js"
28
+ import { FeeOptions } from "./FeeOptions.js"
29
+ import { truncateAddress } from "../../utils.js"
30
+ import type { PrepareSendQuote } from "../../prepareSend.js"
31
+ import type { SupportedToken } from "../../tokens.js"
32
+ import { logger } from "../../logger.js"
33
+ import { RefundWarning } from "./RefundWarning.js"
34
+
35
+ interface PayProps {
36
+ selectedToken?: any // Origin token (optional - user can select)
37
+ onBack?: () => void
38
+ account: Account
39
+ walletClient: WalletClient
40
+ onTransactionStateChange: (transactionStates: TransactionState[]) => void
41
+ onError: (error: Error | string | null) => void
42
+ onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
43
+ onConfirm: () => void
44
+ onComplete: (result: OnCompleteProps) => void
45
+ onSend: (amount: string, recipient: string) => void
46
+ paymasterUrls?: Array<{ chainId: number; url: string }>
47
+ gasless?: boolean
48
+ setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
49
+ quoteProvider?: string
50
+ fundMethod?: string
51
+ onNavigateToMeshConnect?: (
52
+ props: {
53
+ toTokenSymbol: string
54
+ toTokenAmount: string
55
+ toChainId: number
56
+ toRecipientAddress: string
57
+ },
58
+ quote?: PrepareSendQuote | null,
59
+ ) => void
60
+ checkoutOnHandlers?: CheckoutOnHandlers
61
+ recentTokens?: SupportedToken[]
62
+ onRecentTokenSelect?: (token: SupportedToken) => void
63
+ onTrackToken?: (token: any) => void
64
+ toRecipient?: string
65
+ toAmount?: string
66
+ toChainId?: number
67
+ toToken?: string
68
+ toCalldata?: string
69
+ onAmountUpdate?: (amount: string) => void
70
+ }
71
+
72
+ export const Pay: React.FC<PayProps> = ({
73
+ selectedToken: initialOriginToken,
74
+ onBack,
75
+ account,
76
+ walletClient,
77
+ onTransactionStateChange,
78
+ onError,
79
+ onWaitingForWalletConfirm,
80
+ onConfirm,
81
+ onComplete,
82
+ onSend,
83
+ paymasterUrls,
84
+ gasless,
85
+ setWalletConfirmRetryHandler,
86
+ quoteProvider,
87
+ fundMethod,
88
+ onNavigateToMeshConnect,
89
+ checkoutOnHandlers,
90
+ recentTokens,
91
+ onRecentTokenSelect,
92
+ onTrackToken,
93
+ toRecipient,
94
+ toAmount,
95
+ toChainId,
96
+ toToken,
97
+ toCalldata,
98
+ onAmountUpdate,
99
+ }) => {
100
+ const { mode } = useMode()
101
+ const { selectedToken: globalOriginToken, setSelectedToken: setOriginToken } =
102
+ useOriginSelectedToken()
103
+ const {
104
+ selectedToken: globalDestinationToken,
105
+ setSelectedToken: setDestinationToken,
106
+ } = useDestinationSelectedToken()
107
+ const { selectedRecipient, setSelectedRecipient } = useSelectedRecipient()
108
+ const { setAmount: setGlobalAmount } = useSwapAmount()
109
+ const { setCurrentScreen } = useCurrentScreen()
110
+ const { targetAmountUsd } = useTargetAmount()
111
+
112
+ // Use new default token selection hook
113
+ const {
114
+ defaultOriginToken,
115
+ defaultDestinationToken,
116
+ isLoading: isLoadingDefaults,
117
+ } = useDefaultTokenSelection()
118
+
119
+ // Use pay message hook for payment request display
120
+ const { message: payMessage } = usePayMessage()
121
+
122
+ // Use global origin token state or initial prop
123
+ const originToken = globalOriginToken || initialOriginToken
124
+
125
+ // Local state for pay-specific functionality
126
+ const [tokenAmountForBackend, setTokenAmountForBackend] = useState("")
127
+ const [inputDisplayValue, setInputDisplayValue] = useState("")
128
+ const [showOriginTokenSelector, setShowOriginTokenSelector] = useState(false)
129
+ const [showOriginChainList, setShowOriginChainList] = useState(false)
130
+ const [showDestinationTokenSelector, setShowDestinationTokenSelector] =
131
+ useState(false)
132
+ const [showDestinationChainList, setShowDestinationChainList] =
133
+ useState(false)
134
+ const inputRef = useRef<HTMLInputElement>(null)
135
+ const paymentRequestInputRef = useRef<HTMLInputElement>(null)
136
+ const lastAutoSelectedRef = useRef<string | null>(null)
137
+
138
+ // Get sorted tokens to auto-select origin and destination tokens
139
+ const { filteredTokensFormatted } = useTokenList({
140
+ onContinue: () => {}, // Not used for auto-selection
141
+ onError: () => {}, // Not used for auto-selection
142
+ fundMethod: undefined,
143
+ allSupportedTokens: true, // Show all tokens for destination selection
144
+ })
145
+
146
+ // Use useSendForm for quote functionality with EXACT_OUTPUT trade type
147
+ const {
148
+ amountUsdDisplay,
149
+ isLoadingQuote,
150
+ prepareSendQuote,
151
+ setAmount: setSendFormAmount,
152
+ handleSubmit,
153
+ isSubmitting,
154
+ buttonText,
155
+ isValidRecipient,
156
+ selectedDestToken,
157
+ setSelectedDestToken,
158
+ setSelectedDestinationChain,
159
+ quoteError,
160
+ quoteErrorPrettified,
161
+ destinationTokenAddress,
162
+ isValidCustomToken,
163
+ isSenderContractOnOrigin,
164
+ isSenderContractOnDestination,
165
+ feeOptions,
166
+ selectedFeeToken,
167
+ setSelectedFeeToken,
168
+ } = useSendForm({
169
+ account,
170
+ toAmount: tokenAmountForBackend || toAmount, // Use the input amount as target amount for EXACT_OUTPUT
171
+ toRecipient: selectedRecipient || toRecipient || account.address,
172
+ toChainId,
173
+ toToken,
174
+ toCalldata,
175
+ walletClient,
176
+ onTransactionStateChange,
177
+ onError,
178
+ onWaitingForWalletConfirm,
179
+ paymasterUrls,
180
+ gasless,
181
+ onConfirm,
182
+ onComplete,
183
+ onSend,
184
+ selectedToken: originToken as any,
185
+ setWalletConfirmRetryHandler,
186
+ tradeType: TradeType.EXACT_OUTPUT, // Key difference: using EXACT_OUTPUT
187
+ quoteProvider,
188
+ fundMethod,
189
+ mode,
190
+ onNavigateToMeshConnect,
191
+ checkoutOnHandlers,
192
+ })
193
+
194
+ // Auto-select origin token using new hook
195
+ useEffect(() => {
196
+ if (!originToken && !isLoadingDefaults && defaultOriginToken) {
197
+ logger.console.log(
198
+ "[trails-sdk] Auto-selecting origin token:",
199
+ defaultOriginToken,
200
+ )
201
+ setOriginToken(defaultOriginToken as any)
202
+ }
203
+ }, [originToken, isLoadingDefaults, defaultOriginToken, setOriginToken])
204
+
205
+ // Initialize destination token from props or auto-select
206
+ useEffect(() => {
207
+ const currentKey = `${toToken}-${toChainId}`
208
+
209
+ if (
210
+ toToken &&
211
+ toChainId &&
212
+ filteredTokensFormatted?.length > 0 &&
213
+ lastAutoSelectedRef.current !== currentKey
214
+ ) {
215
+ // Find the destination token from props
216
+ const destinationToken = filteredTokensFormatted?.find(
217
+ (token) =>
218
+ token.chainId === Number(toChainId) &&
219
+ (token.contractAddress.toLowerCase() === toToken.toLowerCase() ||
220
+ token.symbol.toLowerCase() === toToken.toLowerCase()),
221
+ )
222
+
223
+ if (destinationToken) {
224
+ const decimals =
225
+ destinationToken.contractInfo?.decimals ??
226
+ (destinationToken as any)?.decimals
227
+
228
+ const formattedToken = {
229
+ ...destinationToken,
230
+ contractInfo: {
231
+ decimals,
232
+ contractAddress: destinationToken.contractAddress,
233
+ symbol: destinationToken.symbol,
234
+ name: destinationToken.name,
235
+ },
236
+ }
237
+
238
+ logger.console.log(
239
+ "[trails-sdk] Auto-selecting destination token from props:",
240
+ formattedToken,
241
+ )
242
+
243
+ setDestinationToken(formattedToken as any)
244
+ setSelectedDestToken({
245
+ symbol: destinationToken.symbol,
246
+ name: destinationToken.name,
247
+ imageUrl: destinationToken.imageUrl,
248
+ decimals,
249
+ })
250
+
251
+ // Set the destination chain
252
+ if (setSelectedDestinationChain) {
253
+ const chainInfo = getChainInfo(Number(toChainId))
254
+ if (chainInfo) {
255
+ setSelectedDestinationChain(chainInfo)
256
+ }
257
+ }
258
+
259
+ // Mark this combination as processed
260
+ lastAutoSelectedRef.current = currentKey
261
+ }
262
+ }
263
+ }, [
264
+ toToken,
265
+ toChainId,
266
+ filteredTokensFormatted,
267
+ setDestinationToken,
268
+ setSelectedDestToken,
269
+ setSelectedDestinationChain,
270
+ ])
271
+
272
+ // Initialize selected recipient from toRecipient prop or default to connected wallet
273
+ useEffect(() => {
274
+ if (toRecipient && !selectedRecipient) {
275
+ setSelectedRecipient(toRecipient)
276
+ } else if (!selectedRecipient && account?.address) {
277
+ // Default to connected wallet address if no recipient is set
278
+ setSelectedRecipient(account.address)
279
+ }
280
+ }, [toRecipient, selectedRecipient, setSelectedRecipient, account?.address])
281
+
282
+ // Initialize and update amount from toAmount prop
283
+ useEffect(() => {
284
+ logger.console.log("[trails-sdk] Pay component toAmount effect:", {
285
+ toAmount,
286
+ tokenAmountForBackend,
287
+ inputDisplayValue,
288
+ willUpdate: !!toAmount && toAmount !== tokenAmountForBackend,
289
+ })
290
+
291
+ // Update amount when toAmount prop changes (for payment requests)
292
+ if (toAmount && toAmount !== tokenAmountForBackend) {
293
+ logger.console.log(
294
+ "[trails-sdk] Updating amount from toAmount prop:",
295
+ toAmount,
296
+ )
297
+ setTokenAmountForBackend(toAmount)
298
+ setInputDisplayValue(toAmount)
299
+ setSendFormAmount(toAmount)
300
+ setGlobalAmount(toAmount)
301
+ }
302
+ }, [
303
+ toAmount,
304
+ tokenAmountForBackend,
305
+ inputDisplayValue,
306
+ setSendFormAmount,
307
+ setGlobalAmount,
308
+ ])
309
+
310
+ // Initialize destination token from global state or default when no toToken prop
311
+ useEffect(() => {
312
+ if (!toToken && !selectedDestToken?.symbol) {
313
+ // Prefer global destination token if set
314
+ const destTokenToUse = globalDestinationToken || defaultDestinationToken
315
+
316
+ if (destTokenToUse && !isLoadingDefaults) {
317
+ logger.console.log(
318
+ "[trails-sdk] Initializing destination token:",
319
+ destTokenToUse,
320
+ )
321
+
322
+ // Set destination token if not already set by global state
323
+ if (!globalDestinationToken && defaultDestinationToken) {
324
+ setDestinationToken(defaultDestinationToken as any)
325
+ }
326
+
327
+ const decimals =
328
+ destTokenToUse.contractInfo?.decimals ??
329
+ (destTokenToUse as any)?.decimals
330
+
331
+ setSelectedDestToken({
332
+ symbol: destTokenToUse.symbol,
333
+ name: destTokenToUse.name,
334
+ imageUrl: destTokenToUse.imageUrl,
335
+ decimals,
336
+ })
337
+
338
+ // Also set the destination chain if available
339
+ if (setSelectedDestinationChain && destTokenToUse.chainId) {
340
+ const chainInfo = getChainInfo(destTokenToUse.chainId)
341
+ if (chainInfo) {
342
+ setSelectedDestinationChain(chainInfo)
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }, [
348
+ toToken,
349
+ globalDestinationToken,
350
+ defaultDestinationToken,
351
+ isLoadingDefaults,
352
+ selectedDestToken?.symbol,
353
+ setDestinationToken,
354
+ setSelectedDestToken,
355
+ setSelectedDestinationChain,
356
+ ])
357
+
358
+ // Auto-focus input field on component mount
359
+ useEffect(() => {
360
+ if (inputRef.current) {
361
+ inputRef.current.focus()
362
+ }
363
+ }, [])
364
+
365
+ // Handle input amount changes with 8 decimal limit and 16 char total limit
366
+ const handleAmountChange = useCallback(
367
+ (value: string) => {
368
+ // Allow empty string
369
+ if (value === "") {
370
+ setInputDisplayValue("")
371
+ setTokenAmountForBackend("")
372
+ setSendFormAmount("")
373
+ setGlobalAmount("")
374
+ onAmountUpdate?.("")
375
+ return
376
+ }
377
+
378
+ // Limit total length to 16 characters
379
+ if (value.length > 16) {
380
+ return
381
+ }
382
+
383
+ // Validate decimal places (max 8 decimals) and allow single decimal point
384
+ const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
385
+ if (!decimalMatch) {
386
+ return // Don't update if invalid format
387
+ }
388
+
389
+ // Store the display value
390
+ setInputDisplayValue(value)
391
+
392
+ // Update the token amount for backend and useSendForm
393
+ setTokenAmountForBackend(value)
394
+ setSendFormAmount(value)
395
+ setGlobalAmount(value)
396
+ onAmountUpdate?.(value)
397
+ },
398
+ [setSendFormAmount, setGlobalAmount, onAmountUpdate],
399
+ )
400
+
401
+ // Get display values based on input type
402
+ const displayAmount = useMemo(() => {
403
+ return inputDisplayValue
404
+ }, [inputDisplayValue])
405
+
406
+ // Dynamic font size based on input length
407
+ const inputStyles = useMemo(() => {
408
+ const inputLength = displayAmount.length
409
+ let fontSize: string
410
+
411
+ if (inputLength > 12) {
412
+ fontSize = "0.875rem"
413
+ } else if (inputLength > 9) {
414
+ fontSize = "1rem"
415
+ } else if (inputLength > 6) {
416
+ fontSize = "1.125rem"
417
+ } else if (inputLength > 3) {
418
+ fontSize = "1.25rem"
419
+ } else {
420
+ fontSize = "1.5rem"
421
+ }
422
+
423
+ return {
424
+ fontSize,
425
+ transition: "all 0.1s ease-in-out",
426
+ }
427
+ }, [displayAmount.length])
428
+
429
+ const handleOriginTokenSelect = useCallback(
430
+ (token: any) => {
431
+ const formattedToken = {
432
+ ...token,
433
+ decimals: token.contractInfo?.decimals || token.decimals,
434
+ contractInfo: {
435
+ decimals: token.contractInfo?.decimals || token.decimals,
436
+ contractAddress: token.contractAddress,
437
+ symbol: token.symbol,
438
+ name: token.name,
439
+ },
440
+ }
441
+
442
+ // Update global origin token state
443
+ setOriginToken(formattedToken as any)
444
+ setShowOriginTokenSelector(false)
445
+ logger.console.log("[trails-sdk] selected origin token", token)
446
+ // Track the token selection
447
+ onTrackToken?.(token)
448
+ },
449
+ [setOriginToken, onTrackToken],
450
+ )
451
+
452
+ const handleDestinationTokenSelect = useCallback(
453
+ (token: any) => {
454
+ const formattedToken = {
455
+ ...token,
456
+ decimals: token.contractInfo?.decimals || token.decimals,
457
+ contractInfo: {
458
+ decimals: token.contractInfo?.decimals || token.decimals,
459
+ contractAddress: token.contractAddress,
460
+ symbol: token.symbol,
461
+ name: token.name,
462
+ },
463
+ }
464
+
465
+ // Update both global destination token state and useSendForm state
466
+ setDestinationToken(formattedToken as any)
467
+ setSelectedDestToken(formattedToken as any)
468
+
469
+ // Update destination chain to match the selected token's chain
470
+ if (setSelectedDestinationChain && token.chainId) {
471
+ const chainInfo = getChainInfo(token.chainId)
472
+ if (chainInfo) {
473
+ setSelectedDestinationChain(chainInfo)
474
+ }
475
+ }
476
+
477
+ setShowDestinationTokenSelector(false)
478
+ logger.console.log("[trails-sdk] selected destination token", token)
479
+ // Track the token selection
480
+ onTrackToken?.(token)
481
+ },
482
+ [
483
+ setDestinationToken,
484
+ setSelectedDestToken,
485
+ setSelectedDestinationChain,
486
+ onTrackToken,
487
+ ],
488
+ )
489
+
490
+ // Show origin chain list screen
491
+ if (showOriginChainList) {
492
+ return (
493
+ <ChainList
494
+ onBack={() => {
495
+ setShowOriginChainList(false)
496
+ setShowOriginTokenSelector(true)
497
+ }}
498
+ />
499
+ )
500
+ }
501
+
502
+ // Show destination chain list screen
503
+ if (showDestinationChainList) {
504
+ return (
505
+ <ChainList
506
+ onBack={() => {
507
+ setShowDestinationChainList(false)
508
+ setShowDestinationTokenSelector(true)
509
+ }}
510
+ />
511
+ )
512
+ }
513
+
514
+ // Show origin token selector screen
515
+ if (showOriginTokenSelector) {
516
+ return (
517
+ <div className="space-y-2">
518
+ <ScreenHeader
519
+ onBack={() => setShowOriginTokenSelector(false)}
520
+ headerContent="Select Token"
521
+ headerContentAlign="left"
522
+ showAccountActions={true}
523
+ />
524
+ <TokenSelector
525
+ onTokenSelect={handleOriginTokenSelect}
526
+ targetAmountUsd={targetAmountUsd}
527
+ onError={onError}
528
+ fundMethod={fundMethod}
529
+ showContinueButton={false}
530
+ compactMode={false}
531
+ recentTokens={recentTokens}
532
+ onRecentTokenSelect={(token) => {
533
+ const formattedToken = {
534
+ ...token,
535
+ decimals: token.decimals,
536
+ contractInfo: {
537
+ decimals: token.decimals,
538
+ contractAddress: token.contractAddress,
539
+ symbol: token.symbol,
540
+ name: token.name,
541
+ },
542
+ } as any
543
+ setOriginToken(formattedToken)
544
+ setShowOriginTokenSelector(false)
545
+ onRecentTokenSelect?.(token)
546
+ }}
547
+ allSupportedTokens={false}
548
+ chainListScreen={true}
549
+ onNavigateToChainList={() => {
550
+ setShowOriginTokenSelector(false)
551
+ setShowOriginChainList(true)
552
+ }}
553
+ onNavigateToFundMethods={() => {
554
+ setCurrentScreen("fund-methods")
555
+ }}
556
+ />
557
+ </div>
558
+ )
559
+ }
560
+
561
+ // Show destination token selector screen
562
+ if (showDestinationTokenSelector) {
563
+ return (
564
+ <div className="space-y-2">
565
+ <ScreenHeader
566
+ onBack={() => setShowDestinationTokenSelector(false)}
567
+ headerContent="Select Token"
568
+ headerContentAlign="left"
569
+ showAccountActions={true}
570
+ />
571
+ <TokenSelector
572
+ onTokenSelect={handleDestinationTokenSelect}
573
+ targetAmountUsd={targetAmountUsd}
574
+ onError={onError}
575
+ fundMethod={fundMethod}
576
+ showContinueButton={false}
577
+ compactMode={false}
578
+ recentTokens={recentTokens}
579
+ onRecentTokenSelect={(token) => {
580
+ const formattedToken = {
581
+ ...token,
582
+ decimals: token.decimals,
583
+ contractInfo: {
584
+ decimals: token.decimals,
585
+ contractAddress: token.contractAddress,
586
+ symbol: token.symbol,
587
+ name: token.name,
588
+ },
589
+ } as any
590
+ setDestinationToken(formattedToken)
591
+ setSelectedDestToken(formattedToken)
592
+ setShowDestinationTokenSelector(false)
593
+ onRecentTokenSelect?.(token)
594
+ }}
595
+ allSupportedTokens={true}
596
+ chainListScreen={true}
597
+ onNavigateToChainList={() => {
598
+ setShowDestinationTokenSelector(false)
599
+ setShowDestinationChainList(true)
600
+ }}
601
+ />
602
+ </div>
603
+ )
604
+ }
605
+
606
+ // Check if this is a payment request (all required props are set)
607
+ const isPaymentRequest = !!(toToken && toAmount && toChainId && toRecipient)
608
+
609
+ return (
610
+ <div className="space-y-4">
611
+ <ScreenHeader
612
+ onBack={onBack}
613
+ headerContent="Pay"
614
+ headerContentAlign="left"
615
+ showAccountActions={true}
616
+ />
617
+
618
+ {isPaymentRequest ? (
619
+ /* Payment Request UI */
620
+ <>
621
+ {/* Payment Request Header */}
622
+ <div className="space-y-1 trails-bg-secondary trails-border-radius-container p-3">
623
+ <div className="flex justify-start">
624
+ <div className="flex items-center font-medium trails-text-primary text-sm whitespace-nowrap overflow-hidden">
625
+ {payMessage}
626
+ </div>
627
+ </div>
628
+ </div>
629
+
630
+ {/* Origin Token Selection for Payment Request */}
631
+ <div className="space-y-1">
632
+ <div className="trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary">
633
+ {/* Amount to Pay Label */}
634
+ <div className="flex justify-between items-center mb-2">
635
+ <div className="text-sm font-bold trails-text-secondary text-left">
636
+ Pay with
637
+ {fundMethod === "qr-code"
638
+ ? " QR Code"
639
+ : fundMethod === "exchange"
640
+ ? " Exchange"
641
+ : ""}
642
+ </div>
643
+ </div>
644
+
645
+ <div className="flex items-center space-x-2">
646
+ {/* Amount Display - Non-editable */}
647
+ <div className="flex-1">
648
+ <div
649
+ className="flex items-center justify-start cursor-text"
650
+ onClick={() => paymentRequestInputRef.current?.focus()}
651
+ >
652
+ <div className="flex items-center">
653
+ <input
654
+ ref={paymentRequestInputRef}
655
+ type="text"
656
+ value={prepareSendQuote?.originAmountFormatted || ""}
657
+ readOnly={true}
658
+ className={`bg-transparent border-none outline-none font-bold text-left trails-text-primary ${
659
+ isLoadingQuote ? "animate-pulse" : ""
660
+ }`}
661
+ style={{
662
+ fontSize: inputStyles.fontSize,
663
+ width: prepareSendQuote?.originAmountFormatted
664
+ ? `${Math.max(prepareSendQuote.originAmountFormatted.length - 1 + 0.5, 1)}ch`
665
+ : "1ch",
666
+ minWidth: "1ch",
667
+ maxWidth: "200px",
668
+ padding: "0",
669
+ margin: "0",
670
+ transition: "all 0.1s ease-in-out",
671
+ }}
672
+ />
673
+ <span
674
+ className="font-bold text-gray-400 dark:text-gray-500"
675
+ style={{
676
+ fontSize: inputStyles.fontSize,
677
+ marginLeft: "0.1em",
678
+ padding: "0",
679
+ transition: "all 0.2s ease-in-out",
680
+ }}
681
+ >
682
+ {originToken?.symbol || "TOKEN"}
683
+ </span>
684
+ {isLoadingQuote && (
685
+ <div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
686
+ )}
687
+ </div>
688
+ </div>
689
+ </div>
690
+
691
+ {/* Origin Token Selection Button */}
692
+ <button
693
+ type="button"
694
+ onClick={() => setShowOriginTokenSelector(true)}
695
+ className="flex items-center space-x-2 trails-bg-card hover:trails-hover-bg trails-border-radius-input px-2.5 py-1.5 border trails-border-primary transition-colors cursor-pointer"
696
+ >
697
+ {originToken ? (
698
+ <>
699
+ <TokenImage
700
+ symbol={originToken.symbol}
701
+ imageUrl={originToken.imageUrl}
702
+ chainId={originToken.chainId}
703
+ size={20}
704
+ />
705
+ <span className="font-medium trails-text-primary text-sm">
706
+ {originToken.symbol}
707
+ </span>
708
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
709
+ </>
710
+ ) : (
711
+ <>
712
+ <span className="font-medium trails-text-muted text-sm">
713
+ Select Token
714
+ </span>
715
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
716
+ </>
717
+ )}
718
+ </button>
719
+ </div>
720
+
721
+ {/* Bottom Info Row */}
722
+ <div className="mt-2 flex justify-between items-center">
723
+ {/* USD Amount */}
724
+ <div className="text-xs trails-text-muted">
725
+ {originToken?.symbol &&
726
+ prepareSendQuote?.originAmountFormatted ? (
727
+ <>≈ {prepareSendQuote?.originAmountUsdDisplay || "$0.00"}</>
728
+ ) : (
729
+ <span>&nbsp;</span>
730
+ )}
731
+ </div>
732
+ <div className="text-xs trails-text-muted">
733
+ <span>&nbsp;</span>
734
+ </div>
735
+ </div>
736
+ </div>
737
+ </div>
738
+ </>
739
+ ) : (
740
+ /* Regular Pay UI */
741
+ <>
742
+ {/* Recipient and Origin Token Section */}
743
+ <div className="space-y-1 trails-bg-secondary trails-border-radius-container p-3">
744
+ {/* Recipient Button - Like Fund.tsx */}
745
+ <div className="relative">
746
+ {toRecipient ? (
747
+ /* Display only - recipient is fixed */
748
+ <div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2">
749
+ <div className="text-left">
750
+ <div className="font-medium trails-text-primary text-sm">
751
+ Recipient address
752
+ </div>
753
+ </div>
754
+ <div className="flex items-center space-x-2">
755
+ <div
756
+ className="font-medium trails-text-primary text-sm"
757
+ title={toRecipient}
758
+ >
759
+ {truncateAddress(toRecipient)}
760
+ </div>
761
+ <Identicon
762
+ value={toRecipient}
763
+ size={24}
764
+ className="flex-shrink-0"
765
+ />
766
+ </div>
767
+ </div>
768
+ ) : (
769
+ /* Interactive button - user can select recipient */
770
+ <button
771
+ type="button"
772
+ onClick={() => setCurrentScreen("recipients")}
773
+ className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
774
+ >
775
+ <div className="text-left">
776
+ <div className="font-medium trails-text-primary text-sm">
777
+ Recipient address
778
+ </div>
779
+ </div>
780
+ <div className="flex items-center space-x-2">
781
+ {selectedRecipient ? (
782
+ <>
783
+ <div
784
+ className="font-medium trails-text-primary text-sm"
785
+ title={selectedRecipient}
786
+ >
787
+ {truncateAddress(selectedRecipient)}
788
+ </div>
789
+ <Identicon
790
+ value={selectedRecipient}
791
+ size={24}
792
+ className="flex-shrink-0"
793
+ />
794
+ </>
795
+ ) : (
796
+ <>
797
+ <div className="font-medium text-gray-900 dark:text-white text-sm">
798
+ Select address
799
+ </div>
800
+ <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
801
+ <div className="w-4 h-4 rounded-full bg-gray-400 dark:bg-gray-500"></div>
802
+ </div>
803
+ </>
804
+ )}
805
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
806
+ </div>
807
+ </button>
808
+ )}
809
+ </div>
810
+
811
+ {/* Origin Token Selector - Like Fund.tsx */}
812
+ <div className="relative">
813
+ <button
814
+ type="button"
815
+ onClick={() => setShowOriginTokenSelector(true)}
816
+ className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
817
+ >
818
+ <div className="text-left">
819
+ <div className="font-medium trails-text-primary text-sm">
820
+ Pay with
821
+ {fundMethod === "qr-code"
822
+ ? " QR Code"
823
+ : fundMethod === "exchange"
824
+ ? " Exchange"
825
+ : ""}
826
+ </div>
827
+ </div>
828
+
829
+ <div className="flex items-center space-x-2">
830
+ {originToken ? (
831
+ <>
832
+ <div className="font-medium trails-text-primary text-sm">
833
+ {isLoadingQuote ? (
834
+ <div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
835
+ ) : prepareSendQuote?.originAmountFormatted ? (
836
+ <span>
837
+ {prepareSendQuote.originAmountFormatted}{" "}
838
+ {originToken.symbol}
839
+ </span>
840
+ ) : (
841
+ ""
842
+ )}
843
+ </div>
844
+ <TokenImage
845
+ symbol={originToken.symbol}
846
+ imageUrl={originToken.imageUrl}
847
+ chainId={originToken.chainId}
848
+ size={24}
849
+ />
850
+ </>
851
+ ) : (
852
+ <>
853
+ <div className="font-medium text-gray-900 dark:text-white text-sm">
854
+ Select payment token
855
+ </div>
856
+ <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
857
+ <Search className="w-4 h-4 text-gray-400" />
858
+ </div>
859
+ </>
860
+ )}
861
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
862
+ </div>
863
+ </button>
864
+ </div>
865
+ </div>
866
+
867
+ <div className="space-y-1">
868
+ {/* Destination Amount Input Section - Like Fund.tsx but for destination token */}
869
+ <div className="trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary">
870
+ {/* Amount to Pay Label */}
871
+ <div className="flex justify-between items-center mb-2">
872
+ <div className="text-sm font-medium trails-text-secondary text-left">
873
+ Recipient receives
874
+ </div>
875
+ </div>
876
+
877
+ <div className="flex items-center space-x-2">
878
+ {/* Amount Input */}
879
+ <div className="flex-1">
880
+ <div
881
+ className="flex items-center justify-start cursor-text"
882
+ onClick={() => inputRef.current?.focus()}
883
+ >
884
+ <div className="flex items-center">
885
+ <input
886
+ ref={inputRef}
887
+ type="text"
888
+ value={displayAmount}
889
+ onChange={(e) => handleAmountChange(e.target.value)}
890
+ placeholder={`0 ${selectedDestToken?.symbol || ""}`}
891
+ readOnly={!!toAmount}
892
+ className={`bg-transparent border-none outline-none font-bold text-left trails-text-primary placeholder-trails-text-primary ${
893
+ isLoadingQuote ? "animate-pulse" : ""
894
+ }`}
895
+ style={{
896
+ fontSize: inputStyles.fontSize,
897
+ width: `${Math.max((displayAmount || "0").length - 1 + 0.5, 1)}ch`,
898
+ minWidth: "1ch",
899
+ maxWidth: "270px",
900
+ padding: "0",
901
+ margin: "0",
902
+ transition: "all 0.1s ease-in-out",
903
+ }}
904
+ inputMode="decimal"
905
+ />
906
+ <span
907
+ className="font-bold text-gray-400 dark:text-gray-500"
908
+ style={{
909
+ fontSize: inputStyles.fontSize,
910
+ marginLeft: "0.1em",
911
+ padding: "0",
912
+ transition: "all 0.2s ease-in-out",
913
+ }}
914
+ >
915
+ {selectedDestToken?.symbol?.slice(0, 4) || "TOKEN"}
916
+ </span>
917
+ {isLoadingQuote && (
918
+ <div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
919
+ )}
920
+ </div>
921
+ </div>
922
+ </div>
923
+
924
+ {/* Destination Token Selection Button */}
925
+ {toToken ? (
926
+ /* Non-clickable display when toToken is provided */
927
+ <div className="flex items-center space-x-2 trails-bg-card trails-border-radius-input px-2.5 py-1.5 border trails-border-primary">
928
+ {selectedDestToken ? (
929
+ <>
930
+ <TokenImage
931
+ symbol={selectedDestToken.symbol}
932
+ imageUrl={selectedDestToken.imageUrl}
933
+ chainId={globalDestinationToken?.chainId || toChainId}
934
+ size={20}
935
+ />
936
+ <span className="font-medium trails-text-primary text-sm">
937
+ {selectedDestToken.symbol}
938
+ </span>
939
+ </>
940
+ ) : (
941
+ <span className="font-medium trails-text-muted text-sm">
942
+ Select Token
943
+ </span>
944
+ )}
945
+ </div>
946
+ ) : (
947
+ /* Clickable button when toToken is not provided */
948
+ <button
949
+ type="button"
950
+ onClick={() => setShowDestinationTokenSelector(true)}
951
+ className="flex items-center space-x-2 trails-bg-card hover:trails-hover-bg trails-border-radius-input px-2.5 py-1.5 border trails-border-primary transition-colors cursor-pointer"
952
+ >
953
+ {selectedDestToken ? (
954
+ <>
955
+ <TokenImage
956
+ symbol={selectedDestToken.symbol}
957
+ imageUrl={selectedDestToken.imageUrl}
958
+ chainId={globalDestinationToken?.chainId || toChainId}
959
+ size={20}
960
+ />
961
+ <span className="font-medium trails-text-primary text-sm">
962
+ {selectedDestToken.symbol}
963
+ </span>
964
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
965
+ </>
966
+ ) : (
967
+ <>
968
+ <span className="font-medium trails-text-muted text-sm">
969
+ Select Token
970
+ </span>
971
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
972
+ </>
973
+ )}
974
+ </button>
975
+ )}
976
+ </div>
977
+
978
+ {/* Bottom Info Row */}
979
+ <div className="mt-2 flex justify-between items-center">
980
+ {/* USD Amount */}
981
+ <div className="text-xs trails-text-muted">
982
+ {selectedDestToken?.symbol && displayAmount ? (
983
+ <>≈ {amountUsdDisplay || "$0.00"}</>
984
+ ) : (
985
+ <span>&nbsp;</span>
986
+ )}
987
+ </div>
988
+ <div className="text-xs trails-text-muted">
989
+ <span>&nbsp;</span>
990
+ </div>
991
+ </div>
992
+ </div>
993
+ </div>
994
+ </>
995
+ )}
996
+
997
+ {/* Exchange/Contract Warning */}
998
+ <RefundWarning
999
+ fundMethod={fundMethod}
1000
+ isSenderContractOnOrigin={isSenderContractOnOrigin}
1001
+ isSenderContractOnDestination={isSenderContractOnDestination}
1002
+ />
1003
+
1004
+ {/* Error Display */}
1005
+ <ErrorDisplay
1006
+ errorPrettified={quoteErrorPrettified}
1007
+ error={quoteError}
1008
+ severity="warning"
1009
+ />
1010
+
1011
+ {prepareSendQuote?.noSufficientBalance ? (
1012
+ <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
1013
+ <div className="flex items-center space-x-2">
1014
+ <svg
1015
+ className="w-4 h-4 text-amber-500 flex-shrink-0"
1016
+ fill="none"
1017
+ stroke="currentColor"
1018
+ viewBox="0 0 24 24"
1019
+ aria-hidden="true"
1020
+ >
1021
+ <path
1022
+ strokeLinecap="round"
1023
+ strokeLinejoin="round"
1024
+ strokeWidth={2}
1025
+ 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"
1026
+ />
1027
+ </svg>
1028
+ <p className="text-sm text-amber-600 dark:text-amber-400">
1029
+ Insufficient balance to complete this transaction
1030
+ </p>
1031
+ </div>
1032
+ </div>
1033
+ ) : null}
1034
+
1035
+ {/* Fee Options */}
1036
+ <FeeOptions
1037
+ feeOptions={feeOptions || []}
1038
+ selectedFeeToken={selectedFeeToken}
1039
+ setSelectedFeeToken={(token) => setSelectedFeeToken(token as any)}
1040
+ chainId={originToken?.chainId}
1041
+ />
1042
+
1043
+ {/* Pay Button */}
1044
+ <form onSubmit={handleSubmit}>
1045
+ <button
1046
+ type="submit"
1047
+ disabled={
1048
+ !tokenAmountForBackend ||
1049
+ parseFloat(tokenAmountForBackend) <= 0 ||
1050
+ isSubmitting ||
1051
+ isLoadingQuote ||
1052
+ !isValidRecipient ||
1053
+ !destinationTokenAddress ||
1054
+ !isValidCustomToken ||
1055
+ prepareSendQuote?.noSufficientBalance ||
1056
+ !prepareSendQuote
1057
+ }
1058
+ 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"
1059
+ >
1060
+ {isSubmitting ? (
1061
+ <div className="flex items-center justify-center">
1062
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
1063
+ <span>{buttonText}</span>
1064
+ </div>
1065
+ ) : prepareSendQuote?.noSufficientBalance ? (
1066
+ "Insufficient Balance"
1067
+ ) : !selectedRecipient ? (
1068
+ "Select recipient address"
1069
+ ) : !selectedDestToken ? (
1070
+ "Select destination token"
1071
+ ) : !tokenAmountForBackend ||
1072
+ parseFloat(tokenAmountForBackend) <= 0 ? (
1073
+ "Enter an amount"
1074
+ ) : !prepareSendQuote && !quoteError ? (
1075
+ "Getting quote..."
1076
+ ) : (
1077
+ buttonText || "Pay"
1078
+ )}
1079
+ </button>
1080
+ </form>
1081
+
1082
+ {/* Quote Details */}
1083
+ {prepareSendQuote && (
1084
+ <div className="space-y-2">
1085
+ <QuoteDetails quote={prepareSendQuote} showContent={true} />
1086
+ </div>
1087
+ )}
1088
+ </div>
1089
+ )
1090
+ }
1091
+
1092
+ export default Pay