0xtrails 0.1.13 → 0.2.1
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 +12 -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/{ccip-D3gTQONK.js → ccip-BbfANth7.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 +18 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +6 -5
- 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/gasless.d.ts +19 -7
- package/dist/gasless.d.ts.map +1 -1
- package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -394
- package/dist/indexerClient.d.ts +10 -0
- package/dist/indexerClient.d.ts.map +1 -1
- package/dist/intentEntrypoint.d.ts +122 -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 +18 -9
- 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/trailsRouter.d.ts +22 -0
- package/dist/trailsRouter.d.ts.map +1 -0
- package/dist/transactions.d.ts +8 -1
- 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/ConnectWallet.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/FeeOption.d.ts +22 -0
- package/dist/widget/components/FeeOption.d.ts.map +1 -0
- package/dist/widget/components/FeeOptions.d.ts +13 -17
- package/dist/widget/components/FeeOptions.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/FundMethods.d.ts +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/FundSendForm.d.ts.map +1 -1
- package/dist/widget/components/Identicon.d.ts +9 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -0
- package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
- package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
- package/dist/widget/components/NativeGasOption.d.ts +12 -0
- package/dist/widget/components/NativeGasOption.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/PaySendForm.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- 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/TokenSelector.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/useBack.d.ts +2 -0
- package/dist/widget/hooks/useBack.d.ts.map +1 -1
- 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/useSelectedFeeToken.d.ts +32 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
- package/dist/widget/hooks/useSelectedMeshExchange.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 +10 -13
- 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 +38 -36
- package/src/aave.ts +6 -1
- package/src/analytics.ts +109 -53
- package/src/apiClient.ts +1 -1
- package/src/cctp.ts +6 -2
- package/src/cctpqueue.ts +7 -7
- package/src/chains.ts +18 -0
- package/src/config.ts +63 -17
- package/src/constants.ts +20 -16
- package/src/contractUtils.ts +33 -2
- package/src/customChains.ts +24 -0
- package/src/gasless.ts +162 -109
- package/src/index.ts +11 -1
- package/src/indexerClient.ts +73 -1
- package/src/intentEntrypoint.ts +218 -0
- package/src/intents.ts +85 -54
- package/src/metaTxnMonitor.ts +1 -0
- package/src/morpho.ts +13 -2
- package/src/pools.ts +68 -86
- package/src/prepareSend.ts +1719 -967
- package/src/prices.ts +51 -7
- package/src/relaySdk.ts +6 -4
- package/src/relayer.ts +6 -3
- package/src/toast.ts +110 -0
- package/src/tokenBalances.ts +112 -20
- package/src/tokens.ts +70 -7
- package/src/trails.ts +81 -80
- package/src/trailsClient.ts +48 -0
- package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
- package/src/transactions.ts +30 -88
- 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 +102 -0
- package/src/widget/components/ChainFilterDropdown.tsx +1 -1
- package/src/widget/components/ChainList.tsx +10 -20
- package/src/widget/components/ClassicSwap.tsx +921 -0
- package/src/widget/components/ConfigDisplay.tsx +41 -5
- package/src/widget/components/ConnectWallet.tsx +168 -11
- package/src/widget/components/ConnectedWallets.tsx +342 -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 +112 -143
- package/src/widget/components/EarnPools.tsx +2 -4
- package/src/widget/components/EarnPoolsFilters.tsx +6 -6
- package/src/widget/components/FeeOption.tsx +78 -0
- package/src/widget/components/FeeOptions.tsx +192 -127
- package/src/widget/components/Fund.tsx +1236 -0
- package/src/widget/components/FundMethods.tsx +4 -4
- package/src/widget/components/FundSendForm.tsx +1 -34
- package/src/widget/components/Identicon.tsx +158 -0
- package/src/widget/components/MeshConnectExchanges.tsx +32 -3
- package/src/widget/components/MeshConnectFlow.tsx +23 -4
- package/src/widget/components/NativeGasOption.tsx +99 -0
- package/src/widget/components/Pay.tsx +1092 -0
- package/src/widget/components/PaySendForm.tsx +1 -38
- package/src/widget/components/QuoteDetails.tsx +1 -30
- 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 +14 -3
- package/src/widget/components/TransactionDetails.tsx +153 -13
- package/src/widget/components/TransferPendingVertical.tsx +1 -1
- package/src/widget/components/TruncatedAddress.tsx +5 -1
- package/src/widget/components/UserPreferences.tsx +155 -0
- package/src/widget/components/WalletList.tsx +1 -1
- package/src/widget/hooks/useBack.tsx +4 -0
- package/src/widget/hooks/useBalanceVisible.tsx +40 -2
- package/src/widget/hooks/useCheckout.ts +13 -0
- package/src/widget/hooks/useCurrentScreen.tsx +4 -0
- package/src/widget/hooks/useDebugScreens.ts +12 -2
- package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -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/useSelectedFeeToken.tsx +299 -0
- package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
- package/src/widget/hooks/useSendForm.ts +257 -49
- 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 +294 -136
- package/dist/address.d.ts +0 -2
- package/dist/address.d.ts.map +0 -1
- package/dist/proxyCaller.d.ts +0 -21
- package/dist/proxyCaller.d.ts.map +0 -1
- package/src/address.ts +0 -6
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
import { ChevronRight, Search, Loader2, ChevronDown } from "lucide-react"
|
|
2
|
+
import { useEffect, useState, useMemo, useRef, useCallback } from "react"
|
|
3
|
+
import type React from "react"
|
|
4
|
+
import type { Account, WalletClient } from "viem"
|
|
5
|
+
import type { TransactionState } from "../../transactions.js"
|
|
6
|
+
import type { OnCompleteProps } from "../hooks/useSendForm.js"
|
|
7
|
+
import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
|
|
8
|
+
import { useSendForm } from "../hooks/useSendForm.js"
|
|
9
|
+
import { TradeType } from "../../prepareSend.js"
|
|
10
|
+
import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
|
|
11
|
+
import { useDestinationSelectedToken } from "../hooks/useDestinationSelectedToken.js"
|
|
12
|
+
import { useSelectedRecipient } from "../hooks/useSelectedRecipient.js"
|
|
13
|
+
import { useSwapAmount } from "../hooks/useSwapAmount.js"
|
|
14
|
+
import { useTokenList } from "../hooks/useTokenList.js"
|
|
15
|
+
import { useMode } from "../hooks/useMode.js"
|
|
16
|
+
import { useCurrentScreen } from "../hooks/useCurrentScreen.js"
|
|
17
|
+
import { useDefaultTokenSelection } from "../hooks/useDefaultTokenSelection.js" // Context version
|
|
18
|
+
import { usePayMessage } from "../hooks/usePayMessage.js"
|
|
19
|
+
import { useTargetAmount } from "../hooks/useTargetAmount.js"
|
|
20
|
+
import { TokenImage } from "./TokenImage.js"
|
|
21
|
+
import { Identicon } from "./Identicon.js"
|
|
22
|
+
import { TokenSelector } from "./TokenSelector.js"
|
|
23
|
+
import { ScreenHeader } from "./ScreenHeader.js"
|
|
24
|
+
import { ChainList } from "./ChainList.js"
|
|
25
|
+
import { QuoteDetails } from "./QuoteDetails.js"
|
|
26
|
+
import { ErrorDisplay } from "./ErrorDisplay.js"
|
|
27
|
+
import { getChainInfo } from "../../chains.js"
|
|
28
|
+
import { FeeOptions } from "./FeeOptions.js"
|
|
29
|
+
import { truncateAddress } from "../../utils.js"
|
|
30
|
+
import type { PrepareSendQuote } from "../../prepareSend.js"
|
|
31
|
+
import type { SupportedToken } from "../../tokens.js"
|
|
32
|
+
import { logger } from "../../logger.js"
|
|
33
|
+
import { RefundWarning } from "./RefundWarning.js"
|
|
34
|
+
|
|
35
|
+
interface PayProps {
|
|
36
|
+
selectedToken?: any // Origin token (optional - user can select)
|
|
37
|
+
onBack?: () => void
|
|
38
|
+
account: Account
|
|
39
|
+
walletClient: WalletClient
|
|
40
|
+
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
41
|
+
onError: (error: Error | string | null) => void
|
|
42
|
+
onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
|
|
43
|
+
onConfirm: () => void
|
|
44
|
+
onComplete: (result: OnCompleteProps) => void
|
|
45
|
+
onSend: (amount: string, recipient: string) => void
|
|
46
|
+
paymasterUrls?: Array<{ chainId: number; url: string }>
|
|
47
|
+
gasless?: boolean
|
|
48
|
+
setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
|
|
49
|
+
quoteProvider?: string
|
|
50
|
+
fundMethod?: string
|
|
51
|
+
onNavigateToMeshConnect?: (
|
|
52
|
+
props: {
|
|
53
|
+
toTokenSymbol: string
|
|
54
|
+
toTokenAmount: string
|
|
55
|
+
toChainId: number
|
|
56
|
+
toRecipientAddress: string
|
|
57
|
+
},
|
|
58
|
+
quote?: PrepareSendQuote | null,
|
|
59
|
+
) => void
|
|
60
|
+
checkoutOnHandlers?: CheckoutOnHandlers
|
|
61
|
+
recentTokens?: SupportedToken[]
|
|
62
|
+
onRecentTokenSelect?: (token: SupportedToken) => void
|
|
63
|
+
onTrackToken?: (token: any) => void
|
|
64
|
+
toRecipient?: string
|
|
65
|
+
toAmount?: string
|
|
66
|
+
toChainId?: number
|
|
67
|
+
toToken?: string
|
|
68
|
+
toCalldata?: string
|
|
69
|
+
onAmountUpdate?: (amount: string) => void
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const Pay: React.FC<PayProps> = ({
|
|
73
|
+
selectedToken: initialOriginToken,
|
|
74
|
+
onBack,
|
|
75
|
+
account,
|
|
76
|
+
walletClient,
|
|
77
|
+
onTransactionStateChange,
|
|
78
|
+
onError,
|
|
79
|
+
onWaitingForWalletConfirm,
|
|
80
|
+
onConfirm,
|
|
81
|
+
onComplete,
|
|
82
|
+
onSend,
|
|
83
|
+
paymasterUrls,
|
|
84
|
+
gasless,
|
|
85
|
+
setWalletConfirmRetryHandler,
|
|
86
|
+
quoteProvider,
|
|
87
|
+
fundMethod,
|
|
88
|
+
onNavigateToMeshConnect,
|
|
89
|
+
checkoutOnHandlers,
|
|
90
|
+
recentTokens,
|
|
91
|
+
onRecentTokenSelect,
|
|
92
|
+
onTrackToken,
|
|
93
|
+
toRecipient,
|
|
94
|
+
toAmount,
|
|
95
|
+
toChainId,
|
|
96
|
+
toToken,
|
|
97
|
+
toCalldata,
|
|
98
|
+
onAmountUpdate,
|
|
99
|
+
}) => {
|
|
100
|
+
const { mode } = useMode()
|
|
101
|
+
const { selectedToken: globalOriginToken, setSelectedToken: setOriginToken } =
|
|
102
|
+
useOriginSelectedToken()
|
|
103
|
+
const {
|
|
104
|
+
selectedToken: globalDestinationToken,
|
|
105
|
+
setSelectedToken: setDestinationToken,
|
|
106
|
+
} = useDestinationSelectedToken()
|
|
107
|
+
const { selectedRecipient, setSelectedRecipient } = useSelectedRecipient()
|
|
108
|
+
const { setAmount: setGlobalAmount } = useSwapAmount()
|
|
109
|
+
const { setCurrentScreen } = useCurrentScreen()
|
|
110
|
+
const { targetAmountUsd } = useTargetAmount()
|
|
111
|
+
|
|
112
|
+
// Use new default token selection hook
|
|
113
|
+
const {
|
|
114
|
+
defaultOriginToken,
|
|
115
|
+
defaultDestinationToken,
|
|
116
|
+
isLoading: isLoadingDefaults,
|
|
117
|
+
} = useDefaultTokenSelection()
|
|
118
|
+
|
|
119
|
+
// Use pay message hook for payment request display
|
|
120
|
+
const { message: payMessage } = usePayMessage()
|
|
121
|
+
|
|
122
|
+
// Use global origin token state or initial prop
|
|
123
|
+
const originToken = globalOriginToken || initialOriginToken
|
|
124
|
+
|
|
125
|
+
// Local state for pay-specific functionality
|
|
126
|
+
const [tokenAmountForBackend, setTokenAmountForBackend] = useState("")
|
|
127
|
+
const [inputDisplayValue, setInputDisplayValue] = useState("")
|
|
128
|
+
const [showOriginTokenSelector, setShowOriginTokenSelector] = useState(false)
|
|
129
|
+
const [showOriginChainList, setShowOriginChainList] = useState(false)
|
|
130
|
+
const [showDestinationTokenSelector, setShowDestinationTokenSelector] =
|
|
131
|
+
useState(false)
|
|
132
|
+
const [showDestinationChainList, setShowDestinationChainList] =
|
|
133
|
+
useState(false)
|
|
134
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
135
|
+
const paymentRequestInputRef = useRef<HTMLInputElement>(null)
|
|
136
|
+
const lastAutoSelectedRef = useRef<string | null>(null)
|
|
137
|
+
|
|
138
|
+
// Get sorted tokens to auto-select origin and destination tokens
|
|
139
|
+
const { filteredTokensFormatted } = useTokenList({
|
|
140
|
+
onContinue: () => {}, // Not used for auto-selection
|
|
141
|
+
onError: () => {}, // Not used for auto-selection
|
|
142
|
+
fundMethod: undefined,
|
|
143
|
+
allSupportedTokens: true, // Show all tokens for destination selection
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Use useSendForm for quote functionality with EXACT_OUTPUT trade type
|
|
147
|
+
const {
|
|
148
|
+
amountUsdDisplay,
|
|
149
|
+
isLoadingQuote,
|
|
150
|
+
prepareSendQuote,
|
|
151
|
+
setAmount: setSendFormAmount,
|
|
152
|
+
handleSubmit,
|
|
153
|
+
isSubmitting,
|
|
154
|
+
buttonText,
|
|
155
|
+
isValidRecipient,
|
|
156
|
+
selectedDestToken,
|
|
157
|
+
setSelectedDestToken,
|
|
158
|
+
setSelectedDestinationChain,
|
|
159
|
+
quoteError,
|
|
160
|
+
quoteErrorPrettified,
|
|
161
|
+
destinationTokenAddress,
|
|
162
|
+
isValidCustomToken,
|
|
163
|
+
isSenderContractOnOrigin,
|
|
164
|
+
isSenderContractOnDestination,
|
|
165
|
+
feeOptions,
|
|
166
|
+
selectedFeeToken,
|
|
167
|
+
setSelectedFeeToken,
|
|
168
|
+
} = useSendForm({
|
|
169
|
+
account,
|
|
170
|
+
toAmount: tokenAmountForBackend || toAmount, // Use the input amount as target amount for EXACT_OUTPUT
|
|
171
|
+
toRecipient: selectedRecipient || toRecipient || account.address,
|
|
172
|
+
toChainId,
|
|
173
|
+
toToken,
|
|
174
|
+
toCalldata,
|
|
175
|
+
walletClient,
|
|
176
|
+
onTransactionStateChange,
|
|
177
|
+
onError,
|
|
178
|
+
onWaitingForWalletConfirm,
|
|
179
|
+
paymasterUrls,
|
|
180
|
+
gasless,
|
|
181
|
+
onConfirm,
|
|
182
|
+
onComplete,
|
|
183
|
+
onSend,
|
|
184
|
+
selectedToken: originToken as any,
|
|
185
|
+
setWalletConfirmRetryHandler,
|
|
186
|
+
tradeType: TradeType.EXACT_OUTPUT, // Key difference: using EXACT_OUTPUT
|
|
187
|
+
quoteProvider,
|
|
188
|
+
fundMethod,
|
|
189
|
+
mode,
|
|
190
|
+
onNavigateToMeshConnect,
|
|
191
|
+
checkoutOnHandlers,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Auto-select origin token using new hook
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (!originToken && !isLoadingDefaults && defaultOriginToken) {
|
|
197
|
+
logger.console.log(
|
|
198
|
+
"[trails-sdk] Auto-selecting origin token:",
|
|
199
|
+
defaultOriginToken,
|
|
200
|
+
)
|
|
201
|
+
setOriginToken(defaultOriginToken as any)
|
|
202
|
+
}
|
|
203
|
+
}, [originToken, isLoadingDefaults, defaultOriginToken, setOriginToken])
|
|
204
|
+
|
|
205
|
+
// Initialize destination token from props or auto-select
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
const currentKey = `${toToken}-${toChainId}`
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
toToken &&
|
|
211
|
+
toChainId &&
|
|
212
|
+
filteredTokensFormatted?.length > 0 &&
|
|
213
|
+
lastAutoSelectedRef.current !== currentKey
|
|
214
|
+
) {
|
|
215
|
+
// Find the destination token from props
|
|
216
|
+
const destinationToken = filteredTokensFormatted?.find(
|
|
217
|
+
(token) =>
|
|
218
|
+
token.chainId === Number(toChainId) &&
|
|
219
|
+
(token.contractAddress.toLowerCase() === toToken.toLowerCase() ||
|
|
220
|
+
token.symbol.toLowerCase() === toToken.toLowerCase()),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if (destinationToken) {
|
|
224
|
+
const decimals =
|
|
225
|
+
destinationToken.contractInfo?.decimals ??
|
|
226
|
+
(destinationToken as any)?.decimals
|
|
227
|
+
|
|
228
|
+
const formattedToken = {
|
|
229
|
+
...destinationToken,
|
|
230
|
+
contractInfo: {
|
|
231
|
+
decimals,
|
|
232
|
+
contractAddress: destinationToken.contractAddress,
|
|
233
|
+
symbol: destinationToken.symbol,
|
|
234
|
+
name: destinationToken.name,
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
logger.console.log(
|
|
239
|
+
"[trails-sdk] Auto-selecting destination token from props:",
|
|
240
|
+
formattedToken,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
setDestinationToken(formattedToken as any)
|
|
244
|
+
setSelectedDestToken({
|
|
245
|
+
symbol: destinationToken.symbol,
|
|
246
|
+
name: destinationToken.name,
|
|
247
|
+
imageUrl: destinationToken.imageUrl,
|
|
248
|
+
decimals,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// Set the destination chain
|
|
252
|
+
if (setSelectedDestinationChain) {
|
|
253
|
+
const chainInfo = getChainInfo(Number(toChainId))
|
|
254
|
+
if (chainInfo) {
|
|
255
|
+
setSelectedDestinationChain(chainInfo)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Mark this combination as processed
|
|
260
|
+
lastAutoSelectedRef.current = currentKey
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}, [
|
|
264
|
+
toToken,
|
|
265
|
+
toChainId,
|
|
266
|
+
filteredTokensFormatted,
|
|
267
|
+
setDestinationToken,
|
|
268
|
+
setSelectedDestToken,
|
|
269
|
+
setSelectedDestinationChain,
|
|
270
|
+
])
|
|
271
|
+
|
|
272
|
+
// Initialize selected recipient from toRecipient prop or default to connected wallet
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
if (toRecipient && !selectedRecipient) {
|
|
275
|
+
setSelectedRecipient(toRecipient)
|
|
276
|
+
} else if (!selectedRecipient && account?.address) {
|
|
277
|
+
// Default to connected wallet address if no recipient is set
|
|
278
|
+
setSelectedRecipient(account.address)
|
|
279
|
+
}
|
|
280
|
+
}, [toRecipient, selectedRecipient, setSelectedRecipient, account?.address])
|
|
281
|
+
|
|
282
|
+
// Initialize and update amount from toAmount prop
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
logger.console.log("[trails-sdk] Pay component toAmount effect:", {
|
|
285
|
+
toAmount,
|
|
286
|
+
tokenAmountForBackend,
|
|
287
|
+
inputDisplayValue,
|
|
288
|
+
willUpdate: !!toAmount && toAmount !== tokenAmountForBackend,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// Update amount when toAmount prop changes (for payment requests)
|
|
292
|
+
if (toAmount && toAmount !== tokenAmountForBackend) {
|
|
293
|
+
logger.console.log(
|
|
294
|
+
"[trails-sdk] Updating amount from toAmount prop:",
|
|
295
|
+
toAmount,
|
|
296
|
+
)
|
|
297
|
+
setTokenAmountForBackend(toAmount)
|
|
298
|
+
setInputDisplayValue(toAmount)
|
|
299
|
+
setSendFormAmount(toAmount)
|
|
300
|
+
setGlobalAmount(toAmount)
|
|
301
|
+
}
|
|
302
|
+
}, [
|
|
303
|
+
toAmount,
|
|
304
|
+
tokenAmountForBackend,
|
|
305
|
+
inputDisplayValue,
|
|
306
|
+
setSendFormAmount,
|
|
307
|
+
setGlobalAmount,
|
|
308
|
+
])
|
|
309
|
+
|
|
310
|
+
// Initialize destination token from global state or default when no toToken prop
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
if (!toToken && !selectedDestToken?.symbol) {
|
|
313
|
+
// Prefer global destination token if set
|
|
314
|
+
const destTokenToUse = globalDestinationToken || defaultDestinationToken
|
|
315
|
+
|
|
316
|
+
if (destTokenToUse && !isLoadingDefaults) {
|
|
317
|
+
logger.console.log(
|
|
318
|
+
"[trails-sdk] Initializing destination token:",
|
|
319
|
+
destTokenToUse,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
// Set destination token if not already set by global state
|
|
323
|
+
if (!globalDestinationToken && defaultDestinationToken) {
|
|
324
|
+
setDestinationToken(defaultDestinationToken as any)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const decimals =
|
|
328
|
+
destTokenToUse.contractInfo?.decimals ??
|
|
329
|
+
(destTokenToUse as any)?.decimals
|
|
330
|
+
|
|
331
|
+
setSelectedDestToken({
|
|
332
|
+
symbol: destTokenToUse.symbol,
|
|
333
|
+
name: destTokenToUse.name,
|
|
334
|
+
imageUrl: destTokenToUse.imageUrl,
|
|
335
|
+
decimals,
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// Also set the destination chain if available
|
|
339
|
+
if (setSelectedDestinationChain && destTokenToUse.chainId) {
|
|
340
|
+
const chainInfo = getChainInfo(destTokenToUse.chainId)
|
|
341
|
+
if (chainInfo) {
|
|
342
|
+
setSelectedDestinationChain(chainInfo)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}, [
|
|
348
|
+
toToken,
|
|
349
|
+
globalDestinationToken,
|
|
350
|
+
defaultDestinationToken,
|
|
351
|
+
isLoadingDefaults,
|
|
352
|
+
selectedDestToken?.symbol,
|
|
353
|
+
setDestinationToken,
|
|
354
|
+
setSelectedDestToken,
|
|
355
|
+
setSelectedDestinationChain,
|
|
356
|
+
])
|
|
357
|
+
|
|
358
|
+
// Auto-focus input field on component mount
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
if (inputRef.current) {
|
|
361
|
+
inputRef.current.focus()
|
|
362
|
+
}
|
|
363
|
+
}, [])
|
|
364
|
+
|
|
365
|
+
// Handle input amount changes with 8 decimal limit and 16 char total limit
|
|
366
|
+
const handleAmountChange = useCallback(
|
|
367
|
+
(value: string) => {
|
|
368
|
+
// Allow empty string
|
|
369
|
+
if (value === "") {
|
|
370
|
+
setInputDisplayValue("")
|
|
371
|
+
setTokenAmountForBackend("")
|
|
372
|
+
setSendFormAmount("")
|
|
373
|
+
setGlobalAmount("")
|
|
374
|
+
onAmountUpdate?.("")
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Limit total length to 16 characters
|
|
379
|
+
if (value.length > 16) {
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Validate decimal places (max 8 decimals) and allow single decimal point
|
|
384
|
+
const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
|
|
385
|
+
if (!decimalMatch) {
|
|
386
|
+
return // Don't update if invalid format
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Store the display value
|
|
390
|
+
setInputDisplayValue(value)
|
|
391
|
+
|
|
392
|
+
// Update the token amount for backend and useSendForm
|
|
393
|
+
setTokenAmountForBackend(value)
|
|
394
|
+
setSendFormAmount(value)
|
|
395
|
+
setGlobalAmount(value)
|
|
396
|
+
onAmountUpdate?.(value)
|
|
397
|
+
},
|
|
398
|
+
[setSendFormAmount, setGlobalAmount, onAmountUpdate],
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
// Get display values based on input type
|
|
402
|
+
const displayAmount = useMemo(() => {
|
|
403
|
+
return inputDisplayValue
|
|
404
|
+
}, [inputDisplayValue])
|
|
405
|
+
|
|
406
|
+
// Dynamic font size based on input length
|
|
407
|
+
const inputStyles = useMemo(() => {
|
|
408
|
+
const inputLength = displayAmount.length
|
|
409
|
+
let fontSize: string
|
|
410
|
+
|
|
411
|
+
if (inputLength > 12) {
|
|
412
|
+
fontSize = "0.875rem"
|
|
413
|
+
} else if (inputLength > 9) {
|
|
414
|
+
fontSize = "1rem"
|
|
415
|
+
} else if (inputLength > 6) {
|
|
416
|
+
fontSize = "1.125rem"
|
|
417
|
+
} else if (inputLength > 3) {
|
|
418
|
+
fontSize = "1.25rem"
|
|
419
|
+
} else {
|
|
420
|
+
fontSize = "1.5rem"
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
fontSize,
|
|
425
|
+
transition: "all 0.1s ease-in-out",
|
|
426
|
+
}
|
|
427
|
+
}, [displayAmount.length])
|
|
428
|
+
|
|
429
|
+
const handleOriginTokenSelect = useCallback(
|
|
430
|
+
(token: any) => {
|
|
431
|
+
const formattedToken = {
|
|
432
|
+
...token,
|
|
433
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
434
|
+
contractInfo: {
|
|
435
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
436
|
+
contractAddress: token.contractAddress,
|
|
437
|
+
symbol: token.symbol,
|
|
438
|
+
name: token.name,
|
|
439
|
+
},
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Update global origin token state
|
|
443
|
+
setOriginToken(formattedToken as any)
|
|
444
|
+
setShowOriginTokenSelector(false)
|
|
445
|
+
logger.console.log("[trails-sdk] selected origin token", token)
|
|
446
|
+
// Track the token selection
|
|
447
|
+
onTrackToken?.(token)
|
|
448
|
+
},
|
|
449
|
+
[setOriginToken, onTrackToken],
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
const handleDestinationTokenSelect = useCallback(
|
|
453
|
+
(token: any) => {
|
|
454
|
+
const formattedToken = {
|
|
455
|
+
...token,
|
|
456
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
457
|
+
contractInfo: {
|
|
458
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
459
|
+
contractAddress: token.contractAddress,
|
|
460
|
+
symbol: token.symbol,
|
|
461
|
+
name: token.name,
|
|
462
|
+
},
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Update both global destination token state and useSendForm state
|
|
466
|
+
setDestinationToken(formattedToken as any)
|
|
467
|
+
setSelectedDestToken(formattedToken as any)
|
|
468
|
+
|
|
469
|
+
// Update destination chain to match the selected token's chain
|
|
470
|
+
if (setSelectedDestinationChain && token.chainId) {
|
|
471
|
+
const chainInfo = getChainInfo(token.chainId)
|
|
472
|
+
if (chainInfo) {
|
|
473
|
+
setSelectedDestinationChain(chainInfo)
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
setShowDestinationTokenSelector(false)
|
|
478
|
+
logger.console.log("[trails-sdk] selected destination token", token)
|
|
479
|
+
// Track the token selection
|
|
480
|
+
onTrackToken?.(token)
|
|
481
|
+
},
|
|
482
|
+
[
|
|
483
|
+
setDestinationToken,
|
|
484
|
+
setSelectedDestToken,
|
|
485
|
+
setSelectedDestinationChain,
|
|
486
|
+
onTrackToken,
|
|
487
|
+
],
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
// Show origin chain list screen
|
|
491
|
+
if (showOriginChainList) {
|
|
492
|
+
return (
|
|
493
|
+
<ChainList
|
|
494
|
+
onBack={() => {
|
|
495
|
+
setShowOriginChainList(false)
|
|
496
|
+
setShowOriginTokenSelector(true)
|
|
497
|
+
}}
|
|
498
|
+
/>
|
|
499
|
+
)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Show destination chain list screen
|
|
503
|
+
if (showDestinationChainList) {
|
|
504
|
+
return (
|
|
505
|
+
<ChainList
|
|
506
|
+
onBack={() => {
|
|
507
|
+
setShowDestinationChainList(false)
|
|
508
|
+
setShowDestinationTokenSelector(true)
|
|
509
|
+
}}
|
|
510
|
+
/>
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Show origin token selector screen
|
|
515
|
+
if (showOriginTokenSelector) {
|
|
516
|
+
return (
|
|
517
|
+
<div className="space-y-2">
|
|
518
|
+
<ScreenHeader
|
|
519
|
+
onBack={() => setShowOriginTokenSelector(false)}
|
|
520
|
+
headerContent="Select Token"
|
|
521
|
+
headerContentAlign="left"
|
|
522
|
+
showAccountActions={true}
|
|
523
|
+
/>
|
|
524
|
+
<TokenSelector
|
|
525
|
+
onTokenSelect={handleOriginTokenSelect}
|
|
526
|
+
targetAmountUsd={targetAmountUsd}
|
|
527
|
+
onError={onError}
|
|
528
|
+
fundMethod={fundMethod}
|
|
529
|
+
showContinueButton={false}
|
|
530
|
+
compactMode={false}
|
|
531
|
+
recentTokens={recentTokens}
|
|
532
|
+
onRecentTokenSelect={(token) => {
|
|
533
|
+
const formattedToken = {
|
|
534
|
+
...token,
|
|
535
|
+
decimals: token.decimals,
|
|
536
|
+
contractInfo: {
|
|
537
|
+
decimals: token.decimals,
|
|
538
|
+
contractAddress: token.contractAddress,
|
|
539
|
+
symbol: token.symbol,
|
|
540
|
+
name: token.name,
|
|
541
|
+
},
|
|
542
|
+
} as any
|
|
543
|
+
setOriginToken(formattedToken)
|
|
544
|
+
setShowOriginTokenSelector(false)
|
|
545
|
+
onRecentTokenSelect?.(token)
|
|
546
|
+
}}
|
|
547
|
+
allSupportedTokens={false}
|
|
548
|
+
chainListScreen={true}
|
|
549
|
+
onNavigateToChainList={() => {
|
|
550
|
+
setShowOriginTokenSelector(false)
|
|
551
|
+
setShowOriginChainList(true)
|
|
552
|
+
}}
|
|
553
|
+
onNavigateToFundMethods={() => {
|
|
554
|
+
setCurrentScreen("fund-methods")
|
|
555
|
+
}}
|
|
556
|
+
/>
|
|
557
|
+
</div>
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Show destination token selector screen
|
|
562
|
+
if (showDestinationTokenSelector) {
|
|
563
|
+
return (
|
|
564
|
+
<div className="space-y-2">
|
|
565
|
+
<ScreenHeader
|
|
566
|
+
onBack={() => setShowDestinationTokenSelector(false)}
|
|
567
|
+
headerContent="Select Token"
|
|
568
|
+
headerContentAlign="left"
|
|
569
|
+
showAccountActions={true}
|
|
570
|
+
/>
|
|
571
|
+
<TokenSelector
|
|
572
|
+
onTokenSelect={handleDestinationTokenSelect}
|
|
573
|
+
targetAmountUsd={targetAmountUsd}
|
|
574
|
+
onError={onError}
|
|
575
|
+
fundMethod={fundMethod}
|
|
576
|
+
showContinueButton={false}
|
|
577
|
+
compactMode={false}
|
|
578
|
+
recentTokens={recentTokens}
|
|
579
|
+
onRecentTokenSelect={(token) => {
|
|
580
|
+
const formattedToken = {
|
|
581
|
+
...token,
|
|
582
|
+
decimals: token.decimals,
|
|
583
|
+
contractInfo: {
|
|
584
|
+
decimals: token.decimals,
|
|
585
|
+
contractAddress: token.contractAddress,
|
|
586
|
+
symbol: token.symbol,
|
|
587
|
+
name: token.name,
|
|
588
|
+
},
|
|
589
|
+
} as any
|
|
590
|
+
setDestinationToken(formattedToken)
|
|
591
|
+
setSelectedDestToken(formattedToken)
|
|
592
|
+
setShowDestinationTokenSelector(false)
|
|
593
|
+
onRecentTokenSelect?.(token)
|
|
594
|
+
}}
|
|
595
|
+
allSupportedTokens={true}
|
|
596
|
+
chainListScreen={true}
|
|
597
|
+
onNavigateToChainList={() => {
|
|
598
|
+
setShowDestinationTokenSelector(false)
|
|
599
|
+
setShowDestinationChainList(true)
|
|
600
|
+
}}
|
|
601
|
+
/>
|
|
602
|
+
</div>
|
|
603
|
+
)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Check if this is a payment request (all required props are set)
|
|
607
|
+
const isPaymentRequest = !!(toToken && toAmount && toChainId && toRecipient)
|
|
608
|
+
|
|
609
|
+
return (
|
|
610
|
+
<div className="space-y-4">
|
|
611
|
+
<ScreenHeader
|
|
612
|
+
onBack={onBack}
|
|
613
|
+
headerContent="Pay"
|
|
614
|
+
headerContentAlign="left"
|
|
615
|
+
showAccountActions={true}
|
|
616
|
+
/>
|
|
617
|
+
|
|
618
|
+
{isPaymentRequest ? (
|
|
619
|
+
/* Payment Request UI */
|
|
620
|
+
<>
|
|
621
|
+
{/* Payment Request Header */}
|
|
622
|
+
<div className="space-y-1 trails-bg-secondary trails-border-radius-container p-3">
|
|
623
|
+
<div className="flex justify-start">
|
|
624
|
+
<div className="flex items-center font-medium trails-text-primary text-sm whitespace-nowrap overflow-hidden">
|
|
625
|
+
{payMessage}
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
</div>
|
|
629
|
+
|
|
630
|
+
{/* Origin Token Selection for Payment Request */}
|
|
631
|
+
<div className="space-y-1">
|
|
632
|
+
<div className="trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary">
|
|
633
|
+
{/* Amount to Pay Label */}
|
|
634
|
+
<div className="flex justify-between items-center mb-2">
|
|
635
|
+
<div className="text-sm font-bold trails-text-secondary text-left">
|
|
636
|
+
Pay with
|
|
637
|
+
{fundMethod === "qr-code"
|
|
638
|
+
? " QR Code"
|
|
639
|
+
: fundMethod === "exchange"
|
|
640
|
+
? " Exchange"
|
|
641
|
+
: ""}
|
|
642
|
+
</div>
|
|
643
|
+
</div>
|
|
644
|
+
|
|
645
|
+
<div className="flex items-center space-x-2">
|
|
646
|
+
{/* Amount Display - Non-editable */}
|
|
647
|
+
<div className="flex-1">
|
|
648
|
+
<div
|
|
649
|
+
className="flex items-center justify-start cursor-text"
|
|
650
|
+
onClick={() => paymentRequestInputRef.current?.focus()}
|
|
651
|
+
>
|
|
652
|
+
<div className="flex items-center">
|
|
653
|
+
<input
|
|
654
|
+
ref={paymentRequestInputRef}
|
|
655
|
+
type="text"
|
|
656
|
+
value={prepareSendQuote?.originAmountFormatted || ""}
|
|
657
|
+
readOnly={true}
|
|
658
|
+
className={`bg-transparent border-none outline-none font-bold text-left trails-text-primary ${
|
|
659
|
+
isLoadingQuote ? "animate-pulse" : ""
|
|
660
|
+
}`}
|
|
661
|
+
style={{
|
|
662
|
+
fontSize: inputStyles.fontSize,
|
|
663
|
+
width: prepareSendQuote?.originAmountFormatted
|
|
664
|
+
? `${Math.max(prepareSendQuote.originAmountFormatted.length - 1 + 0.5, 1)}ch`
|
|
665
|
+
: "1ch",
|
|
666
|
+
minWidth: "1ch",
|
|
667
|
+
maxWidth: "200px",
|
|
668
|
+
padding: "0",
|
|
669
|
+
margin: "0",
|
|
670
|
+
transition: "all 0.1s ease-in-out",
|
|
671
|
+
}}
|
|
672
|
+
/>
|
|
673
|
+
<span
|
|
674
|
+
className="font-bold text-gray-400 dark:text-gray-500"
|
|
675
|
+
style={{
|
|
676
|
+
fontSize: inputStyles.fontSize,
|
|
677
|
+
marginLeft: "0.1em",
|
|
678
|
+
padding: "0",
|
|
679
|
+
transition: "all 0.2s ease-in-out",
|
|
680
|
+
}}
|
|
681
|
+
>
|
|
682
|
+
{originToken?.symbol || "TOKEN"}
|
|
683
|
+
</span>
|
|
684
|
+
{isLoadingQuote && (
|
|
685
|
+
<div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
|
|
686
|
+
)}
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
{/* Origin Token Selection Button */}
|
|
692
|
+
<button
|
|
693
|
+
type="button"
|
|
694
|
+
onClick={() => setShowOriginTokenSelector(true)}
|
|
695
|
+
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"
|
|
696
|
+
>
|
|
697
|
+
{originToken ? (
|
|
698
|
+
<>
|
|
699
|
+
<TokenImage
|
|
700
|
+
symbol={originToken.symbol}
|
|
701
|
+
imageUrl={originToken.imageUrl}
|
|
702
|
+
chainId={originToken.chainId}
|
|
703
|
+
size={20}
|
|
704
|
+
/>
|
|
705
|
+
<span className="font-medium trails-text-primary text-sm">
|
|
706
|
+
{originToken.symbol}
|
|
707
|
+
</span>
|
|
708
|
+
<ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
|
|
709
|
+
</>
|
|
710
|
+
) : (
|
|
711
|
+
<>
|
|
712
|
+
<span className="font-medium trails-text-muted text-sm">
|
|
713
|
+
Select Token
|
|
714
|
+
</span>
|
|
715
|
+
<ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
|
|
716
|
+
</>
|
|
717
|
+
)}
|
|
718
|
+
</button>
|
|
719
|
+
</div>
|
|
720
|
+
|
|
721
|
+
{/* Bottom Info Row */}
|
|
722
|
+
<div className="mt-2 flex justify-between items-center">
|
|
723
|
+
{/* USD Amount */}
|
|
724
|
+
<div className="text-xs trails-text-muted">
|
|
725
|
+
{originToken?.symbol &&
|
|
726
|
+
prepareSendQuote?.originAmountFormatted ? (
|
|
727
|
+
<>≈ {prepareSendQuote?.originAmountUsdDisplay || "$0.00"}</>
|
|
728
|
+
) : (
|
|
729
|
+
<span> </span>
|
|
730
|
+
)}
|
|
731
|
+
</div>
|
|
732
|
+
<div className="text-xs trails-text-muted">
|
|
733
|
+
<span> </span>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
</>
|
|
739
|
+
) : (
|
|
740
|
+
/* Regular Pay UI */
|
|
741
|
+
<>
|
|
742
|
+
{/* Recipient and Origin Token Section */}
|
|
743
|
+
<div className="space-y-1 trails-bg-secondary trails-border-radius-container p-3">
|
|
744
|
+
{/* Recipient Button - Like Fund.tsx */}
|
|
745
|
+
<div className="relative">
|
|
746
|
+
{toRecipient ? (
|
|
747
|
+
/* Display only - recipient is fixed */
|
|
748
|
+
<div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2">
|
|
749
|
+
<div className="text-left">
|
|
750
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
751
|
+
Recipient address
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
<div className="flex items-center space-x-2">
|
|
755
|
+
<div
|
|
756
|
+
className="font-medium trails-text-primary text-sm"
|
|
757
|
+
title={toRecipient}
|
|
758
|
+
>
|
|
759
|
+
{truncateAddress(toRecipient)}
|
|
760
|
+
</div>
|
|
761
|
+
<Identicon
|
|
762
|
+
value={toRecipient}
|
|
763
|
+
size={24}
|
|
764
|
+
className="flex-shrink-0"
|
|
765
|
+
/>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
) : (
|
|
769
|
+
/* Interactive button - user can select recipient */
|
|
770
|
+
<button
|
|
771
|
+
type="button"
|
|
772
|
+
onClick={() => setCurrentScreen("recipients")}
|
|
773
|
+
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"
|
|
774
|
+
>
|
|
775
|
+
<div className="text-left">
|
|
776
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
777
|
+
Recipient address
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
<div className="flex items-center space-x-2">
|
|
781
|
+
{selectedRecipient ? (
|
|
782
|
+
<>
|
|
783
|
+
<div
|
|
784
|
+
className="font-medium trails-text-primary text-sm"
|
|
785
|
+
title={selectedRecipient}
|
|
786
|
+
>
|
|
787
|
+
{truncateAddress(selectedRecipient)}
|
|
788
|
+
</div>
|
|
789
|
+
<Identicon
|
|
790
|
+
value={selectedRecipient}
|
|
791
|
+
size={24}
|
|
792
|
+
className="flex-shrink-0"
|
|
793
|
+
/>
|
|
794
|
+
</>
|
|
795
|
+
) : (
|
|
796
|
+
<>
|
|
797
|
+
<div className="font-medium text-gray-900 dark:text-white text-sm">
|
|
798
|
+
Select address
|
|
799
|
+
</div>
|
|
800
|
+
<div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
801
|
+
<div className="w-4 h-4 rounded-full bg-gray-400 dark:bg-gray-500"></div>
|
|
802
|
+
</div>
|
|
803
|
+
</>
|
|
804
|
+
)}
|
|
805
|
+
<ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
|
|
806
|
+
</div>
|
|
807
|
+
</button>
|
|
808
|
+
)}
|
|
809
|
+
</div>
|
|
810
|
+
|
|
811
|
+
{/* Origin Token Selector - Like Fund.tsx */}
|
|
812
|
+
<div className="relative">
|
|
813
|
+
<button
|
|
814
|
+
type="button"
|
|
815
|
+
onClick={() => setShowOriginTokenSelector(true)}
|
|
816
|
+
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"
|
|
817
|
+
>
|
|
818
|
+
<div className="text-left">
|
|
819
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
820
|
+
Pay with
|
|
821
|
+
{fundMethod === "qr-code"
|
|
822
|
+
? " QR Code"
|
|
823
|
+
: fundMethod === "exchange"
|
|
824
|
+
? " Exchange"
|
|
825
|
+
: ""}
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
|
|
829
|
+
<div className="flex items-center space-x-2">
|
|
830
|
+
{originToken ? (
|
|
831
|
+
<>
|
|
832
|
+
<div className="font-medium trails-text-primary text-sm">
|
|
833
|
+
{isLoadingQuote ? (
|
|
834
|
+
<div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
|
835
|
+
) : prepareSendQuote?.originAmountFormatted ? (
|
|
836
|
+
<span>
|
|
837
|
+
{prepareSendQuote.originAmountFormatted}{" "}
|
|
838
|
+
{originToken.symbol}
|
|
839
|
+
</span>
|
|
840
|
+
) : (
|
|
841
|
+
""
|
|
842
|
+
)}
|
|
843
|
+
</div>
|
|
844
|
+
<TokenImage
|
|
845
|
+
symbol={originToken.symbol}
|
|
846
|
+
imageUrl={originToken.imageUrl}
|
|
847
|
+
chainId={originToken.chainId}
|
|
848
|
+
size={24}
|
|
849
|
+
/>
|
|
850
|
+
</>
|
|
851
|
+
) : (
|
|
852
|
+
<>
|
|
853
|
+
<div className="font-medium text-gray-900 dark:text-white text-sm">
|
|
854
|
+
Select payment token
|
|
855
|
+
</div>
|
|
856
|
+
<div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
857
|
+
<Search className="w-4 h-4 text-gray-400" />
|
|
858
|
+
</div>
|
|
859
|
+
</>
|
|
860
|
+
)}
|
|
861
|
+
<ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
|
|
862
|
+
</div>
|
|
863
|
+
</button>
|
|
864
|
+
</div>
|
|
865
|
+
</div>
|
|
866
|
+
|
|
867
|
+
<div className="space-y-1">
|
|
868
|
+
{/* Destination Amount Input Section - Like Fund.tsx but for destination token */}
|
|
869
|
+
<div className="trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary">
|
|
870
|
+
{/* Amount to Pay Label */}
|
|
871
|
+
<div className="flex justify-between items-center mb-2">
|
|
872
|
+
<div className="text-sm font-medium trails-text-secondary text-left">
|
|
873
|
+
Recipient receives
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
876
|
+
|
|
877
|
+
<div className="flex items-center space-x-2">
|
|
878
|
+
{/* Amount Input */}
|
|
879
|
+
<div className="flex-1">
|
|
880
|
+
<div
|
|
881
|
+
className="flex items-center justify-start cursor-text"
|
|
882
|
+
onClick={() => inputRef.current?.focus()}
|
|
883
|
+
>
|
|
884
|
+
<div className="flex items-center">
|
|
885
|
+
<input
|
|
886
|
+
ref={inputRef}
|
|
887
|
+
type="text"
|
|
888
|
+
value={displayAmount}
|
|
889
|
+
onChange={(e) => handleAmountChange(e.target.value)}
|
|
890
|
+
placeholder={`0 ${selectedDestToken?.symbol || ""}`}
|
|
891
|
+
readOnly={!!toAmount}
|
|
892
|
+
className={`bg-transparent border-none outline-none font-bold text-left trails-text-primary placeholder-trails-text-primary ${
|
|
893
|
+
isLoadingQuote ? "animate-pulse" : ""
|
|
894
|
+
}`}
|
|
895
|
+
style={{
|
|
896
|
+
fontSize: inputStyles.fontSize,
|
|
897
|
+
width: `${Math.max((displayAmount || "0").length - 1 + 0.5, 1)}ch`,
|
|
898
|
+
minWidth: "1ch",
|
|
899
|
+
maxWidth: "270px",
|
|
900
|
+
padding: "0",
|
|
901
|
+
margin: "0",
|
|
902
|
+
transition: "all 0.1s ease-in-out",
|
|
903
|
+
}}
|
|
904
|
+
inputMode="decimal"
|
|
905
|
+
/>
|
|
906
|
+
<span
|
|
907
|
+
className="font-bold text-gray-400 dark:text-gray-500"
|
|
908
|
+
style={{
|
|
909
|
+
fontSize: inputStyles.fontSize,
|
|
910
|
+
marginLeft: "0.1em",
|
|
911
|
+
padding: "0",
|
|
912
|
+
transition: "all 0.2s ease-in-out",
|
|
913
|
+
}}
|
|
914
|
+
>
|
|
915
|
+
{selectedDestToken?.symbol?.slice(0, 4) || "TOKEN"}
|
|
916
|
+
</span>
|
|
917
|
+
{isLoadingQuote && (
|
|
918
|
+
<div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
|
|
919
|
+
)}
|
|
920
|
+
</div>
|
|
921
|
+
</div>
|
|
922
|
+
</div>
|
|
923
|
+
|
|
924
|
+
{/* Destination Token Selection Button */}
|
|
925
|
+
{toToken ? (
|
|
926
|
+
/* Non-clickable display when toToken is provided */
|
|
927
|
+
<div className="flex items-center space-x-2 trails-bg-card trails-border-radius-input px-2.5 py-1.5 border trails-border-primary">
|
|
928
|
+
{selectedDestToken ? (
|
|
929
|
+
<>
|
|
930
|
+
<TokenImage
|
|
931
|
+
symbol={selectedDestToken.symbol}
|
|
932
|
+
imageUrl={selectedDestToken.imageUrl}
|
|
933
|
+
chainId={globalDestinationToken?.chainId || toChainId}
|
|
934
|
+
size={20}
|
|
935
|
+
/>
|
|
936
|
+
<span className="font-medium trails-text-primary text-sm">
|
|
937
|
+
{selectedDestToken.symbol}
|
|
938
|
+
</span>
|
|
939
|
+
</>
|
|
940
|
+
) : (
|
|
941
|
+
<span className="font-medium trails-text-muted text-sm">
|
|
942
|
+
Select Token
|
|
943
|
+
</span>
|
|
944
|
+
)}
|
|
945
|
+
</div>
|
|
946
|
+
) : (
|
|
947
|
+
/* Clickable button when toToken is not provided */
|
|
948
|
+
<button
|
|
949
|
+
type="button"
|
|
950
|
+
onClick={() => setShowDestinationTokenSelector(true)}
|
|
951
|
+
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"
|
|
952
|
+
>
|
|
953
|
+
{selectedDestToken ? (
|
|
954
|
+
<>
|
|
955
|
+
<TokenImage
|
|
956
|
+
symbol={selectedDestToken.symbol}
|
|
957
|
+
imageUrl={selectedDestToken.imageUrl}
|
|
958
|
+
chainId={globalDestinationToken?.chainId || toChainId}
|
|
959
|
+
size={20}
|
|
960
|
+
/>
|
|
961
|
+
<span className="font-medium trails-text-primary text-sm">
|
|
962
|
+
{selectedDestToken.symbol}
|
|
963
|
+
</span>
|
|
964
|
+
<ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
|
|
965
|
+
</>
|
|
966
|
+
) : (
|
|
967
|
+
<>
|
|
968
|
+
<span className="font-medium trails-text-muted text-sm">
|
|
969
|
+
Select Token
|
|
970
|
+
</span>
|
|
971
|
+
<ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
|
|
972
|
+
</>
|
|
973
|
+
)}
|
|
974
|
+
</button>
|
|
975
|
+
)}
|
|
976
|
+
</div>
|
|
977
|
+
|
|
978
|
+
{/* Bottom Info Row */}
|
|
979
|
+
<div className="mt-2 flex justify-between items-center">
|
|
980
|
+
{/* USD Amount */}
|
|
981
|
+
<div className="text-xs trails-text-muted">
|
|
982
|
+
{selectedDestToken?.symbol && displayAmount ? (
|
|
983
|
+
<>≈ {amountUsdDisplay || "$0.00"}</>
|
|
984
|
+
) : (
|
|
985
|
+
<span> </span>
|
|
986
|
+
)}
|
|
987
|
+
</div>
|
|
988
|
+
<div className="text-xs trails-text-muted">
|
|
989
|
+
<span> </span>
|
|
990
|
+
</div>
|
|
991
|
+
</div>
|
|
992
|
+
</div>
|
|
993
|
+
</div>
|
|
994
|
+
</>
|
|
995
|
+
)}
|
|
996
|
+
|
|
997
|
+
{/* Exchange/Contract Warning */}
|
|
998
|
+
<RefundWarning
|
|
999
|
+
fundMethod={fundMethod}
|
|
1000
|
+
isSenderContractOnOrigin={isSenderContractOnOrigin}
|
|
1001
|
+
isSenderContractOnDestination={isSenderContractOnDestination}
|
|
1002
|
+
/>
|
|
1003
|
+
|
|
1004
|
+
{/* Error Display */}
|
|
1005
|
+
<ErrorDisplay
|
|
1006
|
+
errorPrettified={quoteErrorPrettified}
|
|
1007
|
+
error={quoteError}
|
|
1008
|
+
severity="warning"
|
|
1009
|
+
/>
|
|
1010
|
+
|
|
1011
|
+
{prepareSendQuote?.noSufficientBalance ? (
|
|
1012
|
+
<div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
|
|
1013
|
+
<div className="flex items-center space-x-2">
|
|
1014
|
+
<svg
|
|
1015
|
+
className="w-4 h-4 text-amber-500 flex-shrink-0"
|
|
1016
|
+
fill="none"
|
|
1017
|
+
stroke="currentColor"
|
|
1018
|
+
viewBox="0 0 24 24"
|
|
1019
|
+
aria-hidden="true"
|
|
1020
|
+
>
|
|
1021
|
+
<path
|
|
1022
|
+
strokeLinecap="round"
|
|
1023
|
+
strokeLinejoin="round"
|
|
1024
|
+
strokeWidth={2}
|
|
1025
|
+
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"
|
|
1026
|
+
/>
|
|
1027
|
+
</svg>
|
|
1028
|
+
<p className="text-sm text-amber-600 dark:text-amber-400">
|
|
1029
|
+
Insufficient balance to complete this transaction
|
|
1030
|
+
</p>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
) : null}
|
|
1034
|
+
|
|
1035
|
+
{/* Fee Options */}
|
|
1036
|
+
<FeeOptions
|
|
1037
|
+
feeOptions={feeOptions || []}
|
|
1038
|
+
selectedFeeToken={selectedFeeToken}
|
|
1039
|
+
setSelectedFeeToken={(token) => setSelectedFeeToken(token as any)}
|
|
1040
|
+
chainId={originToken?.chainId}
|
|
1041
|
+
/>
|
|
1042
|
+
|
|
1043
|
+
{/* Pay Button */}
|
|
1044
|
+
<form onSubmit={handleSubmit}>
|
|
1045
|
+
<button
|
|
1046
|
+
type="submit"
|
|
1047
|
+
disabled={
|
|
1048
|
+
!tokenAmountForBackend ||
|
|
1049
|
+
parseFloat(tokenAmountForBackend) <= 0 ||
|
|
1050
|
+
isSubmitting ||
|
|
1051
|
+
isLoadingQuote ||
|
|
1052
|
+
!isValidRecipient ||
|
|
1053
|
+
!destinationTokenAddress ||
|
|
1054
|
+
!isValidCustomToken ||
|
|
1055
|
+
prepareSendQuote?.noSufficientBalance ||
|
|
1056
|
+
!prepareSendQuote
|
|
1057
|
+
}
|
|
1058
|
+
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"
|
|
1059
|
+
>
|
|
1060
|
+
{isSubmitting ? (
|
|
1061
|
+
<div className="flex items-center justify-center">
|
|
1062
|
+
<Loader2 className="w-5 h-5 animate-spin mr-2" />
|
|
1063
|
+
<span>{buttonText}</span>
|
|
1064
|
+
</div>
|
|
1065
|
+
) : prepareSendQuote?.noSufficientBalance ? (
|
|
1066
|
+
"Insufficient Balance"
|
|
1067
|
+
) : !selectedRecipient ? (
|
|
1068
|
+
"Select recipient address"
|
|
1069
|
+
) : !selectedDestToken ? (
|
|
1070
|
+
"Select destination token"
|
|
1071
|
+
) : !tokenAmountForBackend ||
|
|
1072
|
+
parseFloat(tokenAmountForBackend) <= 0 ? (
|
|
1073
|
+
"Enter an amount"
|
|
1074
|
+
) : !prepareSendQuote && !quoteError ? (
|
|
1075
|
+
"Getting quote..."
|
|
1076
|
+
) : (
|
|
1077
|
+
buttonText || "Pay"
|
|
1078
|
+
)}
|
|
1079
|
+
</button>
|
|
1080
|
+
</form>
|
|
1081
|
+
|
|
1082
|
+
{/* Quote Details */}
|
|
1083
|
+
{prepareSendQuote && (
|
|
1084
|
+
<div className="space-y-2">
|
|
1085
|
+
<QuoteDetails quote={prepareSendQuote} showContent={true} />
|
|
1086
|
+
</div>
|
|
1087
|
+
)}
|
|
1088
|
+
</div>
|
|
1089
|
+
)
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
export default Pay
|