0xtrails 0.1.13 → 0.2.0
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.map +1 -1
- package/dist/analytics.d.ts +11 -2
- package/dist/analytics.d.ts.map +1 -1
- package/dist/apiClient.d.ts +1 -1
- package/dist/apiClient.d.ts.map +1 -1
- package/dist/{proxyCaller.d.ts → balanceInjector.d.ts} +5 -4
- package/dist/balanceInjector.d.ts.map +1 -0
- package/dist/{ccip-D3gTQONK.js → ccip-D6ToCrWc.js} +12 -12
- package/dist/cctp.d.ts.map +1 -1
- package/dist/cctpqueue.d.ts +3 -3
- package/dist/cctpqueue.d.ts.map +1 -1
- package/dist/chains.d.ts.map +1 -1
- package/dist/config.d.ts +17 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +5 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/contractUtils.d.ts +2 -0
- package/dist/contractUtils.d.ts.map +1 -1
- package/dist/customChains.d.ts +24 -0
- package/dist/customChains.d.ts.map +1 -0
- package/dist/{index-CnUM7lKf.js → index-BqgeTLL8.js} +34072 -30146
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +411 -400
- package/dist/intentEntrypoint.d.ts +96 -0
- package/dist/intentEntrypoint.d.ts.map +1 -0
- package/dist/intents.d.ts +5 -3
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxnMonitor.d.ts.map +1 -1
- package/dist/morpho.d.ts.map +1 -1
- package/dist/pools.d.ts +3 -1
- package/dist/pools.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +8 -2
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +1 -1
- package/dist/prices.d.ts.map +1 -1
- package/dist/relaySdk.d.ts.map +1 -1
- package/dist/relayer.d.ts.map +1 -1
- package/dist/toast.d.ts +9 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/tokenBalances.d.ts +6 -2
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/trails.d.ts +6 -5
- package/dist/trails.d.ts.map +1 -1
- package/dist/trailsClient.d.ts +12 -0
- package/dist/trailsClient.d.ts.map +1 -0
- package/dist/transactions.d.ts +8 -0
- 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/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/AccountSettings.d.ts +7 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -0
- package/dist/widget/components/ChainList.d.ts +0 -1
- package/dist/widget/components/ChainList.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +46 -0
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
- package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts +9 -0
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
- package/dist/widget/components/DebugMenu.d.ts.map +1 -1
- package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
- package/dist/widget/components/DebugToast.d.ts +3 -0
- package/dist/widget/components/DebugToast.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/EarnPools.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts +44 -0
- package/dist/widget/components/Fund.d.ts.map +1 -0
- package/dist/widget/components/Identicon.d.ts +9 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -0
- package/dist/widget/components/Pay.d.ts +46 -0
- package/dist/widget/components/Pay.d.ts.map +1 -0
- package/dist/widget/components/Receive.d.ts.map +1 -1
- package/dist/widget/components/RecentTokens.d.ts.map +1 -1
- package/dist/widget/components/Recipients.d.ts +9 -0
- package/dist/widget/components/Recipients.d.ts.map +1 -0
- package/dist/widget/components/RefundWarning.d.ts +9 -0
- package/dist/widget/components/RefundWarning.d.ts.map +1 -0
- package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts +1 -5
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/ThemeSyncer.d.ts +6 -0
- package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
- package/dist/widget/components/Toast.d.ts +24 -0
- package/dist/widget/components/Toast.d.ts.map +1 -0
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
- package/dist/widget/components/TruncatedAddress.d.ts +2 -0
- package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
- package/dist/widget/components/UserPreferences.d.ts +7 -0
- package/dist/widget/components/UserPreferences.d.ts.map +1 -0
- package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
- package/dist/widget/hooks/useBalanceVisible.d.ts.map +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/useDebugScreens.d.ts +1 -1
- package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
- package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts +34 -0
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
- package/dist/widget/hooks/useRecipients.d.ts +17 -0
- package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
- package/dist/widget/hooks/useSendForm.d.ts +2 -0
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
- package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
- package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
- package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
- package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useTheme.d.ts +14 -0
- package/dist/widget/hooks/useTheme.d.ts.map +1 -0
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +9 -0
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +29 -28
- package/src/aave.ts +6 -1
- package/src/analytics.ts +103 -53
- package/src/apiClient.ts +1 -1
- package/src/{proxyCaller.ts → balanceInjector.ts} +22 -17
- package/src/cctp.ts +6 -2
- package/src/cctpqueue.ts +7 -7
- package/src/chains.ts +8 -0
- package/src/config.ts +40 -9
- package/src/constants.ts +11 -8
- package/src/contractUtils.ts +33 -2
- package/src/customChains.ts +24 -0
- package/src/index.ts +11 -1
- package/src/intentEntrypoint.ts +253 -0
- package/src/intents.ts +87 -54
- package/src/metaTxnMonitor.ts +1 -0
- package/src/morpho.ts +13 -2
- package/src/pools.ts +68 -86
- package/src/prepareSend.ts +437 -207
- package/src/prices.ts +51 -7
- package/src/relaySdk.ts +6 -4
- package/src/relayer.ts +2 -0
- package/src/toast.ts +110 -0
- package/src/tokenBalances.ts +112 -20
- package/src/tokens.ts +70 -7
- package/src/trails.ts +80 -77
- package/src/trailsClient.ts +45 -0
- package/src/transactions.ts +27 -35
- package/src/umd.tsx +1 -1
- package/src/wallets.ts +2 -1
- package/src/widget/assets/sequence-logo.svg +15 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +18 -159
- package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
- package/src/widget/components/AccountSettings.tsx +96 -0
- package/src/widget/components/ChainFilterDropdown.tsx +1 -1
- package/src/widget/components/ChainList.tsx +10 -20
- package/src/widget/components/ClassicSwap.tsx +923 -0
- package/src/widget/components/ConfigDisplay.tsx +8 -5
- package/src/widget/components/ConnectedWallets.tsx +260 -0
- package/src/widget/components/DebugMenu.tsx +2 -0
- package/src/widget/components/DebugScreensList.tsx +3 -0
- package/src/widget/components/DebugToast.tsx +63 -0
- package/src/widget/components/Earn.tsx +108 -116
- package/src/widget/components/EarnPools.tsx +2 -4
- package/src/widget/components/EarnPoolsFilters.tsx +6 -6
- package/src/widget/components/Fund.tsx +1245 -0
- package/src/widget/components/FundMethods.tsx +1 -1
- package/src/widget/components/FundSendForm.tsx +1 -1
- package/src/widget/components/Identicon.tsx +158 -0
- package/src/widget/components/Pay.tsx +1088 -0
- package/src/widget/components/PaySendForm.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +1 -1
- package/src/widget/components/Receipt.tsx +1 -1
- package/src/widget/components/Receive.tsx +4 -2
- package/src/widget/components/RecentTokens.tsx +2 -1
- package/src/widget/components/Recipients.tsx +448 -0
- package/src/widget/components/RefundWarning.tsx +61 -0
- package/src/widget/components/ScreenHeader.tsx +1 -1
- package/src/widget/components/SimpleSwap.tsx +74 -58
- package/src/widget/components/Swap.tsx +35 -853
- package/src/widget/components/SwapSettings.tsx +5 -11
- package/src/widget/components/ThemeProvider.tsx +32 -0
- package/src/widget/components/ThemeSyncer.tsx +47 -0
- package/src/widget/components/Toast.tsx +315 -0
- package/src/widget/components/TokenList.tsx +2 -34
- package/src/widget/components/TokenSelector.tsx +3 -3
- package/src/widget/components/TransactionDetails.tsx +153 -13
- package/src/widget/components/TruncatedAddress.tsx +5 -1
- package/src/widget/components/UserPreferences.tsx +156 -0
- package/src/widget/components/WalletList.tsx +1 -1
- package/src/widget/hooks/useBalanceVisible.tsx +40 -2
- package/src/widget/hooks/useCheckout.ts +13 -0
- package/src/widget/hooks/useCurrentScreen.tsx +3 -0
- package/src/widget/hooks/useDebugScreens.ts +12 -2
- package/src/widget/hooks/useDefaultTokenSelection.tsx +475 -0
- package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
- package/src/widget/hooks/usePayMessage.tsx +370 -0
- package/src/widget/hooks/useRecipients.ts +168 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
- package/src/widget/hooks/useSendForm.ts +179 -26
- package/src/widget/hooks/useSwapAmount.tsx +50 -0
- package/src/widget/hooks/useSwapSettings.tsx +100 -0
- package/src/widget/hooks/useTargetAmount.ts +23 -0
- package/src/widget/hooks/useTheme.tsx +80 -0
- package/src/widget/hooks/useTokenList.ts +20 -11
- package/src/widget/index.css +45 -21
- package/src/widget/widget.tsx +164 -68
- package/dist/address.d.ts +0 -2
- package/dist/address.d.ts.map +0 -1
- package/dist/proxyCaller.d.ts.map +0 -1
- package/src/address.ts +0 -6
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChevronRight,
|
|
3
|
+
Search,
|
|
4
|
+
Loader2,
|
|
5
|
+
ChevronDown,
|
|
6
|
+
ArrowDown,
|
|
7
|
+
} from "lucide-react"
|
|
8
|
+
import { useEffect, useState, useMemo, useRef, useCallback } from "react"
|
|
9
|
+
import type React from "react"
|
|
10
|
+
import type { Account, WalletClient } from "viem"
|
|
11
|
+
import type { TransactionState } from "../../transactions.js"
|
|
12
|
+
import type { OnCompleteProps } from "../hooks/useSendForm.js"
|
|
13
|
+
import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
|
|
14
|
+
import { useSendForm } from "../hooks/useSendForm.js"
|
|
15
|
+
import { TradeType } from "../../prepareSend.js"
|
|
16
|
+
import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
|
|
17
|
+
import { useDestinationSelectedToken } from "../hooks/useDestinationSelectedToken.js"
|
|
18
|
+
import { useSelectedRecipient } from "../hooks/useSelectedRecipient.js"
|
|
19
|
+
import { useSwapAmount } from "../hooks/useSwapAmount.js"
|
|
20
|
+
import { useMode } from "../hooks/useMode.js"
|
|
21
|
+
import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
|
|
22
|
+
import { useCurrentScreen } from "../hooks/useCurrentScreen.js"
|
|
23
|
+
import { useDefaultTokenSelection } from "../hooks/useDefaultTokenSelection.js" // Context version
|
|
24
|
+
import { TokenImage } from "./TokenImage.js"
|
|
25
|
+
import { Identicon } from "./Identicon.js"
|
|
26
|
+
import { TokenSelector } from "./TokenSelector.js"
|
|
27
|
+
import { ScreenHeader } from "./ScreenHeader.js"
|
|
28
|
+
import { ChainList } from "./ChainList.js"
|
|
29
|
+
import { QuoteDetails } from "./QuoteDetails.js"
|
|
30
|
+
import { ErrorDisplay } from "./ErrorDisplay.js"
|
|
31
|
+
import { getChainInfo } from "../../chains.js"
|
|
32
|
+
import { formatUsdAmountDisplay } from "../../tokenBalances.js"
|
|
33
|
+
import { MINIMUM_USD_AMOUNT_FOR_SWAP } from "../../constants.js"
|
|
34
|
+
import { truncateAddress } from "../../utils.js"
|
|
35
|
+
import type { PrepareSendQuote } from "../../prepareSend.js"
|
|
36
|
+
import type { SupportedToken } from "../../tokens.js"
|
|
37
|
+
import { logger } from "../../logger.js"
|
|
38
|
+
import { RefundWarning } from "./RefundWarning.js"
|
|
39
|
+
|
|
40
|
+
interface FundProps {
|
|
41
|
+
onBack?: () => void
|
|
42
|
+
account: Account
|
|
43
|
+
walletClient: WalletClient
|
|
44
|
+
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
45
|
+
onError: (error: Error | string | null) => void
|
|
46
|
+
onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
|
|
47
|
+
onConfirm: () => void
|
|
48
|
+
onComplete: (result: OnCompleteProps) => void
|
|
49
|
+
onSend: (amount: string, recipient: string) => void
|
|
50
|
+
paymasterUrls?: Array<{ chainId: number; url: string }>
|
|
51
|
+
gasless?: boolean
|
|
52
|
+
setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
|
|
53
|
+
quoteProvider?: string
|
|
54
|
+
fundMethod?: string
|
|
55
|
+
onNavigateToMeshConnect?: (
|
|
56
|
+
props: {
|
|
57
|
+
toTokenSymbol: string
|
|
58
|
+
toTokenAmount: string
|
|
59
|
+
toChainId: number
|
|
60
|
+
toRecipientAddress: string
|
|
61
|
+
},
|
|
62
|
+
quote?: PrepareSendQuote | null,
|
|
63
|
+
) => void
|
|
64
|
+
checkoutOnHandlers?: CheckoutOnHandlers
|
|
65
|
+
recentTokens?: SupportedToken[]
|
|
66
|
+
onRecentTokenSelect?: (token: SupportedToken) => void
|
|
67
|
+
onTrackToken?: (token: any) => void
|
|
68
|
+
toRecipient?: string
|
|
69
|
+
toAmount?: string
|
|
70
|
+
toChainId?: number
|
|
71
|
+
toToken?: string
|
|
72
|
+
toCalldata?: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const Fund: React.FC<FundProps> = ({
|
|
76
|
+
onBack,
|
|
77
|
+
account,
|
|
78
|
+
walletClient,
|
|
79
|
+
onTransactionStateChange,
|
|
80
|
+
onError,
|
|
81
|
+
onWaitingForWalletConfirm,
|
|
82
|
+
onConfirm,
|
|
83
|
+
onComplete,
|
|
84
|
+
onSend,
|
|
85
|
+
paymasterUrls,
|
|
86
|
+
gasless,
|
|
87
|
+
setWalletConfirmRetryHandler,
|
|
88
|
+
quoteProvider,
|
|
89
|
+
fundMethod,
|
|
90
|
+
onNavigateToMeshConnect,
|
|
91
|
+
checkoutOnHandlers,
|
|
92
|
+
recentTokens,
|
|
93
|
+
onRecentTokenSelect,
|
|
94
|
+
onTrackToken,
|
|
95
|
+
toRecipient,
|
|
96
|
+
toChainId,
|
|
97
|
+
toToken,
|
|
98
|
+
toCalldata,
|
|
99
|
+
}) => {
|
|
100
|
+
const { mode } = useMode()
|
|
101
|
+
const { isBalanceVisible } = useBalanceVisible()
|
|
102
|
+
const { selectedToken: originToken, setSelectedToken: setOriginToken } =
|
|
103
|
+
useOriginSelectedToken()
|
|
104
|
+
const {
|
|
105
|
+
selectedToken: globalDestinationToken,
|
|
106
|
+
setSelectedToken: setDestinationToken,
|
|
107
|
+
} = useDestinationSelectedToken()
|
|
108
|
+
const { selectedRecipient, setSelectedRecipient } = useSelectedRecipient()
|
|
109
|
+
const { amount: globalAmount, setAmount: setGlobalAmount } = useSwapAmount()
|
|
110
|
+
const { setCurrentScreen } = useCurrentScreen()
|
|
111
|
+
|
|
112
|
+
// Use new default token selection hook
|
|
113
|
+
const {
|
|
114
|
+
defaultOriginToken,
|
|
115
|
+
defaultDestinationToken,
|
|
116
|
+
isLoading: isLoadingDefaults,
|
|
117
|
+
} = useDefaultTokenSelection()
|
|
118
|
+
|
|
119
|
+
// Local state for fund-specific functionality
|
|
120
|
+
const [isInputTypeUsd, setIsInputTypeUsd] = useState(false)
|
|
121
|
+
const [tokenAmountForBackend, setTokenAmountForBackend] = useState("")
|
|
122
|
+
const [inputDisplayValue, setInputDisplayValue] = useState("")
|
|
123
|
+
const [showOriginTokenSelector, setShowOriginTokenSelector] = useState(false)
|
|
124
|
+
const [showDestinationTokenSelector, setShowDestinationTokenSelector] =
|
|
125
|
+
useState(false)
|
|
126
|
+
const [showOriginChainList, setShowOriginChainList] = useState(false)
|
|
127
|
+
const [showDestinationChainList, setShowDestinationChainList] =
|
|
128
|
+
useState(false)
|
|
129
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
130
|
+
|
|
131
|
+
// Use useSendForm for quote functionality
|
|
132
|
+
const {
|
|
133
|
+
amountUsdDisplay,
|
|
134
|
+
balanceFormatted,
|
|
135
|
+
isLoadingQuote,
|
|
136
|
+
prepareSendQuote,
|
|
137
|
+
setAmount: setSendFormAmount,
|
|
138
|
+
handleSubmit,
|
|
139
|
+
isSubmitting,
|
|
140
|
+
buttonText,
|
|
141
|
+
isValidRecipient,
|
|
142
|
+
selectedDestToken,
|
|
143
|
+
setSelectedDestToken,
|
|
144
|
+
setSelectedDestinationChain,
|
|
145
|
+
quoteError,
|
|
146
|
+
quoteErrorPrettified,
|
|
147
|
+
isSameTokenWithoutCustomCalldata,
|
|
148
|
+
destinationTokenAddress,
|
|
149
|
+
isValidCustomToken,
|
|
150
|
+
isSenderContractOnOrigin,
|
|
151
|
+
isSenderContractOnDestination,
|
|
152
|
+
} = useSendForm({
|
|
153
|
+
account,
|
|
154
|
+
toAmount: undefined, // Don't pass toAmount for fund form - user enters input amount
|
|
155
|
+
toRecipient: selectedRecipient || account.address,
|
|
156
|
+
toChainId,
|
|
157
|
+
toToken,
|
|
158
|
+
toCalldata,
|
|
159
|
+
walletClient,
|
|
160
|
+
onTransactionStateChange,
|
|
161
|
+
onError,
|
|
162
|
+
onWaitingForWalletConfirm,
|
|
163
|
+
paymasterUrls,
|
|
164
|
+
gasless,
|
|
165
|
+
onConfirm,
|
|
166
|
+
onComplete,
|
|
167
|
+
onSend,
|
|
168
|
+
selectedToken: originToken as any,
|
|
169
|
+
setWalletConfirmRetryHandler,
|
|
170
|
+
tradeType: TradeType.EXACT_INPUT,
|
|
171
|
+
quoteProvider,
|
|
172
|
+
fundMethod,
|
|
173
|
+
mode,
|
|
174
|
+
onNavigateToMeshConnect,
|
|
175
|
+
checkoutOnHandlers,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Auto-select origin and destination tokens using new hook
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (!originToken && !isLoadingDefaults && defaultOriginToken) {
|
|
181
|
+
logger.console.log(
|
|
182
|
+
"[trails-sdk] Auto-selecting origin token:",
|
|
183
|
+
defaultOriginToken,
|
|
184
|
+
)
|
|
185
|
+
setOriginToken(defaultOriginToken as any)
|
|
186
|
+
}
|
|
187
|
+
}, [originToken, isLoadingDefaults, defaultOriginToken, setOriginToken])
|
|
188
|
+
|
|
189
|
+
// Auto-select destination token using new hook
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (
|
|
192
|
+
!globalDestinationToken &&
|
|
193
|
+
!toToken &&
|
|
194
|
+
originToken &&
|
|
195
|
+
!isLoadingDefaults &&
|
|
196
|
+
defaultDestinationToken
|
|
197
|
+
) {
|
|
198
|
+
logger.console.log(
|
|
199
|
+
"[trails-sdk] Auto-selecting destination token:",
|
|
200
|
+
defaultDestinationToken,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const decimals =
|
|
204
|
+
defaultDestinationToken.contractInfo?.decimals ??
|
|
205
|
+
(defaultDestinationToken as any)?.decimals
|
|
206
|
+
|
|
207
|
+
setDestinationToken(defaultDestinationToken as any)
|
|
208
|
+
setSelectedDestToken({
|
|
209
|
+
symbol: defaultDestinationToken.symbol,
|
|
210
|
+
name: defaultDestinationToken.name,
|
|
211
|
+
imageUrl: defaultDestinationToken.imageUrl,
|
|
212
|
+
decimals,
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// Also set the destination chain
|
|
216
|
+
if (setSelectedDestinationChain) {
|
|
217
|
+
const chainInfo = getChainInfo(defaultDestinationToken.chainId)
|
|
218
|
+
if (chainInfo) {
|
|
219
|
+
setSelectedDestinationChain(chainInfo)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}, [
|
|
224
|
+
originToken,
|
|
225
|
+
globalDestinationToken,
|
|
226
|
+
toToken,
|
|
227
|
+
isLoadingDefaults,
|
|
228
|
+
defaultDestinationToken,
|
|
229
|
+
setDestinationToken,
|
|
230
|
+
setSelectedDestToken,
|
|
231
|
+
setSelectedDestinationChain,
|
|
232
|
+
])
|
|
233
|
+
|
|
234
|
+
// Initialize selected recipient from toRecipient prop or default to connected wallet
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (toRecipient && !selectedRecipient) {
|
|
237
|
+
setSelectedRecipient(toRecipient)
|
|
238
|
+
} else if (!selectedRecipient && account?.address) {
|
|
239
|
+
// Default to connected wallet address if no recipient is set
|
|
240
|
+
setSelectedRecipient(account.address)
|
|
241
|
+
}
|
|
242
|
+
}, [toRecipient, selectedRecipient, setSelectedRecipient, account?.address])
|
|
243
|
+
|
|
244
|
+
// Sync global destination token with useSendForm
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
if (globalDestinationToken && selectedDestToken && !toToken) {
|
|
247
|
+
const decimals =
|
|
248
|
+
globalDestinationToken.contractInfo?.decimals ??
|
|
249
|
+
(globalDestinationToken as any)?.decimals
|
|
250
|
+
|
|
251
|
+
// Convert global token format to useSendForm format
|
|
252
|
+
const formattedToken = {
|
|
253
|
+
symbol: globalDestinationToken.symbol,
|
|
254
|
+
name: globalDestinationToken.name,
|
|
255
|
+
imageUrl: globalDestinationToken.imageUrl,
|
|
256
|
+
decimals,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Only update if the tokens are different (to avoid infinite loops)
|
|
260
|
+
if (selectedDestToken.symbol !== formattedToken.symbol) {
|
|
261
|
+
logger.console.log(
|
|
262
|
+
"[trails-sdk] Restoring destination token from global state:",
|
|
263
|
+
formattedToken,
|
|
264
|
+
)
|
|
265
|
+
setSelectedDestToken(formattedToken)
|
|
266
|
+
|
|
267
|
+
// Also update destination chain to match the selected token's chain
|
|
268
|
+
if (setSelectedDestinationChain && globalDestinationToken.chainId) {
|
|
269
|
+
const chainInfo = getChainInfo(globalDestinationToken.chainId)
|
|
270
|
+
if (chainInfo) {
|
|
271
|
+
setSelectedDestinationChain(chainInfo)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}, [
|
|
277
|
+
globalDestinationToken,
|
|
278
|
+
selectedDestToken,
|
|
279
|
+
setSelectedDestToken,
|
|
280
|
+
setSelectedDestinationChain,
|
|
281
|
+
toToken,
|
|
282
|
+
])
|
|
283
|
+
|
|
284
|
+
// Force restore global destination token on mount (runs after useSendForm initialization)
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
if (globalDestinationToken && !toToken) {
|
|
287
|
+
const decimals =
|
|
288
|
+
globalDestinationToken.contractInfo?.decimals ??
|
|
289
|
+
(globalDestinationToken as any)?.decimals
|
|
290
|
+
|
|
291
|
+
const timeoutId = setTimeout(() => {
|
|
292
|
+
const formattedToken = {
|
|
293
|
+
symbol: globalDestinationToken.symbol,
|
|
294
|
+
name: globalDestinationToken.name,
|
|
295
|
+
imageUrl: globalDestinationToken.imageUrl,
|
|
296
|
+
decimals,
|
|
297
|
+
}
|
|
298
|
+
logger.console.log(
|
|
299
|
+
"[trails-sdk] Force restoring destination token on mount:",
|
|
300
|
+
formattedToken,
|
|
301
|
+
)
|
|
302
|
+
setSelectedDestToken(formattedToken)
|
|
303
|
+
|
|
304
|
+
if (setSelectedDestinationChain && globalDestinationToken.chainId) {
|
|
305
|
+
const chainInfo = getChainInfo(globalDestinationToken.chainId)
|
|
306
|
+
if (chainInfo) {
|
|
307
|
+
setSelectedDestinationChain(chainInfo)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}, 50)
|
|
311
|
+
|
|
312
|
+
return () => clearTimeout(timeoutId)
|
|
313
|
+
}
|
|
314
|
+
}, [
|
|
315
|
+
globalDestinationToken,
|
|
316
|
+
setSelectedDestToken,
|
|
317
|
+
setSelectedDestinationChain,
|
|
318
|
+
toToken,
|
|
319
|
+
])
|
|
320
|
+
|
|
321
|
+
// Initialize local amount states from global amount
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
if (globalAmount && !tokenAmountForBackend && !inputDisplayValue) {
|
|
324
|
+
logger.console.log(
|
|
325
|
+
"[trails-sdk] Restoring amount from global state:",
|
|
326
|
+
globalAmount,
|
|
327
|
+
)
|
|
328
|
+
setTokenAmountForBackend(globalAmount)
|
|
329
|
+
setInputDisplayValue(globalAmount)
|
|
330
|
+
setSendFormAmount(globalAmount)
|
|
331
|
+
}
|
|
332
|
+
}, [
|
|
333
|
+
globalAmount,
|
|
334
|
+
tokenAmountForBackend,
|
|
335
|
+
inputDisplayValue,
|
|
336
|
+
setSendFormAmount,
|
|
337
|
+
])
|
|
338
|
+
|
|
339
|
+
// Debug logging for component mount
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
logger.console.log("[trails-sdk] Fund component mounted/updated", {
|
|
342
|
+
originToken: originToken?.symbol,
|
|
343
|
+
originChainId: originToken?.chainId,
|
|
344
|
+
globalDestinationToken: globalDestinationToken?.symbol,
|
|
345
|
+
globalDestinationChainId: globalDestinationToken?.chainId,
|
|
346
|
+
selectedDestToken: selectedDestToken?.symbol,
|
|
347
|
+
toChainId,
|
|
348
|
+
globalAmount,
|
|
349
|
+
tokenAmountForBackend,
|
|
350
|
+
})
|
|
351
|
+
}, [
|
|
352
|
+
originToken,
|
|
353
|
+
globalDestinationToken,
|
|
354
|
+
selectedDestToken,
|
|
355
|
+
globalAmount,
|
|
356
|
+
tokenAmountForBackend,
|
|
357
|
+
toChainId,
|
|
358
|
+
])
|
|
359
|
+
|
|
360
|
+
// Auto-focus input field on component mount
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
const timer = setTimeout(() => {
|
|
363
|
+
if (inputRef.current) {
|
|
364
|
+
inputRef.current.focus()
|
|
365
|
+
}
|
|
366
|
+
}, 100)
|
|
367
|
+
|
|
368
|
+
return () => clearTimeout(timer)
|
|
369
|
+
}, [])
|
|
370
|
+
|
|
371
|
+
// Get source token price for USD conversions
|
|
372
|
+
const sourceTokenPrice = (originToken as any)?.tokenPriceUsd || 0
|
|
373
|
+
|
|
374
|
+
// Sync display value with token amount only when mode changes (not during typing)
|
|
375
|
+
const [lastInputMode, setLastInputMode] = useState(isInputTypeUsd)
|
|
376
|
+
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
// Only sync when mode actually changes, not during normal typing
|
|
379
|
+
if (lastInputMode !== isInputTypeUsd && tokenAmountForBackend) {
|
|
380
|
+
const tokenAmount = parseFloat(tokenAmountForBackend) || 0
|
|
381
|
+
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
382
|
+
// Show USD with max 2 decimals
|
|
383
|
+
const usdAmount = tokenAmount * sourceTokenPrice
|
|
384
|
+
setInputDisplayValue(Number(usdAmount.toFixed(2)).toString())
|
|
385
|
+
} else {
|
|
386
|
+
// Show token with max 8 decimals
|
|
387
|
+
setInputDisplayValue(Number(tokenAmount.toFixed(8)).toString())
|
|
388
|
+
}
|
|
389
|
+
setLastInputMode(isInputTypeUsd)
|
|
390
|
+
}
|
|
391
|
+
}, [isInputTypeUsd, sourceTokenPrice, tokenAmountForBackend, lastInputMode])
|
|
392
|
+
|
|
393
|
+
// Handle input amount changes with 8 decimal limit and 16 char total limit
|
|
394
|
+
const handleAmountChange = useCallback(
|
|
395
|
+
(value: string) => {
|
|
396
|
+
// Allow empty string
|
|
397
|
+
if (value === "") {
|
|
398
|
+
setInputDisplayValue("")
|
|
399
|
+
setTokenAmountForBackend("")
|
|
400
|
+
setSendFormAmount("")
|
|
401
|
+
setGlobalAmount("")
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Limit total length to 16 characters
|
|
406
|
+
if (value.length > 16) {
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Validate decimal places (max 8 decimals) and allow single decimal point
|
|
411
|
+
const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
|
|
412
|
+
if (!decimalMatch) {
|
|
413
|
+
return // Don't update if invalid format
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Store the display value
|
|
417
|
+
setInputDisplayValue(value)
|
|
418
|
+
|
|
419
|
+
// Update the token amount for backend and useSendForm
|
|
420
|
+
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
421
|
+
const usdAmount = parseFloat(value) || 0
|
|
422
|
+
const tokenAmount = usdAmount / sourceTokenPrice
|
|
423
|
+
const tokenAmountStr = tokenAmount.toString()
|
|
424
|
+
setTokenAmountForBackend(tokenAmountStr)
|
|
425
|
+
setSendFormAmount(tokenAmountStr)
|
|
426
|
+
setGlobalAmount(tokenAmountStr)
|
|
427
|
+
} else {
|
|
428
|
+
setTokenAmountForBackend(value)
|
|
429
|
+
setSendFormAmount(value)
|
|
430
|
+
setGlobalAmount(value)
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
[setSendFormAmount, isInputTypeUsd, sourceTokenPrice, setGlobalAmount],
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
// Get display values based on input type
|
|
437
|
+
const displayAmount = useMemo(() => {
|
|
438
|
+
return inputDisplayValue
|
|
439
|
+
}, [inputDisplayValue])
|
|
440
|
+
|
|
441
|
+
const displayUsdValue = useMemo(() => {
|
|
442
|
+
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
443
|
+
// Show token amount when in USD mode
|
|
444
|
+
const tokenAmount = parseFloat(tokenAmountForBackend) || 0
|
|
445
|
+
return `${tokenAmount.toFixed(8)} ${originToken?.symbol || "TOKEN"}`
|
|
446
|
+
}
|
|
447
|
+
return amountUsdDisplay || "$0.00"
|
|
448
|
+
}, [
|
|
449
|
+
tokenAmountForBackend,
|
|
450
|
+
isInputTypeUsd,
|
|
451
|
+
sourceTokenPrice,
|
|
452
|
+
originToken?.symbol,
|
|
453
|
+
amountUsdDisplay,
|
|
454
|
+
])
|
|
455
|
+
|
|
456
|
+
// Handle percentage clicks for quick amounts
|
|
457
|
+
const handlePercentageClick = useCallback(
|
|
458
|
+
(percentage: number) => {
|
|
459
|
+
if (!balanceFormatted) {
|
|
460
|
+
return
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const balance = parseFloat(balanceFormatted)
|
|
464
|
+
if (Number.isNaN(balance)) return
|
|
465
|
+
|
|
466
|
+
const amount = (balance * percentage) / 100
|
|
467
|
+
// Cap decimals to 8 places
|
|
468
|
+
const cappedAmount = parseFloat(amount.toFixed(8))
|
|
469
|
+
const tokenAmountStr = cappedAmount.toString()
|
|
470
|
+
|
|
471
|
+
// Update all states consistently
|
|
472
|
+
setTokenAmountForBackend(tokenAmountStr)
|
|
473
|
+
setSendFormAmount(tokenAmountStr)
|
|
474
|
+
setGlobalAmount(tokenAmountStr)
|
|
475
|
+
|
|
476
|
+
// Update display based on current mode
|
|
477
|
+
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
478
|
+
const usdAmount = cappedAmount * sourceTokenPrice
|
|
479
|
+
setInputDisplayValue(Number(usdAmount.toFixed(2)).toString())
|
|
480
|
+
} else {
|
|
481
|
+
setInputDisplayValue(tokenAmountStr)
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
[
|
|
485
|
+
balanceFormatted,
|
|
486
|
+
setSendFormAmount,
|
|
487
|
+
isInputTypeUsd,
|
|
488
|
+
sourceTokenPrice,
|
|
489
|
+
setGlobalAmount,
|
|
490
|
+
],
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
// Handle input type toggle (USD ↔ Token)
|
|
494
|
+
const handleInputTypeToggle = useCallback(() => {
|
|
495
|
+
// Use tokenAmountForBackend as the source of truth for conversion
|
|
496
|
+
const currentTokenAmount = parseFloat(tokenAmountForBackend) || 0
|
|
497
|
+
|
|
498
|
+
if (isInputTypeUsd && sourceTokenPrice > 0) {
|
|
499
|
+
// Switching from USD to token mode
|
|
500
|
+
// Display the token amount (limit to 8 decimals)
|
|
501
|
+
const tokenAmountStr = Number(currentTokenAmount.toFixed(8)).toString()
|
|
502
|
+
setInputDisplayValue(tokenAmountStr)
|
|
503
|
+
} else if (!isInputTypeUsd && sourceTokenPrice > 0) {
|
|
504
|
+
// Switching from token to USD mode
|
|
505
|
+
// Display USD amount (limit to 2 decimals)
|
|
506
|
+
const usdAmount = currentTokenAmount * sourceTokenPrice
|
|
507
|
+
const usdAmountStr = Number(usdAmount.toFixed(2)).toString()
|
|
508
|
+
setInputDisplayValue(usdAmountStr)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Toggle the mode
|
|
512
|
+
setIsInputTypeUsd(!isInputTypeUsd)
|
|
513
|
+
|
|
514
|
+
// Focus the input field after toggling
|
|
515
|
+
setTimeout(() => {
|
|
516
|
+
if (inputRef.current) {
|
|
517
|
+
inputRef.current.focus()
|
|
518
|
+
// Select all text for easy replacement
|
|
519
|
+
inputRef.current.select()
|
|
520
|
+
}
|
|
521
|
+
}, 0)
|
|
522
|
+
}, [tokenAmountForBackend, isInputTypeUsd, sourceTokenPrice])
|
|
523
|
+
|
|
524
|
+
// Dynamic font size based on input length - matching Earn.tsx
|
|
525
|
+
const inputStyles = useMemo(() => {
|
|
526
|
+
const inputLength = displayAmount.length
|
|
527
|
+
let fontSize: string
|
|
528
|
+
|
|
529
|
+
if (inputLength > 12) {
|
|
530
|
+
fontSize = "0.875rem"
|
|
531
|
+
} else if (inputLength > 9) {
|
|
532
|
+
fontSize = "1rem"
|
|
533
|
+
} else if (inputLength > 6) {
|
|
534
|
+
fontSize = "1.125rem"
|
|
535
|
+
} else if (inputLength > 3) {
|
|
536
|
+
fontSize = "1.25rem"
|
|
537
|
+
} else {
|
|
538
|
+
fontSize = "1.5rem"
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
fontSize,
|
|
543
|
+
transition: "all 0.1s ease-in-out",
|
|
544
|
+
}
|
|
545
|
+
}, [displayAmount.length])
|
|
546
|
+
|
|
547
|
+
const handleOriginTokenSelect = useCallback(
|
|
548
|
+
(token: any) => {
|
|
549
|
+
const formattedToken = {
|
|
550
|
+
...token,
|
|
551
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
552
|
+
contractInfo: {
|
|
553
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
554
|
+
contractAddress: token.contractAddress,
|
|
555
|
+
symbol: token.symbol,
|
|
556
|
+
name: token.name,
|
|
557
|
+
},
|
|
558
|
+
} as any
|
|
559
|
+
setOriginToken(formattedToken)
|
|
560
|
+
logger.console.log("[trails-sdk] selected origin token", token)
|
|
561
|
+
setShowOriginTokenSelector(false)
|
|
562
|
+
// Track the token selection
|
|
563
|
+
onTrackToken?.(token)
|
|
564
|
+
},
|
|
565
|
+
[setOriginToken, onTrackToken],
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
const handleDestinationTokenSelect = useCallback(
|
|
569
|
+
(token: any) => {
|
|
570
|
+
const formattedToken = {
|
|
571
|
+
...token,
|
|
572
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
573
|
+
contractInfo: {
|
|
574
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
575
|
+
contractAddress: token.contractAddress,
|
|
576
|
+
symbol: token.symbol,
|
|
577
|
+
name: token.name,
|
|
578
|
+
},
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Update both global destination token state and useSendForm state
|
|
582
|
+
setDestinationToken(formattedToken as any)
|
|
583
|
+
setSelectedDestToken(formattedToken as any)
|
|
584
|
+
|
|
585
|
+
// Update destination chain to match the selected token's chain
|
|
586
|
+
if (setSelectedDestinationChain && token.chainId) {
|
|
587
|
+
const chainInfo = getChainInfo(token.chainId)
|
|
588
|
+
if (chainInfo) {
|
|
589
|
+
setSelectedDestinationChain(chainInfo)
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
setShowDestinationTokenSelector(false)
|
|
594
|
+
logger.console.log("[trails-sdk] selected destination token", token)
|
|
595
|
+
// Track the token selection
|
|
596
|
+
onTrackToken?.(token)
|
|
597
|
+
},
|
|
598
|
+
[
|
|
599
|
+
setDestinationToken,
|
|
600
|
+
setSelectedDestToken,
|
|
601
|
+
setSelectedDestinationChain,
|
|
602
|
+
onTrackToken,
|
|
603
|
+
],
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
// Show origin chain list screen
|
|
607
|
+
if (showOriginChainList) {
|
|
608
|
+
return (
|
|
609
|
+
<ChainList
|
|
610
|
+
onBack={() => {
|
|
611
|
+
setShowOriginChainList(false)
|
|
612
|
+
setShowOriginTokenSelector(true)
|
|
613
|
+
}}
|
|
614
|
+
/>
|
|
615
|
+
)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Show destination chain list screen
|
|
619
|
+
if (showDestinationChainList) {
|
|
620
|
+
return (
|
|
621
|
+
<ChainList
|
|
622
|
+
onBack={() => {
|
|
623
|
+
setShowDestinationChainList(false)
|
|
624
|
+
setShowDestinationTokenSelector(true)
|
|
625
|
+
}}
|
|
626
|
+
/>
|
|
627
|
+
)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Show origin token selector screen
|
|
631
|
+
if (showOriginTokenSelector) {
|
|
632
|
+
return (
|
|
633
|
+
<div className="space-y-2">
|
|
634
|
+
<ScreenHeader
|
|
635
|
+
onBack={() => setShowOriginTokenSelector(false)}
|
|
636
|
+
headerContent="Select From Token"
|
|
637
|
+
headerContentAlign="left"
|
|
638
|
+
showAccountActions={true}
|
|
639
|
+
/>
|
|
640
|
+
<TokenSelector
|
|
641
|
+
onTokenSelect={handleOriginTokenSelect}
|
|
642
|
+
onError={onError}
|
|
643
|
+
fundMethod={fundMethod}
|
|
644
|
+
showContinueButton={false}
|
|
645
|
+
compactMode={false}
|
|
646
|
+
recentTokens={recentTokens}
|
|
647
|
+
onRecentTokenSelect={(token) => {
|
|
648
|
+
const formattedToken = {
|
|
649
|
+
...token,
|
|
650
|
+
decimals: token.decimals,
|
|
651
|
+
contractInfo: {
|
|
652
|
+
decimals: token.decimals,
|
|
653
|
+
contractAddress: token.contractAddress,
|
|
654
|
+
symbol: token.symbol,
|
|
655
|
+
name: token.name,
|
|
656
|
+
},
|
|
657
|
+
} as any
|
|
658
|
+
setOriginToken(formattedToken)
|
|
659
|
+
setShowOriginTokenSelector(false)
|
|
660
|
+
onRecentTokenSelect?.(token)
|
|
661
|
+
}}
|
|
662
|
+
allSupportedTokens={false}
|
|
663
|
+
chainListScreen={true}
|
|
664
|
+
onNavigateToChainList={() => {
|
|
665
|
+
setShowOriginTokenSelector(false)
|
|
666
|
+
setShowOriginChainList(true)
|
|
667
|
+
}}
|
|
668
|
+
/>
|
|
669
|
+
</div>
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Show destination token selector screen
|
|
674
|
+
if (showDestinationTokenSelector) {
|
|
675
|
+
return (
|
|
676
|
+
<div className="space-y-2">
|
|
677
|
+
<ScreenHeader
|
|
678
|
+
onBack={() => setShowDestinationTokenSelector(false)}
|
|
679
|
+
headerContent="Select To Token"
|
|
680
|
+
headerContentAlign="left"
|
|
681
|
+
showAccountActions={true}
|
|
682
|
+
/>
|
|
683
|
+
<TokenSelector
|
|
684
|
+
onTokenSelect={handleDestinationTokenSelect}
|
|
685
|
+
onError={onError}
|
|
686
|
+
fundMethod={fundMethod}
|
|
687
|
+
showContinueButton={false}
|
|
688
|
+
compactMode={false}
|
|
689
|
+
recentTokens={recentTokens}
|
|
690
|
+
onRecentTokenSelect={(token) => {
|
|
691
|
+
const formattedToken = {
|
|
692
|
+
...token,
|
|
693
|
+
decimals: token.decimals,
|
|
694
|
+
contractInfo: {
|
|
695
|
+
decimals: token.decimals,
|
|
696
|
+
contractAddress: token.contractAddress,
|
|
697
|
+
symbol: token.symbol,
|
|
698
|
+
name: token.name,
|
|
699
|
+
},
|
|
700
|
+
} as any
|
|
701
|
+
setDestinationToken(formattedToken)
|
|
702
|
+
setSelectedDestToken(formattedToken)
|
|
703
|
+
setShowDestinationTokenSelector(false)
|
|
704
|
+
onRecentTokenSelect?.(token)
|
|
705
|
+
}}
|
|
706
|
+
allSupportedTokens={true}
|
|
707
|
+
chainListScreen={true}
|
|
708
|
+
onNavigateToChainList={() => {
|
|
709
|
+
setShowDestinationTokenSelector(false)
|
|
710
|
+
setShowDestinationChainList(true)
|
|
711
|
+
}}
|
|
712
|
+
/>
|
|
713
|
+
</div>
|
|
714
|
+
)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (!originToken) {
|
|
718
|
+
return (
|
|
719
|
+
<div className="space-y-2">
|
|
720
|
+
<ScreenHeader
|
|
721
|
+
onBack={onBack}
|
|
722
|
+
headerContent="Fund"
|
|
723
|
+
headerContentAlign="left"
|
|
724
|
+
showAccountActions={true}
|
|
725
|
+
/>
|
|
726
|
+
|
|
727
|
+
<div className="space-y-2">
|
|
728
|
+
{/* Token Selector */}
|
|
729
|
+
<div className="relative">
|
|
730
|
+
<button
|
|
731
|
+
type="button"
|
|
732
|
+
onClick={() => setShowOriginTokenSelector(true)}
|
|
733
|
+
className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-hover-bg trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
|
|
734
|
+
>
|
|
735
|
+
<div className="flex items-center space-x-3 flex-1">
|
|
736
|
+
<div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
737
|
+
<Search className="w-4 h-4 text-gray-400" />
|
|
738
|
+
</div>
|
|
739
|
+
<div className="text-left flex-1">
|
|
740
|
+
<div className="font-medium text-gray-900 dark:text-white text-sm">
|
|
741
|
+
From token
|
|
742
|
+
</div>
|
|
743
|
+
<div className="text-xs trails-text-muted">Select token</div>
|
|
744
|
+
</div>
|
|
745
|
+
</div>
|
|
746
|
+
<ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
|
|
747
|
+
</button>
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return (
|
|
755
|
+
<div className="space-y-1">
|
|
756
|
+
<ScreenHeader
|
|
757
|
+
onBack={onBack}
|
|
758
|
+
headerContent="Fund"
|
|
759
|
+
headerContentAlign="left"
|
|
760
|
+
showAccountActions={true}
|
|
761
|
+
/>
|
|
762
|
+
|
|
763
|
+
<div className="space-y-1">
|
|
764
|
+
{/* Origin Amount Input Section */}
|
|
765
|
+
<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 focus-within:border-gray-400 dark:focus-within:border-gray-500">
|
|
766
|
+
{/* Deposit Label and Percentage Buttons */}
|
|
767
|
+
<div className="flex justify-between items-center mb-2">
|
|
768
|
+
<div className="text-sm font-medium trails-text-secondary text-left">
|
|
769
|
+
Deposit
|
|
770
|
+
</div>
|
|
771
|
+
|
|
772
|
+
{/* Percentage Buttons */}
|
|
773
|
+
{originToken && (
|
|
774
|
+
<div className="flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
|
775
|
+
<button
|
|
776
|
+
type="button"
|
|
777
|
+
onClick={() => handlePercentageClick(25)}
|
|
778
|
+
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"
|
|
779
|
+
>
|
|
780
|
+
25%
|
|
781
|
+
</button>
|
|
782
|
+
<button
|
|
783
|
+
type="button"
|
|
784
|
+
onClick={() => handlePercentageClick(50)}
|
|
785
|
+
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"
|
|
786
|
+
>
|
|
787
|
+
50%
|
|
788
|
+
</button>
|
|
789
|
+
<button
|
|
790
|
+
type="button"
|
|
791
|
+
onClick={() => handlePercentageClick(75)}
|
|
792
|
+
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"
|
|
793
|
+
>
|
|
794
|
+
75%
|
|
795
|
+
</button>
|
|
796
|
+
<button
|
|
797
|
+
type="button"
|
|
798
|
+
onClick={() => handlePercentageClick(100)}
|
|
799
|
+
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"
|
|
800
|
+
>
|
|
801
|
+
Max
|
|
802
|
+
</button>
|
|
803
|
+
</div>
|
|
804
|
+
)}
|
|
805
|
+
</div>
|
|
806
|
+
|
|
807
|
+
<div className="flex items-center space-x-2">
|
|
808
|
+
{/* Amount Input */}
|
|
809
|
+
<div className="flex-1">
|
|
810
|
+
<div className="flex items-center justify-start">
|
|
811
|
+
<div className="flex items-center">
|
|
812
|
+
<input
|
|
813
|
+
ref={inputRef}
|
|
814
|
+
type="text"
|
|
815
|
+
value={displayAmount}
|
|
816
|
+
onChange={(e) => handleAmountChange(e.target.value)}
|
|
817
|
+
placeholder="0"
|
|
818
|
+
className={`bg-transparent border-none outline-none font-bold text-left trails-text-primary placeholder-trails-text-primary ${
|
|
819
|
+
isLoadingQuote ? "animate-pulse" : ""
|
|
820
|
+
}`}
|
|
821
|
+
style={{
|
|
822
|
+
fontSize: inputStyles.fontSize,
|
|
823
|
+
width: `${Math.max((displayAmount || "0").length, 1)}ch`,
|
|
824
|
+
minWidth: "1ch",
|
|
825
|
+
maxWidth: "270px",
|
|
826
|
+
padding: "0",
|
|
827
|
+
margin: "0",
|
|
828
|
+
transition: "all 0.1s ease-in-out",
|
|
829
|
+
}}
|
|
830
|
+
inputMode="decimal"
|
|
831
|
+
/>
|
|
832
|
+
<span
|
|
833
|
+
className="font-bold text-gray-400 dark:text-gray-500"
|
|
834
|
+
style={{
|
|
835
|
+
fontSize: inputStyles.fontSize,
|
|
836
|
+
marginLeft: "0.1em",
|
|
837
|
+
padding: "0",
|
|
838
|
+
transition: "all 0.2s ease-in-out",
|
|
839
|
+
}}
|
|
840
|
+
>
|
|
841
|
+
{isInputTypeUsd
|
|
842
|
+
? "USD"
|
|
843
|
+
: originToken?.symbol.slice(0, 4) || ""}
|
|
844
|
+
</span>
|
|
845
|
+
{isLoadingQuote && (
|
|
846
|
+
<div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
|
|
847
|
+
)}
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
</div>
|
|
851
|
+
|
|
852
|
+
{/* Token Selection Button */}
|
|
853
|
+
<button
|
|
854
|
+
type="button"
|
|
855
|
+
onClick={() => setShowOriginTokenSelector(true)}
|
|
856
|
+
className="flex items-center space-x-2 trails-bg-card hover:trails-hover-bg trails-border-radius-input px-2.5 py-1.5 border trails-border-primary transition-colors cursor-pointer"
|
|
857
|
+
>
|
|
858
|
+
{originToken ? (
|
|
859
|
+
<>
|
|
860
|
+
<TokenImage
|
|
861
|
+
symbol={originToken.symbol}
|
|
862
|
+
imageUrl={originToken.imageUrl}
|
|
863
|
+
chainId={originToken.chainId}
|
|
864
|
+
size={20}
|
|
865
|
+
/>
|
|
866
|
+
<span className="font-medium trails-text-primary text-sm">
|
|
867
|
+
{originToken.symbol}
|
|
868
|
+
</span>
|
|
869
|
+
<ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
|
|
870
|
+
</>
|
|
871
|
+
) : (
|
|
872
|
+
<>
|
|
873
|
+
<span className="font-medium trails-text-muted text-sm">
|
|
874
|
+
Select Token
|
|
875
|
+
</span>
|
|
876
|
+
<ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
|
|
877
|
+
</>
|
|
878
|
+
)}
|
|
879
|
+
</button>
|
|
880
|
+
</div>
|
|
881
|
+
|
|
882
|
+
{/* Bottom Info Row */}
|
|
883
|
+
<div className="mt-2 flex justify-between items-center">
|
|
884
|
+
{/* USD Amount */}
|
|
885
|
+
<div className="text-xs trails-text-muted">
|
|
886
|
+
{originToken?.symbol && displayAmount ? (
|
|
887
|
+
<>≈ {amountUsdDisplay || "$0.00"}</>
|
|
888
|
+
) : (
|
|
889
|
+
<span> </span>
|
|
890
|
+
)}
|
|
891
|
+
</div>
|
|
892
|
+
|
|
893
|
+
{/* Origin Token Balance */}
|
|
894
|
+
<div className="text-xs trails-text-muted text-right">
|
|
895
|
+
{originToken ? (
|
|
896
|
+
<button
|
|
897
|
+
type="button"
|
|
898
|
+
className="text-xs trails-text-muted cursor-pointer hover:trails-hover-text transition-colors bg-transparent border-none p-0"
|
|
899
|
+
onClick={() => handlePercentageClick(100)}
|
|
900
|
+
onKeyDown={(e) => {
|
|
901
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
902
|
+
e.preventDefault()
|
|
903
|
+
handlePercentageClick(100)
|
|
904
|
+
}
|
|
905
|
+
}}
|
|
906
|
+
title="Click to use full balance"
|
|
907
|
+
>
|
|
908
|
+
Balance:{" "}
|
|
909
|
+
{isBalanceVisible
|
|
910
|
+
? `${balanceFormatted || "0.00"} ${originToken.symbol}`
|
|
911
|
+
: "••••••"}
|
|
912
|
+
</button>
|
|
913
|
+
) : (
|
|
914
|
+
<span> </span>
|
|
915
|
+
)}
|
|
916
|
+
</div>
|
|
917
|
+
</div>
|
|
918
|
+
</div>
|
|
919
|
+
|
|
920
|
+
{/* USD Value Toggle */}
|
|
921
|
+
<div
|
|
922
|
+
className="flex items-center justify-start"
|
|
923
|
+
style={{ display: "none" }}
|
|
924
|
+
>
|
|
925
|
+
<button
|
|
926
|
+
type="button"
|
|
927
|
+
onClick={handleInputTypeToggle}
|
|
928
|
+
className="flex items-center justify-start gap-2 px-3 py-1.5 rounded-md transition-colors cursor-pointer text-blue-600 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-blue-700"
|
|
929
|
+
style={{ color: "#155DFC" }}
|
|
930
|
+
>
|
|
931
|
+
<svg
|
|
932
|
+
width="20"
|
|
933
|
+
height="21"
|
|
934
|
+
viewBox="0 0 20 21"
|
|
935
|
+
fill="none"
|
|
936
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
937
|
+
aria-label="Toggle between USD and token amount"
|
|
938
|
+
>
|
|
939
|
+
<title>Toggle between USD and token amount</title>
|
|
940
|
+
<path
|
|
941
|
+
d="M11.3879 14.7175V10.4181C11.3879 10.2209 11.4547 10.0554 11.5884 9.9217C11.7216 9.78848 11.8868 9.72187 12.0841 9.72187C12.2814 9.72187 12.4469 9.78848 12.5805 9.9217C12.7138 10.0554 12.7804 10.2209 12.7804 10.4181V14.7175L14.0859 13.412C14.2135 13.2844 14.3729 13.2175 14.5642 13.2115C14.7559 13.2059 14.9214 13.2728 15.0606 13.412C15.1999 13.5397 15.2725 13.6991 15.2785 13.8904C15.2841 14.0821 15.2173 14.2475 15.078 14.3868L12.5715 16.8933C12.5019 16.963 12.4264 17.0122 12.3452 17.0409C12.264 17.0702 12.1769 17.0848 12.0841 17.0848C11.9913 17.0848 11.9042 17.0702 11.823 17.0409C11.7418 17.0122 11.6664 16.963 11.5967 16.8933L9.09021 14.3868C8.96256 14.2592 8.89874 14.0997 8.89874 13.9085C8.89874 13.7168 8.96836 13.5513 9.10761 13.412C9.24687 13.2844 9.40933 13.2175 9.59499 13.2115C9.78066 13.2059 9.94312 13.2728 10.0824 13.412L11.3879 14.7175ZM7.21031 6.11874L5.90483 7.42422C5.75397 7.57508 5.58571 7.6447 5.40004 7.6331C5.21437 7.6215 5.05772 7.55187 4.93007 7.42422C4.79082 7.28497 4.72119 7.11949 4.72119 6.92779C4.72119 6.73655 4.78501 6.57711 4.91266 6.44946L7.41919 3.94294C7.48882 3.87331 7.56424 3.82388 7.64547 3.79463C7.7267 3.76585 7.81374 3.75146 7.90657 3.75146C7.9994 3.75146 8.08644 3.76585 8.16767 3.79463C8.2489 3.82388 8.32432 3.87331 8.39395 3.94294L10.9005 6.44946C11.0281 6.57711 11.0919 6.73957 11.0919 6.93684C11.0919 7.13412 11.0281 7.29658 10.9005 7.42422C10.7612 7.56347 10.596 7.6331 10.4047 7.6331C10.213 7.6331 10.0476 7.56347 9.90831 7.42422L8.60283 6.11874V10.4181C8.60283 10.6154 8.53622 10.7806 8.403 10.9139C8.26932 11.0475 8.10384 11.1144 7.90657 11.1144C7.7093 11.1144 7.54405 11.0475 7.41083 10.9139C7.27715 10.7806 7.21031 10.6154 7.21031 10.4181V6.11874Z"
|
|
942
|
+
fill="#155DFC"
|
|
943
|
+
/>
|
|
944
|
+
</svg>
|
|
945
|
+
<div
|
|
946
|
+
className="text-sm font-extrabold leading-[130%]"
|
|
947
|
+
style={{ color: "#155DFC" }}
|
|
948
|
+
>
|
|
949
|
+
{isBalanceVisible ? displayUsdValue : "••••••"}
|
|
950
|
+
</div>
|
|
951
|
+
</button>
|
|
952
|
+
</div>
|
|
953
|
+
|
|
954
|
+
{/* Arrow Down Between Sections */}
|
|
955
|
+
<div className="relative">
|
|
956
|
+
<div className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 p-1.5 trails-border-radius-button trails-bg-tertiary transition-colors border-2 border-white dark:border-gray-800 z-1">
|
|
957
|
+
<ArrowDown
|
|
958
|
+
className="w-5 h-5 text-gray-900 dark:text-white"
|
|
959
|
+
strokeWidth={2.5}
|
|
960
|
+
/>
|
|
961
|
+
</div>
|
|
962
|
+
</div>
|
|
963
|
+
</div>
|
|
964
|
+
|
|
965
|
+
{/* Destination Section */}
|
|
966
|
+
<div className="space-y-1 trails-bg-secondary trails-border-radius-container p-3">
|
|
967
|
+
{/* Recipient Button */}
|
|
968
|
+
<div className="relative">
|
|
969
|
+
{toRecipient ? (
|
|
970
|
+
/* Display only - recipient is fixed */
|
|
971
|
+
<div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2">
|
|
972
|
+
<div className="text-left">
|
|
973
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
974
|
+
Recipient address
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
<div className="flex items-center space-x-2">
|
|
978
|
+
<div
|
|
979
|
+
className="font-medium trails-text-primary text-sm"
|
|
980
|
+
title={toRecipient}
|
|
981
|
+
>
|
|
982
|
+
{truncateAddress(toRecipient)}
|
|
983
|
+
</div>
|
|
984
|
+
<Identicon
|
|
985
|
+
value={toRecipient}
|
|
986
|
+
size={24}
|
|
987
|
+
className="flex-shrink-0"
|
|
988
|
+
/>
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
991
|
+
) : (
|
|
992
|
+
/* Interactive button - user can select recipient */
|
|
993
|
+
<button
|
|
994
|
+
type="button"
|
|
995
|
+
onClick={() => setCurrentScreen("recipients")}
|
|
996
|
+
className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
|
|
997
|
+
>
|
|
998
|
+
<div className="text-left">
|
|
999
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
1000
|
+
Recipient address
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
<div className="flex items-center space-x-2">
|
|
1004
|
+
{selectedRecipient ? (
|
|
1005
|
+
<>
|
|
1006
|
+
<div
|
|
1007
|
+
className="font-medium trails-text-primary text-sm"
|
|
1008
|
+
title={selectedRecipient}
|
|
1009
|
+
>
|
|
1010
|
+
{truncateAddress(selectedRecipient)}
|
|
1011
|
+
</div>
|
|
1012
|
+
<Identicon
|
|
1013
|
+
value={selectedRecipient}
|
|
1014
|
+
size={24}
|
|
1015
|
+
className="flex-shrink-0"
|
|
1016
|
+
/>
|
|
1017
|
+
</>
|
|
1018
|
+
) : (
|
|
1019
|
+
<>
|
|
1020
|
+
<div className="font-medium text-gray-900 dark:text-white text-sm">
|
|
1021
|
+
Select address
|
|
1022
|
+
</div>
|
|
1023
|
+
<div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
1024
|
+
<div className="w-4 h-4 rounded-full bg-gray-400 dark:bg-gray-500"></div>
|
|
1025
|
+
</div>
|
|
1026
|
+
</>
|
|
1027
|
+
)}
|
|
1028
|
+
<ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
|
|
1029
|
+
</div>
|
|
1030
|
+
</button>
|
|
1031
|
+
)}
|
|
1032
|
+
</div>
|
|
1033
|
+
|
|
1034
|
+
{/* Destination Token Selector */}
|
|
1035
|
+
<div className="relative">
|
|
1036
|
+
{toToken ? (
|
|
1037
|
+
/* Display only - destination token is fixed */
|
|
1038
|
+
<div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2">
|
|
1039
|
+
<div className="text-left">
|
|
1040
|
+
<div className="font-medium trails-text-primary text-sm whitespace-nowrap">
|
|
1041
|
+
Receiving amount
|
|
1042
|
+
</div>
|
|
1043
|
+
</div>
|
|
1044
|
+
<div className="flex items-center space-x-2">
|
|
1045
|
+
{selectedDestToken ? (
|
|
1046
|
+
<>
|
|
1047
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
1048
|
+
{isLoadingQuote ? (
|
|
1049
|
+
<div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
|
1050
|
+
) : prepareSendQuote?.destinationAmountFormatted ? (
|
|
1051
|
+
<span className="whitespace-nowrap">
|
|
1052
|
+
{prepareSendQuote.destinationAmountFormatted}{" "}
|
|
1053
|
+
{selectedDestToken.symbol}
|
|
1054
|
+
</span>
|
|
1055
|
+
) : (
|
|
1056
|
+
""
|
|
1057
|
+
)}
|
|
1058
|
+
</div>
|
|
1059
|
+
<TokenImage
|
|
1060
|
+
symbol={selectedDestToken.symbol}
|
|
1061
|
+
imageUrl={selectedDestToken.imageUrl}
|
|
1062
|
+
chainId={globalDestinationToken?.chainId || toChainId}
|
|
1063
|
+
size={24}
|
|
1064
|
+
/>
|
|
1065
|
+
</>
|
|
1066
|
+
) : (
|
|
1067
|
+
<>
|
|
1068
|
+
<div className="font-medium text-gray-900 dark:text-white text-sm">
|
|
1069
|
+
Select token
|
|
1070
|
+
</div>
|
|
1071
|
+
<div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
1072
|
+
<Search className="w-4 h-4 text-gray-400" />
|
|
1073
|
+
</div>
|
|
1074
|
+
</>
|
|
1075
|
+
)}
|
|
1076
|
+
</div>
|
|
1077
|
+
</div>
|
|
1078
|
+
) : (
|
|
1079
|
+
/* Interactive button - user can select destination token */
|
|
1080
|
+
<button
|
|
1081
|
+
type="button"
|
|
1082
|
+
onClick={() => setShowDestinationTokenSelector(true)}
|
|
1083
|
+
className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
|
|
1084
|
+
>
|
|
1085
|
+
<div className="text-left">
|
|
1086
|
+
<div className="font-medium trails-text-primary text-sm whitespace-nowrap">
|
|
1087
|
+
Receiving amount
|
|
1088
|
+
</div>
|
|
1089
|
+
</div>
|
|
1090
|
+
|
|
1091
|
+
<div className="flex items-center space-x-2">
|
|
1092
|
+
{selectedDestToken ? (
|
|
1093
|
+
<>
|
|
1094
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
1095
|
+
{isLoadingQuote ? (
|
|
1096
|
+
<div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
|
1097
|
+
) : prepareSendQuote?.destinationAmountFormatted ? (
|
|
1098
|
+
<span className="whitespace-nowrap">
|
|
1099
|
+
{prepareSendQuote.destinationAmountFormatted}{" "}
|
|
1100
|
+
{selectedDestToken.symbol}
|
|
1101
|
+
</span>
|
|
1102
|
+
) : (
|
|
1103
|
+
""
|
|
1104
|
+
)}
|
|
1105
|
+
</div>
|
|
1106
|
+
<TokenImage
|
|
1107
|
+
symbol={selectedDestToken.symbol}
|
|
1108
|
+
imageUrl={selectedDestToken.imageUrl}
|
|
1109
|
+
chainId={globalDestinationToken?.chainId || toChainId}
|
|
1110
|
+
size={24}
|
|
1111
|
+
/>
|
|
1112
|
+
</>
|
|
1113
|
+
) : (
|
|
1114
|
+
<>
|
|
1115
|
+
<div className="font-medium text-gray-900 dark:text-white text-sm">
|
|
1116
|
+
Select token
|
|
1117
|
+
</div>
|
|
1118
|
+
<div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
1119
|
+
<Search className="w-4 h-4 text-gray-400" />
|
|
1120
|
+
</div>
|
|
1121
|
+
</>
|
|
1122
|
+
)}
|
|
1123
|
+
<ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
|
|
1124
|
+
</div>
|
|
1125
|
+
</button>
|
|
1126
|
+
)}
|
|
1127
|
+
</div>
|
|
1128
|
+
</div>
|
|
1129
|
+
|
|
1130
|
+
<div className="py-2"></div>
|
|
1131
|
+
|
|
1132
|
+
{/* Exchange/Contract Warning */}
|
|
1133
|
+
<RefundWarning
|
|
1134
|
+
fundMethod={fundMethod}
|
|
1135
|
+
isSenderContractOnOrigin={isSenderContractOnOrigin}
|
|
1136
|
+
isSenderContractOnDestination={isSenderContractOnDestination}
|
|
1137
|
+
/>
|
|
1138
|
+
|
|
1139
|
+
{/* Error Display */}
|
|
1140
|
+
|
|
1141
|
+
<ErrorDisplay
|
|
1142
|
+
errorPrettified={quoteErrorPrettified}
|
|
1143
|
+
error={quoteError}
|
|
1144
|
+
severity="warning"
|
|
1145
|
+
/>
|
|
1146
|
+
|
|
1147
|
+
{prepareSendQuote?.noSufficientBalance ? (
|
|
1148
|
+
<div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
|
|
1149
|
+
<div className="flex items-center space-x-2">
|
|
1150
|
+
<svg
|
|
1151
|
+
className="w-4 h-4 text-amber-500 flex-shrink-0"
|
|
1152
|
+
fill="none"
|
|
1153
|
+
stroke="currentColor"
|
|
1154
|
+
viewBox="0 0 24 24"
|
|
1155
|
+
aria-hidden="true"
|
|
1156
|
+
>
|
|
1157
|
+
<path
|
|
1158
|
+
strokeLinecap="round"
|
|
1159
|
+
strokeLinejoin="round"
|
|
1160
|
+
strokeWidth={2}
|
|
1161
|
+
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"
|
|
1162
|
+
/>
|
|
1163
|
+
</svg>
|
|
1164
|
+
<p className="text-sm text-amber-600 dark:text-amber-400">
|
|
1165
|
+
Insufficient balance to complete this transaction
|
|
1166
|
+
</p>
|
|
1167
|
+
</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
) : prepareSendQuote?.minimumNotMet ? (
|
|
1170
|
+
<div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
|
|
1171
|
+
<div className="flex items-center space-x-2">
|
|
1172
|
+
<svg
|
|
1173
|
+
className="w-4 h-4 text-amber-500 flex-shrink-0"
|
|
1174
|
+
fill="none"
|
|
1175
|
+
stroke="currentColor"
|
|
1176
|
+
viewBox="0 0 24 24"
|
|
1177
|
+
aria-hidden="true"
|
|
1178
|
+
>
|
|
1179
|
+
<path
|
|
1180
|
+
strokeLinecap="round"
|
|
1181
|
+
strokeLinejoin="round"
|
|
1182
|
+
strokeWidth={2}
|
|
1183
|
+
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"
|
|
1184
|
+
/>
|
|
1185
|
+
</svg>
|
|
1186
|
+
<p className="text-sm text-amber-600 dark:text-amber-400">
|
|
1187
|
+
Please enter an amount above{" "}
|
|
1188
|
+
{formatUsdAmountDisplay(MINIMUM_USD_AMOUNT_FOR_SWAP)} otherwise
|
|
1189
|
+
transfer may fail
|
|
1190
|
+
</p>
|
|
1191
|
+
</div>
|
|
1192
|
+
</div>
|
|
1193
|
+
) : null}
|
|
1194
|
+
|
|
1195
|
+
{/* Fund Button */}
|
|
1196
|
+
<form onSubmit={handleSubmit}>
|
|
1197
|
+
<button
|
|
1198
|
+
type="submit"
|
|
1199
|
+
disabled={
|
|
1200
|
+
!tokenAmountForBackend ||
|
|
1201
|
+
parseFloat(tokenAmountForBackend) <= 0 ||
|
|
1202
|
+
isSubmitting ||
|
|
1203
|
+
isLoadingQuote ||
|
|
1204
|
+
!isValidRecipient ||
|
|
1205
|
+
!destinationTokenAddress ||
|
|
1206
|
+
!isValidCustomToken ||
|
|
1207
|
+
prepareSendQuote?.noSufficientBalance ||
|
|
1208
|
+
isSameTokenWithoutCustomCalldata ||
|
|
1209
|
+
!prepareSendQuote
|
|
1210
|
+
}
|
|
1211
|
+
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"
|
|
1212
|
+
>
|
|
1213
|
+
{isSubmitting ? (
|
|
1214
|
+
<div className="flex items-center justify-center">
|
|
1215
|
+
<Loader2 className="w-5 h-5 animate-spin mr-2" />
|
|
1216
|
+
<span>{buttonText}</span>
|
|
1217
|
+
</div>
|
|
1218
|
+
) : isSameTokenWithoutCustomCalldata ? (
|
|
1219
|
+
"Select Different Tokens"
|
|
1220
|
+
) : prepareSendQuote?.noSufficientBalance ? (
|
|
1221
|
+
"Insufficient Balance"
|
|
1222
|
+
) : !selectedRecipient ? (
|
|
1223
|
+
"Select recipient address"
|
|
1224
|
+
) : !selectedDestToken ? (
|
|
1225
|
+
"Select destination token"
|
|
1226
|
+
) : !tokenAmountForBackend ||
|
|
1227
|
+
parseFloat(tokenAmountForBackend) <= 0 ? (
|
|
1228
|
+
"Enter an amount"
|
|
1229
|
+
) : (
|
|
1230
|
+
buttonText || "Fund"
|
|
1231
|
+
)}
|
|
1232
|
+
</button>
|
|
1233
|
+
</form>
|
|
1234
|
+
|
|
1235
|
+
{/* Quote Details */}
|
|
1236
|
+
{prepareSendQuote && (
|
|
1237
|
+
<div className="space-y-2">
|
|
1238
|
+
<QuoteDetails quote={prepareSendQuote} showContent={true} />
|
|
1239
|
+
</div>
|
|
1240
|
+
)}
|
|
1241
|
+
</div>
|
|
1242
|
+
)
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
export default Fund
|