diff --git a/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml b/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml index 485ecaf6d3..a1bd4f0ed6 100644 --- a/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml +++ b/.github/ISSUE_TEMPLATE/add-orbit-chain-request.yml @@ -70,6 +70,7 @@ body: options: - "1" - "42161" + - "42170" - "421614" - "11155111" - "17000" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ae50d87f53..f723caa1be 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -95,6 +95,7 @@ jobs: - name: Run nitro testnode if: inputs.test_type != 'cctp' + timeout-minutes: 20 uses: OffchainLabs/actions/run-nitro-test-node@a20a76172ce524832ac897bef2fa10a62ed81c29 with: nitro-testnode-ref: badbcbea9b43d46e115da4d7c9f2f57c31af8431 diff --git a/packages/arb-token-bridge-ui/package.json b/packages/arb-token-bridge-ui/package.json index abe147b101..928566c2df 100644 --- a/packages/arb-token-bridge-ui/package.json +++ b/packages/arb-token-bridge-ui/package.json @@ -10,7 +10,7 @@ "@headlessui/react": "^1.7.8", "@headlessui/tailwindcss": "^0.1.2", "@heroicons/react": "^2.0.18", - "@offchainlabs/cobalt": "^0.3.11", + "@offchainlabs/cobalt": "^0.3.12", "@rainbow-me/rainbowkit": "^0.12.16", "@rehooks/local-storage": "^2.4.4", "@sentry/react": "^8.33.1", @@ -23,29 +23,30 @@ "axios": "^1.7.4", "boring-avatars": "^1.7.0", "cheerio": "^1.0.0-rc.12", - "dayjs": "^1.11.8", + "dayjs": "^1.11.13", "ethers": "^5.6.0", - "graphql": "^16.8.1", + "exponential-backoff": "^3.1.1", + "graphql": "^16.9.0", "lodash-es": "^4.17.21", - "next": "^14.2.12", + "next": "^14.2.22", "next-query-params": "^5.0.0", "overmind": "^28.0.1", "overmind-react": "^29.0.1", - "posthog-js": "^1.155.4", + "posthog-js": "^1.200.0", "query-string": "^8.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-loader-spinner": "^5.4.5", "react-syntax-highlighter": "^15.6.1", "react-toastify": "^9.1.1", - "react-use": "^17.2.4", + "react-use": "^17.6.0", "react-virtualized": "^9.22.3", "sharp": "^0.33.5", - "swr": "^2.1.2", - "tailwind-merge": "^2.0.0", + "swr": "^2.3.0", + "tailwind-merge": "^2.5.5", "use-query-params": "^2.2.1", "wagmi": "^0.12.18", - "zod": "^3.22.4", + "zod": "^3.24.1", "zustand": "^4.3.9" }, "scripts": { @@ -82,7 +83,7 @@ "devDependencies": { "@next/eslint-plugin-next": "^13.1.5", "@synthetixio/synpress": "3.7.3", - "@testing-library/react": "^14.2.1", + "@testing-library/react": "^16.1.0", "@types/jest": "^29.5.1", "@types/lodash-es": "^4.17.6", "@types/node": "^16.6.1", @@ -108,14 +109,14 @@ "jest": "^29.4.0", "jest-environment-jsdom": "^29.4.0", "patch-package": "^8.0.0", - "postcss": "^8.4.31", + "postcss": "^8.4.49", "postinstall-postinstall": "^2.1.0", "prettier": "^2.7.1", "prettier-plugin-tailwindcss": "^0.1.11", - "satori": "^0.10.11", - "start-server-and-test": "^2.0.0", - "tailwindcss": "^3.2.4", - "ts-node": "^10.9.1", + "satori": "^0.12.0", + "start-server-and-test": "^2.0.9", + "tailwindcss": "^3.4.16", + "ts-node": "^10.9.2", "typescript": "^5.2.2" } } diff --git a/packages/arb-token-bridge-ui/public/icons/history.svg b/packages/arb-token-bridge-ui/public/icons/history.svg new file mode 100644 index 0000000000..0e766b0c49 --- /dev/null +++ b/packages/arb-token-bridge-ui/public/icons/history.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/arb-token-bridge-ui/public/icons/wallet.svg b/packages/arb-token-bridge-ui/public/icons/wallet.svg new file mode 100644 index 0000000000..6623ec3f47 --- /dev/null +++ b/packages/arb-token-bridge-ui/public/icons/wallet.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_Logo.png b/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_Logo.png new file mode 100644 index 0000000000..8c7f51374f Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_Logo.png differ diff --git a/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_NativeTokenLogo.png b/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_NativeTokenLogo.png new file mode 100644 index 0000000000..231b8d2fb2 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/DataLakeMainnet_NativeTokenLogo.png differ diff --git a/packages/arb-token-bridge-ui/public/images/LightningIcon.svg b/packages/arb-token-bridge-ui/public/images/LightningIcon.svg new file mode 100644 index 0000000000..b3b74ca881 --- /dev/null +++ b/packages/arb-token-bridge-ui/public/images/LightningIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/arb-token-bridge-ui/public/images/PlumeDevnet_Logo.png b/packages/arb-token-bridge-ui/public/images/PlumeDevnet_Logo.png new file mode 100644 index 0000000000..3228298b61 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/PlumeDevnet_Logo.png differ diff --git a/packages/arb-token-bridge-ui/public/images/SocialMainnet_Logo.webp b/packages/arb-token-bridge-ui/public/images/SocialMainnet_Logo.webp new file mode 100644 index 0000000000..5dbca13596 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/SocialMainnet_Logo.webp differ diff --git a/packages/arb-token-bridge-ui/public/images/unite-mainnet_Logo.png b/packages/arb-token-bridge-ui/public/images/unite-mainnet_Logo.png new file mode 100644 index 0000000000..1a90c1d876 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/unite-mainnet_Logo.png differ diff --git a/packages/arb-token-bridge-ui/public/images/unite-testnet_Logo.png b/packages/arb-token-bridge-ui/public/images/unite-testnet_Logo.png new file mode 100644 index 0000000000..1a90c1d876 Binary files /dev/null and b/packages/arb-token-bridge-ui/public/images/unite-testnet_Logo.png differ diff --git a/packages/arb-token-bridge-ui/scripts/generateCoreChainsToMonitor.ts b/packages/arb-token-bridge-ui/scripts/generateCoreChainsToMonitor.ts index e9170e9b7c..13f9ef6af7 100644 --- a/packages/arb-token-bridge-ui/scripts/generateCoreChainsToMonitor.ts +++ b/packages/arb-token-bridge-ui/scripts/generateCoreChainsToMonitor.ts @@ -1,7 +1,8 @@ import fs from 'fs' import 'dotenv/config' import { getArbitrumNetwork } from '@arbitrum/sdk' -import { ChainId, rpcURLs } from '../src/util/networks' +import { rpcURLs } from '../src/util/networks' +import { ChainId } from '../src/types/ChainId' import { getChainToMonitor } from './utils' // github secrets return '' for empty values, so we need to sanitize the value diff --git a/packages/arb-token-bridge-ui/src/api-utils/ServerSubgraphUtils.ts b/packages/arb-token-bridge-ui/src/api-utils/ServerSubgraphUtils.ts index 0925c16e5d..60a1d083bd 100644 --- a/packages/arb-token-bridge-ui/src/api-utils/ServerSubgraphUtils.ts +++ b/packages/arb-token-bridge-ui/src/api-utils/ServerSubgraphUtils.ts @@ -5,7 +5,7 @@ import { NormalizedCacheObject } from '@apollo/client' -import { ChainId } from '../util/networks' +import { ChainId } from '../types/ChainId' /** * The API key to be used for calls to The Graph Network. diff --git a/packages/arb-token-bridge-ui/src/components/App/App.tsx b/packages/arb-token-bridge-ui/src/components/App/App.tsx index d23f339fb8..51b398d039 100644 --- a/packages/arb-token-bridge-ui/src/components/App/App.tsx +++ b/packages/arb-token-bridge-ui/src/components/App/App.tsx @@ -21,7 +21,6 @@ import { AppContextProvider } from './AppContext' import { config, useActions, useAppState } from '../../state' import { MainContent } from '../MainContent/MainContent' import { ArbTokenBridgeStoreSync } from '../syncers/ArbTokenBridgeStoreSync' -import { BalanceUpdater } from '../syncers/BalanceUpdater' import { TokenListSyncer } from '../syncers/TokenListSyncer' import { Header } from '../common/Header' import { HeaderAccountPopover } from '../common/HeaderAccountPopover' @@ -219,7 +218,6 @@ function AppContent() { - diff --git a/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx b/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx index e70d0713f0..cd669ab479 100644 --- a/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx +++ b/packages/arb-token-bridge-ui/src/components/App/AppContext.tsx @@ -1,23 +1,16 @@ import { createContext, useContext, useReducer, Dispatch } from 'react' -export enum TransactionHistoryTab { - DEPOSITS = 0, - WITHDRAWALS = 1, - CCTP = 2 -} type AppContextState = { layout: { isTransferPanelVisible: boolean isTransferring: boolean - isTransactionHistoryPanelVisible: boolean } } const initialState: AppContextState = { layout: { isTransferPanelVisible: true, - isTransferring: false, - isTransactionHistoryPanelVisible: false + isTransferring: false } } @@ -29,7 +22,6 @@ const AppContext = createContext([initialState, () => {}]) type Action = | { type: 'layout.set_is_transfer_panel_visible'; payload: boolean } | { type: 'layout.set_is_transferring'; payload: boolean } - | { type: 'layout.set_txhistory_panel_visible'; payload: boolean } function reducer(state: AppContextState, action: Action) { switch (action.type) { @@ -39,15 +31,6 @@ function reducer(state: AppContextState, action: Action) { layout: { ...state.layout, isTransferPanelVisible: action.payload } } - case 'layout.set_txhistory_panel_visible': - return { - ...state, - layout: { - ...state.layout, - isTransactionHistoryPanelVisible: action.payload - } - } - case 'layout.set_is_transferring': return { ...state, @@ -88,17 +71,7 @@ export const useAppContextActions = (dispatchOverride?: Dispatch) => { dispatch({ type: 'layout.set_is_transferring', payload }) } - const openTransactionHistoryPanel = () => { - dispatch({ type: 'layout.set_txhistory_panel_visible', payload: true }) - } - - const closeTransactionHistoryPanel = () => { - dispatch({ type: 'layout.set_txhistory_panel_visible', payload: false }) - } - return { - setTransferring, - openTransactionHistoryPanel, - closeTransactionHistoryPanel + setTransferring } } diff --git a/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx b/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx index 158308db54..aa021558ec 100644 --- a/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx +++ b/packages/arb-token-bridge-ui/src/components/MainContent/MainContent.tsx @@ -1,68 +1,57 @@ -import { useEffect, useMemo } from 'react' -import { useAccount } from 'wagmi' import { useLocalStorage } from '@uidotdev/usehooks' +import { Tab } from '@headlessui/react' +import { create } from 'zustand' import { TransferPanel } from '../TransferPanel/TransferPanel' -import { SidePanel } from '../common/SidePanel' -import { useAppContextActions, useAppContextState } from '../App/AppContext' import { ArbitrumStats, statsLocalStorageKey } from './ArbitrumStats' import { SettingsDialog } from '../common/SettingsDialog' import { TransactionHistory } from '../TransactionHistory/TransactionHistory' -import { useTransactionHistory } from '../../hooks/useTransactionHistory' -import { isTxPending } from '../TransactionHistory/helpers' -import { TransactionStatusInfo } from '../TransactionHistory/TransactionStatusInfo' +import { TopNavBar } from '../TopNavBar' +import { useBalanceUpdater } from '../syncers/useBalanceUpdater' -function TransactionHistorySidePanel() { - const { closeTransactionHistoryPanel } = useAppContextActions() - const { - layout: { isTransactionHistoryPanelVisible } - } = useAppContextState() - const { address } = useAccount() - - const transactionHistoryProps = useTransactionHistory(address, { - runFetcher: true - }) - - const { transactions, updatePendingTransaction } = transactionHistoryProps - - const pendingTransactions = useMemo(() => { - return transactions.filter(isTxPending) - }, [transactions]) - - useEffect(() => { - const interval = setInterval(() => { - pendingTransactions.forEach(updatePendingTransaction) - }, 10_000) - - return () => clearInterval(interval) - }, [pendingTransactions, updatePendingTransaction]) +enum MainContentTabs { + Bridge = 0, + TransactionHistory = 1 +} - return ( - - - - ) +type MainContentTabStore = { + selectedTab: MainContentTabs + setSelectedTab: (index: MainContentTabs) => void + switchToBridgeTab: () => void + switchToTransactionHistoryTab: () => void } +export const useMainContentTabs = create(set => ({ + selectedTab: MainContentTabs.Bridge, + setSelectedTab: (index: MainContentTabs) => set({ selectedTab: index }), + switchToBridgeTab: () => set({ selectedTab: MainContentTabs.Bridge }), + switchToTransactionHistoryTab: () => + set({ selectedTab: MainContentTabs.TransactionHistory }) +})) + export function MainContent() { const [isArbitrumStatsVisible] = useLocalStorage(statsLocalStorageKey) + const { selectedTab, setSelectedTab } = useMainContentTabs() + + useBalanceUpdater() return ( <> -
- - - +
+ + + + + + + + + + +
- - {/* Settings panel */} diff --git a/packages/arb-token-bridge-ui/src/components/Sidebar/AccountMenuItem.tsx b/packages/arb-token-bridge-ui/src/components/Sidebar/AccountMenuItem.tsx index 96d648990e..07e8cc4f7b 100644 --- a/packages/arb-token-bridge-ui/src/components/Sidebar/AccountMenuItem.tsx +++ b/packages/arb-token-bridge-ui/src/components/Sidebar/AccountMenuItem.tsx @@ -17,7 +17,6 @@ export const AccountMenuItem = () => { accountShort, ensName, ensAvatar, - openTransactionHistory, disconnect, udInfo, chain, @@ -36,12 +35,6 @@ export const AccountMenuItem = () => { /> } > - } - onClick={openTransactionHistory} - isMobile - /> {chain && ( { const posthog = usePostHog() return ( -
+
) diff --git a/packages/arb-token-bridge-ui/src/components/TopNavBar.tsx b/packages/arb-token-bridge-ui/src/components/TopNavBar.tsx new file mode 100644 index 0000000000..9e2105d68d --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/TopNavBar.tsx @@ -0,0 +1,48 @@ +import { Tab } from '@headlessui/react' +import { PaperAirplaneIcon } from '@heroicons/react/24/outline' +import { PropsWithChildren } from 'react' +import { twMerge } from 'tailwind-merge' +import Image from 'next/image' +import { useTransactionReminderInfo } from './TransactionHistory/useTransactionReminderInfo' + +function StyledTab({ children, ...props }: PropsWithChildren) { + return ( + + {children} + + ) +} + +StyledTab.displayName = 'StyledTab' + +export function TopNavBar() { + const { colorClassName } = useTransactionReminderInfo() + + return ( + + + + Bridge + + + history icon + Txn History{' '} + + + + ) +} diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/EmptyTransactionHistory.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/EmptyTransactionHistory.tsx index 6c0d3e1b69..8a93052271 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/EmptyTransactionHistory.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/EmptyTransactionHistory.tsx @@ -52,5 +52,9 @@ export const EmptyTransactionHistory = ({ ) } - return Looks like no transactions here yet. + return ( + + No {tabType} transactions. + + ) } diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx index a51d4e9656..b1166da64a 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistory.tsx @@ -1,24 +1,8 @@ -import dayjs from 'dayjs' -import { useMemo } from 'react' -import { Tab } from '@headlessui/react' import { create } from 'zustand' -import { UseTransactionHistoryResult } from '../../hooks/useTransactionHistory' -import { TransactionHistoryTable } from './TransactionHistoryTable' -import { - isTxClaimable, - isTxCompleted, - isTxExpired, - isTxFailed, - isTxPending -} from './helpers' import { MergedTransaction } from '../../state/app/state' -import { TabButton } from '../common/Tab' -import { TransactionsTableDetails } from './TransactionsTableDetails' -import { Address } from '../../util/AddressUtils' - -const tabClasses = - 'text-white px-3 mr-2 border-b-2 ui-selected:border-white ui-not-selected:border-transparent ui-not-selected:text-white/80 arb-hover' +import { TransactionHistorySearchBar } from './TransactionHistorySearchBar' +import { TransactionHistorySearchResults } from './TransactionHistorySearchResults' type TxDetailsStore = { tx: MergedTransaction | null @@ -46,93 +30,12 @@ export const useTxDetailsStore = create(set => ({ reset: () => set({ tx: null }) })) -export const TransactionHistory = ({ - props -}: { - props: UseTransactionHistoryResult & { address: Address | undefined } -}) => { - const { transactions, address } = props - - const oldestTxTimeAgoString = useMemo(() => { - return dayjs(transactions[transactions.length - 1]?.createdAt).toNow(true) - }, [transactions]) - - const groupedTransactions = useMemo( - () => - transactions.reduce( - (acc, tx) => { - if (isTxCompleted(tx) || isTxExpired(tx)) { - acc.settled.push(tx) - } - if (isTxPending(tx)) { - acc.pending.push(tx) - } - if (isTxClaimable(tx)) { - acc.claimable.push(tx) - } - if (isTxFailed(tx)) { - acc.failed.push(tx) - } - return acc - }, - { - settled: [] as MergedTransaction[], - pending: [] as MergedTransaction[], - claimable: [] as MergedTransaction[], - failed: [] as MergedTransaction[] - } - ), - [transactions] - ) - - const pendingTransactions = [ - ...groupedTransactions.failed, - ...groupedTransactions.pending, - ...groupedTransactions.claimable - ] - - const settledTransactions = groupedTransactions.settled - +export const TransactionHistory = () => { return ( - <> - - - - Pending transactions - - - Settled transactions - - +
+ - - - - - - - - - - - + +
) } diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistorySearchBar.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistorySearchBar.tsx new file mode 100644 index 0000000000..f6486fcc35 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistorySearchBar.tsx @@ -0,0 +1,101 @@ +import { create } from 'zustand' +import { isAddress } from 'ethers/lib/utils.js' +import { Address, useAccount } from 'wagmi' +import { useCallback, useEffect } from 'react' +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline' +import { twMerge } from 'tailwind-merge' + +import { Button } from '../common/Button' + +export enum TransactionHistorySearchError { + INVALID_ADDRESS = 'That doesn’t seem to be a valid address, please try again.' +} + +type TransactionHistoryAddressStore = { + address: string + sanitizedAddress: Address | undefined + searchError: TransactionHistorySearchError | undefined + setAddress: (address: string) => void + setSanitizedAddress: (address: string) => void + setSearchError: (error: TransactionHistorySearchError | undefined) => void +} + +export const useTransactionHistoryAddressStore = + create(set => ({ + address: '', + sanitizedAddress: undefined, + setAddress: (address: string) => set({ address }), + setSanitizedAddress: (address: string) => { + if (isAddress(address)) { + set({ sanitizedAddress: address }) + } + }, + searchError: undefined, + setSearchError: (error: TransactionHistorySearchError | undefined) => + set({ searchError: error }) + })) + +export function TransactionHistorySearchBar() { + const { address, setAddress, setSanitizedAddress, setSearchError } = + useTransactionHistoryAddressStore() + const { address: connectedAddress } = useAccount() + + useEffect(() => { + if (address === '' && connectedAddress) { + setSanitizedAddress(connectedAddress) + setSearchError(undefined) + } + }, [address, connectedAddress, setSanitizedAddress, setSearchError]) + + const searchTxForAddress = useCallback(() => { + if (address === '') { + return + } + + if (!isAddress(address)) { + setSearchError(TransactionHistorySearchError.INVALID_ADDRESS) + return + } + + setSanitizedAddress(address) + setSearchError(undefined) + }, [address, setSanitizedAddress, setSearchError]) + + return ( +
+
event.preventDefault()} + > + + setAddress(event.target.value)} + inputMode="search" + placeholder="Search by address" + aria-label="Transaction history wallet address input" + className="h-full w-full bg-transparent py-1 pl-1 pr-3 text-sm font-light placeholder:text-white/60" + // stop password managers from autofilling + data-1p-ignore + data-lpignore="true" + data-form-type="other" + /> + + +
+ ) +} diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistorySearchResults.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistorySearchResults.tsx new file mode 100644 index 0000000000..012cbe2146 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistorySearchResults.tsx @@ -0,0 +1,146 @@ +import dayjs from 'dayjs' +import { useEffect, useMemo } from 'react' +import { Tab } from '@headlessui/react' + +import { MergedTransaction } from '../../state/app/state' +import { + ContentWrapper, + TransactionHistoryTable +} from './TransactionHistoryTable' +import { TransactionStatusInfo } from '../TransactionHistory/TransactionStatusInfo' +import { + isTxClaimable, + isTxCompleted, + isTxExpired, + isTxFailed, + isTxPending +} from './helpers' +import { TabButton } from '../common/Tab' +import { TransactionsTableDetails } from './TransactionsTableDetails' +import { useTransactionHistory } from '../../hooks/useTransactionHistory' +import { useTransactionHistoryAddressStore } from './TransactionHistorySearchBar' + +function useTransactionHistoryUpdater() { + const { sanitizedAddress } = useTransactionHistoryAddressStore() + + const transactionHistoryProps = useTransactionHistory(sanitizedAddress, { + runFetcher: true + }) + + const { transactions, updatePendingTransaction } = transactionHistoryProps + + const pendingTransactions = useMemo(() => { + return transactions.filter(isTxPending) + }, [transactions]) + + useEffect(() => { + const interval = setInterval(() => { + pendingTransactions.forEach(updatePendingTransaction) + }, 10_000) + + return () => clearInterval(interval) + }, [pendingTransactions, updatePendingTransaction]) + + return transactionHistoryProps +} + +const tabClasses = + 'text-white px-3 mr-2 border-b-2 ui-selected:border-white ui-not-selected:border-transparent ui-not-selected:text-white/80 arb-hover' + +export function TransactionHistorySearchResults() { + const props = useTransactionHistoryUpdater() + const { transactions } = props + const { searchError } = useTransactionHistoryAddressStore() + + const oldestTxTimeAgoString = useMemo(() => { + return dayjs(transactions[transactions.length - 1]?.createdAt).toNow(true) + }, [transactions]) + + const groupedTransactions = useMemo( + () => + transactions.reduce( + (acc, tx) => { + if (isTxCompleted(tx) || isTxExpired(tx)) { + acc.settled.push(tx) + } + if (isTxPending(tx)) { + acc.pending.push(tx) + } + if (isTxClaimable(tx)) { + acc.claimable.push(tx) + } + if (isTxFailed(tx)) { + acc.failed.push(tx) + } + return acc + }, + { + settled: [] as MergedTransaction[], + pending: [] as MergedTransaction[], + claimable: [] as MergedTransaction[], + failed: [] as MergedTransaction[] + } + ), + [transactions] + ) + + const pendingTransactions = [ + ...groupedTransactions.failed, + ...groupedTransactions.pending, + ...groupedTransactions.claimable + ] + + const settledTransactions = groupedTransactions.settled + + if (searchError) { + return ( + +

{searchError}

+
+ ) + } + + return ( + <> +
+ +
+ + + + Pending transactions + + + Settled transactions + + + + + + + + + + + + + + + ) +} diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx index 98d1043210..2c05ec0da8 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionHistoryTable.tsx @@ -4,10 +4,10 @@ import { useEffect, useMemo, useRef, - useState + forwardRef } from 'react' import { twMerge } from 'tailwind-merge' -import { AutoSizer, Column, Table } from 'react-virtualized' +import { Column, Table } from 'react-virtualized' import { ExclamationCircleIcon, PlusCircleIcon @@ -15,11 +15,7 @@ import { import dayjs from 'dayjs' import { getProviderForChainId } from '@/token-bridge-sdk/utils' -import { - getStandardizedDate, - getStandardizedTime, - isTokenDeposit -} from '../../state/app/utils' +import { isTokenDeposit } from '../../state/app/utils' import { ChainPair, UseTransactionHistoryResult @@ -30,7 +26,6 @@ import { isTxPending } from './helpers' import { PendingDepositWarning } from './PendingDepositWarning' import { TransactionsTableRow } from './TransactionsTableRow' import { EmptyTransactionHistory } from './EmptyTransactionHistory' -import { Address } from '../../util/AddressUtils' import { MergedTransaction } from '../../state/app/state' import { useNativeCurrency } from '../../hooks/useNativeCurrency' @@ -50,21 +45,25 @@ export const BatchTransferNativeTokenTooltip = ({ ) } -export const ContentWrapper = ({ - children, - className = '' -}: PropsWithChildren<{ className?: string }>) => { +export const ContentWrapper = forwardRef< + HTMLDivElement, + PropsWithChildren<{ className?: string }> +>(({ children, className = '', ...props }, ref) => { return (
{children}
) -} +}) + +ContentWrapper.displayName = 'ContentWrapper' const TableHeader = ({ children, @@ -131,7 +130,6 @@ const FailedChainPairsTooltip = ({ } type TransactionHistoryTableProps = UseTransactionHistoryResult & { - address: Address | undefined selectedTabIndex: number oldestTxTimeAgoString: string } @@ -141,7 +139,6 @@ export const TransactionHistoryTable = ( ) => { const { transactions, - address, loading, completed, error, @@ -151,14 +148,29 @@ export const TransactionHistoryTable = ( oldestTxTimeAgoString } = props - const contentAboveTable = useRef(null) - + const TABLE_HEADER_HEIGHT = 52 + const TABLE_ROW_HEIGHT = 60 const isTxHistoryEmpty = transactions.length === 0 const isPendingTab = selectedTabIndex === 0 const paused = !loading && !completed - const [tableHeight, setTableHeight] = useState(0) + const contentWrapperRef = useRef(null) + const tableRef = useRef(null) + + const tableHeight = useMemo(() => { + if (window.innerWidth < 768) { + return TABLE_ROW_HEIGHT * (transactions.length + 1) + TABLE_HEADER_HEIGHT + } + const SIDE_PANEL_HEADER_HEIGHT = 125 + const viewportHeight = window.innerHeight + const contentWrapperOffsetTop = contentWrapperRef.current?.offsetTop ?? 0 + return Math.max( + // we subtract a little padding at the end so that the table doesn't end at the edge of the screen + viewportHeight - contentWrapperOffsetTop - SIDE_PANEL_HEADER_HEIGHT, + 0 + ) + }, [contentWrapperRef.current?.offsetTop, transactions.length]) const pendingTokenDepositsCount = useMemo(() => { return transactions.filter(tx => isTokenDeposit(tx) && isTxPending(tx)) @@ -169,36 +181,15 @@ export const TransactionHistoryTable = ( return transactions.filter(isTxPending)[0]?.txId }, [transactions]) - // TODO: look into https://www.npmjs.com/package/react-intersection-observer that could simplify this + // recalculate table height when tx number changes, or when user selects different tab useEffect(() => { - // Calculate table height to be passed to the React Virtualized Table - const currentRef = contentAboveTable.current - const SIDE_PANEL_HEADER_HEIGHT = 125 - - // Adjust the table size whenever the content above it is resized - const observer = new ResizeObserver(entries => { - if (entries[0]) { - const aboveHeight = entries[0].contentRect.height - const viewportHeight = window.innerHeight - const newTableHeight = Math.max( - // we subtract a little padding at the end so that the table doesn't end at the edge of the screen - viewportHeight - aboveHeight - SIDE_PANEL_HEADER_HEIGHT - 20, - 0 - ) - setTableHeight(newTableHeight) - } - }) - - if (currentRef) { - observer.observe(currentRef) - } - - return () => { - if (currentRef) { - observer.unobserve(currentRef) - } - } - }, [transactions.length]) + tableRef.current?.recomputeRowHeights() + }, [ + transactions.length, + selectedTabIndex, + isTxHistoryEmpty, + contentWrapperRef.current?.offsetTop + ]) if (isTxHistoryEmpty) { return ( @@ -213,13 +204,15 @@ export const TransactionHistoryTable = ( } return ( - +
{loading ? (
@@ -227,7 +220,7 @@ export const TransactionHistoryTable = (
) : ( -
+
@@ -243,85 +236,81 @@ export const TransactionHistoryTable = (
{pendingTokenDepositsCount > 0 && }
- - {() => ( -
( -
- {props.columns} -
- )} - className="table-auto last:border-b-0" - rowGetter={({ index }) => transactions[index]} - rowRenderer={({ index, style }) => { - const tx = transactions[index] +
( +
+ {props.columns} +
+ )} + className="table-auto last:border-b-0" + rowGetter={({ index }) => transactions[index]} + rowRenderer={({ index, style }) => { + const tx = transactions[index] - if (!tx) { - return null - } + if (!tx) { + return null + } - const isLastRow = index + 1 === transactions.length - const key = `${tx.parentChainId}-${tx.childChainId}-${tx.txId}` - const secondsPassed = dayjs().diff(dayjs(tx.createdAt), 'second') + const isLastRow = index + 1 === transactions.length + const key = `${tx.parentChainId}-${tx.childChainId}-${tx.txId}` + const secondsPassed = dayjs().diff(dayjs(tx.createdAt), 'second') - // only blink the topmost tx, in case many txs are queued in a short amount of time - const isTopmostPendingTx = - topmostPendingTxId && topmostPendingTxId === tx.txId + // only blink the topmost tx, in case many txs are queued in a short amount of time + const isTopmostPendingTx = + topmostPendingTxId && topmostPendingTxId === tx.txId - return ( -
- -
- ) - }} - > - TIME} - /> - TOKEN} - /> - FROM} - /> - TO} - /> - STATUS} - /> -
- )} - + return ( +
+ +
+ ) + }} + > + TIME} + /> + TOKEN} + /> + FROM} + /> + TO} + /> + STATUS} + /> + ) } diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionStatusInfo.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionStatusInfo.tsx index 88ab67d208..fb4ee1f9d7 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionStatusInfo.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionStatusInfo.tsx @@ -3,21 +3,12 @@ Format: "You have [X] deposits to retry and [Y] withdrawals ready to claim. [CTA]" */ -import { useAccount } from 'wagmi' -import { useMemo } from 'react' -import { - DocumentTextIcon, - ExclamationTriangleIcon -} from '@heroicons/react/24/outline' +import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' import { twMerge } from 'tailwind-merge' import Image from 'next/image' import ArrowsIcon from '@/images/arrows.svg' -import { isDepositReadyToRedeem } from '../../state/app/utils' -import { useAppContextActions } from '../App/AppContext' -import { useTransactionHistory } from '../../hooks/useTransactionHistory' -import { Button } from '../common/Button' -import { isTxClaimable, isTxPending } from './helpers' +import { useTransactionReminderInfo } from './useTransactionReminderInfo' const Content = ({ numClaimableTransactions, @@ -87,79 +78,37 @@ const Content = ({ ) } - return ( -
- - See transaction history -
- ) + return null } export const TransactionStatusInfo = () => { - const { address } = useAccount() - const { openTransactionHistoryPanel } = useAppContextActions() - const { transactions } = useTransactionHistory(address) - const { numClaimableTransactions, numRetryablesToRedeem, - numPendingTransactions - } = useMemo(() => { - return transactions.reduce( - (acc, tx) => { - // standard bridge withdrawal - if (isTxClaimable(tx)) { - acc.numClaimableTransactions += 1 - } - // failed retryable - if (isDepositReadyToRedeem(tx)) { - acc.numRetryablesToRedeem += 1 - } - // all pending - if (isTxPending(tx)) { - acc.numPendingTransactions += 1 - } - return acc - }, - { - numClaimableTransactions: 0, - numRetryablesToRedeem: 0, - numPendingTransactions: 0 - } - ) - }, [transactions]) + numPendingTransactions, + colorClassName + } = useTransactionReminderInfo() - const buttonClassName = useMemo(() => { - if (numRetryablesToRedeem > 0) { - return 'bg-red-700' - } - if (numClaimableTransactions > 0) { - return 'bg-lime-dark' - } - if (numPendingTransactions > 0) { - return 'bg-cyan-dark' - } - return 'bg-gray-1 text-white/70' - }, [numClaimableTransactions, numPendingTransactions, numRetryablesToRedeem]) + if ( + numClaimableTransactions === 0 && + numRetryablesToRedeem === 0 && + numPendingTransactions === 0 + ) { + return null + } return ( - +
) } diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx index a4122e6275..e3802a095b 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetails.tsx @@ -22,12 +22,12 @@ import { GET_HELP_LINK, ether } from '../../constants' import { useTransactionHistory } from '../../hooks/useTransactionHistory' import { shortenAddress } from '../../util/CommonUtils' import { isTxCompleted } from './helpers' -import { Address } from '../../util/AddressUtils' import { sanitizeTokenSymbol } from '../../util/TokenUtils' import { isBatchTransfer } from '../../util/TokenDepositUtils' import { BatchTransferNativeTokenTooltip } from './TransactionHistoryTable' import { useNativeCurrency } from '../../hooks/useNativeCurrency' import { isCustomDestinationAddressTx } from '../../state/app/utils' +import { useTransactionHistoryAddressStore } from './TransactionHistorySearchBar' const DetailsBox = ({ children, @@ -43,14 +43,11 @@ const DetailsBox = ({ ) } -export const TransactionsTableDetails = ({ - address -}: { - address: Address | undefined -}) => { +export const TransactionsTableDetails = () => { + const { sanitizedAddress } = useTransactionHistoryAddressStore() const { tx: txFromStore, isOpen, close, reset } = useTxDetailsStore() const { ethToUSD } = useETHPrice() - const { transactions } = useTransactionHistory(address) + const { transactions } = useTransactionHistory(sanitizedAddress) const tx = useMemo(() => { if (!txFromStore) { @@ -69,7 +66,7 @@ export const TransactionsTableDetails = ({ const childProvider = getProviderForChainId(tx?.childChainId ?? 0) const nativeCurrency = useNativeCurrency({ provider: childProvider }) - if (!tx || !address || !nativeCurrency) { + if (!tx || !sanitizedAddress || !nativeCurrency) { return null } @@ -82,9 +79,9 @@ export const TransactionsTableDetails = ({ !isNetwork(tx.parentChainId).isTestnet && tx.asset === ether.symbol const isDifferentSourceAddress = - address.toLowerCase() !== tx.sender?.toLowerCase() + sanitizedAddress.toLowerCase() !== tx.sender?.toLowerCase() const isDifferentDestinationAddress = isCustomDestinationAddressTx({ - sender: address, + sender: sanitizedAddress, destination: tx.destination }) @@ -269,7 +266,7 @@ export const TransactionsTableDetails = ({ )} - + {!isTxCompleted(tx) && ( diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsSteps.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsSteps.tsx index a349b939ff..7eff130e15 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsSteps.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsSteps.tsx @@ -21,7 +21,7 @@ import { ExternalLink } from '../common/ExternalLink' import { TransferCountdown } from '../common/TransferCountdown' import { isDepositReadyToRedeem } from '../../state/app/utils' import { Address } from '../../util/AddressUtils' -import { isTeleportTx } from '../../hooks/useTransactions' +import { isTeleportTx } from '../../types/Transactions' import { firstRetryableLegRequiresRedeem, secondRetryableLegForTeleportRequiresRedeem @@ -31,6 +31,7 @@ import { minutesToHumanReadableTime, useTransferDuration } from '../../hooks/useTransferDuration' +import { useTransactionHistoryAddressStore } from './TransactionHistorySearchBar' function needsToClaimTransfer(tx: MergedTransaction) { return tx.isCctp || tx.isWithdrawal @@ -136,14 +137,7 @@ const LastStepEndItem = ({ (!isTeleport && isDepositReadyToRedeem(tx)) || (isTeleport && secondRetryableLegForTeleportRequiresRedeem(tx)) ) { - return ( - - ) + return } return null @@ -162,13 +156,12 @@ export const TransactionFailedOnNetwork = ({ ) export const TransactionsTableDetailsSteps = ({ - tx, - address + tx }: { tx: MergedTransaction - address: Address | undefined }) => { const { approximateDurationInMinutes } = useTransferDuration(tx) + const { sanitizedAddress } = useTransactionHistoryAddressStore() const { sourceChainId } = tx @@ -240,9 +233,7 @@ export const TransactionsTableDetailsSteps = ({ /> )} - {isTeleportTx(tx) && ( - - )} + {isTeleportTx(tx) && } {/* If claiming is required we show this step */} {needsToClaimTransfer(tx) && ( @@ -256,7 +247,6 @@ export const TransactionsTableDetailsSteps = ({ type={tx.isWithdrawal ? 'withdrawals' : 'deposits'} isError={false} tx={tx} - address={address} /> ) } @@ -268,7 +258,7 @@ export const TransactionsTableDetailsSteps = ({ done={isTxCompleted(tx)} failure={isTxExpired(tx) || isDestinationChainFailure} text={destinationChainTxText} - endItem={} + endItem={} />
) diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsTeleporterSteps.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsTeleporterSteps.tsx index 0540259aa3..a9a573b189 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsTeleporterSteps.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableDetailsTeleporterSteps.tsx @@ -1,5 +1,4 @@ import { useMemo } from 'react' -import { Address } from 'wagmi' import { twMerge } from 'tailwind-merge' import { ArrowTopRightOnSquareIcon, @@ -72,11 +71,9 @@ const TeleportMiddleStepFailureExplanationNote = ({ } export const TransactionsTableDetailsTeleporterSteps = ({ - tx, - address + tx }: { tx: TeleporterMergedTransaction - address: Address | undefined }) => { const l2TxID = tx.parentToChildMsgData?.childTxId const isFirstRetryableLegSucceeded = @@ -96,15 +93,8 @@ export const TransactionsTableDetailsTeleporterSteps = ({ typeof tx.l2ToL3MsgData?.l3TxID !== 'undefined' const firstRetryableRedeemButton = useMemo( - () => ( - - ), - [tx, address] + () => , + [tx] ) const firstTransactionExternalLink = useMemo( diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx index 515ba807c6..01f887a666 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRow.tsx @@ -121,11 +121,9 @@ const StatusLabel = ({ tx }: { tx: MergedTransaction }) => { export function TransactionsTableRow({ tx, - address, className = '' }: { tx: MergedTransaction - address: Address | undefined className?: string }) { const { open: openTxDetails } = useTxDetailsStore() @@ -189,7 +187,7 @@ export function TransactionsTableRow({
@@ -259,7 +257,6 @@ export function TransactionsTableRow({ tx={tx} isError={isError} type={tx.isWithdrawal ? 'withdrawals' : 'deposits'} - address={address} />
diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx index af4fe167ef..1b6bce2c75 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/TransactionsTableRowAction.tsx @@ -1,4 +1,6 @@ import { useCallback } from 'react' +import { useAccount, useNetwork } from 'wagmi' + import { GET_HELP_LINK } from '../../constants' import { useClaimWithdrawal } from '../../hooks/useClaimWithdrawal' import { useClaimCctp } from '../../state/cctpState' @@ -13,31 +15,37 @@ import { getNetworkName } from '../../util/networks' import { errorToast } from '../common/atoms/Toast' import { Button } from '../common/Button' import { useSwitchNetworkWithConfig } from '../../hooks/useSwitchNetworkWithConfig' -import { useNetwork } from 'wagmi' import { isDepositReadyToRedeem } from '../../state/app/utils' import { useRedeemRetryable } from '../../hooks/useRedeemRetryable' import { TransferCountdown } from '../common/TransferCountdown' -import { Address } from '../../util/AddressUtils' import { getChainIdForRedeemingRetryable } from '../../util/RetryableUtils' -import { isTeleportTx } from '../../hooks/useTransactions' +import { isTeleportTx } from '../../types/Transactions' import { useRedeemTeleporter } from '../../hooks/useRedeemTeleporter' import { sanitizeTokenSymbol } from '../../util/TokenUtils' import { formatAmount } from '../../util/NumberUtils' +import { useTransactionHistoryAddressStore } from './TransactionHistorySearchBar' +import { Tooltip } from '../common/Tooltip' export function TransactionsTableRowAction({ tx, isError, - type, - address + type }: { tx: MergedTransaction | TeleporterMergedTransaction isError: boolean type: 'deposits' | 'withdrawals' - address: Address | undefined }) { + const { address: connectedAddress } = useAccount() const { chain } = useNetwork() const { switchNetworkAsync } = useSwitchNetworkWithConfig() const networkName = getNetworkName(chain?.id ?? 0) + const { sanitizedAddress: searchedAddress } = + useTransactionHistoryAddressStore() + + const isViewingAnotherAddress = + connectedAddress && + searchedAddress && + connectedAddress.toLowerCase() !== searchedAddress.toLowerCase() const tokenSymbol = sanitizeTokenSymbol(tx.asset, { erc20L1Address: tx.tokenAddress, @@ -48,10 +56,10 @@ export function TransactionsTableRowAction({ const { claim: claimCctp, isClaiming: isClaimingCctp } = useClaimCctp(tx) const { redeem, isRedeeming: isRetryableRedeeming } = useRedeemRetryable( tx, - address + searchedAddress ) const { redeem: teleporterRedeem, isRedeeming: isTeleporterRedeeming } = - useRedeemTeleporter(tx, address) + useRedeemTeleporter(tx, searchedAddress) const isRedeeming = isRetryableRedeeming || isTeleporterRedeeming @@ -163,16 +171,25 @@ export function TransactionsTableRowAction({ return isClaiming || isClaimingCctp ? ( Claiming... ) : ( - + + ) } diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts b/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts index bc44d3cec3..ce795b4526 100644 --- a/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/helpers.ts @@ -15,7 +15,8 @@ import { TeleporterMergedTransaction, WithdrawalStatus } from '../../state/app/state' -import { ChainId, getL1BlockTime, isNetwork } from '../../util/networks' +import { getL1BlockTime, isNetwork } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { Deposit, Transfer } from '../../hooks/useTransactionHistory' import { getParentToChildMessageDataFromParentTxHash, @@ -31,20 +32,12 @@ import { getAttestationHashAndMessageFromReceipt } from '../../util/cctp/getAtte import { getOutgoingMessageState } from '../../util/withdrawals/helpers' import { getUniqueIdOrHashFromEvent } from '../../hooks/useArbTokenBridge' import { getProviderForChainId } from '../../token-bridge-sdk/utils' -import { isTeleportTx } from '../../hooks/useTransactions' +import { isTeleportTx } from '../../types/Transactions' const PARENT_CHAIN_TX_DETAILS_OF_CLAIM_TX = 'arbitrum:bridge:claim:parent:tx:details' const DEPOSITS_LOCAL_STORAGE_KEY = 'arbitrum:bridge:deposits' -export enum StatusLabel { - PENDING = 'Pending', - CLAIMABLE = 'Claimable', - SUCCESS = 'Success', - EXPIRED = 'Expired', - FAILURE = 'Failure' -} - function isDeposit(tx: MergedTransaction): boolean { return !tx.isWithdrawal } diff --git a/packages/arb-token-bridge-ui/src/components/TransactionHistory/useTransactionReminderInfo.ts b/packages/arb-token-bridge-ui/src/components/TransactionHistory/useTransactionReminderInfo.ts new file mode 100644 index 0000000000..7efe41e359 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/TransactionHistory/useTransactionReminderInfo.ts @@ -0,0 +1,60 @@ +import { useMemo } from 'react' +import { useAccount } from 'wagmi' + +import { useTransactionHistory } from '../../hooks/useTransactionHistory' +import { isTxClaimable, isTxPending } from './helpers' +import { isDepositReadyToRedeem } from '../../state/app/utils' + +export function useTransactionReminderInfo() { + const { address } = useAccount() + const { transactions } = useTransactionHistory(address) + + const { + numClaimableTransactions, + numRetryablesToRedeem, + numPendingTransactions + } = useMemo(() => { + return transactions.reduce( + (acc, tx) => { + // standard bridge withdrawal + if (isTxClaimable(tx)) { + acc.numClaimableTransactions += 1 + } + // failed retryable + if (isDepositReadyToRedeem(tx)) { + acc.numRetryablesToRedeem += 1 + } + // all pending + if (isTxPending(tx)) { + acc.numPendingTransactions += 1 + } + return acc + }, + { + numClaimableTransactions: 0, + numRetryablesToRedeem: 0, + numPendingTransactions: 0 + } + ) + }, [transactions]) + + const colorClassName = useMemo(() => { + if (numRetryablesToRedeem > 0) { + return { dark: 'bg-red-700', light: 'bg-retry' } + } + if (numClaimableTransactions > 0) { + return { dark: 'bg-lime-dark', light: 'bg-claim' } + } + if (numPendingTransactions > 0) { + return { dark: 'bg-cyan-dark', light: 'bg-pending' } + } + return { dark: 'bg-gray-1 text-white/70', light: '' } + }, [numClaimableTransactions, numPendingTransactions, numRetryablesToRedeem]) + + return { + numClaimableTransactions, + numRetryablesToRedeem, + numPendingTransactions, + colorClassName + } +} diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/EstimatedGas.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/EstimatedGas.tsx index be0675afcc..0b1de4f338 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/EstimatedGas.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/EstimatedGas.tsx @@ -3,7 +3,8 @@ import { useMemo } from 'react' import { twMerge } from 'tailwind-merge' import { useAppState } from '../../state' -import { ChainId, getNetworkName, isNetwork } from '../../util/networks' +import { getNetworkName, isNetwork } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { Tooltip } from '../common/Tooltip' import { formatAmount } from '../../util/NumberUtils' import { useNativeCurrency } from '../../hooks/useNativeCurrency' diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/OneNovaTransferDialog.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/OneNovaTransferDialog.tsx index 44a8c540d5..c48987fa35 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/OneNovaTransferDialog.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/OneNovaTransferDialog.tsx @@ -7,7 +7,8 @@ import { BridgesTable } from '../common/BridgesTable' import { SecurityNotGuaranteed } from './SecurityLabels' import { Dialog, UseDialogProps } from '../common/Dialog' import { FastBridgeInfo, FastBridgeNames } from '../../util/fastBridges' -import { ChainId, getNetworkName, isNetwork } from '../../util/networks' +import { getNetworkName, isNetwork } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { ether } from '../../constants' import { useArbQueryParams } from '../../hooks/useArbQueryParams' import { useNetworks } from '../../hooks/useNetworks' diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx index 65fcb9dafe..6d95a50d2a 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenRow.tsx @@ -34,9 +34,9 @@ import { TokenLogoFallback } from './TokenInfo' import { useBalanceOnSourceChain } from '../../hooks/useBalanceOnSourceChain' import { useSourceChainNativeCurrencyDecimals } from '../../hooks/useSourceChainNativeCurrencyDecimals' -function tokenListIdsToNames(ids: number[]): string { +function tokenListIdsToNames(ids: string[]): string { return ids - .map((tokenListId: number) => listIdsToNames[tokenListId]) + .map((tokenListId: string) => listIdsToNames[tokenListId]) .join(', ') } @@ -90,7 +90,7 @@ function TokenListInfo({ token }: { token: ERC20BridgeToken | null }) { return 'Native USDC on Arbitrum Sepolia' } - const listIds: Set = token.listIds + const listIds: Set = token.listIds const listIdsSize = listIds.size if (listIdsSize === 0) { return 'Added by User' diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx index 93af1c6977..8d90a95caa 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx @@ -47,7 +47,7 @@ import { useSetInputAmount } from '../../hooks/TransferPanel/useSetInputAmount' export const ARB_ONE_NATIVE_USDC_TOKEN = { ...ArbOneNativeUSDC, - listIds: new Set(), + listIds: new Set(), type: TokenType.ERC20, // the address field is for L1 address but native USDC does not have an L1 address // the L2 address is used instead to avoid errors @@ -57,7 +57,7 @@ export const ARB_ONE_NATIVE_USDC_TOKEN = { export const ARB_SEPOLIA_NATIVE_USDC_TOKEN = { ...ArbOneNativeUSDC, - listIds: new Set(), + listIds: new Set(), type: TokenType.ERC20, address: CommonAddress.ArbitrumSepolia.USDC, l2Address: CommonAddress.ArbitrumSepolia.USDC diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferDisabledDialog.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferDisabledDialog.tsx index 12b9dcfdd0..8f5d993e54 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferDisabledDialog.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferDisabledDialog.tsx @@ -6,7 +6,8 @@ import { Dialog } from '../common/Dialog' import { sanitizeTokenSymbol } from '../../util/TokenUtils' import { useNetworks } from '../../hooks/useNetworks' import { ExternalLink } from '../common/ExternalLink' -import { ChainId, getNetworkName } from '../../util/networks' +import { getNetworkName } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { getL2ConfigForTeleport } from '../../token-bridge-sdk/teleport' import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' import { withdrawOnlyTokens } from '../../util/WithdrawOnlyUtils' diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index b3b08daae4..5710d4ba57 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -75,6 +75,7 @@ import { MoveFundsButton } from './MoveFundsButton' import { ProjectsListing } from '../common/ProjectsListing' import { useAmountBigNumber } from './hooks/useAmountBigNumber' import { useSourceChainNativeCurrencyDecimals } from '../../hooks/useSourceChainNativeCurrencyDecimals' +import { useMainContentTabs } from '../MainContent/MainContent' const signerUndefinedError = 'Signer is undefined' const transferNotAllowedError = 'Transfer not allowed' @@ -140,8 +141,8 @@ export function TransferPanel() { chainId: networks.sourceChain.id }) - const { openTransactionHistoryPanel, setTransferring } = - useAppContextActions() + const { setTransferring } = useAppContextActions() + const { switchToTransactionHistoryTab } = useMainContentTabs() const { addPendingTransaction } = useTransactionHistory(walletAddress) const isCctpTransfer = useIsCctpTransfer() @@ -497,7 +498,7 @@ export function TransferPanel() { } addPendingTransaction(newTransfer) - openTransactionHistoryPanel() + switchToTransactionHistoryTab() setTransferring(false) clearAmountInput() } catch (e) { @@ -836,7 +837,7 @@ export function TransferPanel() { ) } - openTransactionHistoryPanel() + switchToTransactionHistoryTab() setTransferring(false) clearAmountInput() diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/hooks.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/hooks.ts index 4d748b11ca..9cb4413a42 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/hooks.ts +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/hooks.ts @@ -16,7 +16,7 @@ const commonUSDC = { type: TokenType.ERC20, symbol: 'USDC', decimals: 6, - listIds: new Set() + listIds: new Set() } export function useUpdateUSDCTokenData() { diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/utils.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/utils.ts index 930bab1453..8f6ee3a2ba 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/utils.ts +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/utils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '../../../util/networks' +import { ChainId } from '../../../types/ChainId' export enum NetworkType { parentChain = 'parentChain', diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx index 53015bc306..3761043fd8 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelSummary.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react' import { InformationCircleIcon } from '@heroicons/react/24/outline' import { twMerge } from 'tailwind-merge' +import Image from 'next/image' import { formatAmount } from '../../util/NumberUtils' import { getNetworkName, isNetwork } from '../../util/networks' @@ -20,6 +21,7 @@ import { NoteBox } from '../common/NoteBox' import { DISABLED_CHAIN_IDS } from './useTransferReadiness' import { useIsBatchTransferSupported } from '../../hooks/TransferPanel/useIsBatchTransferSupported' import { getConfirmationTime } from '../../util/WithdrawalUtils' +import LightningIcon from '@/images/LightningIcon.svg' export type TransferPanelSummaryToken = { symbol: string @@ -284,7 +286,7 @@ function ConfirmationTimeInfo({ chainId }: { chainId: number }) { return ( <> Confirmation time: - + {confirmationTimeInReadableFormat} @@ -292,13 +294,19 @@ function ConfirmationTimeInfo({ chainId }: { chainId: number }) { {confirmationTimeInReadableFormatShort} {fastWithdrawalActive && ( - - - +
+ + + +
+ Lightning Icon + FAST +
+
)}
diff --git a/packages/arb-token-bridge-ui/src/components/common/AddCustomChain.tsx b/packages/arb-token-bridge-ui/src/components/common/AddCustomChain.tsx index 76a346f12e..9be3138a8d 100644 --- a/packages/arb-token-bridge-ui/src/components/common/AddCustomChain.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/AddCustomChain.tsx @@ -11,7 +11,6 @@ import SyntaxHighlighter from 'react-syntax-highlighter' import { stackoverflowDark } from 'react-syntax-highlighter/dist/esm/styles/hljs' import { - ChainId, ChainWithRpcUrl, getCustomChainsFromLocalStorage, getCustomChainFromLocalStorageById, @@ -22,6 +21,7 @@ import { rpcURLs, isNetwork } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { Loader } from './atoms/Loader' import { Erc20Data, fetchErc20Data } from '../../util/TokenUtils' import { getProviderForChainId } from '@/token-bridge-sdk/utils' diff --git a/packages/arb-token-bridge-ui/src/components/common/HeaderAccountPopover.tsx b/packages/arb-token-bridge-ui/src/components/common/HeaderAccountPopover.tsx index 14a265f973..11e9ba28ee 100644 --- a/packages/arb-token-bridge-ui/src/components/common/HeaderAccountPopover.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/HeaderAccountPopover.tsx @@ -4,8 +4,7 @@ import { ArrowTopRightOnSquareIcon, ChevronDownIcon, Cog6ToothIcon, - DocumentDuplicateIcon, - DocumentTextIcon + DocumentDuplicateIcon } from '@heroicons/react/24/outline' import { useState } from 'react' import { useCopyToClipboard, useMedia } from 'react-use' @@ -31,7 +30,6 @@ export function HeaderAccountPopover({ accountShort, ensName, ensAvatar, - openTransactionHistory, disconnect, udInfo, chain, @@ -130,17 +128,6 @@ export function HeaderAccountPopover({
- {/* Transactions button */} - {isCorrectNetworkConnected && ( - - )} - {/* Explorer button */} {isCorrectNetworkConnected && chain && ( ( - callback: () => T, - delay: number -): { forceTrigger: typeof callback } => { - const savedCallback = useRef(callback) - const savedTimer = useRef(undefined) - - // Remember the latest callback if it changes. - useEffect(() => { - savedCallback.current = callback - }, [callback]) - - // Set up the interval. - useEffect(() => { - savedTimer.current = setInterval(() => savedCallback.current(), delay) - return () => clearInterval(savedTimer.current!) - }, [delay]) - - const forceTrigger = () => { - clearInterval(savedTimer.current!) - // make call then setup the timer again - const res = savedCallback.current() - savedTimer.current = setInterval(() => savedCallback.current(), delay) - return res - } - - return { forceTrigger } -} diff --git a/packages/arb-token-bridge-ui/src/components/common/Layout.tsx b/packages/arb-token-bridge-ui/src/components/common/Layout.tsx index 119e218f30..b88df815e4 100644 --- a/packages/arb-token-bridge-ui/src/components/common/Layout.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/Layout.tsx @@ -52,7 +52,7 @@ export function Layout(props: LayoutProps) { aria-hidden />
-
+
diff --git a/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx b/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx index dc328d6034..aab6e65c24 100644 --- a/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/NetworkSelectionContainer.tsx @@ -15,7 +15,8 @@ import { import { twMerge } from 'tailwind-merge' import { AutoSizer, List, ListRowProps } from 'react-virtualized' -import { ChainId, isNetwork, getNetworkName } from '../../util/networks' +import { isNetwork, getNetworkName } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { useIsTestnetMode } from '../../hooks/useIsTestnetMode' import { SearchPanel } from './SearchPanel/SearchPanel' import { SearchPanelTable } from './SearchPanel/SearchPanelTable' diff --git a/packages/arb-token-bridge-ui/src/components/common/TransferCountdown.tsx b/packages/arb-token-bridge-ui/src/components/common/TransferCountdown.tsx index 5806342bc8..d19c8c2e5b 100644 --- a/packages/arb-token-bridge-ui/src/components/common/TransferCountdown.tsx +++ b/packages/arb-token-bridge-ui/src/components/common/TransferCountdown.tsx @@ -5,7 +5,7 @@ import { useTransferDuration } from '../../hooks/useTransferDuration' import { isNetwork } from '../../util/networks' -import { isTeleportTx } from '../../hooks/useTransactions' +import { isTeleportTx } from '../../types/Transactions' /** * Displays a transfer countdown for a deposit, withdrawal, or cctp. diff --git a/packages/arb-token-bridge-ui/src/components/syncers/BalanceUpdater.tsx b/packages/arb-token-bridge-ui/src/components/syncers/BalanceUpdater.tsx deleted file mode 100644 index 6a35e1cea2..0000000000 --- a/packages/arb-token-bridge-ui/src/components/syncers/BalanceUpdater.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useEffect } from 'react' -import { useLatest } from 'react-use' -import { useAccount } from 'wagmi' - -import { useAppState } from '../../state' -import { useUpdateUSDCBalances } from '../../hooks/CCTP/useUpdateUSDCBalances' - -// Updates all balances periodically -const BalanceUpdater = (): JSX.Element => { - const { - app: { arbTokenBridge, selectedToken } - } = useAppState() - const { address: walletAddress } = useAccount() - const latestTokenBridge = useLatest(arbTokenBridge) - - const { updateUSDCBalances } = useUpdateUSDCBalances({ - walletAddress - }) - - useEffect(() => { - const interval = setInterval(() => { - updateUSDCBalances() - - if (selectedToken) { - latestTokenBridge?.current?.token?.updateTokenData( - selectedToken.address - ) - } - }, 10000) - - return () => clearInterval(interval) - }, [selectedToken]) - - return <> -} - -export { BalanceUpdater } diff --git a/packages/arb-token-bridge-ui/src/components/syncers/useBalanceUpdater.tsx b/packages/arb-token-bridge-ui/src/components/syncers/useBalanceUpdater.tsx new file mode 100644 index 0000000000..728322ced2 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/components/syncers/useBalanceUpdater.tsx @@ -0,0 +1,26 @@ +import { useInterval, useLatest } from 'react-use' +import { useAccount } from 'wagmi' + +import { useAppState } from '../../state' +import { useUpdateUSDCBalances } from '../../hooks/CCTP/useUpdateUSDCBalances' + +// Updates all balances periodically +export function useBalanceUpdater() { + const { + app: { arbTokenBridge, selectedToken } + } = useAppState() + const { address: walletAddress } = useAccount() + const latestTokenBridge = useLatest(arbTokenBridge) + + const { updateUSDCBalances } = useUpdateUSDCBalances({ + walletAddress + }) + + useInterval(() => { + updateUSDCBalances() + + if (selectedToken) { + latestTokenBridge?.current?.token?.updateTokenData(selectedToken.address) + } + }, 10000) +} diff --git a/packages/arb-token-bridge-ui/src/constants.ts b/packages/arb-token-bridge-ui/src/constants.ts index 85b03a5336..1e2e7b9ab4 100644 --- a/packages/arb-token-bridge-ui/src/constants.ts +++ b/packages/arb-token-bridge-ui/src/constants.ts @@ -12,8 +12,6 @@ export const DOCS_DOMAIN = 'https://docs.arbitrum.io' export const USDC_LEARN_MORE_LINK = `${DOCS_DOMAIN}/bridge-tokens/concepts/usdc-concept` -export const FAST_BRIDGE_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/18213771832987` - export const TOKEN_APPROVAL_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/18213893952923` export const ETH_BALANCE_ARTICLE_LINK = `${SUPPORT_LINK_BASE}/hc/en-us/articles/18213854684699` diff --git a/packages/arb-token-bridge-ui/src/generateOpenGraphImages.tsx b/packages/arb-token-bridge-ui/src/generateOpenGraphImages.tsx index 6df1761284..01ff07ff39 100644 --- a/packages/arb-token-bridge-ui/src/generateOpenGraphImages.tsx +++ b/packages/arb-token-bridge-ui/src/generateOpenGraphImages.tsx @@ -2,7 +2,8 @@ import React from 'react' import satori, { Font } from 'satori' import sharp from 'sharp' import fs from 'fs' -import { ChainId, isNetwork } from './util/networks' +import { isNetwork } from './util/networks' +import { ChainId } from './types/ChainId' import { getBridgeUiConfigForChain } from './util/bridgeUiConfig' import { orbitMainnets, orbitTestnets } from './util/orbitChainsList' diff --git a/packages/arb-token-bridge-ui/src/hooks/CCTP/useCCTPIsBlocked.ts b/packages/arb-token-bridge-ui/src/hooks/CCTP/useCCTPIsBlocked.ts index d4c8c0bc98..c8ec3a1701 100644 --- a/packages/arb-token-bridge-ui/src/hooks/CCTP/useCCTPIsBlocked.ts +++ b/packages/arb-token-bridge-ui/src/hooks/CCTP/useCCTPIsBlocked.ts @@ -1,5 +1,5 @@ import useSWRImmutable from 'swr/immutable' -import { ChainId } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { getCctpUtils } from '@/token-bridge-sdk/cctp' export function useCCTPIsBlocked() { diff --git a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useChainIdsForNetworkSelection.ts b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useChainIdsForNetworkSelection.ts index 6d139ed254..68b776da8f 100644 --- a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useChainIdsForNetworkSelection.ts +++ b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useChainIdsForNetworkSelection.ts @@ -1,8 +1,8 @@ import { - ChainId, getDestinationChainIds, getSupportedChainIds } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { useIsTestnetMode } from '../useIsTestnetMode' import { useNetworks } from '../useNetworks' diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts index e31ac3dd95..924645d6de 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useArbQueryParams.test.ts @@ -2,7 +2,8 @@ * @jest-environment jsdom */ import { registerCustomArbitrumNetwork } from '@arbitrum/sdk' -import { ChainId, customChainLocalStorageKey } from '../../util/networks' +import { customChainLocalStorageKey } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { AmountQueryParam, ChainParam } from '../useArbQueryParams' import { createMockOrbitChain } from './helpers' diff --git a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts index ec03fd1f9c..b7b2de2db3 100644 --- a/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts +++ b/packages/arb-token-bridge-ui/src/hooks/__tests__/useNetworks.test.ts @@ -2,7 +2,8 @@ * @jest-environment jsdom */ import { registerCustomArbitrumNetwork } from '@arbitrum/sdk' -import { ChainId, customChainLocalStorageKey } from '../../util/networks' +import { customChainLocalStorageKey } from '../../util/networks' +import { ChainId } from '../../types/ChainId' import { sanitizeQueryParams } from '../useNetworks' import { createMockOrbitChain } from './helpers' diff --git a/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts b/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts index c4b4451e12..4d67ec0638 100644 --- a/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts +++ b/packages/arb-token-bridge-ui/src/hooks/arbTokenBridge.types.ts @@ -1,27 +1,13 @@ import { Signer } from '@ethersproject/abstract-signer' -import { TransactionReceipt } from '@ethersproject/abstract-provider' -import { BigNumber, ContractReceipt, ethers } from 'ethers' +import { BigNumber, ContractReceipt } from 'ethers' import { TokenList } from '@uniswap/token-lists' import { EventArgs, - ParentEthDepositTransaction, - ParentEthDepositTransactionReceipt, - ParentContractCallTransaction, - ParentContractCallTransactionReceipt, - ChildContractTransaction, - ChildTransactionReceipt, ChildToParentMessageStatus as OutgoingMessageState, ChildToParentTransactionEvent } from '@arbitrum/sdk' -import { StandardArbERC20 } from '@arbitrum/sdk/dist/lib/abi/StandardArbERC20' import { WithdrawalInitiatedEvent } from '@arbitrum/sdk/dist/lib/abi/L2ArbitrumGateway' -import { - NewTransaction, - Transaction, - ParentToChildMessageData -} from './useTransactions' - export { OutgoingMessageState } export enum TokenType { @@ -39,21 +25,6 @@ export type TransactionLifecycle = Partial<{ onTxError: (error: any) => void }> -export type L1EthDepositTransactionLifecycle = TransactionLifecycle< - ParentEthDepositTransaction, - ParentEthDepositTransactionReceipt -> - -export type L1ContractCallTransactionLifecycle = TransactionLifecycle< - ParentContractCallTransaction, - ParentContractCallTransactionReceipt -> - -export type L2ContractCallTransactionLifecycle = TransactionLifecycle< - ChildContractTransaction, - ChildTransactionReceipt -> - export enum NodeBlockDeadlineStatusTypes { NODE_NOT_CREATED, EXECUTE_CALL_EXCEPTION @@ -97,7 +68,7 @@ export interface BridgeToken { address: string l2Address?: string logoURI?: string - listIds: Set // no listID indicates added by user + listIds: Set // no listID indicates added by user isL2Native?: boolean } @@ -106,27 +77,9 @@ export interface ERC20BridgeToken extends BridgeToken { decimals: number } -export interface L2TokenData { - balance: BigNumber - contract: StandardArbERC20 -} - export interface ContractStorage { [contractAddress: string]: T | undefined } -export interface BridgeBalance { - balance: BigNumber | null - - arbChainBalance: BigNumber | null -} - -// removing 'tokens' / 'balance' could result in one interface -export interface AddressToSymbol { - [tokenAddress: string]: string -} -export interface AddressToDecimals { - [tokenAddress: string]: number -} export type GasEstimates = { estimatedParentChainGas: BigNumber @@ -147,8 +100,8 @@ export interface ArbTokenBridgeEth { export interface ArbTokenBridgeToken { add: (erc20L1orL2Address: string) => Promise addL2NativeToken: (erc20L2Address: string) => void - addTokensFromList: (tokenList: TokenList, listID: number) => void - removeTokensFromList: (listID: number) => void + addTokensFromList: (tokenList: TokenList, listID: string) => void + removeTokensFromList: (listID: string) => void updateTokenData: (l1Address: string) => Promise triggerOutbox: (params: { event: L2ToL1EventResultPlus @@ -156,22 +109,8 @@ export interface ArbTokenBridgeToken { }) => Promise } -export interface TransactionActions { - addTransaction: (transaction: NewTransaction) => void - updateTransaction: ( - txReceipt: TransactionReceipt, - tx?: ethers.ContractTransaction, - l1ToL2MsgData?: ParentToChildMessageData - ) => void -} - -export type ArbTokenBridgeTransactions = { - transactions: Transaction[] -} & Pick - export interface ArbTokenBridge { bridgeTokens: ContractStorage | undefined eth: ArbTokenBridgeEth token: ArbTokenBridgeToken - transactions: ArbTokenBridgeTransactions } diff --git a/packages/arb-token-bridge-ui/src/hooks/useAccountIsBlocked.ts b/packages/arb-token-bridge-ui/src/hooks/useAccountIsBlocked.ts index 1ec288e21a..dd4bd75035 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useAccountIsBlocked.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useAccountIsBlocked.ts @@ -66,10 +66,13 @@ export function useAccountIsBlocked() { return null } - return [address.toLowerCase(), 'useAccountIsBlocked'] + return [ + address.toLocaleLowerCase() as Address, + 'useAccountIsBlocked' + ] as const }, [address]) - const { data: isBlocked } = useSWRImmutable( + const { data: isBlocked } = useSWRImmutable( queryKey, // Extracts the first element of the query key as the fetcher param ([_address]) => fetcher(_address) diff --git a/packages/arb-token-bridge-ui/src/hooks/useAccountMenu.ts b/packages/arb-token-bridge-ui/src/hooks/useAccountMenu.ts index d94d0ac0bd..0c894fafe9 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useAccountMenu.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useAccountMenu.ts @@ -11,9 +11,7 @@ import { useEnsAvatar } from 'wagmi' import { useArbQueryParams } from './useArbQueryParams' -import { trackEvent } from '../util/AnalyticsUtils' import { shortenAddress } from '../util/CommonUtils' -import { useAppContextActions } from '../components/App/AppContext' import { onDisconnectHandler } from '../util/walletConnectUtils' type UDInfo = { name: string | null } @@ -53,7 +51,6 @@ export const useAccountMenu = () => { }) const { chain } = useNetwork() - const { openTransactionHistoryPanel } = useAppContextActions() const [, setQueryParams] = useArbQueryParams() const [udInfo, setUDInfo] = useState(udInfoDefaults) @@ -101,17 +98,11 @@ export const useAccountMenu = () => { resolveUdName() }, [address, l1Provider]) - function openTransactionHistory() { - openTransactionHistoryPanel() - trackEvent('Open Transaction History Click', { pageElement: 'Header' }) - } - return { address, accountShort, ensName, ensAvatar, - openTransactionHistory, disconnect, udInfo, chain, diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index 0d07a46d3e..0d61bd7394 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -32,7 +32,7 @@ import { getChainQueryParamForChain, isValidChainQueryParam } from '../types/ChainQueryParam' -import { ChainId } from '../util/networks' +import { ChainId } from '../types/ChainId' export enum AmountQueryParamEnum { MAX = 'max' diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts b/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts index 2d462063da..39e39f2b83 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useArbTokenBridge.ts @@ -12,7 +12,6 @@ import { } from '@arbitrum/sdk' import { L2ToL1TransactionEvent as ClassicL2ToL1TransactionEvent } from '@arbitrum/sdk/dist/lib/abi/ArbSys' -import useTransactions from './useTransactions' import { ArbTokenBridge, ContractStorage, @@ -137,10 +136,7 @@ export const useArbTokenBridge = ( const l1NetworkID = useMemo(() => String(l1.network.id), [l1.network.id]) - const [transactions, { addTransaction, updateTransaction }] = - useTransactions() - - const removeTokensFromList = (listID: number) => { + const removeTokensFromList = (listID: string) => { setBridgeTokens(prevBridgeTokens => { const newBridgeTokens = { ...prevBridgeTokens } for (const address in bridgeTokens) { @@ -157,7 +153,7 @@ export const useArbTokenBridge = ( }) } - const addTokensFromList = async (arbTokenList: TokenList, listId: number) => { + const addTokensFromList = async (arbTokenList: TokenList, listId: string) => { const l1ChainID = l1.network.id const l2ChainID = l2.network.id @@ -537,11 +533,6 @@ export const useArbTokenBridge = ( removeTokensFromList, updateTokenData, triggerOutbox: triggerOutboxToken - }, - transactions: { - transactions, - updateTransaction, - addTransaction } } } diff --git a/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts b/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts index f174dc8fb3..be9b21769d 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useClaimWithdrawal.ts @@ -15,6 +15,7 @@ import { fetchErc20Data } from '../util/TokenUtils' import { fetchNativeCurrency } from './useNativeCurrency' import { getProviderForChainId } from '@/token-bridge-sdk/utils' import { captureSentryErrorWithExtraData } from '../util/SentryUtils' +import { useTransactionHistoryAddressStore } from '../components/TransactionHistory/TransactionHistorySearchBar' export type UseClaimWithdrawalResult = { claim: () => Promise @@ -28,8 +29,11 @@ export function useClaimWithdrawal( app: { arbTokenBridge } } = useAppState() const { address } = useAccount() + const { sanitizedAddress } = useTransactionHistoryAddressStore() const { data: signer } = useSigner({ chainId: tx.parentChainId }) - const { updatePendingTransaction } = useTransactionHistory(address) + const { updatePendingTransaction } = useTransactionHistory( + sanitizedAddress ?? address + ) const [isClaiming, setIsClaiming] = useState(false) const claim = useCallback(async () => { diff --git a/packages/arb-token-bridge-ui/src/hooks/useDebouncedValue.ts b/packages/arb-token-bridge-ui/src/hooks/useDebouncedValue.ts deleted file mode 100644 index 6e70fe1ba7..0000000000 --- a/packages/arb-token-bridge-ui/src/hooks/useDebouncedValue.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useState, useEffect } from 'react' - -export function useDebouncedValue(value: T, delay: number) { - const [debouncedValue, setDebouncedValue] = useState(value) - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value) - }, delay) - - return () => { - clearTimeout(handler) - } - }, [value, delay]) - - return debouncedValue -} diff --git a/packages/arb-token-bridge-ui/src/hooks/useIsTestnetMode.ts b/packages/arb-token-bridge-ui/src/hooks/useIsTestnetMode.ts index 3917b493f6..052f54629a 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useIsTestnetMode.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useIsTestnetMode.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react' import { useNetworks } from './useNetworks' -import { ChainId, isNetwork } from '../util/networks' +import { isNetwork } from '../util/networks' +import { ChainId } from '../types/ChainId' export const useIsTestnetMode = () => { const [networks, setNetworks] = useNetworks() diff --git a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts index b141f2d144..58f97cd4a8 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useNetworks.ts @@ -4,7 +4,8 @@ import { useCallback, useMemo } from 'react' import { mainnet, arbitrum } from '@wagmi/core/chains' import { useArbQueryParams } from './useArbQueryParams' -import { ChainId, getCustomChainsFromLocalStorage } from '../util/networks' +import { getCustomChainsFromLocalStorage } from '../util/networks' +import { ChainId } from '../types/ChainId' import { sepolia, holesky, diff --git a/packages/arb-token-bridge-ui/src/hooks/useRedeemTeleporter.ts b/packages/arb-token-bridge-ui/src/hooks/useRedeemTeleporter.ts index 4845c0e67f..20edd6715d 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useRedeemTeleporter.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useRedeemTeleporter.ts @@ -26,7 +26,7 @@ import { isUserRejectedError } from '../util/isUserRejectedError' import { errorToast } from '../components/common/atoms/Toast' import { useTransactionHistory } from './useTransactionHistory' import { Address } from '../util/AddressUtils' -import { isTeleportTx, L2ToL3MessageData } from './useTransactions' +import { isTeleportTx, L2ToL3MessageData } from '../types/Transactions' import { UseRedeemRetryableResult } from './useRedeemRetryable' import { getUpdatedTeleportTransfer } from '../components/TransactionHistory/helpers' diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts index 940eb0181b..c2f2779c19 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransactionHistory.ts @@ -4,12 +4,8 @@ import useSWRInfinite from 'swr/infinite' import { useCallback, useEffect, useMemo, useState } from 'react' import dayjs from 'dayjs' -import { - ChainId, - getChains, - getChildChainIds, - isNetwork -} from '../util/networks' +import { getChains, getChildChainIds, isNetwork } from '../util/networks' +import { ChainId } from '../types/ChainId' import { fetchWithdrawals } from '../util/withdrawals/fetchWithdrawals' import { fetchDeposits } from '../util/deposits/fetchDeposits' import { @@ -17,7 +13,7 @@ import { L2ToL1EventResultPlus, WithdrawalInitiated } from './arbTokenBridge.types' -import { isTeleportTx, Transaction } from './useTransactions' +import { isTeleportTx, Transaction } from '../types/Transactions' import { MergedTransaction } from '../state/app/state' import { isCustomDestinationAddressTx, @@ -161,13 +157,7 @@ async function transformTransaction(tx: Transfer): Promise { } if (isDeposit(tx)) { - return transformDeposit( - await updateAdditionalDepositData({ - depositTx: tx, - parentProvider, - childProvider - }) - ) + return transformDeposit(await updateAdditionalDepositData(tx)) } let withdrawal: L2ToL1EventResultPlus | undefined @@ -838,7 +828,7 @@ export const useTransactionHistory = ( if (isLoadingTxsWithoutStatus || error) { return { - transactions: [], + transactions: newTransactionsData || [], loading: isLoadingTxsWithoutStatus, error, failedChainPairs: [], diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransactions.ts b/packages/arb-token-bridge-ui/src/hooks/useTransactions.ts deleted file mode 100644 index ffe0ee4886..0000000000 --- a/packages/arb-token-bridge-ui/src/hooks/useTransactions.ts +++ /dev/null @@ -1,442 +0,0 @@ -import { useReducer, useEffect, useMemo } from 'react' -import { TransactionReceipt } from '@ethersproject/abstract-provider' -import { AssetType, TransactionActions } from './arbTokenBridge.types' -import { BigNumber, ethers } from 'ethers' -import { ParentToChildMessageStatus } from '@arbitrum/sdk' -import { - MergedTransaction, - TeleporterMergedTransaction -} from '../state/app/state' - -type Action = - | { type: 'ADD_TRANSACTION'; transaction: Transaction } - | { type: 'SET_SUCCESS'; txID: string } - | { type: 'SET_FAILURE'; txID: string } - | { type: 'SET_INITIAL_TRANSACTIONS'; transactions: Transaction[] } - | { type: 'CLEAR_PENDING' } - | { type: 'CONFIRM_TRANSACTION'; txID: string } - | { type: 'REMOVE_TRANSACTION'; txID: string } - | { type: 'SET_BLOCK_NUMBER'; txID: string; blockNumber?: number } - | { type: 'SET_RESOLVED_TIMESTAMP'; txID: string; timestamp?: string } - | { type: 'ADD_TRANSACTIONS'; transactions: Transaction[] } - | { - type: 'UPDATE_PARENT_TO_CHILD_MSG_DATA' - txID: string - parentToChildMsgData: ParentToChildMessageData - } - | { type: 'SET_TRANSACTIONS'; transactions: Transaction[] } - -export type TxnStatus = 'pending' | 'success' | 'failure' | 'confirmed' - -/** @interface - * Transaction - * @alias Transaction - * @description Bridge transaction data with up to date status. - */ -export type TxnType = - | 'deposit' - | 'deposit-l1' - | 'deposit-l2' // unused; keeping for cache backwrads compatability - | 'withdraw' - | 'outbox' - | 'approve' - | 'deposit-l2-auto-redeem' // unused; keeping for cache backwrads compatability - | 'deposit-l2-ticket-created' // unused; keeping for cache backwrads compatability - | 'approve-l2' - -const deprecatedTxTypes: Set = new Set([ - 'deposit-l2-auto-redeem', - 'deposit-l2-ticket-created', - 'deposit-l2' -]) - -export const txnTypeToLayer = (txnType: TxnType): 1 | 2 => { - switch (txnType) { - case 'deposit': - case 'deposit-l1': - case 'outbox': - case 'approve': - return 1 - case 'deposit-l2': - case 'withdraw': - case 'deposit-l2-auto-redeem': - case 'deposit-l2-ticket-created': - case 'approve-l2': - return 2 - } -} - -export interface ParentToChildMessageData { - status: ParentToChildMessageStatus - retryableCreationTxID: string - childTxId?: string - fetchingUpdate: boolean -} - -export interface L2ToL3MessageData { - status: ParentToChildMessageStatus - retryableCreationTxID?: string - l2ForwarderRetryableTxID?: string - l3TxID?: string - l2ChainId: number -} - -export type ChildToParentMessageData = { - uniqueId: BigNumber -} - -type TransactionBase = { - type: TxnType - status?: TxnStatus - value: string | null - value2?: string - txID?: string - assetName: string - assetType: AssetType - tokenAddress?: string - sender: string - destination?: string - blockNumber?: number - l1NetworkID: string - l2NetworkID?: string - timestampResolved?: string // time when its status was changed - timestampCreated?: string //time when this transaction is first added to the list - parentToChildMsgData?: ParentToChildMessageData - childToParentMsgData?: ChildToParentMessageData - isClassic?: boolean -} - -export interface Transaction extends TransactionBase { - txID: string - direction: 'deposit' | 'withdrawal' - source: 'subgraph' | 'event_logs' | 'local_storage_cache' - parentChainId: number - childChainId: number - nonce?: number -} - -export interface TeleporterTransaction extends Transaction { - l2ToL3MsgData: L2ToL3MessageData -} - -export interface NewTransaction extends TransactionBase { - status: 'pending' -} - -export interface FailedTransaction extends TransactionBase { - status: 'failure' -} - -// TODO: enforce this type restriction -export interface DepositTransaction extends Transaction { - parentToChildMsgData: ParentToChildMessageData - type: 'deposit' | 'deposit-l1' -} - -export function isTeleportTx( - tx: Transaction | MergedTransaction -): tx is TeleporterTransaction | TeleporterMergedTransaction { - return (tx as TeleporterTransaction).l2ToL3MsgData !== undefined -} - -function updateStatus(state: Transaction[], status: TxnStatus, txID: string) { - const newState = [...state] - const index = newState.findIndex(txn => txn.txID === txID) - const transaction = newState[index] - - if (!transaction) { - console.warn('transaction not found', txID) - return state - } - - newState[index] = { - ...transaction, - status - } - return newState -} - -function updateBlockNumber( - state: Transaction[], - txID: string, - blockNumber?: number -) { - const newState = [...state] - const index = newState.findIndex(txn => txn.txID === txID) - const transaction = newState[index] - - if (!transaction) { - console.warn('transaction not found', txID) - return state - } - - newState[index] = { - ...transaction, - blockNumber - } - return newState -} - -function updateTxnParentToChildMsg( - state: Transaction[], - txID: string, - parentToChildMsgData: ParentToChildMessageData -) { - const newState = [...state] - const index = newState.findIndex(txn => txn.txID === txID) - const transaction = newState[index] - - if (!transaction) { - console.warn('transaction not found', txID) - return state - } - - if (!(transaction.type === 'deposit' || transaction.type === 'deposit-l1')) { - throw new Error( - "Attempting to add a parentToChildMsg to a tx that isn't a deposit:" + - txID - ) - } - - const previousParentToChildMsgData = transaction.parentToChildMsgData - if (!previousParentToChildMsgData) { - newState[index] = { - ...transaction, - parentToChildMsgData: { - status: parentToChildMsgData.status, - retryableCreationTxID: parentToChildMsgData.retryableCreationTxID, - fetchingUpdate: false - } - } - return newState - } - - newState[index] = { - ...transaction, - parentToChildMsgData: { - ...previousParentToChildMsgData, - ...parentToChildMsgData - } - } - return newState -} - -function updateResolvedTimestamp( - state: Transaction[], - txID: string, - timestamp?: string -) { - const newState = [...state] - const index = newState.findIndex(txn => txn.txID === txID) - const transaction = newState[index] - - if (!transaction) { - console.warn('transaction not found', txID) - return state - } - - newState[index] = { - ...transaction, - timestampResolved: timestamp - } - - return newState -} -function reducer(state: Transaction[], action: Action) { - switch (action.type) { - case 'SET_INITIAL_TRANSACTIONS': { - // Add l1 to L2 stuff with pending status - return [...action.transactions] - } - case 'ADD_TRANSACTIONS': { - // sanity / safety check: ensure no duplicates: - const currentTxIds = new Set(state.map(tx => tx.txID)) - const txsToAdd = action.transactions.filter(tx => { - if (!currentTxIds.has(tx.txID)) { - return true - } else { - console.warn( - `Warning: trying to add ${tx.txID} which is already included` - ) - return false - } - }) - return state.concat(txsToAdd) - } - case 'ADD_TRANSACTION': { - return state.concat(action.transaction) - } - case 'REMOVE_TRANSACTION': { - return state.filter(txn => txn.txID !== action.txID) - } - case 'SET_SUCCESS': { - return updateStatus(state, 'success', action.txID) - } - case 'SET_FAILURE': { - return updateStatus(state, 'failure', action.txID) - } - case 'CLEAR_PENDING': { - return state.filter(txn => txn.status !== 'pending') - } - case 'CONFIRM_TRANSACTION': { - return updateStatus(state, 'confirmed', action.txID) - } - case 'SET_BLOCK_NUMBER': { - return updateBlockNumber(state, action.txID, action.blockNumber) - } - case 'SET_RESOLVED_TIMESTAMP': { - return updateResolvedTimestamp(state, action.txID, action.timestamp) - } - case 'UPDATE_PARENT_TO_CHILD_MSG_DATA': { - return updateTxnParentToChildMsg( - state, - action.txID, - action.parentToChildMsgData - ) - } - case 'SET_TRANSACTIONS': { - return action.transactions - } - default: - return state - } -} - -const localStorageReducer = (state: Transaction[], action: Action) => { - const newState = reducer(state, action) - // don't cache fetchingUpdate state - const stateForCache = newState.map(tx => { - if (tx.parentToChildMsgData && tx.parentToChildMsgData.fetchingUpdate) { - return { - ...tx, - parentToChildMsgData: { - ...tx.parentToChildMsgData, - fetchingUpdate: false - } - } - } - return tx - }) - window.localStorage.setItem('arbTransactions', JSON.stringify(stateForCache)) - return newState -} - -const useTransactions = (): [Transaction[], TransactionActions] => { - const [state, dispatch] = useReducer(localStorageReducer, []) - - useEffect(() => { - const cachedTransactions = window.localStorage.getItem('arbTransactions') - dispatch({ - type: 'SET_INITIAL_TRANSACTIONS', - transactions: cachedTransactions ? JSON.parse(cachedTransactions) : [] - }) - }, []) - - const addTransaction = (transaction: NewTransaction) => { - if (!transaction.txID) { - console.warn(' Cannot add transaction: TxID not included (???)') - return - } - const tx = { - ...transaction, - timestampCreated: new Date().toISOString() - } as Transaction - return dispatch({ - type: 'ADD_TRANSACTION', - transaction: tx - }) - } - - const updateTxnParentToChildMsgData = async ( - txID: string, - parentToChildMsgData: ParentToChildMessageData - ) => { - dispatch({ - type: 'UPDATE_PARENT_TO_CHILD_MSG_DATA', - txID: txID, - parentToChildMsgData - }) - } - - const setTransactionSuccess = (txID: string) => { - return dispatch({ - type: 'SET_SUCCESS', - txID: txID - }) - } - const setTransactionBlock = (txID: string, blockNumber?: number) => { - return dispatch({ - type: 'SET_BLOCK_NUMBER', - txID, - blockNumber - }) - } - const setResolvedTimestamp = (txID: string, timestamp?: string) => { - return dispatch({ - type: 'SET_RESOLVED_TIMESTAMP', - txID, - timestamp - }) - } - const setTransactionFailure = (txID?: string) => { - if (!txID) { - console.warn(' Cannot set transaction failure: TxID not included (???)') - return - } - return dispatch({ - type: 'SET_FAILURE', - txID: txID - }) - } - - const updateTransaction = ( - txReceipt: TransactionReceipt, - tx?: ethers.ContractTransaction, - parentToChildMsgData?: ParentToChildMessageData - ) => { - if (!txReceipt.transactionHash) { - return console.warn( - '*** TransactionHash not included in transaction receipt (???) *** ' - ) - } - switch (txReceipt.status) { - case 0: { - setTransactionFailure(txReceipt.transactionHash) - break - } - case 1: { - setTransactionSuccess(txReceipt.transactionHash) - break - } - default: - console.warn('*** Status not included in transaction receipt *** ') - break - } - if (tx?.blockNumber) { - setTransactionBlock(txReceipt.transactionHash, tx.blockNumber) - } - if (tx) { - setResolvedTimestamp(txReceipt.transactionHash, new Date().toISOString()) - } - if (parentToChildMsgData) { - updateTxnParentToChildMsgData( - txReceipt.transactionHash, - parentToChildMsgData - ) - } - } - - const transactions = useMemo(() => { - return state.filter(tx => !deprecatedTxTypes.has(tx.type)) - }, [state]) - - return [ - transactions, - { - addTransaction, - updateTransaction - } - ] -} - -export default useTransactions diff --git a/packages/arb-token-bridge-ui/src/hooks/useTransferDuration.ts b/packages/arb-token-bridge-ui/src/hooks/useTransferDuration.ts index f8671e23e2..ceb92a04c5 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useTransferDuration.ts +++ b/packages/arb-token-bridge-ui/src/hooks/useTransferDuration.ts @@ -3,12 +3,7 @@ import { isValidTeleportChainPair } from '@/token-bridge-sdk/teleport' import { MergedTransaction } from '../state/app/state' import { useRemainingTimeCctp } from '../state/cctpState' -import { - getBlockNumberReferenceChainIdByChainId, - getConfirmPeriodBlocks, - getL1BlockTime, - isNetwork -} from '../util/networks' +import { isNetwork } from '../util/networks' import { getConfirmationTime } from '../util/WithdrawalUtils' const DEPOSIT_TIME_MINUTES = { @@ -31,13 +26,6 @@ const DEPOSIT_TIME_MINUTES_ORBIT = { testnet: 1 } -/** - * Buffer for after a node is confirmable but isn't yet confirmed. - * A rollup block (RBlock) typically gets asserted every 30-60 minutes. - */ -const CONFIRMATION_BUFFER_MINUTES = 60 -const SECONDS_IN_MIN = 60 - type UseTransferDurationResult = { approximateDurationInMinutes: number estimatedMinutesLeft: number | null @@ -122,22 +110,10 @@ export function getWithdrawalConfirmationDate({ // For new txs createdAt won't be defined yet, we default to the current time in that case const createdAtDate = createdAt ? dayjs(createdAt) : dayjs() - const { confirmationTimeInSeconds, fastWithdrawalActive } = - getConfirmationTime(withdrawalFromChainId) - if (fastWithdrawalActive && confirmationTimeInSeconds) { - return createdAtDate.add(confirmationTimeInSeconds, 'second') - } - - const blockNumberReferenceChainId = getBlockNumberReferenceChainIdByChainId({ - chainId: withdrawalFromChainId - }) - // the block time is always base chain's block time regardless of withdrawing from L3 to L2 or from L2 to L1 - // and similarly, the confirm period blocks is always the number of blocks on the base chain - const confirmationSeconds = - getL1BlockTime(blockNumberReferenceChainId) * - getConfirmPeriodBlocks(withdrawalFromChainId) + - CONFIRMATION_BUFFER_MINUTES * SECONDS_IN_MIN - return createdAtDate.add(confirmationSeconds, 'second') + const { confirmationTimeInSeconds } = getConfirmationTime( + withdrawalFromChainId + ) + return createdAtDate.add(confirmationTimeInSeconds, 'second') } function getWithdrawalDuration(tx: MergedTransaction) { diff --git a/packages/arb-token-bridge-ui/src/pages/api/cctp/[type].ts b/packages/arb-token-bridge-ui/src/pages/api/cctp/[type].ts index ff4e3d2021..6925c98b5d 100644 --- a/packages/arb-token-bridge-ui/src/pages/api/cctp/[type].ts +++ b/packages/arb-token-bridge-ui/src/pages/api/cctp/[type].ts @@ -1,7 +1,7 @@ import { gql } from '@apollo/client' import { NextApiRequest, NextApiResponse } from 'next' -import { ChainId } from '../../../util/networks' +import { ChainId } from '../../../types/ChainId' import { Address } from '../../../util/AddressUtils' import { diff --git a/packages/arb-token-bridge-ui/src/pages/api/chains/[chainId]/block-number.ts b/packages/arb-token-bridge-ui/src/pages/api/chains/[chainId]/block-number.ts index 02dd20f839..f23b2fd4c3 100644 --- a/packages/arb-token-bridge-ui/src/pages/api/chains/[chainId]/block-number.ts +++ b/packages/arb-token-bridge-ui/src/pages/api/chains/[chainId]/block-number.ts @@ -1,7 +1,7 @@ import { NextApiRequest, NextApiResponse } from 'next' import { gql } from '@apollo/client' -import { ChainId } from '../../../../util/networks' +import { ChainId } from '../../../../types/ChainId' import { getL1SubgraphClient, getL2SubgraphClient, diff --git a/packages/arb-token-bridge-ui/src/state/app/state.ts b/packages/arb-token-bridge-ui/src/state/app/state.ts index e2b723a676..d962b01372 100644 --- a/packages/arb-token-bridge-ui/src/state/app/state.ts +++ b/packages/arb-token-bridge-ui/src/state/app/state.ts @@ -10,7 +10,7 @@ import { ChildToParentMessageData, L2ToL3MessageData, TxnType -} from '../../hooks/useTransactions' +} from '../../types/Transactions' import { ConnectionState } from '../../util' import { CCTPSupportedChainId } from '../cctpState' import { Address } from '../../util/AddressUtils' diff --git a/packages/arb-token-bridge-ui/src/state/app/utils.ts b/packages/arb-token-bridge-ui/src/state/app/utils.ts index b2a14492df..a5012cb2e0 100644 --- a/packages/arb-token-bridge-ui/src/state/app/utils.ts +++ b/packages/arb-token-bridge-ui/src/state/app/utils.ts @@ -17,7 +17,7 @@ import { isTeleportTx, TeleporterTransaction, Transaction -} from '../../hooks/useTransactions' +} from '../../types/Transactions' import { getUniqueIdOrHashFromEvent } from '../../hooks/useArbTokenBridge' import { firstRetryableLegRequiresRedeem, @@ -289,14 +289,6 @@ export function isCustomDestinationAddressTx( return tx.sender.toLowerCase() !== tx.destination.toLowerCase() } -export const isWithdrawalReadyToClaim = (tx: MergedTransaction) => { - return ( - isWithdrawal(tx) && - isPending(tx) && - tx.status === outgoingStateToString[OutgoingMessageState.CONFIRMED] - ) -} - export const isDepositReadyToRedeem = (tx: MergedTransaction) => { if (isTeleportTx(tx)) { return ( diff --git a/packages/arb-token-bridge-ui/src/state/cctpState.ts b/packages/arb-token-bridge-ui/src/state/cctpState.ts index 7f0eb43f84..523aa80b73 100644 --- a/packages/arb-token-bridge-ui/src/state/cctpState.ts +++ b/packages/arb-token-bridge-ui/src/state/cctpState.ts @@ -5,12 +5,8 @@ import useSWRImmutable from 'swr/immutable' import { useInterval } from 'react-use' import { getCctpUtils } from '@/token-bridge-sdk/cctp' -import { - ChainId, - getL1BlockTime, - getNetworkName, - isNetwork -} from '../util/networks' +import { getL1BlockTime, getNetworkName, isNetwork } from '../util/networks' +import { ChainId } from '../types/ChainId' import { fetchCCTPDeposits, fetchCCTPWithdrawals } from '../util/cctp/fetchCCTP' import { DepositStatus, MergedTransaction, WithdrawalStatus } from './app/state' import { normalizeTimestamp } from './app/utils' diff --git a/packages/arb-token-bridge-ui/src/state/index.ts b/packages/arb-token-bridge-ui/src/state/index.ts index 35d1a41c11..29116cf134 100644 --- a/packages/arb-token-bridge-ui/src/state/index.ts +++ b/packages/arb-token-bridge-ui/src/state/index.ts @@ -1,9 +1,5 @@ import { IContext } from 'overmind' -import { - createActionsHook, - createStateHook, - createEffectsHook -} from 'overmind-react' +import { createActionsHook, createStateHook } from 'overmind-react' import { namespaced } from 'overmind/config' import * as app from './app' @@ -20,4 +16,3 @@ export type Context = IContext<{ export const useAppState = createStateHook() export const useActions = createActionsHook() -export const useEffects = createEffectsHook() diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/cctp.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/cctp.ts index eb446b7679..e816a8394b 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/cctp.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/cctp.ts @@ -5,7 +5,7 @@ import { ChainDomain } from '../pages/api/cctp/[type]' import { prepareWriteContract, writeContract } from '@wagmi/core' import { MessageTransmitterAbi } from '../util/cctp/MessageTransmitterAbi' import { CCTPSupportedChainId } from '../state/cctpState' -import { ChainId } from '../util/networks' +import { ChainId } from '../types/ChainId' import { CommonAddress } from '../util/CommonAddressUtils' import { Address } from '../util/AddressUtils' diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts index 54e7483bbf..b8bc6ae6dc 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts @@ -1,7 +1,8 @@ import { BigNumber, Signer } from 'ethers' import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers' -import { ChainId, isNetwork, rpcURLs } from '../util/networks' +import { isNetwork, rpcURLs } from '../util/networks' +import { ChainId } from '../types/ChainId' import { BridgeTransferStarterPropsWithChainIds } from './BridgeTransferStarter' import { isValidTeleportChainPair } from './teleport' import { diff --git a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts index cb41de6fc8..15e2c0614c 100644 --- a/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts +++ b/packages/arb-token-bridge-ui/src/types/ChainQueryParam.ts @@ -1,10 +1,10 @@ import { Chain } from 'wagmi' import * as chains from 'wagmi/chains' import { - ChainId, getCustomChainFromLocalStorageById, getSupportedChainIds } from '../util/networks' +import { ChainId } from '../types/ChainId' import * as customChains from '../util/wagmi/wagmiAdditionalNetworks' import { getOrbitChains, orbitChains } from '../util/orbitChainsList' import { chainToWagmiChain } from '../util/wagmi/wagmiAdditionalNetworks' diff --git a/packages/arb-token-bridge-ui/src/types/Transactions.ts b/packages/arb-token-bridge-ui/src/types/Transactions.ts new file mode 100644 index 0000000000..8fa341fa26 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/types/Transactions.ts @@ -0,0 +1,85 @@ +import { BigNumber } from 'ethers' +import { ParentToChildMessageStatus } from '@arbitrum/sdk' + +import { AssetType } from '../hooks/arbTokenBridge.types' +import { + MergedTransaction, + TeleporterMergedTransaction +} from '../state/app/state' + +export type TxnStatus = 'pending' | 'success' | 'failure' | 'confirmed' + +/** @interface + * Transaction + * @alias Transaction + * @description Bridge transaction data with up to date status. + */ +export type TxnType = + | 'deposit' + | 'deposit-l1' + | 'deposit-l2' // unused; keeping for cache backwrads compatability + | 'withdraw' + | 'outbox' + | 'approve' + | 'deposit-l2-auto-redeem' // unused; keeping for cache backwrads compatability + | 'deposit-l2-ticket-created' // unused; keeping for cache backwrads compatability + | 'approve-l2' + +export interface ParentToChildMessageData { + status: ParentToChildMessageStatus + retryableCreationTxID: string + childTxId?: string + fetchingUpdate: boolean +} + +export interface L2ToL3MessageData { + status: ParentToChildMessageStatus + retryableCreationTxID?: string + l2ForwarderRetryableTxID?: string + l3TxID?: string + l2ChainId: number +} + +export type ChildToParentMessageData = { + uniqueId: BigNumber +} + +type TransactionBase = { + type: TxnType + status?: TxnStatus + value: string | null + value2?: string + txID?: string + assetName: string + assetType: AssetType + tokenAddress?: string + sender: string + destination?: string + blockNumber?: number + l1NetworkID: string + l2NetworkID?: string + timestampResolved?: string // time when its status was changed + timestampCreated?: string //time when this transaction is first added to the list + parentToChildMsgData?: ParentToChildMessageData + childToParentMsgData?: ChildToParentMessageData + isClassic?: boolean +} + +export interface Transaction extends TransactionBase { + txID: string + direction: 'deposit' | 'withdrawal' + source: 'subgraph' | 'event_logs' | 'local_storage_cache' + parentChainId: number + childChainId: number + nonce?: number +} + +export interface TeleporterTransaction extends Transaction { + l2ToL3MsgData: L2ToL3MessageData +} + +export function isTeleportTx( + tx: Transaction | MergedTransaction +): tx is TeleporterTransaction | TeleporterMergedTransaction { + return (tx as TeleporterTransaction).l2ToL3MsgData !== undefined +} diff --git a/packages/arb-token-bridge-ui/src/util/AddressUtils.ts b/packages/arb-token-bridge-ui/src/util/AddressUtils.ts index 4f33e3da77..9e15ef0750 100644 --- a/packages/arb-token-bridge-ui/src/util/AddressUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/AddressUtils.ts @@ -1,3 +1,5 @@ +import { Provider } from '@ethersproject/providers' + import { getAPIBaseUrl } from '.' import { getProviderForChainId } from '../token-bridge-sdk/utils' @@ -29,3 +31,14 @@ export async function addressIsDenylisted(address: string) { return false } } + +export function getNonce( + address: string | undefined, + { provider }: { provider: Provider } +): Promise { + if (typeof address === 'undefined') { + return 0 as unknown as Promise + } + + return provider.getTransactionCount(address) +} diff --git a/packages/arb-token-bridge-ui/src/util/ExponentialBackoffUtils.ts b/packages/arb-token-bridge-ui/src/util/ExponentialBackoffUtils.ts new file mode 100644 index 0000000000..c46210ce24 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/ExponentialBackoffUtils.ts @@ -0,0 +1,14 @@ +import { backOff as _backOff, BackoffOptions } from 'exponential-backoff' + +const backoffOptions: BackoffOptions = { + startingDelay: 1_000, + timeMultiple: 1.5 +} + +export function backOff(request: () => Promise): Promise { + return _backOff(request, backoffOptions) +} + +export function wait(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)) +} diff --git a/packages/arb-token-bridge-ui/src/util/L2ApprovalUtils.ts b/packages/arb-token-bridge-ui/src/util/L2ApprovalUtils.ts index b5bfe7d038..55001b5acb 100644 --- a/packages/arb-token-bridge-ui/src/util/L2ApprovalUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/L2ApprovalUtils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '../util/networks' +import { ChainId } from '../types/ChainId' import { xErc20RequiresApprovalOnChildChain } from './xErc20Utils' export type RequireL2ApproveToken = { diff --git a/packages/arb-token-bridge-ui/src/util/L2NativeUtils.ts b/packages/arb-token-bridge-ui/src/util/L2NativeUtils.ts index e223f7b9ed..a6f0cb8652 100644 --- a/packages/arb-token-bridge-ui/src/util/L2NativeUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/L2NativeUtils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '../util/networks' +import { ChainId } from '../types/ChainId' import { CommonAddress } from './CommonAddressUtils' export type L2NativeToken = { diff --git a/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts b/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts index f65d704ccb..ad7f89ba80 100644 --- a/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/RetryableUtils.ts @@ -12,7 +12,7 @@ import { TeleporterMergedTransaction } from '../state/app/state' import { normalizeTimestamp } from '../state/app/utils' -import { isTeleportTx } from '../hooks/useTransactions' +import { isTeleportTx } from '../types/Transactions' type GetRetryableTicketParams = { parentChainTxHash: string diff --git a/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts b/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts index 53d3d6c5f3..dcdd31f896 100644 --- a/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/SubgraphUtils.ts @@ -1,4 +1,4 @@ -import { ChainId } from './networks' +import { ChainId } from '../types/ChainId' import { getAPIBaseUrl } from '.' export function hasL1Subgraph(l2ChainId: number) { diff --git a/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts b/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts index 1622484a9d..09fe0ccd56 100644 --- a/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/TokenListUtils.ts @@ -8,12 +8,15 @@ import CMCLogo from '@/images/lists/cmc.png' import CoinGeckoLogo from '@/images/lists/coinGecko.svg' import ArbitrumLogo from '@/images/lists/ArbitrumLogo.png' import { ArbTokenBridge } from '../hooks/arbTokenBridge.types' -import { ChainId } from './networks' +import { ChainId } from '../types/ChainId' +import orbitChainsData from './orbitChainsData.json' -export const SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID = 0 +export const SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID = + 'SPECIAL_ARBITRUM_TOKEN_TOKEN_LIST_ID' export interface BridgeTokenList { - id: number + // string is required here to avoid duplicates when mapping orbit chains to tokenlists + id: string originChainID: number url: string name: string @@ -34,7 +37,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ isArbitrumTokenTokenList: true }, { - id: 1, + id: '1', originChainID: ChainId.ArbitrumOne, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/arbed_arb_whitelist_era.json', name: 'Arbitrum Whitelist Era', @@ -42,7 +45,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ logoURI: ArbitrumLogo }, { - id: 2, + id: '2', originChainID: ChainId.ArbitrumOne, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/arbed_uniswap_labs_default.json', name: 'Arbed Uniswap List', @@ -50,7 +53,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ logoURI: UniswapLogo }, { - id: 4, + id: '4', originChainID: ChainId.ArbitrumOne, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/arbed_coingecko.json', name: 'Arbed CoinGecko List', @@ -58,7 +61,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ logoURI: CoinGeckoLogo }, { - id: 5, + id: '5', originChainID: ChainId.ArbitrumOne, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/arbed_coinmarketcap.json', name: 'Arbed CMC List', @@ -66,7 +69,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ logoURI: CMCLogo }, { - id: 6, + id: '6', originChainID: ChainId.ArbitrumNova, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/42170_arbed_uniswap_labs_default.json', name: 'Arbed Uniswap List', @@ -78,7 +81,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ // TODO: remove list for chain ID 412346 after fix: // https://github.com/OffchainLabs/arb-token-bridge/issues/564 { - id: 9, + id: '9', // Local node originChainID: ChainId.ArbitrumLocal, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/421613_arbed_coinmarketcap.json', @@ -87,7 +90,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ logoURI: CMCLogo }, { - id: 10, + id: '10', originChainID: ChainId.ArbitrumSepolia, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/421614_arbed_uniswap_labs.json', name: 'Arbed Uniswap List', @@ -96,7 +99,7 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ }, // CoinGecko { - id: 11, + id: '11', originChainID: ChainId.ArbitrumNova, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/42170_arbed_coingecko.json', name: 'Arbed CoinGecko List', @@ -104,25 +107,15 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ logoURI: CoinGeckoLogo }, { - id: 13, + id: '13', originChainID: ChainId.ArbitrumSepolia, url: 'https://tokenlist.arbitrum.io/ArbTokenLists/421614_arbed_coingecko.json', name: 'Arbed CoinGecko List', isDefault: true, logoURI: CoinGeckoLogo }, - // Orbit { - id: 14, - // Xai - originChainID: 660279, - url: 'https://tokenlist.arbitrum.io/ArbTokenLists/660279_arbed_uniswap_labs.json', - name: 'Arbed Uniswap List', - isDefault: true, - logoURI: UniswapLogo - }, - { - id: 660279, + id: '660279', // Xai originChainID: 660279, url: 'tokenLists/660279_default.json', @@ -130,42 +123,33 @@ export const BRIDGE_TOKEN_LISTS: BridgeTokenList[] = [ isDefault: true, logoURI: '/images/XaiLogo.svg' }, - { - id: 15, - // Rari - originChainID: 1380012617, - url: 'https://tokenlist.arbitrum.io/ArbTokenLists/1380012617_arbed_uniswap_labs.json', - name: 'Arbed Uniswap List', - isDefault: true, - logoURI: UniswapLogo - }, - { - id: 16, - // Muster - originChainID: 4078, - url: 'https://tokenlist.arbitrum.io/ArbTokenLists/4078_arbed_uniswap_labs.json', - name: 'Arbed Uniswap List', - isDefault: true, - logoURI: UniswapLogo - }, - { - id: 17, - // Proof of Play Apex - originChainID: 70700, - url: 'https://tokenlist.arbitrum.io/ArbTokenLists/70700_arbed_uniswap_labs.json', - name: 'Arbed Uniswap List', - isDefault: true, - logoURI: UniswapLogo - }, - { - id: 19, - // SX Network - originChainID: 4162, - url: 'https://tokenlist.arbitrum.io/ArbTokenLists/4162_arbed_uniswap_labs.json', - name: 'Arbed Uniswap List', - isDefault: true, - logoURI: UniswapLogo - } + // For all orbit chains, + ...orbitChainsData.mainnet + .concat(orbitChainsData.testnet) + .reduce((acc, chain) => { + // Only include arbified native token list for L3 settling to ArbOne + if (chain.parentChainId === ChainId.ArbitrumOne) { + acc.push({ + id: `${chain.chainId}_native`, + originChainID: chain.chainId, + url: `https://tokenlist.arbitrum.io/ArbTokenLists/${chain.chainId}_arbed_native_list.json`, + name: `${chain.name} Default List`, + isDefault: true, + logoURI: ArbitrumLogo + }) + } + + acc.push({ + id: `${chain.chainId}_uniswap`, + originChainID: chain.chainId, + url: `https://tokenlist.arbitrum.io/ArbTokenLists/${chain.chainId}_arbed_uniswap_labs.json`, + name: `${chain.name} Arbed Uniswap List`, + isDefault: true, + logoURI: UniswapLogo + }) + + return acc + }, [] as BridgeTokenList[]) ] export const listIdsToNames: { [key: string]: string } = {} @@ -176,7 +160,7 @@ BRIDGE_TOKEN_LISTS.forEach(bridgeTokenList => { export interface TokenListWithId extends TokenList { l2ChainId: string - bridgeTokenListId: number + bridgeTokenListId: string isValid?: boolean } diff --git a/packages/arb-token-bridge-ui/src/util/TokenTeleportEnabledUtils.ts b/packages/arb-token-bridge-ui/src/util/TokenTeleportEnabledUtils.ts index 3eabd7ed9d..72127f7bc5 100644 --- a/packages/arb-token-bridge-ui/src/util/TokenTeleportEnabledUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/TokenTeleportEnabledUtils.ts @@ -1,4 +1,4 @@ -import { ChainId } from './networks' +import { ChainId } from '../types/ChainId' export type TeleportEnabledToken = { symbol: string diff --git a/packages/arb-token-bridge-ui/src/util/TokenTransferDisabledUtils.ts b/packages/arb-token-bridge-ui/src/util/TokenTransferDisabledUtils.ts index 563f9b6549..863b01a5d4 100644 --- a/packages/arb-token-bridge-ui/src/util/TokenTransferDisabledUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/TokenTransferDisabledUtils.ts @@ -1,4 +1,4 @@ -import { ChainId } from '../util/networks' +import { ChainId } from '../types/ChainId' export type TransferDisabledToken = { symbol: string diff --git a/packages/arb-token-bridge-ui/src/util/TokenUtils.ts b/packages/arb-token-bridge-ui/src/util/TokenUtils.ts index 428e0dc305..b180be53c8 100644 --- a/packages/arb-token-bridge-ui/src/util/TokenUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/TokenUtils.ts @@ -11,7 +11,8 @@ import { import { ERC20__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory' import { CommonAddress } from './CommonAddressUtils' -import { ChainId, isNetwork } from './networks' +import { isNetwork } from './networks' +import { ChainId } from '../types/ChainId' import { defaultErc20Decimals } from '../defaults' import { ERC20BridgeToken, TokenType } from '../hooks/arbTokenBridge.types' import { getBridger, getChainIdFromProvider } from '../token-bridge-sdk/utils' diff --git a/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts b/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts index beeb63fe91..08a45510a7 100644 --- a/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/WithdrawOnlyUtils.ts @@ -4,7 +4,8 @@ import { ethers } from 'ethers' import { getProviderForChainId } from '@/token-bridge-sdk/utils' -import { ChainId, isNetwork } from '../util/networks' +import { isNetwork } from '../util/networks' +import { ChainId } from '../types/ChainId' import { isTokenArbitrumOneUSDCe, isTokenArbitrumSepoliaUSDCe @@ -217,6 +218,24 @@ export const withdrawOnlyTokens: { [chainId: number]: WithdrawOnlyToken[] } = { l2CustomAddr: '', l1Address: '0x83e817E1574e2201a005EC0f7e700ED5606F555E', l2Address: '0x87ABaD012da6DcD0438e36967FcaD54C9d64F86C' + }, + { + symbol: 'Pepe', + l2CustomAddr: '', + l1Address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933', + l2Address: '0x35E6A59F786d9266c7961eA28c7b768B33959cbB' + }, + { + symbol: 'cbBTC', + l2CustomAddr: '', + l1Address: '0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf', + l2Address: '0x4A605F93288e95db40cE72934b888641D9689a48' + }, + { + symbol: 'NST', + l2CustomAddr: '', + l1Address: '0x70Bef3bB2f001dA2fDDb207dAe696cD9FAFf3f5d', + l2Address: '0xd5A1a674F0DA33A4147a8Cd96143E598e738c7FF' } ], [ChainId.ArbitrumNova]: [] diff --git a/packages/arb-token-bridge-ui/src/util/WithdrawalUtils.ts b/packages/arb-token-bridge-ui/src/util/WithdrawalUtils.ts index ac29f2c02c..7d14ef12e6 100644 --- a/packages/arb-token-bridge-ui/src/util/WithdrawalUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/WithdrawalUtils.ts @@ -10,8 +10,11 @@ import { GasEstimates } from '../hooks/arbTokenBridge.types' import { Address } from './AddressUtils' import { captureSentryErrorWithExtraData } from './SentryUtils' import { getBridgeUiConfigForChain } from './bridgeUiConfig' -import { isNetwork } from './networks' - +import { + getBlockNumberReferenceChainIdByChainId, + getConfirmPeriodBlocks, + getL1BlockTime +} from './networks' export async function withdrawInitTxEstimateGas({ amount, address, @@ -111,22 +114,26 @@ export async function withdrawInitTxEstimateGas({ const SECONDS_IN_MINUTE = 60 const SECONDS_IN_HOUR = 3600 const SECONDS_IN_DAY = 86400 -const DEFAULT_CONFIRMATION_TIME = 7 * SECONDS_IN_DAY -const DEFAULT_TESTNET_CONFIRMATION_TIME = SECONDS_IN_HOUR +/** + * Buffer for after a node is confirmable but isn't yet confirmed. + * A rollup block (RBlock) typically gets asserted every 30-60 minutes. + */ +const CONFIRMATION_BUFFER_MINUTES = 60 function formatDuration(seconds: number, short = false): string { if (seconds < SECONDS_IN_MINUTE) { - return `${seconds} ${short ? 'secs' : 'seconds'}` + return `${seconds} ${short ? 'secs' : seconds === 1 ? 'second' : 'seconds'}` } if (seconds < SECONDS_IN_HOUR) { - return `${Math.round(seconds / SECONDS_IN_MINUTE)} ${ - short ? 'mins' : 'minutes' - }` + const minutes = Math.round(seconds / SECONDS_IN_MINUTE) + return `${minutes} ${short ? 'mins' : minutes === 1 ? 'minute' : 'minutes'}` } if (seconds < SECONDS_IN_DAY) { - return `${Math.round(seconds / SECONDS_IN_HOUR)} hours` + const hours = Math.round(seconds / SECONDS_IN_HOUR) + return `${hours} ${hours === 1 ? 'hour' : 'hours'}` } - return `${Math.round(seconds / SECONDS_IN_DAY)} days` + const days = Math.round(seconds / SECONDS_IN_DAY) + return `${days} ${days === 1 ? 'day' : 'days'}` } /** @@ -135,7 +142,6 @@ function formatDuration(seconds: number, short = false): string { */ export function getConfirmationTime(chainId: number) { const { fastWithdrawalTime } = getBridgeUiConfigForChain(chainId) - const isTestnet = isNetwork(chainId).isTestnet const fastWithdrawalActive = typeof fastWithdrawalTime !== 'undefined' @@ -144,9 +150,16 @@ export function getConfirmationTime(chainId: number) { if (fastWithdrawalActive) { confirmationTimeInSeconds = fastWithdrawalTime / 1000 } else { - confirmationTimeInSeconds = isTestnet - ? DEFAULT_TESTNET_CONFIRMATION_TIME - : DEFAULT_CONFIRMATION_TIME + // Calculate confirmation period using block time from root chain: + // - Ethereum mainnet for Arbitrum chains + // - Parent chain for Base chains + const blockNumberReferenceChainId = getBlockNumberReferenceChainIdByChainId( + { chainId } + ) + confirmationTimeInSeconds = + getL1BlockTime(blockNumberReferenceChainId) * + getConfirmPeriodBlocks(chainId) + + CONFIRMATION_BUFFER_MINUTES * SECONDS_IN_MINUTE } const confirmationTimeInReadableFormat = formatDuration( diff --git a/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts b/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts index fe0e2f7952..445272a7f7 100644 --- a/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts +++ b/packages/arb-token-bridge-ui/src/util/__tests__/networks.test.ts @@ -1,11 +1,11 @@ import { registerCustomArbitrumNetwork } from '@arbitrum/sdk' import { - ChainId, getBlockNumberReferenceChainIdByChainId, getDestinationChainIds, getSupportedChainIds } from '../networks' +import { ChainId } from '../../types/ChainId' import { orbitTestnets } from '../orbitChainsList' const xaiTestnetChainId = 37714555429 diff --git a/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts b/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts index d7d4461918..03d8d03695 100644 --- a/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts +++ b/packages/arb-token-bridge-ui/src/util/bridgeUiConfig.ts @@ -1,4 +1,5 @@ -import { ChainId, getCustomChainFromLocalStorageById } from './networks' +import { getCustomChainFromLocalStorageById } from './networks' +import { ChainId } from '../types/ChainId' import { orbitChains, BridgeUiConfig } from './orbitChainsList' export function getBridgeUiConfigForChain(chainId: number): BridgeUiConfig { diff --git a/packages/arb-token-bridge-ui/src/util/cctp/fetchCCTP.ts b/packages/arb-token-bridge-ui/src/util/cctp/fetchCCTP.ts index bddf51db87..7b19be6214 100644 --- a/packages/arb-token-bridge-ui/src/util/cctp/fetchCCTP.ts +++ b/packages/arb-token-bridge-ui/src/util/cctp/fetchCCTP.ts @@ -1,4 +1,4 @@ -import { ChainId } from '../networks' +import { ChainId } from '../../types/ChainId' import { getAPIBaseUrl, sanitizeQueryParams } from '..' import { CompletedCCTPTransfer, diff --git a/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts b/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts index b2ec571855..17acc4dd64 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/fetchDeposits.ts @@ -6,7 +6,7 @@ import { FetchDepositsFromSubgraphResult } from './fetchDepositsFromSubgraph' import { AssetType } from '../../hooks/arbTokenBridge.types' -import { Transaction } from '../../hooks/useTransactions' +import { Transaction } from '../../types/Transactions' import { defaultErc20Decimals } from '../../defaults' import { fetchNativeCurrency } from '../../hooks/useNativeCurrency' import { diff --git a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts index 53781fd739..bdd6cc93ec 100644 --- a/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/deposits/helpers.ts @@ -18,7 +18,7 @@ import { Transaction, TxnStatus, TeleporterTransaction -} from '../../hooks/useTransactions' +} from '../../types/Transactions' import { fetchErc20Data } from '../TokenUtils' import { getL2ConfigForTeleport, @@ -31,15 +31,12 @@ import { normalizeTimestamp } from '../../state/app/utils' -export const updateAdditionalDepositData = async ({ - depositTx, - parentProvider, - childProvider -}: { +export const updateAdditionalDepositData = async ( depositTx: Transaction - parentProvider: Provider - childProvider: Provider -}): Promise => { +): Promise => { + const parentProvider = getProviderForChainId(depositTx.parentChainId) + const childProvider = getProviderForChainId(depositTx.childChainId) + // 1. for all the fetched txns, fetch the transaction receipts and update their exact status // 2. on the basis of those, finally calculate the status of the transaction diff --git a/packages/arb-token-bridge-ui/src/util/fastBridges.ts b/packages/arb-token-bridge-ui/src/util/fastBridges.ts index 31e258336c..45f17f6c77 100644 --- a/packages/arb-token-bridge-ui/src/util/fastBridges.ts +++ b/packages/arb-token-bridge-ui/src/util/fastBridges.ts @@ -10,7 +10,7 @@ import Wormhole from '@/images/bridge/wormhole.svg' // import LIFI from '@/images/bridge/lifi.webp' import Router from '@/images/bridge/router.webp' -import { ChainId } from './networks' +import { ChainId } from '../types/ChainId' import { USDC_LEARN_MORE_LINK } from '../constants' export enum FastBridgeNames { diff --git a/packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts b/packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts index ce13b2be68..ed19d16c55 100644 --- a/packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts +++ b/packages/arb-token-bridge-ui/src/util/fetchL2Gateways.ts @@ -18,18 +18,7 @@ import { export async function fetchL2Gateways(l2Provider: Provider) { const l2Network = await getArbitrumNetwork(l2Provider) - if (!l2Network.tokenBridge) { - return [] - } - - /* configure gateway addresses for fetching withdrawals */ - const { childErc20Gateway, childCustomGateway, childWethGateway } = - l2Network.tokenBridge - const gatewaysToUse = [ - childErc20Gateway, - childCustomGateway, - childWethGateway - ] + const gatewaysToUse = [] const l2ArbReverseGateway = l2ArbReverseGatewayAddresses[l2Network.chainId] const l2DaiGateway = l2DaiGatewayAddresses[l2Network.chainId] const l2wstETHGateway = l2wstETHGatewayAddresses[l2Network.chainId] diff --git a/packages/arb-token-bridge-ui/src/util/index.ts b/packages/arb-token-bridge-ui/src/util/index.ts index 4e96205668..6844b28394 100644 --- a/packages/arb-token-bridge-ui/src/util/index.ts +++ b/packages/arb-token-bridge-ui/src/util/index.ts @@ -13,13 +13,6 @@ export const sanitizeImageSrc = (url: string): string => { return url } -export function preloadImages(imageSources: string[]) { - imageSources.forEach(imageSrc => { - const image = new Image() - image.src = imageSrc - }) -} - export const loadEnvironmentVariableWithFallback = ({ env, fallback diff --git a/packages/arb-token-bridge-ui/src/util/infura.ts b/packages/arb-token-bridge-ui/src/util/infura.ts index 20fa95514d..dfe3b4d48a 100644 --- a/packages/arb-token-bridge-ui/src/util/infura.ts +++ b/packages/arb-token-bridge-ui/src/util/infura.ts @@ -1,7 +1,7 @@ import { providers } from 'ethers' import { Chain, ChainProviderFn } from 'wagmi' -import { ChainId } from './networks' +import { ChainId } from '../types/ChainId' // custom implementation based on https://github.com/wevm/wagmi/blob/wagmi%400.12.13/packages/core/src/providers/infura.ts // with multiple infura keys support diff --git a/packages/arb-token-bridge-ui/src/util/networks.ts b/packages/arb-token-bridge-ui/src/util/networks.ts index 813f5fecf5..ee6eaa341e 100644 --- a/packages/arb-token-bridge-ui/src/util/networks.ts +++ b/packages/arb-token-bridge-ui/src/util/networks.ts @@ -1,4 +1,4 @@ -import { StaticJsonRpcProvider } from '@ethersproject/providers' +import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers' import { ArbitrumNetwork, getChildrenForNetwork, @@ -10,27 +10,10 @@ import { import { loadEnvironmentVariableWithFallback } from './index' import { getBridgeUiConfigForChain } from './bridgeUiConfig' import { fetchErc20Data } from './TokenUtils' +import { orbitChains } from './orbitChainsList' +import { ChainId } from '../types/ChainId' import { getRpcUrl } from './rpc/getRpcUrl' -export enum ChainId { - // L1 - Ethereum = 1, - // L1 Testnets - Local = 1337, - Sepolia = 11155111, - Holesky = 17000, - // L2 - ArbitrumOne = 42161, - ArbitrumNova = 42170, - Base = 8453, - // L2 Testnets - ArbitrumSepolia = 421614, - ArbitrumLocal = 412346, - BaseSepolia = 84532, - // L3 Testnets - L3Local = 333333 -} - /** The network that you reference when calling `block.number` in solidity */ type BlockNumberReferenceNetwork = { chainId: ChainId @@ -587,6 +570,16 @@ export function getSupportedChainIds({ }) } +export function isAlchemyChain(chainId: number) { + const chain = orbitChains[chainId] + + if (typeof chain === 'undefined') { + return false + } + + return chain.rpcUrl.toLowerCase().includes('alchemy.com') +} + export function mapCustomChainToNetworkData(chain: ChainWithRpcUrl) { // custom chain details need to be added to various objects to make it work with the UI // diff --git a/packages/arb-token-bridge-ui/src/util/orbitChainsData.json b/packages/arb-token-bridge-ui/src/util/orbitChainsData.json index 259c700f50..de7701c857 100644 --- a/packages/arb-token-bridge-ui/src/util/orbitChainsData.json +++ b/packages/arb-token-bridge-ui/src/util/orbitChainsData.json @@ -435,7 +435,8 @@ "name": "SX Network", "symbol": "SX", "logoUrl": "/images/sxTokenLogo.png" - } + }, + "fastWithdrawalTime": 43200000 } }, { @@ -670,7 +671,8 @@ "name": "EDU", "symbol": "EDU", "logoUrl": "/images/EduChainTokenLogo.png" - } + }, + "fastWithdrawalTime": 3600000 } }, { @@ -862,6 +864,147 @@ }, "fastWithdrawalTime": 900000 } + }, + { + "chainId": 88899, + "confirmPeriodBlocks": 274908, + "ethBridge": { + "bridge": "0x80b4c2dBEacFF9921cD456e5E1489919185b8a1d", + "inbox": "0x319a9c8be1CF9ECB16D29d6327A6Fa2e26Bf42BC", + "outbox": "0xd9AD042DCb18628a941C8868bf25e5eCf897E48c", + "rollup": "0xd6df93D93c2554412622C567965DA7B16929f0A5", + "sequencerInbox": "0x3A2898B10c88cb619635efdC027538D7Aa99BF79" + }, + "nativeToken": "0xA6C6ea2e0140849bE02A3a34780CF61b766916c5", + "explorerUrl": "https://unite-mainnet.explorer.alchemy.com/", + "rpcUrl": "https://unite-mainnet.g.alchemy.com/public", + "isCustom": true, + "isTestnet": false, + "name": "Unite Mainnet", + "slug": "unite-mainnet", + "parentChainId": 8453, + "tokenBridge": { + "parentCustomGateway": "0xeA2Ffb3BA907418a5a9A9Cdb90212c814230Ba5d", + "parentErc20Gateway": "0x03b9C7978143A3A360cb37101F2A3f7f1B0eE8e8", + "parentGatewayRouter": "0xbaD53A171A9D0785EC179b5C3eFd3d7083b8D3a7", + "parentMultiCall": "0xb444317D808b5cFfE66495920D40A35E7D247cC4", + "parentProxyAdmin": "0x0000000000000000000000000000000000000000", + "parentWeth": "0x0000000000000000000000000000000000000000", + "parentWethGateway": "0x0000000000000000000000000000000000000000", + "childCustomGateway": "0xa123C9a36e7162686197B92800f62573E69FE379", + "childErc20Gateway": "0x012595d521139F2Df03F0666a09f08caC99C4b1B", + "childGatewayRouter": "0x3224F78EC97bB795D16D5954Aa9e408f882520B2", + "childMultiCall": "0x95FdfA72d6C87968da0f38ac17a847203838f56C", + "childProxyAdmin": "0x0000000000000000000000000000000000000000", + "childWeth": "0x0000000000000000000000000000000000000000", + "childWethGateway": "0x0000000000000000000000000000000000000000" + }, + "bridgeUiConfig": { + "color": "#7aad00", + "network": { + "name": "Unite Mainnet", + "logo": "/images/unite-mainnet_Logo.png" + }, + "nativeTokenData": { + "name": "Unite", + "symbol": "UNITE", + "logoUrl": "/images/unite-mainnet_Logo.png" + } + } + }, + { + "chainId": 149, + "confirmPeriodBlocks": 45818, + "ethBridge": { + "bridge": "0xa2ed3b6B4069295F5C89F60B61a196dE09741fb6", + "inbox": "0xBC0862181F93f2fef61Cd5Edd4C7D8C5cCDb76AE", + "outbox": "0x51229C00f364F40dF07C4e71de02e6D16Ffb1aEa", + "rollup": "0x1210118afD15EF90CA4f83cF65dc659876299e7A", + "sequencerInbox": "0xf80Ce375724AF270D23526B93c37E9B17A64bD8a" + }, + "nativeToken": "0x7777778E9199FBC424b7494D55D26579c011E8c6", + "explorerUrl": "https://socialnetwork-mainnet.explorer.alchemy.com/", + "rpcUrl": "https://socialnetwork-mainnet.g.alchemy.com/public", + "isCustom": true, + "isTestnet": false, + "name": "Social Network", + "slug": "social-network", + "parentChainId": 1, + "tokenBridge": { + "parentCustomGateway": "0xE32125cE1A8fFf30B5c0bB164B7E81816CA626F6", + "parentErc20Gateway": "0xB2e209Af16f087D2c985fFcee64EcabEF10A149F", + "parentGatewayRouter": "0x7eAc61b1477906a94e35F5efFAC24305C1654A19", + "parentMultiCall": "0x7cdCB0Cc61f47B8Dd8f47C5A29edaDd84a1BDf5e", + "parentProxyAdmin": "0x0000000000000000000000000000000000000000", + "parentWeth": "0x0000000000000000000000000000000000000000", + "parentWethGateway": "0x0000000000000000000000000000000000000000", + "childCustomGateway": "0xE00D3DBD78aE6D674bE410Eb4828611897579746", + "childErc20Gateway": "0x33888CC3084689e2929277488D6D4f20a90B83BB", + "childGatewayRouter": "0x5cCcde8552d6eb4f27313766fe4D590b4677fbF8", + "childMultiCall": "0x7Cab274d056ec9CdB4231B30a36D36FD789DF9C3", + "childProxyAdmin": "0x0000000000000000000000000000000000000000", + "childWeth": "0x0000000000000000000000000000000000000000", + "childWethGateway": "0x0000000000000000000000000000000000000000" + }, + "bridgeUiConfig": { + "color": "#12266B", + "network": { + "name": "Social Network", + "logo": "/images/SocialMainnet_Logo.webp" + }, + "nativeTokenData": { + "name": "Earth", + "symbol": "Earth" + } + } + }, + { + "chainId": 140, + "confirmPeriodBlocks": 1081, + "ethBridge": { + "bridge": "0x77778b624B03D5D41FeC06641629BB3C98D94892", + "inbox": "0x1CB71Be210c6a0BE2b95B6FBD09b1a43Ae679f97", + "outbox": "0xf32E5481acde9E41cdD0EFAc6cDe3C7876cf65b3", + "rollup": "0xeD6F7916Da27b1A4680EB0C63aA3C768bCc10EDA", + "sequencerInbox": "0x7F17f54174c07F9213dc0EF9009c082542549A9C" + }, + "nativeToken": "0x6e6C39CAC539Ab057d357d16d69Fd04d2b0fc38f", + "explorerUrl": "https://explorer.data-lake.co/", + "rpcUrl": "https://rpc.data-lake.co/", + "isCustom": true, + "isTestnet": false, + "name": "Data Lake Mainnet", + "slug": "data-lake-mainnet", + "parentChainId": 42170, + "tokenBridge": { + "parentCustomGateway": "0xB6725767227Aa858b112a4E563a7D357eddbECEa", + "parentErc20Gateway": "0x1fbdB03f9fe57Cf0049e0F238984C7a9cf4D8ca1", + "parentGatewayRouter": "0x63a9cd1aF4d8E254470E32F9D11FA516fdCc4a14", + "parentMultiCall": "0x23d1171380e3cbAA161F6EB31e3A3c6B548A467a", + "parentProxyAdmin": "0x0000000000000000000000000000000000000000", + "parentWeth": "0x0000000000000000000000000000000000000000", + "parentWethGateway": "0x0000000000000000000000000000000000000000", + "childCustomGateway": "0x0E13f3E7828925F31D3FE3e8Aa7343Cb95a78525", + "childErc20Gateway": "0x3b0825034A4F5b725990df6621EbE09C1ecb24dB", + "childGatewayRouter": "0x71e8d7eC6311Bbf7654E54115FD2c23d8C10Ff5f", + "childMultiCall": "0x8c4a651c207c63fd388574FeEcE51AAFD23f69a7", + "childProxyAdmin": "0x0000000000000000000000000000000000000000", + "childWeth": "0x0000000000000000000000000000000000000000", + "childWethGateway": "0x0000000000000000000000000000000000000000" + }, + "bridgeUiConfig": { + "color": "#876D9C", + "network": { + "name": "Data Lake Mainnet", + "logo": "/images/DataLakeMainnet_Logo.png", + "description": "The Data Lake Chain is a pioneering Layer 3 blockchain designed to empower decentralized science (DeSci) and healthcare research. Built on Arbitrum Orbit, the Data Lake Chain is tailored for secure and compliant management of consents." + }, + "nativeTokenData": { + "name": "Data Lake Token", + "symbol": "LAKE", + "logoUrl": "/images/DataLakeMainnet_NativeTokenLogo.png" + } + } } ], "testnet": [ @@ -1284,6 +1427,95 @@ "logoUrl": "/images/UsdcLogo.svg" } } + }, + { + "chainId": 888991, + "confirmPeriodBlocks": 900, + "ethBridge": { + "bridge": "0xA1AF96d8315DCa65F2ff6f8170Fa3e5dB0A6aD3C", + "inbox": "0xCA323c4C0A04109F303e3bfd879AEd5F49c9f0C4", + "outbox": "0x54a8712d8d5f8D3cD29737d06734eE493758B7cb", + "rollup": "0xB2Ff332B564DA65EDd2FDcbf298458837c732997", + "sequencerInbox": "0x0b172ba2776C08F6c3147ed2471A32EC4592812B" + }, + "nativeToken": "0xf7a942db06a0cd6945ba4635c46e31c54ed175a4", + "explorerUrl": "https://unite-testnet.explorer.alchemy.com/", + "rpcUrl": "https://unite-testnet.g.alchemy.com/public", + "isCustom": true, + "isTestnet": true, + "name": "Unite Testnet", + "slug": "unite-testnet", + "parentChainId": 84532, + "tokenBridge": { + "parentCustomGateway": "0xb7B03E20ca71c6036F8F841cCc213e7CaC3e9868", + "parentErc20Gateway": "0x98614745322365Ae378Db2A6319F873A4775398C", + "parentGatewayRouter": "0xA8f3C0d5eC3E9ACd3422cBf5f25c6C4e608c2499", + "parentMultiCall": "0xE0753Df74d86D6B25aCd2d049389c7E52e2dd728", + "parentProxyAdmin": "0x0000000000000000000000000000000000000000", + "parentWeth": "0x0000000000000000000000000000000000000000", + "parentWethGateway": "0x0000000000000000000000000000000000000000", + "childCustomGateway": "0x01a902C5b160F0c786aDfc702799D25218d8A9B4", + "childErc20Gateway": "0xaABa25Ba10B9aFEf17D403B4038886e7FD743E83", + "childGatewayRouter": "0xdbAEDA962D2D201F65c06C81Dd5A0FC948FB3A20", + "childMultiCall": "0x7df4290268655542E0B39465820a39C4b091D77e", + "childProxyAdmin": "0x0000000000000000000000000000000000000000", + "childWeth": "0x0000000000000000000000000000000000000000", + "childWethGateway": "0x0000000000000000000000000000000000000000" + }, + "bridgeUiConfig": { + "color": "#7aad00", + "network": { + "name": "Unite Testnet", + "logo": "/images/unite-testnet_Logo.png" + }, + "nativeTokenData": { + "name": "Unite", + "symbol": "UNITE", + "logoUrl": "/images/unite-testnet_Logo.png" + } + } + }, + { + "chainId": 98864, + "confirmPeriodBlocks": 60, + "ethBridge": { + "bridge": "0xF3BE11Ead404740017e4839712Af28e3c0b5C202", + "inbox": "0xDe66d6a8BabE07f6838ea712e708BBD47837de52", + "outbox": "0xE8806C827217b9C1D9610b5334254Da4d143dF3D", + "rollup": "0x7404f23fc2189e090E2342FaaF7f20efa7bD366a", + "sequencerInbox": "0xDcAb173C5D846d686856A2D3300B8a64ba12130D" + }, + "explorerUrl": "https://test-explorer.plumenetwork.xyz/", + "rpcUrl": "https://test-rpc.plumenetwork.xyz/", + "isCustom": true, + "isTestnet": true, + "name": "Plume Devnet", + "slug": "plume-devnet", + "parentChainId": 11155111, + "tokenBridge": { + "parentCustomGateway": "0xa8168b76Aa023228CaA8Ef5110477cD6d02F1508", + "parentErc20Gateway": "0xD7A43a671c177Ed55D2B4bCaFc597Bb669Ce4B99", + "parentGatewayRouter": "0x0a4671a34D411868852be59cCa137D5f251dEe52", + "parentMultiCall": "0x73465577E9FD7Cd585E4270F23A9eBa99B92b6eD", + "parentProxyAdmin": "0x0000000000000000000000000000000000000000", + "parentWeth": "0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9", + "parentWethGateway": "0x8a3f580894fF2C51E202Da7712f5550533a956B1", + "childCustomGateway": "0xA7E44Ce807F488791c6Bc8877c9CA7b36c68B9C0", + "childErc20Gateway": "0xc9F79584303fDcb2cf5d147D7Dc79c38EA3BEC11", + "childGatewayRouter": "0x52a0A8a9A65F90E160940972A0de15491172db87", + "childMultiCall": "0xA569f0e5e284A45F6155cbc4C904ad3cae0a82b0", + "childProxyAdmin": "0x0000000000000000000000000000000000000000", + "childWeth": "0x1738E5247c85f96c9D35FE55800557C5479b7063", + "childWethGateway": "0xc260574cD5F7469d9a840f85A6648F74b7Bd4097" + }, + "bridgeUiConfig": { + "color": "#F43B3A", + "network": { + "name": "Plume Devnet", + "logo": "/images/PlumeDevnet_Logo.png", + "description": "Bringing the real world onchain. Optimized for real world assets." + } + } } ] } diff --git a/packages/arb-token-bridge-ui/src/util/teleports/helpers.ts b/packages/arb-token-bridge-ui/src/util/teleports/helpers.ts index dbab4fd3bf..af7da8a792 100644 --- a/packages/arb-token-bridge-ui/src/util/teleports/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/teleports/helpers.ts @@ -10,7 +10,7 @@ import { MergedTransaction } from '../../state/app/state' import { FetchEthTeleportsFromSubgraphResult } from './fetchEthTeleportsFromSubgraph' import { TeleportFromSubgraph } from './fetchTeleports' import { AssetType } from '../../hooks/arbTokenBridge.types' -import { Transaction } from '../../hooks/useTransactions' +import { Transaction } from '../../types/Transactions' import { transformDeposit } from '../../state/app/utils' import { updateAdditionalDepositData } from '../deposits/helpers' import { fetchErc20Data } from '../TokenUtils' @@ -70,14 +70,7 @@ export async function transformTeleportFromSubgraph( childChainId: Number(tx.childChainId) } as Transaction - const childProvider = getProviderForChainId(Number(tx.childChainId)) - return transformDeposit( - await updateAdditionalDepositData({ - depositTx, - parentProvider, - childProvider - }) - ) + return transformDeposit(await updateAdditionalDepositData(depositTx)) } // Erc20 transfers @@ -112,12 +105,5 @@ export async function transformTeleportFromSubgraph( childChainId: l3ChainId } as Transaction - const childProvider = getProviderForChainId(l3ChainId) - return transformDeposit( - await updateAdditionalDepositData({ - depositTx, - parentProvider, - childProvider - }) - ) + return transformDeposit(await updateAdditionalDepositData(depositTx)) } diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts b/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts index ad75742cc3..172e185d3c 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/getWagmiChain.ts @@ -13,7 +13,8 @@ import { baseSepolia, base } from './wagmiAdditionalNetworks' -import { ChainId, getCustomChainFromLocalStorageById } from '../networks' +import { getCustomChainFromLocalStorageById } from '../networks' +import { ChainId } from '../../types/ChainId' import { orbitChains } from '../orbitChainsList' export function getWagmiChain(chainId: number): Chain { diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts index 4b748a2080..179f8ead9b 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/setup.ts @@ -17,7 +17,8 @@ import { baseSepolia } from './wagmiAdditionalNetworks' import { isTestingEnvironment } from '../CommonUtils' -import { getCustomChainsFromLocalStorage, ChainId, rpcURLs } from '../networks' +import { getCustomChainsFromLocalStorage, rpcURLs } from '../networks' +import { ChainId } from '../../types/ChainId' import { getOrbitChains } from '../orbitChainsList' import { getWagmiChain } from './getWagmiChain' import { customInfuraProvider } from '../infura' @@ -148,14 +149,13 @@ export function getProps(targetChainKey: string | null) { chains }) + wallets[0]?.wallets.push(okxWallet({ chains, projectId })) + const connectors = connectorsForWallets([ ...wallets, { groupName: 'More', - wallets: [ - trustWallet({ chains, projectId }), - okxWallet({ chains, projectId }) - ] + wallets: [trustWallet({ chains, projectId })] } ]) diff --git a/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts b/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts index 8c9228e5cd..0d0e867606 100644 --- a/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts +++ b/packages/arb-token-bridge-ui/src/util/wagmi/wagmiAdditionalNetworks.ts @@ -1,7 +1,8 @@ import { Chain, sepolia as sepoliaDefault } from 'wagmi' import { ether } from '../../constants' -import { ChainId, ChainWithRpcUrl, explorerUrls, rpcURLs } from '../networks' +import { ChainWithRpcUrl, explorerUrls, rpcURLs } from '../networks' +import { ChainId } from '../../types/ChainId' import { getBridgeUiConfigForChain } from '../bridgeUiConfig' import { NativeCurrencyBase } from '../../hooks/useNativeCurrency' diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts index a9955e6dc8..6550370a86 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchETHWithdrawalsFromEventLogs.ts @@ -5,7 +5,7 @@ import { ChildToParentMessageReader } from '@arbitrum/sdk' * Fetches initiated ETH withdrawals from event logs in range of [fromBlock, toBlock]. * * @param query Query params - * @param query.receiver Address that will receive the funds + * @param query.receiver Address that received the funds * @param query.fromBlock Start at this block number (including) * @param query.toBlock Stop at this block number (including) * @param query.l2Provider Provider for the L2 network @@ -22,9 +22,10 @@ export function fetchETHWithdrawalsFromEventLogs({ l2Provider: Provider }) { if (typeof receiver === 'undefined') { - return [] + return Promise.resolve([]) } - // funds sent by this address + + // funds received by this address return ChildToParentMessageReader.getChildToParentEvents( l2Provider, { fromBlock, toBlock }, diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts index 0b3d9531e3..8296e2085f 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogs.ts @@ -10,6 +10,15 @@ function dedupeEvents( return [...new Map(events.map(item => [item.txHash, item])).values()] } +export type FetchTokenWithdrawalsFromEventLogsParams = { + sender?: string + receiver?: string + fromBlock: BlockTag + toBlock: BlockTag + l2Provider: Provider + l2GatewayAddresses?: string[] +} + /** * Fetches initiated token withdrawals from event logs in range of [fromBlock, toBlock]. * @@ -28,14 +37,7 @@ export async function fetchTokenWithdrawalsFromEventLogs({ toBlock, l2Provider, l2GatewayAddresses = [] -}: { - sender?: string - receiver?: string - fromBlock: BlockTag - toBlock: BlockTag - l2Provider: Provider - l2GatewayAddresses?: string[] -}) { +}: FetchTokenWithdrawalsFromEventLogsParams) { const erc20Bridger = await Erc20Bridger.fromProvider(l2Provider) const promises: ReturnType[] = [] diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogsSequentially.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogsSequentially.ts new file mode 100644 index 0000000000..3e421ca077 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchTokenWithdrawalsFromEventLogsSequentially.ts @@ -0,0 +1,115 @@ +import { constants } from 'ethers' +import { Provider, BlockTag } from '@ethersproject/providers' +import { Erc20Bridger } from '@arbitrum/sdk' + +import { + fetchTokenWithdrawalsFromEventLogs, + FetchTokenWithdrawalsFromEventLogsParams +} from './fetchTokenWithdrawalsFromEventLogs' + +import { backOff, wait } from '../ExponentialBackoffUtils' + +type FetchTokenWithdrawalsFromEventLogsQuery = { + params: FetchTokenWithdrawalsFromEventLogsParams + priority: number +} + +export type Query = { + sender?: string + receiver?: string + gateways?: string[] +} + +export type FetchTokenWithdrawalsFromEventLogsSequentiallyParams = { + sender?: string + receiver?: string + provider: Provider + fromBlock?: BlockTag + toBlock?: BlockTag + /** + * How long to delay in-between queries of different priority. Defaults to 0. + */ + delayMs?: number + queries: Query[] +} + +export type FetchTokenWithdrawalsFromEventLogsSequentiallyResult = Awaited< + ReturnType +> + +export async function fetchTokenWithdrawalsFromEventLogsSequentially({ + provider, + fromBlock = 0, + toBlock = 'latest', + delayMs = 0, + queries: queriesProp +}: FetchTokenWithdrawalsFromEventLogsSequentiallyParams): Promise { + // keep track of priority; increment as queries are added + let priority = 0 + + // keep track of queries + const queries: FetchTokenWithdrawalsFromEventLogsQuery[] = [] + + // helper function to reuse common params + function buildQueryParams({ + sender, + receiver, + gateways = [] + }: Query): FetchTokenWithdrawalsFromEventLogsQuery['params'] { + return { + sender, + receiver, + fromBlock, + toBlock, + l2Provider: provider, + l2GatewayAddresses: gateways + } + } + + // for sanitizing, adding queries and incrementing priority + function addQuery(params: FetchTokenWithdrawalsFromEventLogsQuery['params']) { + const gateways = params.l2GatewayAddresses ?? [] + const gatewaysSanitized = gateways.filter(g => g !== constants.AddressZero) + + if (gatewaysSanitized.length === 0) { + return + } + + queries.push({ + params: { ...params, l2GatewayAddresses: gatewaysSanitized }, + priority: ++priority + }) + } + + queriesProp.forEach(query => { + addQuery(buildQueryParams(query)) + }) + + // for iterating through all priorities in the while loop below + let currentPriority = 1 + + // final result + const result: FetchTokenWithdrawalsFromEventLogsSequentiallyResult = [] + + while (currentPriority <= priority) { + const currentPriorityQueries = queries.filter( + query => query.priority === currentPriority + ) + + const currentPriorityResults = await Promise.all( + currentPriorityQueries.map(query => + backOff(() => fetchTokenWithdrawalsFromEventLogs(query.params)) + ) + ) + + currentPriorityResults.forEach(r => { + result.push(...r) + }) + + await wait(delayMs) + + currentPriority++ + } + + return result +} diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts index 624182bdae..aba40e7207 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/fetchWithdrawals.ts @@ -7,11 +7,41 @@ import { fetchWithdrawalsFromSubgraph } from './fetchWithdrawalsFromSubgraph' import { fetchLatestSubgraphBlockNumber } from '../SubgraphUtils' -import { fetchTokenWithdrawalsFromEventLogs } from './fetchTokenWithdrawalsFromEventLogs' -import { fetchL2Gateways } from '../fetchL2Gateways' + import { Withdrawal } from '../../hooks/useTransactionHistory' import { attachTimestampToTokenWithdrawal } from './helpers' import { WithdrawalInitiated } from '../../hooks/arbTokenBridge.types' +import { + Query, + fetchTokenWithdrawalsFromEventLogsSequentially +} from './fetchTokenWithdrawalsFromEventLogsSequentially' +import { backOff, wait } from '../ExponentialBackoffUtils' +import { isAlchemyChain } from '../networks' +import { getArbitrumNetwork } from '@arbitrum/sdk' +import { fetchL2Gateways } from '../fetchL2Gateways' +import { constants } from 'ethers' +import { getNonce } from '../AddressUtils' + +async function getGateways(provider: Provider): Promise<{ + standardGateway: string + wethGateway: string + customGateway: string + otherGateways: string[] +}> { + const network = await getArbitrumNetwork(provider) + + const standardGateway = network.tokenBridge?.childErc20Gateway + const customGateway = network.tokenBridge?.childCustomGateway + const wethGateway = network.tokenBridge?.childWethGateway + const otherGateways = await fetchL2Gateways(provider) + + return { + standardGateway: standardGateway ?? constants.AddressZero, + wethGateway: wethGateway ?? constants.AddressZero, + customGateway: customGateway ?? constants.AddressZero, + otherGateways + } +} export type FetchWithdrawalsParams = { sender?: string @@ -43,8 +73,6 @@ export async function fetchWithdrawals({ const l1ChainID = (await l1Provider.getNetwork()).chainId const l2ChainID = (await l2Provider.getNetwork()).chainId - const l2GatewayAddresses = await fetchL2Gateways(l2Provider) - if (!fromBlock) { fromBlock = 0 } @@ -85,23 +113,69 @@ export async function fetchWithdrawals({ console.log('Error fetching withdrawals from subgraph', error) } - const [ethWithdrawalsFromEventLogs, tokenWithdrawalsFromEventLogs] = - await Promise.all([ - fetchETHWithdrawalsFromEventLogs({ - receiver, - fromBlock: toBlock + 1, - toBlock: 'latest', - l2Provider: l2Provider - }), - fetchTokenWithdrawalsFromEventLogs({ - sender, - receiver, - fromBlock: toBlock + 1, - toBlock: 'latest', - l2Provider: l2Provider, - l2GatewayAddresses - }) - ]) + const gateways = await getGateways(l2Provider) + const senderNonce = await getNonce(sender, { provider: l2Provider }) + + const queries: Query[] = [] + + // alchemy as a raas has a global rate limit across their chains, so we have to fetch sequentially and wait in-between requests to work around this + const isAlchemy = isAlchemyChain(l2ChainID) + const delayMs = isAlchemy ? 2_000 : 0 + + const allGateways = [ + gateways.standardGateway, + gateways.wethGateway, + gateways.customGateway, + ...gateways.otherGateways + ] + + // sender queries; only add if nonce > 0 + if (senderNonce > 0) { + if (isAlchemy) { + // for alchemy, fetch sequentially + queries.push({ sender, gateways: [gateways.standardGateway] }) + queries.push({ sender, gateways: [gateways.wethGateway] }) + queries.push({ sender, gateways: [gateways.customGateway] }) + queries.push({ sender, gateways: gateways.otherGateways }) + } else { + // for other chains, fetch in parallel + queries.push({ sender, gateways: allGateways }) + } + } + + if (isAlchemy) { + // for alchemy, fetch sequentially + queries.push({ receiver, gateways: [gateways.standardGateway] }) + queries.push({ receiver, gateways: [gateways.wethGateway] }) + queries.push({ receiver, gateways: [gateways.customGateway] }) + queries.push({ receiver, gateways: gateways.otherGateways }) + } else { + // for other chains, fetch in parallel + queries.push({ receiver, gateways: allGateways }) + } + + const ethWithdrawalsFromEventLogs = await backOff(() => + fetchETHWithdrawalsFromEventLogs({ + receiver, + // not sure why eslint is treating "toBlock" as "number | undefined" here + // even though typescript recognizes it as "number" + fromBlock: toBlock ?? 0 + 1, + toBlock: 'latest', + l2Provider: l2Provider + }) + ) + + await wait(delayMs) + + const tokenWithdrawalsFromEventLogs = + await fetchTokenWithdrawalsFromEventLogsSequentially({ + sender, + receiver, + fromBlock: toBlock + 1, + toBlock: 'latest', + provider: l2Provider, + queries + }) const mappedEthWithdrawalsFromEventLogs: Withdrawal[] = ethWithdrawalsFromEventLogs.map(tx => { diff --git a/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts b/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts index 89ab0369be..dd069b4201 100644 --- a/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts +++ b/packages/arb-token-bridge-ui/src/util/withdrawals/helpers.ts @@ -33,20 +33,6 @@ export type EthWithdrawal = L2ToL1EventResult & { childChainId: number } -export const updateAdditionalWithdrawalData = async ( - withdrawalTx: L2ToL1EventResultPlus, - l1Provider: Provider, - l2Provider: Provider -) => { - const l2toL1TxWithDeadline = await attachNodeBlockDeadlineToEvent( - withdrawalTx as L2ToL1EventResultPlus, - l1Provider, - l2Provider - ) - - return l2toL1TxWithDeadline -} - export async function attachTimestampToTokenWithdrawal({ withdrawal, l2Provider @@ -128,55 +114,6 @@ export async function getOutgoingMessageState( } } -export async function attachNodeBlockDeadlineToEvent( - event: L2ToL1EventResultPlus, - l1Provider: Provider, - l2Provider: Provider -) { - if ( - event.outgoingMessageState === OutgoingMessageState.EXECUTED || - event.outgoingMessageState === OutgoingMessageState.CONFIRMED - ) { - return event - } - - const messageReader = ChildToParentMessageReader.fromEvent(l1Provider, event) - - try { - const firstExecutableBlock = await messageReader.getFirstExecutableBlock( - l2Provider - ) - - return { ...event, nodeBlockDeadline: firstExecutableBlock?.toNumber() } - } catch (e) { - const expectedError = "batch doesn't exist" - const expectedError2 = 'CALL_EXCEPTION' - - const err = e as Error & { error: Error } - const errorMessage = err && (err.message || err.error?.message) - - if (errorMessage.includes(expectedError)) { - const nodeBlockDeadline: NodeBlockDeadlineStatus = - NodeBlockDeadlineStatusTypes.NODE_NOT_CREATED - return { - ...event, - nodeBlockDeadline - } - } else if (errorMessage.includes(expectedError2)) { - // in classic we simulate `executeTransaction` in `hasExecuted` - // which might revert if the L2 to L1 call fail - const nodeBlockDeadline: NodeBlockDeadlineStatus = - NodeBlockDeadlineStatusTypes.EXECUTE_CALL_EXCEPTION - return { - ...event, - nodeBlockDeadline - } - } else { - throw e - } - } -} - export function isTokenWithdrawal( withdrawal: WithdrawalInitiated | EthWithdrawal ): withdrawal is WithdrawalInitiated { diff --git a/packages/arb-token-bridge-ui/src/util/xErc20Utils.ts b/packages/arb-token-bridge-ui/src/util/xErc20Utils.ts index 267763edaf..203a5e1059 100644 --- a/packages/arb-token-bridge-ui/src/util/xErc20Utils.ts +++ b/packages/arb-token-bridge-ui/src/util/xErc20Utils.ts @@ -1,6 +1,6 @@ import { getProviderForChainId } from '@/token-bridge-sdk/utils' import { fetchErc20L2GatewayAddress } from './TokenUtils' -import { ChainId } from './networks' +import { ChainId } from '../types/ChainId' import { TokenWithdrawalApprovalParams } from './L2ApprovalUtils' export const xErc20Gateways: { diff --git a/packages/arb-token-bridge-ui/synpress.config.ts b/packages/arb-token-bridge-ui/synpress.config.ts index 84a332c1b0..31ae417ba1 100644 --- a/packages/arb-token-bridge-ui/synpress.config.ts +++ b/packages/arb-token-bridge-ui/synpress.config.ts @@ -226,7 +226,8 @@ export default defineConfig({ }, baseUrl: 'http://localhost:3000', specPattern: tests, - supportFile: 'tests/support/index.ts' + supportFile: 'tests/support/index.ts', + defaultCommandTimeout: 20_000 } }) diff --git a/packages/arb-token-bridge-ui/tailwind.config.js b/packages/arb-token-bridge-ui/tailwind.config.js index 687d267831..355dd9a77c 100644 --- a/packages/arb-token-bridge-ui/tailwind.config.js +++ b/packages/arb-token-bridge-ui/tailwind.config.js @@ -32,6 +32,11 @@ module.exports = { 'orange-dark': '#60461F', 'lime-dark': '#31572A', + // TRANSACTION STATUS COLORS + claim: '#6AD28A', + retry: '#CD0000', + pending: '#CCB069', + // NEUTRAL (GRAYS) 'gray-1': '#191919', 'gray-2': '#E5E5E5', diff --git a/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts b/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts index 267a499bf2..af0f75e815 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts @@ -16,7 +16,7 @@ import { findGasFeeSummary, findGasFeeForChain, findMoveFundsButton, - startTransfer, + clickMoveFundsButton, findSelectTokenButton, openTransactionDetails, closeTransactionDetails, @@ -25,8 +25,9 @@ import { findClaimButton, selectTransactionsPanelTab, confirmSpending, - closeTransactionHistoryPanel, - claimCctp + claimCctp, + switchToTransferPanelTab, + switchToTransactionHistoryTab } from '../support/commands' import { NetworkType, NetworkName } from '../support/common' @@ -65,11 +66,12 @@ declare global { findGasFeeForChain: typeof findGasFeeForChain findGasFeeSummary: typeof findGasFeeSummary findMoveFundsButton: typeof findMoveFundsButton - startTransfer: typeof startTransfer + clickMoveFundsButton: typeof clickMoveFundsButton findSelectTokenButton: typeof findSelectTokenButton openTransactionDetails: typeof openTransactionDetails closeTransactionDetails: typeof closeTransactionDetails - closeTransactionHistoryPanel: typeof closeTransactionHistoryPanel + switchToTransferPanelTab: typeof switchToTransferPanelTab + switchToTransactionHistoryTab: typeof switchToTransactionHistoryTab findTransactionDetailsCustomDestinationAddress: typeof findTransactionDetailsCustomDestinationAddress findTransactionInTransactionHistory: typeof findTransactionInTransactionHistory findClaimButton: typeof findClaimButton diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts index c96ceb903c..d3d90a3861 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/approveToken.cy.ts @@ -39,7 +39,7 @@ describe('Approve token for deposit', () => { timeout: 50000, interval: 500 }) - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton({ shouldConfirmInMetamask: false }) cy.findByText(/pay a one-time approval fee/).click() cy.findByRole('button', { name: /Pay approval fee of/ @@ -50,7 +50,7 @@ describe('Approve token for deposit', () => { * If confirm spending fails, test is still considered to be passing by Cypress * We add another check to make sure the test fails if needed */ - cy.wait(10_000) + cy.wait(25_000) cy.rejectMetamaskTransaction() }) }) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts index b8661d2da5..0850de87f2 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/batchDeposit.cy.ts @@ -131,7 +131,7 @@ describe('Batch Deposit', () => { } context('should deposit successfully', () => { - cy.startTransfer() + cy.clickMoveFundsButton() cy.findTransactionInTransactionHistory({ ...txData, duration: depositTime @@ -141,38 +141,40 @@ describe('Batch Deposit', () => { context('deposit should complete successfully', () => { cy.selectTransactionsPanelTab('settled') - cy.waitUntil(() => cy.findTransactionInTransactionHistory(txData), { - errorMsg: 'Could not find settled ERC20 Batch Deposit transaction', - timeout: 60_000, - interval: 500 - }) + cy.findTransactionInTransactionHistory(txData) cy.findTransactionInTransactionHistory({ duration: 'a few seconds ago', ...txData }) - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() }) context('funds should reach destination account successfully', () => { // should have more funds on destination chain - cy.findByLabelText(`${ERC20TokenSymbol} balance amount on childChain`) - .invoke('text') - .then(parseFloat) - .should('be.gt', Number(parentErc20Balance)) - cy.findByLabelText(`${nativeTokenSymbol} balance amount on childChain`) - .invoke('text') - .then(parseFloat) - .should( - 'be.gt', + cy.findByLabelText( + `${ERC20TokenSymbol} balance amount on childChain` + ).should($el => { + const currentBalance = parseFloat($el.text()) + expect(currentBalance).to.be.gt(Number(parentErc20Balance)) + }) + + cy.findByLabelText( + `${nativeTokenSymbol} balance amount on childChain` + ).should($el => { + const currentBalance = parseFloat($el.text()) + expect(currentBalance).to.be.gt( Number(parentNativeTokenBalance) + nativeCurrencyAmountToSend ) + }) // the balance on the source chain should not be the same as before - cy.findByLabelText(`${ERC20TokenSymbol} balance amount on parentChain`) - .invoke('text') - .then(parseFloat) - .should('be.lt', Number(parentErc20Balance)) + cy.findByLabelText( + `${ERC20TokenSymbol} balance amount on parentChain` + ).should($el => { + const currentBalance = parseFloat($el.text()) + expect(currentBalance).to.be.lt(Number(parentErc20Balance)) + }) }) context('transfer panel amount should be reset', () => { @@ -236,7 +238,7 @@ describe('Batch Deposit', () => { } context('should deposit successfully', () => { - cy.startTransfer() + cy.clickMoveFundsButton() cy.findTransactionInTransactionHistory({ ...txData, duration: depositTime @@ -251,11 +253,7 @@ describe('Batch Deposit', () => { context('deposit should complete successfully', () => { cy.selectTransactionsPanelTab('settled') - cy.waitUntil(() => cy.findTransactionInTransactionHistory(txData), { - errorMsg: 'Could not find settled ERC20 Batch Deposit transaction', - timeout: 60_000, - interval: 500 - }) + cy.findTransactionInTransactionHistory(txData) cy.findTransactionInTransactionHistory({ duration: 'a few seconds ago', @@ -266,7 +264,7 @@ describe('Batch Deposit', () => { Cypress.env('CUSTOM_DESTINATION_ADDRESS') ) cy.closeTransactionDetails() - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() }) context('transfer panel amount should be reset', () => { diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts index f655a787ce..ff663af7a0 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts @@ -86,7 +86,7 @@ describe('Deposit USDC through CCTP', () => { }) it('should initiate depositing USDC to the same address through CCTP successfully', () => { - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton().click() confirmAndApproveCctpDeposit() cy.confirmSpending(USDCAmountToSend.toString()) @@ -113,7 +113,6 @@ describe('Deposit USDC through CCTP', () => { it('should claim deposit', () => { cy.claimCctp(0.00014, { accept: false }) - cy.closeTransactionHistoryPanel() cy.claimCctp(0.00015, { accept: false }) }) @@ -123,7 +122,7 @@ describe('Deposit USDC through CCTP', () => { */ it.skip('should initiate depositing USDC to custom destination address through CCTP successfully', () => { cy.fillCustomDestinationAddress() - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton().click() confirmAndApproveCctpDeposit() cy.confirmSpending(USDCAmountToSend.toString()) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts index 27894707c3..7deaf23462 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/depositERC20.cy.ts @@ -89,7 +89,7 @@ describe('Deposit Token', () => { }) context('should deposit successfully', () => { - cy.startTransfer() + cy.clickMoveFundsButton() cy.findTransactionInTransactionHistory({ duration: depositTime, amount: ERC20AmountToSend, @@ -98,7 +98,7 @@ describe('Deposit Token', () => { }) context('transfer panel amount should be reset', () => { - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() cy.findAmountInput().should('have.value', '') cy.findMoveFundsButton().should('be.disabled') }) @@ -130,7 +130,7 @@ describe('Deposit Token', () => { }) context('should deposit successfully', () => { - cy.startTransfer() + cy.clickMoveFundsButton() const txData = { amount: ERC20AmountToSend, symbol: testCase.symbol @@ -150,20 +150,11 @@ describe('Deposit Token', () => { // switch to settled transactions cy.selectTransactionsPanelTab('settled') - //wait for some time for tx to go through and find the new amount in settled transactions - cy.waitUntil( - () => - cy.findTransactionInTransactionHistory({ - duration: 'a few seconds ago', - amount: ERC20AmountToSend, - symbol: testCase.symbol - }), - { - errorMsg: 'Could not find settled ERC20 Deposit transaction', - timeout: 60_000, - interval: 500 - } - ) + cy.findTransactionInTransactionHistory({ + duration: 'a few seconds ago', + amount: ERC20AmountToSend, + symbol: testCase.symbol + }) // open the tx details popup const txData = { amount: ERC20AmountToSend, @@ -182,7 +173,7 @@ describe('Deposit Token', () => { context('funds should reach destination account successfully', () => { // close transaction history - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() // the custom destination address should now have some balance greater than zero cy.findByLabelText(`${testCase.symbol} balance amount on childChain`) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/depositNativeToken.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/depositNativeToken.cy.ts index 7a57a14504..65afe0f894 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/depositNativeToken.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/depositNativeToken.cy.ts @@ -31,13 +31,13 @@ describe('Deposit native token', () => { cy.findGasFeeSummary(zeroToLessThanOneEth) cy.findGasFeeForChain(getL1NetworkName(), zeroToLessThanOneEth) cy.findGasFeeForChain(getL2NetworkName(), zeroToLessThanOneNativeToken) - cy.startTransfer() + cy.clickMoveFundsButton() cy.findTransactionInTransactionHistory({ duration: depositTime, amount: ETHAmountToDeposit, symbol: nativeTokenSymbol }) - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() cy.findAmountInput().should('have.value', '') cy.findMoveFundsButton().should('be.disabled') }) @@ -53,7 +53,7 @@ describe('Deposit native token', () => { cy.findGasFeeSummary(zeroToLessThanOneEth) cy.findGasFeeForChain(getL1NetworkName(), zeroToLessThanOneEth) cy.findGasFeeForChain(getL2NetworkName(), zeroToLessThanOneNativeToken) - cy.startTransfer() + cy.clickMoveFundsButton() const txData = { amount: ETHAmountToDeposit, @@ -75,7 +75,7 @@ describe('Deposit native token', () => { ) cy.closeTransactionDetails() - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() cy.findAmountInput().should('have.value', '') cy.findMoveFundsButton().should('be.disabled') }) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/readClassicDeposits.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/readClassicDeposits.cy.ts index 727778240c..fc06ea750a 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/readClassicDeposits.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/readClassicDeposits.cy.ts @@ -46,11 +46,8 @@ describe('Read classic deposit messages', () => { context('User has classic native token deposit transaction', () => { it('can read successful native token deposit', () => { - // log in to metamask - cy.login({ - networkType: 'parentChain', - networkName: 'mainnet' - }) + cy.visit('/') + window.localStorage.setItem( `arbitrum:bridge:deposits-${Cypress.env('ADDRESS').toLowerCase()}`, JSON.stringify([ @@ -61,7 +58,13 @@ describe('Read classic deposit messages', () => { ]) ) - cy.openTransactionsPanel('settled') + // log in to metamask + cy.login({ + networkType: 'parentChain', + networkName: 'mainnet' + }) + + cy.switchToTransactionHistoryTab('settled') const destinationTxHash = '0xd3ff2a70a115411e1ae4917351dca49281368684394d0dcac136fa08d9d9b436' @@ -75,11 +78,8 @@ describe('Read classic deposit messages', () => { context('User has classic ERC-20 deposit transaction', () => { it('can read successful ERC-20 deposit', () => { - // log in to metamask - cy.login({ - networkType: 'parentChain', - networkName: 'mainnet' - }) + cy.visit('/') + window.localStorage.setItem( `arbitrum:bridge:deposits-${Cypress.env('ADDRESS').toLowerCase()}`, JSON.stringify([ @@ -92,7 +92,13 @@ describe('Read classic deposit messages', () => { ]) ) - cy.openTransactionsPanel('settled') + // log in to metamask + cy.login({ + networkType: 'parentChain', + networkName: 'mainnet' + }) + + cy.switchToTransactionHistoryTab('settled') const destinationTxHash = '0x6cecd3bfc3ec73181c4ac0253d3f51e5aa8d26157ca7439ff9ab465de14a436f' diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/redeemRetryable.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/redeemRetryable.cy.ts index 4aea7f5971..0473ee9391 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/redeemRetryable.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/redeemRetryable.cy.ts @@ -69,7 +69,10 @@ describe('Redeem ERC20 Deposit', () => { context('deposit should be redeemed', () => { // open transaction history and wait for deposit to fetch data - cy.openTransactionsPanel('pending') + cy.switchToTransactionHistoryTab('pending') + + // give ci more time to fetch the transactions + cy.wait(15_000) // find the Retry button and the amount in the row cy.findTransactionInTransactionHistory({ @@ -94,7 +97,7 @@ describe('Redeem ERC20 Deposit', () => { symbol: 'WETH' }) - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() // wait for the destination balance to update cy.wait(5_000) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/txHistory.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/txHistory.cy.ts index c9f1c0c53a..e1608e2962 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/txHistory.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/txHistory.cy.ts @@ -13,7 +13,7 @@ describe('Transaction History', () => { }) // open tx history panel context('open transactions history panel', () => { - cy.openTransactionsPanel('pending') + cy.switchToTransactionHistoryTab('pending') cy.findAllByTestId(CLAIMABLE_ROW_IDENTIFIER) .its('length') .should('be.gt', 0) @@ -30,7 +30,7 @@ describe('Transaction History', () => { } }) context('open transactions history panel', () => { - cy.openTransactionsPanel('settled') + cy.switchToTransactionHistoryTab('settled') cy.findAllByTestId(CLAIMABLE_ROW_IDENTIFIER) .its('length') .should('be.gt', 0) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawCctp.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawCctp.cy.ts index e1e062f1c4..15591ee460 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawCctp.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawCctp.cy.ts @@ -72,7 +72,7 @@ describe('Withdraw USDC through CCTP', () => { 'be.visible' ) cy.findGasFeeForChain(/You'll have to pay Sepolia gas fee upon claiming./i) - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton().click() confirmAndApproveCctpWithdrawal() cy.confirmSpending(USDCAmountToSend.toString()) @@ -97,7 +97,6 @@ describe('Withdraw USDC through CCTP', () => { it('should claim deposit', () => { cy.changeMetamaskNetwork('sepolia') cy.claimCctp(0.00012, { accept: true }) - cy.closeTransactionHistoryPanel() cy.claimCctp(0.00013, { accept: true }) }) @@ -110,7 +109,7 @@ describe('Withdraw USDC through CCTP', () => { ) cy.findGasFeeForChain(/You'll have to pay Sepolia gas fee upon claiming./i) cy.fillCustomDestinationAddress() - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton().click() confirmAndApproveCctpWithdrawal() cy.confirmSpending(USDCAmountToSend.toString()) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts index bbc0ff696f..5355a5fa71 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawERC20.cy.ts @@ -103,7 +103,7 @@ describe('Withdraw ERC20 Token', () => { }) context('should show clickable withdraw button', () => { - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton({ shouldConfirmInMetamask: false }) }) context('should withdraw successfully', () => { @@ -142,7 +142,7 @@ describe('Withdraw ERC20 Token', () => { }) context('transfer panel amount should be reset', () => { - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() cy.findAmountInput().should('have.value', '') cy.findMoveFundsButton().should('be.disabled') }) @@ -153,9 +153,7 @@ describe('Withdraw ERC20 Token', () => { cy.login({ networkType: 'parentChain' }) // login to L1 to claim the funds (otherwise would need to change network after clicking on claim) - cy.findByLabelText('Open Transaction History') - .should('be.visible') - .click() + cy.switchToTransactionHistoryTab('pending') cy.findClaimButton( formatAmount(ERC20AmountToSend, { @@ -175,7 +173,7 @@ describe('Withdraw ERC20 Token', () => { })}` ).should('be.visible') - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() cy.searchAndSelectToken({ tokenName: testCase.symbol, @@ -220,7 +218,7 @@ describe('Withdraw ERC20 Token', () => { }) context('should show clickable withdraw button', () => { - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton({ shouldConfirmInMetamask: false }) }) context('should initiate withdrawal successfully', () => { @@ -265,7 +263,7 @@ describe('Withdraw ERC20 Token', () => { // close popup cy.closeTransactionDetails() - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() // the balance on the source chain should not be the same as before cy.findByLabelText(`${testCase.symbol} balance amount on childChain`) diff --git a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts index 6bb2fd751d..21259a3d93 100644 --- a/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts +++ b/packages/arb-token-bridge-ui/tests/e2e/specs/withdrawNativeToken.cy.ts @@ -61,7 +61,7 @@ describe('Withdraw native token', () => { ETHToWithdraw = Number((Math.random() * 0.001).toFixed(5)) // generate a new withdrawal amount for each test-run attempt so that findAllByText doesn't stall coz of prev transactions cy.login({ networkType: 'childChain' }) cy.typeAmount(ETHToWithdraw) - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton({ shouldConfirmInMetamask: false }) cy.findByText(/Arbitrum’s bridge/i).should('be.visible') // the Continue withdrawal button should be disabled at first @@ -96,7 +96,7 @@ describe('Withdraw native token', () => { }) context('transfer panel amount should be reset', () => { - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() cy.findAmountInput().should('have.value', '') cy.findMoveFundsButton().should('be.disabled') }) @@ -106,9 +106,7 @@ describe('Withdraw native token', () => { // increase the timeout for this test as claim button can take ~(20 blocks *10 blocks/sec) to activate cy.login({ networkType: 'parentChain' }) // login to L1 to claim the funds (otherwise would need to change network after clicking on claim) - cy.findByLabelText('Open Transaction History') - .should('be.visible') - .click() + cy.switchToTransactionHistoryTab('pending') cy.findClaimButton( formatAmount(ETHToWithdraw, { @@ -128,7 +126,7 @@ describe('Withdraw native token', () => { })}` ).should('be.visible') - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() // the balance on the destination chain should not be the same as before cy.findByLabelText( @@ -148,7 +146,7 @@ describe('Withdraw native token', () => { cy.typeAmount(ETHToWithdraw) cy.fillCustomDestinationAddress() - cy.findMoveFundsButton().click() + cy.clickMoveFundsButton({ shouldConfirmInMetamask: false }) cy.findByText(/Arbitrum’s bridge/i).should('be.visible') // the Continue withdrawal button should be disabled at first @@ -194,7 +192,7 @@ describe('Withdraw native token', () => { cy.closeTransactionDetails() context('transfer panel amount should be reset', () => { - cy.closeTransactionHistoryPanel() + cy.switchToTransferPanelTab() cy.findAmountInput().should('have.value', '') cy.findMoveFundsButton().should('be.disabled') }) diff --git a/packages/arb-token-bridge-ui/tests/support/commands.ts b/packages/arb-token-bridge-ui/tests/support/commands.ts index 7010ca34d3..64f4367fbe 100644 --- a/packages/arb-token-bridge-ui/tests/support/commands.ts +++ b/packages/arb-token-bridge-ui/tests/support/commands.ts @@ -108,31 +108,6 @@ export const selectTransactionsPanelTab = (tab: 'pending' | 'settled') => { .and('equal', 'selected') } -export const openTransactionsPanel = (tab: 'pending' | 'settled') => { - cy.log(`opening transactions panel on ${tab}`) - cy.findByRole('button', { name: /account header button/i }) - .should('be.visible') - .click() - cy.findByRole('button', { name: /transactions/i }) - .should('be.visible') - .click() - - cy.selectTransactionsPanelTab(tab) - - // Waiting for transactions to be fetched - return cy.waitUntil( - () => - cy - .findByText(/Showing \d+ \w+ transactions made in/) - .should('be.visible'), - { - errorMsg: 'Failed to fetch transactions.', - timeout: 120_000, - interval: 500 - } - ) -} - export const searchAndSelectToken = ({ tokenName, tokenAddress @@ -247,11 +222,17 @@ export function findMoveFundsButton(): Cypress.Chainable> { .should('be.visible') } -export function startTransfer() { +export function clickMoveFundsButton({ + shouldConfirmInMetamask = true +}: { + shouldConfirmInMetamask?: boolean +} = {}) { cy.wait(5_000) cy.findMoveFundsButton().click() cy.wait(15_000) - cy.confirmMetamaskTransaction() + if (shouldConfirmInMetamask) { + cy.confirmMetamaskTransaction() + } } export function findSelectTokenButton( @@ -263,8 +244,20 @@ export function findSelectTokenButton( .should('have.text', text) } -export function closeTransactionHistoryPanel() { - cy.findByLabelText('Close side panel').click() +export function switchToTransferPanelTab() { + return cy.findByLabelText('Switch to Bridge Tab').click() +} + +export function switchToTransactionHistoryTab(tab: 'pending' | 'settled') { + cy.log(`opening transactions panel on ${tab}`) + + cy.findByLabelText('Switch to Transaction History Tab').click() + + cy.selectTransactionsPanelTab(tab) + + cy.findByText(/Showing \d+ \w+ transactions made in/, { + timeout: 120_000 + }).should('be.visible') } export function openTransactionDetails({ @@ -317,6 +310,8 @@ export function findTransactionInTransactionHistory({ amount2?: number duration?: string }) { + const timeout = 120_000 + // Replace . with \. const parsedAmount = amount.toString().replace(/\./g, '\\.') @@ -326,15 +321,18 @@ export function findTransactionInTransactionHistory({ }` ) - cy.findByTestId(rowId).as('row') + cy.findByTestId(rowId, { timeout }).as('row') if (duration) { - cy.get('@row').findAllByText(duration).first().should('be.visible') + cy.get('@row', { timeout }) + .findAllByText(duration, { timeout }) + .first() + .should('be.visible', { timeout }) } - cy.get('@row') - .findByLabelText('Transaction details button') - .should('be.visible') - return cy.get('@row') + cy.get('@row', { timeout }) + .findByLabelText('Transaction details button', { timeout }) + .should('be.visible', { timeout }) + return cy.get('@row', { timeout }) } export function findClaimButton( @@ -372,7 +370,7 @@ export function claimCctp(amount: number, options: { accept: boolean }) { const formattedAmount = formatAmount(amount, { symbol: 'USDC' }) - cy.openTransactionsPanel('pending') + cy.switchToTransactionHistoryTab('pending') cy.findTransactionInTransactionHistory({ amount, symbol: 'USDC' @@ -391,7 +389,6 @@ Cypress.Commands.addAll({ connectToApp, login, logout, - openTransactionsPanel, selectTransactionsPanelTab, searchAndSelectToken, fillCustomDestinationAddress, @@ -404,9 +401,10 @@ Cypress.Commands.addAll({ findGasFeeForChain, findGasFeeSummary, findMoveFundsButton, - startTransfer, + clickMoveFundsButton, findSelectTokenButton, - closeTransactionHistoryPanel, + switchToTransferPanelTab, + switchToTransactionHistoryTab, openTransactionDetails, closeTransactionDetails, findTransactionInTransactionHistory, diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 13cb924a31..9e7591a891 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -17,7 +17,7 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", - "@arbitrum/sdk": "^4.0.1", + "@arbitrum/sdk": "^4.0.2", "@octokit/rest": "^21.0.2", "axios": "^1.7.7", "commander": "^12.1.0", diff --git a/packages/scripts/src/addOrbitChain/provider.ts b/packages/scripts/src/addOrbitChain/provider.ts index c29faa0d4f..d7287dedb7 100644 --- a/packages/scripts/src/addOrbitChain/provider.ts +++ b/packages/scripts/src/addOrbitChain/provider.ts @@ -6,13 +6,21 @@ export const getProvider = (chainInfo: { name: string; chainId: number; }) => { + const THROTTLE_LIMIT = 10; + const connection: ConnectionInfo = { url: chainInfo.rpcUrl, - timeout: 30000, + timeout: 300000, allowGzip: true, skipFetchSetup: true, - throttleLimit: 3, - throttleSlotInterval: 1000, + throttleLimit: THROTTLE_LIMIT, + throttleSlotInterval: 3000, + throttleCallback: async (attempt: number) => { + // Always retry until we hit the THROTTLE_LIMIT + // Otherwise, it only throttles for specific response codes + // Return true to continue retrying, false to stop + return attempt <= THROTTLE_LIMIT; + }, headers: { Accept: "*/*", "Accept-Encoding": "gzip, deflate, br", diff --git a/packages/scripts/src/addOrbitChain/schemas.ts b/packages/scripts/src/addOrbitChain/schemas.ts index 532e8a1d6e..f18adf2c69 100644 --- a/packages/scripts/src/addOrbitChain/schemas.ts +++ b/packages/scripts/src/addOrbitChain/schemas.ts @@ -36,6 +36,13 @@ export const getParentChainInfo = (parentChainId: number) => { chainId: 42161, name: "Arbitrum One", }; + case 42170: // Arbitrum Nova + return { + rpcUrl: "https://nova.arbitrum.io/rpc", + blockExplorer: "https://nova.arbiscan.io", + chainId: 42170, + name: "Arbitrum Nova", + }; case 11155111: // Sepolia return { rpcUrl: INFURA_KEY diff --git a/yarn.lock b/yarn.lock index c1346b3d9a..23414ead7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -78,17 +78,6 @@ tslib "^2.3.0" zen-observable-ts "^1.2.5" -"@arbitrum/sdk@^4.0.1": - version "4.0.1" - resolved "https://registry.npmjs.org/@arbitrum/sdk/-/sdk-4.0.1.tgz" - integrity sha512-uW0Pe/oICbmlHpIpYOaHHWsNQRG+3UbCa3s0SJsp2O1Kt9b0M0CX/fEdFOFLyAi3OxHonNEfzhfvQrALy9C3Yw== - dependencies: - "@ethersproject/address" "^5.0.8" - "@ethersproject/bignumber" "^5.1.1" - "@ethersproject/bytes" "^5.0.8" - async-mutex "^0.4.0" - ethers "^5.1.0" - "@arbitrum/sdk@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-4.0.2.tgz#23555858f49e2b237b94a65bd486c65edb7b1690" @@ -428,13 +417,6 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.23.1": - version "7.23.4" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz" - integrity sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/runtime@^7.23.2": version "7.23.5" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz" @@ -1184,12 +1166,12 @@ resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== -"@hapi/hoek@^9.0.0": +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": version "9.3.0" resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz" integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== -"@hapi/topo@^5.0.0": +"@hapi/topo@^5.0.0", "@hapi/topo@^5.1.0": version "5.1.0" resolved "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz" integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== @@ -1651,7 +1633,7 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -1786,10 +1768,10 @@ "@motionone/dom" "^10.16.2" tslib "^2.3.1" -"@next/env@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.14.tgz#08f5175dab727102da02301ba61f7239773670fa" - integrity sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg== +"@next/env@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.22.tgz#8898ae47595badbfacebfc1585f42a4e06a97301" + integrity sha512-EQ6y1QeNQglNmNIXvwP/Bb+lf7n9WtgcWvtoFsHquVLCJUuxRs+6SfZ5EK0/EqkkLex4RrDySvKgKNN7PXip7Q== "@next/eslint-plugin-next@14.2.5": version "14.2.5" @@ -1805,50 +1787,50 @@ dependencies: glob "7.1.7" -"@next/swc-darwin-arm64@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.14.tgz#6dde2dac699dfe948b527385f2b350b3151989f4" - integrity sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw== - -"@next/swc-darwin-x64@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.14.tgz#25800213c4dc0f8cd765c88073d28a3144698e31" - integrity sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww== - -"@next/swc-linux-arm64-gnu@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.14.tgz#9c66bd1287d0c3633e7bf354f9c01e1b79747615" - integrity sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg== - -"@next/swc-linux-arm64-musl@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.14.tgz#da2ae16a24bb2b2a46447154e95da85c557ab09a" - integrity sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A== - -"@next/swc-linux-x64-gnu@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.14.tgz#635c62109b9cf0464e6322955a36931ebb9ed3e2" - integrity sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w== - -"@next/swc-linux-x64-musl@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.14.tgz#1565caf6fa77c3280d8b05ffc8c542ff144a4855" - integrity sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ== - -"@next/swc-win32-arm64-msvc@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.14.tgz#8df4feb3c9280155e9299f3cdfa32f3cface336a" - integrity sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A== - -"@next/swc-win32-ia32-msvc@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.14.tgz#1f0a2bafbb63147c8db102ca1524db9ffa959d0c" - integrity sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ== - -"@next/swc-win32-x64-msvc@14.2.14": - version "14.2.14" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.14.tgz#9d6446b3a8d5e67e199049d59ce7c0b8bd33ab51" - integrity sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg== +"@next/swc-darwin-arm64@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.22.tgz#2b3fcb42247ba951b19a48fc03f1d6fe65629baa" + integrity sha512-HUaLiehovgnqY4TMBZJ3pDaOsTE1spIXeR10pWgdQVPYqDGQmHJBj3h3V6yC0uuo/RoY2GC0YBFRkOX3dI9WVQ== + +"@next/swc-darwin-x64@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.22.tgz#11ecc609e9530b3edf8ddfd1fd3bd6aca4e1bfda" + integrity sha512-ApVDANousaAGrosWvxoGdLT0uvLBUC+srqOcpXuyfglA40cP2LBFaGmBjhgpxYk5z4xmunzqQvcIgXawTzo2uQ== + +"@next/swc-linux-arm64-gnu@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.22.tgz#4c08dd223e50c348f561af2285e27fb326ffabbf" + integrity sha512-3O2J99Bk9aM+d4CGn9eEayJXHuH9QLx0BctvWyuUGtJ3/mH6lkfAPRI4FidmHMBQBB4UcvLMfNf8vF0NZT7iKw== + +"@next/swc-linux-arm64-musl@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.22.tgz#0b285336f145887d421b3762f3d7c75f847ec1b3" + integrity sha512-H/hqfRz75yy60y5Eg7DxYfbmHMjv60Dsa6IWHzpJSz4MRkZNy5eDnEW9wyts9bkxwbOVZNPHeb3NkqanP+nGPg== + +"@next/swc-linux-x64-gnu@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.22.tgz#a936b6cfea0364571102f0389c6368d6acf3e294" + integrity sha512-LckLwlCLcGR1hlI5eiJymR8zSHPsuruuwaZ3H2uudr25+Dpzo6cRFjp/3OR5UYJt8LSwlXv9mmY4oI2QynwpqQ== + +"@next/swc-linux-x64-musl@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.22.tgz#0359497840d0b7d8c095d0d9735bc6aec68cef5d" + integrity sha512-qGUutzmh0PoFU0fCSu0XYpOfT7ydBZgDfcETIeft46abPqP+dmePhwRGLhFKwZWxNWQCPprH26TjaTxM0Nv8mw== + +"@next/swc-win32-arm64-msvc@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.22.tgz#9fd249d49ffccf3388400ab24472c432cdd04c24" + integrity sha512-K6MwucMWmIvMb9GlvT0haYsfIPxfQD8yXqxwFy4uLFMeXIb2TcVYQimxkaFZv86I7sn1NOZnpOaVk5eaxThGIw== + +"@next/swc-win32-ia32-msvc@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.22.tgz#70d8d5a48e78c7382c3e0544af28c2788ca6b551" + integrity sha512-5IhDDTPEbzPR31ZzqHe90LnNe7BlJUZvC4sA1thPJV6oN5WmtWjZ0bOYfNsyZx00FJt7gggNs6SrsX0UEIcIpA== + +"@next/swc-win32-x64-msvc@14.2.22": + version "14.2.22" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.22.tgz#b034f544c1346093a235f6bba46497a1ba344fc1" + integrity sha512-nvRaB1PyG4scn9/qNzlkwEwLzuoPH3Gjp7Q/pLuwUgOTt1oPMlnCI3A3rgkt+eZnU71emOiEv/mR201HoURPGg== "@noble/curves@1.2.0", "@noble/curves@~1.2.0": version "1.2.0" @@ -2087,10 +2069,10 @@ dependencies: "@octokit/openapi-types" "^22.2.0" -"@offchainlabs/cobalt@^0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@offchainlabs/cobalt/-/cobalt-0.3.11.tgz#6d6ec1c81bf77f897952cbd33f151e1a7cb596a1" - integrity sha512-xczC0CkMzAkbIFnvtX8EqKRUq3FPT/7MtP0sXBkbcaHvcgxNYs/fafmvn8/zmQxM+RBOJ/no17UNL4I3Cp9s+w== +"@offchainlabs/cobalt@^0.3.12": + version "0.3.12" + resolved "https://registry.yarnpkg.com/@offchainlabs/cobalt/-/cobalt-0.3.12.tgz#f841028f0e698b57adbaa75c2225aec59baef821" + integrity sha512-ULbCD0DfxO8xmrPOmHcB4HuW+c81qAdbvXw5it5jMilb2bVjPoE4gtCALbVppkGbcft0P3FgEpKHbtfS7sEfeg== "@parcel/watcher-android-arm64@2.4.1": version "2.4.1" @@ -2470,6 +2452,13 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + "@sideway/formula@^3.0.1": version "3.0.1" resolved "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz" @@ -2801,7 +2790,7 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/react@^14.0.0", "@testing-library/react@^14.2.1": +"@testing-library/react@^14.0.0": version "14.2.1" resolved "https://registry.npmjs.org/@testing-library/react/-/react-14.2.1.tgz" integrity sha512-sGdjws32ai5TLerhvzThYFbpnF9XtL65Cjf+gB0Dhr29BGqK+mAeN7SURSdu+eqgET4ANcWoC7FQpkaiGvBr+A== @@ -2810,6 +2799,13 @@ "@testing-library/dom" "^9.0.0" "@types/react-dom" "^18.0.0" +"@testing-library/react@^16.1.0": + version "16.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.1.0.tgz#aa0c61398bac82eaf89776967e97de41ac742d71" + integrity sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg== + dependencies: + "@babel/runtime" "^7.12.5" + "@tippyjs/react@^4.2.6": version "4.2.6" resolved "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz" @@ -5311,12 +5307,7 @@ camelize@^1.0.0: resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz" integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== -caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: - version "1.0.30001503" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz" - integrity sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw== - -caniuse-lite@^1.0.30001579: +caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503, caniuse-lite@^1.0.30001579: version "1.0.30001643" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz" integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== @@ -5782,6 +5773,11 @@ core-js@^2.4.0: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-js@^3.38.1: + version "3.39.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.39.0.tgz#57f7647f4d2d030c32a72ea23a0555b2eaa30f83" + integrity sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g== + core-js@^3.6.4: version "3.31.0" resolved "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz" @@ -5843,6 +5839,11 @@ css-color-keywords@^1.0.0: resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz" integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== +css-gradient-parser@^0.0.16: + version "0.0.16" + resolved "https://registry.yarnpkg.com/css-gradient-parser/-/css-gradient-parser-0.0.16.tgz#5735da0822aef39da9b1960b314792ab542d9bb5" + integrity sha512-3O5QdqgFRUbXvK1x5INf1YkBz1UKSWqrd63vWsum8MNHDBYD5urm3QtxZbKU259OrEXNM26lP/MPY3d1IGkBgA== + css-in-js-utils@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz" @@ -5921,10 +5922,10 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.2, csstype@^3.0.6, csstype@^3.0.7: - version "3.1.2" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.0.2, csstype@^3.0.7, csstype@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== cypress-terminal-report@^5.3.10: version "5.3.10" @@ -6016,10 +6017,10 @@ dayjs@1.11.12: resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz" integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg== -dayjs@^1.10.4, dayjs@^1.11.8: - version "1.11.8" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz" - integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ== +dayjs@^1.10.4, dayjs@^1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== debug@2.6.9: version "2.6.9" @@ -6028,12 +6029,12 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@4, debug@4.4.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== dependencies: - ms "2.1.2" + ms "^2.1.3" debug@4.3.5: version "4.3.5" @@ -7556,6 +7557,11 @@ expect@^29.0.0, expect@^29.5.0: jest-message-util "^29.5.0" jest-util "^29.5.0" +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + express@^4.17.3: version "4.21.0" resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" @@ -7665,6 +7671,17 @@ fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" @@ -7675,11 +7692,6 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-loops@^1.1.3: - version "1.1.4" - resolved "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz" - integrity sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg== - fast-redact@^3.0.0: version "3.2.0" resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.2.0.tgz" @@ -8287,18 +8299,6 @@ glob@10.3.10: minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-scurry "^1.10.1" -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@7.1.7: version "7.1.7" resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" @@ -8311,6 +8311,18 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -8435,10 +8447,10 @@ graphql-tag@^2.12.6: dependencies: tslib "^2.1.0" -graphql@^16.8.1: - version "16.8.1" - resolved "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz" - integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== +graphql@^16.9.0: + version "16.9.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" + integrity sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw== h3@^1.10.2, h3@^1.11.1: version "1.11.1" @@ -8912,13 +8924,12 @@ ini@~1.3.0: resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -inline-style-prefixer@^6.0.0: - version "6.0.4" - resolved "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz" - integrity sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg== +inline-style-prefixer@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz#9310f3cfa2c6f3901d1480f373981c02691781e8" + integrity sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw== dependencies: css-in-js-utils "^3.1.0" - fast-loops "^1.1.3" internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" @@ -9495,6 +9506,15 @@ jackspeak@^2.3.5: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jayson@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/jayson/-/jayson-4.1.0.tgz" @@ -9888,12 +9908,7 @@ jest@^29.4.0: import-local "^3.0.2" jest-cli "^29.5.0" -jiti@^1.18.2: - version "1.18.2" - resolved "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz" - integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg== - -jiti@^1.21.0: +jiti@^1.21.0, jiti@^1.21.6: version "1.21.6" resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== @@ -9903,6 +9918,17 @@ jju@^1.4.0: resolved "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz" integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== +joi@^17.13.3: + version "17.13.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + joi@^17.7.0: version "17.9.2" resolved "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz" @@ -10201,10 +10227,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lilconfig@^2.0.5, lilconfig@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== +lilconfig@^3.0.0, lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== linebreak@^1.1.0: version "1.1.0" @@ -10266,14 +10292,7 @@ lit-element@^3.3.0: "@lit/reactive-element" "^1.3.0" lit-html "^2.7.0" -lit-html@^2.7.0: - version "2.7.4" - resolved "https://registry.npmjs.org/lit-html/-/lit-html-2.7.4.tgz" - integrity sha512-/Jw+FBpeEN+z8X6PJva5n7+0MzCVAH2yypN99qHYYkq8bI+j7I39GH+68Z/MZD6rGKDK9RpzBw7CocfmHfq6+g== - dependencies: - "@types/trusted-types" "^2.0.2" - -lit-html@^2.8.0: +lit-html@^2.7.0, lit-html@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.8.0.tgz#96456a4bb4ee717b9a7d2f94562a16509d39bffa" integrity sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q== @@ -10581,7 +10600,7 @@ methods@~1.1.2: resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -10655,7 +10674,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^9.0.1: +minimatch@^9.0.1, minimatch@^9.0.4: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -10667,7 +10686,7 @@ minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.7, minimist@^1. resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== @@ -10714,7 +10733,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -10741,29 +10760,24 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nano-css@^5.3.1: - version "5.3.5" - resolved "https://registry.npmjs.org/nano-css/-/nano-css-5.3.5.tgz" - integrity sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg== +nano-css@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.2.tgz#584884ddd7547278f6d6915b6805069742679a32" + integrity sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw== dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" css-tree "^1.1.2" - csstype "^3.0.6" + csstype "^3.1.2" fastest-stable-stringify "^2.0.2" - inline-style-prefixer "^6.0.0" - rtl-css-js "^1.14.0" - sourcemap-codec "^1.4.8" + inline-style-prefixer "^7.0.1" + rtl-css-js "^1.16.1" stacktrace-js "^2.0.2" - stylis "^4.0.6" - -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + stylis "^4.3.0" -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +nanoid@^3.3.6, nanoid@^3.3.7: + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== napi-build-utils@^1.0.1: version "1.0.2" @@ -10797,12 +10811,12 @@ next-query-params@^5.0.0: dependencies: tslib "^2.0.3" -next@^14.2.12: - version "14.2.14" - resolved "https://registry.yarnpkg.com/next/-/next-14.2.14.tgz#115f29443dfb96d23b4b5ab5c4547de339202ba7" - integrity sha512-Q1coZG17MW0Ly5x76shJ4dkC23woLAhhnDnw+DfTc7EpZSGuWrlsZ3bZaO8t6u1Yu8FVfhkqJE+U8GC7E0GLPQ== +next@^14.2.21: + version "14.2.22" + resolved "https://registry.yarnpkg.com/next/-/next-14.2.22.tgz#0cd664916ef4c725f31fa812d870348cffd0115b" + integrity sha512-Ps2caobQ9hlEhscLPiPm3J3SYhfwfpMqzsoCMZGWxt9jBRK9hoBZj2A37i8joKhsyth2EuVKDVJCTF5/H4iEDw== dependencies: - "@next/env" "14.2.14" + "@next/env" "14.2.22" "@swc/helpers" "0.5.5" busboy "1.6.0" caniuse-lite "^1.0.30001579" @@ -10810,15 +10824,15 @@ next@^14.2.12: postcss "8.4.31" styled-jsx "5.1.1" optionalDependencies: - "@next/swc-darwin-arm64" "14.2.14" - "@next/swc-darwin-x64" "14.2.14" - "@next/swc-linux-arm64-gnu" "14.2.14" - "@next/swc-linux-arm64-musl" "14.2.14" - "@next/swc-linux-x64-gnu" "14.2.14" - "@next/swc-linux-x64-musl" "14.2.14" - "@next/swc-win32-arm64-msvc" "14.2.14" - "@next/swc-win32-ia32-msvc" "14.2.14" - "@next/swc-win32-x64-msvc" "14.2.14" + "@next/swc-darwin-arm64" "14.2.22" + "@next/swc-darwin-x64" "14.2.22" + "@next/swc-linux-arm64-gnu" "14.2.22" + "@next/swc-linux-arm64-musl" "14.2.22" + "@next/swc-linux-x64-gnu" "14.2.22" + "@next/swc-linux-x64-musl" "14.2.22" + "@next/swc-win32-arm64-msvc" "14.2.22" + "@next/swc-win32-ia32-msvc" "14.2.22" + "@next/swc-win32-x64-msvc" "14.2.22" no-case@^3.0.4: version "3.0.4" @@ -11361,6 +11375,11 @@ package-hash@^4.0.0: lodash.flattendeep "^4.4.0" release-zalgo "^1.0.0" +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + pako@^0.2.5: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" @@ -11495,7 +11514,7 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.10.1: +path-scurry@^1.10.1, path-scurry@^1.11.1: version "1.11.1" resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== @@ -11550,10 +11569,10 @@ picocolors@^1.0.0: resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picocolors@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" @@ -11678,25 +11697,25 @@ postcss-js@^4.0.1: dependencies: camelcase-css "^2.0.1" -postcss-load-config@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz" - integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== +postcss-load-config@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== dependencies: - lilconfig "^2.0.5" - yaml "^2.1.1" + lilconfig "^3.0.0" + yaml "^2.3.4" -postcss-nested@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz" - integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== +postcss-nested@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== dependencies: - postcss-selector-parser "^6.0.11" + postcss-selector-parser "^6.1.1" -postcss-selector-parser@^6.0.11: - version "6.0.13" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz" - integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== +postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -11706,7 +11725,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@8.4.31, postcss@^8.4.23, postcss@^8.4.31: +postcss@8.4.31: version "8.4.31" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== @@ -11715,23 +11734,24 @@ postcss@8.4.31, postcss@^8.4.23, postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.43: - version "8.4.47" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== +postcss@^8.4.43, postcss@^8.4.47, postcss@^8.4.49: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== dependencies: nanoid "^3.3.7" - picocolors "^1.1.0" + picocolors "^1.1.1" source-map-js "^1.2.1" -posthog-js@^1.155.4: - version "1.155.4" - resolved "https://registry.npmjs.org/posthog-js/-/posthog-js-1.155.4.tgz" - integrity sha512-suxwAsmZGqMDXJe/RaCKI3PaDEHiuMDDhKcJklgGAg7eDnywieRkr5CoPcOOvnqTDMnuOPETr98jpYBXKUwGFQ== +posthog-js@^1.200.0: + version "1.200.0" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.200.0.tgz#46e89f85dc7ac7536ac3ce15b182ad3cec6ae9db" + integrity sha512-NuS9PF8whpxdG084XHzb/jE4WNRw7doej5fKzlTarzynbcblKiYbP/g9SMWCYcCAA63srIQzNOhbiaYDY7+y1A== dependencies: + core-js "^3.38.1" fflate "^0.4.8" preact "^10.19.3" - web-vitals "^4.0.1" + web-vitals "^4.2.0" postinstall-postinstall@^2.1.0: version "2.1.0" @@ -12196,10 +12216,10 @@ react-universal-interface@^0.6.2: resolved "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz" integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== -react-use@^17.2.4: - version "17.4.0" - resolved "https://registry.npmjs.org/react-use/-/react-use-17.4.0.tgz" - integrity sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q== +react-use@^17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.6.0.tgz#2101a3a79dc965a25866b21f5d6de4b128488a14" + integrity sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g== dependencies: "@types/js-cookie" "^2.2.6" "@xobotyi/scrollbar-width" "^1.9.5" @@ -12207,7 +12227,7 @@ react-use@^17.2.4: fast-deep-equal "^3.1.3" fast-shallow-equal "^1.0.0" js-cookie "^2.2.1" - nano-css "^5.3.1" + nano-css "^5.6.2" react-universal-interface "^0.6.2" resize-observer-polyfill "^1.5.1" screenfull "^5.1.0" @@ -12433,7 +12453,7 @@ resolve.exports@^2.0.0: resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.1.7, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.2: +resolve@^1.1.7, resolve@^1.20.0, resolve@^1.22.1: version "1.22.2" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz" integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== @@ -12442,7 +12462,7 @@ resolve@^1.1.7, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.2: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.22.4: +resolve@^1.22.4, resolve@^1.22.8: version "1.22.8" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -12547,9 +12567,9 @@ rpc-websockets@^7.5.1: bufferutil "^4.0.1" utf-8-validate "^5.0.2" -rtl-css-js@^1.14.0: +rtl-css-js@^1.16.1: version "1.16.1" - resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80" integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== dependencies: "@babel/runtime" "^7.1.2" @@ -12575,7 +12595,7 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" -rxjs@^7.5.1, rxjs@^7.8.0: +rxjs@^7.5.1, rxjs@^7.8.0, rxjs@^7.8.1: version "7.8.1" resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== @@ -12631,14 +12651,15 @@ safe-stable-stringify@^2.1.0: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -satori@^0.10.11: - version "0.10.11" - resolved "https://registry.yarnpkg.com/satori/-/satori-0.10.11.tgz#4d198beef405668120566d29a554411c534d8a5d" - integrity sha512-yLm1xPRPZUaKcBZJ6nmezoJjHB4MqV8x7Mu0PyZUJodRWRDD27UbeMwzuY9LEGG57WYLO4CQsGPlbHWV1Ex9TQ== +satori@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/satori/-/satori-0.12.0.tgz#6ffd11daedbf63fc6a68411639894da128b7a99f" + integrity sha512-e0e+qQyeFwEszujN7SpWpRtZgww7Nh8lSO3bUn2spHZ5JpqEl3zJ3P14/JlWruxEwdgREs35ZnavrPrWaRVFDg== dependencies: "@shuding/opentype.js" "1.4.0-beta.0" css-background-parser "^0.1.0" css-box-shadow "1.0.0-3" + css-gradient-parser "^0.0.16" css-to-react-native "^3.0.0" emoji-regex "^10.2.1" escape-html "^1.0.3" @@ -13071,11 +13092,6 @@ source-map@^0.7.3: resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== -sourcemap-codec@^1.4.8: - version "1.4.8" - resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -13213,19 +13229,19 @@ stacktrace-js@^2.0.2: stack-generator "^2.0.5" stacktrace-gps "^3.0.4" -start-server-and-test@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.0.0.tgz" - integrity sha512-UqKLw0mJbfrsG1jcRLTUlvuRi9sjNuUiDOLI42r7R5fA9dsFoywAy9DoLXNYys9B886E4RCKb+qM1Gzu96h7DQ== +start-server-and-test@^2.0.9: + version "2.0.9" + resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-2.0.9.tgz#a58160fa95e072aeb41e104472dea01b052b7443" + integrity sha512-DDceIvc4wdpr+z3Aqkot2QMho8TcUBh5qH0wEHDpEexBTzlheOcmh53d3dExABY4J5C7qS2UbSXqRWLtxpbWIQ== dependencies: arg "^5.0.2" bluebird "3.7.2" check-more-types "2.24.0" - debug "4.3.4" + debug "4.4.0" execa "5.1.1" lazy-ass "1.6.0" ps-tree "1.2.0" - wait-on "7.0.1" + wait-on "8.0.1" statuses@2.0.1: version "2.0.1" @@ -13537,19 +13553,19 @@ styled-tools@^1.7.2: resolved "https://registry.npmjs.org/styled-tools/-/styled-tools-1.7.2.tgz" integrity sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg== -stylis@^4.0.6: - version "4.2.0" - resolved "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz" - integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== +stylis@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4" + integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now== -sucrase@^3.32.0: - version "3.32.0" - resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz" - integrity sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ== +sucrase@^3.35.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== dependencies: "@jridgewell/gen-mapping" "^0.3.2" commander "^4.0.0" - glob "7.1.6" + glob "^10.3.10" lines-and-columns "^1.1.6" mz "^2.7.0" pirates "^4.0.1" @@ -13591,12 +13607,13 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -swr@^2.1.2: - version "2.1.5" - resolved "https://registry.npmjs.org/swr/-/swr-2.1.5.tgz" - integrity sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw== +swr@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.3.0.tgz#66fa45023efd4199f4e7ce608c255709a135943d" + integrity sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA== dependencies: - use-sync-external-store "^1.2.0" + dequal "^2.0.3" + use-sync-external-store "^1.4.0" symbol-observable@^4.0.0: version "4.0.0" @@ -13632,41 +13649,38 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" -tailwind-merge@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.0.0.tgz" - integrity sha512-WO8qghn9yhsldLSg80au+3/gY9E4hFxIvQ3qOmlpXnqpDKoMruKfi/56BbbMg6fHTQJ9QD3cc79PoWqlaQE4rw== - dependencies: - "@babel/runtime" "^7.23.1" +tailwind-merge@^2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.5.5.tgz#98167859b856a2a6b8d2baf038ee171b9d814e39" + integrity sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA== -tailwindcss@^3.2.4: - version "3.3.2" - resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz" - integrity sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w== +tailwindcss@^3.4.16: + version "3.4.16" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.16.tgz#35a7c3030844d6000fc271878db4096b6a8d2ec9" + integrity sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" - chokidar "^3.5.3" + chokidar "^3.6.0" didyoumean "^1.2.2" dlv "^1.1.3" - fast-glob "^3.2.12" + fast-glob "^3.3.2" glob-parent "^6.0.2" is-glob "^4.0.3" - jiti "^1.18.2" - lilconfig "^2.1.0" - micromatch "^4.0.5" + jiti "^1.21.6" + lilconfig "^3.1.3" + micromatch "^4.0.8" normalize-path "^3.0.0" object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.23" + picocolors "^1.1.1" + postcss "^8.4.47" postcss-import "^15.1.0" postcss-js "^4.0.1" - postcss-load-config "^4.0.1" - postcss-nested "^6.0.1" - postcss-selector-parser "^6.0.11" - postcss-value-parser "^4.2.0" - resolve "^1.22.2" - sucrase "^3.32.0" + postcss-load-config "^4.0.2" + postcss-nested "^6.2.0" + postcss-selector-parser "^6.1.2" + resolve "^1.22.8" + sucrase "^3.35.0" tapable@^1.1.3: version "1.1.3" @@ -13966,10 +13980,10 @@ ts-invariant@^0.10.3: dependencies: tslib "^2.1.0" -ts-node@^10.9.1: - version "10.9.1" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" @@ -14357,6 +14371,11 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +use-sync-external-store@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc" + integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw== + user-home@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz" @@ -14554,7 +14573,18 @@ wagmi@^0.12.18: abitype "^0.3.0" use-sync-external-store "^1.2.0" -wait-on@7.0.1, wait-on@^7.0.1: +wait-on@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-8.0.1.tgz#13c8ec77115517f8fbc2d670521a444201f03f53" + integrity sha512-1wWQOyR2LVVtaqrcIL2+OM+x7bkpmzVROa0Nf6FryXkS+er5Sa1kzFGjzZRqLnHa3n1rACFLeTwUqE1ETL9Mig== + dependencies: + axios "^1.7.7" + joi "^17.13.3" + lodash "^4.17.21" + minimist "^1.2.8" + rxjs "^7.8.1" + +wait-on@^7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz" integrity sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog== @@ -14579,10 +14609,10 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -web-vitals@^4.0.1: - version "4.2.3" - resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.3.tgz" - integrity sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q== +web-vitals@^4.2.0: + version "4.2.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7" + integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== webidl-conversions@^3.0.0: version "3.0.1" @@ -14892,15 +14922,10 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^2.1.1: - version "2.3.1" - resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz" - integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== - -yaml@^2.2.2: - version "2.3.4" - resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz" - integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== +yaml@^2.2.2, yaml@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773" + integrity sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg== yargs-parser@^18.1.2: version "18.1.3" @@ -14985,24 +15010,12 @@ zen-observable@0.8.15: resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== -zod@^3.22.4: - version "3.22.4" - resolved "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz" - integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== - -zod@^3.23.8: - version "3.23.8" - resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== - -zustand@^4.3.1: - version "4.3.8" - resolved "https://registry.npmjs.org/zustand/-/zustand-4.3.8.tgz" - integrity sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg== - dependencies: - use-sync-external-store "1.2.0" +zod@^3.23.8, zod@^3.24.1: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== -zustand@^4.3.9: +zustand@^4.3.1, zustand@^4.3.9: version "4.3.9" resolved "https://registry.npmjs.org/zustand/-/zustand-4.3.9.tgz" integrity sha512-Tat5r8jOMG1Vcsj8uldMyqYKC5IZvQif8zetmLHs9WoZlntTHmIoNM8TpLRY31ExncuUvUOXehd0kvahkuHjDw==