diff --git a/src/pages/bridge/components/TxTable/index.tsx b/src/pages/bridge/components/TxTable/index.tsx index a5e2f9482..44723d47e 100644 --- a/src/pages/bridge/components/TxTable/index.tsx +++ b/src/pages/bridge/components/TxTable/index.tsx @@ -23,7 +23,7 @@ import Link from "@/components/Link" import { useApp } from "@/contexts/AppContextProvider" import useTokenInfo from "@/hooks/useTokenInfo" import { toTokenDisplay } from "@/utils" -import { generateExploreLink, truncateHash } from "@/utils" +import { generateTxLink, truncateHash } from "@/utils" const useStyles = makeStyles()(theme => { return { @@ -212,7 +212,7 @@ const TxRow = props => { From {tx.fromName}: - + {truncateHash(tx.hash)} {!tx.fromBlockNumber && } @@ -226,7 +226,7 @@ const TxRow = props => { To {tx.toName}: {tx.toHash ? ( - + {truncateHash(tx.toHash)} ) : ( diff --git a/src/pages/nftBridge/Header/TransactionHistory.tsx b/src/pages/nftBridge/Header/TransactionHistory.tsx index e49c1cc21..d8ca0c539 100644 --- a/src/pages/nftBridge/Header/TransactionHistory.tsx +++ b/src/pages/nftBridge/Header/TransactionHistory.tsx @@ -4,7 +4,7 @@ import { CircularProgress, Stack, Typography } from "@mui/material" import { BRIDGE_PAGE_SIZE } from "@/constants" import { useApp } from "@/contexts/AppContextProvider" -import useTxStore from "@/stores/txStore" +import useNFTTxStore from "@/stores/nftTxStore" import TxTable from "../components/TxTable" @@ -42,9 +42,10 @@ const TransactionsList = (props: any) => { txHistory: { refreshPageTransactions }, } = useApp() - const { page, total, loading, frontTransactions, pageTransactions } = useTxStore() + const { page, total, loading, frontTransactions } = useNFTTxStore() - if (!pageTransactions?.length) { + // TODO: waiting for api + if (!frontTransactions?.length) { return ( Your transactions will appear here... @@ -67,7 +68,7 @@ const TransactionsList = (props: any) => { ({ container: { @@ -89,14 +89,14 @@ const Header = () => { const { clearViewingList, clearSelectedList } = useNFTBridgeStore() - const { - txHistory: { refreshPageTransactions }, - } = useApp() + // const { + // txHistory: { refreshPageTransactions }, + // } = useApp() const { historyVisible, changeHistoryVisible } = useBridgeStore() const handleOpen = () => { changeHistoryVisible(true) - refreshPageTransactions(1) + // refreshPageTransactions(1) } const handleClose = () => { @@ -132,8 +132,7 @@ const Header = () => { - coming soon! - {/* */} + diff --git a/src/pages/nftBridge/NFTPanel/Transfer/Send.tsx b/src/pages/nftBridge/NFTPanel/Transfer/Send.tsx index 661985d1e..59d6e2c08 100644 --- a/src/pages/nftBridge/NFTPanel/Transfer/Send.tsx +++ b/src/pages/nftBridge/NFTPanel/Transfer/Send.tsx @@ -11,6 +11,7 @@ import { useAsyncMemo } from "@/hooks" import useGasFee from "@/hooks/useGasFee" import useApprove from "@/hooks/useNFTApprove" import useNFTBridgeStore from "@/stores/nftBridgeStore" +import useNFTTxStore from "@/stores/nftTxStore" import ApproveLoadingModal from "./ApproveLoadingModal" import SendLoadingModal from "./SendLoadingModal" @@ -20,8 +21,9 @@ const Send = () => { const { walletCurrentAddress } = useWeb3Context() const { networksAndSigners } = useApp() + const { addNFTTransaction, updateNFTTransaction } = useNFTTxStore() const { tokenInstance, gatewayAddress, isLayer1 } = useNFTBridgeContext() - const { contract, selectedList, exciseSelected, updatePromptMessage } = useNFTBridgeStore() + const { contract, selectedList, exciseSelected, updatePromptMessage, fromNetwork, toNetwork } = useNFTBridgeStore() const selectedTokenIds = useNFTBridgeStore(state => state.selectedTokenIds()) const { setApproval, checkApproval } = useApprove() @@ -69,15 +71,31 @@ const Send = () => { } } - const handleSend = () => { + const handleSend = async () => { setSendLoading(true) setSendModalLoading(true) - const tx = isLayer1 ? deposite() : withdraw() - tx.then(result => { - console.log(result, "send result") - setTxHash(result.transactionHash) - exciseSelected() + const tx = isLayer1 ? await deposite() : await withdraw() + addNFTTransaction({ + hash: tx.hash, + fromName: fromNetwork.name, + toName: toNetwork.name, + fromExplore: fromNetwork.explorer, + toExplore: toNetwork.explorer, + tokenType: contract.type, + tokenAddress: isLayer1 ? contract.l1 : contract.l2, + amounts: selectedList.map(item => item.transferAmount), + tokenIds: selectedTokenIds, + isL1: isLayer1, }) + tx.wait() + .then(receipt => { + console.log(receipt, "send receipt") + updateNFTTransaction(tx.hash, { + fromBlockNumber: receipt.blockNumber, + }) + setTxHash(receipt.transactionHash) + exciseSelected() + }) .catch(error => { updatePromptMessage(error.message) }) @@ -104,8 +122,7 @@ const Send = () => { value: gasFee, }, ) - const txResult = await tx.wait() - return txResult + return tx } const deposite1155 = async () => { @@ -119,8 +136,7 @@ const Send = () => { value: gasFee, }, ) - const txResult = await tx.wait() - return txResult + return tx } const withdraw = () => { @@ -138,8 +154,7 @@ const Send = () => { gasLimit, { value: gasFee }, ) - const txResult = await tx.wait() - return txResult + return tx } const withdraw1155 = async () => { @@ -151,8 +166,7 @@ const Send = () => { gasLimit, { value: gasFee }, ) - const txResult = await tx.wait() - return txResult + return tx } const handleCloseSendModal = () => { diff --git a/src/pages/nftBridge/NFTPanel/Transfer/TransactionResultModal.tsx b/src/pages/nftBridge/NFTPanel/Transfer/TransactionResultModal.tsx index 4f89dc5df..303883694 100644 --- a/src/pages/nftBridge/NFTPanel/Transfer/TransactionResultModal.tsx +++ b/src/pages/nftBridge/NFTPanel/Transfer/TransactionResultModal.tsx @@ -5,7 +5,7 @@ import { Button } from "@mui/material" import Link from "@/components/Link" import { BLOCK_EXPLORER } from "@/constants" import { useWeb3Context } from "@/contexts/Web3ContextProvider" -import { generateExploreLink } from "@/utils" +import { generateTxLink } from "@/utils" import Modal from "../../components/Modal" @@ -17,7 +17,7 @@ const TransactionResultModal = props => { const txUrl = useMemo(() => { if (hash && chainId) { const explorer = BLOCK_EXPLORER[chainId] - return generateExploreLink(explorer, hash) + return generateTxLink(explorer, hash) } return "" }, [chainId, hash]) diff --git a/src/pages/nftBridge/components/TxTable/index.tsx b/src/pages/nftBridge/components/TxTable/index.tsx index 5d5ac8ea6..759d30ec9 100644 --- a/src/pages/nftBridge/components/TxTable/index.tsx +++ b/src/pages/nftBridge/components/TxTable/index.tsx @@ -19,8 +19,7 @@ import { import Link from "@/components/Link" import { useApp } from "@/contexts/AppContextProvider" -import useSymbol from "@/hooks/useSymbol" -import { generateExploreLink, truncateHash } from "@/utils" +import { generateContractLink, generateTxLink, truncateAddress, truncateHash } from "@/utils" const useStyles = makeStyles()(theme => { return { @@ -35,7 +34,7 @@ const useStyles = makeStyles()(theme => { boxShadow: "unset", border: `1px solid ${theme.palette.border.main}`, borderRadius: "1rem", - width: "70rem", + width: "82rem", }, tableTitle: { marginTop: "2.8rem", @@ -52,10 +51,15 @@ const useStyles = makeStyles()(theme => { }, }, chip: { - width: "12.6rem", - height: "3.8rem", - fontSize: "1.6rem", + width: "9rem", + height: "2.8rem", + fontSize: "1.2rem", + padding: 0, fontWeight: 500, + ".MuiChip-label": { + paddingLeft: 0, + paddingRight: 0, + }, }, pendingChip: { color: theme.palette.tagWarning.main, @@ -100,6 +104,9 @@ const TxTable = (props: any) => { Status + Contract Address + Type + Token IDs Amount Txn Hash @@ -165,8 +172,6 @@ const TxRow = props => { return txStatus(tx.toBlockNumber, tx.isL1, true) }, [tx, txStatus]) - const { loading: symbolLoading, symbol } = useSymbol(tx.symbolToken, tx.isL1) - return ( @@ -184,17 +189,37 @@ const TxRow = props => { )} - + + + + {truncateAddress(tx.tokenAddress)} + + + + - {tx.amount} - {symbolLoading ? : {symbol}} + + + + {tx.tokenIds.map(item => ( + {item} + ))} + + + + + {tx.amounts.map(item => ( + {item} + ))} + + From {tx.fromName}: - + {truncateHash(tx.hash)} @@ -204,7 +229,7 @@ const TxRow = props => { To {tx.toName}: {tx.toHash ? ( - + {truncateHash(tx.toHash)} ) : ( diff --git a/src/stores/nftTxStore.ts b/src/stores/nftTxStore.ts new file mode 100644 index 000000000..40df86561 --- /dev/null +++ b/src/stores/nftTxStore.ts @@ -0,0 +1,197 @@ +import produce from "immer" +import create from "zustand" +import { persist } from "zustand/middleware" + +import { fetchTxListUrl } from "@/apis/bridge" +import { networks } from "@/constants" +import { NFT_BRIDGE_TRANSACTIONS } from "@/utils/storageKey" + +interface NFTTxStore { + page: number + total: number + loading: boolean + frontTransactions: Transaction[] + transactions: Transaction[] + pageTransactions: Transaction[] + addNFTTransaction: (tx) => void + updateNFTTransaction: (hash, tx) => void + generateNFTTransactions: (transactions, safeBlockNumber) => void + comboPageNFTTransactions: (address, page, rowsPerPage, safeBlockNumber) => Promise + clearNFTTransactions: () => void +} +interface Transaction { + hash: string + toHash?: string + fromName: string + toName: string + fromExplore: string + toExplore?: string + fromBlockNumber?: number + toBlockNumber?: number + tokenType: string + tokenAdress: string + amounts: Array + tokenIds: Array + isL1: boolean +} + +const formatBackTxList = (backList, safeBlockNumber) => { + if (!backList.length) { + return [] + } + return backList.map(tx => { + const amount = tx.amount + const fromName = networks[+!tx.isL1].name + const fromExplore = networks[+!tx.isL1].explorer + const toName = networks[+tx.isL1].name + const toExplore = networks[+tx.isL1].explorer + const toHash = tx.finalizeTx?.hash + const fromEstimatedEndTime = tx.isL1 && tx.blockNumber > safeBlockNumber ? Date.now() + (tx.blockNumber - safeBlockNumber) * 12 * 1000 : undefined + const toEstimatedEndTime = + !tx.isL1 && tx.finalizeTx?.blockNumber && tx.finalizeTx.blockNumber > safeBlockNumber + ? Date.now() + (tx.finalizeTx.blockNumber - safeBlockNumber) * 12 * 1000 + : undefined + return { + hash: tx.hash, + amount, + fromName, + fromExplore, + fromBlockNumber: tx.blockNumber, + fromEstimatedEndTime, + toHash, + toName, + toExplore, + toBlockNumber: tx.finalizeTx?.blockNumber, + isL1: tx.isL1, + symbolToken: tx.isL1 ? tx.l1Token : tx.l2Token, + toEstimatedEndTime, + } + }) +} + +const useNFTTxStore = create()( + persist( + (set, get) => ({ + page: 1, + total: 0, + frontTransactions: [], + loading: false, + // frontTransactions + backendTransactions.slice(0, 2) + transactions: [], + pageTransactions: [], + // when user send a transaction + addNFTTransaction: newTx => + set(state => ({ + frontTransactions: [newTx, ...state.frontTransactions], + transactions: [newTx, ...state.transactions], + })), + // wait transaction success in from network + updateNFTTransaction: (hash, updateOpts) => + set( + produce(state => { + const frontTx = state.frontTransactions.find(item => item.hash === hash) + if (frontTx) { + for (const key in updateOpts) { + frontTx[key] = updateOpts[key] + } + } + // for stay on "recent tx" page + const recentTx = state.transactions.find(item => item.hash === hash) + if (recentTx) { + for (const key in updateOpts) { + recentTx[key] = updateOpts[key] + } + } + // for keep "bridge history" open + const pageTx = state.pageTransactions.find(item => item.hash === hash) + if (pageTx) { + for (const key in updateOpts) { + pageTx[key] = updateOpts[key] + } + } + }), + ), + // polling transactions + // slim frontTransactions and keep the latest 3 backTransactions + generateNFTTransactions: (historyList, safeBlockNumber) => { + const realHistoryList = historyList.filter(item => item) + if (realHistoryList.length) { + const formattedHistoryList = formatBackTxList(realHistoryList, safeBlockNumber) + const formattedHistoryListHash = formattedHistoryList.map(item => item.hash) + const formattedHistoryListMap = Object.fromEntries(formattedHistoryList.map(item => [item.hash, item])) + const pendingFrontList = get().frontTransactions.filter(item => !formattedHistoryListHash.includes(item.hash)) + const pendingFrontListHash = pendingFrontList.map(item => item.hash) + const syncList = formattedHistoryList.filter(item => !pendingFrontListHash.includes(item.hash)) + const restList = get().transactions.filter(item => item.toHash) + + const refreshPageTransaction = get().pageTransactions.map(item => { + if (formattedHistoryListMap[item.hash]) { + return formattedHistoryListMap[item.hash] + } + return item + }) + set({ + transactions: pendingFrontList.concat([...syncList, ...restList].slice(0, 2)), + frontTransactions: pendingFrontList, + pageTransactions: refreshPageTransaction, + }) + } + }, + clearNFTTransactions: () => { + set({ + frontTransactions: [], + transactions: [], + pageTransactions: [], + page: 1, + total: 0, + }) + }, + + // page transactions + comboPageNFTTransactions: async (address, page, rowsPerPage, safeBlockNumber) => { + const frontTransactions = get().frontTransactions + set({ loading: true }) + const offset = (page - 1) * rowsPerPage + // const offset = gap > 0 ? gap : 0; + if (frontTransactions.length >= rowsPerPage + offset) { + set({ + pageTransactions: frontTransactions.slice(offset, offset + rowsPerPage), + page, + loading: false, + }) + return + } + + const currentPageFrontTransactions = frontTransactions.slice((page - 1) * rowsPerPage) + const gap = (page - 1) * rowsPerPage - frontTransactions.length + const relativeOffset = gap > 0 ? gap : 0 + const limit = rowsPerPage - currentPageFrontTransactions.length + + return scrollRequest(`${fetchTxListUrl}?address=${address}&offset=${relativeOffset}&limit=${limit}`) + .then(data => { + set({ + pageTransactions: [...frontTransactions, ...formatBackTxList(data.data.result, safeBlockNumber)], + total: data.data.total, + page, + loading: false, + }) + if (page === 1) { + // keep transactions always frontList + the latest two history list + set({ + transactions: [...frontTransactions, ...formatBackTxList(data.data.result, safeBlockNumber).slice(0, 2)], + }) + } + }) + .catch(error => { + set({ loading: false }) + return Promise.reject(`${error.status}:${error.message}`) + }) + }, + }), + { + name: NFT_BRIDGE_TRANSACTIONS, + }, + ), +) + +export default useNFTTxStore diff --git a/src/utils/common.ts b/src/utils/common.ts index 10ae9516e..b79f04d05 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -25,8 +25,12 @@ export function requireEnv(entry) { } } -export const generateExploreLink = (explorer, hash) => { +export const generateTxLink = (explorer, hash) => { return `${explorer}/tx/${hash}` } +export const generateContractLink = (explorer, address) => { + return `${explorer}/address/${address}` +} + export const isProduction = requireEnv("REACT_APP_SCROLL_ENVIRONMENT") === requireEnv("REACT_APP_MAIN_ENVIRONMENT") diff --git a/src/utils/storageKey.ts b/src/utils/storageKey.ts index c4353f683..c71a7c7b0 100644 --- a/src/utils/storageKey.ts +++ b/src/utils/storageKey.ts @@ -9,4 +9,6 @@ export const BRIDGE_TOKEN_SYMBOL = "bridgeTokenSymbol" export const BRIDGE_TRANSACTIONS = "bridgeTransactions" +export const NFT_BRIDGE_TRANSACTIONS = "nftBridgeTransactions" + export const APP_VERSION = "appVersion"