0xtrails 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aave.d.ts +8 -0
- package/dist/aave.d.ts.map +1 -1
- package/dist/abortController.d.ts +8 -0
- package/dist/abortController.d.ts.map +1 -0
- package/dist/{ccip-BlV1Mry3.js → ccip-Xjh9d1gb.js} +7 -7
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/error.d.ts +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/estimate.d.ts +52 -0
- package/dist/estimate.d.ts.map +1 -1
- package/dist/fees.d.ts +19 -0
- package/dist/fees.d.ts.map +1 -0
- package/dist/{index-BNWCIGfQ.js → index-BnhdZ8Ho.js} +76406 -75798
- package/dist/index.js +726 -520
- package/dist/intents.d.ts +40 -0
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxnMonitor.d.ts +3 -3
- package/dist/metaTxnMonitor.d.ts.map +1 -1
- package/dist/metaTxns.d.ts +3 -3
- package/dist/metaTxns.d.ts.map +1 -1
- package/dist/morpho.d.ts +8 -0
- package/dist/morpho.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +19 -75
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/queryParams.d.ts.map +1 -1
- package/dist/relayer.d.ts +6 -6
- package/dist/relayer.d.ts.map +1 -1
- package/dist/sequenceWallet.d.ts +2 -2
- package/dist/sequenceWallet.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/transactions.d.ts +4 -2
- package/dist/transactions.d.ts.map +1 -1
- package/dist/wallets.d.ts.map +1 -1
- package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
- package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -1
- package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +4 -2
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts +4 -0
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
- package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
- package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts +2 -2
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
- package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
- package/dist/widget/components/FeeBreakdown.d.ts +9 -0
- package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
- package/dist/widget/components/Fund.d.ts +2 -2
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +13 -7
- package/dist/widget/components/FundSwap.d.ts.map +1 -0
- package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
- package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/Modal.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts +2 -2
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
- package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
- package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +14 -36
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
- package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +19 -10
- package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
- package/dist/widget/components/QuoteDetails.d.ts +1 -0
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/Receive.d.ts.map +1 -1
- package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
- package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Recipients.d.ts.map +1 -1
- package/dist/widget/components/RequiredPropsError.d.ts +8 -0
- package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
- package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts +3 -2
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
- package/dist/widget/components/TokenImage.d.ts +1 -0
- package/dist/widget/components/TokenImage.d.ts.map +1 -1
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
- package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Tooltip.d.ts +9 -0
- package/dist/widget/components/Tooltip.d.ts.map +1 -0
- package/dist/widget/components/UserPreferences.d.ts.map +1 -1
- package/dist/widget/components/WaasFeeOptions.d.ts +9 -0
- package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
- package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
- package/dist/widget/components/WalletConnect.d.ts.map +1 -1
- package/dist/widget/components/WalletList.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +2 -0
- package/dist/widget/css/index.css +554 -0
- package/dist/widget/hooks/useBack.d.ts +1 -0
- package/dist/widget/hooks/useBack.d.ts.map +1 -1
- package/dist/widget/hooks/useCheckout.d.ts +1 -1
- package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts +83 -0
- package/dist/widget/hooks/useQuote.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
- package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +2 -2
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +9 -4
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +18 -12
- package/src/aave.ts +32 -0
- package/src/abortController.ts +35 -0
- package/src/config.ts +12 -4
- package/src/constants.ts +5 -0
- package/src/error.ts +19 -1
- package/src/estimate.ts +416 -5
- package/src/fees.ts +199 -0
- package/src/intents.ts +161 -11
- package/src/metaTxnMonitor.ts +3 -3
- package/src/metaTxns.ts +3 -5
- package/src/morpho.ts +32 -0
- package/src/prepareSend.ts +714 -550
- package/src/queryParams.ts +2 -1
- package/src/relayer.ts +11 -11
- package/src/sequenceWallet.ts +2 -2
- package/src/tokens.ts +7 -1
- package/src/trails.ts +3 -3
- package/src/transactions.ts +62 -18
- package/src/wallets.ts +8 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +3 -13
- package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
- package/src/widget/components/AccountSettings.tsx +48 -54
- package/src/widget/components/ChainFilterDropdown.tsx +24 -3
- package/src/widget/components/ClassicSwap.tsx +131 -213
- package/src/widget/components/ConnectWallet.tsx +8 -38
- package/src/widget/components/ConnectedWallets.tsx +132 -77
- package/src/widget/components/DynamicInputStyles.tsx +76 -0
- package/src/widget/components/Earn.tsx +82 -593
- package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
- package/src/widget/components/FeeBreakdown.tsx +155 -0
- package/src/widget/components/Fund.tsx +41 -108
- package/src/widget/components/FundMethods.tsx +82 -159
- package/src/widget/components/FundSwap.tsx +52 -0
- package/src/widget/components/FundingMethodSelectorButton.tsx +70 -0
- package/src/widget/components/Identicon.tsx +164 -95
- package/src/widget/components/MeshConnectExchanges.tsx +2 -15
- package/src/widget/components/Modal.tsx +0 -8
- package/src/widget/components/Pay.tsx +214 -237
- package/src/widget/components/PercentageMaxButtons.tsx +77 -0
- package/src/widget/components/PoolDeposit.tsx +569 -0
- package/src/widget/components/PoolWithdraw.tsx +884 -0
- package/src/widget/components/PriceImpactWarning.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +43 -12
- package/src/widget/components/Receipt.tsx +16 -2
- package/src/widget/components/Receive.tsx +0 -2
- package/src/widget/components/RecipientSelectorButton.tsx +44 -0
- package/src/widget/components/Recipients.tsx +63 -157
- package/src/widget/components/RequiredPropsError.tsx +33 -0
- package/src/widget/components/ScreenHeader.tsx +62 -34
- package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
- package/src/widget/components/Swap.tsx +4 -45
- package/src/widget/components/SwapSettings.tsx +2 -14
- package/src/widget/components/ThemeProvider.tsx +2 -1
- package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
- package/src/widget/components/TokenImage.tsx +22 -5
- package/src/widget/components/TokenList.tsx +0 -1
- package/src/widget/components/TokenSelector.tsx +63 -53
- package/src/widget/components/TokenSelectorButton.tsx +98 -0
- package/src/widget/components/Tooltip.tsx +51 -0
- package/src/widget/components/TransferPendingVertical.tsx +1 -1
- package/src/widget/components/UserPreferences.tsx +6 -24
- package/src/widget/components/WaasFeeOptions.tsx +450 -0
- package/src/widget/components/WalletConfirmation.tsx +76 -14
- package/src/widget/components/WalletConnect.tsx +93 -29
- package/src/widget/components/WalletList.tsx +4 -2
- package/src/widget/hooks/useBack.tsx +2 -0
- package/src/widget/hooks/useCheckout.ts +36 -20
- package/src/widget/hooks/useCurrentScreen.tsx +1 -0
- package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
- package/src/widget/hooks/usePayMessage.tsx +86 -11
- package/src/widget/hooks/useQuote.ts +413 -0
- package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
- package/src/widget/hooks/useSendForm.ts +32 -6
- package/src/widget/index.css +27 -0
- package/src/widget/widget.tsx +326 -283
- package/dist/widget/components/FundSendForm.d.ts.map +0 -1
- package/dist/widget/components/PaySendForm.d.ts.map +0 -1
- package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
- package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
- package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
- package/src/widget/components/FundSendForm.tsx +0 -903
- package/src/widget/components/PaySendForm.tsx +0 -869
- package/src/widget/components/SimpleSwap.tsx +0 -983
- package/src/widget/hooks/useSwapSettings.tsx +0 -100
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
import {
|
|
3
|
+
calculateMaxNativeAmount,
|
|
4
|
+
getDefaultGasCostEstimate,
|
|
5
|
+
} from "../../estimate.js"
|
|
6
|
+
|
|
7
|
+
interface PercentageMaxButtonsProps {
|
|
8
|
+
userBalance: string | undefined
|
|
9
|
+
isNativeToken: boolean
|
|
10
|
+
gasCostFormatted?: string
|
|
11
|
+
chainId?: number
|
|
12
|
+
onAmountSelect: (amount: string) => void
|
|
13
|
+
className?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PercentageMaxButtons: React.FC<PercentageMaxButtonsProps> = ({
|
|
17
|
+
userBalance,
|
|
18
|
+
isNativeToken,
|
|
19
|
+
gasCostFormatted,
|
|
20
|
+
chainId,
|
|
21
|
+
onAmountSelect,
|
|
22
|
+
className = "",
|
|
23
|
+
}) => {
|
|
24
|
+
// Don't render if no balance
|
|
25
|
+
if (!userBalance) return null
|
|
26
|
+
|
|
27
|
+
const handlePercentageClick = (percentage: number) => {
|
|
28
|
+
const balance = parseFloat(userBalance)
|
|
29
|
+
if (Number.isNaN(balance)) return
|
|
30
|
+
|
|
31
|
+
const amount = (balance * percentage) / 100
|
|
32
|
+
onAmountSelect(amount.toFixed(6))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleMaxClick = async () => {
|
|
36
|
+
if (isNativeToken) {
|
|
37
|
+
// For native tokens, subtract gas cost
|
|
38
|
+
// Priority: 1) Use actual gas cost from quote, 2) Fetch real gas price, 3) Fallback to 1% of balance
|
|
39
|
+
const effectiveGasCost =
|
|
40
|
+
gasCostFormatted ||
|
|
41
|
+
(await getDefaultGasCostEstimate(userBalance, chainId))
|
|
42
|
+
const maxAmount = calculateMaxNativeAmount(userBalance, effectiveGasCost)
|
|
43
|
+
onAmountSelect(maxAmount)
|
|
44
|
+
} else {
|
|
45
|
+
// For ERC20 tokens, use full balance
|
|
46
|
+
onAmountSelect(userBalance)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className={`flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200 ${className}`}
|
|
53
|
+
>
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
onClick={() => handlePercentageClick(20)}
|
|
57
|
+
className="py-0.5 px-1.5 text-xs font-medium rounded-full transition-colors cursor-pointer trails-bg-percentage-button trails-text-percentage-button trails-hover-percentage-button"
|
|
58
|
+
>
|
|
59
|
+
20%
|
|
60
|
+
</button>
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
onClick={() => handlePercentageClick(50)}
|
|
64
|
+
className="py-0.5 px-1.5 text-xs font-medium rounded-full transition-colors cursor-pointer trails-bg-percentage-button trails-text-percentage-button trails-hover-percentage-button"
|
|
65
|
+
>
|
|
66
|
+
50%
|
|
67
|
+
</button>
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
onClick={handleMaxClick}
|
|
71
|
+
className="py-0.5 px-1.5 text-xs font-medium rounded-full transition-colors cursor-pointer trails-bg-percentage-button trails-text-percentage-button trails-hover-percentage-button"
|
|
72
|
+
>
|
|
73
|
+
Max
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TrendingUp,
|
|
3
|
+
ArrowDown,
|
|
4
|
+
ChevronRight,
|
|
5
|
+
Search,
|
|
6
|
+
Loader2,
|
|
7
|
+
} from "lucide-react"
|
|
8
|
+
import { useEffect, useState, useRef } from "react"
|
|
9
|
+
import type React from "react"
|
|
10
|
+
import type { Account, WalletClient } from "viem"
|
|
11
|
+
import { zeroAddress } from "viem"
|
|
12
|
+
import type { TransactionState } from "../../transactions.js"
|
|
13
|
+
import type { OnCompleteProps } from "../hooks/useSendForm.js"
|
|
14
|
+
import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
|
|
15
|
+
import { useSendForm } from "../hooks/useSendForm.js"
|
|
16
|
+
import { TradeType } from "../../prepareSend.js"
|
|
17
|
+
import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
|
|
18
|
+
import { useEarnPool } from "../hooks/useEarnPool.js"
|
|
19
|
+
import { useTokenList } from "../hooks/useTokenList.js"
|
|
20
|
+
import { useMode } from "../hooks/useMode.js"
|
|
21
|
+
import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
|
|
22
|
+
import { TokenImage } from "./TokenImage.js"
|
|
23
|
+
import { TokenSelector } from "./TokenSelector.js"
|
|
24
|
+
import { EarnPools } from "./EarnPools.js"
|
|
25
|
+
import { ChainList } from "./ChainList.js"
|
|
26
|
+
import { QuoteDetails } from "./QuoteDetails.js"
|
|
27
|
+
import { PercentageMaxButtons } from "./PercentageMaxButtons.js"
|
|
28
|
+
import { TokenSelectorButton } from "./TokenSelectorButton.js"
|
|
29
|
+
import { FundingMethodSelectorButton } from "./FundingMethodSelectorButton.js"
|
|
30
|
+
import { formatTvl } from "../../prices.js"
|
|
31
|
+
import { useAmountUsd } from "../hooks/useAmountUsd.js"
|
|
32
|
+
import { getExplorerUrlForAddress } from "../../explorer.js"
|
|
33
|
+
import aaveLogo from "../assets/aave.svg"
|
|
34
|
+
import morphoLogo from "../assets/morpho.svg"
|
|
35
|
+
import type { PrepareSendQuote } from "../../prepareSend.js"
|
|
36
|
+
import type { SupportedToken } from "../../tokens.js"
|
|
37
|
+
import { logger } from "../../logger.js"
|
|
38
|
+
import { useDynamicInputStyles } from "./DynamicInputStyles.js"
|
|
39
|
+
|
|
40
|
+
interface PoolDepositProps {
|
|
41
|
+
account?: Account
|
|
42
|
+
walletClient?: WalletClient
|
|
43
|
+
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
44
|
+
onError: (error: Error | string | null) => void
|
|
45
|
+
onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
|
|
46
|
+
onConfirm: () => void
|
|
47
|
+
onComplete: (result: OnCompleteProps) => void
|
|
48
|
+
onSend: (amount: string, recipient: string) => void
|
|
49
|
+
paymasterUrls?: Array<{ chainId: number; url: string }>
|
|
50
|
+
gasless?: boolean
|
|
51
|
+
setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
|
|
52
|
+
quoteProvider?: string
|
|
53
|
+
fundMethod?: string
|
|
54
|
+
onNavigateToMeshConnect?: (
|
|
55
|
+
props: {
|
|
56
|
+
toTokenSymbol: string
|
|
57
|
+
toTokenAmount: string
|
|
58
|
+
toChainId: number
|
|
59
|
+
toRecipientAddress: string
|
|
60
|
+
},
|
|
61
|
+
quote?: PrepareSendQuote | null,
|
|
62
|
+
) => void
|
|
63
|
+
checkoutOnHandlers?: CheckoutOnHandlers
|
|
64
|
+
recentTokens?: SupportedToken[]
|
|
65
|
+
onRecentTokenSelect?: (token: SupportedToken) => void
|
|
66
|
+
onTrackToken?: (token: any) => void
|
|
67
|
+
onPoolSelectorStateChange?: (isShowing: boolean) => void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const PoolDeposit: React.FC<PoolDepositProps> = ({
|
|
71
|
+
account,
|
|
72
|
+
walletClient,
|
|
73
|
+
onTransactionStateChange,
|
|
74
|
+
onError,
|
|
75
|
+
onWaitingForWalletConfirm,
|
|
76
|
+
onConfirm,
|
|
77
|
+
onComplete,
|
|
78
|
+
onSend,
|
|
79
|
+
paymasterUrls,
|
|
80
|
+
gasless,
|
|
81
|
+
setWalletConfirmRetryHandler,
|
|
82
|
+
quoteProvider,
|
|
83
|
+
fundMethod,
|
|
84
|
+
onNavigateToMeshConnect,
|
|
85
|
+
checkoutOnHandlers,
|
|
86
|
+
recentTokens,
|
|
87
|
+
onRecentTokenSelect,
|
|
88
|
+
onTrackToken,
|
|
89
|
+
onPoolSelectorStateChange,
|
|
90
|
+
}) => {
|
|
91
|
+
const { mode } = useMode()
|
|
92
|
+
const { isBalanceVisible } = useBalanceVisible()
|
|
93
|
+
const { selectedToken: originToken, setSelectedToken: setOriginToken } =
|
|
94
|
+
useOriginSelectedToken()
|
|
95
|
+
const { selectedPool, setSelectedPool } = useEarnPool()
|
|
96
|
+
|
|
97
|
+
const [showEarnPools, setShowEarnPools] = useState(false)
|
|
98
|
+
const [showOriginTokenSelector, setShowOriginTokenSelector] = useState(false)
|
|
99
|
+
const [showOriginChainList, setShowOriginChainList] = useState(false)
|
|
100
|
+
const [amount, setAmount] = useState("")
|
|
101
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
102
|
+
|
|
103
|
+
// Get sorted tokens to auto-select the highest USD value token
|
|
104
|
+
const { filteredTokensFormatted, isLoadingTokens } = useTokenList({
|
|
105
|
+
onContinue: () => {}, // Not used for auto-selection
|
|
106
|
+
onError: () => {}, // Not used for auto-selection
|
|
107
|
+
fundMethod: undefined,
|
|
108
|
+
allSupportedTokens: false,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Use useSendForm for quote functionality when both token and pool are selected
|
|
112
|
+
const {
|
|
113
|
+
balanceFormatted,
|
|
114
|
+
isLoadingQuote,
|
|
115
|
+
prepareSendQuote,
|
|
116
|
+
setAmount: setSendFormAmount,
|
|
117
|
+
handleSubmit,
|
|
118
|
+
isSubmitting,
|
|
119
|
+
buttonText,
|
|
120
|
+
isValidRecipient,
|
|
121
|
+
} = useSendForm({
|
|
122
|
+
account,
|
|
123
|
+
toAmount: undefined,
|
|
124
|
+
toRecipient: selectedPool?.depositAddress,
|
|
125
|
+
toChainId: selectedPool?.chainId,
|
|
126
|
+
toToken: selectedPool?.token.address,
|
|
127
|
+
toCalldata: undefined,
|
|
128
|
+
walletClient,
|
|
129
|
+
onTransactionStateChange,
|
|
130
|
+
onError,
|
|
131
|
+
onWaitingForWalletConfirm,
|
|
132
|
+
paymasterUrls,
|
|
133
|
+
gasless,
|
|
134
|
+
onConfirm,
|
|
135
|
+
onComplete,
|
|
136
|
+
onSend,
|
|
137
|
+
selectedToken: originToken as any,
|
|
138
|
+
setWalletConfirmRetryHandler,
|
|
139
|
+
tradeType: TradeType.EXACT_INPUT,
|
|
140
|
+
quoteProvider,
|
|
141
|
+
fundMethod,
|
|
142
|
+
mode,
|
|
143
|
+
onNavigateToMeshConnect,
|
|
144
|
+
checkoutOnHandlers,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Calculate USD value using the underlying token (for pool tokens like aBasUSDC)
|
|
148
|
+
const { amountUsdFormatted: underlyingTokenUsdDisplay } = useAmountUsd({
|
|
149
|
+
amount: amount,
|
|
150
|
+
token: originToken?.contractAddress, // Use the origin token's address for USD calculation
|
|
151
|
+
chainId: originToken?.chainId,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Auto-select the highest USD value token as origin token
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (
|
|
157
|
+
!originToken &&
|
|
158
|
+
!isLoadingTokens &&
|
|
159
|
+
filteredTokensFormatted?.length > 0
|
|
160
|
+
) {
|
|
161
|
+
const highestValueToken = filteredTokensFormatted[0] // First token is highest USD value
|
|
162
|
+
if (highestValueToken && Number(highestValueToken.balanceUsd) > 0) {
|
|
163
|
+
const decimals =
|
|
164
|
+
highestValueToken.contractInfo?.decimals ??
|
|
165
|
+
(highestValueToken as any)?.decimals
|
|
166
|
+
setOriginToken({
|
|
167
|
+
...highestValueToken,
|
|
168
|
+
contractInfo: {
|
|
169
|
+
decimals,
|
|
170
|
+
contractAddress: highestValueToken.contractAddress,
|
|
171
|
+
symbol: highestValueToken.symbol,
|
|
172
|
+
name: highestValueToken.name,
|
|
173
|
+
},
|
|
174
|
+
} as any)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}, [originToken, isLoadingTokens, filteredTokensFormatted, setOriginToken])
|
|
178
|
+
|
|
179
|
+
// Auto-focus input field on component mount
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (inputRef.current) {
|
|
182
|
+
inputRef.current.focus()
|
|
183
|
+
}
|
|
184
|
+
}, [])
|
|
185
|
+
|
|
186
|
+
// Notify parent component about pool selector visibility
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (onPoolSelectorStateChange) {
|
|
189
|
+
// Only hide tabs when showing EarnPools selector
|
|
190
|
+
onPoolSelectorStateChange(showEarnPools)
|
|
191
|
+
}
|
|
192
|
+
}, [showEarnPools, onPoolSelectorStateChange])
|
|
193
|
+
|
|
194
|
+
const handleAmountChange = (value: string) => {
|
|
195
|
+
// Validate decimal places (max 8 decimals)
|
|
196
|
+
const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
|
|
197
|
+
if (!decimalMatch && value !== "") {
|
|
198
|
+
return // Don't update if more than 8 decimals
|
|
199
|
+
}
|
|
200
|
+
setAmount(value)
|
|
201
|
+
setSendFormAmount(value) // Sync with useSendForm for quote functionality
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const handlePoolSelect = (pool: any) => {
|
|
205
|
+
logger.console.log("Selected pool:", pool)
|
|
206
|
+
setSelectedPool(pool)
|
|
207
|
+
setShowEarnPools(false)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const handleOriginTokenSelect = (token: any) => {
|
|
211
|
+
const formattedToken = {
|
|
212
|
+
...token,
|
|
213
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
214
|
+
contractInfo: {
|
|
215
|
+
decimals: token.contractInfo?.decimals || token.decimals,
|
|
216
|
+
contractAddress: token.contractAddress,
|
|
217
|
+
symbol: token.symbol,
|
|
218
|
+
name: token.name,
|
|
219
|
+
},
|
|
220
|
+
} as any
|
|
221
|
+
setOriginToken(formattedToken)
|
|
222
|
+
logger.console.log("[trails-sdk] selected origin token", token)
|
|
223
|
+
setShowOriginTokenSelector(false)
|
|
224
|
+
// Track the token selection
|
|
225
|
+
onTrackToken?.(token)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Handle amount selection from percentage buttons
|
|
229
|
+
const handleAmountSelect = (selectedAmount: string) => {
|
|
230
|
+
setAmount(selectedAmount)
|
|
231
|
+
setSendFormAmount(selectedAmount)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Dynamic font size based on input length
|
|
235
|
+
const inputStyles = useDynamicInputStyles({
|
|
236
|
+
inputValue: amount,
|
|
237
|
+
variant: "default",
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
if (showOriginChainList) {
|
|
241
|
+
return (
|
|
242
|
+
<ChainList
|
|
243
|
+
onBack={() => {
|
|
244
|
+
setShowOriginChainList(false)
|
|
245
|
+
setShowOriginTokenSelector(true)
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (showEarnPools) {
|
|
252
|
+
return (
|
|
253
|
+
<EarnPools
|
|
254
|
+
onBack={() => setShowEarnPools(false)}
|
|
255
|
+
onPoolSelect={handlePoolSelect}
|
|
256
|
+
/>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (showOriginTokenSelector) {
|
|
261
|
+
return (
|
|
262
|
+
<div className="space-y-2">
|
|
263
|
+
<TokenSelector
|
|
264
|
+
onTokenSelect={handleOriginTokenSelect}
|
|
265
|
+
onError={onError}
|
|
266
|
+
fundMethod={fundMethod}
|
|
267
|
+
showContinueButton={false}
|
|
268
|
+
compactMode={false}
|
|
269
|
+
recentTokens={recentTokens}
|
|
270
|
+
onRecentTokenSelect={onRecentTokenSelect}
|
|
271
|
+
allSupportedTokens={false}
|
|
272
|
+
chainListScreen={true}
|
|
273
|
+
onNavigateToChainList={() => {
|
|
274
|
+
setShowOriginTokenSelector(false)
|
|
275
|
+
setShowOriginChainList(true)
|
|
276
|
+
}}
|
|
277
|
+
/>
|
|
278
|
+
</div>
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="space-y-2">
|
|
284
|
+
{/* Input Section - Amount + Token Selection */}
|
|
285
|
+
<div className="pt-4 pb-4 trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary min-h-[120px] flex flex-col">
|
|
286
|
+
{/* Deposit Label */}
|
|
287
|
+
<div className="mb-4 flex justify-between items-center">
|
|
288
|
+
<div className="text-sm font-medium trails-text-secondary text-left m-0">
|
|
289
|
+
{mode === "fund" ? "Payment method" : "From"}
|
|
290
|
+
</div>
|
|
291
|
+
<FundingMethodSelectorButton />
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<div className="flex items-center space-x-2 flex-1">
|
|
295
|
+
{/* Amount Input */}
|
|
296
|
+
<div className="flex-1">
|
|
297
|
+
<div className="flex items-center space-x-2">
|
|
298
|
+
<input
|
|
299
|
+
ref={inputRef}
|
|
300
|
+
id="amount"
|
|
301
|
+
type="text"
|
|
302
|
+
value={amount}
|
|
303
|
+
onChange={(e) => handleAmountChange(e.target.value)}
|
|
304
|
+
placeholder={"0"}
|
|
305
|
+
className={`w-full bg-transparent font-bold trails-text-primary placeholder:trails-text-muted border-none outline-none ${
|
|
306
|
+
isLoadingQuote ? "animate-pulse" : ""
|
|
307
|
+
}`}
|
|
308
|
+
style={inputStyles}
|
|
309
|
+
inputMode="decimal"
|
|
310
|
+
/>
|
|
311
|
+
{isLoadingQuote && (
|
|
312
|
+
<div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
{/* Token Selection Button */}
|
|
318
|
+
<div className="relative">
|
|
319
|
+
<TokenSelectorButton
|
|
320
|
+
token={originToken}
|
|
321
|
+
chainId={originToken?.chainId}
|
|
322
|
+
onSelect={() => setShowOriginTokenSelector(true)}
|
|
323
|
+
/>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
{/* Bottom Info Row */}
|
|
328
|
+
<div className="mt-4 flex justify-between items-center">
|
|
329
|
+
{/* USD Amount */}
|
|
330
|
+
{originToken?.symbol && (
|
|
331
|
+
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
332
|
+
≈ {underlyingTokenUsdDisplay || "$0.00"}
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
{/* Origin Token Balance and Percentage Buttons */}
|
|
337
|
+
{originToken && balanceFormatted && (
|
|
338
|
+
<div className="flex items-center space-x-2">
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
className="text-xs text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 transition-colors bg-transparent border-none p-0"
|
|
342
|
+
onClick={() => handleAmountSelect(balanceFormatted || "0")}
|
|
343
|
+
onKeyDown={(e) => {
|
|
344
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
345
|
+
e.preventDefault()
|
|
346
|
+
handleAmountSelect(balanceFormatted || "0")
|
|
347
|
+
}
|
|
348
|
+
}}
|
|
349
|
+
title="Click to use full balance"
|
|
350
|
+
>
|
|
351
|
+
Balance:{" "}
|
|
352
|
+
{isBalanceVisible ? balanceFormatted || "0.00" : "••••••"}
|
|
353
|
+
</button>
|
|
354
|
+
|
|
355
|
+
{/* Percentage Buttons */}
|
|
356
|
+
<PercentageMaxButtons
|
|
357
|
+
userBalance={balanceFormatted}
|
|
358
|
+
isNativeToken={originToken.contractAddress === zeroAddress}
|
|
359
|
+
gasCostFormatted={prepareSendQuote?.gasCostFormatted}
|
|
360
|
+
chainId={originToken.chainId}
|
|
361
|
+
onAmountSelect={handleAmountSelect}
|
|
362
|
+
className="opacity-100"
|
|
363
|
+
/>
|
|
364
|
+
</div>
|
|
365
|
+
)}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
{/* Flip Button - Absolutely Positioned */}
|
|
370
|
+
<div className="relative">
|
|
371
|
+
<button
|
|
372
|
+
type="button"
|
|
373
|
+
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 hover:trails-hover-bg transition-colors cursor-pointer border-2 border-white dark:border-gray-800"
|
|
374
|
+
>
|
|
375
|
+
<ArrowDown
|
|
376
|
+
className="w-5 h-5 text-gray-900 dark:text-white"
|
|
377
|
+
strokeWidth={2.5}
|
|
378
|
+
/>
|
|
379
|
+
</button>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
{/* Destination Vault Selection */}
|
|
383
|
+
<div className="mb-4 trails-bg-secondary trails-border-radius-container transition-all duration-200 border border-transparent hover:!bg-white dark:hover:!bg-white hover:border-gray-400 dark:hover:border-gray-500">
|
|
384
|
+
{selectedPool ? (
|
|
385
|
+
<div className="p-3 trails-border-radius-container trails-bg-secondary transition-all overflow-hidden">
|
|
386
|
+
{/* Vault Label */}
|
|
387
|
+
<div className="flex justify-between items-center mb-2">
|
|
388
|
+
<div className="text-sm font-semibold trails-text-secondary text-left">
|
|
389
|
+
Vault
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
<div className="px-1">
|
|
394
|
+
<div className="flex items-center justify-between">
|
|
395
|
+
<div className="flex items-center space-x-3">
|
|
396
|
+
<div style={{ width: "32px", height: "32px" }}>
|
|
397
|
+
<a
|
|
398
|
+
href={getExplorerUrlForAddress({
|
|
399
|
+
address: selectedPool.token.address,
|
|
400
|
+
chainId: selectedPool.chainId,
|
|
401
|
+
})}
|
|
402
|
+
target="_blank"
|
|
403
|
+
rel="noopener noreferrer"
|
|
404
|
+
className="cursor-pointer"
|
|
405
|
+
>
|
|
406
|
+
<TokenImage
|
|
407
|
+
symbol={selectedPool.token.symbol}
|
|
408
|
+
imageUrl={selectedPool.token.logoUrl}
|
|
409
|
+
chainId={selectedPool.chainId}
|
|
410
|
+
contractAddress={selectedPool.token.address}
|
|
411
|
+
size={32}
|
|
412
|
+
/>
|
|
413
|
+
</a>
|
|
414
|
+
</div>
|
|
415
|
+
<div>
|
|
416
|
+
<h3 className="font-medium text-gray-900 dark:text-white text-sm">
|
|
417
|
+
{selectedPool.poolUrl ? (
|
|
418
|
+
<a
|
|
419
|
+
href={selectedPool.poolUrl}
|
|
420
|
+
target="_blank"
|
|
421
|
+
rel="noopener noreferrer"
|
|
422
|
+
className="hover:underline cursor-pointer"
|
|
423
|
+
>
|
|
424
|
+
{selectedPool.name}
|
|
425
|
+
</a>
|
|
426
|
+
) : (
|
|
427
|
+
selectedPool.name
|
|
428
|
+
)}
|
|
429
|
+
</h3>
|
|
430
|
+
<div className="flex items-center space-x-2">
|
|
431
|
+
<span className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
|
|
432
|
+
{selectedPool.protocol === "Aave" && (
|
|
433
|
+
<img
|
|
434
|
+
src={aaveLogo}
|
|
435
|
+
alt="Aave"
|
|
436
|
+
className="w-3 h-3 mr-1"
|
|
437
|
+
/>
|
|
438
|
+
)}
|
|
439
|
+
{selectedPool.protocol === "Morpho" && (
|
|
440
|
+
<img
|
|
441
|
+
src={morphoLogo}
|
|
442
|
+
alt="Morpho"
|
|
443
|
+
className="w-3 h-3 mr-1"
|
|
444
|
+
/>
|
|
445
|
+
)}
|
|
446
|
+
{selectedPool.protocolUrl ? (
|
|
447
|
+
<a
|
|
448
|
+
href={selectedPool.protocolUrl}
|
|
449
|
+
target="_blank"
|
|
450
|
+
rel="noopener noreferrer"
|
|
451
|
+
className="hover:underline cursor-pointer"
|
|
452
|
+
>
|
|
453
|
+
{selectedPool.protocol}
|
|
454
|
+
</a>
|
|
455
|
+
) : (
|
|
456
|
+
selectedPool.protocol
|
|
457
|
+
)}
|
|
458
|
+
</span>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
title="Select Vault"
|
|
465
|
+
onClick={() => setShowEarnPools(true)}
|
|
466
|
+
className="text-right flex items-center space-x-3 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded p-2 transition-colors"
|
|
467
|
+
>
|
|
468
|
+
<div>
|
|
469
|
+
<div className="flex items-center justify-end space-x-1 text-green-600 dark:text-green-400 mb-1 whitespace-nowrap">
|
|
470
|
+
<TrendingUp className="w-3 h-3" />
|
|
471
|
+
<span className="font-semibold text-sm">
|
|
472
|
+
{selectedPool.apy.toFixed(1)}% APY
|
|
473
|
+
</span>
|
|
474
|
+
</div>
|
|
475
|
+
<p className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
|
476
|
+
TVL: {formatTvl(selectedPool.tvl)}
|
|
477
|
+
</p>
|
|
478
|
+
</div>
|
|
479
|
+
<ChevronRight className="w-4 h-4 text-gray-400" />
|
|
480
|
+
</button>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
) : (
|
|
485
|
+
<button
|
|
486
|
+
type="button"
|
|
487
|
+
onClick={() => setShowEarnPools(true)}
|
|
488
|
+
className="w-full py-6 px-4 trails-list-item trails-border-radius-container transition-all duration-200 cursor-pointer"
|
|
489
|
+
>
|
|
490
|
+
<div className="flex items-center justify-between">
|
|
491
|
+
<div className="flex items-center space-x-3 flex-1">
|
|
492
|
+
<div className="w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
|
493
|
+
<Search className="w-4 h-4 text-gray-400" />
|
|
494
|
+
</div>
|
|
495
|
+
<div className="text-left flex-1">
|
|
496
|
+
<div className="font-semibold text-gray-900 dark:text-white text-sm">
|
|
497
|
+
Select vault to earn yield with
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
<ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
|
|
502
|
+
</div>
|
|
503
|
+
</button>
|
|
504
|
+
)}
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
{prepareSendQuote?.noSufficientBalance ? (
|
|
508
|
+
<div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
|
|
509
|
+
<div className="flex items-center space-x-2">
|
|
510
|
+
<svg
|
|
511
|
+
className="w-4 h-4 text-amber-500 flex-shrink-0"
|
|
512
|
+
fill="none"
|
|
513
|
+
stroke="currentColor"
|
|
514
|
+
viewBox="0 0 24 24"
|
|
515
|
+
aria-hidden="true"
|
|
516
|
+
>
|
|
517
|
+
<path
|
|
518
|
+
strokeLinecap="round"
|
|
519
|
+
strokeLinejoin="round"
|
|
520
|
+
strokeWidth={2}
|
|
521
|
+
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"
|
|
522
|
+
/>
|
|
523
|
+
</svg>
|
|
524
|
+
<p className="text-sm text-amber-600 dark:text-amber-400">
|
|
525
|
+
Insufficient balance to complete this transaction
|
|
526
|
+
</p>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
) : null}
|
|
530
|
+
|
|
531
|
+
{/* Quote Details */}
|
|
532
|
+
{prepareSendQuote && (
|
|
533
|
+
<div className="mb-4">
|
|
534
|
+
<QuoteDetails quote={prepareSendQuote} showContent={true} />
|
|
535
|
+
</div>
|
|
536
|
+
)}
|
|
537
|
+
|
|
538
|
+
<form onSubmit={handleSubmit}>
|
|
539
|
+
<button
|
|
540
|
+
type="submit"
|
|
541
|
+
disabled={
|
|
542
|
+
!amount ||
|
|
543
|
+
!isValidRecipient ||
|
|
544
|
+
isSubmitting ||
|
|
545
|
+
!originToken ||
|
|
546
|
+
!selectedPool ||
|
|
547
|
+
isLoadingQuote ||
|
|
548
|
+
!prepareSendQuote ||
|
|
549
|
+
prepareSendQuote?.noSufficientBalance
|
|
550
|
+
}
|
|
551
|
+
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`}
|
|
552
|
+
>
|
|
553
|
+
{isSubmitting ? (
|
|
554
|
+
<div className="flex items-center justify-center">
|
|
555
|
+
<Loader2
|
|
556
|
+
className={`w-5 h-5 animate-spin mr-2 ${"text-gray-400"}`}
|
|
557
|
+
/>
|
|
558
|
+
<span>{buttonText}</span>
|
|
559
|
+
</div>
|
|
560
|
+
) : prepareSendQuote?.noSufficientBalance ? (
|
|
561
|
+
"Insufficient Balance"
|
|
562
|
+
) : (
|
|
563
|
+
buttonText || "Deposit"
|
|
564
|
+
)}
|
|
565
|
+
</button>
|
|
566
|
+
</form>
|
|
567
|
+
</div>
|
|
568
|
+
)
|
|
569
|
+
}
|