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,884 @@
1
+ import { TrendingUp, ChevronRight, Search, Loader2 } from "lucide-react"
2
+ import { useEffect, useState, useRef, useMemo } from "react"
3
+ import type React from "react"
4
+ import type { Account, WalletClient } from "viem"
5
+ import { createPublicClient, http, parseUnits, formatUnits } from "viem"
6
+ import type { TransactionState } from "../../transactions.js"
7
+ import type { OnCompleteProps } from "../hooks/useSendForm.js"
8
+ import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
9
+ import { useSendForm } from "../hooks/useSendForm.js"
10
+ import { TradeType } from "../../prepareSend.js"
11
+ import { useEarnPool } from "../hooks/useEarnPool.js"
12
+ import { useMode } from "../hooks/useMode.js"
13
+ import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
14
+ import { TokenImage } from "./TokenImage.js"
15
+ import { EarnPools } from "./EarnPools.js"
16
+ import { QuoteDetails } from "./QuoteDetails.js"
17
+ import { PercentageMaxButtons } from "./PercentageMaxButtons.js"
18
+ import { formatTvl } from "../../prices.js"
19
+ import { getExplorerUrlForAddress } from "../../explorer.js"
20
+ import { getChainInfo } from "../../chains.js"
21
+ import { useAmountUsd } from "../hooks/useAmountUsd.js"
22
+ import aaveLogo from "../assets/aave.svg"
23
+ import morphoLogo from "../assets/morpho.svg"
24
+ import type { PrepareSendQuote } from "../../prepareSend.js"
25
+ import type { SupportedToken } from "../../tokens.js"
26
+ import { logger } from "../../logger.js"
27
+ import { generateAaveWithdrawCalldata } from "../../aave.js"
28
+ import { generateMorphoWithdrawCalldata } from "../../morpho.js"
29
+ import { formatAmount } from "../../tokenBalances.js"
30
+ import { useDynamicInputStyles } from "./DynamicInputStyles.js"
31
+ import { TokenDisplayNonSelectable } from "./TokenDisplayNonSelectable.js"
32
+
33
+ interface PoolWithdrawProps {
34
+ account?: Account
35
+ walletClient?: WalletClient
36
+ onTransactionStateChange: (transactionStates: TransactionState[]) => void
37
+ onError: (error: Error | string | null) => void
38
+ onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
39
+ onConfirm: () => void
40
+ onComplete: (result: OnCompleteProps) => void
41
+ onSend: (amount: string, recipient: string) => void
42
+ paymasterUrls?: Array<{ chainId: number; url: string }>
43
+ gasless?: boolean
44
+ setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
45
+ quoteProvider?: string
46
+ fundMethod?: string
47
+ onNavigateToMeshConnect?: (
48
+ props: {
49
+ toTokenSymbol: string
50
+ toTokenAmount: string
51
+ toChainId: number
52
+ toRecipientAddress: string
53
+ },
54
+ quote?: PrepareSendQuote | null,
55
+ ) => void
56
+ checkoutOnHandlers?: CheckoutOnHandlers
57
+ recentTokens?: SupportedToken[]
58
+ onRecentTokenSelect?: (token: SupportedToken) => void
59
+ onTrackToken?: (token: any) => void
60
+ onPoolSelectorStateChange?: (isShowing: boolean) => void
61
+ }
62
+
63
+ export const PoolWithdraw: React.FC<PoolWithdrawProps> = ({
64
+ account,
65
+ walletClient,
66
+ onTransactionStateChange,
67
+ onError,
68
+ onWaitingForWalletConfirm,
69
+ onConfirm,
70
+ onComplete,
71
+ onSend,
72
+ paymasterUrls,
73
+ gasless,
74
+ setWalletConfirmRetryHandler,
75
+ quoteProvider,
76
+ fundMethod,
77
+ onNavigateToMeshConnect,
78
+ checkoutOnHandlers,
79
+ onPoolSelectorStateChange,
80
+ }) => {
81
+ const { mode } = useMode()
82
+ const { isBalanceVisible } = useBalanceVisible()
83
+ const { selectedPool, setSelectedPool } = useEarnPool()
84
+
85
+ const [showEarnPools, setShowEarnPools] = useState(false)
86
+ const [amount, setAmount] = useState("")
87
+ const [aTokenAddress, setATokenAddress] = useState<string | null>(null)
88
+ const [poolBalance, setPoolBalance] = useState<string | null>(null)
89
+ const [isLoadingBalance, setIsLoadingBalance] = useState(false)
90
+ const inputRef = useRef<HTMLInputElement>(null)
91
+
92
+ // Log when pool is selected
93
+ useEffect(() => {
94
+ if (selectedPool) {
95
+ logger.console.log("[pool-withdraw] Pool selected:", {
96
+ name: selectedPool.name,
97
+ protocol: selectedPool.protocol,
98
+ chainId: selectedPool.chainId,
99
+ depositAddress: selectedPool.depositAddress,
100
+ tokenAddress: selectedPool.token.address,
101
+ tokenSymbol: selectedPool.token.symbol,
102
+ tokenDecimals: selectedPool.token.decimals,
103
+ })
104
+ } else {
105
+ logger.console.log("[pool-withdraw] No pool selected")
106
+ }
107
+ }, [selectedPool])
108
+
109
+ // Fetch aToken address for Aave pools
110
+ useEffect(() => {
111
+ const fetchATokenAddress = async () => {
112
+ if (!selectedPool || selectedPool.protocol !== "Aave") {
113
+ logger.console.log(
114
+ "[pool-withdraw] Not an Aave pool, skipping aToken fetch",
115
+ {
116
+ hasPool: !!selectedPool,
117
+ protocol: selectedPool?.protocol,
118
+ },
119
+ )
120
+ setATokenAddress(null)
121
+ return
122
+ }
123
+
124
+ logger.console.log(
125
+ "[pool-withdraw] Fetching aToken address for Aave pool...",
126
+ {
127
+ poolAddress: selectedPool.depositAddress,
128
+ underlyingAsset: selectedPool.token.address,
129
+ },
130
+ )
131
+
132
+ try {
133
+ const chain = getChainInfo(selectedPool.chainId)
134
+ if (!chain) {
135
+ logger.console.error(
136
+ "[pool-withdraw] Chain not found:",
137
+ selectedPool.chainId,
138
+ )
139
+ return
140
+ }
141
+
142
+ const publicClient = createPublicClient({
143
+ chain,
144
+ transport: http(),
145
+ })
146
+
147
+ // Call getReserveAToken on the pool contract to get aToken address
148
+ const aTokenAddr = (await publicClient.readContract({
149
+ address: selectedPool.depositAddress as `0x${string}`,
150
+ abi: [
151
+ {
152
+ name: "getReserveAToken",
153
+ type: "function",
154
+ stateMutability: "view",
155
+ inputs: [{ name: "asset", type: "address" }],
156
+ outputs: [{ type: "address" }],
157
+ },
158
+ ],
159
+ functionName: "getReserveAToken",
160
+ args: [selectedPool.token.address as `0x${string}`],
161
+ })) as string
162
+
163
+ logger.console.log(
164
+ "[pool-withdraw] ✅ Successfully fetched aToken address:",
165
+ {
166
+ aTokenAddress: aTokenAddr,
167
+ underlyingAsset: selectedPool.token.address,
168
+ },
169
+ )
170
+ setATokenAddress(aTokenAddr)
171
+ } catch (error) {
172
+ logger.console.error(
173
+ "[pool-withdraw] ❌ Error fetching aToken address:",
174
+ {
175
+ error,
176
+ poolAddress: selectedPool.depositAddress,
177
+ underlyingAsset: selectedPool.token.address,
178
+ },
179
+ )
180
+ setATokenAddress(null)
181
+ }
182
+ }
183
+
184
+ fetchATokenAddress()
185
+ }, [selectedPool])
186
+
187
+ // Fetch pool balance (aToken balance for Aave, pool contract balance for Morpho, pool token balance for others)
188
+ useEffect(() => {
189
+ const fetchPoolBalance = async () => {
190
+ if (!selectedPool || !account) {
191
+ logger.console.log("[pool-withdraw] Cannot fetch balance:", {
192
+ hasPool: !!selectedPool,
193
+ hasAccount: !!account,
194
+ })
195
+ setPoolBalance(null)
196
+ return
197
+ }
198
+
199
+ // For Aave, wait for aToken address to be fetched
200
+ if (selectedPool.protocol === "Aave" && !aTokenAddress) {
201
+ logger.console.log(
202
+ "[pool-withdraw] Waiting for aToken address before fetching balance",
203
+ )
204
+ setPoolBalance(null)
205
+ return
206
+ }
207
+
208
+ setIsLoadingBalance(true)
209
+
210
+ try {
211
+ const chain = getChainInfo(selectedPool.chainId)
212
+ if (!chain) {
213
+ logger.console.error(
214
+ "[pool-withdraw] Chain not found:",
215
+ selectedPool.chainId,
216
+ )
217
+ setIsLoadingBalance(false)
218
+ return
219
+ }
220
+
221
+ const publicClient = createPublicClient({
222
+ chain,
223
+ transport: http(),
224
+ })
225
+
226
+ // Determine which token address to query
227
+ let tokenToQuery: string
228
+ if (selectedPool.protocol === "Aave" && aTokenAddress) {
229
+ tokenToQuery = aTokenAddress // Use aToken for Aave
230
+ } else if (selectedPool.protocol === "Morpho") {
231
+ tokenToQuery = selectedPool.depositAddress // Use pool contract for Morpho
232
+ } else {
233
+ tokenToQuery = selectedPool.token.address // Use underlying token for others
234
+ }
235
+
236
+ logger.console.log("[pool-withdraw] Fetching balance:", {
237
+ protocol: selectedPool.protocol,
238
+ tokenToQuery,
239
+ isAToken: selectedPool.protocol === "Aave",
240
+ isMorphoPool: selectedPool.protocol === "Morpho",
241
+ userAddress: account.address,
242
+ })
243
+
244
+ // Fetch balance using balanceOf
245
+ const balance = (await publicClient.readContract({
246
+ address: tokenToQuery as `0x${string}`,
247
+ abi: [
248
+ {
249
+ name: "balanceOf",
250
+ type: "function",
251
+ stateMutability: "view",
252
+ inputs: [{ name: "account", type: "address" }],
253
+ outputs: [{ type: "uint256" }],
254
+ },
255
+ ],
256
+ functionName: "balanceOf",
257
+ args: [account.address],
258
+ })) as bigint
259
+
260
+ // Fetch decimals
261
+ const decimals = (await publicClient.readContract({
262
+ address: tokenToQuery as `0x${string}`,
263
+ abi: [
264
+ {
265
+ name: "decimals",
266
+ type: "function",
267
+ stateMutability: "view",
268
+ inputs: [],
269
+ outputs: [{ type: "uint8" }],
270
+ },
271
+ ],
272
+ functionName: "decimals",
273
+ })) as number
274
+
275
+ // Format balance using viem's formatUnits and our formatAmount utility
276
+ const balanceInTokenUnits = formatUnits(balance, decimals)
277
+ const balanceFormatted = formatAmount(balanceInTokenUnits, {
278
+ maxFractionDigits: 6,
279
+ minFractionDigits: 0,
280
+ })
281
+
282
+ logger.console.log("[pool-withdraw] ✅ Successfully fetched balance:", {
283
+ tokenAddress: tokenToQuery,
284
+ balanceRaw: balance.toString(),
285
+ decimals,
286
+ balanceInTokenUnits,
287
+ balanceFormatted,
288
+ })
289
+
290
+ setPoolBalance(balanceFormatted)
291
+ setIsLoadingBalance(false)
292
+ } catch (error) {
293
+ logger.console.error("[pool-withdraw] ❌ Error fetching balance:", {
294
+ error,
295
+ selectedPool: selectedPool.name,
296
+ aTokenAddress,
297
+ })
298
+ setPoolBalance(null)
299
+ setIsLoadingBalance(false)
300
+ }
301
+ }
302
+
303
+ fetchPoolBalance()
304
+ }, [selectedPool, aTokenAddress, account])
305
+
306
+ // Generate withdraw calldata dynamically based on user's entered amount
307
+ const withdrawCalldata = useMemo(() => {
308
+ if (!account?.address) {
309
+ logger.console.log(
310
+ "[pool-withdraw] No account found, skipping calldata generation",
311
+ )
312
+ return undefined
313
+ }
314
+
315
+ const isAave = selectedPool?.protocol === "Aave"
316
+ const isMorpho = selectedPool?.protocol === "Morpho"
317
+
318
+ // Only generate calldata for Aave and Morpho protocols
319
+ if (!selectedPool || (!isAave && !isMorpho) || !amount || amount === "0") {
320
+ logger.console.log("[pool-withdraw] Skipping calldata generation:", {
321
+ hasPool: !!selectedPool,
322
+ protocol: selectedPool?.protocol,
323
+ isAave,
324
+ isMorpho,
325
+ amount,
326
+ })
327
+ return undefined
328
+ }
329
+
330
+ try {
331
+ // Convert amount to wei using the token's decimals
332
+ const amountWei = parseUnits(amount, selectedPool.token.decimals || 18)
333
+
334
+ let calldata: `0x${string}`
335
+
336
+ if (isAave) {
337
+ calldata = generateAaveWithdrawCalldata(
338
+ selectedPool.token.address, // underlying asset (e.g., USDC)
339
+ amountWei, // actual amount to withdraw in wei
340
+ account.address, // recipient (user's wallet)
341
+ )
342
+ } else if (isMorpho) {
343
+ calldata = generateMorphoWithdrawCalldata(
344
+ selectedPool.token.address, // underlying asset (e.g., USDC)
345
+ amountWei, // actual amount to withdraw in wei
346
+ account.address, // recipient (user's wallet)
347
+ )
348
+ } else {
349
+ return undefined
350
+ }
351
+
352
+ logger.console.log("[pool-withdraw] Generated withdraw calldata:", {
353
+ pool: selectedPool.name,
354
+ protocol: selectedPool.protocol,
355
+ asset: selectedPool.token.address,
356
+ amount: amount,
357
+ amountWei: amountWei.toString(),
358
+ recipient: account.address,
359
+ calldata,
360
+ })
361
+
362
+ return calldata
363
+ } catch (error) {
364
+ logger.console.error(
365
+ "[pool-withdraw] ❌ Error generating calldata:",
366
+ error,
367
+ )
368
+ return undefined
369
+ }
370
+ }, [selectedPool, amount, account?.address])
371
+
372
+ // Convert pool to token format for useSendForm
373
+ // For Aave, use the aToken address
374
+ // For Morpho, use the pool contract address
375
+ // For others, use the underlying token address
376
+ const poolToken = useMemo(() => {
377
+ if (!selectedPool) {
378
+ logger.console.log("[pool-withdraw] No pool selected, poolToken is null")
379
+ return null
380
+ }
381
+
382
+ const isAave = selectedPool.protocol === "Aave"
383
+ const isMorpho = selectedPool.protocol === "Morpho"
384
+
385
+ let contractAddressToUse: string
386
+ if (isAave && aTokenAddress) {
387
+ contractAddressToUse = aTokenAddress
388
+ } else if (isMorpho) {
389
+ contractAddressToUse = selectedPool.depositAddress
390
+ } else {
391
+ contractAddressToUse = selectedPool.token.address
392
+ }
393
+
394
+ logger.console.log("[pool-withdraw] Constructing poolToken object:", {
395
+ poolName: selectedPool.name,
396
+ protocol: selectedPool.protocol,
397
+ isAave,
398
+ isMorpho,
399
+ aTokenAddress,
400
+ depositAddress: selectedPool.depositAddress,
401
+ underlyingTokenAddress: selectedPool.token.address,
402
+ willUseAddress: contractAddressToUse,
403
+ })
404
+
405
+ const token = {
406
+ symbol: selectedPool.token.symbol,
407
+ imageUrl: selectedPool.token.logoUrl,
408
+ chainId: selectedPool.chainId,
409
+ contractAddress: contractAddressToUse,
410
+ decimals: selectedPool.token.decimals || 18,
411
+ contractInfo: {
412
+ decimals: selectedPool.token.decimals || 18,
413
+ contractAddress: contractAddressToUse,
414
+ symbol: selectedPool.token.symbol,
415
+ name: selectedPool.token.name || selectedPool.token.symbol,
416
+ },
417
+ } as any
418
+
419
+ logger.console.log("[pool-withdraw] ✅ Constructed poolToken:", token)
420
+
421
+ return token
422
+ }, [selectedPool, aTokenAddress])
423
+
424
+ // Use useSendForm for quote functionality
425
+ // Transaction is always sent TO the pool contract (depositAddress)
426
+ // For Aave, withdraw() calldata specifies the recipient (user's address)
427
+ // Set up source token (aToken for Aave, pool shares for Morpho)
428
+ const finalSelectedToken = poolToken
429
+ ? {
430
+ chainId: selectedPool?.chainId,
431
+ ...poolToken,
432
+ }
433
+ : null
434
+
435
+ // Log what we're passing to useSendForm
436
+ useEffect(() => {
437
+ logger.console.log("[pool-withdraw] useSendForm configuration:", {
438
+ hasAccount: !!account,
439
+ accountAddress: account?.address,
440
+ toRecipient: selectedPool?.depositAddress,
441
+ toChainId: selectedPool?.chainId,
442
+ toToken: finalSelectedToken?.contractAddress, // Use aToken/pool address as destination token
443
+ hasCalldata: !!withdrawCalldata,
444
+ calldataLength: withdrawCalldata?.length,
445
+ selectedToken: finalSelectedToken,
446
+ selectedTokenChainId: finalSelectedToken?.chainId,
447
+ selectedTokenAddress: finalSelectedToken?.contractAddress,
448
+ tradeType: "EXACT_INPUT",
449
+ amount: amount,
450
+ isSameChain: selectedPool?.chainId === finalSelectedToken?.chainId,
451
+ willBeSameToken: true, // toToken is same as selectedToken's contractAddress
452
+ })
453
+ }, [account, selectedPool, withdrawCalldata, finalSelectedToken, amount])
454
+
455
+ const {
456
+ balanceFormatted: _unusedBalanceFormatted, // Not used, we fetch balance separately
457
+ isLoadingQuote,
458
+ prepareSendQuote,
459
+ setAmount: setSendFormAmount,
460
+ handleSubmit,
461
+ isSubmitting,
462
+ buttonText,
463
+ isValidRecipient,
464
+ selectedDestToken: returnedDestToken, // Get the destination token set by useSendForm
465
+ setSelectedDestinationChain, // Function to set destination chain
466
+ supportedChains, // Available chains
467
+ } = useSendForm({
468
+ account,
469
+ toAmount: undefined,
470
+ toRecipient: selectedPool?.depositAddress, // Always the pool contract address
471
+ toChainId: selectedPool?.chainId,
472
+ toToken: finalSelectedToken?.contractAddress, // aToken address (Aave) or pool address (Morpho)
473
+ toCalldata: withdrawCalldata, // Aave/Morpho withdraw calldata
474
+ walletClient,
475
+ onTransactionStateChange,
476
+ onError,
477
+ onWaitingForWalletConfirm,
478
+ paymasterUrls,
479
+ gasless,
480
+ onConfirm,
481
+ onComplete,
482
+ onSend,
483
+ selectedToken: finalSelectedToken, // Source token (what we're withdrawing from)
484
+ setWalletConfirmRetryHandler,
485
+ tradeType: TradeType.EXACT_INPUT, // User specifies exact input amount to withdraw
486
+ quoteProvider,
487
+ fundMethod,
488
+ mode,
489
+ onNavigateToMeshConnect,
490
+ checkoutOnHandlers,
491
+ })
492
+
493
+ // Calculate USD value using the underlying token (for pool tokens like aBasUSDC)
494
+ const { amountUsdFormatted: underlyingTokenUsdDisplay } = useAmountUsd({
495
+ amount: amount,
496
+ token: selectedPool?.token.address, // Use underlying token address (e.g., USDC for aBasUSDC)
497
+ chainId: selectedPool?.chainId,
498
+ })
499
+
500
+ // Log pool balance when it changes
501
+ useEffect(() => {
502
+ logger.console.log("[pool-withdraw] Pool balance updated:", {
503
+ poolBalance,
504
+ hasBalance: !!poolBalance,
505
+ isLoadingBalance,
506
+ })
507
+ }, [poolBalance, isLoadingBalance])
508
+
509
+ // Log the destination token returned from useSendForm
510
+ useEffect(() => {
511
+ logger.console.log("[pool-withdraw] Destination token from useSendForm:", {
512
+ symbol: returnedDestToken?.symbol,
513
+ name: returnedDestToken?.name,
514
+ decimals: returnedDestToken?.decimals,
515
+ sourceTokenAddress: finalSelectedToken?.contractAddress,
516
+ })
517
+ }, [returnedDestToken, finalSelectedToken])
518
+
519
+ // Set destination chain to match the pool's chain (same-chain transaction)
520
+ useEffect(() => {
521
+ if (selectedPool && supportedChains && setSelectedDestinationChain) {
522
+ const destinationChain = supportedChains.find(
523
+ (chain) => chain.id === selectedPool.chainId,
524
+ )
525
+
526
+ if (destinationChain) {
527
+ logger.console.log("[pool-withdraw] Setting destination chain:", {
528
+ chainId: destinationChain.id,
529
+ chainName: destinationChain.name,
530
+ poolChainId: selectedPool.chainId,
531
+ isSameChain: true,
532
+ })
533
+ setSelectedDestinationChain(destinationChain)
534
+ } else {
535
+ logger.console.warn(
536
+ "[pool-withdraw] Destination chain not found in supported chains:",
537
+ {
538
+ poolChainId: selectedPool.chainId,
539
+ supportedChainIds: supportedChains.map((c) => c.id),
540
+ },
541
+ )
542
+ }
543
+ }
544
+ }, [selectedPool, supportedChains, setSelectedDestinationChain])
545
+
546
+ // Auto-focus input field on component mount
547
+ useEffect(() => {
548
+ if (inputRef.current) {
549
+ inputRef.current.focus()
550
+ }
551
+ }, [])
552
+
553
+ // Notify parent component about pool selector visibility
554
+ useEffect(() => {
555
+ if (onPoolSelectorStateChange) {
556
+ // Only hide tabs when showing EarnPools selector
557
+ onPoolSelectorStateChange(showEarnPools)
558
+ }
559
+ }, [showEarnPools, onPoolSelectorStateChange])
560
+
561
+ const handleAmountChange = (value: string) => {
562
+ // Validate decimal places (max 8 decimals)
563
+ const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
564
+ if (!decimalMatch && value !== "") {
565
+ return // Don't update if more than 8 decimals
566
+ }
567
+ setAmount(value)
568
+ setSendFormAmount(value) // Sync with useSendForm for quote functionality
569
+ }
570
+
571
+ const handlePoolSelect = (pool: any) => {
572
+ logger.console.log("Selected pool for withdrawal:", pool)
573
+ setSelectedPool(pool)
574
+ setShowEarnPools(false)
575
+ }
576
+
577
+ // Handle amount selection from percentage buttons
578
+ const handleAmountSelect = (selectedAmount: string) => {
579
+ setAmount(selectedAmount)
580
+ setSendFormAmount(selectedAmount)
581
+ }
582
+
583
+ // Dynamic font size based on input length
584
+ const inputStyles = useDynamicInputStyles({
585
+ inputValue: amount,
586
+ variant: "default",
587
+ })
588
+
589
+ // Get chain info for selected pool
590
+ const chainInfo = useMemo(() => {
591
+ return selectedPool ? getChainInfo(selectedPool.chainId) : null
592
+ }, [selectedPool])
593
+
594
+ if (showEarnPools) {
595
+ return (
596
+ <EarnPools
597
+ onBack={() => setShowEarnPools(false)}
598
+ onPoolSelect={handlePoolSelect}
599
+ />
600
+ )
601
+ }
602
+
603
+ return (
604
+ <div className="space-y-2">
605
+ {/* Pool Selection (Primary Section) */}
606
+ <div className="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">
607
+ {selectedPool ? (
608
+ <div className="p-3 trails-border-radius-container trails-bg-secondary transition-all overflow-hidden">
609
+ {/* Vault Label */}
610
+ <div className="flex justify-between items-center mb-2">
611
+ <div className="text-sm font-semibold trails-text-secondary text-left">
612
+ From
613
+ </div>
614
+ </div>
615
+
616
+ <div className="px-1">
617
+ <div className="flex items-center justify-between">
618
+ <div className="flex items-center space-x-3">
619
+ <div style={{ width: "32px", height: "32px" }}>
620
+ <a
621
+ href={getExplorerUrlForAddress({
622
+ address: selectedPool.token.address,
623
+ chainId: selectedPool.chainId,
624
+ })}
625
+ target="_blank"
626
+ rel="noopener noreferrer"
627
+ className="cursor-pointer"
628
+ >
629
+ <TokenImage
630
+ symbol={selectedPool.token.symbol}
631
+ imageUrl={selectedPool.token.logoUrl}
632
+ chainId={selectedPool.chainId}
633
+ contractAddress={selectedPool.token.address}
634
+ size={32}
635
+ />
636
+ </a>
637
+ </div>
638
+ <div>
639
+ <h3 className="font-medium text-gray-900 dark:text-white text-sm">
640
+ {selectedPool.poolUrl ? (
641
+ <a
642
+ href={selectedPool.poolUrl}
643
+ target="_blank"
644
+ rel="noopener noreferrer"
645
+ className="hover:underline cursor-pointer"
646
+ >
647
+ {selectedPool.name}
648
+ </a>
649
+ ) : (
650
+ selectedPool.name
651
+ )}
652
+ </h3>
653
+ <div className="flex items-center space-x-2">
654
+ <span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
655
+ {selectedPool.protocol === "Aave" && (
656
+ <img
657
+ src={aaveLogo}
658
+ alt="Aave"
659
+ className="w-3 h-3 mr-1"
660
+ />
661
+ )}
662
+ {selectedPool.protocol === "Morpho" && (
663
+ <img
664
+ src={morphoLogo}
665
+ alt="Morpho"
666
+ className="w-3 h-3 mr-1"
667
+ />
668
+ )}
669
+ {selectedPool.protocolUrl ? (
670
+ <a
671
+ href={selectedPool.protocolUrl}
672
+ target="_blank"
673
+ rel="noopener noreferrer"
674
+ className="hover:underline cursor-pointer"
675
+ >
676
+ {selectedPool.protocol}
677
+ </a>
678
+ ) : (
679
+ selectedPool.protocol
680
+ )}
681
+ </span>
682
+ </div>
683
+ </div>
684
+ </div>
685
+ <button
686
+ type="button"
687
+ title="Select Vault"
688
+ onClick={() => setShowEarnPools(true)}
689
+ 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"
690
+ >
691
+ <div>
692
+ <div className="flex items-center justify-end space-x-1 text-green-600 dark:text-green-400 mb-1 whitespace-nowrap">
693
+ <TrendingUp className="w-3 h-3" />
694
+ <span className="font-semibold text-sm">
695
+ {selectedPool.apy.toFixed(1)}% APY
696
+ </span>
697
+ </div>
698
+ <p className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
699
+ TVL: {formatTvl(selectedPool.tvl)}
700
+ </p>
701
+ </div>
702
+ <ChevronRight className="w-4 h-4 text-gray-400" />
703
+ </button>
704
+ </div>
705
+ </div>
706
+ </div>
707
+ ) : (
708
+ <button
709
+ type="button"
710
+ onClick={() => setShowEarnPools(true)}
711
+ className="w-full py-6 px-4 trails-list-item trails-border-radius-container transition-all duration-200 cursor-pointer"
712
+ >
713
+ <div className="flex items-center justify-between">
714
+ <div className="flex items-center space-x-3 flex-1">
715
+ <div className="w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
716
+ <Search className="w-4 h-4 text-gray-400" />
717
+ </div>
718
+ <div className="text-left flex-1">
719
+ <div className="font-semibold text-gray-900 dark:text-white text-sm">
720
+ Select vault to withdraw from
721
+ </div>
722
+ </div>
723
+ </div>
724
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
725
+ </div>
726
+ </button>
727
+ )}
728
+ </div>
729
+
730
+ {/* Amount Input Section */}
731
+ {selectedPool && (
732
+ <div className="pt-4 pb-4 mb-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">
733
+ {/* Withdraw Label */}
734
+ <div className="mb-4 flex justify-between items-center">
735
+ <div className="text-sm font-medium trails-text-secondary text-left m-0">
736
+ Amount
737
+ </div>
738
+ </div>
739
+
740
+ <div className="flex items-center space-x-2 flex-1">
741
+ {/* Amount Input */}
742
+ <div className="flex-1">
743
+ <div className="flex items-center space-x-2">
744
+ <input
745
+ ref={inputRef}
746
+ id="amount"
747
+ type="text"
748
+ value={amount}
749
+ onChange={(e) => handleAmountChange(e.target.value)}
750
+ placeholder={"0"}
751
+ className={`w-full bg-transparent font-bold trails-text-primary placeholder:trails-text-muted border-none outline-none ${
752
+ isLoadingQuote ? "animate-pulse" : ""
753
+ }`}
754
+ style={inputStyles}
755
+ inputMode="decimal"
756
+ />
757
+ {isLoadingQuote && (
758
+ <div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
759
+ )}
760
+ </div>
761
+ </div>
762
+
763
+ {/* Token Display (Not Selectable) */}
764
+ <TokenDisplayNonSelectable
765
+ symbol={selectedPool.token.symbol}
766
+ imageUrl={selectedPool.token.logoUrl}
767
+ chainId={selectedPool.chainId}
768
+ contractAddress={selectedPool.token.address}
769
+ chainName={chainInfo?.name}
770
+ />
771
+ </div>
772
+
773
+ {/* Bottom Info Row */}
774
+ <div className="mt-4 flex justify-between items-center">
775
+ {/* USD Amount */}
776
+ <div className="text-xs text-gray-500 dark:text-gray-400">
777
+ {selectedPool && amount ? (
778
+ <>≈ {underlyingTokenUsdDisplay || "$0.00"}</>
779
+ ) : (
780
+ <span>&nbsp;</span>
781
+ )}
782
+ </div>
783
+
784
+ {/* Pool Balance and Percentage Buttons */}
785
+ {poolBalance && (
786
+ <div className="flex items-center space-x-2">
787
+ <button
788
+ type="button"
789
+ 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"
790
+ onClick={() => handleAmountSelect(poolBalance || "0")}
791
+ onKeyDown={(e) => {
792
+ if (e.key === "Enter" || e.key === " ") {
793
+ e.preventDefault()
794
+ handleAmountSelect(poolBalance || "0")
795
+ }
796
+ }}
797
+ title="Click to withdraw full balance"
798
+ >
799
+ Pool Balance:{" "}
800
+ {isBalanceVisible
801
+ ? isLoadingBalance
802
+ ? "Loading..."
803
+ : poolBalance || "0.00"
804
+ : "••••••"}
805
+ </button>
806
+
807
+ {/* Percentage Buttons */}
808
+ <PercentageMaxButtons
809
+ userBalance={poolBalance || undefined}
810
+ isNativeToken={false} // Pool tokens are never native tokens
811
+ gasCostFormatted={prepareSendQuote?.gasCostFormatted}
812
+ chainId={selectedPool.chainId}
813
+ onAmountSelect={handleAmountSelect}
814
+ className="opacity-100"
815
+ />
816
+ </div>
817
+ )}
818
+ </div>
819
+ </div>
820
+ )}
821
+
822
+ {prepareSendQuote?.noSufficientBalance ? (
823
+ <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
824
+ <div className="flex items-center space-x-2">
825
+ <svg
826
+ className="w-4 h-4 text-amber-500 flex-shrink-0"
827
+ fill="none"
828
+ stroke="currentColor"
829
+ viewBox="0 0 24 24"
830
+ aria-hidden="true"
831
+ >
832
+ <path
833
+ strokeLinecap="round"
834
+ strokeLinejoin="round"
835
+ strokeWidth={2}
836
+ 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"
837
+ />
838
+ </svg>
839
+ <p className="text-sm text-amber-600 dark:text-amber-400">
840
+ Insufficient balance to complete this transaction
841
+ </p>
842
+ </div>
843
+ </div>
844
+ ) : null}
845
+
846
+ {/* Quote Details */}
847
+ {prepareSendQuote && (
848
+ <div className="mb-4">
849
+ <QuoteDetails quote={prepareSendQuote} showContent={true} />
850
+ </div>
851
+ )}
852
+
853
+ {selectedPool && (
854
+ <form onSubmit={handleSubmit}>
855
+ <button
856
+ type="submit"
857
+ disabled={
858
+ !amount ||
859
+ !isValidRecipient ||
860
+ isSubmitting ||
861
+ isLoadingQuote ||
862
+ !prepareSendQuote ||
863
+ prepareSendQuote?.noSufficientBalance
864
+ }
865
+ 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`}
866
+ >
867
+ {isSubmitting ? (
868
+ <div className="flex items-center justify-center">
869
+ <Loader2
870
+ className={`w-5 h-5 animate-spin mr-2 ${"text-gray-400"}`}
871
+ />
872
+ <span>{buttonText}</span>
873
+ </div>
874
+ ) : prepareSendQuote?.noSufficientBalance ? (
875
+ "Insufficient Balance"
876
+ ) : (
877
+ "Withdraw"
878
+ )}
879
+ </button>
880
+ </form>
881
+ )}
882
+ </div>
883
+ )
884
+ }