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.
- package/dist/aave.d.ts +8 -0
- package/dist/aave.d.ts.map +1 -1
- package/dist/{ccip-BlV1Mry3.js → ccip-CXlshvBY.js} +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -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/{index-BNWCIGfQ.js → index-_QuyGrjU.js} +72332 -72246
- package/dist/index.js +2 -2
- 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 +16 -6
- 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/wallets.d.ts.map +1 -1
- package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
- package/dist/widget/components/AccountSettings.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +2 -0
- 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/Earn.d.ts.map +1 -1
- 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} +11 -5
- 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/Modal.d.ts.map +1 -1
- 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} +11 -34
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
- package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +16 -8
- package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
- package/dist/widget/components/QuoteDetails.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 +1 -0
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- 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/UserPreferences.d.ts.map +1 -1
- package/dist/widget/components/WaasFeeOptions.d.ts +8 -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/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/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.map +1 -1
- package/dist/widget/index.js +1 -1
- package/dist/widget/widget.d.ts +4 -4
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +18 -12
- package/src/aave.ts +32 -0
- package/src/config.ts +12 -4
- package/src/constants.ts +2 -0
- package/src/error.ts +19 -1
- package/src/estimate.ts +416 -5
- 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 +503 -166
- 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/wallets.ts +8 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +3 -13
- package/src/widget/components/AccountSettings.tsx +6 -24
- package/src/widget/components/ClassicSwap.tsx +111 -155
- package/src/widget/components/ConnectWallet.tsx +4 -37
- package/src/widget/components/ConnectedWallets.tsx +113 -58
- package/src/widget/components/Earn.tsx +73 -589
- package/src/widget/components/Fund.tsx +31 -82
- package/src/widget/components/FundMethods.tsx +82 -159
- package/src/widget/components/FundSwap.tsx +52 -0
- package/src/widget/components/FundingMethodSelectorButton.tsx +60 -0
- package/src/widget/components/Modal.tsx +6 -2
- package/src/widget/components/Pay.tsx +183 -208
- package/src/widget/components/PercentageMaxButtons.tsx +77 -0
- package/src/widget/components/PoolDeposit.tsx +593 -0
- package/src/widget/components/PoolWithdraw.tsx +903 -0
- package/src/widget/components/QuoteDetails.tsx +22 -8
- package/src/widget/components/Receive.tsx +0 -2
- package/src/widget/components/RecipientSelectorButton.tsx +42 -0
- package/src/widget/components/Recipients.tsx +62 -156
- package/src/widget/components/RequiredPropsError.tsx +33 -0
- package/src/widget/components/ScreenHeader.tsx +5 -1
- package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
- package/src/widget/components/Swap.tsx +2 -43
- package/src/widget/components/SwapSettings.tsx +2 -14
- package/src/widget/components/TokenImage.tsx +21 -4
- package/src/widget/components/TokenList.tsx +0 -1
- package/src/widget/components/TokenSelector.tsx +1 -0
- package/src/widget/components/TokenSelectorButton.tsx +75 -0
- package/src/widget/components/UserPreferences.tsx +6 -24
- package/src/widget/components/WaasFeeOptions.tsx +331 -0
- package/src/widget/components/WalletConfirmation.tsx +55 -3
- 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/useSelectedFundMethod.tsx +41 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
- package/src/widget/hooks/useSendForm.ts +24 -2
- package/src/widget/index.css +27 -0
- package/src/widget/widget.tsx +169 -111
- 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
|
@@ -1,903 +0,0 @@
|
|
|
1
|
-
import { ChevronDown, Loader2, RefreshCcw } from "lucide-react"
|
|
2
|
-
import type React from "react"
|
|
3
|
-
import { useEffect, useRef, useState, useCallback, useMemo } from "react"
|
|
4
|
-
import type { Account, WalletClient } from "viem"
|
|
5
|
-
import { formatUnits, isAddress } from "viem"
|
|
6
|
-
import type { TransactionState } from "../../transactions.js"
|
|
7
|
-
import type { OnCompleteProps, Token, TokenInfo } from "../hooks/useSendForm.js"
|
|
8
|
-
import { useSendForm } from "../hooks/useSendForm.js"
|
|
9
|
-
import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
|
|
10
|
-
import { ChainImage } from "./ChainImage.js"
|
|
11
|
-
import { TokenImage } from "./TokenImage.js"
|
|
12
|
-
import { QuoteDetails } from "./QuoteDetails.js"
|
|
13
|
-
import { TruncatedAddress } from "./TruncatedAddress.js"
|
|
14
|
-
// import { RefundAddressInput } from "./RefundAddressInput.js"
|
|
15
|
-
import { TradeType } from "../../prepareSend.js"
|
|
16
|
-
import type { PrepareSendQuote } from "../../prepareSend.js"
|
|
17
|
-
import { formatAmount, formatUsdAmountDisplay } from "../../tokenBalances.js"
|
|
18
|
-
import { ScreenHeader } from "./ScreenHeader.js"
|
|
19
|
-
import { ErrorDisplay } from "./ErrorDisplay.js"
|
|
20
|
-
import { useMode } from "../hooks/useMode.js"
|
|
21
|
-
import { getExplorerUrlForAddress } from "../../explorer.js"
|
|
22
|
-
import { logger } from "../../logger.js"
|
|
23
|
-
import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
|
|
24
|
-
|
|
25
|
-
interface FundSendFormProps {
|
|
26
|
-
selectedToken: Token
|
|
27
|
-
onSend: (amount: string, recipient: string) => void
|
|
28
|
-
onBack?: () => void
|
|
29
|
-
onConfirm: () => void
|
|
30
|
-
onComplete: (result: OnCompleteProps) => void
|
|
31
|
-
account: Account
|
|
32
|
-
toRecipient?: string
|
|
33
|
-
toAmount?: string
|
|
34
|
-
toChainId?: number
|
|
35
|
-
toToken?: string
|
|
36
|
-
toCalldata?: string
|
|
37
|
-
walletClient: WalletClient
|
|
38
|
-
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
39
|
-
onError: (error: Error | string | null) => void
|
|
40
|
-
onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
|
|
41
|
-
paymasterUrls?: Array<{ chainId: number; url: string }>
|
|
42
|
-
gasless?: boolean
|
|
43
|
-
setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
|
|
44
|
-
quoteProvider?: string
|
|
45
|
-
fundMethod?: string
|
|
46
|
-
onNavigateToMeshConnect?: (
|
|
47
|
-
props: {
|
|
48
|
-
toTokenSymbol: string
|
|
49
|
-
toTokenAmount: string
|
|
50
|
-
toChainId: number
|
|
51
|
-
toRecipientAddress: string
|
|
52
|
-
},
|
|
53
|
-
quote?: PrepareSendQuote | null,
|
|
54
|
-
) => void
|
|
55
|
-
checkoutOnHandlers?: CheckoutOnHandlers
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const FundSendForm: React.FC<FundSendFormProps> = ({
|
|
59
|
-
selectedToken,
|
|
60
|
-
onSend,
|
|
61
|
-
onBack,
|
|
62
|
-
onConfirm,
|
|
63
|
-
onComplete,
|
|
64
|
-
account,
|
|
65
|
-
toAmount,
|
|
66
|
-
toRecipient,
|
|
67
|
-
toChainId,
|
|
68
|
-
toToken,
|
|
69
|
-
toCalldata,
|
|
70
|
-
walletClient,
|
|
71
|
-
onTransactionStateChange,
|
|
72
|
-
onError,
|
|
73
|
-
onWaitingForWalletConfirm,
|
|
74
|
-
paymasterUrls,
|
|
75
|
-
gasless,
|
|
76
|
-
setWalletConfirmRetryHandler,
|
|
77
|
-
quoteProvider,
|
|
78
|
-
fundMethod,
|
|
79
|
-
onNavigateToMeshConnect,
|
|
80
|
-
checkoutOnHandlers,
|
|
81
|
-
}) => {
|
|
82
|
-
const { mode } = useMode()
|
|
83
|
-
const { isBalanceVisible } = useBalanceVisible()
|
|
84
|
-
// Local state for fund-specific functionality
|
|
85
|
-
const [isInputTypeUsd, setIsInputTypeUsd] = useState(false)
|
|
86
|
-
const [refetchTrigger, setRefetchTrigger] = useState(0)
|
|
87
|
-
// const [isRefundAddressOpen, setIsRefundAddressOpen] = useState(false)
|
|
88
|
-
// const [refundAddress, setRefundAddress] = useState<string>(account.address)
|
|
89
|
-
|
|
90
|
-
const [tokenAmountForBackend, setTokenAmountForBackend] = useState("")
|
|
91
|
-
const [inputDisplayValue, setInputDisplayValue] = useState("")
|
|
92
|
-
const inputRef = useRef<HTMLInputElement>(null)
|
|
93
|
-
const chainDropdownRef = useRef<HTMLDivElement>(null)
|
|
94
|
-
const tokenDropdownRef = useRef<HTMLDivElement>(null)
|
|
95
|
-
|
|
96
|
-
// Auto-focus input field on component mount
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
if (inputRef.current) {
|
|
99
|
-
inputRef.current.focus()
|
|
100
|
-
}
|
|
101
|
-
}, [])
|
|
102
|
-
|
|
103
|
-
const {
|
|
104
|
-
amount: hookAmount,
|
|
105
|
-
amountUsdDisplay,
|
|
106
|
-
balanceFormatted,
|
|
107
|
-
balanceUsdDisplay,
|
|
108
|
-
chainInfo,
|
|
109
|
-
isSubmitting,
|
|
110
|
-
isLoadingQuote,
|
|
111
|
-
selectedDestinationChain,
|
|
112
|
-
selectedDestToken,
|
|
113
|
-
setAmount: setHookAmount,
|
|
114
|
-
handleSubmit,
|
|
115
|
-
buttonText,
|
|
116
|
-
toAmountFormatted,
|
|
117
|
-
toAmountDisplay,
|
|
118
|
-
sourceTokenPrices,
|
|
119
|
-
destTokenPrices,
|
|
120
|
-
isValidRecipient,
|
|
121
|
-
recipient,
|
|
122
|
-
recipientInput,
|
|
123
|
-
setRecipient,
|
|
124
|
-
setRecipientInput,
|
|
125
|
-
handleRecipientInputChange,
|
|
126
|
-
ensAddress,
|
|
127
|
-
isChainDropdownOpen,
|
|
128
|
-
isTokenDropdownOpen,
|
|
129
|
-
setIsChainDropdownOpen,
|
|
130
|
-
setIsTokenDropdownOpen,
|
|
131
|
-
supportedChains,
|
|
132
|
-
supportedTokens,
|
|
133
|
-
setSelectedDestinationChain,
|
|
134
|
-
setSelectedDestToken,
|
|
135
|
-
prepareSendQuote,
|
|
136
|
-
quoteError,
|
|
137
|
-
quoteErrorPrettified,
|
|
138
|
-
isSameTokenWithoutCustomCalldata,
|
|
139
|
-
destinationTokenAddress,
|
|
140
|
-
isRecipientContract,
|
|
141
|
-
} = useSendForm({
|
|
142
|
-
account,
|
|
143
|
-
// Don't pass toAmount for fund form - user enters input amount
|
|
144
|
-
toRecipient: toRecipient || account.address,
|
|
145
|
-
toChainId,
|
|
146
|
-
toToken,
|
|
147
|
-
toCalldata,
|
|
148
|
-
// refundAddress,
|
|
149
|
-
walletClient,
|
|
150
|
-
onTransactionStateChange,
|
|
151
|
-
onError,
|
|
152
|
-
onWaitingForWalletConfirm,
|
|
153
|
-
paymasterUrls,
|
|
154
|
-
gasless,
|
|
155
|
-
onConfirm,
|
|
156
|
-
onComplete,
|
|
157
|
-
onSend,
|
|
158
|
-
selectedToken,
|
|
159
|
-
setWalletConfirmRetryHandler,
|
|
160
|
-
tradeType: TradeType.EXACT_INPUT,
|
|
161
|
-
quoteProvider,
|
|
162
|
-
fundMethod,
|
|
163
|
-
mode,
|
|
164
|
-
onNavigateToMeshConnect,
|
|
165
|
-
checkoutOnHandlers,
|
|
166
|
-
refetchTrigger,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// Get source token price for USD conversions
|
|
170
|
-
const sourceTokenPrice = sourceTokenPrices?.[0]?.price?.value ?? 0
|
|
171
|
-
|
|
172
|
-
// Get destination token price for receive USD value
|
|
173
|
-
const destTokenPrice = destTokenPrices?.[0]?.price?.value ?? 0
|
|
174
|
-
|
|
175
|
-
// Sync display value with token amount only when mode changes (not during typing)
|
|
176
|
-
const [lastInputMode, setLastInputMode] = useState(isInputTypeUsd)
|
|
177
|
-
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
// Only sync when mode actually changes, not during normal typing
|
|
180
|
-
if (lastInputMode !== isInputTypeUsd && tokenAmountForBackend) {
|
|
181
|
-
const tokenAmount = parseFloat(tokenAmountForBackend) || 0
|
|
182
|
-
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
183
|
-
// Show USD with max 2 decimals
|
|
184
|
-
const usdAmount = tokenAmount * sourceTokenPrice
|
|
185
|
-
setInputDisplayValue(Number(usdAmount.toFixed(2)).toString())
|
|
186
|
-
} else {
|
|
187
|
-
// Show token with max 8 decimals
|
|
188
|
-
setInputDisplayValue(Number(tokenAmount.toFixed(8)).toString())
|
|
189
|
-
}
|
|
190
|
-
setLastInputMode(isInputTypeUsd)
|
|
191
|
-
}
|
|
192
|
-
}, [isInputTypeUsd, sourceTokenPrice, tokenAmountForBackend, lastInputMode])
|
|
193
|
-
|
|
194
|
-
// Handle click outside for dropdowns
|
|
195
|
-
useEffect(() => {
|
|
196
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
197
|
-
if (
|
|
198
|
-
chainDropdownRef.current &&
|
|
199
|
-
!chainDropdownRef.current.contains(event.target as Node)
|
|
200
|
-
) {
|
|
201
|
-
setIsChainDropdownOpen(false)
|
|
202
|
-
}
|
|
203
|
-
if (
|
|
204
|
-
tokenDropdownRef.current &&
|
|
205
|
-
!tokenDropdownRef.current.contains(event.target as Node)
|
|
206
|
-
) {
|
|
207
|
-
setIsTokenDropdownOpen(false)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (isChainDropdownOpen || isTokenDropdownOpen) {
|
|
212
|
-
document.addEventListener("click", handleClickOutside)
|
|
213
|
-
return () => document.removeEventListener("click", handleClickOutside)
|
|
214
|
-
}
|
|
215
|
-
}, [
|
|
216
|
-
setIsChainDropdownOpen,
|
|
217
|
-
setIsTokenDropdownOpen,
|
|
218
|
-
isChainDropdownOpen,
|
|
219
|
-
isTokenDropdownOpen,
|
|
220
|
-
])
|
|
221
|
-
|
|
222
|
-
// Handle input amount changes with 8 decimal limit and 16 char total limit
|
|
223
|
-
const handleAmountChange = useCallback(
|
|
224
|
-
(value: string) => {
|
|
225
|
-
// Allow empty string
|
|
226
|
-
if (value === "") {
|
|
227
|
-
setInputDisplayValue("")
|
|
228
|
-
setTokenAmountForBackend("")
|
|
229
|
-
setHookAmount("")
|
|
230
|
-
return
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Limit total length to 16 characters
|
|
234
|
-
if (value.length > 16) {
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Validate decimal places (max 8 decimals) and allow single decimal point
|
|
239
|
-
const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
|
|
240
|
-
if (!decimalMatch) {
|
|
241
|
-
return // Don't update if invalid format
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Store the display value
|
|
245
|
-
setInputDisplayValue(value)
|
|
246
|
-
|
|
247
|
-
// Update the token amount for backend and useSendForm
|
|
248
|
-
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
249
|
-
const usdAmount = parseFloat(value) || 0
|
|
250
|
-
const tokenAmount = usdAmount / sourceTokenPrice
|
|
251
|
-
setTokenAmountForBackend(tokenAmount.toString())
|
|
252
|
-
setHookAmount(tokenAmount.toString())
|
|
253
|
-
} else {
|
|
254
|
-
setTokenAmountForBackend(value)
|
|
255
|
-
setHookAmount(value)
|
|
256
|
-
}
|
|
257
|
-
},
|
|
258
|
-
[setHookAmount, isInputTypeUsd, sourceTokenPrice],
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
// Get display values based on input type
|
|
262
|
-
const displayAmount = useMemo(() => {
|
|
263
|
-
return inputDisplayValue
|
|
264
|
-
}, [inputDisplayValue])
|
|
265
|
-
|
|
266
|
-
const displayUsdValue = useMemo(() => {
|
|
267
|
-
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
268
|
-
// Show token amount when in USD mode
|
|
269
|
-
const tokenAmount = parseFloat(tokenAmountForBackend) || 0
|
|
270
|
-
return `${formatAmount(tokenAmount)} ${selectedToken.symbol}`
|
|
271
|
-
}
|
|
272
|
-
return amountUsdDisplay
|
|
273
|
-
}, [
|
|
274
|
-
tokenAmountForBackend,
|
|
275
|
-
isInputTypeUsd,
|
|
276
|
-
sourceTokenPrice,
|
|
277
|
-
selectedToken.symbol,
|
|
278
|
-
amountUsdDisplay,
|
|
279
|
-
])
|
|
280
|
-
|
|
281
|
-
// Calculate USD value for the receive section based on destination token
|
|
282
|
-
const receiveUsdValue = useMemo(() => {
|
|
283
|
-
if (destTokenPrice > 0) {
|
|
284
|
-
const destinationAmount = parseFloat(toAmountFormatted) || 0
|
|
285
|
-
const usdValue = destinationAmount * destTokenPrice
|
|
286
|
-
logger.console.log("[trails-sdk] Receive USD calculation:", {
|
|
287
|
-
toAmountFormatted,
|
|
288
|
-
destinationAmount,
|
|
289
|
-
destTokenPrice,
|
|
290
|
-
usdValue,
|
|
291
|
-
formatted: formatUsdAmountDisplay(usdValue),
|
|
292
|
-
})
|
|
293
|
-
return formatUsdAmountDisplay(usdValue)
|
|
294
|
-
}
|
|
295
|
-
return formatUsdAmountDisplay(0)
|
|
296
|
-
}, [toAmountFormatted, destTokenPrice])
|
|
297
|
-
|
|
298
|
-
// Handle percentage clicks for quick amounts
|
|
299
|
-
const handlePercentageClick = useCallback(
|
|
300
|
-
(percentage: number) => {
|
|
301
|
-
if (!selectedToken.balance || !selectedToken.contractInfo?.decimals) {
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const totalBalance = parseFloat(
|
|
306
|
-
formatUnits(
|
|
307
|
-
BigInt(selectedToken.balance),
|
|
308
|
-
selectedToken.contractInfo.decimals,
|
|
309
|
-
),
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
const calculatedAmount = (totalBalance * percentage) / 100
|
|
313
|
-
// Cap decimals to 8 places
|
|
314
|
-
const cappedAmount = parseFloat(calculatedAmount.toFixed(8))
|
|
315
|
-
const tokenAmountStr = cappedAmount.toString()
|
|
316
|
-
|
|
317
|
-
// Update all states consistently
|
|
318
|
-
setTokenAmountForBackend(tokenAmountStr)
|
|
319
|
-
setHookAmount(tokenAmountStr)
|
|
320
|
-
|
|
321
|
-
// Update display based on current mode
|
|
322
|
-
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
323
|
-
const usdAmount = cappedAmount * sourceTokenPrice
|
|
324
|
-
setInputDisplayValue(Number(usdAmount.toFixed(2)).toString())
|
|
325
|
-
} else {
|
|
326
|
-
setInputDisplayValue(tokenAmountStr)
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
[selectedToken, setHookAmount, isInputTypeUsd, sourceTokenPrice],
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
// Handle input type toggle (USD ↔ Token)
|
|
333
|
-
const handleInputTypeToggle = useCallback(() => {
|
|
334
|
-
// Use tokenAmountForBackend as the source of truth for conversion
|
|
335
|
-
const currentTokenAmount = parseFloat(tokenAmountForBackend) || 0
|
|
336
|
-
|
|
337
|
-
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
338
|
-
// Switching from USD to token mode
|
|
339
|
-
// Display the token amount (limit to 8 decimals)
|
|
340
|
-
const tokenAmountStr = Number(currentTokenAmount.toFixed(8)).toString()
|
|
341
|
-
setInputDisplayValue(tokenAmountStr)
|
|
342
|
-
} else if (!isInputTypeUsd && sourceTokenPrice > 0) {
|
|
343
|
-
// Switching from token to USD mode
|
|
344
|
-
// Display USD amount (limit to 2 decimals)
|
|
345
|
-
const usdAmount = currentTokenAmount * sourceTokenPrice
|
|
346
|
-
const usdAmountStr = Number(usdAmount.toFixed(2)).toString()
|
|
347
|
-
setInputDisplayValue(usdAmountStr)
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// hookAmount stays as token amount (don't change it)
|
|
351
|
-
// tokenAmountForBackend stays as token amount (don't change it)
|
|
352
|
-
|
|
353
|
-
setIsInputTypeUsd(!isInputTypeUsd)
|
|
354
|
-
// Focus the input field after toggling
|
|
355
|
-
setTimeout(() => {
|
|
356
|
-
if (inputRef.current) {
|
|
357
|
-
inputRef.current.focus()
|
|
358
|
-
// Select all text for easy replacement
|
|
359
|
-
inputRef.current.select()
|
|
360
|
-
}
|
|
361
|
-
}, 0)
|
|
362
|
-
}, [tokenAmountForBackend, isInputTypeUsd, sourceTokenPrice])
|
|
363
|
-
|
|
364
|
-
// Handle manual quote refetch
|
|
365
|
-
const handleRefetchQuote = useCallback(() => {
|
|
366
|
-
setRefetchTrigger((prev) => prev + 1)
|
|
367
|
-
}, [])
|
|
368
|
-
|
|
369
|
-
// Dynamic font size based on input length
|
|
370
|
-
const inputStyles = useMemo(() => {
|
|
371
|
-
const inputLength = displayAmount.length
|
|
372
|
-
let fontSize = "text-6xl" // Much larger initial size
|
|
373
|
-
|
|
374
|
-
if (inputLength > 12) {
|
|
375
|
-
fontSize = "text-2xl"
|
|
376
|
-
} else if (inputLength > 9) {
|
|
377
|
-
fontSize = "text-3xl"
|
|
378
|
-
} else if (inputLength > 6) {
|
|
379
|
-
fontSize = "text-4xl"
|
|
380
|
-
} else if (inputLength > 3) {
|
|
381
|
-
fontSize = "text-5xl"
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return {
|
|
385
|
-
fontSize,
|
|
386
|
-
transition: "all 0.1s ease-in-out",
|
|
387
|
-
}
|
|
388
|
-
}, [displayAmount.length])
|
|
389
|
-
|
|
390
|
-
logger.console.log("[trails-sdk] FundForm", {
|
|
391
|
-
hookAmount, // actual token amount used by backend
|
|
392
|
-
displayAmount, // what user sees in input
|
|
393
|
-
isInputTypeUsd,
|
|
394
|
-
sourceTokenPrice,
|
|
395
|
-
toAmount,
|
|
396
|
-
isSubmitting,
|
|
397
|
-
selectedDestinationChain,
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
if (!selectedDestinationChain) {
|
|
401
|
-
return null
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (!selectedToken) {
|
|
405
|
-
return null
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return (
|
|
409
|
-
<div className="space-y-2">
|
|
410
|
-
<ScreenHeader
|
|
411
|
-
onBack={onBack}
|
|
412
|
-
headerContent="Fund"
|
|
413
|
-
headerContentAlign="left"
|
|
414
|
-
showAccountActions={true}
|
|
415
|
-
/>
|
|
416
|
-
|
|
417
|
-
{/* Balance Info Section */}
|
|
418
|
-
<div className="flex items-center space-x-4 p-4 trails-border-radius-container trails-bg-secondary">
|
|
419
|
-
<div className="flex items-start justify-between w-full">
|
|
420
|
-
{/* Left side - Chain and Token images with token name */}
|
|
421
|
-
<div className="flex items-start space-x-2">
|
|
422
|
-
<div className="flex items-center space-x-2">
|
|
423
|
-
<div style={{ width: "32px", height: "32px" }}>
|
|
424
|
-
<a
|
|
425
|
-
href={getExplorerUrlForAddress({
|
|
426
|
-
address: selectedToken.contractAddress,
|
|
427
|
-
chainId: selectedToken.chainId,
|
|
428
|
-
})}
|
|
429
|
-
target="_blank"
|
|
430
|
-
rel="noopener noreferrer"
|
|
431
|
-
className="cursor-pointer"
|
|
432
|
-
>
|
|
433
|
-
<TokenImage
|
|
434
|
-
symbol={selectedToken.symbol}
|
|
435
|
-
imageUrl={selectedToken.imageUrl}
|
|
436
|
-
chainId={selectedToken.chainId}
|
|
437
|
-
size={32}
|
|
438
|
-
/>
|
|
439
|
-
</a>
|
|
440
|
-
</div>
|
|
441
|
-
<div className="flex flex-col">
|
|
442
|
-
<span className="text-sm font-medium max-w-[135px] truncate text-left text-gray-900 dark:text-white">
|
|
443
|
-
{selectedToken.name}
|
|
444
|
-
</span>
|
|
445
|
-
<span className="text-sm text-gray-500 dark:text-gray-400">
|
|
446
|
-
on {chainInfo?.name || "Unknown Chain"}
|
|
447
|
-
</span>
|
|
448
|
-
</div>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
{/* Right side - USD value and amount */}
|
|
453
|
-
{fundMethod !== "qr-code" && fundMethod !== "exchange" && (
|
|
454
|
-
<div className="text-right">
|
|
455
|
-
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
|
456
|
-
<span className="text-gray-600 dark:text-gray-400">
|
|
457
|
-
Balance:{" "}
|
|
458
|
-
</span>
|
|
459
|
-
{isBalanceVisible ? balanceUsdDisplay : "••••••"}
|
|
460
|
-
</div>
|
|
461
|
-
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
462
|
-
{isBalanceVisible
|
|
463
|
-
? `${balanceFormatted} ${selectedToken.symbol}`
|
|
464
|
-
: "••••••"}
|
|
465
|
-
</div>
|
|
466
|
-
</div>
|
|
467
|
-
)}
|
|
468
|
-
</div>
|
|
469
|
-
</div>
|
|
470
|
-
|
|
471
|
-
<form onSubmit={handleSubmit} className="space-y-2">
|
|
472
|
-
{/* Origin Amount Input Section */}
|
|
473
|
-
<div className="space-y-1">
|
|
474
|
-
{/* Amount Input */}
|
|
475
|
-
<div className="flex items-center justify-center">
|
|
476
|
-
<div className="flex items-center">
|
|
477
|
-
<input
|
|
478
|
-
ref={inputRef}
|
|
479
|
-
type="text"
|
|
480
|
-
value={displayAmount}
|
|
481
|
-
onChange={(e) => handleAmountChange(e.target.value)}
|
|
482
|
-
placeholder="0"
|
|
483
|
-
className={`bg-transparent border-none outline-none ${inputStyles.fontSize} font-bold text-right trails-text-primary placeholder-trails-text-primary`}
|
|
484
|
-
style={{
|
|
485
|
-
width: `${Math.max((displayAmount || "0").length, 1)}ch`,
|
|
486
|
-
minWidth: "1ch",
|
|
487
|
-
maxWidth: "270px",
|
|
488
|
-
padding: "0",
|
|
489
|
-
margin: "0",
|
|
490
|
-
}}
|
|
491
|
-
inputMode="decimal"
|
|
492
|
-
/>
|
|
493
|
-
<span
|
|
494
|
-
className={`${inputStyles.fontSize} font-bold text-gray-400 dark:text-gray-500`}
|
|
495
|
-
style={{
|
|
496
|
-
marginLeft:
|
|
497
|
-
displayAmount && displayAmount !== "0" ? "0.2em" : "0.1em",
|
|
498
|
-
padding: "0",
|
|
499
|
-
transition: "all 0.2s ease-in-out",
|
|
500
|
-
}}
|
|
501
|
-
>
|
|
502
|
-
{isInputTypeUsd ? "USD" : selectedToken.symbol.slice(0, 4)}
|
|
503
|
-
</span>
|
|
504
|
-
</div>
|
|
505
|
-
</div>
|
|
506
|
-
|
|
507
|
-
{/* USD Value centered below input */}
|
|
508
|
-
<div className="flex items-center justify-center">
|
|
509
|
-
<button
|
|
510
|
-
type="button"
|
|
511
|
-
onClick={handleInputTypeToggle}
|
|
512
|
-
className="flex items-center justify-center gap-2 px-3 py-1.5 rounded-md transition-colors cursor-pointer text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:trails-hover-bg hover:text-gray-700 dark:hover:text-gray-200"
|
|
513
|
-
>
|
|
514
|
-
<span className="text-xs font-medium tracking-[-2px]">⇅</span>
|
|
515
|
-
<div className="text-sm font-normal">{displayUsdValue}</div>
|
|
516
|
-
</button>
|
|
517
|
-
</div>
|
|
518
|
-
|
|
519
|
-
{/* Percentage Buttons */}
|
|
520
|
-
<div className="flex space-x-1 justify-center">
|
|
521
|
-
{[25, 50, 75, 100].map((percentage) => (
|
|
522
|
-
<button
|
|
523
|
-
key={percentage}
|
|
524
|
-
type="button"
|
|
525
|
-
onClick={() => handlePercentageClick(percentage)}
|
|
526
|
-
className="py-1 px-2 text-xs font-medium trails-border-radius-container border border-solid transition-colors cursor-pointer border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:trails-hover-bg hover:border-gray-400 dark:hover:border-gray-500"
|
|
527
|
-
>
|
|
528
|
-
{percentage === 100 ? "MAX" : `${percentage}%`}
|
|
529
|
-
</button>
|
|
530
|
-
))}
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
533
|
-
|
|
534
|
-
{/* Chain Selection */}
|
|
535
|
-
{!toChainId && (
|
|
536
|
-
<div className="mb-4">
|
|
537
|
-
<label
|
|
538
|
-
htmlFor="destination-chain"
|
|
539
|
-
className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
|
|
540
|
-
>
|
|
541
|
-
Destination Chain
|
|
542
|
-
</label>
|
|
543
|
-
<div className="relative" ref={chainDropdownRef}>
|
|
544
|
-
<button
|
|
545
|
-
type="button"
|
|
546
|
-
onClick={() => setIsChainDropdownOpen(!isChainDropdownOpen)}
|
|
547
|
-
className="w-full flex items-center px-4 py-3 border border-solid trails-border-radius-dropdown hover:border-gray-400 cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 trails-dropdown"
|
|
548
|
-
>
|
|
549
|
-
<ChainImage chainId={selectedDestinationChain.id} size={24} />
|
|
550
|
-
<span className="ml-2 flex-1 text-left">
|
|
551
|
-
{selectedDestinationChain.name}
|
|
552
|
-
</span>
|
|
553
|
-
<ChevronDown
|
|
554
|
-
className={`h-5 w-5 text-gray-400 transition-transform ${
|
|
555
|
-
isChainDropdownOpen ? "transform rotate-180" : ""
|
|
556
|
-
}`}
|
|
557
|
-
/>
|
|
558
|
-
</button>
|
|
559
|
-
|
|
560
|
-
{isChainDropdownOpen && (
|
|
561
|
-
<div className="absolute z-10 w-full mt-1 border border-solid trails-border-radius-dropdown shadow-lg max-h-60 overflow-y-auto custom-scrollbar trails-dropdown">
|
|
562
|
-
{supportedChains.map((chain) => (
|
|
563
|
-
<button
|
|
564
|
-
key={chain.id}
|
|
565
|
-
type="button"
|
|
566
|
-
onClick={(e) => {
|
|
567
|
-
e.preventDefault()
|
|
568
|
-
e.stopPropagation()
|
|
569
|
-
setSelectedDestinationChain(chain)
|
|
570
|
-
setIsChainDropdownOpen(false)
|
|
571
|
-
}}
|
|
572
|
-
onMouseDown={(e) => {
|
|
573
|
-
e.preventDefault()
|
|
574
|
-
e.stopPropagation()
|
|
575
|
-
}}
|
|
576
|
-
className={`w-full flex items-center px-4 py-3 cursor-pointer trails-dropdown-item ${
|
|
577
|
-
selectedDestinationChain.id === chain.id
|
|
578
|
-
? "trails-dropdown-item-selected"
|
|
579
|
-
: "hover:trails-dropdown-item"
|
|
580
|
-
}`}
|
|
581
|
-
>
|
|
582
|
-
<ChainImage chainId={chain.id} size={24} />
|
|
583
|
-
<span className="ml-2">{chain.name}</span>
|
|
584
|
-
{selectedDestinationChain.id === chain.id && (
|
|
585
|
-
<span className="ml-auto text-gray-900 dark:text-white">
|
|
586
|
-
•
|
|
587
|
-
</span>
|
|
588
|
-
)}
|
|
589
|
-
</button>
|
|
590
|
-
))}
|
|
591
|
-
</div>
|
|
592
|
-
)}
|
|
593
|
-
</div>
|
|
594
|
-
</div>
|
|
595
|
-
)}
|
|
596
|
-
|
|
597
|
-
{/* Token Selection */}
|
|
598
|
-
{!toToken && (
|
|
599
|
-
<div className="mb-4">
|
|
600
|
-
<label
|
|
601
|
-
htmlFor="token"
|
|
602
|
-
className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300 text-left"
|
|
603
|
-
>
|
|
604
|
-
Receive Token
|
|
605
|
-
</label>
|
|
606
|
-
<div className="relative" ref={tokenDropdownRef}>
|
|
607
|
-
<button
|
|
608
|
-
type="button"
|
|
609
|
-
onClick={() => setIsTokenDropdownOpen(!isTokenDropdownOpen)}
|
|
610
|
-
className="w-full flex items-center px-4 py-3 border border-solid trails-border-radius-dropdown hover:border-gray-400 cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 trails-dropdown"
|
|
611
|
-
>
|
|
612
|
-
<div className="w-5 h-5 rounded-full flex items-center justify-center text-sm bg-gray-100 dark:bg-gray-700">
|
|
613
|
-
<TokenImage
|
|
614
|
-
symbol={selectedDestToken?.symbol}
|
|
615
|
-
imageUrl={selectedDestToken?.imageUrl}
|
|
616
|
-
size={24}
|
|
617
|
-
/>
|
|
618
|
-
</div>
|
|
619
|
-
<span className="ml-2 flex-1 text-left">
|
|
620
|
-
{selectedDestToken?.name} ({selectedDestToken?.symbol})
|
|
621
|
-
</span>
|
|
622
|
-
<ChevronDown
|
|
623
|
-
className={`h-5 w-5 text-gray-400 transition-transform ${
|
|
624
|
-
isTokenDropdownOpen ? "transform rotate-180" : ""
|
|
625
|
-
}`}
|
|
626
|
-
/>
|
|
627
|
-
</button>
|
|
628
|
-
|
|
629
|
-
{isTokenDropdownOpen && (
|
|
630
|
-
<div className="absolute z-10 w-full mt-1 border border-solid trails-border-radius-dropdown shadow-lg max-h-60 overflow-y-auto custom-scrollbar trails-dropdown">
|
|
631
|
-
{supportedTokens.map((token) => (
|
|
632
|
-
<button
|
|
633
|
-
key={`${token.contractAddress}-${token.chainId}`}
|
|
634
|
-
type="button"
|
|
635
|
-
onClick={() => {
|
|
636
|
-
setSelectedDestToken(token as TokenInfo)
|
|
637
|
-
setIsTokenDropdownOpen(false)
|
|
638
|
-
}}
|
|
639
|
-
className={`w-full flex items-center px-4 py-3 cursor-pointer trails-dropdown-item ${
|
|
640
|
-
selectedDestToken?.symbol === token.symbol
|
|
641
|
-
? "trails-dropdown-item-selected"
|
|
642
|
-
: "hover:trails-dropdown-item"
|
|
643
|
-
}`}
|
|
644
|
-
>
|
|
645
|
-
<TokenImage
|
|
646
|
-
symbol={token.symbol}
|
|
647
|
-
imageUrl={token.imageUrl}
|
|
648
|
-
size={24}
|
|
649
|
-
/>
|
|
650
|
-
<span className="ml-2">
|
|
651
|
-
{token.name} ({token.symbol})
|
|
652
|
-
</span>
|
|
653
|
-
{selectedDestToken?.symbol === token.symbol && (
|
|
654
|
-
<span className="ml-auto text-gray-900 dark:text-white">
|
|
655
|
-
•
|
|
656
|
-
</span>
|
|
657
|
-
)}
|
|
658
|
-
</button>
|
|
659
|
-
))}
|
|
660
|
-
</div>
|
|
661
|
-
)}
|
|
662
|
-
</div>
|
|
663
|
-
</div>
|
|
664
|
-
)}
|
|
665
|
-
|
|
666
|
-
{/* Recipient Input */}
|
|
667
|
-
{!toRecipient && (
|
|
668
|
-
<div className="mb-4">
|
|
669
|
-
<div className="flex justify-between items-center mb-1">
|
|
670
|
-
<div>
|
|
671
|
-
<label
|
|
672
|
-
htmlFor="recipient"
|
|
673
|
-
className="text-sm font-medium text-gray-700 dark:text-gray-300"
|
|
674
|
-
>
|
|
675
|
-
{toCalldata ? "Destination Address" : "Recipient Address"}
|
|
676
|
-
</label>
|
|
677
|
-
{recipient &&
|
|
678
|
-
isAddress(recipient) &&
|
|
679
|
-
recipient.toLowerCase() === account.address.toLowerCase() && (
|
|
680
|
-
<div className="text-xs mt-0.5 text-left text-gray-400">
|
|
681
|
-
Same as sender
|
|
682
|
-
</div>
|
|
683
|
-
)}
|
|
684
|
-
</div>
|
|
685
|
-
<div className="h-7 flex items-center">
|
|
686
|
-
{recipient !== account.address ? (
|
|
687
|
-
<button
|
|
688
|
-
type="button"
|
|
689
|
-
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
690
|
-
event.preventDefault()
|
|
691
|
-
setRecipientInput(account.address)
|
|
692
|
-
setRecipient(account.address)
|
|
693
|
-
}}
|
|
694
|
-
className={`px-2 py-1 text-xs cursor-pointer trails-border-radius-button transition-colors bg-blue-500 hover:bg-blue-600 text-white`}
|
|
695
|
-
>
|
|
696
|
-
Use Account
|
|
697
|
-
</button>
|
|
698
|
-
) : null}
|
|
699
|
-
</div>
|
|
700
|
-
</div>
|
|
701
|
-
<input
|
|
702
|
-
id="recipient"
|
|
703
|
-
type="text"
|
|
704
|
-
value={recipientInput}
|
|
705
|
-
onChange={handleRecipientInputChange}
|
|
706
|
-
placeholder="0x... or name.eth"
|
|
707
|
-
className="block w-full px-4 py-3 border border-solid trails-border-radius-input focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-mono text-sm trails-input"
|
|
708
|
-
/>
|
|
709
|
-
{ensAddress && <p className="text-sm text-gray-400">{recipient}</p>}
|
|
710
|
-
</div>
|
|
711
|
-
)}
|
|
712
|
-
|
|
713
|
-
{/* Receive Section */}
|
|
714
|
-
<div className="space-y-1">
|
|
715
|
-
<div className="flex items-center justify-between">
|
|
716
|
-
<div
|
|
717
|
-
className={`text-lg font-semibold text-left ${"text-gray-900 dark:text-white"}`}
|
|
718
|
-
>
|
|
719
|
-
Receive
|
|
720
|
-
</div>
|
|
721
|
-
<button
|
|
722
|
-
type="button"
|
|
723
|
-
onClick={handleRefetchQuote}
|
|
724
|
-
disabled={
|
|
725
|
-
isLoadingQuote ||
|
|
726
|
-
!tokenAmountForBackend ||
|
|
727
|
-
!selectedDestToken ||
|
|
728
|
-
!selectedDestinationChain ||
|
|
729
|
-
!isValidRecipient
|
|
730
|
-
}
|
|
731
|
-
className={`p-2 rounded-md transition-colors cursor-pointer ${
|
|
732
|
-
isLoadingQuote ||
|
|
733
|
-
!tokenAmountForBackend ||
|
|
734
|
-
!selectedDestToken ||
|
|
735
|
-
!selectedDestinationChain ||
|
|
736
|
-
!isValidRecipient
|
|
737
|
-
? "opacity-50 cursor-not-allowed text-gray-400 dark:text-gray-500"
|
|
738
|
-
: "text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200"
|
|
739
|
-
}`}
|
|
740
|
-
title="Refetch quote"
|
|
741
|
-
>
|
|
742
|
-
<RefreshCcw
|
|
743
|
-
className={`h-4 w-4 ${isLoadingQuote ? "animate-spin" : ""}`}
|
|
744
|
-
/>
|
|
745
|
-
</button>
|
|
746
|
-
</div>
|
|
747
|
-
|
|
748
|
-
<div className="p-2">
|
|
749
|
-
<div className="flex items-center space-x-3">
|
|
750
|
-
<a
|
|
751
|
-
href={getExplorerUrlForAddress({
|
|
752
|
-
address: destinationTokenAddress || "",
|
|
753
|
-
chainId: selectedDestinationChain.id,
|
|
754
|
-
})}
|
|
755
|
-
target="_blank"
|
|
756
|
-
rel="noopener noreferrer"
|
|
757
|
-
className="cursor-pointer"
|
|
758
|
-
>
|
|
759
|
-
<TokenImage
|
|
760
|
-
symbol={selectedDestToken?.symbol}
|
|
761
|
-
imageUrl={selectedDestToken?.imageUrl}
|
|
762
|
-
chainId={selectedDestinationChain.id}
|
|
763
|
-
size={32}
|
|
764
|
-
/>
|
|
765
|
-
</a>
|
|
766
|
-
<div>
|
|
767
|
-
<div className="flex items-center space-x-2">
|
|
768
|
-
<div
|
|
769
|
-
className={`text-lg font-semibold ${"text-gray-900 dark:text-white"} ${isLoadingQuote ? "animate-pulse" : ""}`}
|
|
770
|
-
>
|
|
771
|
-
{toAmountDisplay} {selectedDestToken?.symbol}
|
|
772
|
-
</div>
|
|
773
|
-
{isLoadingQuote && (
|
|
774
|
-
<div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 border-blue-500 dark:border-blue-400" />
|
|
775
|
-
)}
|
|
776
|
-
</div>
|
|
777
|
-
<div
|
|
778
|
-
className={`text-xs text-left ${"text-gray-500 dark:text-gray-400"} ${isLoadingQuote ? "animate-pulse" : ""}`}
|
|
779
|
-
>
|
|
780
|
-
≈ {receiveUsdValue}{" "}
|
|
781
|
-
{selectedDestinationChain
|
|
782
|
-
? `on ${selectedDestinationChain.name}`
|
|
783
|
-
: ""}
|
|
784
|
-
</div>
|
|
785
|
-
</div>
|
|
786
|
-
</div>
|
|
787
|
-
</div>
|
|
788
|
-
|
|
789
|
-
{/* Show recipient address if different from sender */}
|
|
790
|
-
{recipient &&
|
|
791
|
-
recipient.toLowerCase() !== account.address.toLowerCase() && (
|
|
792
|
-
<div className="px-2 pb-1">
|
|
793
|
-
<div className="text-xs text-left text-gray-500 dark:text-gray-400">
|
|
794
|
-
{isRecipientContract ? "Destination Contract" : "Recipient"}:{" "}
|
|
795
|
-
<TruncatedAddress
|
|
796
|
-
address={recipient}
|
|
797
|
-
chainId={selectedDestinationChain.id}
|
|
798
|
-
/>
|
|
799
|
-
</div>
|
|
800
|
-
</div>
|
|
801
|
-
)}
|
|
802
|
-
</div>
|
|
803
|
-
|
|
804
|
-
{/* Custom Calldata */}
|
|
805
|
-
{toCalldata && (
|
|
806
|
-
<div className="px-2 pb-1">
|
|
807
|
-
<p className="text-[10px] text-left text-gray-500 dark:text-gray-400">
|
|
808
|
-
This transaction includes custom calldata for contract interaction
|
|
809
|
-
at the destination address
|
|
810
|
-
</p>
|
|
811
|
-
</div>
|
|
812
|
-
)}
|
|
813
|
-
|
|
814
|
-
{/* Refund Address Input */}
|
|
815
|
-
{/* <RefundAddressInput
|
|
816
|
-
account={account}
|
|
817
|
-
isOpen={isRefundAddressOpen}
|
|
818
|
-
onToggle={() => setIsRefundAddressOpen(!isRefundAddressOpen)}
|
|
819
|
-
refundAddress={refundAddress}
|
|
820
|
-
onRefundAddressChange={setRefundAddress}
|
|
821
|
-
chainId={selectedDestinationChain.id}
|
|
822
|
-
/> */}
|
|
823
|
-
|
|
824
|
-
{/* Warning Messages - Show only one at a time */}
|
|
825
|
-
{isSameTokenWithoutCustomCalldata ? (
|
|
826
|
-
<ErrorDisplay
|
|
827
|
-
errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different origin token."
|
|
828
|
-
severity="error"
|
|
829
|
-
/>
|
|
830
|
-
) : (
|
|
831
|
-
<ErrorDisplay
|
|
832
|
-
errorPrettified={quoteErrorPrettified}
|
|
833
|
-
error={quoteError}
|
|
834
|
-
severity="warning"
|
|
835
|
-
/>
|
|
836
|
-
)}
|
|
837
|
-
{prepareSendQuote?.noSufficientBalance ? (
|
|
838
|
-
<div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
|
|
839
|
-
<div className="flex items-center space-x-2">
|
|
840
|
-
<svg
|
|
841
|
-
className="w-4 h-4 text-amber-500 flex-shrink-0"
|
|
842
|
-
fill="none"
|
|
843
|
-
stroke="currentColor"
|
|
844
|
-
viewBox="0 0 24 24"
|
|
845
|
-
aria-hidden="true"
|
|
846
|
-
>
|
|
847
|
-
<path
|
|
848
|
-
strokeLinecap="round"
|
|
849
|
-
strokeLinejoin="round"
|
|
850
|
-
strokeWidth={2}
|
|
851
|
-
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"
|
|
852
|
-
/>
|
|
853
|
-
</svg>
|
|
854
|
-
<p className="text-sm text-amber-600 dark:text-amber-400">
|
|
855
|
-
Insufficient balance to complete this transaction
|
|
856
|
-
</p>
|
|
857
|
-
</div>
|
|
858
|
-
</div>
|
|
859
|
-
) : null}
|
|
860
|
-
|
|
861
|
-
{/* Continue Button */}
|
|
862
|
-
<button
|
|
863
|
-
type="submit"
|
|
864
|
-
disabled={
|
|
865
|
-
!tokenAmountForBackend ||
|
|
866
|
-
parseFloat(tokenAmountForBackend) <= 0 ||
|
|
867
|
-
isSubmitting ||
|
|
868
|
-
isLoadingQuote ||
|
|
869
|
-
!isValidRecipient ||
|
|
870
|
-
buttonText === "No quote available" ||
|
|
871
|
-
buttonText === "Getting quote..." ||
|
|
872
|
-
prepareSendQuote?.noSufficientBalance ||
|
|
873
|
-
isSameTokenWithoutCustomCalldata
|
|
874
|
-
}
|
|
875
|
-
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`}
|
|
876
|
-
>
|
|
877
|
-
{isSubmitting ? (
|
|
878
|
-
<div className="flex items-center justify-center">
|
|
879
|
-
<Loader2 className="w-5 h-5 animate-spin mr-2 text-white dark:text-gray-400" />
|
|
880
|
-
<span>{buttonText}</span>
|
|
881
|
-
</div>
|
|
882
|
-
) : isSameTokenWithoutCustomCalldata ? (
|
|
883
|
-
"Select Different Tokens"
|
|
884
|
-
) : prepareSendQuote?.noSufficientBalance ? (
|
|
885
|
-
"Insufficient Balance"
|
|
886
|
-
) : !tokenAmountForBackend ||
|
|
887
|
-
parseFloat(tokenAmountForBackend) <= 0 ? (
|
|
888
|
-
"Enter an amount"
|
|
889
|
-
) : (
|
|
890
|
-
buttonText
|
|
891
|
-
)}
|
|
892
|
-
</button>
|
|
893
|
-
|
|
894
|
-
{/* Quote Details */}
|
|
895
|
-
{prepareSendQuote && (
|
|
896
|
-
<div className="space-y-2">
|
|
897
|
-
<QuoteDetails quote={prepareSendQuote} showContent={true} />
|
|
898
|
-
</div>
|
|
899
|
-
)}
|
|
900
|
-
</form>
|
|
901
|
-
</div>
|
|
902
|
-
)
|
|
903
|
-
}
|