0xtrails 0.2.4 → 0.2.5

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