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
|
@@ -754,7 +754,7 @@ export const PaySendForm: React.FC<PaySendFormProps> = ({
|
|
|
754
754
|
{/* Warning Messages - Show only one at a time */}
|
|
755
755
|
{isSameTokenWithoutCustomCalldata ? (
|
|
756
756
|
<ErrorDisplay
|
|
757
|
-
errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different
|
|
757
|
+
errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different origin token."
|
|
758
758
|
severity="error"
|
|
759
759
|
/>
|
|
760
760
|
) : (
|
|
@@ -4,7 +4,7 @@ import type React from "react"
|
|
|
4
4
|
import { getExplorerUrlForAddress } from "../../explorer.js"
|
|
5
5
|
import type { PrepareSendQuote } from "../../prepareSend.js"
|
|
6
6
|
import { useState, useEffect, useRef } from "react"
|
|
7
|
-
import { truncateAddress } from "../../
|
|
7
|
+
import { truncateAddress } from "../../utils.js"
|
|
8
8
|
import { PriceImpactWarning } from "./PriceImpactWarning.js"
|
|
9
9
|
import { usePriceImpactWarning } from "../hooks/usePriceImpactWarning.js"
|
|
10
10
|
|
|
@@ -9,7 +9,7 @@ import { YellowWarningAnimation } from "./YellowWarningAnimation.js"
|
|
|
9
9
|
import { getTxTimeDiff } from "../../transactions.js"
|
|
10
10
|
import { QuoteDetails } from "./QuoteDetails.js"
|
|
11
11
|
import type { PrepareSendQuote } from "../../prepareSend.js"
|
|
12
|
-
import { truncateAddress } from "../../
|
|
12
|
+
import { truncateAddress } from "../../utils.js"
|
|
13
13
|
import { formatElapsed } from "../../utils.js"
|
|
14
14
|
import { ChainImage } from "./ChainImage.js"
|
|
15
15
|
import { getChainInfo } from "../../chains.js"
|
|
@@ -4,7 +4,7 @@ import { QrCode } from "./QrCode.js"
|
|
|
4
4
|
import { ScreenHeader } from "./ScreenHeader.js"
|
|
5
5
|
import { getExplorerUrlForAddress } from "../../explorer.js"
|
|
6
6
|
import { ExternalLink, Copy, Check } from "lucide-react"
|
|
7
|
-
import { truncateAddress } from "../../
|
|
7
|
+
import { truncateAddress } from "../../utils.js"
|
|
8
8
|
import { useResolveEnsName } from "../../ens.js"
|
|
9
9
|
|
|
10
10
|
interface ReceiveProps {
|
|
@@ -60,6 +60,7 @@ export const Receive: React.FC<ReceiveProps> = ({
|
|
|
60
60
|
onBack={onBack}
|
|
61
61
|
headerContent="Receive"
|
|
62
62
|
headerContentAlign="left"
|
|
63
|
+
showAccountActions={true}
|
|
63
64
|
/>
|
|
64
65
|
|
|
65
66
|
<div className="flex flex-col justify-center min-h-full space-y-6 pt-8">
|
|
@@ -96,6 +97,7 @@ export const Receive: React.FC<ReceiveProps> = ({
|
|
|
96
97
|
onBack={onBack}
|
|
97
98
|
headerContent={`Pay ${ensName ? ensName : truncateAddress(resolvedAddress)}`}
|
|
98
99
|
headerContentAlign="left"
|
|
100
|
+
showAccountActions={true}
|
|
99
101
|
/>
|
|
100
102
|
|
|
101
103
|
<div className="flex flex-col justify-center min-h-full space-y-6 pt-2">
|
|
@@ -163,7 +165,7 @@ export const Receive: React.FC<ReceiveProps> = ({
|
|
|
163
165
|
<button
|
|
164
166
|
type="button"
|
|
165
167
|
onClick={onPay}
|
|
166
|
-
className="
|
|
168
|
+
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"
|
|
167
169
|
>
|
|
168
170
|
Pay {ensName ? ensName : truncateAddress(resolvedAddress)}
|
|
169
171
|
</button>
|
|
@@ -20,7 +20,8 @@ export const RecentTokens: React.FC<RecentTokensProps> = ({
|
|
|
20
20
|
const isTokenSelected = (token: SupportedToken) => {
|
|
21
21
|
if (!selectedToken) return false
|
|
22
22
|
return (
|
|
23
|
-
token.contractAddress ===
|
|
23
|
+
token.contractAddress.toLowerCase() ===
|
|
24
|
+
selectedToken.contractAddress.toLowerCase() &&
|
|
24
25
|
token.chainId === selectedToken.chainId
|
|
25
26
|
)
|
|
26
27
|
}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from "react"
|
|
2
|
+
import type React from "react"
|
|
3
|
+
import { isAddress } from "viem"
|
|
4
|
+
import { Copy, RotateCcw } from "lucide-react"
|
|
5
|
+
import { useAccount, useConnections } from "wagmi"
|
|
6
|
+
import { ScreenHeader } from "./ScreenHeader.js"
|
|
7
|
+
import { SearchInputField } from "./SearchInputField.js"
|
|
8
|
+
import { Identicon } from "./Identicon.js"
|
|
9
|
+
import { useCurrentScreen } from "../hooks/useCurrentScreen.js"
|
|
10
|
+
import { useSelectedRecipient } from "../hooks/useSelectedRecipient.js"
|
|
11
|
+
import { useRecipients, type RecentRecipient } from "../hooks/useRecipients.js"
|
|
12
|
+
import { useResolveEnsAddress, useResolveEnsName } from "../../ens.js"
|
|
13
|
+
import { truncateAddress } from "../../utils.js"
|
|
14
|
+
import { logger } from "../../logger.js"
|
|
15
|
+
import { useMode } from "../hooks/useMode.js"
|
|
16
|
+
import { useWallets, wagmiConnectorToWalletId } from "../../wallets.js"
|
|
17
|
+
|
|
18
|
+
interface RecipientsProps {
|
|
19
|
+
onBack?: () => void
|
|
20
|
+
selectedRecipient?: string
|
|
21
|
+
onRecipientSelect?: (address: string) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Recipients: React.FC<RecipientsProps> = ({
|
|
25
|
+
onBack,
|
|
26
|
+
selectedRecipient = "",
|
|
27
|
+
onRecipientSelect,
|
|
28
|
+
}) => {
|
|
29
|
+
const { mode } = useMode()
|
|
30
|
+
const { setCurrentScreen } = useCurrentScreen()
|
|
31
|
+
const { setSelectedRecipient } = useSelectedRecipient()
|
|
32
|
+
const { recentRecipients, addRecentRecipient, clearRecentRecipients } =
|
|
33
|
+
useRecipients()
|
|
34
|
+
const [recipientInput, setRecipientInput] = useState(selectedRecipient)
|
|
35
|
+
const [isValidAddress, setIsValidAddress] = useState(false)
|
|
36
|
+
const [copiedAddress, setCopiedAddress] = useState<string | null>(null)
|
|
37
|
+
|
|
38
|
+
// Connected wallets functionality
|
|
39
|
+
const { address } = useAccount()
|
|
40
|
+
const connections = useConnections()
|
|
41
|
+
const { wallets: allWallets } = useWallets()
|
|
42
|
+
|
|
43
|
+
// Use wagmi ENS resolution for both directions
|
|
44
|
+
const trimmedInput = recipientInput.trim()
|
|
45
|
+
|
|
46
|
+
// Forward resolution: ENS name → address
|
|
47
|
+
const { ensAddress, isLoading: isLoadingEnsAddress } = useResolveEnsAddress({
|
|
48
|
+
textInput: trimmedInput,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Reverse resolution: address → ENS name
|
|
52
|
+
const { ensName, isLoading: isLoadingEnsName } = useResolveEnsName({
|
|
53
|
+
address: isAddress(trimmedInput) ? trimmedInput : "",
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const isLoadingEns = isLoadingEnsAddress || isLoadingEnsName
|
|
57
|
+
|
|
58
|
+
// Get all connected wallets with their details
|
|
59
|
+
const getConnectedWallets = () => {
|
|
60
|
+
const walletMap = new Map()
|
|
61
|
+
|
|
62
|
+
connections.forEach((connection) => {
|
|
63
|
+
// Process each account in the connection
|
|
64
|
+
connection.accounts.forEach((account) => {
|
|
65
|
+
if (account && !walletMap.has(account)) {
|
|
66
|
+
const walletId = wagmiConnectorToWalletId(connection.connector)
|
|
67
|
+
const walletConfig = allWallets.find(
|
|
68
|
+
(wallet) => wallet.id === walletId,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
walletMap.set(account, {
|
|
72
|
+
address: account,
|
|
73
|
+
connector: connection.connector,
|
|
74
|
+
walletConfig,
|
|
75
|
+
walletId,
|
|
76
|
+
isActive: account === address,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return Array.from(walletMap.values())
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const connectedWallets = getConnectedWallets()
|
|
86
|
+
|
|
87
|
+
// Validate address and handle ENS resolution
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const validateAddress = async () => {
|
|
90
|
+
if (!recipientInput || recipientInput.trim() === "") {
|
|
91
|
+
setIsValidAddress(false)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const trimmedInput = recipientInput.trim()
|
|
96
|
+
|
|
97
|
+
// Check if it's a valid hex address
|
|
98
|
+
if (isAddress(trimmedInput)) {
|
|
99
|
+
setIsValidAddress(true)
|
|
100
|
+
// Add to recent recipients with reverse ENS resolution if available
|
|
101
|
+
if (ensName) {
|
|
102
|
+
await addRecentRecipient(trimmedInput, trimmedInput, ensName)
|
|
103
|
+
} else {
|
|
104
|
+
await addRecentRecipient(trimmedInput, trimmedInput)
|
|
105
|
+
}
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if it looks like an ENS name
|
|
110
|
+
if (trimmedInput.includes(".") && trimmedInput.endsWith(".eth")) {
|
|
111
|
+
// ENS validation will be handled by the wagmi hook
|
|
112
|
+
// If we have a resolved address, it's valid
|
|
113
|
+
if (ensAddress) {
|
|
114
|
+
setIsValidAddress(true)
|
|
115
|
+
await addRecentRecipient(trimmedInput, ensAddress, trimmedInput)
|
|
116
|
+
} else if (!isLoadingEns) {
|
|
117
|
+
// Not loading and no address means resolution failed or not implemented
|
|
118
|
+
setIsValidAddress(false)
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
setIsValidAddress(false)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const timeoutId = setTimeout(validateAddress, 300) // Debounce validation
|
|
126
|
+
return () => clearTimeout(timeoutId)
|
|
127
|
+
}, [recipientInput, ensAddress, ensName, isLoadingEns, addRecentRecipient])
|
|
128
|
+
|
|
129
|
+
const handleInputChange = useCallback((value: string) => {
|
|
130
|
+
setRecipientInput(value)
|
|
131
|
+
}, [])
|
|
132
|
+
|
|
133
|
+
// Handle recent recipient selection
|
|
134
|
+
const handleRecentRecipientSelect = useCallback(
|
|
135
|
+
(recipient: RecentRecipient) => {
|
|
136
|
+
logger.console.log("[trails-sdk] Selected recent recipient:", recipient)
|
|
137
|
+
setSelectedRecipient(recipient.address)
|
|
138
|
+
onRecipientSelect?.(recipient.address)
|
|
139
|
+
setCurrentScreen(mode === "pay" ? "send-form" : "fund-form")
|
|
140
|
+
},
|
|
141
|
+
[setSelectedRecipient, onRecipientSelect, setCurrentScreen, mode],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
// Handle connected wallet selection
|
|
145
|
+
const handleConnectedWalletSelect = useCallback(
|
|
146
|
+
(walletAddress: string) => {
|
|
147
|
+
logger.console.log(
|
|
148
|
+
"[trails-sdk] Selected connected wallet:",
|
|
149
|
+
walletAddress,
|
|
150
|
+
)
|
|
151
|
+
setSelectedRecipient(walletAddress)
|
|
152
|
+
onRecipientSelect?.(walletAddress)
|
|
153
|
+
setCurrentScreen(mode === "pay" ? "send-form" : "fund-form")
|
|
154
|
+
},
|
|
155
|
+
[setSelectedRecipient, onRecipientSelect, setCurrentScreen, mode],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
// Handle copy to clipboard with success indication
|
|
159
|
+
const handleCopyAddress = useCallback(
|
|
160
|
+
async (address: string, e: React.MouseEvent) => {
|
|
161
|
+
e.preventDefault()
|
|
162
|
+
e.stopPropagation()
|
|
163
|
+
try {
|
|
164
|
+
await navigator.clipboard.writeText(address)
|
|
165
|
+
setCopiedAddress(address)
|
|
166
|
+
setTimeout(() => setCopiedAddress(null), 2000) // Clear after 2 seconds
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.console.error("[trails-sdk] Failed to copy address:", error)
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
[],
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const getValidationMessage = () => {
|
|
175
|
+
if (!recipientInput || recipientInput.trim() === "") {
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (isLoadingEns) {
|
|
180
|
+
return "Resolving ENS name..."
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const getValidationColor = () => {
|
|
187
|
+
if (isLoadingEns) {
|
|
188
|
+
return "text-blue-500"
|
|
189
|
+
}
|
|
190
|
+
if (isValidAddress) {
|
|
191
|
+
return "text-green-500"
|
|
192
|
+
}
|
|
193
|
+
if (recipientInput && recipientInput.trim() !== "") {
|
|
194
|
+
return "text-red-500"
|
|
195
|
+
}
|
|
196
|
+
return "text-gray-500"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div className="space-y-4">
|
|
201
|
+
<ScreenHeader
|
|
202
|
+
onBack={onBack}
|
|
203
|
+
headerContent="Recipient address"
|
|
204
|
+
headerContentAlign="left"
|
|
205
|
+
showAccountActions={true}
|
|
206
|
+
/>
|
|
207
|
+
|
|
208
|
+
<div className="space-y-4">
|
|
209
|
+
{/* Input Section */}
|
|
210
|
+
<div className="space-y-2">
|
|
211
|
+
<SearchInputField
|
|
212
|
+
value={recipientInput}
|
|
213
|
+
onChange={handleInputChange}
|
|
214
|
+
placeholder="Enter wallet address"
|
|
215
|
+
autoFocus={true}
|
|
216
|
+
/>
|
|
217
|
+
|
|
218
|
+
{/* Validation Message */}
|
|
219
|
+
{getValidationMessage() && (
|
|
220
|
+
<p className={`text-xs ${getValidationColor()} text-left`}>
|
|
221
|
+
{getValidationMessage()}
|
|
222
|
+
</p>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
{/* Recent Recipients */}
|
|
227
|
+
{recentRecipients.length > 0 && (
|
|
228
|
+
<div className="space-y-2">
|
|
229
|
+
<div className="text-sm font-medium text-gray-700 dark:text-gray-300 text-left">
|
|
230
|
+
Recents
|
|
231
|
+
</div>
|
|
232
|
+
<div className="space-y-1 max-h-48 overflow-y-auto trails-scrollbar">
|
|
233
|
+
{recentRecipients.map((recipient) => {
|
|
234
|
+
// Only highlight if search field contains a valid address that matches this recipient
|
|
235
|
+
const searchQuery = recipientInput.trim()
|
|
236
|
+
const isHighlighted =
|
|
237
|
+
searchQuery &&
|
|
238
|
+
isAddress(searchQuery) &&
|
|
239
|
+
recipient.address.toLowerCase() === searchQuery.toLowerCase()
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<button
|
|
243
|
+
key={recipient.address}
|
|
244
|
+
type="button"
|
|
245
|
+
className={`w-full py-2 px-4 flex items-center space-x-3 transition-all duration-200 trails-border-radius-list-button cursor-pointer group ${
|
|
246
|
+
isHighlighted
|
|
247
|
+
? "trails-list-item-selected border border-blue-200 dark:border-blue-800"
|
|
248
|
+
: "trails-list-item"
|
|
249
|
+
}`}
|
|
250
|
+
onClick={() => handleRecentRecipientSelect(recipient)}
|
|
251
|
+
onKeyDown={(e) => {
|
|
252
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
253
|
+
e.preventDefault()
|
|
254
|
+
handleRecentRecipientSelect(recipient)
|
|
255
|
+
}
|
|
256
|
+
}}
|
|
257
|
+
aria-label={`Select recipient ${recipient.ensName || recipient.address}`}
|
|
258
|
+
>
|
|
259
|
+
{/* Identicon */}
|
|
260
|
+
<div className="relative flex-shrink-0 mr-2">
|
|
261
|
+
<Identicon
|
|
262
|
+
value={recipient.address}
|
|
263
|
+
size={32}
|
|
264
|
+
className="flex-shrink-0"
|
|
265
|
+
/>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<div className="flex-1 min-w-0 text-left">
|
|
269
|
+
<div className="flex items-start">
|
|
270
|
+
<div className="flex-1">
|
|
271
|
+
<div className="flex items-center">
|
|
272
|
+
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
|
273
|
+
{truncateAddress(recipient.address, 8, 4)}
|
|
274
|
+
</span>
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
onClick={(e) =>
|
|
278
|
+
handleCopyAddress(recipient.address, e)
|
|
279
|
+
}
|
|
280
|
+
className={`ml-1 p-0.5 rounded opacity-0 group-hover:opacity-100 transition-all duration-200 cursor-pointer ${
|
|
281
|
+
copiedAddress === recipient.address
|
|
282
|
+
? "bg-green-100 dark:bg-green-900/30 opacity-100"
|
|
283
|
+
: "hover:bg-gray-200 dark:hover:bg-gray-600"
|
|
284
|
+
}`}
|
|
285
|
+
title={
|
|
286
|
+
copiedAddress === recipient.address
|
|
287
|
+
? "Copied!"
|
|
288
|
+
: "Copy address"
|
|
289
|
+
}
|
|
290
|
+
>
|
|
291
|
+
{copiedAddress === recipient.address ? (
|
|
292
|
+
<svg
|
|
293
|
+
className="w-3 h-3 text-green-600 dark:text-green-400"
|
|
294
|
+
fill="none"
|
|
295
|
+
viewBox="0 0 24 24"
|
|
296
|
+
stroke="currentColor"
|
|
297
|
+
aria-label="Copied"
|
|
298
|
+
>
|
|
299
|
+
<title>Copied</title>
|
|
300
|
+
<path
|
|
301
|
+
strokeLinecap="round"
|
|
302
|
+
strokeLinejoin="round"
|
|
303
|
+
strokeWidth={2}
|
|
304
|
+
d="M5 13l4 4L19 7"
|
|
305
|
+
/>
|
|
306
|
+
</svg>
|
|
307
|
+
) : (
|
|
308
|
+
<Copy className="w-3 h-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" />
|
|
309
|
+
)}
|
|
310
|
+
</button>
|
|
311
|
+
</div>
|
|
312
|
+
{recipient.ensName && (
|
|
313
|
+
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
314
|
+
{recipient.ensName}
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</button>
|
|
321
|
+
)
|
|
322
|
+
})}
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
|
|
327
|
+
{/* Connected Wallets - only show when no input */}
|
|
328
|
+
{connectedWallets.length > 0 && !recipientInput.trim() && (
|
|
329
|
+
<div className="space-y-2">
|
|
330
|
+
<div className="flex items-center gap-2">
|
|
331
|
+
<div className="text-sm font-medium text-gray-700 dark:text-gray-300 text-left">
|
|
332
|
+
Connected Wallets
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
<div className="space-y-1 max-h-48 overflow-y-auto trails-scrollbar">
|
|
336
|
+
{connectedWallets.map((wallet) => {
|
|
337
|
+
// Only highlight if search field contains a valid address that matches this wallet
|
|
338
|
+
const searchQuery = recipientInput.trim()
|
|
339
|
+
const isHighlighted =
|
|
340
|
+
searchQuery &&
|
|
341
|
+
isAddress(searchQuery) &&
|
|
342
|
+
wallet.address.toLowerCase() === searchQuery.toLowerCase()
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<button
|
|
346
|
+
key={wallet.address}
|
|
347
|
+
type="button"
|
|
348
|
+
className={`w-full py-2 px-4 flex items-center space-x-3 transition-all duration-200 trails-border-radius-list-button cursor-pointer group ${
|
|
349
|
+
isHighlighted
|
|
350
|
+
? "trails-list-item-selected border border-blue-200 dark:border-blue-800"
|
|
351
|
+
: "trails-list-item"
|
|
352
|
+
}`}
|
|
353
|
+
onClick={() => handleConnectedWalletSelect(wallet.address)}
|
|
354
|
+
onKeyDown={(e) => {
|
|
355
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
356
|
+
e.preventDefault()
|
|
357
|
+
handleConnectedWalletSelect(wallet.address)
|
|
358
|
+
}
|
|
359
|
+
}}
|
|
360
|
+
aria-label={`Select wallet ${wallet.walletConfig?.name || "Wallet"} - ${wallet.address}`}
|
|
361
|
+
>
|
|
362
|
+
{/* Identicon */}
|
|
363
|
+
<div className="relative flex-shrink-0 mr-2">
|
|
364
|
+
<Identicon
|
|
365
|
+
value={wallet.address}
|
|
366
|
+
size={32}
|
|
367
|
+
className="flex-shrink-0"
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<div className="flex-1 min-w-0 text-left">
|
|
372
|
+
<div className="flex items-start">
|
|
373
|
+
<div className="flex-1">
|
|
374
|
+
<div className="flex items-center">
|
|
375
|
+
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
|
376
|
+
{truncateAddress(wallet.address, 8, 4)}
|
|
377
|
+
</span>
|
|
378
|
+
<button
|
|
379
|
+
type="button"
|
|
380
|
+
onClick={(e) =>
|
|
381
|
+
handleCopyAddress(wallet.address, e)
|
|
382
|
+
}
|
|
383
|
+
className={`ml-1 p-0.5 rounded opacity-0 group-hover:opacity-100 transition-all duration-200 cursor-pointer ${
|
|
384
|
+
copiedAddress === wallet.address
|
|
385
|
+
? "bg-green-100 dark:bg-green-900/30 opacity-100"
|
|
386
|
+
: "hover:bg-gray-200 dark:hover:bg-gray-600"
|
|
387
|
+
}`}
|
|
388
|
+
title={
|
|
389
|
+
copiedAddress === wallet.address
|
|
390
|
+
? "Copied!"
|
|
391
|
+
: "Copy address"
|
|
392
|
+
}
|
|
393
|
+
>
|
|
394
|
+
{copiedAddress === wallet.address ? (
|
|
395
|
+
<svg
|
|
396
|
+
className="w-3 h-3 text-green-600 dark:text-green-400"
|
|
397
|
+
fill="none"
|
|
398
|
+
viewBox="0 0 24 24"
|
|
399
|
+
stroke="currentColor"
|
|
400
|
+
aria-label="Copied"
|
|
401
|
+
>
|
|
402
|
+
<title>Copied</title>
|
|
403
|
+
<path
|
|
404
|
+
strokeLinecap="round"
|
|
405
|
+
strokeLinejoin="round"
|
|
406
|
+
strokeWidth={2}
|
|
407
|
+
d="M5 13l4 4L19 7"
|
|
408
|
+
/>
|
|
409
|
+
</svg>
|
|
410
|
+
) : (
|
|
411
|
+
<Copy className="w-3 h-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" />
|
|
412
|
+
)}
|
|
413
|
+
</button>
|
|
414
|
+
</div>
|
|
415
|
+
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
416
|
+
{wallet.walletConfig?.name ||
|
|
417
|
+
wallet.connector?.name ||
|
|
418
|
+
"Wallet"}
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</button>
|
|
424
|
+
)
|
|
425
|
+
})}
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
)}
|
|
429
|
+
|
|
430
|
+
{/* Clear Recents Button */}
|
|
431
|
+
{recentRecipients.length > 0 && (
|
|
432
|
+
<div className="pt-4">
|
|
433
|
+
<button
|
|
434
|
+
type="button"
|
|
435
|
+
onClick={clearRecentRecipients}
|
|
436
|
+
className="flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-700 dark:text-gray-300 trails-bg-secondary rounded-xl hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors cursor-pointer"
|
|
437
|
+
>
|
|
438
|
+
<RotateCcw className="w-3 h-3" />
|
|
439
|
+
Clear recents
|
|
440
|
+
</button>
|
|
441
|
+
</div>
|
|
442
|
+
)}
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export default Recipients
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
|
|
3
|
+
interface RefundWarningProps {
|
|
4
|
+
fundMethod?: string
|
|
5
|
+
isSenderContractOnOrigin?: boolean
|
|
6
|
+
isSenderContractOnDestination?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const RefundWarning: React.FC<RefundWarningProps> = ({
|
|
10
|
+
fundMethod,
|
|
11
|
+
isSenderContractOnOrigin,
|
|
12
|
+
isSenderContractOnDestination,
|
|
13
|
+
}) => {
|
|
14
|
+
// Determine if we should show the warning
|
|
15
|
+
const shouldShowWarning =
|
|
16
|
+
fundMethod === "exchange" ||
|
|
17
|
+
(isSenderContractOnOrigin && !isSenderContractOnDestination)
|
|
18
|
+
|
|
19
|
+
if (!shouldShowWarning) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Determine the warning message
|
|
24
|
+
let warningMessage = ""
|
|
25
|
+
if (fundMethod === "exchange") {
|
|
26
|
+
warningMessage =
|
|
27
|
+
"Using exchange funding may result in loss of funds if the transaction fails and the sender address cannot accept refunds on the origin chain."
|
|
28
|
+
} else if (isSenderContractOnOrigin && !isSenderContractOnDestination) {
|
|
29
|
+
warningMessage =
|
|
30
|
+
"The sender is a contract address. Funds may be lost if the transaction fails and the contract cannot accept refunds on the destination chain."
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
|
|
35
|
+
<div className="flex items-start space-x-2">
|
|
36
|
+
<svg
|
|
37
|
+
className="w-4 h-4 text-amber-500 flex-shrink-0 mt-0.5"
|
|
38
|
+
fill="none"
|
|
39
|
+
stroke="currentColor"
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
>
|
|
43
|
+
<path
|
|
44
|
+
strokeLinecap="round"
|
|
45
|
+
strokeLinejoin="round"
|
|
46
|
+
strokeWidth={2}
|
|
47
|
+
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"
|
|
48
|
+
/>
|
|
49
|
+
</svg>
|
|
50
|
+
<div>
|
|
51
|
+
<p className="text-sm font-medium text-amber-600 dark:text-amber-400">
|
|
52
|
+
Risk of Fund Loss
|
|
53
|
+
</p>
|
|
54
|
+
<p className="text-xs mt-1 text-amber-600 dark:text-amber-400">
|
|
55
|
+
{warningMessage}
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -50,7 +50,7 @@ export const ScreenHeader: React.FC<ScreenHeaderProps> = ({
|
|
|
50
50
|
{(rightSideContent || (isConnected && showAccountActions)) && (
|
|
51
51
|
<div className="absolute right-0 top-1/2 -translate-y-1/2 -translate-x-0 flex items-center gap-2 z-10">
|
|
52
52
|
{rightSideContent && (
|
|
53
|
-
<div className="text-right max-w-[
|
|
53
|
+
<div className="text-right max-w-[280px]">{rightSideContent}</div>
|
|
54
54
|
)}
|
|
55
55
|
{isConnected && showAccountActions && <AccountActionsDropdown />}
|
|
56
56
|
</div>
|