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.
- package/dist/aave.d.ts +8 -0
- package/dist/aave.d.ts.map +1 -1
- package/dist/abortController.d.ts +8 -0
- package/dist/abortController.d.ts.map +1 -0
- package/dist/{ccip-BlV1Mry3.js → ccip-Xjh9d1gb.js} +7 -7
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/error.d.ts +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/estimate.d.ts +52 -0
- package/dist/estimate.d.ts.map +1 -1
- package/dist/fees.d.ts +19 -0
- package/dist/fees.d.ts.map +1 -0
- package/dist/{index-BNWCIGfQ.js → index-BnhdZ8Ho.js} +76406 -75798
- package/dist/index.js +726 -520
- package/dist/intents.d.ts +40 -0
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxnMonitor.d.ts +3 -3
- package/dist/metaTxnMonitor.d.ts.map +1 -1
- package/dist/metaTxns.d.ts +3 -3
- package/dist/metaTxns.d.ts.map +1 -1
- package/dist/morpho.d.ts +8 -0
- package/dist/morpho.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +19 -75
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/queryParams.d.ts.map +1 -1
- package/dist/relayer.d.ts +6 -6
- package/dist/relayer.d.ts.map +1 -1
- package/dist/sequenceWallet.d.ts +2 -2
- package/dist/sequenceWallet.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/transactions.d.ts +4 -2
- package/dist/transactions.d.ts.map +1 -1
- package/dist/wallets.d.ts.map +1 -1
- package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
- package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -1
- package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +4 -2
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts +4 -0
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
- package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
- package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts +2 -2
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
- package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
- package/dist/widget/components/FeeBreakdown.d.ts +9 -0
- package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
- package/dist/widget/components/Fund.d.ts +2 -2
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +13 -7
- package/dist/widget/components/FundSwap.d.ts.map +1 -0
- package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
- package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/Modal.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts +2 -2
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
- package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
- package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +14 -36
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
- package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +19 -10
- package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
- package/dist/widget/components/QuoteDetails.d.ts +1 -0
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/Receive.d.ts.map +1 -1
- package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
- package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Recipients.d.ts.map +1 -1
- package/dist/widget/components/RequiredPropsError.d.ts +8 -0
- package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
- package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts +3 -2
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
- package/dist/widget/components/TokenImage.d.ts +1 -0
- package/dist/widget/components/TokenImage.d.ts.map +1 -1
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
- package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Tooltip.d.ts +9 -0
- package/dist/widget/components/Tooltip.d.ts.map +1 -0
- package/dist/widget/components/UserPreferences.d.ts.map +1 -1
- package/dist/widget/components/WaasFeeOptions.d.ts +9 -0
- package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
- package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
- package/dist/widget/components/WalletConnect.d.ts.map +1 -1
- package/dist/widget/components/WalletList.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +2 -0
- package/dist/widget/css/index.css +554 -0
- package/dist/widget/hooks/useBack.d.ts +1 -0
- package/dist/widget/hooks/useBack.d.ts.map +1 -1
- package/dist/widget/hooks/useCheckout.d.ts +1 -1
- package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts +83 -0
- package/dist/widget/hooks/useQuote.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
- package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +2 -2
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +9 -4
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +18 -12
- package/src/aave.ts +32 -0
- package/src/abortController.ts +35 -0
- package/src/config.ts +12 -4
- package/src/constants.ts +5 -0
- package/src/error.ts +19 -1
- package/src/estimate.ts +416 -5
- package/src/fees.ts +199 -0
- package/src/intents.ts +161 -11
- package/src/metaTxnMonitor.ts +3 -3
- package/src/metaTxns.ts +3 -5
- package/src/morpho.ts +32 -0
- package/src/prepareSend.ts +714 -550
- package/src/queryParams.ts +2 -1
- package/src/relayer.ts +11 -11
- package/src/sequenceWallet.ts +2 -2
- package/src/tokens.ts +7 -1
- package/src/trails.ts +3 -3
- package/src/transactions.ts +62 -18
- package/src/wallets.ts +8 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +3 -13
- package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
- package/src/widget/components/AccountSettings.tsx +48 -54
- package/src/widget/components/ChainFilterDropdown.tsx +24 -3
- package/src/widget/components/ClassicSwap.tsx +131 -213
- package/src/widget/components/ConnectWallet.tsx +8 -38
- package/src/widget/components/ConnectedWallets.tsx +132 -77
- package/src/widget/components/DynamicInputStyles.tsx +76 -0
- package/src/widget/components/Earn.tsx +82 -593
- package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
- package/src/widget/components/FeeBreakdown.tsx +155 -0
- package/src/widget/components/Fund.tsx +41 -108
- package/src/widget/components/FundMethods.tsx +82 -159
- package/src/widget/components/FundSwap.tsx +52 -0
- package/src/widget/components/FundingMethodSelectorButton.tsx +70 -0
- package/src/widget/components/Identicon.tsx +164 -95
- package/src/widget/components/MeshConnectExchanges.tsx +2 -15
- package/src/widget/components/Modal.tsx +0 -8
- package/src/widget/components/Pay.tsx +214 -237
- package/src/widget/components/PercentageMaxButtons.tsx +77 -0
- package/src/widget/components/PoolDeposit.tsx +569 -0
- package/src/widget/components/PoolWithdraw.tsx +884 -0
- package/src/widget/components/PriceImpactWarning.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +43 -12
- package/src/widget/components/Receipt.tsx +16 -2
- package/src/widget/components/Receive.tsx +0 -2
- package/src/widget/components/RecipientSelectorButton.tsx +44 -0
- package/src/widget/components/Recipients.tsx +63 -157
- package/src/widget/components/RequiredPropsError.tsx +33 -0
- package/src/widget/components/ScreenHeader.tsx +62 -34
- package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
- package/src/widget/components/Swap.tsx +4 -45
- package/src/widget/components/SwapSettings.tsx +2 -14
- package/src/widget/components/ThemeProvider.tsx +2 -1
- package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
- package/src/widget/components/TokenImage.tsx +22 -5
- package/src/widget/components/TokenList.tsx +0 -1
- package/src/widget/components/TokenSelector.tsx +63 -53
- package/src/widget/components/TokenSelectorButton.tsx +98 -0
- package/src/widget/components/Tooltip.tsx +51 -0
- package/src/widget/components/TransferPendingVertical.tsx +1 -1
- package/src/widget/components/UserPreferences.tsx +6 -24
- package/src/widget/components/WaasFeeOptions.tsx +450 -0
- package/src/widget/components/WalletConfirmation.tsx +76 -14
- package/src/widget/components/WalletConnect.tsx +93 -29
- package/src/widget/components/WalletList.tsx +4 -2
- package/src/widget/hooks/useBack.tsx +2 -0
- package/src/widget/hooks/useCheckout.ts +36 -20
- package/src/widget/hooks/useCurrentScreen.tsx +1 -0
- package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
- package/src/widget/hooks/usePayMessage.tsx +86 -11
- package/src/widget/hooks/useQuote.ts +413 -0
- package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
- package/src/widget/hooks/useSendForm.ts +32 -6
- package/src/widget/index.css +27 -0
- package/src/widget/widget.tsx +326 -283
- package/dist/widget/components/FundSendForm.d.ts.map +0 -1
- package/dist/widget/components/PaySendForm.d.ts.map +0 -1
- package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
- package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
- package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
- package/src/widget/components/FundSendForm.tsx +0 -903
- package/src/widget/components/PaySendForm.tsx +0 -869
- package/src/widget/components/SimpleSwap.tsx +0 -983
- 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> </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
|
+
}
|