diff --git a/src/assets/svg/dollar.svg b/src/assets/svg/dollar.svg index 088d261c8d..55a87b5560 100644 --- a/src/assets/svg/dollar.svg +++ b/src/assets/svg/dollar.svg @@ -1,10 +1,12 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/assets/svg/sprite.svg b/src/assets/svg/sprite.svg index 449d60d81f..1cf67b5582 100644 --- a/src/assets/svg/sprite.svg +++ b/src/assets/svg/sprite.svg @@ -3,23 +3,19 @@ + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> @@ -30,41 +26,33 @@ + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> @@ -76,8 +64,7 @@ + fill="currentcolor" /> @@ -85,12 +72,23 @@ + + + + + + + + + fill="currentcolor" /> @@ -101,45 +99,36 @@ + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> @@ -147,17 +136,8 @@ - + @@ -165,41 +145,22 @@ - + - + stroke="currentcolor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /> + + stroke="currentcolor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /> + fill="currentcolor" /> @@ -211,8 +172,7 @@ + fill="#A9A9A9" /> @@ -223,24 +183,18 @@ + fill="currentcolor" /> + stroke="#A9A9A9" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> + fill="currentcolor" /> @@ -252,8 +206,7 @@ + fill="#222222" /> @@ -265,8 +218,7 @@ + fill="currentcolor" /> @@ -278,8 +230,7 @@ + fill="currentcolor" /> @@ -291,8 +242,7 @@ + fill="currentcolor" /> @@ -300,35 +250,26 @@ - + + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> @@ -355,96 +296,72 @@ - + fill="#E84142" /> - - + + fill="white" /> + fill="white" /> - + - - - - + + + + + fill="white" /> + fill="white" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> @@ -453,49 +370,39 @@ - + + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> + fill="currentcolor" /> @@ -516,4 +423,4 @@ - + \ No newline at end of file diff --git a/src/components/Announcement/Popups/TransactionPopup.tsx b/src/components/Announcement/Popups/TransactionPopup.tsx index 8ab9e21076..d1c3d5e057 100644 --- a/src/components/Announcement/Popups/TransactionPopup.tsx +++ b/src/components/Announcement/Popups/TransactionPopup.tsx @@ -166,6 +166,7 @@ const SUMMARY: { [type in TRANSACTION_TYPE]: SummaryFunction } = { [TRANSACTION_TYPE.CANCEL_LIMIT_ORDER]: summaryCancelLimitOrder, [TRANSACTION_TYPE.TRANSFER_TOKEN]: summaryTransferToken, + [TRANSACTION_TYPE.KYBERDAO_CLAIM_GAS_REFUND]: summary1Token, //todo namgold [TRANSACTION_TYPE.KYBERDAO_CLAIM]: summary1Token, [TRANSACTION_TYPE.KYBERDAO_UNDELEGATE]: summaryDelegateDao, [TRANSACTION_TYPE.KYBERDAO_MIGRATE]: summary2Token, diff --git a/src/components/Header/groups/KyberDaoGroup.tsx b/src/components/Header/groups/KyberDaoGroup.tsx index 38165fe4db..11efd6414c 100644 --- a/src/components/Header/groups/KyberDaoGroup.tsx +++ b/src/components/Header/groups/KyberDaoGroup.tsx @@ -45,7 +45,14 @@ const KyberDAONavGroup = () => { Vote - + { + mixpanelHandler(MIXPANEL_TYPE.GAS_REFUND_SOURCE_CLICK, { source: 'KyberDAO_tab' }) + }} + > KNC Utility diff --git a/src/components/Icons/Icon.tsx b/src/components/Icons/Icon.tsx index 43e126e02b..08c51f9056 100644 --- a/src/components/Icons/Icon.tsx +++ b/src/components/Icons/Icon.tsx @@ -1,21 +1,24 @@ import React from 'react' import sprite from 'assets/svg/sprite.svg' +import { ICON_ID } from 'constants/index' export default function Icon({ id, size, style, + color, ...rest }: { - id: string + id: ICON_ID size?: number | string + color?: string style?: React.CSSProperties title?: string }) { return (
- +
diff --git a/src/components/SwapForm/TradeSummary.tsx b/src/components/SwapForm/TradeSummary.tsx index 034410a078..47bf50ce49 100644 --- a/src/components/SwapForm/TradeSummary.tsx +++ b/src/components/SwapForm/TradeSummary.tsx @@ -285,7 +285,12 @@ const TradeSummary: React.FC = ({ routeSummary, slippage }) => { - + { + mixpanelHandler(MIXPANEL_TYPE.GAS_REFUND_SOURCE_CLICK, { source: 'Swap_page_more_info' }) + }} + > {account ? gasRefundPerCentage * 100 : '--'}% Refund diff --git a/src/components/WalletPopup/AccountInfo/ActionButtonGroup.tsx b/src/components/WalletPopup/AccountInfo/ActionButtonGroup.tsx index 8ab430229b..e14573d6b3 100644 --- a/src/components/WalletPopup/AccountInfo/ActionButtonGroup.tsx +++ b/src/components/WalletPopup/AccountInfo/ActionButtonGroup.tsx @@ -6,6 +6,7 @@ import { ReactComponent as DollarIcon } from 'assets/svg/dollar.svg' import { ButtonLight } from 'components/Button' import SendIcon from 'components/Icons/SendIcon' import { ClickHandlerProps } from 'components/WalletPopup/AccountInfo' +import useTheme from 'hooks/useTheme' const ActionButton = styled(ButtonLight)` flex: 0 1 105px; @@ -17,6 +18,7 @@ type Props = { className?: string } & ClickHandlerProps const ActionButtonGroup: React.FC = ({ onClickBuy, onClickReceive, onClickSend, className, disabledSend }) => { + const theme = useTheme() return ( = ({ onClickBuy, onClickReceive, onClic }} > - + Buy diff --git a/src/components/WalletPopup/AccountInfo/MinimalActionButtonGroup.tsx b/src/components/WalletPopup/AccountInfo/MinimalActionButtonGroup.tsx index fcda480fd8..3d275ef7b3 100644 --- a/src/components/WalletPopup/AccountInfo/MinimalActionButtonGroup.tsx +++ b/src/components/WalletPopup/AccountInfo/MinimalActionButtonGroup.tsx @@ -5,6 +5,7 @@ import { ReactComponent as DollarIcon } from 'assets/svg/dollar.svg' import { ButtonLight } from 'components/Button' import SendIcon from 'components/Icons/SendIcon' import { ClickHandlerProps } from 'components/WalletPopup/AccountInfo' +import useTheme from 'hooks/useTheme' const MinimalActionButton = styled(ButtonLight)` flex: 0 0 36px; @@ -23,6 +24,7 @@ const MinimalActionButtonGroup: React.FC = ({ className, disabledSend, }) => { + const theme = useTheme() return ( = ({ }} > - + diff --git a/src/components/WalletPopup/AccountInfo/Settings.tsx b/src/components/WalletPopup/AccountInfo/Settings.tsx deleted file mode 100644 index 7c3e230d35..0000000000 --- a/src/components/WalletPopup/AccountInfo/Settings.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { ChainId } from '@kyberswap/ks-sdk-core' -import { Trans } from '@lingui/macro' -import { rgba } from 'polished' -import { BarChart2, LogOut, Settings as SettingsIcon } from 'react-feather' -import { Text } from 'rebass' -import styled, { css } from 'styled-components' - -import Column from 'components/Column' -import MenuFlyout from 'components/MenuFlyout' -import { PROMM_ANALYTICS_URL } from 'constants/index' -import { useActiveWeb3React } from 'hooks' -import useDisconnectWallet from 'hooks/web3/useDisconnectWallet' -import { ExternalLink } from 'theme' - -const shareStyleMenuItem = css` - display: flex; - align-items: center; - gap: 4px; - font-weight: 400; - cursor: pointer; - padding: 8px 12px; - color: ${({ theme }) => theme.text}; - :hover { - color: ${({ theme }) => theme.primary}; - text-decoration: none; - background-color: ${({ theme }) => rgba(theme.background, 0.2)}; - } -` - -const MenuItem = styled.div` - ${shareStyleMenuItem} -` - -const MenuItemLink = styled(ExternalLink)` - ${shareStyleMenuItem} -` - -const IconWrapper = styled.div` - display: flex; - width: 20px; - height: 20px; - justify-content: center; - align-items: center; -` - -const customStyleMenu = { padding: '8px 0px' } - -const Settings: React.FC = () => { - const disconnectWallet = useDisconnectWallet() - const { chainId, account = '' } = useActiveWeb3React() - - return ( - - - - } - modalWhenMobile={false} - customStyle={customStyleMenu} - > - - {chainId !== ChainId.SOLANA && ( - - - Analytics ↗ - - )} - - - - Disconnect - - - - - ) -} - -export default Settings diff --git a/src/components/WalletPopup/AccountInfo/index.tsx b/src/components/WalletPopup/AccountInfo/index.tsx index e36260ef26..380567c1e7 100644 --- a/src/components/WalletPopup/AccountInfo/index.tsx +++ b/src/components/WalletPopup/AccountInfo/index.tsx @@ -1,26 +1,26 @@ import { Trans } from '@lingui/macro' -import { Eye, EyeOff } from 'react-feather' +import { ChevronRight, Eye, EyeOff, Star } from 'react-feather' import { Flex, Text } from 'rebass' -import styled from 'styled-components' +import styled, { css } from 'styled-components' -import CopyHelper from 'components/Copy' import Loader from 'components/Loader' -import Row from 'components/Row' import ActionButtonGroup from 'components/WalletPopup/AccountInfo/ActionButtonGroup' import CardBackground from 'components/WalletPopup/AccountInfo/CardBackground' import MinimalActionButtonGroup from 'components/WalletPopup/AccountInfo/MinimalActionButtonGroup' -import Settings from 'components/WalletPopup/AccountInfo/Settings' -import { SUPPORTED_WALLETS } from 'constants/wallets' -import { useActiveWeb3React } from 'hooks' +import { useRewards } from 'hooks/useRewards' import useTheme from 'hooks/useTheme' -import { useIsDarkMode } from 'state/user/hooks' -import { ExternalLinkIcon } from 'theme' -import { formatNumberWithPrecisionRange, getEtherscanLink, shortenAddress } from 'utils' +import { formatNumberWithPrecisionRange } from 'utils' + +import { View } from '../type' const ContentWrapper = styled.div` position: relative; width: 100%; - height: 160px; +` + +const RewardWrapper = styled.div` + position: relative; + width: 100%; ` const Content = styled.div` @@ -29,9 +29,10 @@ const Content = styled.div` width: 100%; height: 100%; - padding: 20px; + padding: 12px 16px; display: flex; + gap: 4px; flex-direction: column; justify-content: space-between; ` @@ -39,6 +40,7 @@ const Content = styled.div` const BalanceTitle = styled.span` font-size: 12px; font-weight: 500; + line-height: 16px; color: ${({ theme }) => theme.subText}; ` @@ -68,32 +70,25 @@ const Wrapper = styled.div.attrs(props => ({ display: none; } - &[data-minimal='true'] { - ${MinimalActionButtonGroup} { - display: flex; - align-self: flex-end; - } - ${ActionButtonGroup} { - display: none; - } - ${ContentWrapper} { - height: 120px; - } - ${Content} { - padding: 12px; - } - ${BalanceValue} { - font-size: 20px; - } - } -` - -const IconWrapper = styled.div` - display: flex; - width: 20px; - height: 20px; - justify-content: center; - align-items: center; + ${({ $minimal }) => + $minimal && + css` + & { + ${MinimalActionButtonGroup} { + display: flex; + align-self: flex-end; + } + ${ActionButtonGroup} { + display: none; + } + ${Content} { + padding: 12px; + } + ${BalanceValue} { + font-size: 20px; + } + } + `} ` type Props = { @@ -101,6 +96,7 @@ type Props = { isMinimal: boolean toggleShowBalance: () => void showBalance: boolean + setView: React.Dispatch> } & ClickHandlerProps export type ClickHandlerProps = { @@ -119,37 +115,18 @@ export default function AccountInfo({ isMinimal, showBalance, toggleShowBalance, + setView, }: Props) { - const { chainId, account = '', walletKey } = useActiveWeb3React() const theme = useTheme() - const isDarkMode = useIsDarkMode() + const { + totalReward: { usd }, + } = useRewards() return ( - - - {walletKey && ( - - {SUPPORTED_WALLETS[walletKey].name - - )} - - {shortenAddress(chainId, account, 5, false)} - - - - - - - - - + Total Balance {showBalance ? : } - + {typeof totalBalanceInUsd === 'number' ? ( @@ -193,7 +170,31 @@ export default function AccountInfo({ - + + + + + + + + + Total Available Rewards + + + setView(View.REWARD_CENTER)} + > + + ${formatNumberWithPrecisionRange(usd, 0, 8)} + + + + + + + Your Wallet Address - + theme.subText}; +` + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + flex: 1 1 100%; + gap: 16px; + overflow-y: scroll; +` + +const Content = styled.div` + position: relative; + z-index: 2; + + width: 100%; + height: 100%; + padding: 12px 16px; + + display: flex; + gap: 4px; + flex-direction: column; + justify-content: space-between; +` +const BalanceValue = styled.span` + font-size: 36px; + font-weight: 500; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +` + +const RewardWrapper = styled.div` + display: flex; + border-radius: 44px; + background-color: ${({ theme }) => theme.background}; + width: 100%; + padding: 6px 12px; + align-items: center; + gap: 4px; +` + +const TABS = [ + { + title: t`Voting Rewards`, + value: REWARD_TYPE.VOTING_REWARDS, + }, + { + title: t`Gas Refund`, + value: REWARD_TYPE.GAS_REFUND, + }, +] as { title: string; value: REWARD_TYPE }[] + +export default function RewardCenter() { + const { mixpanelHandler } = useMixpanel() + const theme = useTheme() + const [activeTab, setActiveTab] = useState(REWARD_TYPE.VOTING_REWARDS) + const { rewards, totalReward } = useRewards() + const currentReward = rewards[activeTab] + + const [claiming, setClaiming] = useState(false) + const claimRewards = useCallback(async () => { + try { + setClaiming(true) + mixpanelHandler(MIXPANEL_TYPE.GAS_REFUND_CLAIM_CLICK, { source: 'wallet UI', token_amount: currentReward.knc }) + await currentReward.claim() + } finally { + setClaiming(false) + } + }, [currentReward, mixpanelHandler]) + + return ( + + + + + + + + + Total Available Rewards + + + + {formatNumberWithPrecisionRange(totalReward.knc, 0, 8)} KNC + + {totalReward.usd ? `~ $${formatNumberWithPrecisionRange(totalReward.usd, 0, 8)}` : '$ --'} + + + + + + activeTab={activeTab} setActiveTab={setActiveTab} tabs={TABS} /> + + + Your Reward + + + + + + {currentReward.knc} KNC + + + + + Claim + + + + + + ) +} diff --git a/src/components/WalletPopup/Transactions/Icon.tsx b/src/components/WalletPopup/Transactions/Icon.tsx index 254ee76d69..f018e038b6 100644 --- a/src/components/WalletPopup/Transactions/Icon.tsx +++ b/src/components/WalletPopup/Transactions/Icon.tsx @@ -1,5 +1,6 @@ import { ReactNode } from 'react' import { Repeat } from 'react-feather' +import { DefaultTheme } from 'styled-components' import { ReactComponent as ApproveIcon } from 'assets/svg/approve_icon.svg' import { ReactComponent as BridgeIcon } from 'assets/svg/bridge_icon.svg' @@ -8,9 +9,11 @@ import { ReactComponent as LiquidityIcon } from 'assets/svg/liquidity_icon.svg' import { ReactComponent as ThunderIcon } from 'assets/svg/thunder_icon.svg' import { MoneyBag } from 'components/Icons' import IconFailure from 'components/Icons/Failed' +import IconSprite from 'components/Icons/Icon' import SendIcon from 'components/Icons/SendIcon' import StakeIcon from 'components/Icons/Stake' import VoteIcon from 'components/Icons/Vote' +import useTheme from 'hooks/useTheme' import { TRANSACTION_GROUP, TRANSACTION_TYPE, TransactionDetails } from 'state/transactions/type' const MAP_ICON_BY_GROUP: { [group in TRANSACTION_GROUP]: ReactNode } = { @@ -20,7 +23,10 @@ const MAP_ICON_BY_GROUP: { [group in TRANSACTION_GROUP]: ReactNode } = { [TRANSACTION_GROUP.OTHER]: null, } -const MAP_ICON_BY_TYPE: Partial> = { +const MAP_ICON_BY_TYPE: (theme: DefaultTheme) => Partial> = ( + theme: DefaultTheme, +) => ({ + [TRANSACTION_TYPE.KYBERDAO_CLAIM_GAS_REFUND]: , //todo namgold: test this [TRANSACTION_TYPE.CANCEL_LIMIT_ORDER]: , [TRANSACTION_TYPE.BRIDGE]: , [TRANSACTION_TYPE.CROSS_CHAIN_SWAP]: , @@ -30,10 +36,11 @@ const MAP_ICON_BY_TYPE: Partial> = { [TRANSACTION_TYPE.KYBERDAO_STAKE]: , [TRANSACTION_TYPE.KYBERDAO_MIGRATE]: , [TRANSACTION_TYPE.KYBERDAO_UNSTAKE]: , -} +}) const Icon = ({ txs }: { txs: TransactionDetails }) => { - const icon = MAP_ICON_BY_TYPE[txs.type] || MAP_ICON_BY_GROUP[txs.group] || + const theme = useTheme() + const icon = MAP_ICON_BY_TYPE(theme)[txs.type] || MAP_ICON_BY_GROUP[txs.group] || return icon as JSX.Element } export default Icon diff --git a/src/components/WalletPopup/Transactions/Tab.tsx b/src/components/WalletPopup/Transactions/Tab.tsx index 15548e492e..265795e721 100644 --- a/src/components/WalletPopup/Transactions/Tab.tsx +++ b/src/components/WalletPopup/Transactions/Tab.tsx @@ -1,12 +1,7 @@ -import { ChainId } from '@kyberswap/ks-sdk-core' -import { t } from '@lingui/macro' -import { memo, useLayoutEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useLayoutEffect, useState } from 'react' import styled, { css } from 'styled-components' import Row from 'components/Row' -import { useActiveWeb3React } from 'hooks' -import { isSupportKyberDao } from 'hooks/kyberdao' -import { TRANSACTION_GROUP } from 'state/transactions/type' const ListTab = styled.div` display: flex; @@ -31,7 +26,7 @@ const TabWrapper = styled(Row).attrs(props => ({ position: relative; width: 100%; - background-color: ${({ theme }) => theme.background}; + background-color: ${({ theme }) => theme.buttonBlack}; border-radius: 20px; justify-content: center; @@ -93,13 +88,8 @@ const TabWrapper = styled(Row).attrs(props => ({ } ` -const tabActiveCSS = css` - border-radius: 20px; - color: ${({ theme }) => theme.text}; - background-color: ${({ theme }) => (theme.darkMode ? theme.tabActive : theme.buttonGray)}; -` - const TabItem = styled.div<{ active: boolean }>` + width: 100%; padding: 6px; font-weight: 500; font-size: 12px; @@ -108,92 +98,67 @@ const TabItem = styled.div<{ active: boolean }>` cursor: pointer; user-select: none; color: ${({ theme }) => theme.subText}; + border-radius: 20px; :hover { - ${tabActiveCSS} + color: ${({ theme }) => theme.text}; + background-color: ${({ theme }) => theme.tabActive}; } - ${({ active }) => (active ? tabActiveCSS : '')} + ${({ active }) => + active + ? css` + color: ${({ theme }) => theme.text}; + background-color: ${({ theme }) => theme.border} !important; + ` + : null} ` -const listTab = [ - { text: t`All`, value: '' }, - { text: t`Swaps`, value: TRANSACTION_GROUP.SWAP }, - { text: t`Liquidity`, value: TRANSACTION_GROUP.LIQUIDITY }, - { text: t`KyberDAO`, value: TRANSACTION_GROUP.KYBERDAO }, - { text: t`Others`, value: TRANSACTION_GROUP.OTHER }, -] as const - -type Props = { - activeTab: string - setActiveTab: React.Dispatch> + +interface TabProps { + activeTab: T + setActiveTab: React.Dispatch> + tabs: readonly { readonly title: string; readonly value: T }[] } -const Tab: React.FC = ({ activeTab, setActiveTab }) => { +function Tab({ activeTab, setActiveTab, tabs }: TabProps) { const [isScrollable, setScrollable] = useState(false) const [scrollLeft, setScrollLeft] = useState(false) const [scrollRight, setScrollRight] = useState(false) - const { chainId } = useActiveWeb3React() - - const listRef = useRef(null) - - const handleScroll = () => { - const node = listRef.current - if (!node) { - return - } + const [listRef, setListRef] = useState(null) - const { clientWidth, scrollWidth, scrollLeft } = node + const handleScroll = useCallback(() => { + if (!listRef) return + const { clientWidth, scrollWidth, scrollLeft } = listRef setScrollable(clientWidth < scrollWidth) setScrollLeft(scrollLeft > 0) setScrollRight(scrollLeft < scrollWidth - clientWidth) - } + }, [listRef]) useLayoutEffect(() => { - const { ResizeObserver } = window - const node = listRef.current - if (!node) { - return - } - + if (!listRef) return const resizeHandler = () => { - const { clientWidth, scrollWidth, scrollLeft } = node + const { clientWidth, scrollWidth, scrollLeft } = listRef setScrollable(clientWidth < scrollWidth) setScrollLeft(scrollLeft > 0) setScrollRight(scrollLeft < scrollWidth - clientWidth) } + const { ResizeObserver } = window if (typeof ResizeObserver === 'function') { const resizeObserver = new ResizeObserver(resizeHandler) - resizeObserver.observe(node) + resizeObserver.observe(listRef) return () => resizeObserver.disconnect() } else { window.addEventListener('resize', resizeHandler) return () => window.removeEventListener('resize', resizeHandler) } - }, []) - - const filterTab = useMemo(() => { - return listTab.filter(tab => { - if (tab.value === TRANSACTION_GROUP.KYBERDAO) { - return isSupportKyberDao(chainId) - } - if (tab.value === TRANSACTION_GROUP.LIQUIDITY) { - return chainId !== ChainId.SOLANA - } - return true - }) - }, [chainId]) + }, [listRef]) return ( - - {filterTab.map(tab => ( - setActiveTab(tab.value)} - > - {tab.text} + setListRef(listRef)} onScroll={handleScroll}> + {tabs.map(tab => ( + setActiveTab(tab.value)}> + {tab.title} ))} @@ -201,4 +166,4 @@ const Tab: React.FC = ({ activeTab, setActiveTab }) => { ) } -export default memo(Tab) +export default Tab diff --git a/src/components/WalletPopup/Transactions/TransactionItem.tsx b/src/components/WalletPopup/Transactions/TransactionItem.tsx index e2045d2e0f..c955375de8 100644 --- a/src/components/WalletPopup/Transactions/TransactionItem.tsx +++ b/src/components/WalletPopup/Transactions/TransactionItem.tsx @@ -70,7 +70,7 @@ const Description1Token = (transaction: TransactionDetails) => { const { extraInfo = {}, type } = transaction const { tokenSymbol, tokenAmount, tokenAddress } = extraInfo as TransactionExtraInfo1Token // +10KNC or -10KNC - const plus = [TRANSACTION_TYPE.KYBERDAO_CLAIM].includes(type) + const plus = [TRANSACTION_TYPE.KYBERDAO_CLAIM, TRANSACTION_TYPE.KYBERDAO_CLAIM_GAS_REFUND].includes(type) return } @@ -342,13 +342,14 @@ const DESCRIPTION_MAP: { [TRANSACTION_TYPE.ELASTIC_DEPOSIT_LIQUIDITY]: DescriptionStakeFarm, [TRANSACTION_TYPE.ELASTIC_WITHDRAW_LIQUIDITY]: DescriptionStakeFarm, - [TRANSACTION_TYPE.KYBERDAO_CLAIM]: Description1Token, - [TRANSACTION_TYPE.APPROVE]: DescriptionApproveClaim, [TRANSACTION_TYPE.CLAIM_REWARD]: DescriptionApproveClaim, [TRANSACTION_TYPE.KYBERDAO_STAKE]: DescriptionKyberDaoStake, [TRANSACTION_TYPE.KYBERDAO_UNSTAKE]: DescriptionKyberDaoStake, + [TRANSACTION_TYPE.KYBERDAO_CLAIM]: Description1Token, + [TRANSACTION_TYPE.KYBERDAO_CLAIM_GAS_REFUND]: Description1Token, //todo namgold: test this + [TRANSACTION_TYPE.TRANSFER_TOKEN]: Description1Token, [TRANSACTION_TYPE.UNWRAP_TOKEN]: Description2Token, diff --git a/src/components/WalletPopup/Transactions/index.tsx b/src/components/WalletPopup/Transactions/index.tsx index 223892b72f..cc38ea1e58 100644 --- a/src/components/WalletPopup/Transactions/index.tsx +++ b/src/components/WalletPopup/Transactions/index.tsx @@ -1,4 +1,5 @@ -import { Trans } from '@lingui/macro' +import { ChainId } from '@kyberswap/ks-sdk-core' +import { Trans, t } from '@lingui/macro' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Info } from 'react-feather' import AutoSizer from 'react-virtualized-auto-sizer' @@ -11,6 +12,7 @@ import { NUMBERS } from 'components/WalletPopup/Transactions/helper' import useCancellingOrders, { CancellingOrderInfo } from 'components/swapv2/LimitOrder/useCancellingOrders' import { useActiveWeb3React } from 'hooks' import { fetchListTokenByAddresses, findCacheToken, useIsLoadedTokenDefault } from 'hooks/Tokens' +import { isSupportKyberDao } from 'hooks/kyberdao' import useTheme from 'hooks/useTheme' import { useSortRecentTransactions } from 'state/transactions/hooks' import { @@ -103,6 +105,13 @@ function RowItem({ /> ) } +const listTab = [ + { title: t`All`, value: '' }, + { title: t`Swaps`, value: TRANSACTION_GROUP.SWAP }, + { title: t`Liquidity`, value: TRANSACTION_GROUP.LIQUIDITY }, + { title: t`KyberDAO`, value: TRANSACTION_GROUP.KYBERDAO }, + { title: t`Others`, value: TRANSACTION_GROUP.OTHER }, +] as const // This is intentional, we don't need to persist in localStorage let storedActiveTab = '' @@ -165,9 +174,21 @@ function ListTransaction({ isMinimal }: { isMinimal: boolean }) { storedActiveTab = activeTab }, [activeTab]) + const filterTab = useMemo(() => { + return listTab.filter(tab => { + if (tab.value === TRANSACTION_GROUP.KYBERDAO) { + return isSupportKyberDao(chainId) + } + if (tab.value === TRANSACTION_GROUP.LIQUIDITY) { + return chainId !== ChainId.SOLANA + } + return true + }) + }, [chainId]) + return ( - + activeTab={activeTab} setActiveTab={setActiveTab} tabs={filterTab} /> {formatTransactions.length === 0 ? ( diff --git a/src/components/WalletPopup/WalletView.tsx b/src/components/WalletPopup/WalletView.tsx index ab82b8aae6..6456c9c52e 100644 --- a/src/components/WalletPopup/WalletView.tsx +++ b/src/components/WalletPopup/WalletView.tsx @@ -1,12 +1,13 @@ -import { Trans, t } from '@lingui/macro' +import { Trans } from '@lingui/macro' import { rgba } from 'polished' import { useEffect, useLayoutEffect, useRef, useState } from 'react' -import { ChevronLeft, FileText, StopCircle, X } from 'react-feather' +import { ChevronLeft, FileText, LogOut, StopCircle, X } from 'react-feather' import { useNavigate } from 'react-router-dom' import { Flex, Text } from 'rebass' import styled from 'styled-components' import { ReactComponent as DragHandleIcon } from 'assets/svg/wallet_drag_handle.svg' +import CopyHelper from 'components/Copy' import SendIcon from 'components/Icons/SendIcon' import Row from 'components/Row' import AccountInfo from 'components/WalletPopup/AccountInfo' @@ -14,15 +15,40 @@ import MyAssets from 'components/WalletPopup/MyAssets' import PinButton from 'components/WalletPopup/PinButton' import SendToken from 'components/WalletPopup/SendToken' import { APP_PATHS } from 'constants/index' +import { SUPPORTED_WALLETS } from 'constants/wallets' +import { useActiveWeb3React } from 'hooks' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' +import useDisconnectWallet from 'hooks/web3/useDisconnectWallet' +import { useIsDarkMode } from 'state/user/hooks' import { useTokensHasBalance } from 'state/wallet/hooks' +import { ExternalLinkIcon } from 'theme' +import { getEtherscanLink, shortenAddress } from 'utils' import ReceiveToken from './ReceiveToken' +import RewardCenter from './RewardCenter' import ListTransaction from './Transactions' +import { View } from './type' export const HANDLE_CLASS_NAME = 'walletPopupDragHandle' +const IconWrapper = styled.div` + display: flex; + width: 20px; + height: 20px; + justify-content: center; + align-items: center; +` + +const LogOutIcon = styled(LogOut)` + cursor: pointer; + color: ${({ theme }) => theme.subText}; + :hover { + opacity: 0.8; + text-decoration: none; + } +` + type WrapperProps = { $pinned: boolean; $blur: boolean } const Wrapper = styled.div.attrs(props => ({ 'data-pinned': props.$pinned, @@ -76,13 +102,6 @@ const ContentWrapper = styled.div` gap: 14px; ` -const View = { - ASSETS: t`Assets`, - SEND_TOKEN: t`Send`, - RECEIVE_TOKEN: t`Receive`, - TRANSACTIONS: t`Transactions`, -} as const - type Props = { onDismiss: () => void onPin?: () => void @@ -110,6 +129,9 @@ export default function WalletView({ const navigate = useNavigate() const nodeRef = useRef(null) const [isMinimal, setMinimal] = useState(false) + const { chainId, account = '', walletKey } = useActiveWeb3React() + const isDarkMode = useIsDarkMode() + const disconnectWallet = useDisconnectWallet() const { loading: loadingTokens, @@ -167,6 +189,7 @@ export default function WalletView({ onClickSend={handleClickSend} isMinimal={isMinimal} disabledSend={!currencies.length} + setView={setView} /> ) } @@ -200,12 +223,15 @@ export default function WalletView({ return case View.RECEIVE_TOKEN: return + case View.REWARD_CENTER: + return } return null } const isSendTab = view === View.SEND_TOKEN - const isExchangeTokenTab = isSendTab || view === View.RECEIVE_TOKEN + const isShowArrow = isSendTab || view === View.RECEIVE_TOKEN + const isShowBack = isShowArrow || view === View.REWARD_CENTER useLayoutEffect(() => { // handle minimal mode when width & height become small @@ -286,17 +312,34 @@ export default function WalletView({ justifyContent: 'space-between', }} > - {isExchangeTokenTab ? ( + {isShowBack ? ( <> setView(View.ASSETS)} color={theme.subText} /> - {view} + {isShowArrow && ( + + )}{' '} + {view} ) : ( - - Your Account - + + {walletKey && ( + + {SUPPORTED_WALLETS[walletKey].name + + )} + + {shortenAddress(chainId, account, 5, false)} + + + + + )} {onPin && onUnpin && } diff --git a/src/components/WalletPopup/index.tsx b/src/components/WalletPopup/index.tsx index 255819cba1..aa2fef74e8 100644 --- a/src/components/WalletPopup/index.tsx +++ b/src/components/WalletPopup/index.tsx @@ -125,11 +125,10 @@ const WalletPopup: React.FC = ({ isModalOpen, onDismissModal, isPinned, s zIndex: isPinned ? Z_INDEXS.WALLET_POPUP : Z_INDEXS.MODAL + 1, cursor: 'auto', transition: 'top 150ms, left 150ms', - top, - left, }} enableResizing={isPinned} disableDragging={!isPinned} + bounds="body" > { - if (!rewardDistributorContract) { - throw new Error(CONTRACT_NOT_FOUND_MSG) - } - try { - const isValidClaim = await rewardDistributorContract.isValidClaim( - cycle, - index, - address, - tokens, - cumulativeAmounts, - merkleProof, - ) - if (!isValidClaim) { - throw new Error('Invalid claim') - } - const estimateGas = await rewardDistributorContract.estimateGas.claim( - cycle, - index, - address, - tokens, - cumulativeAmounts, - merkleProof, - ) - const tx = await rewardDistributorContract.claim( - cycle, - index, - address, - tokens, - cumulativeAmounts, - merkleProof, - { - gasLimit: calculateGasMargin(estimateGas), - }, - ) - addTransactionWithType({ - hash: tx.hash, - type: TRANSACTION_TYPE.KYBERDAO_CLAIM, - extraInfo: { - contract: kyberDaoInfo?.rewardsDistributor, - tokenAmount: formatAmount, - tokenSymbol: 'KNC', - tokenAddress: kyberDaoInfo?.KNCAddress, - }, - }) - return tx.hash - } catch (error) { - if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { - throw new Error('Transaction rejected.') - } else { - throw error - } + const claimVotingRewards = useCallback(async () => { + if (!userRewards || !userRewards.userReward || !account) throw new Error(t`Invalid claim`) + const { cycle, userReward } = userRewards + const { index, tokens, cumulativeAmounts, proof } = userReward + const address = account + const merkleProof = proof + const formatAmount = formatUnitsToFixed(remainingCumulativeAmount) + + if (!rewardDistributorContract) { + throw new Error(CONTRACT_NOT_FOUND_MSG) + } + try { + const isValidClaim = await rewardDistributorContract.isValidClaim( + cycle, + index, + address, + tokens, + cumulativeAmounts, + merkleProof, + ) + if (!isValidClaim) throw new Error(t`Invalid claim`) + const estimateGas = await rewardDistributorContract.estimateGas.claim( + cycle, + index, + address, + tokens, + cumulativeAmounts, + merkleProof, + ) + const tx = await rewardDistributorContract.claim(cycle, index, address, tokens, cumulativeAmounts, merkleProof, { + gasLimit: calculateGasMargin(estimateGas), + }) + addTransactionWithType({ + hash: tx.hash, + type: TRANSACTION_TYPE.KYBERDAO_CLAIM, + extraInfo: { + contract: kyberDaoInfo?.rewardsDistributor, + tokenAmount: formatAmount, + tokenSymbol: 'KNC', + tokenAddress: kyberDaoInfo?.KNCAddress, + }, + }) + return tx.hash as string + } catch (error) { + if (error?.code === 4001 || error?.code === 'ACTION_REJECTED') { + throw new Error('Transaction rejected.') + } else { + throw error } - }, - [rewardDistributorContract, addTransactionWithType, kyberDaoInfo], - ) - return { claim } + } + }, [ + userRewards, + account, + remainingCumulativeAmount, + rewardDistributorContract, + addTransactionWithType, + kyberDaoInfo?.rewardsDistributor, + kyberDaoInfo?.KNCAddress, + ]) + return claimVotingRewards } export const useVotingActions = () => { @@ -426,15 +424,8 @@ export function useVotingInfo() { if (!userRewards?.userReward?.tokens || !claimedRewardAmounts?.[0]) return BigNumber.from(0) return ( userRewards?.userReward?.tokens?.map((_: string, index: number) => { - const cummulativeAmount = - userRewards.userReward && - userRewards.userReward.cumulativeAmounts && - userRewards.userReward.cumulativeAmounts[index] - - if (!cummulativeAmount) { - return BigNumber.from(0) - } - + const cummulativeAmount = userRewards.userReward?.cumulativeAmounts?.[index] + if (cummulativeAmount) return BigNumber.from(0) return BigNumber.from(cummulativeAmount).sub(BigNumber.from(claimedRewardAmounts[0])) })[0] || BigNumber.from(0) ) @@ -512,7 +503,7 @@ export function useVotingInfo() { fetcher(url).then(res => res.rewardStats), ) - return { + const result = { daoInfo: daoInfo || localStoredDaoInfo || undefined, userRewards, calculateVotingPower, @@ -528,16 +519,7 @@ export function useVotingInfo() { usd: rewardStats ? +rewardStats.pending?.totalAmountInUSD + +rewardStats.liquidated?.totalAmountInUSD : 0, }, } -} - -const aggregateValue = ( - values: ({ [key in T]: string | number } | undefined)[], - field: T, -): number => { - return values.reduce((acc, cur) => { - const value = cur?.[field] ?? 0 - return (typeof value === 'number' ? value : parseFloat(value)) + acc - }, 0) + return result } export function useGasRefundTier(): GasRefundTierInfo { @@ -600,6 +582,74 @@ export function useGasRefundInfo({ rewardStatus = KNCUtilityTabs.Available }: { } } +export function useClaimGasRefundRewards() { + const { account, chainId } = useActiveWeb3React() + const { library, connector } = useWeb3React() + const addTransactionWithType = useTransactionAdder() + const { claimableReward } = useGasRefundInfo({}) + const notify = useNotify() + + const claimGasRefundRewards = useCallback(async (): Promise => { + if (!account || !library || !claimableReward || claimableReward.knc <= 0) throw new Error(t`Invalid claim`) + + const url = REWARD_SERVICE_API + '/rewards/claim' + const data = { + wallet: account, + chainId: chainId.toString(), + clientCode: 'gas-refund', + ref: '', + } + let response: any + try { + response = await axios({ method: 'POST', url, data }) + if (response?.data?.code !== 200000) throw new Error(response?.data?.message) + } catch (error) { + console.error('Claim error:', { error }) + notify({ + title: t`Claim Error`, + summary: error?.response?.data?.message || error?.message || 'Unknown error', + type: NotificationType.ERROR, + }) + throw error + } + + const rewardContractAddress = response.data.data.ContractAddress + const encodedData = response.data.data.EncodedData + try { + const tx = await sendEVMTransaction(account, library, rewardContractAddress, encodedData, BigNumber.from(0)) + if (!tx) throw new Error() + addTransactionWithType({ + hash: tx.hash, + type: TRANSACTION_TYPE.KYBERDAO_CLAIM_GAS_REFUND, + extraInfo: { + tokenAddress: KNC[chainId].address, + tokenAmount: claimableReward.knc.toString(), + tokenSymbol: 'KNC', + }, + }) + return tx.hash as string + } catch (error) { + if (didUserReject(connector, error)) { + notify({ + title: t`Transaction rejected`, + summary: t`In order to claim, you must accept in your wallet.`, + type: NotificationType.ERROR, + }) + throw new Error('Transaction rejected.') + } else { + console.error('Claim error:', { error }) + notify({ + title: t`Claim Error`, + summary: error.message || 'Unknown error', + type: NotificationType.ERROR, + }) + throw error + } + } + }, [account, addTransactionWithType, chainId, claimableReward, library, notify, connector]) + return claimGasRefundRewards +} + export const useEligibleTransactions = (page = 1, pageSize = 100): EligibleTxsInfo | undefined => { const { account, chainId } = useActiveWeb3React() const kyberDaoInfo = useKyberDAOInfo() diff --git a/src/hooks/useMixpanel.ts b/src/hooks/useMixpanel.ts index 7e4bca5b85..6f074f504f 100644 --- a/src/hooks/useMixpanel.ts +++ b/src/hooks/useMixpanel.ts @@ -174,6 +174,8 @@ export enum MIXPANEL_TYPE { KYBER_DAO_VOTE_CLICK, KYBER_DAO_CLAIM_CLICK, KYBER_DAO_FEATURE_REQUEST_CLICK, + GAS_REFUND_CLAIM_CLICK, + GAS_REFUND_SOURCE_CLICK, // notification NOTIFICATION_CLICK_MENU, @@ -1067,6 +1069,16 @@ export default function useMixpanel(currencies?: { [field in Field]?: Currency } mixpanel.track('KyberDAO - Feature Request Click', payload) break } + case MIXPANEL_TYPE.GAS_REFUND_CLAIM_CLICK: { + const { token_amount, source } = payload + mixpanel.track('Gas refund - Click claim reward', { token_amount, source }) + break + } + case MIXPANEL_TYPE.GAS_REFUND_SOURCE_CLICK: { + const { source } = payload + mixpanel.track('Gas refund - KNC Utility source click', { source }) + break + } case MIXPANEL_TYPE.LO_CLICK_PLACE_ORDER: { mixpanel.track('Limit Order - Place Order Click', payload) break @@ -1726,6 +1738,7 @@ export const useGlobalMixpanelEvents = () => { 'cross-chain': 'Cross Chain', 'notification-center': 'Notification', [APP_PATHS.KYBERAI_ABOUT]: 'KyberAI About', + [APP_PATHS.KYBERDAO_KNC_UTILITY]: 'Gas refund - KNC Utility', } const protectedPaths: { [key: string]: string } = { [APP_PATHS.KYBERAI_RANKINGS]: 'KyberAI Rankings', diff --git a/src/hooks/useRewards.ts b/src/hooks/useRewards.ts new file mode 100644 index 0000000000..d1314bd0f5 --- /dev/null +++ b/src/hooks/useRewards.ts @@ -0,0 +1,44 @@ +import { useMemo } from 'react' + +import { REWARD_TYPE } from 'components/WalletPopup/type' +import { useKNCPrice } from 'state/application/hooks' +import { aggregateValue } from 'utils/array' + +import { useClaimGasRefundRewards, useClaimVotingRewards, useGasRefundInfo, useVotingInfo } from './kyberdao' + +export const useRewards = () => { + const kncPrice = useKNCPrice() + const { claimableReward } = useGasRefundInfo({}) + const { knc, usd } = claimableReward || {} + const { remainingCumulativeAmount } = useVotingInfo() + + const claimGasRefund = useClaimGasRefundRewards() + const claimVotingRewards = useClaimVotingRewards() + + const rewards: { [key in REWARD_TYPE]: { knc: number; usd: number; claim: () => Promise } } = useMemo(() => { + return { + [REWARD_TYPE.GAS_REFUND]: { knc: knc || 0, usd: usd || 0, claim: claimGasRefund }, + [REWARD_TYPE.VOTING_REWARDS]: { + knc: remainingCumulativeAmount.toNumber(), + usd: remainingCumulativeAmount.toNumber() * kncPrice, + claim: claimVotingRewards, + }, + } + }, [knc, kncPrice, remainingCumulativeAmount, usd, claimVotingRewards, claimGasRefund]) + + const totalReward = useMemo(() => { + const rewardsValues = Object.values(rewards) + return { + usd: aggregateValue(rewardsValues, 'usd'), + knc: aggregateValue(rewardsValues, 'knc'), + } + }, [rewards]) + + return useMemo( + () => ({ + rewards, + totalReward, + }), + [rewards, totalReward], + ) +} diff --git a/src/pages/Icons/index.tsx b/src/pages/Icons/index.tsx index f30b54b9a9..110b3d11d5 100644 --- a/src/pages/Icons/index.tsx +++ b/src/pages/Icons/index.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components' import sprite from 'assets/svg/sprite.svg' +import { ICON_IDS } from 'constants/index' const Wrapper = styled.div` display: flex; @@ -32,36 +33,11 @@ const IconWrapper = styled.div` flex: 1; } ` -const iconIds = [ - 'truesight-v2', - 'notification-2', - 'bullish', - 'bearish', - 'trending-soon', - 'flame', - 'download', - 'upload', - 'coin-bag', - 'speaker', - 'share', - 'swap', - 'copy', - 'open-link', - 'star', - 'fullscreen', - 'leaderboard', - 'liquid', - 'alarm', - 'on-chain', - 'technical-analysis', - 'news', - 'arrow', -] export default function Icons() { return ( - {iconIds.map((id: string) => ( + {ICON_IDS.map(id => ( diff --git a/src/pages/KyberDAO/KNCUtility/GasRefundBox.tsx b/src/pages/KyberDAO/KNCUtility/GasRefundBox.tsx index 60c820484a..5c123a6f4c 100644 --- a/src/pages/KyberDAO/KNCUtility/GasRefundBox.tsx +++ b/src/pages/KyberDAO/KNCUtility/GasRefundBox.tsx @@ -1,34 +1,28 @@ -import { Trans, t } from '@lingui/macro' -import axios from 'axios' -import { BigNumber } from 'ethers' +import { Trans } from '@lingui/macro' import { darken } from 'polished' import { useCallback, useState } from 'react' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import styled, { css } from 'styled-components' -import { NotificationType } from 'components/Announcement/type' import { ButtonLight, ButtonPrimary } from 'components/Button' import Dots from 'components/Dots' import { RowBetween } from 'components/Row' import { MouseoverTooltip, TextDashed } from 'components/Tooltip' -import { REWARD_SERVICE_API } from 'constants/env' -import { KNC } from 'constants/tokens' -import { useActiveWeb3React, useWeb3React } from 'hooks' +import { useActiveWeb3React } from 'hooks' import { isSupportKyberDao, + useClaimGasRefundRewards, useEligibleTransactions, useGasRefundInfo, useGasRefundTier, useVotingInfo, } from 'hooks/kyberdao' +import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' -import { useNotify, useOpenNetworkModal, useWalletModalToggle } from 'state/application/hooks' -import { useTransactionAdder } from 'state/transactions/hooks' -import { TRANSACTION_TYPE } from 'state/transactions/type' +import { useOpenNetworkModal, useWalletModalToggle } from 'state/application/hooks' import { LinkStyledButton, MEDIA_WIDTHS } from 'theme' import { formattedNum } from 'utils' -import { sendEVMTransaction } from 'utils/sendTransaction' import TimerCountdown from '../TimerCountdown' import EligibleTxModal from './EligibleTxModal' @@ -77,87 +71,35 @@ const Tab = styled(Text)<{ active?: boolean }>` ` export default function GasRefundBox() { + const { mixpanelHandler } = useMixpanel() const { account, chainId } = useActiveWeb3React() - const { library } = useWeb3React() const [selectedTab, setSelectedTab] = useState(KNCUtilityTabs.Available) const theme = useTheme() const { totalReward, reward, claimableReward } = useGasRefundInfo({ rewardStatus: selectedTab }) const toggleWalletModal = useWalletModalToggle() const [isShowEligibleTx, setShowEligibleTx] = useState(false) const openNetworkModal = useOpenNetworkModal() - const notify = useNotify() - const [claiming, setClaiming] = useState(false) - const addTransactionWithType = useTransactionAdder() const eligibleTxs = useEligibleTransactions(1, 1) const upToXXSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToXXSmall}px)`) const upToExtraSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToExtraSmall}px)`) const { userTier, gasRefundPerCentage } = useGasRefundTier() const { daoInfo: { first_epoch_start_timestamp = 0, current_epoch = 0, epoch_period_in_seconds = 0 } = {} } = useVotingInfo() - const claimRewards = useCallback(async () => { - if (!account || !library || !claimableReward || claimableReward.knc <= 0) return - - setClaiming(true) - const url = REWARD_SERVICE_API + '/rewards/claim' - const data = { - wallet: account, - chainId: chainId.toString(), - clientCode: 'gas-refund', - ref: '', - } - let response: any - try { - response = await axios({ method: 'POST', url, data }) - if (response?.data?.code !== 200000) throw new Error(response?.data?.message) - } catch (error) { - console.error('Claim error:', { error }) - notify({ - title: t`Claim Error`, - summary: error?.response?.data?.message || error?.message || 'Unknown error', - type: NotificationType.ERROR, - }) - setClaiming(false) - return - } - - const rewardContractAddress = response.data.data.ContractAddress - const encodedData = response.data.data.EncodedData + const claimReward = useClaimGasRefundRewards() + const [claiming, setClaiming] = useState(false) + const handleClaimReward = useCallback(async () => { try { - const tx = await sendEVMTransaction( - account, - library, - rewardContractAddress, - encodedData, - BigNumber.from(0), - async transactionResponse => { - const transactionReceipt = await transactionResponse.wait() - if (transactionReceipt.status === 1) { - setClaiming(false) - } - }, - ) - if (!tx) throw new Error() - addTransactionWithType({ - hash: tx.hash, - type: TRANSACTION_TYPE.CLAIM_REWARD, - extraInfo: { - tokenAddress: KNC[chainId].address, - tokenAmount: claimableReward.knc.toString(), - tokenSymbol: 'KNC', - }, - }) - } catch (error) { - console.error('Claim error:', { error }) - notify({ - title: t`Claim Error`, - summary: error.message || 'Unknown error', - type: NotificationType.ERROR, + setClaiming(true) + mixpanelHandler(MIXPANEL_TYPE.GAS_REFUND_CLAIM_CLICK, { + source: 'KNC Utility page', + token_amount: claimableReward?.knc, }) + await claimReward() } finally { setClaiming(false) } - }, [account, addTransactionWithType, chainId, claimableReward, library, notify]) + }, [claimReward, claimableReward?.knc, mixpanelHandler]) return ( @@ -228,7 +170,11 @@ export default function GasRefundBox() { account ? ( isSupportKyberDao(chainId) ? ( claiming ? ( - + Claiming @@ -236,7 +182,7 @@ export default function GasRefundBox() { ) : ( Claim diff --git a/src/pages/KyberDAO/StakeKNC/index.tsx b/src/pages/KyberDAO/StakeKNC/index.tsx index 7fdaf3e892..9ad3efce8a 100644 --- a/src/pages/KyberDAO/StakeKNC/index.tsx +++ b/src/pages/KyberDAO/StakeKNC/index.tsx @@ -241,7 +241,14 @@ export default function StakeKNC() { Discover more staking KNC utility and benefits{' '} - here ↗ + { + mixpanelHandler(MIXPANEL_TYPE.GAS_REFUND_SOURCE_CLICK, { source: 'StakeKNC_page_KNC_utility' }) + }} + > + here ↗ + @@ -250,7 +257,17 @@ export default function StakeKNC() { text={ Tier {userTier} - You are eligible for{' '} - {gasRefundPerCentage * 100}% gas refund. + { + mixpanelHandler(MIXPANEL_TYPE.GAS_REFUND_SOURCE_CLICK, { + source: 'StakeKNC_page_KNC_utility_tier', + }) + }} + > + {gasRefundPerCentage * 100}% gas refund + + . } > diff --git a/src/pages/KyberDAO/Vote/index.tsx b/src/pages/KyberDAO/Vote/index.tsx index 2c99b87af2..f6138f0101 100644 --- a/src/pages/KyberDAO/Vote/index.tsx +++ b/src/pages/KyberDAO/Vote/index.tsx @@ -15,7 +15,7 @@ import { AutoRow, RowBetween, RowFit } from 'components/Row' import { MouseoverTooltip } from 'components/Tooltip' import TransactionConfirmationModal, { TransactionErrorContent } from 'components/TransactionConfirmationModal' import { useActiveWeb3React } from 'hooks' -import { useClaimRewardActions, useVotingActions, useVotingInfo } from 'hooks/kyberdao' +import { useClaimVotingRewards, useVotingActions, useVotingInfo } from 'hooks/kyberdao' import useMixpanel, { MIXPANEL_TYPE } from 'hooks/useMixpanel' import useTheme from 'hooks/useTheme' import { ApplicationModal } from 'state/application/actions' @@ -119,7 +119,6 @@ export default function Vote() { daoInfo, remainingCumulativeAmount, claimedRewardAmount, - userRewards, stakerInfo, stakerInfoNextEpoch, rewardStats: { knc, usd }, @@ -127,7 +126,7 @@ export default function Vote() { const kncPrice = useKNCPrice() - const { claim } = useClaimRewardActions() + const claimVotingRewards = useClaimVotingRewards() const { vote } = useVotingActions() const { switchToEthereum } = useSwitchToEthereum() @@ -175,34 +174,21 @@ export default function Vote() { }, [toggleClaimConfirmModal, mixpanelHandler, switchToEthereum]) const handleConfirmClaim = useCallback(async () => { - if (!userRewards || !userRewards.userReward || !account) return - const { cycle, userReward } = userRewards - const { index, tokens, cumulativeAmounts, proof } = userReward setPendingText(t`Claming ${formatUnitsToFixed(remainingCumulativeAmount)} KNC`) setShowConfirm(true) setAttemptingTxn(true) toggleClaimConfirmModal() - const params = { - cycle, - index, - address: account, - tokens, - cumulativeAmounts, - merkleProof: proof, - formatAmount: formatUnitsToFixed(remainingCumulativeAmount), + try { + const tx = await claimVotingRewards() + setTxHash(tx) + } catch (error) { + setTransactionError(error?.message) + setTxHash(undefined) + } finally { + setAttemptingTxn(false) } - claim(params) - .then(tx => { - setAttemptingTxn(false) - setTxHash(tx) - }) - .catch(error => { - setTransactionError(error?.message) - setAttemptingTxn(false) - setTxHash(undefined) - }) - }, [userRewards, account, claim, remainingCumulativeAmount, toggleClaimConfirmModal]) + }, [claimVotingRewards, remainingCumulativeAmount, toggleClaimConfirmModal]) const handleVote = useCallback( async (proposal_id: number, option: number) => { diff --git a/src/pages/TrueSightV2/components/MultipleChainDropdown.tsx b/src/pages/TrueSightV2/components/MultipleChainDropdown.tsx index e7f7b3d1e1..4f42cc20c0 100644 --- a/src/pages/TrueSightV2/components/MultipleChainDropdown.tsx +++ b/src/pages/TrueSightV2/components/MultipleChainDropdown.tsx @@ -7,6 +7,7 @@ import Column from 'components/Column' import Icon from 'components/Icons/Icon' import Modal from 'components/Modal' import Row, { RowFit } from 'components/Row' +import { ICON_ID } from 'constants/index' import useTheme from 'hooks/useTheme' import SimpleTooltip from './SimpleTooltip' @@ -46,7 +47,7 @@ const StyledMobileChainIcon = styled.div` gap: 6px; padding: 10px 8px; ` -const ChainIcon = ({ id, name, onClick }: { id: string; name: string; onClick: () => void }) => { +const ChainIcon = ({ id, name, onClick }: { id: ICON_ID; name: string; onClick: () => void }) => { return ( @@ -56,7 +57,7 @@ const ChainIcon = ({ id, name, onClick }: { id: string; name: string; onClick: ( ) } -const MobileChainIcon = ({ id, name, onClick }: { id: string; name: string; onClick: () => void }) => { +const MobileChainIcon = ({ id, name, onClick }: { id: ICON_ID; name: string; onClick: () => void }) => { const theme = useTheme() return ( diff --git a/src/pages/TrueSightV2/pages/SingleToken.tsx b/src/pages/TrueSightV2/pages/SingleToken.tsx index 03ce90efa1..29de65834e 100644 --- a/src/pages/TrueSightV2/pages/SingleToken.tsx +++ b/src/pages/TrueSightV2/pages/SingleToken.tsx @@ -582,8 +582,8 @@ export default function SingleToken() { ReactNode title: string }[] = [ diff --git a/src/state/transactions/type.ts b/src/state/transactions/type.ts index 234363df98..be0dc920f6 100644 --- a/src/state/transactions/type.ts +++ b/src/state/transactions/type.ts @@ -151,6 +151,7 @@ export enum TRANSACTION_TYPE { KYBERDAO_MIGRATE = 'KyberDAO Migrate', KYBERDAO_VOTE = 'KyberDAO Vote', KYBERDAO_CLAIM = 'KyberDAO Claim Voting Reward', + KYBERDAO_CLAIM_GAS_REFUND = 'Gas Refund', CANCEL_LIMIT_ORDER = 'Cancel Limit Order', TRANSFER_TOKEN = 'Send', @@ -188,6 +189,7 @@ export const GROUP_TRANSACTION_BY_TYPE = { TRANSACTION_TYPE.KYBERDAO_MIGRATE, TRANSACTION_TYPE.KYBERDAO_VOTE, TRANSACTION_TYPE.KYBERDAO_CLAIM, + TRANSACTION_TYPE.KYBERDAO_CLAIM_GAS_REFUND, ], OTHER: [ // to make sure you don't forgot diff --git a/src/utils/array.ts b/src/utils/array.ts index ac36e2698c..bdaa63450f 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -27,3 +27,13 @@ export const uniqueArray = (array: T[], keySelector = (item: T): U => item }) return result } + +export const aggregateValue = ( + values: ({ [key in T]: string | number } | undefined)[], + field: T, +): number => { + return values.reduce((acc, cur) => { + const value = cur?.[field] ?? 0 + return (typeof value === 'number' ? value : parseFloat(value)) + acc + }, 0) +}