diff --git a/apps/web/src/app/components/Header/index.tsx b/apps/web/src/app/components/Header/index.tsx index 6d234e862..af50d4bb4 100644 --- a/apps/web/src/app/components/Header/index.tsx +++ b/apps/web/src/app/components/Header/index.tsx @@ -13,17 +13,20 @@ import { Button, IconButton } from '@/app/components/Button'; import Logo from '@/app/components/Logo'; import { DropdownPopper } from '@/app/components/Popover'; import { Typography } from '@/app/theme'; +import RecentActivityIcon from '@/assets/icons/activity.svg'; import CopyIcon from '@/assets/icons/copy.svg'; import WalletIcon from '@/assets/icons/wallet.svg'; import { useWalletModalToggle } from '@/store/application/hooks'; import { useAllTransactions } from '@/store/transactions/hooks'; import { shortenAddress } from '@/utils'; +import { useIsAnyTxPending } from '@/hooks/useCombinedTransactions'; import { useSignedInWallets } from '@/hooks/useWallets'; import { xChainMap } from '@balancednetwork/xwagmi'; import { bnJs } from '@balancednetwork/xwagmi'; import { Placement } from '@popperjs/core'; import { UseQueryResult, useQuery } from '@tanstack/react-query'; +import RecentActivity from '../RecentActivity'; import { MouseoverTooltip } from '../Tooltip'; import Wallet from '../Wallet'; import { notificationCSS } from '../Wallet/ICONWallets/utils'; @@ -53,6 +56,29 @@ const WalletButtonWrapper = styled(Box)<{ $hasnotification?: boolean }>` } `; +const RecentActivityButtonWrapper = styled(Box)<{ $hasnotification?: boolean }>` + position: relative; + margin-left: 15px; + ${({ $hasnotification }) => ($hasnotification ? notificationCSS : '')} + &::before, &::after { + left: 7px; + top: 13px; + ${({ theme }) => `background-color: ${theme.colors.bg5}`}; + } +`; + +const SpinningIcon = styled(RecentActivityIcon)` + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(-720deg); + } + } + animation: spin 2s ease-in-out infinite; +`; + export const StyledAddress = styled(Typography)` &:hover { color: #2fccdc; @@ -137,10 +163,12 @@ export default function Header(props: { title?: string; className?: string }) { const wallets = useSignedInWallets(); const { data: claimableICX } = useClaimableICX(); const hasBTCB = useHasBTCB(); - + const isAnyTxPending = useIsAnyTxPending(); + const walletButtonRef = React.useRef(null); const [anchor, setAnchor] = React.useState(null); - const walletButtonRef = React.useRef(null); + const recentActivityButtonRef = React.useRef(null); + const [recentActivityAnchor, setRecentActivityAnchor] = React.useState(null); const toggleWalletMenu = () => { setAnchor(anchor ? null : walletButtonRef.current); @@ -155,6 +183,11 @@ export default function Header(props: { title?: string; className?: string }) { } }; + const toggleRecentActivityMenu = () => { + setRecentActivityAnchor(recentActivityAnchor ? null : recentActivityButtonRef.current); + }; + const closeRecentActivityMenu = useCallback(() => setRecentActivityAnchor(null), []); + return (
@@ -235,6 +268,30 @@ export default function Header(props: { title?: string; className?: string }) { + + + +
+ + {isAnyTxPending ? ( + + ) : ( + + )} + + + + + +
+
+
)} diff --git a/apps/web/src/app/components/PoolLogoWithNetwork/index.tsx b/apps/web/src/app/components/PoolLogoWithNetwork/index.tsx index e40a3551b..e0ddf2960 100644 --- a/apps/web/src/app/components/PoolLogoWithNetwork/index.tsx +++ b/apps/web/src/app/components/PoolLogoWithNetwork/index.tsx @@ -8,7 +8,7 @@ import { xChainMap } from '@balancednetwork/xwagmi'; import { ChainLogo } from '../ChainLogo'; import CurrencyLogo from '../CurrencyLogo'; -const IconWrapper = styled(Box)<{ $respoVersion?: boolean }>` +const IconWrapper = styled(Box)<{ $respoVersion?: boolean; $compactVersion?: boolean }>` width: 48px; height: 48px; border-radius: 50%; @@ -39,9 +39,17 @@ const IconWrapper = styled(Box)<{ $respoVersion?: boolean }>` } `} `}; + + ${({ $compactVersion }) => + $compactVersion && + css` + width: 25px; + height: 25px; + background-color: rgb(12, 42, 77); + `} `; -const NetworkWrap = styled.div` +const NetworkWrap = styled.div<{ $compactVersion?: boolean }>` position: absolute; border-radius: 50%; width: 14px; @@ -58,12 +66,33 @@ const NetworkWrap = styled.div` top: 0; left: 0; } + + ${({ $compactVersion }) => + $compactVersion && + css` + width: 10px; + height: 10px; + outline: 1px solid #0c2a4d; + bottom: -3px; + margin-left: -6px; + + img { + width: 10px; + height: 10px; + } + `} `; -export const PoolLogoWrapper = styled(Box)` -position: relative; +export const PoolLogoWrapper = styled(Box)<{ $compactVersion?: boolean }>` + position: relative; display: flex; min-width: 80px; + + ${({ $compactVersion }) => + $compactVersion && + css` + min-width: 40px; + `} `; function PoolLogoWithNetwork({ @@ -71,21 +100,23 @@ function PoolLogoWithNetwork({ quoteCurrency, respoVersion, chainId, + compactVersion, }: { baseCurrency: Currency; quoteCurrency: Currency; respoVersion?: boolean; chainId: XChainId; + compactVersion?: boolean; }) { return ( - - + + - + - + diff --git a/apps/web/src/app/components/RecentActivity/HistoryItem.tsx b/apps/web/src/app/components/RecentActivity/HistoryItem.tsx new file mode 100644 index 000000000..1b7ff8714 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/HistoryItem.tsx @@ -0,0 +1,106 @@ +import { MMTransaction } from '@/store/transactions/useMMTransactionStore'; +import { XTransaction, XTransactionType, getTxTrackerLink } from '@balancednetwork/xwagmi'; +import { motion } from 'framer-motion'; +import React from 'react'; +import BridgeTransaction from './transactions/BridgeTransaction'; +import CollateralTransaction from './transactions/CollateralTransaction'; +import DepositXTokenTransaction from './transactions/DepositXTokenTransaction'; +import LPStakeTransaction from './transactions/LPStakeTransaction'; +import LPTransaction from './transactions/LPTransaction'; +import LoanTransaction from './transactions/LoanTransaction'; +import MMSwapTransaction from './transactions/MMSwapTransaction'; +import RewardsFeesTransaction from './transactions/RewardsFeesTransaction'; +import RewardsLPTransaction from './transactions/RewardsLPTransaction'; +import RewardsSavingsTransaction from './transactions/RewardsSavingsTransaction'; +import SavingsTransaction from './transactions/SavingsTransaction'; +import SwapTransaction from './transactions/SwapTransaction'; + +interface HistoryItemProps { + transaction: MMTransaction | XTransaction; + isMMTransaction: (transaction: MMTransaction | XTransaction) => transaction is MMTransaction; +} + +const HistoryItem: React.FC = ({ transaction, isMMTransaction }) => { + const hash = transaction.id.split('/')[1] || transaction.id; + const xChainId = isMMTransaction(transaction) ? transaction.fromAmount.currency.xChainId : transaction.sourceChainId; + const trackerLink = getTxTrackerLink(hash, xChainId); + + const handleClick = () => { + if (trackerLink) { + window.open(trackerLink, '_blank'); + } + }; + + const renderContent = () => { + if (isMMTransaction(transaction)) { + return ; + } + + switch (transaction.type) { + //Swap + case XTransactionType.SWAP: + case XTransactionType.SWAP_ON_ICON: + return ; + + //Bridge + case XTransactionType.BRIDGE: + return ; + + // Collateral + case XTransactionType.DEPOSIT_ON_ICON: + case XTransactionType.DEPOSIT: + case XTransactionType.WITHDRAW: + case XTransactionType.WITHDRAW_ON_ICON: + return ; + + //Loan + case XTransactionType.BORROW: + case XTransactionType.REPAY: + case XTransactionType.BORROW_ON_ICON: + case XTransactionType.REPAY_ON_ICON: + return ; + + //Liquidity + case XTransactionType.LP_REMOVE_LIQUIDITY: + case XTransactionType.LP_ADD_LIQUIDITY: + return ; + case XTransactionType.LP_DEPOSIT_XTOKEN: + case XTransactionType.LP_WITHDRAW_XTOKEN: + return ; + case XTransactionType.LP_UNSTAKE: + case XTransactionType.LP_STAKE: + return ; + + //Savings + case XTransactionType.SAVINGS_LOCK_BNUSD: + case XTransactionType.SAVINGS_UNLOCK_BNUSD: + return ; + + //Rewards + case XTransactionType.LP_CLAIM_REWARDS: + return ; + case XTransactionType.SAVINGS_CLAIM_REWARDS: + return ; + case XTransactionType.CLAIM_NETWORK_FEES: + return ; + + default: + return
Unknown Transaction Type - {transaction.type}
; + } + }; + + return ( + + {renderContent()} + + ); +}; + +export default HistoryItem; diff --git a/apps/web/src/app/components/RecentActivity/index.tsx b/apps/web/src/app/components/RecentActivity/index.tsx new file mode 100644 index 000000000..5da7e40f0 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/index.tsx @@ -0,0 +1,58 @@ +import { Typography } from '@/app/theme'; +import { useCombinedTransactions } from '@/hooks/useCombinedTransactions'; +import React from 'react'; +import styled from 'styled-components'; +import HistoryItem from './HistoryItem'; + +const Wrap = styled.div` + padding: 25px 0; + width: 400px; + max-width: calc(100vw - 4px); + + ul { + max-height: 500px; + overflow-y: auto; + list-style: none; + padding: 0 25px; + margin: 0; + } +`; + +const ListItem = styled.li` + padding: 20px 0; + display: block; + border-bottom: 1px solid ${({ theme }) => theme.colors.divider}; + + &:last-child { + border-bottom: none; + margin-bottom: 0; + } + +`; + +const RecentActivity: React.FC = () => { + const { transactions, isMMTransaction } = useCombinedTransactions(); + + return ( + + + Recent Activity + + {transactions.length === 0 ? ( + + No activity to display. + + ) : ( +
    + {transactions.map(transaction => ( + + + + ))} +
+ )} +
+ ); +}; + +export default RecentActivity; diff --git a/apps/web/src/app/components/RecentActivity/transactions/BridgeTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/BridgeTransaction.tsx new file mode 100644 index 000000000..311661796 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/BridgeTransaction.tsx @@ -0,0 +1,50 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance } from '@/utils/formatter'; +import { XTransaction, XTransactionStatus, xChainMap } from '@balancednetwork/xwagmi'; +import React, { useMemo } from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { ElapsedTime } from './_styledComponents'; +import { Amount, Meta, Title } from './_styledComponents'; +import { Container, Details } from './_styledComponents'; + +interface BridgeTransactionProps { + transaction: XTransaction; +} + +const BridgeTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const inputXToken = transaction.input.inputAmount.currency; + const inputAmount = transaction.input.inputAmount.toFixed(); + const destination = transaction.finalDestinationChainId; + const prices = useOraclePrices(); + + const elapsedTime = useElapsedTime(transaction.createdAt); + + return ( + + +
+ Transfer {inputXToken.symbol} + + {formatBalance(inputAmount, prices?.[inputXToken.symbol]?.toFixed() || 1)} {inputXToken.symbol} to{' '} + {xChainMap[destination]?.name} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default BridgeTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/CollateralTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/CollateralTransaction.tsx new file mode 100644 index 000000000..1c8e306af --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/CollateralTransaction.tsx @@ -0,0 +1,59 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance, formatSymbol } from '@/utils/formatter'; +import { XTransaction, XTransactionType } from '@balancednetwork/xwagmi'; +import React from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +interface CollateralTransactionProps { + transaction: XTransaction; +} + +const CollateralTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const input = transaction.input; + const prices = useOraclePrices(); + const inputXToken = input?.inputAmount?.currency; + const inputAmount = input?.inputAmount; + const elapsedTime = useElapsedTime(transaction?.createdAt); + + const isDeposit = + transaction.type === XTransactionType.DEPOSIT || transaction.type === XTransactionType.DEPOSIT_ON_ICON; + const isICON = + transaction.type === XTransactionType.WITHDRAW_ON_ICON || transaction.type === XTransactionType.DEPOSIT_ON_ICON; + + return ( + + +
+ + {isDeposit ? 'Deposit' : inputXToken.symbol === 'wICX' ? 'Unstake' : 'Withdraw'}{' '} + {formatSymbol(inputXToken.symbol)} collateral + + + {inputAmount && + formatBalance( + inputAmount.multiply(inputAmount.lessThan(0) ? -1 : 1).toExact(), + prices?.[inputXToken.symbol]?.toFixed() || 1, + )}{' '} + {formatSymbol(inputXToken.symbol)} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default CollateralTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/DepositXTokenTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/DepositXTokenTransaction.tsx new file mode 100644 index 000000000..104034902 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/DepositXTokenTransaction.tsx @@ -0,0 +1,51 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance, formatSymbol } from '@/utils/formatter'; +import { XTransaction, XTransactionType } from '@balancednetwork/xwagmi'; +import React from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +interface DepositXTokenTransactionProps { + transaction: XTransaction; +} + +const DepositXTokenTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const input = transaction?.input; + const inputXToken = input?.inputAmount?.currency; + const prices = useOraclePrices(); + const elapsedTime = useElapsedTime(transaction?.createdAt); + + const isDeposit = transaction.type === XTransactionType.LP_DEPOSIT_XTOKEN; + + return ( + + +
+ + {isDeposit ? 'Supply' : 'Withdraw'} {formatSymbol(inputXToken?.symbol)}{' '} + + + {input?.inputAmount && + formatBalance(input.inputAmount.toExact(), prices?.[inputXToken?.symbol]?.toFixed() || 1)}{' '} + {inputXToken?.symbol} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default DepositXTokenTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/LPStakeTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/LPStakeTransaction.tsx new file mode 100644 index 000000000..e212a0a99 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/LPStakeTransaction.tsx @@ -0,0 +1,53 @@ +import { SUPPORTED_TOKENS_LIST } from '@/constants/tokens'; +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance, formatSymbol } from '@/utils/formatter'; +import { XTransaction, XTransactionType } from '@balancednetwork/xwagmi'; +import React from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import PoolLogoWithNetwork from '../../PoolLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +interface LPStakeTransactionProps { + transaction: XTransaction; +} + +const LPStakeTransaction: React.FC = ({ transaction }) => { + const input = transaction?.input; + const symbolA = input?.tokenASymbol; + const symbolB = input?.tokenBSymbol; + const elapsedTime = useElapsedTime(transaction?.createdAt); + + const isStake = transaction.type === XTransactionType.LP_STAKE; + + const tokenA = SUPPORTED_TOKENS_LIST.find(token => token.symbol === symbolA); + const tokenB = SUPPORTED_TOKENS_LIST.find(token => token.symbol === symbolB); + + return ( + + {tokenA && tokenB ? ( + + ) : null} +
+ + {isStake ? 'Stake' : 'Unstake'} {formatSymbol(symbolA)} / {formatSymbol(symbolB)} + + {input.inputAmount && input.inputAmount.toSignificant(5)} LP +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default LPStakeTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/LPTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/LPTransaction.tsx new file mode 100644 index 000000000..d5b39f5dd --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/LPTransaction.tsx @@ -0,0 +1,57 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance, formatSymbol } from '@/utils/formatter'; +import { XTransaction, XTransactionType } from '@balancednetwork/xwagmi'; +import React from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import PoolLogoWithNetwork from '../../PoolLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +interface LPTransactionProps { + transaction: XTransaction; +} + +const LPTransaction: React.FC = ({ transaction }) => { + const input = transaction?.input; + const prices = useOraclePrices(); + // const primaryMessage = xMessageActions.getOf(transaction.id, true); + const elapsedTime = useElapsedTime(transaction?.createdAt); + + const isWithdraw = transaction.type === XTransactionType.LP_REMOVE_LIQUIDITY; + + const tokenAmountA = isWithdraw ? input?.withdrawAmountA : input?.inputAmount; + const tokenAmountB = isWithdraw ? input?.withdrawAmountB : input?.outputAmount; + + return ( + + {tokenAmountA && tokenAmountB ? ( + + ) : null} +
+ {`${isWithdraw ? 'Withdraw' : 'Supply'} ${formatSymbol(tokenAmountA?.currency.symbol)} / ${formatSymbol(tokenAmountB?.currency.symbol)}`} + + + {formatBalance(tokenAmountA?.toExact(), prices?.[tokenAmountA?.currency.symbol]?.toFixed() || 1)}{' '} + {formatSymbol(tokenAmountA?.currency.symbol)} + {' / '} + {formatBalance(tokenAmountB?.toExact(), prices?.[tokenAmountB?.currency.symbol]?.toFixed() || 1)}{' '} + {formatSymbol(tokenAmountB?.currency.symbol)} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default LPTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/LoanTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/LoanTransaction.tsx new file mode 100644 index 000000000..1a09d07c0 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/LoanTransaction.tsx @@ -0,0 +1,51 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance, formatSymbol } from '@/utils/formatter'; +import { XChainId, XTransaction, XTransactionType } from '@balancednetwork/xwagmi'; +import React from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +interface LoanTransactionProps { + transaction: XTransaction; +} + +const LoanTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const input = transaction.input; + const inputXToken = input?.inputAmount?.currency; + const inputAmount = input?.inputAmount; + const elapsedTime = useElapsedTime(transaction?.createdAt); + + const isBorrow = transaction.type === XTransactionType.BORROW || transaction.type === XTransactionType.BORROW_ON_ICON; + + return ( + + +
+ + {isBorrow ? 'Borrow' : 'Repay'} + {' bnUSD'} + + + {inputAmount && formatBalance(inputAmount.multiply(inputAmount.lessThan(0) ? -1 : 1).toExact(), 1)} + {' bnUSD'} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default LoanTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/MMSwapTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/MMSwapTransaction.tsx new file mode 100644 index 000000000..83dd40a6d --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/MMSwapTransaction.tsx @@ -0,0 +1,107 @@ +import useIntentProvider from '@/hooks/useIntentProvider'; +import { intentService } from '@/lib/intent'; +import { useOraclePrices } from '@/store/oracle/hooks'; +import { MMTransaction, MMTransactionActions, MMTransactionStatus } from '@/store/transactions/useMMTransactionStore'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance, formatSymbol } from '@/utils/formatter'; +import { xChainMap } from '@balancednetwork/xwagmi'; +import React, { useState } from 'react'; +import { Flex } from 'rebass'; +import styled, { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +enum CancelStatus { + None, + Signing, + AwaitingConfirmation, + Success, + Failed, +} + +interface MMSwapTransactionProps { + transaction: MMTransaction; +} + +const MMSwapTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const { fromAmount, toAmount } = transaction; + const prices = useOraclePrices(); + const [status, setStatus] = useState(CancelStatus.None); + const { data: intentProvider } = useIntentProvider(transaction.fromAmount.currency.wrapped); + // const primaryMessage = xMessageActions.getOf(transaction.id, true); + + const elapsedTime = useElapsedTime(transaction.createdAt); + + const handleCancel = async () => { + setStatus(CancelStatus.Signing); + try { + if (intentProvider) { + const result = await intentService.cancelIntentOrder( + transaction.orderId, + xChainMap[transaction.fromAmount.currency.xChainId].intentChainId, + intentProvider, + ); + + if (result.ok) { + MMTransactionActions.cancel(transaction.id); + setStatus(CancelStatus.Success); + } else { + setStatus(CancelStatus.None); + } + } + } catch (e) { + console.error(e); + setStatus(CancelStatus.None); + } + }; + + return ( + <> + + +
+ + Swap {formatSymbol(fromAmount.currency.symbol)} for {formatSymbol(toAmount.currency.symbol)} + + + {formatBalance(fromAmount.toFixed(), prices?.[fromAmount.currency.symbol]?.toFixed() || 1)}{' '} + {formatSymbol(fromAmount.currency.symbol)} for{' '} + {formatBalance(toAmount.toFixed(), prices[toAmount.currency.address]?.toFixed() || 1)}{' '} + {formatSymbol(toAmount.currency.symbol)} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ {transaction.status === MMTransactionStatus.pending ? ( + + {status === CancelStatus.None && Cancel} + {status === CancelStatus.Signing && Signing...} + {status === CancelStatus.AwaitingConfirmation && Canceling...} + {status === CancelStatus.Success && Done} + + ) : null} + + ); +}; + +const CancelButton = styled.button` + background: none; + border: none; + color: #fb6a6a; + cursor: pointer; + font-size: 14px; + padding: 0; +`; + +export default MMSwapTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/RewardsFeesTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/RewardsFeesTransaction.tsx new file mode 100644 index 000000000..54575e998 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/RewardsFeesTransaction.tsx @@ -0,0 +1,44 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance } from '@/utils/formatter'; +import { XTransaction, XTransactionStatus, xChainMap } from '@balancednetwork/xwagmi'; +import React, { useMemo } from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { ElapsedTime } from './_styledComponents'; +import { Amount, Meta, Title } from './_styledComponents'; +import { Container, Details } from './_styledComponents'; + +interface RewardsFeesTransactionProps { + transaction: XTransaction; +} + +const RewardsFeesTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const inputXToken = transaction.input.inputAmount.currency; + const inputAmount = transaction.input.inputAmount.toFixed(); + const prices = useOraclePrices(); + + const elapsedTime = useElapsedTime(transaction.createdAt); + + return ( + + +
+ Claim network fees + {/* + {formatBalance(inputAmount, prices?.[inputXToken.symbol]?.toFixed() || 1)} {inputXToken.symbol} to{' '} + {xChainMap[destination]?.name} + */} +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default RewardsFeesTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/RewardsLPTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/RewardsLPTransaction.tsx new file mode 100644 index 000000000..ab3fc8666 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/RewardsLPTransaction.tsx @@ -0,0 +1,51 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance } from '@/utils/formatter'; +import { XTransaction, XTransactionStatus, xChainMap } from '@balancednetwork/xwagmi'; +import React, { useMemo } from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { ElapsedTime } from './_styledComponents'; +import { Amount, Meta, Title } from './_styledComponents'; +import { Container, Details } from './_styledComponents'; + +interface RewardsLPTransactionProps { + transaction: XTransaction; +} + +const RewardsLPTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const inputXToken = transaction.input.inputAmount.currency; + const inputAmount = transaction.input.inputAmount.toFixed(); + const prices = useOraclePrices(); + + console.log('rewards tx', transaction); + + const elapsedTime = useElapsedTime(transaction.createdAt); + + return ( + + +
+ Claim liquidity rewards + {/* + {formatBalance(inputAmount, prices?.[inputXToken.symbol]?.toFixed() || 1)} {inputXToken.symbol} to{' '} + {xChainMap[destination]?.name} + */} +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default RewardsLPTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/RewardsSavingsTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/RewardsSavingsTransaction.tsx new file mode 100644 index 000000000..fff196285 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/RewardsSavingsTransaction.tsx @@ -0,0 +1,51 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance } from '@/utils/formatter'; +import { XTransaction, XTransactionStatus, xChainMap } from '@balancednetwork/xwagmi'; +import React, { useMemo } from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { ElapsedTime } from './_styledComponents'; +import { Amount, Meta, Title } from './_styledComponents'; +import { Container, Details } from './_styledComponents'; + +interface RewardsSavingsTransactionProps { + transaction: XTransaction; +} + +const RewardsSavingsTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const inputXToken = transaction.input.inputAmount.currency; + const inputAmount = transaction.input.inputAmount.toFixed(); + const prices = useOraclePrices(); + + console.log('rewards tx', transaction); + + const elapsedTime = useElapsedTime(transaction.createdAt); + + return ( + + +
+ Claim savings rewards + {/* + {formatBalance(inputAmount, prices?.[inputXToken.symbol]?.toFixed() || 1)} {inputXToken.symbol} to{' '} + {xChainMap[destination]?.name} + */} +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default RewardsSavingsTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/SavingsTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/SavingsTransaction.tsx new file mode 100644 index 000000000..6f0dd4f13 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/SavingsTransaction.tsx @@ -0,0 +1,48 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance, formatSymbol } from '@/utils/formatter'; +import { XTransaction, XTransactionType } from '@balancednetwork/xwagmi'; +import React from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +interface SavingsTransactionProps { + transaction: XTransaction; +} + +const SavingsTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const input = transaction?.input; + const inputXToken = input?.inputAmount?.currency; + const elapsedTime = useElapsedTime(transaction?.createdAt); + + const isLock = transaction.type === XTransactionType.SAVINGS_LOCK_BNUSD; + + return ( + + +
+ + {isLock ? 'Deposit' : 'Withdraw'} {formatSymbol(inputXToken?.symbol)} + + + {input?.inputAmount && formatBalance(input.inputAmount.toExact(), 1)} {inputXToken?.symbol} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default SavingsTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/SwapTransaction.tsx b/apps/web/src/app/components/RecentActivity/transactions/SwapTransaction.tsx new file mode 100644 index 000000000..fa9b47621 --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/SwapTransaction.tsx @@ -0,0 +1,59 @@ +import { useOraclePrices } from '@/store/oracle/hooks'; +import { useElapsedTime } from '@/store/user/hooks'; +import { formatRelativeTime } from '@/utils'; +import { formatBalance } from '@/utils/formatter'; +import { XTransaction } from '@balancednetwork/xwagmi'; +import React from 'react'; +import { useTheme } from 'styled-components'; +import CurrencyLogoWithNetwork from '../../CurrencyLogoWithNetwork'; +import TransactionStatusDisplay from './TransactionStatusDisplay'; +import { Amount, Container, Details, ElapsedTime, Meta, Title } from './_styledComponents'; + +interface SwapTransactionProps { + transaction: XTransaction; +} + +const SwapTransaction: React.FC = ({ transaction }) => { + const theme = useTheme(); + const input = transaction?.input; + const inputXToken = input?.inputAmount?.currency; + const outputXToken = input?.outputAmount?.currency; + const prices = useOraclePrices(); + // const primaryMessage = xMessageActions.getOf(transaction.id, true); + const elapsedTime = useElapsedTime(transaction?.createdAt); + + // Guard against missing data + if (!inputXToken || !outputXToken) { + return null; + } + + return ( + + +
+ + Swap {inputXToken.symbol} for {outputXToken.symbol} + + + {input.inputAmount && + formatBalance(input.inputAmount.toExact(), prices?.[inputXToken.symbol]?.toFixed() || 1)}{' '} + {inputXToken.symbol} for{' '} + {input.outputAmount && + formatBalance(input.outputAmount.toExact(), prices[outputXToken.symbol]?.toFixed() || 1)}{' '} + {outputXToken.symbol} + +
+ + + {formatRelativeTime(elapsedTime)} + +
+ ); +}; + +export default SwapTransaction; diff --git a/apps/web/src/app/components/RecentActivity/transactions/TransactionStatusDisplay.tsx b/apps/web/src/app/components/RecentActivity/transactions/TransactionStatusDisplay.tsx new file mode 100644 index 000000000..4aed4773d --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/TransactionStatusDisplay.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { Flex } from 'rebass'; +import styled from 'styled-components'; +import Spinner from '../../Spinner'; +import { Status } from './_styledComponents'; + +interface TransactionStatusProps { + status: string; +} + +const SpinnerWrap = styled.div` + margin-left: 6px; + position: relative; + + top: -4px; +`; + +const TransactionStatusDisplay: React.FC = ({ status }) => { + const getStatusText = (status: string) => { + switch (status.toLowerCase()) { + case 'pending': + return ( + + Pending + + + + + ); + case 'completed': + case 'success': + return 'Completed'; + case 'failed': + return 'Failed'; + case 'cancelled': + return 'Cancelled'; + default: + return status; + } + }; + + return {getStatusText(status)}; +}; + +export default TransactionStatusDisplay; diff --git a/apps/web/src/app/components/RecentActivity/transactions/_styledComponents.ts b/apps/web/src/app/components/RecentActivity/transactions/_styledComponents.ts new file mode 100644 index 000000000..8958009ac --- /dev/null +++ b/apps/web/src/app/components/RecentActivity/transactions/_styledComponents.ts @@ -0,0 +1,66 @@ +import styled from 'styled-components'; + +export const Title = styled.div` + font-weight: bold; + color: #FFFFFF; + font-size: 14px; + transition: all 0.2s ease-in-out; + padding-right: 5px; + padding-left: 0; +`; + +export const Amount = styled.div` + color: ${({ theme }) => theme.colors.text1}; + font-size: 14px; + opacity: 0.9; + transition: all 0.2s ease-in-out; + padding-right: 5px; + padding-left: 0; +`; + +export const Status = styled.div` + color: ${({ theme }) => theme.colors.text}; + font-size: 14px; + text-align: right; + transition: all 0.2s ease-in-out; +`; + +export const Container = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + &:hover { + ${Title} { + color: ${({ theme }) => theme.colors.primaryBright}; + padding-right: 0; + padding-left: 5px; + } + ${Amount} { + color: ${({ theme }) => theme.colors.primaryBright}; + padding-right: 0; + padding-left: 5px; + } + ${Status} { + color: ${({ theme }) => theme.colors.primaryBright}; + } + } +`; + +export const Details = styled.div` + flex-grow: 1; + margin-left: 12px; +`; + +export const ElapsedTime = styled.div` + color: ${({ theme }) => theme.colors.text1}; + font-size: 14px; + text-align: right; + opacity: 0.9; +`; + +export const Meta = styled.div` + display: flex; + flex-direction: column; + align-items: flex-end; +`; diff --git a/apps/web/src/app/components/home/CollateralPanel.tsx b/apps/web/src/app/components/home/CollateralPanel.tsx index 44958383b..271094aaa 100644 --- a/apps/web/src/app/components/home/CollateralPanel.tsx +++ b/apps/web/src/app/components/home/CollateralPanel.tsx @@ -23,6 +23,7 @@ import IconUnstakeSICX from '@/assets/icons/timer-color.svg'; import IconKeepSICX from '@/assets/icons/wallet-tick-color.svg'; import { NETWORK_ID } from '@/constants/config'; import { SLIDER_RANGE_MAX_BOTTOM_THRESHOLD } from '@/constants/index'; +import { SUPPORTED_TOKENS_LIST, wICX } from '@/constants/tokens'; import { MODAL_ID, modalActions } from '@/hooks/useModalStore'; import useWidth from '@/hooks/useWidth'; import { useICXUnstakingTime } from '@/store/application/hooks'; @@ -41,7 +42,8 @@ import { useHasEnoughICX } from '@/store/wallet/hooks'; import { parseUnits } from '@/utils'; import { formatSymbol, useWrongSymbol } from '@/utils/formatter'; import { showMessageOnBeforeUnload } from '@/utils/messages'; -import { getXChainType } from '@balancednetwork/xwagmi'; +import { CurrencyAmount } from '@balancednetwork/sdk-core'; +import { XTransactionType, getICONXTransactionInput, getXChainType } from '@balancednetwork/xwagmi'; import { xChainMap } from '@balancednetwork/xwagmi'; import { useXConnect, useXConnectors } from '@balancednetwork/xwagmi'; import { bnJs } from '@balancednetwork/xwagmi'; @@ -214,6 +216,10 @@ const CollateralPanel = () => { const cx = bnJs.inject({ account }).getContract(collateralTokenAddress!); const decimals: string = await cx.decimals(); + if (!account) { + return; + } + if (shouldDeposit) { try { if (isHandlingICX) { @@ -226,6 +232,12 @@ const CollateralPanel = () => { { pending: t`Depositing collateral...`, summary: t`Deposited ${collateralDifference.dp(2).toFormat()} ICX as collateral.`, + type: XTransactionType.DEPOSIT_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.DEPOSIT_ON_ICON, + CurrencyAmount.fromRawAmount(wICX[1], collateralDifference.times(10 ** 18).toFixed(0)), + ), }, ); } else { @@ -243,6 +255,15 @@ const CollateralPanel = () => { summary: t`Deposited ${collateralDifference.toFixed( collateralDecimalPlaces, )} ${collateralType} as collateral.`, + type: XTransactionType.DEPOSIT_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.DEPOSIT_ON_ICON, + CurrencyAmount.fromRawAmount( + SUPPORTED_TOKENS_LIST.find(token => token.symbol === collateralType)!, + collateralDifference.times(10 ** Number(decimals)).toFixed(0), + ), + ), }, ); } @@ -273,6 +294,12 @@ const CollateralPanel = () => { { pending: t`Withdrawing collateral...`, summary: t`${collateralDifference.dp(2).toFormat()} ICX is unstaking.`, + type: XTransactionType.WITHDRAW_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.WITHDRAW_ON_ICON, + CurrencyAmount.fromRawAmount(wICX[1], collateralDifference.times(10 ** 18).toFixed(0)), + ), }, ); } else { @@ -290,6 +317,15 @@ const CollateralPanel = () => { summary: t`${collateralDifferenceInSICX .dp(collateralDecimalPlaces) .toFormat()} ${formatSymbol(collateralType)} added to your wallet.`, + type: XTransactionType.WITHDRAW_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.WITHDRAW_ON_ICON, + CurrencyAmount.fromRawAmount( + SUPPORTED_TOKENS_LIST.find(token => token.symbol === 'sICX')!, + collateralDifference.times(10 ** 18).toFixed(0), + ), + ), }, ); } @@ -308,6 +344,15 @@ const CollateralPanel = () => { summary: t`${collateralDifference .dp(collateralDecimalPlaces) .toFormat()} ${formatSymbol(collateralType)} added to your wallet.`, + type: XTransactionType.WITHDRAW_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.WITHDRAW_ON_ICON, + CurrencyAmount.fromRawAmount( + SUPPORTED_TOKENS_LIST.find(token => token.symbol === collateralType)!, + collateralDifference.times(10 ** Number(decimals)).toFixed(0), + ), + ), }, ); } diff --git a/apps/web/src/app/components/home/LoanPanel.tsx b/apps/web/src/app/components/home/LoanPanel.tsx index 740821d91..f3b545dc0 100644 --- a/apps/web/src/app/components/home/LoanPanel.tsx +++ b/apps/web/src/app/components/home/LoanPanel.tsx @@ -30,13 +30,15 @@ import { useTransactionAdder } from '@/store/transactions/hooks'; import { useHasEnoughICX } from '@/store/wallet/hooks'; import { parseUnits } from '@/utils'; import { showMessageOnBeforeUnload } from '@/utils/messages'; -import { bnJs } from '@balancednetwork/xwagmi'; +import { XTransactionType, bnJs, getICONXTransactionInput } from '@balancednetwork/xwagmi'; import ModalContent from '@/app/components/ModalContent'; +import { bnUSD } from '@/constants/tokens'; import { MODAL_ID, modalActions } from '@/hooks/useModalStore'; import useWidth from '@/hooks/useWidth'; import { useIconReact } from '@/packages/icon-react'; import { useWrongSymbol } from '@/utils/formatter'; +import { CurrencyAmount } from '@balancednetwork/sdk-core'; import { ICON_XCALL_NETWORK_ID } from '@balancednetwork/xwagmi'; import BigNumber from 'bignumber.js'; import { PanelInfoItem, PanelInfoWrap, UnderPanel } from './CollateralPanel'; @@ -152,6 +154,12 @@ const LoanPanel = () => { { pending: t`Borrowing bnUSD...`, summary: t`Borrowed ${differenceAmount.dp(2).toFormat()} bnUSD.`, + type: XTransactionType.BORROW_ON_ICON, + input: getICONXTransactionInput( + iconAccount, + XTransactionType.BORROW_ON_ICON, + CurrencyAmount.fromRawAmount(bnUSD[1], differenceAmount.times(10 ** 18).toFixed(0)), + ), }, ); // close modal @@ -184,6 +192,12 @@ const LoanPanel = () => { { pending: t`Repaying bnUSD...`, summary: t`Repaid ${repayAmount.dp(2).toFormat()} bnUSD.`, + type: XTransactionType.REPAY_ON_ICON, + input: getICONXTransactionInput( + iconAccount, + XTransactionType.REPAY_ON_ICON, + CurrencyAmount.fromRawAmount(bnUSD[1], repayAmount.times(10 ** 18).toFixed(0)), + ), }, ); // close modal diff --git a/apps/web/src/app/components/home/RewardsPanel/NetworkFeesRewards.tsx b/apps/web/src/app/components/home/RewardsPanel/NetworkFeesRewards.tsx index c19f32d23..9e9b84a46 100644 --- a/apps/web/src/app/components/home/RewardsPanel/NetworkFeesRewards.tsx +++ b/apps/web/src/app/components/home/RewardsPanel/NetworkFeesRewards.tsx @@ -25,9 +25,11 @@ import { useHasNetworkFees } from '@/store/reward/hooks'; import { useTransactionAdder } from '@/store/transactions/hooks'; import { useHasEnoughICX } from '@/store/wallet/hooks'; import { showMessageOnBeforeUnload } from '@/utils/messages'; -import { bnJs } from '@balancednetwork/xwagmi'; +import { XTransactionType, bnJs, getICONXTransactionInput } from '@balancednetwork/xwagmi'; +import { bnUSD } from '@/constants/tokens'; import { useSavingsXChainId } from '@/store/savings/hooks'; +import { CurrencyAmount } from '@balancednetwork/sdk-core'; import RewardsGrid from './RewardsGrid'; const NetworkFeesReward = ({ showGlobalTooltip }: { showGlobalTooltip: boolean }) => { @@ -53,6 +55,7 @@ const NetworkFeesReward = ({ showGlobalTooltip }: { showGlobalTooltip: boolean } const handleClaim = () => { window.addEventListener('beforeunload', showMessageOnBeforeUnload); + if (!account) return; bnJs .inject({ account }) @@ -63,6 +66,12 @@ const NetworkFeesReward = ({ showGlobalTooltip }: { showGlobalTooltip: boolean } { summary: t`Claimed network fees.`, pending: t`Claiming network fees...`, + type: XTransactionType.CLAIM_NETWORK_FEES, + input: getICONXTransactionInput( + account, + XTransactionType.CLAIM_NETWORK_FEES, + CurrencyAmount.fromRawAmount(bnUSD[1], 0), + ), }, ); toggleOpen(); diff --git a/apps/web/src/app/pages/trade/xswap/_components/MMSwapModal.tsx b/apps/web/src/app/pages/trade/xswap/_components/MMSwapModal.tsx index 4d11b484e..cc66618bf 100644 --- a/apps/web/src/app/pages/trade/xswap/_components/MMSwapModal.tsx +++ b/apps/web/src/app/pages/trade/xswap/_components/MMSwapModal.tsx @@ -185,6 +185,7 @@ const MMSwapModal = ({ toAmount: trade.outputAmount, orderId: BigInt(intentResult.value.id), taskId: '', + createdAt: Date.now(), }); setIntentId(intentHash.value); diff --git a/apps/web/src/app/pages/trade/xswap/_components/SwapModal.tsx b/apps/web/src/app/pages/trade/xswap/_components/SwapModal.tsx index d2eaa8da3..d6bfc8592 100644 --- a/apps/web/src/app/pages/trade/xswap/_components/SwapModal.tsx +++ b/apps/web/src/app/pages/trade/xswap/_components/SwapModal.tsx @@ -19,7 +19,7 @@ import { useTransactionAdder } from '@/store/transactions/hooks'; import { useHasEnoughICX } from '@/store/wallet/hooks'; import { formatBigNumber, shortenAddress, toDec } from '@/utils'; import { formatSymbol } from '@/utils/formatter'; -import { getRlpEncodedSwapData } from '@balancednetwork/xwagmi'; +import { XTransactionType, getICONXTransactionInput, getRlpEncodedSwapData } from '@balancednetwork/xwagmi'; import { bnJs } from '@balancednetwork/xwagmi'; type SwapModalProps = { @@ -70,6 +70,13 @@ const SwapModal = (props: SwapModalProps) => { { pending: message.pendingMessage, summary: message.successMessage, + type: XTransactionType.SWAP_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.SWAP_ON_ICON, + executionTrade.inputAmount, + executionTrade.outputAmount, + ), }, ); handleDismiss(); @@ -97,6 +104,13 @@ const SwapModal = (props: SwapModalProps) => { { pending: message.pendingMessage, summary: message.successMessage, + type: XTransactionType.SWAP_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.SWAP_ON_ICON, + executionTrade.inputAmount, + executionTrade.outputAmount, + ), }, ); handleDismiss(); @@ -128,6 +142,13 @@ const SwapModal = (props: SwapModalProps) => { { pending: message.pendingMessage, summary: message.successMessage, + type: XTransactionType.SWAP_ON_ICON, + input: getICONXTransactionInput( + account, + XTransactionType.SWAP_ON_ICON, + executionTrade.inputAmount, + executionTrade.outputAmount, + ), }, ); handleDismiss(); diff --git a/apps/web/src/app/pages/trade/xswap/_components/SwapPanel.tsx b/apps/web/src/app/pages/trade/xswap/_components/SwapPanel.tsx index 339c46acf..d2b91a77e 100644 --- a/apps/web/src/app/pages/trade/xswap/_components/SwapPanel.tsx +++ b/apps/web/src/app/pages/trade/xswap/_components/SwapPanel.tsx @@ -33,7 +33,6 @@ import { formatBalance, formatSymbol } from '@/utils/formatter'; import { XToken, getXChainType } from '@balancednetwork/xwagmi'; import { useXAccount } from '@balancednetwork/xwagmi'; import { XChainId } from '@balancednetwork/xwagmi'; -import MMPendingIntents from './MMPendingIntents'; import MMSwapCommitButton from './MMSwapCommitButton'; import MMSwapInfo from './MMSwapInfo'; import PriceImpact from './PriceImpact'; @@ -315,8 +314,6 @@ export default function SwapPanel() { handleOutputType('0.002'); }} /> - - diff --git a/apps/web/src/assets/icons/activity.svg b/apps/web/src/assets/icons/activity.svg new file mode 100644 index 000000000..d56f76f97 --- /dev/null +++ b/apps/web/src/assets/icons/activity.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/web/src/hooks/useCombinedTransactions.ts b/apps/web/src/hooks/useCombinedTransactions.ts new file mode 100644 index 000000000..a418b13a7 --- /dev/null +++ b/apps/web/src/hooks/useCombinedTransactions.ts @@ -0,0 +1,40 @@ +import { MMTransaction, MMTransactionStatus, useMMTransactionStore } from '@/store/transactions/useMMTransactionStore'; +import { Transaction, XTransaction, XTransactionStatus, useXTransactionStore } from '@balancednetwork/xwagmi'; +import { useMemo } from 'react'; + +const isMMTransaction = (transaction: MMTransaction | XTransaction): transaction is MMTransaction => { + return !!(transaction as MMTransaction).orderId; +}; + +export const useCombinedTransactions = (): { + transactions: (MMTransaction | XTransaction)[]; + isMMTransaction: (transaction: MMTransaction | XTransaction) => transaction is MMTransaction; +} => { + const xTransactions = useXTransactionStore(state => state.getTransactions()); + const mmTransactions = useMMTransactionStore(state => Object.values(state.transactions)); + + const sortedTransactions = useMemo( + () => [...xTransactions, ...mmTransactions].sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0)), + [xTransactions, mmTransactions], + ); + + return { + transactions: sortedTransactions, + isMMTransaction, + }; +}; + +export const useIsAnyTxPending = (): boolean => { + const { transactions } = useCombinedTransactions(); + const oneHourAgo = Date.now() - 60 * 60 * 1000; // 1 hour in milliseconds + + return transactions.some(tx => { + if (tx.createdAt && tx.createdAt < oneHourAgo) return false; + + if (isMMTransaction(tx)) { + return tx.status === MMTransactionStatus.pending; + } else { + return tx.status === XTransactionStatus.pending; + } + }); +}; diff --git a/apps/web/src/locales/en-US.po b/apps/web/src/locales/en-US.po index 3182360d4..140cd6560 100644 --- a/apps/web/src/locales/en-US.po +++ b/apps/web/src/locales/en-US.po @@ -2860,6 +2860,10 @@ msgstr "FEES (24H)" msgid "Switch to" msgstr "Switch to" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "Transaction failed" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "1W" diff --git a/apps/web/src/locales/es-ES.po b/apps/web/src/locales/es-ES.po index 7bed54c44..7105447d2 100644 --- a/apps/web/src/locales/es-ES.po +++ b/apps/web/src/locales/es-ES.po @@ -2922,6 +2922,10 @@ msgstr "" msgid "Switch to" msgstr "" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "1S" diff --git a/apps/web/src/locales/fr-FR.po b/apps/web/src/locales/fr-FR.po index b132005b5..6a75e079f 100644 --- a/apps/web/src/locales/fr-FR.po +++ b/apps/web/src/locales/fr-FR.po @@ -2920,6 +2920,10 @@ msgstr "" msgid "Switch to" msgstr "" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "1S" diff --git a/apps/web/src/locales/ko-KR.po b/apps/web/src/locales/ko-KR.po index 0578449ad..154566852 100644 --- a/apps/web/src/locales/ko-KR.po +++ b/apps/web/src/locales/ko-KR.po @@ -2922,6 +2922,10 @@ msgstr "" msgid "Switch to" msgstr "" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "1주" diff --git a/apps/web/src/locales/nl-NL.po b/apps/web/src/locales/nl-NL.po index 834658fcd..371a57805 100644 --- a/apps/web/src/locales/nl-NL.po +++ b/apps/web/src/locales/nl-NL.po @@ -2923,6 +2923,10 @@ msgstr "FEES (24U)" msgid "Switch to" msgstr "" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "1W" diff --git a/apps/web/src/locales/pl-PL.po b/apps/web/src/locales/pl-PL.po index 337b685cf..16394a636 100644 --- a/apps/web/src/locales/pl-PL.po +++ b/apps/web/src/locales/pl-PL.po @@ -2922,6 +2922,10 @@ msgstr "" msgid "Switch to" msgstr "" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "1T" diff --git a/apps/web/src/locales/pseudo.po b/apps/web/src/locales/pseudo.po index 92dfbe39e..04db08333 100644 --- a/apps/web/src/locales/pseudo.po +++ b/apps/web/src/locales/pseudo.po @@ -2852,6 +2852,10 @@ msgstr "" msgid "Switch to" msgstr "" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "" diff --git a/apps/web/src/locales/vi-VN.po b/apps/web/src/locales/vi-VN.po index cc27a6361..49bc33a37 100644 --- a/apps/web/src/locales/vi-VN.po +++ b/apps/web/src/locales/vi-VN.po @@ -2922,6 +2922,10 @@ msgstr "" msgid "Switch to" msgstr "" +#: src/store/transactions/hooks.tsx +#~ msgid "Transaction failed" +#~ msgstr "" + #: src/app/pages/trade/xswap/_components/SwapDescription.tsx msgid "1W" msgstr "1 tuần" diff --git a/apps/web/src/store/transactions/hooks.tsx b/apps/web/src/store/transactions/hooks.tsx index 4c29acecb..e4dbd0909 100644 --- a/apps/web/src/store/transactions/hooks.tsx +++ b/apps/web/src/store/transactions/hooks.tsx @@ -10,6 +10,9 @@ import { toast } from 'react-toastify'; import { NotificationError, NotificationPending } from '@/app/components/Notification/TransactionNotification'; import { getTrackerLink } from '@/utils'; +import { CurrencyAmount, XChainId } from '@balancednetwork/sdk-core'; +import { XTransactionInput, XTransactionStatus, XTransactionType, xTransactionActions } from '@balancednetwork/xwagmi'; +import { wICX } from '@balancednetwork/xwagmi'; import { AppDispatch, AppState } from '../index'; import { ICONTxEventLog, addTransaction } from './actions'; import { TransactionDetails } from './reducer'; @@ -26,27 +29,18 @@ export function useTransactionAdder(): ( pending?: string; redirectOnSuccess?: string; isTxSuccessfulBasedOnEvents?: (eventLogs: ICONTxEventLog[]) => boolean; + input?: XTransactionInput; + type?: XTransactionType; }, ) => void { const { account } = useIconReact(); const networkId = useIconNetworkId(); - const dispatch = useDispatch(); return useCallback( ( response: TransactionResponse, - { - summary, - pending, - redirectOnSuccess, - isTxSuccessfulBasedOnEvents, - }: { - summary?: string; - pending?: string; - redirectOnSuccess?: string; - isTxSuccessfulBasedOnEvents?: (eventLogs: ICONTxEventLog[]) => boolean; - } = {}, + { summary, pending, redirectOnSuccess, isTxSuccessfulBasedOnEvents, input, type } = {}, ) => { if (!account) return; if (!networkId) return; @@ -70,8 +64,29 @@ export function useTransactionAdder(): ( toastId: hash, }); + if (type && input) { + xTransactionActions.add({ + id: hash, + type, + status: XTransactionStatus.pending, + secondaryMessageRequired: false, + sourceChainId: '0x1.icon' as XChainId, + finalDestinationChainId: '0x1.icon' as XChainId, + finalDestinationChainInitialBlockHeight: BigInt(0), + createdAt: Date.now(), + input, + }); + } + dispatch( - addTransaction({ hash, from: account, networkId, summary, redirectOnSuccess, isTxSuccessfulBasedOnEvents }), + addTransaction({ + hash, + from: account, + networkId, + summary, + redirectOnSuccess, + isTxSuccessfulBasedOnEvents, + }), ); }, [dispatch, networkId, account], diff --git a/apps/web/src/store/transactions/updater.tsx b/apps/web/src/store/transactions/updater.tsx index 077c8dc06..735862f9d 100644 --- a/apps/web/src/store/transactions/updater.tsx +++ b/apps/web/src/store/transactions/updater.tsx @@ -10,6 +10,7 @@ import { toast } from 'react-toastify'; import { NotificationError, NotificationSuccess } from '@/app/components/Notification/TransactionNotification'; import { useBlockNumber } from '@/store/application/hooks'; import { getTrackerLink } from '@/utils'; +import { XChainId, XTransactionType, useXTransactionStore, xTransactionActions } from '@balancednetwork/xwagmi'; import { AppDispatch } from '../index'; import { ICONTxEventLog, finalizeTransaction } from './actions'; @@ -29,78 +30,119 @@ export default function Updater(): null { const dispatch = useDispatch(); const transactions = useAllTransactions(); - + const xTransactions = useXTransactionStore(state => + Object.values(state.transactions).filter( + tx => + tx.type === XTransactionType.SWAP_ON_ICON || + tx.type === XTransactionType.DEPOSIT_ON_ICON || + tx.type === XTransactionType.WITHDRAW_ON_ICON || + tx.type === XTransactionType.BORROW_ON_ICON || + tx.type === XTransactionType.REPAY_ON_ICON || + tx.type === XTransactionType.CLAIM_NETWORK_FEES, + ), + ); // biome-ignore lint/correctness/useExhaustiveDependencies: React.useEffect(() => { - if (!networkId || !iconService || !transactions) return; - - Object.keys(transactions) - .filter(hash => shouldCheck(transactions[hash])) - .forEach(hash => { - iconService - .getTransactionResult(hash) - .execute() - .then(txResult => { - if (txResult) { - dispatch( - finalizeTransaction({ - networkId, - hash, - receipt: { - blockHash: txResult.blockHash, - blockHeight: txResult.blockHeight, - scoreAddress: txResult.scoreAddress, - // from: receipt.from, - status: Converter.toNumber(txResult.status), - to: txResult.to, - txHash: txResult.txHash, - txIndex: txResult.txIndex, - ...(transactions[hash].isTxSuccessfulBasedOnEvents && { - eventLogs: txResult.eventLogs as ICONTxEventLog[], - }), - }, - }), - ); - - const link = getTrackerLink(networkId, hash, 'transaction'); - const toastProps = { - onClick: () => window.open(link, '_blank'), - }; - - const predicate = transactions[hash].isTxSuccessfulBasedOnEvents; - - // success - if ( - (!predicate && txResult.status === 1) || - (predicate && predicate(txResult.eventLogs as ICONTxEventLog[])) - ) { - toast.update(txResult.txHash, { - ...toastProps, - render: ( - - ), - autoClose: 5000, - }); - } - - // failure - if (txResult.status === 0 || (predicate && !predicate(txResult.eventLogs as ICONTxEventLog[]))) { - toast.update(txResult.txHash, { - ...toastProps, - render: , - autoClose: 5000, - }); - } + if (!networkId || !iconService) return; + + // Check regular transactions + if (transactions) { + Object.keys(transactions) + .filter(hash => shouldCheck(transactions[hash])) + .forEach(hash => checkAndUpdateTransaction(hash)); + } + + // Check SWAP_ON_ICON transactions + xTransactions + .filter(tx => tx.status === 'pending') + .forEach(tx => { + const hash = tx.id; + if (hash) { + checkAndUpdateTransaction(hash); + } + }); + }, [networkId, iconService, transactions, xTransactions, dispatch, lastBlockNumber]); + + const checkAndUpdateTransaction = (hash: string) => { + iconService + .getTransactionResult(hash) + .execute() + .then(txResult => { + if (txResult) { + // Update regular transaction if it exists + if (transactions?.[hash]) { + dispatch( + finalizeTransaction({ + networkId, + hash, + receipt: { + blockHash: txResult.blockHash, + blockHeight: txResult.blockHeight, + scoreAddress: txResult.scoreAddress, + status: Converter.toNumber(txResult.status), + to: txResult.to, + txHash: txResult.txHash, + txIndex: txResult.txIndex, + ...(transactions[hash].isTxSuccessfulBasedOnEvents && { + eventLogs: txResult.eventLogs as ICONTxEventLog[], + }), + }, + }), + ); + } + + const link = getTrackerLink(networkId, hash, 'transaction'); + const toastProps = { + onClick: () => window.open(link, '_blank'), + }; + + // Find corresponding xTransaction if it exists + const xTransaction = xTransactions.find(tx => tx.id === hash); + + // success + if (txResult.status === 1) { + // Update xTransaction status if it exists + if (xTransaction) { + xTransactionActions.success(xTransaction.id); + } + + // Show success notification if regular transaction exists + if (transactions?.[hash]) { + toast.update(txResult.txHash, { + ...toastProps, + render: ( + + ), + autoClose: 5000, + }); + } + } + + // failure + if (txResult.status === 0) { + // Update xTransaction status if it exists + if (xTransaction) { + xTransactionActions.fail(xTransaction.id); + } + + // // Show error notification if regular transaction exists + if (transactions?.[hash]) { + toast.update(txResult.txHash, { + ...toastProps, + render: , + autoClose: 5000, + }); } - }) - .catch(error => { - console.error(`failed to check transaction hash: ${hash}`, error); - }); + } + } + }) + .catch(error => { + console.error(`failed to check transaction hash: ${hash}`, error); }); - }, [networkId, iconService, transactions, dispatch, lastBlockNumber]); + }; return null; } diff --git a/apps/web/src/store/transactions/useMMTransactionStore.tsx b/apps/web/src/store/transactions/useMMTransactionStore.tsx index 70b9147e9..b8215f14a 100644 --- a/apps/web/src/store/transactions/useMMTransactionStore.tsx +++ b/apps/web/src/store/transactions/useMMTransactionStore.tsx @@ -25,6 +25,7 @@ export type MMTransaction = { status: MMTransactionStatus; fromAmount: CurrencyAmount; toAmount: CurrencyAmount; + createdAt: number; }; type MMTransactionStore = { @@ -37,6 +38,7 @@ type MMTransactionStore = { setTaskId: (id: string, taskId: string) => void; getPendingTransactions: () => MMTransaction[]; remove: (id: string) => void; + getTransactions: () => MMTransaction[]; }; export const useMMTransactionStore = create()( @@ -50,7 +52,10 @@ export const useMMTransactionStore = create()( add: (transaction: MMTransaction) => { set(state => { - state.transactions[transaction.id] = transaction; + state.transactions[transaction.id] = { + ...transaction, + createdAt: transaction.createdAt || Date.now(), + }; }); }, @@ -79,21 +84,19 @@ export const useMMTransactionStore = create()( }, getPendingTransactions: () => { - return Object.values(get().transactions).filter((transaction: MMTransaction) => { - return ( - transaction.status !== MMTransactionStatus.success - // signedWallets.some(wallet => wallet.xChainId === transaction.sourceChainId) - ); - }); - // .sort((a: MMTransaction, b: MMTransaction) => { - // const aPrimaryMessage = xMessageActions.getOf(a.id, true); - // const bPrimaryMessage = xMessageActions.getOf(b.id, true); - // if (aPrimaryMessage && bPrimaryMessage) { - // return bPrimaryMessage.createdAt - aPrimaryMessage.createdAt; - // } - // return 0; - // }); + return Object.values(get().transactions) + .filter((transaction: MMTransaction) => { + return transaction.status !== MMTransactionStatus.success; + }) + .sort((a: MMTransaction, b: MMTransaction) => b.createdAt - a.createdAt); + }, + + getTransactions: () => { + return Object.values(get().transactions).sort( + (a: MMTransaction, b: MMTransaction) => b.createdAt - a.createdAt, + ); }, + remove: (id: string) => { set(state => { delete state.transactions[id]; diff --git a/apps/web/src/store/user/hooks.tsx b/apps/web/src/store/user/hooks.tsx index 6faf27e2c..1d024e060 100644 --- a/apps/web/src/store/user/hooks.tsx +++ b/apps/web/src/store/user/hooks.tsx @@ -137,3 +137,30 @@ export function useUserLocaleManager(): [SupportedLocale | null, (newLocale: Sup return [locale, setLocale]; } + +import { useEffect, useState } from 'react'; + +export function useElapsedTime(timestamp: number | undefined): number { + const [elapsedTime, setElapsedTime] = useState(0); + + useEffect(() => { + if (timestamp) { + const updateElapsedTime = () => { + setElapsedTime(Math.floor((Date.now() - timestamp) / 1000)); + }; + + updateElapsedTime(); // Update immediately + + const interval = setInterval( + () => { + updateElapsedTime(); + }, + Date.now() - timestamp > 600000 ? 60000 : 1000, + ); // 600000 ms = 10 minutes + + return () => clearInterval(interval); + } + }, [timestamp]); + + return elapsedTime; +} diff --git a/apps/web/src/utils/index.ts b/apps/web/src/utils/index.ts index a8ca2b6b9..55c901ef4 100644 --- a/apps/web/src/utils/index.ts +++ b/apps/web/src/utils/index.ts @@ -464,3 +464,29 @@ export function getTransactionAttributes(xTransactionInput: XTransactionInput) { return { descriptionAction, descriptionAmount }; } + +/** + * Formats a given time difference in seconds into a human-readable relative time string. + * + * @param difference - The difference in seconds between two Unix timestamps. + * @returns A string representing the relative time, such as "x days ago", "x hours ago", "x mins ago", or "just now". + */ +export function formatRelativeTime(difference: number): string { + const secondsInMinute = 60; + const secondsInHour = 3600; + const secondsInDay = 86400; + + const days = Math.floor(difference / secondsInDay); + const hours = Math.floor((difference % secondsInDay) / secondsInHour); + const minutes = Math.floor((difference % secondsInHour) / secondsInMinute); + + if (days > 0) { + return `${days} days ago`; + } else if (hours > 0) { + return `${hours} hours ago`; + } else if (minutes > 0) { + return `${minutes} mins ago`; + } else { + return `just now`; + } +} diff --git a/packages/xwagmi/src/constants/xChains.ts b/packages/xwagmi/src/constants/xChains.ts index b2c030c71..e831c9fa6 100644 --- a/packages/xwagmi/src/constants/xChains.ts +++ b/packages/xwagmi/src/constants/xChains.ts @@ -345,7 +345,7 @@ export const sui: XChain = { name: 'Sui', xChainId: 'sui', xChainType: 'SUI', - tracker: { tx: 'https://suiscan.xyz/mainnet/tx' }, + tracker: { tx: 'https://suivision.xyz/txblock' }, nativeCurrency: { decimals: 9, name: 'SUI', diff --git a/packages/xwagmi/src/index.ts b/packages/xwagmi/src/index.ts index 3fd9b6ee4..f3e09c71a 100644 --- a/packages/xwagmi/src/index.ts +++ b/packages/xwagmi/src/index.ts @@ -18,6 +18,7 @@ export * from './hooks'; export * from './useXWagmiStore'; export * from './XWagmiProviders'; export * from './xcall'; +export * from './xcall/zustand/useTransactionStore'; export * from './types'; export type * from './types'; diff --git a/packages/xwagmi/src/xcall/types.ts b/packages/xwagmi/src/xcall/types.ts index 762508acd..8db52aea7 100644 --- a/packages/xwagmi/src/xcall/types.ts +++ b/packages/xwagmi/src/xcall/types.ts @@ -37,9 +37,13 @@ export enum XTransactionType { // lend & borrow DEPOSIT = 'deposit', + DEPOSIT_ON_ICON = 'deposit_on_icon', WITHDRAW = 'withdraw', + WITHDRAW_ON_ICON = 'withdraw_on_icon', BORROW = 'borrow', + BORROW_ON_ICON = 'borrow_on_icon', REPAY = 'repay', + REPAY_ON_ICON = 'repay_on_icon', //liquidity LP_DEPOSIT_XTOKEN = 'lp_deposit_xtoken', @@ -54,6 +58,9 @@ export enum XTransactionType { SAVINGS_LOCK_BNUSD = 'savings_lock_bnusd', SAVINGS_UNLOCK_BNUSD = 'savings_unlock_bnusd', SAVINGS_CLAIM_REWARDS = 'savings_claim_rewards', + + // network fees + CLAIM_NETWORK_FEES = 'claim_network_fees', } export enum XMessageStatus { @@ -94,13 +101,15 @@ export type XTransactionInput = { withdrawAmountB?: CurrencyAmount; }; -export type Transaction = { +export interface Transaction { id: string; hash: string; xChainId: XChainId; status: TransactionStatus; timestamp: number; + input?: XTransactionInput; + type?: XTransactionType; pendingMessage?: string; successMessage?: string; errorMessage?: string; @@ -108,7 +117,8 @@ export type Transaction = { onSuccess?: () => void; // Callback on success rawEventLogs?: any[]; -}; + createdAt?: number; +} export type XCallMessageEvent = { eventType: XCallEventType; diff --git a/packages/xwagmi/src/xcall/utils.ts b/packages/xwagmi/src/xcall/utils.ts index e939862ab..08144de57 100644 --- a/packages/xwagmi/src/xcall/utils.ts +++ b/packages/xwagmi/src/xcall/utils.ts @@ -8,6 +8,7 @@ import { xTokenMap } from '@/constants/xTokens'; import { XChain, XToken } from '@/types'; import { uintToBytes } from '@/utils'; import { xChains } from '../constants/xChains'; +import { XTransaction, XTransactionInput, XTransactionType } from './types'; export function getBytesFromNumber(value) { const hexString = value.toString(16).padStart(2, '0'); @@ -112,3 +113,33 @@ export const getSupportedXChainForSwapToken = (currency?: Currency | XToken | nu return xChains.filter(x => xChainIds.includes(x.xChainId)); }; + +export const getICONXTransactionInput = ( + account: string, + type: XTransactionType, + inputAmount: CurrencyAmount, + outputAmount?: CurrencyAmount, +): XTransactionInput => { + return { + direction: { + from: '0x1.icon', + to: '0x1.icon', + }, + type, + account, + inputAmount: inputAmount, + outputAmount: outputAmount, + xCallFee: { + noRollback: 0n, + rollback: 0n, + }, + }; +}; + +export const getTxTrackerLink = (hash?: string, xChainId?: XChainId): string | undefined => { + if (hash && xChainId) { + return `${xChains.find(x => x.xChainId === xChainId)?.tracker.tx}/${hash}`; + } + + return undefined; +}; diff --git a/packages/xwagmi/src/xcall/zustand/useTransactionStore.tsx b/packages/xwagmi/src/xcall/zustand/useTransactionStore.tsx index 2bb871964..af55aba4a 100644 --- a/packages/xwagmi/src/xcall/zustand/useTransactionStore.tsx +++ b/packages/xwagmi/src/xcall/zustand/useTransactionStore.tsx @@ -12,7 +12,7 @@ import { getXPublicClient } from '@/actions'; // } from '@/app/components/Notification/TransactionNotification'; import { isIconTransaction } from '@/utils'; import { getTrackerLink } from '@/utils'; -import { Transaction, TransactionStatus, XTransactionType } from '@/xcall/types'; +import { Transaction, TransactionStatus, XTransactionInput } from '@/xcall/types'; import { XChainId } from '@balancednetwork/sdk-core'; import { persist } from 'zustand/middleware'; import { xTransactionActions } from './useXTransactionStore'; @@ -47,6 +47,7 @@ export const transactionActions = { pendingMessage?: string; successMessage?: string; errorMessage?: string; + input?: XTransactionInput; onSuccess?: () => void; }, ): Transaction => { diff --git a/packages/xwagmi/src/xcall/zustand/useXMessageStore.tsx b/packages/xwagmi/src/xcall/zustand/useXMessageStore.tsx index e005d5006..c6c11142f 100644 --- a/packages/xwagmi/src/xcall/zustand/useXMessageStore.tsx +++ b/packages/xwagmi/src/xcall/zustand/useXMessageStore.tsx @@ -9,7 +9,7 @@ import { immer } from 'zustand/middleware/immer'; import { getXPublicClient } from '@/actions'; import { xChainMap } from '@/constants/xChains'; import { jsonStorageOptions } from '@/utils/zustand'; -import { XCallEventType, XTransaction } from '../types'; +import { XCallEventType, XTransaction, XTransactionType } from '../types'; import { TransactionStatus, XCallEventMap, XMessage, XMessageStatus } from '../types'; import { transactionActions } from './useTransactionStore'; import { useXCallEventScanner, xCallEventActions } from './useXCallEventStore'; @@ -177,6 +177,18 @@ export const useXMessageStore = create()( const xTransaction = xTransactionActions.get(xMessage.xTransactionId); if (!xTransaction) return; + // Skip XMessage handling for ICON transactions + if ( + xTransaction.type === XTransactionType.SWAP_ON_ICON || + xTransaction.type === XTransactionType.DEPOSIT_ON_ICON || + xTransaction.type === XTransactionType.WITHDRAW_ON_ICON || + xTransaction.type === XTransactionType.BORROW_ON_ICON || + xTransaction.type === XTransactionType.REPAY_ON_ICON || + xTransaction.type === XTransactionType.CLAIM_NETWORK_FEES + ) { + return; + } + if (xMessage.isPrimary) { if (xMessage.status === XMessageStatus.CALL_EXECUTED) { if (xTransaction.secondaryMessageRequired) {