0xtrails 0.2.4 → 0.2.6

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 (212) hide show
  1. package/dist/aave.d.ts +8 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/abortController.d.ts +8 -0
  4. package/dist/abortController.d.ts.map +1 -0
  5. package/dist/{ccip-BlV1Mry3.js → ccip-Xjh9d1gb.js} +7 -7
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/constants.d.ts +3 -0
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/error.d.ts +1 -0
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/estimate.d.ts +52 -0
  13. package/dist/estimate.d.ts.map +1 -1
  14. package/dist/fees.d.ts +19 -0
  15. package/dist/fees.d.ts.map +1 -0
  16. package/dist/{index-BNWCIGfQ.js → index-BnhdZ8Ho.js} +76406 -75798
  17. package/dist/index.js +726 -520
  18. package/dist/intents.d.ts +40 -0
  19. package/dist/intents.d.ts.map +1 -1
  20. package/dist/metaTxnMonitor.d.ts +3 -3
  21. package/dist/metaTxnMonitor.d.ts.map +1 -1
  22. package/dist/metaTxns.d.ts +3 -3
  23. package/dist/metaTxns.d.ts.map +1 -1
  24. package/dist/morpho.d.ts +8 -0
  25. package/dist/morpho.d.ts.map +1 -1
  26. package/dist/prepareSend.d.ts +19 -75
  27. package/dist/prepareSend.d.ts.map +1 -1
  28. package/dist/queryParams.d.ts.map +1 -1
  29. package/dist/relayer.d.ts +6 -6
  30. package/dist/relayer.d.ts.map +1 -1
  31. package/dist/sequenceWallet.d.ts +2 -2
  32. package/dist/sequenceWallet.d.ts.map +1 -1
  33. package/dist/tokens.d.ts.map +1 -1
  34. package/dist/transactions.d.ts +4 -2
  35. package/dist/transactions.d.ts.map +1 -1
  36. package/dist/wallets.d.ts.map +1 -1
  37. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  38. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
  39. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
  40. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  41. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  42. package/dist/widget/components/ClassicSwap.d.ts +4 -2
  43. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  44. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  45. package/dist/widget/components/ConnectedWallets.d.ts +4 -0
  46. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  47. package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
  48. package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
  49. package/dist/widget/components/Earn.d.ts +2 -2
  50. package/dist/widget/components/Earn.d.ts.map +1 -1
  51. package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
  52. package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
  53. package/dist/widget/components/FeeBreakdown.d.ts +9 -0
  54. package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
  55. package/dist/widget/components/Fund.d.ts +2 -2
  56. package/dist/widget/components/Fund.d.ts.map +1 -1
  57. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  58. package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +13 -7
  59. package/dist/widget/components/FundSwap.d.ts.map +1 -0
  60. package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
  61. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
  62. package/dist/widget/components/Identicon.d.ts.map +1 -1
  63. package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
  64. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  65. package/dist/widget/components/Modal.d.ts.map +1 -1
  66. package/dist/widget/components/Pay.d.ts +2 -2
  67. package/dist/widget/components/Pay.d.ts.map +1 -1
  68. package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
  69. package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
  70. package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +14 -36
  71. package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
  72. package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +19 -10
  73. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
  74. package/dist/widget/components/QuoteDetails.d.ts +1 -0
  75. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  76. package/dist/widget/components/Receipt.d.ts.map +1 -1
  77. package/dist/widget/components/Receive.d.ts.map +1 -1
  78. package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
  79. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
  80. package/dist/widget/components/Recipients.d.ts.map +1 -1
  81. package/dist/widget/components/RequiredPropsError.d.ts +8 -0
  82. package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
  83. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  84. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  85. package/dist/widget/components/Swap.d.ts +3 -2
  86. package/dist/widget/components/Swap.d.ts.map +1 -1
  87. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  88. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  89. package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
  90. package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
  91. package/dist/widget/components/TokenImage.d.ts +1 -0
  92. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  93. package/dist/widget/components/TokenList.d.ts.map +1 -1
  94. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  95. package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
  96. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
  97. package/dist/widget/components/Tooltip.d.ts +9 -0
  98. package/dist/widget/components/Tooltip.d.ts.map +1 -0
  99. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  100. package/dist/widget/components/WaasFeeOptions.d.ts +9 -0
  101. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
  102. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  103. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  104. package/dist/widget/components/WalletList.d.ts.map +1 -1
  105. package/dist/widget/css/compiled.css +2 -0
  106. package/dist/widget/css/index.css +554 -0
  107. package/dist/widget/hooks/useBack.d.ts +1 -0
  108. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  109. package/dist/widget/hooks/useCheckout.d.ts +1 -1
  110. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  111. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  112. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  113. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
  114. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  115. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
  116. package/dist/widget/hooks/useQuote.d.ts +83 -0
  117. package/dist/widget/hooks/useQuote.d.ts.map +1 -0
  118. package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
  119. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
  120. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
  121. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  122. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  123. package/dist/widget/index.js +2 -2
  124. package/dist/widget/widget.d.ts +9 -4
  125. package/dist/widget/widget.d.ts.map +1 -1
  126. package/package.json +18 -12
  127. package/src/aave.ts +32 -0
  128. package/src/abortController.ts +35 -0
  129. package/src/config.ts +12 -4
  130. package/src/constants.ts +5 -0
  131. package/src/error.ts +19 -1
  132. package/src/estimate.ts +416 -5
  133. package/src/fees.ts +199 -0
  134. package/src/intents.ts +161 -11
  135. package/src/metaTxnMonitor.ts +3 -3
  136. package/src/metaTxns.ts +3 -5
  137. package/src/morpho.ts +32 -0
  138. package/src/prepareSend.ts +714 -550
  139. package/src/queryParams.ts +2 -1
  140. package/src/relayer.ts +11 -11
  141. package/src/sequenceWallet.ts +2 -2
  142. package/src/tokens.ts +7 -1
  143. package/src/trails.ts +3 -3
  144. package/src/transactions.ts +62 -18
  145. package/src/wallets.ts +8 -0
  146. package/src/widget/compiled.css +2 -2
  147. package/src/widget/components/AccountActionsDropdown.tsx +3 -13
  148. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
  149. package/src/widget/components/AccountSettings.tsx +48 -54
  150. package/src/widget/components/ChainFilterDropdown.tsx +24 -3
  151. package/src/widget/components/ClassicSwap.tsx +131 -213
  152. package/src/widget/components/ConnectWallet.tsx +8 -38
  153. package/src/widget/components/ConnectedWallets.tsx +132 -77
  154. package/src/widget/components/DynamicInputStyles.tsx +76 -0
  155. package/src/widget/components/Earn.tsx +82 -593
  156. package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
  157. package/src/widget/components/FeeBreakdown.tsx +155 -0
  158. package/src/widget/components/Fund.tsx +41 -108
  159. package/src/widget/components/FundMethods.tsx +82 -159
  160. package/src/widget/components/FundSwap.tsx +52 -0
  161. package/src/widget/components/FundingMethodSelectorButton.tsx +70 -0
  162. package/src/widget/components/Identicon.tsx +164 -95
  163. package/src/widget/components/MeshConnectExchanges.tsx +2 -15
  164. package/src/widget/components/Modal.tsx +0 -8
  165. package/src/widget/components/Pay.tsx +214 -237
  166. package/src/widget/components/PercentageMaxButtons.tsx +77 -0
  167. package/src/widget/components/PoolDeposit.tsx +569 -0
  168. package/src/widget/components/PoolWithdraw.tsx +884 -0
  169. package/src/widget/components/PriceImpactWarning.tsx +1 -1
  170. package/src/widget/components/QuoteDetails.tsx +43 -12
  171. package/src/widget/components/Receipt.tsx +16 -2
  172. package/src/widget/components/Receive.tsx +0 -2
  173. package/src/widget/components/RecipientSelectorButton.tsx +44 -0
  174. package/src/widget/components/Recipients.tsx +63 -157
  175. package/src/widget/components/RequiredPropsError.tsx +33 -0
  176. package/src/widget/components/ScreenHeader.tsx +62 -34
  177. package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
  178. package/src/widget/components/Swap.tsx +4 -45
  179. package/src/widget/components/SwapSettings.tsx +2 -14
  180. package/src/widget/components/ThemeProvider.tsx +2 -1
  181. package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
  182. package/src/widget/components/TokenImage.tsx +22 -5
  183. package/src/widget/components/TokenList.tsx +0 -1
  184. package/src/widget/components/TokenSelector.tsx +63 -53
  185. package/src/widget/components/TokenSelectorButton.tsx +98 -0
  186. package/src/widget/components/Tooltip.tsx +51 -0
  187. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  188. package/src/widget/components/UserPreferences.tsx +6 -24
  189. package/src/widget/components/WaasFeeOptions.tsx +450 -0
  190. package/src/widget/components/WalletConfirmation.tsx +76 -14
  191. package/src/widget/components/WalletConnect.tsx +93 -29
  192. package/src/widget/components/WalletList.tsx +4 -2
  193. package/src/widget/hooks/useBack.tsx +2 -0
  194. package/src/widget/hooks/useCheckout.ts +36 -20
  195. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  196. package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
  197. package/src/widget/hooks/usePayMessage.tsx +86 -11
  198. package/src/widget/hooks/useQuote.ts +413 -0
  199. package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
  200. package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
  201. package/src/widget/hooks/useSendForm.ts +32 -6
  202. package/src/widget/index.css +27 -0
  203. package/src/widget/widget.tsx +326 -283
  204. package/dist/widget/components/FundSendForm.d.ts.map +0 -1
  205. package/dist/widget/components/PaySendForm.d.ts.map +0 -1
  206. package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
  207. package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
  208. package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
  209. package/src/widget/components/FundSendForm.tsx +0 -903
  210. package/src/widget/components/PaySendForm.tsx +0 -869
  211. package/src/widget/components/SimpleSwap.tsx +0 -983
  212. package/src/widget/hooks/useSwapSettings.tsx +0 -100
@@ -0,0 +1,77 @@
1
+ import type React from "react"
2
+ import {
3
+ calculateMaxNativeAmount,
4
+ getDefaultGasCostEstimate,
5
+ } from "../../estimate.js"
6
+
7
+ interface PercentageMaxButtonsProps {
8
+ userBalance: string | undefined
9
+ isNativeToken: boolean
10
+ gasCostFormatted?: string
11
+ chainId?: number
12
+ onAmountSelect: (amount: string) => void
13
+ className?: string
14
+ }
15
+
16
+ export const PercentageMaxButtons: React.FC<PercentageMaxButtonsProps> = ({
17
+ userBalance,
18
+ isNativeToken,
19
+ gasCostFormatted,
20
+ chainId,
21
+ onAmountSelect,
22
+ className = "",
23
+ }) => {
24
+ // Don't render if no balance
25
+ if (!userBalance) return null
26
+
27
+ const handlePercentageClick = (percentage: number) => {
28
+ const balance = parseFloat(userBalance)
29
+ if (Number.isNaN(balance)) return
30
+
31
+ const amount = (balance * percentage) / 100
32
+ onAmountSelect(amount.toFixed(6))
33
+ }
34
+
35
+ const handleMaxClick = async () => {
36
+ if (isNativeToken) {
37
+ // For native tokens, subtract gas cost
38
+ // Priority: 1) Use actual gas cost from quote, 2) Fetch real gas price, 3) Fallback to 1% of balance
39
+ const effectiveGasCost =
40
+ gasCostFormatted ||
41
+ (await getDefaultGasCostEstimate(userBalance, chainId))
42
+ const maxAmount = calculateMaxNativeAmount(userBalance, effectiveGasCost)
43
+ onAmountSelect(maxAmount)
44
+ } else {
45
+ // For ERC20 tokens, use full balance
46
+ onAmountSelect(userBalance)
47
+ }
48
+ }
49
+
50
+ return (
51
+ <div
52
+ className={`flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200 ${className}`}
53
+ >
54
+ <button
55
+ type="button"
56
+ onClick={() => handlePercentageClick(20)}
57
+ className="py-0.5 px-1.5 text-xs font-medium rounded-full transition-colors cursor-pointer trails-bg-percentage-button trails-text-percentage-button trails-hover-percentage-button"
58
+ >
59
+ 20%
60
+ </button>
61
+ <button
62
+ type="button"
63
+ onClick={() => handlePercentageClick(50)}
64
+ className="py-0.5 px-1.5 text-xs font-medium rounded-full transition-colors cursor-pointer trails-bg-percentage-button trails-text-percentage-button trails-hover-percentage-button"
65
+ >
66
+ 50%
67
+ </button>
68
+ <button
69
+ type="button"
70
+ onClick={handleMaxClick}
71
+ className="py-0.5 px-1.5 text-xs font-medium rounded-full transition-colors cursor-pointer trails-bg-percentage-button trails-text-percentage-button trails-hover-percentage-button"
72
+ >
73
+ Max
74
+ </button>
75
+ </div>
76
+ )
77
+ }
@@ -0,0 +1,569 @@
1
+ import {
2
+ TrendingUp,
3
+ ArrowDown,
4
+ ChevronRight,
5
+ Search,
6
+ Loader2,
7
+ } from "lucide-react"
8
+ import { useEffect, useState, useRef } from "react"
9
+ import type React from "react"
10
+ import type { Account, WalletClient } from "viem"
11
+ import { zeroAddress } from "viem"
12
+ import type { TransactionState } from "../../transactions.js"
13
+ import type { OnCompleteProps } from "../hooks/useSendForm.js"
14
+ import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
15
+ import { useSendForm } from "../hooks/useSendForm.js"
16
+ import { TradeType } from "../../prepareSend.js"
17
+ import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
18
+ import { useEarnPool } from "../hooks/useEarnPool.js"
19
+ import { useTokenList } from "../hooks/useTokenList.js"
20
+ import { useMode } from "../hooks/useMode.js"
21
+ import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
22
+ import { TokenImage } from "./TokenImage.js"
23
+ import { TokenSelector } from "./TokenSelector.js"
24
+ import { EarnPools } from "./EarnPools.js"
25
+ import { ChainList } from "./ChainList.js"
26
+ import { QuoteDetails } from "./QuoteDetails.js"
27
+ import { PercentageMaxButtons } from "./PercentageMaxButtons.js"
28
+ import { TokenSelectorButton } from "./TokenSelectorButton.js"
29
+ import { FundingMethodSelectorButton } from "./FundingMethodSelectorButton.js"
30
+ import { formatTvl } from "../../prices.js"
31
+ import { useAmountUsd } from "../hooks/useAmountUsd.js"
32
+ import { getExplorerUrlForAddress } from "../../explorer.js"
33
+ import aaveLogo from "../assets/aave.svg"
34
+ import morphoLogo from "../assets/morpho.svg"
35
+ import type { PrepareSendQuote } from "../../prepareSend.js"
36
+ import type { SupportedToken } from "../../tokens.js"
37
+ import { logger } from "../../logger.js"
38
+ import { useDynamicInputStyles } from "./DynamicInputStyles.js"
39
+
40
+ interface PoolDepositProps {
41
+ account?: Account
42
+ walletClient?: WalletClient
43
+ onTransactionStateChange: (transactionStates: TransactionState[]) => void
44
+ onError: (error: Error | string | null) => void
45
+ onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
46
+ onConfirm: () => void
47
+ onComplete: (result: OnCompleteProps) => void
48
+ onSend: (amount: string, recipient: string) => void
49
+ paymasterUrls?: Array<{ chainId: number; url: string }>
50
+ gasless?: boolean
51
+ setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
52
+ quoteProvider?: string
53
+ fundMethod?: string
54
+ onNavigateToMeshConnect?: (
55
+ props: {
56
+ toTokenSymbol: string
57
+ toTokenAmount: string
58
+ toChainId: number
59
+ toRecipientAddress: string
60
+ },
61
+ quote?: PrepareSendQuote | null,
62
+ ) => void
63
+ checkoutOnHandlers?: CheckoutOnHandlers
64
+ recentTokens?: SupportedToken[]
65
+ onRecentTokenSelect?: (token: SupportedToken) => void
66
+ onTrackToken?: (token: any) => void
67
+ onPoolSelectorStateChange?: (isShowing: boolean) => void
68
+ }
69
+
70
+ export const PoolDeposit: React.FC<PoolDepositProps> = ({
71
+ account,
72
+ walletClient,
73
+ onTransactionStateChange,
74
+ onError,
75
+ onWaitingForWalletConfirm,
76
+ onConfirm,
77
+ onComplete,
78
+ onSend,
79
+ paymasterUrls,
80
+ gasless,
81
+ setWalletConfirmRetryHandler,
82
+ quoteProvider,
83
+ fundMethod,
84
+ onNavigateToMeshConnect,
85
+ checkoutOnHandlers,
86
+ recentTokens,
87
+ onRecentTokenSelect,
88
+ onTrackToken,
89
+ onPoolSelectorStateChange,
90
+ }) => {
91
+ const { mode } = useMode()
92
+ const { isBalanceVisible } = useBalanceVisible()
93
+ const { selectedToken: originToken, setSelectedToken: setOriginToken } =
94
+ useOriginSelectedToken()
95
+ const { selectedPool, setSelectedPool } = useEarnPool()
96
+
97
+ const [showEarnPools, setShowEarnPools] = useState(false)
98
+ const [showOriginTokenSelector, setShowOriginTokenSelector] = useState(false)
99
+ const [showOriginChainList, setShowOriginChainList] = useState(false)
100
+ const [amount, setAmount] = useState("")
101
+ const inputRef = useRef<HTMLInputElement>(null)
102
+
103
+ // Get sorted tokens to auto-select the highest USD value token
104
+ const { filteredTokensFormatted, isLoadingTokens } = useTokenList({
105
+ onContinue: () => {}, // Not used for auto-selection
106
+ onError: () => {}, // Not used for auto-selection
107
+ fundMethod: undefined,
108
+ allSupportedTokens: false,
109
+ })
110
+
111
+ // Use useSendForm for quote functionality when both token and pool are selected
112
+ const {
113
+ balanceFormatted,
114
+ isLoadingQuote,
115
+ prepareSendQuote,
116
+ setAmount: setSendFormAmount,
117
+ handleSubmit,
118
+ isSubmitting,
119
+ buttonText,
120
+ isValidRecipient,
121
+ } = useSendForm({
122
+ account,
123
+ toAmount: undefined,
124
+ toRecipient: selectedPool?.depositAddress,
125
+ toChainId: selectedPool?.chainId,
126
+ toToken: selectedPool?.token.address,
127
+ toCalldata: undefined,
128
+ walletClient,
129
+ onTransactionStateChange,
130
+ onError,
131
+ onWaitingForWalletConfirm,
132
+ paymasterUrls,
133
+ gasless,
134
+ onConfirm,
135
+ onComplete,
136
+ onSend,
137
+ selectedToken: originToken as any,
138
+ setWalletConfirmRetryHandler,
139
+ tradeType: TradeType.EXACT_INPUT,
140
+ quoteProvider,
141
+ fundMethod,
142
+ mode,
143
+ onNavigateToMeshConnect,
144
+ checkoutOnHandlers,
145
+ })
146
+
147
+ // Calculate USD value using the underlying token (for pool tokens like aBasUSDC)
148
+ const { amountUsdFormatted: underlyingTokenUsdDisplay } = useAmountUsd({
149
+ amount: amount,
150
+ token: originToken?.contractAddress, // Use the origin token's address for USD calculation
151
+ chainId: originToken?.chainId,
152
+ })
153
+
154
+ // Auto-select the highest USD value token as origin token
155
+ useEffect(() => {
156
+ if (
157
+ !originToken &&
158
+ !isLoadingTokens &&
159
+ filteredTokensFormatted?.length > 0
160
+ ) {
161
+ const highestValueToken = filteredTokensFormatted[0] // First token is highest USD value
162
+ if (highestValueToken && Number(highestValueToken.balanceUsd) > 0) {
163
+ const decimals =
164
+ highestValueToken.contractInfo?.decimals ??
165
+ (highestValueToken as any)?.decimals
166
+ setOriginToken({
167
+ ...highestValueToken,
168
+ contractInfo: {
169
+ decimals,
170
+ contractAddress: highestValueToken.contractAddress,
171
+ symbol: highestValueToken.symbol,
172
+ name: highestValueToken.name,
173
+ },
174
+ } as any)
175
+ }
176
+ }
177
+ }, [originToken, isLoadingTokens, filteredTokensFormatted, setOriginToken])
178
+
179
+ // Auto-focus input field on component mount
180
+ useEffect(() => {
181
+ if (inputRef.current) {
182
+ inputRef.current.focus()
183
+ }
184
+ }, [])
185
+
186
+ // Notify parent component about pool selector visibility
187
+ useEffect(() => {
188
+ if (onPoolSelectorStateChange) {
189
+ // Only hide tabs when showing EarnPools selector
190
+ onPoolSelectorStateChange(showEarnPools)
191
+ }
192
+ }, [showEarnPools, onPoolSelectorStateChange])
193
+
194
+ const handleAmountChange = (value: string) => {
195
+ // Validate decimal places (max 8 decimals)
196
+ const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
197
+ if (!decimalMatch && value !== "") {
198
+ return // Don't update if more than 8 decimals
199
+ }
200
+ setAmount(value)
201
+ setSendFormAmount(value) // Sync with useSendForm for quote functionality
202
+ }
203
+
204
+ const handlePoolSelect = (pool: any) => {
205
+ logger.console.log("Selected pool:", pool)
206
+ setSelectedPool(pool)
207
+ setShowEarnPools(false)
208
+ }
209
+
210
+ const handleOriginTokenSelect = (token: any) => {
211
+ const formattedToken = {
212
+ ...token,
213
+ decimals: token.contractInfo?.decimals || token.decimals,
214
+ contractInfo: {
215
+ decimals: token.contractInfo?.decimals || token.decimals,
216
+ contractAddress: token.contractAddress,
217
+ symbol: token.symbol,
218
+ name: token.name,
219
+ },
220
+ } as any
221
+ setOriginToken(formattedToken)
222
+ logger.console.log("[trails-sdk] selected origin token", token)
223
+ setShowOriginTokenSelector(false)
224
+ // Track the token selection
225
+ onTrackToken?.(token)
226
+ }
227
+
228
+ // Handle amount selection from percentage buttons
229
+ const handleAmountSelect = (selectedAmount: string) => {
230
+ setAmount(selectedAmount)
231
+ setSendFormAmount(selectedAmount)
232
+ }
233
+
234
+ // Dynamic font size based on input length
235
+ const inputStyles = useDynamicInputStyles({
236
+ inputValue: amount,
237
+ variant: "default",
238
+ })
239
+
240
+ if (showOriginChainList) {
241
+ return (
242
+ <ChainList
243
+ onBack={() => {
244
+ setShowOriginChainList(false)
245
+ setShowOriginTokenSelector(true)
246
+ }}
247
+ />
248
+ )
249
+ }
250
+
251
+ if (showEarnPools) {
252
+ return (
253
+ <EarnPools
254
+ onBack={() => setShowEarnPools(false)}
255
+ onPoolSelect={handlePoolSelect}
256
+ />
257
+ )
258
+ }
259
+
260
+ if (showOriginTokenSelector) {
261
+ return (
262
+ <div className="space-y-2">
263
+ <TokenSelector
264
+ onTokenSelect={handleOriginTokenSelect}
265
+ onError={onError}
266
+ fundMethod={fundMethod}
267
+ showContinueButton={false}
268
+ compactMode={false}
269
+ recentTokens={recentTokens}
270
+ onRecentTokenSelect={onRecentTokenSelect}
271
+ allSupportedTokens={false}
272
+ chainListScreen={true}
273
+ onNavigateToChainList={() => {
274
+ setShowOriginTokenSelector(false)
275
+ setShowOriginChainList(true)
276
+ }}
277
+ />
278
+ </div>
279
+ )
280
+ }
281
+
282
+ return (
283
+ <div className="space-y-2">
284
+ {/* Input Section - Amount + Token Selection */}
285
+ <div className="pt-4 pb-4 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 min-h-[120px] flex flex-col">
286
+ {/* Deposit Label */}
287
+ <div className="mb-4 flex justify-between items-center">
288
+ <div className="text-sm font-medium trails-text-secondary text-left m-0">
289
+ {mode === "fund" ? "Payment method" : "From"}
290
+ </div>
291
+ <FundingMethodSelectorButton />
292
+ </div>
293
+
294
+ <div className="flex items-center space-x-2 flex-1">
295
+ {/* Amount Input */}
296
+ <div className="flex-1">
297
+ <div className="flex items-center space-x-2">
298
+ <input
299
+ ref={inputRef}
300
+ id="amount"
301
+ type="text"
302
+ value={amount}
303
+ onChange={(e) => handleAmountChange(e.target.value)}
304
+ placeholder={"0"}
305
+ className={`w-full bg-transparent font-bold trails-text-primary placeholder:trails-text-muted border-none outline-none ${
306
+ isLoadingQuote ? "animate-pulse" : ""
307
+ }`}
308
+ style={inputStyles}
309
+ inputMode="decimal"
310
+ />
311
+ {isLoadingQuote && (
312
+ <div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
313
+ )}
314
+ </div>
315
+ </div>
316
+
317
+ {/* Token Selection Button */}
318
+ <div className="relative">
319
+ <TokenSelectorButton
320
+ token={originToken}
321
+ chainId={originToken?.chainId}
322
+ onSelect={() => setShowOriginTokenSelector(true)}
323
+ />
324
+ </div>
325
+ </div>
326
+
327
+ {/* Bottom Info Row */}
328
+ <div className="mt-4 flex justify-between items-center">
329
+ {/* USD Amount */}
330
+ {originToken?.symbol && (
331
+ <div className="text-xs text-gray-500 dark:text-gray-400">
332
+ ≈ {underlyingTokenUsdDisplay || "$0.00"}
333
+ </div>
334
+ )}
335
+
336
+ {/* Origin Token Balance and Percentage Buttons */}
337
+ {originToken && balanceFormatted && (
338
+ <div className="flex items-center space-x-2">
339
+ <button
340
+ type="button"
341
+ className="text-xs text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 transition-colors bg-transparent border-none p-0"
342
+ onClick={() => handleAmountSelect(balanceFormatted || "0")}
343
+ onKeyDown={(e) => {
344
+ if (e.key === "Enter" || e.key === " ") {
345
+ e.preventDefault()
346
+ handleAmountSelect(balanceFormatted || "0")
347
+ }
348
+ }}
349
+ title="Click to use full balance"
350
+ >
351
+ Balance:{" "}
352
+ {isBalanceVisible ? balanceFormatted || "0.00" : "••••••"}
353
+ </button>
354
+
355
+ {/* Percentage Buttons */}
356
+ <PercentageMaxButtons
357
+ userBalance={balanceFormatted}
358
+ isNativeToken={originToken.contractAddress === zeroAddress}
359
+ gasCostFormatted={prepareSendQuote?.gasCostFormatted}
360
+ chainId={originToken.chainId}
361
+ onAmountSelect={handleAmountSelect}
362
+ className="opacity-100"
363
+ />
364
+ </div>
365
+ )}
366
+ </div>
367
+ </div>
368
+
369
+ {/* Flip Button - Absolutely Positioned */}
370
+ <div className="relative">
371
+ <button
372
+ type="button"
373
+ className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 p-1.5 trails-border-radius-button trails-bg-tertiary hover:trails-hover-bg transition-colors cursor-pointer border-2 border-white dark:border-gray-800"
374
+ >
375
+ <ArrowDown
376
+ className="w-5 h-5 text-gray-900 dark:text-white"
377
+ strokeWidth={2.5}
378
+ />
379
+ </button>
380
+ </div>
381
+
382
+ {/* Destination Vault Selection */}
383
+ <div className="mb-4 trails-bg-secondary trails-border-radius-container transition-all duration-200 border border-transparent hover:!bg-white dark:hover:!bg-white hover:border-gray-400 dark:hover:border-gray-500">
384
+ {selectedPool ? (
385
+ <div className="p-3 trails-border-radius-container trails-bg-secondary transition-all overflow-hidden">
386
+ {/* Vault Label */}
387
+ <div className="flex justify-between items-center mb-2">
388
+ <div className="text-sm font-semibold trails-text-secondary text-left">
389
+ Vault
390
+ </div>
391
+ </div>
392
+
393
+ <div className="px-1">
394
+ <div className="flex items-center justify-between">
395
+ <div className="flex items-center space-x-3">
396
+ <div style={{ width: "32px", height: "32px" }}>
397
+ <a
398
+ href={getExplorerUrlForAddress({
399
+ address: selectedPool.token.address,
400
+ chainId: selectedPool.chainId,
401
+ })}
402
+ target="_blank"
403
+ rel="noopener noreferrer"
404
+ className="cursor-pointer"
405
+ >
406
+ <TokenImage
407
+ symbol={selectedPool.token.symbol}
408
+ imageUrl={selectedPool.token.logoUrl}
409
+ chainId={selectedPool.chainId}
410
+ contractAddress={selectedPool.token.address}
411
+ size={32}
412
+ />
413
+ </a>
414
+ </div>
415
+ <div>
416
+ <h3 className="font-medium text-gray-900 dark:text-white text-sm">
417
+ {selectedPool.poolUrl ? (
418
+ <a
419
+ href={selectedPool.poolUrl}
420
+ target="_blank"
421
+ rel="noopener noreferrer"
422
+ className="hover:underline cursor-pointer"
423
+ >
424
+ {selectedPool.name}
425
+ </a>
426
+ ) : (
427
+ selectedPool.name
428
+ )}
429
+ </h3>
430
+ <div className="flex items-center space-x-2">
431
+ <span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
432
+ {selectedPool.protocol === "Aave" && (
433
+ <img
434
+ src={aaveLogo}
435
+ alt="Aave"
436
+ className="w-3 h-3 mr-1"
437
+ />
438
+ )}
439
+ {selectedPool.protocol === "Morpho" && (
440
+ <img
441
+ src={morphoLogo}
442
+ alt="Morpho"
443
+ className="w-3 h-3 mr-1"
444
+ />
445
+ )}
446
+ {selectedPool.protocolUrl ? (
447
+ <a
448
+ href={selectedPool.protocolUrl}
449
+ target="_blank"
450
+ rel="noopener noreferrer"
451
+ className="hover:underline cursor-pointer"
452
+ >
453
+ {selectedPool.protocol}
454
+ </a>
455
+ ) : (
456
+ selectedPool.protocol
457
+ )}
458
+ </span>
459
+ </div>
460
+ </div>
461
+ </div>
462
+ <button
463
+ type="button"
464
+ title="Select Vault"
465
+ onClick={() => setShowEarnPools(true)}
466
+ className="text-right flex items-center space-x-3 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded p-2 transition-colors"
467
+ >
468
+ <div>
469
+ <div className="flex items-center justify-end space-x-1 text-green-600 dark:text-green-400 mb-1 whitespace-nowrap">
470
+ <TrendingUp className="w-3 h-3" />
471
+ <span className="font-semibold text-sm">
472
+ {selectedPool.apy.toFixed(1)}% APY
473
+ </span>
474
+ </div>
475
+ <p className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
476
+ TVL: {formatTvl(selectedPool.tvl)}
477
+ </p>
478
+ </div>
479
+ <ChevronRight className="w-4 h-4 text-gray-400" />
480
+ </button>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ ) : (
485
+ <button
486
+ type="button"
487
+ onClick={() => setShowEarnPools(true)}
488
+ className="w-full py-6 px-4 trails-list-item trails-border-radius-container transition-all duration-200 cursor-pointer"
489
+ >
490
+ <div className="flex items-center justify-between">
491
+ <div className="flex items-center space-x-3 flex-1">
492
+ <div className="w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
493
+ <Search className="w-4 h-4 text-gray-400" />
494
+ </div>
495
+ <div className="text-left flex-1">
496
+ <div className="font-semibold text-gray-900 dark:text-white text-sm">
497
+ Select vault to earn yield with
498
+ </div>
499
+ </div>
500
+ </div>
501
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
502
+ </div>
503
+ </button>
504
+ )}
505
+ </div>
506
+
507
+ {prepareSendQuote?.noSufficientBalance ? (
508
+ <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
509
+ <div className="flex items-center space-x-2">
510
+ <svg
511
+ className="w-4 h-4 text-amber-500 flex-shrink-0"
512
+ fill="none"
513
+ stroke="currentColor"
514
+ viewBox="0 0 24 24"
515
+ aria-hidden="true"
516
+ >
517
+ <path
518
+ strokeLinecap="round"
519
+ strokeLinejoin="round"
520
+ strokeWidth={2}
521
+ 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"
522
+ />
523
+ </svg>
524
+ <p className="text-sm text-amber-600 dark:text-amber-400">
525
+ Insufficient balance to complete this transaction
526
+ </p>
527
+ </div>
528
+ </div>
529
+ ) : null}
530
+
531
+ {/* Quote Details */}
532
+ {prepareSendQuote && (
533
+ <div className="mb-4">
534
+ <QuoteDetails quote={prepareSendQuote} showContent={true} />
535
+ </div>
536
+ )}
537
+
538
+ <form onSubmit={handleSubmit}>
539
+ <button
540
+ type="submit"
541
+ disabled={
542
+ !amount ||
543
+ !isValidRecipient ||
544
+ isSubmitting ||
545
+ !originToken ||
546
+ !selectedPool ||
547
+ isLoadingQuote ||
548
+ !prepareSendQuote ||
549
+ prepareSendQuote?.noSufficientBalance
550
+ }
551
+ 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`}
552
+ >
553
+ {isSubmitting ? (
554
+ <div className="flex items-center justify-center">
555
+ <Loader2
556
+ className={`w-5 h-5 animate-spin mr-2 ${"text-gray-400"}`}
557
+ />
558
+ <span>{buttonText}</span>
559
+ </div>
560
+ ) : prepareSendQuote?.noSufficientBalance ? (
561
+ "Insufficient Balance"
562
+ ) : (
563
+ buttonText || "Deposit"
564
+ )}
565
+ </button>
566
+ </form>
567
+ </div>
568
+ )
569
+ }