From e7cd50af16ed665ae13985a1246d8544bdb629a6 Mon Sep 17 00:00:00 2001 From: Bartek Date: Tue, 13 Aug 2024 15:34:16 +0200 Subject: [PATCH 01/10] ci: ignore axios advisory on yarn audit (#1839) --- audit-ci.jsonc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/audit-ci.jsonc b/audit-ci.jsonc index 7003d1ccb1..df74e90f0e 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -9,6 +9,9 @@ // https://github.com/advisories/GHSA-977x-g7h5-7qgw "GHSA-977x-g7h5-7qgw", // https://github.com/advisories/GHSA-f7q4-pwc6-w24p - "GHSA-f7q4-pwc6-w24p" + "GHSA-f7q4-pwc6-w24p", + // axios + // https://github.com/advisories/GHSA-8hc4-vh64-cxmj + "GHSA-8hc4-vh64-cxmj" ] } From 46d0a0138354974b60d399f4a833350d8fbbf229 Mon Sep 17 00:00:00 2001 From: Bartek Date: Wed, 14 Aug 2024 12:16:55 +0200 Subject: [PATCH 02/10] feat: show native currency input for batch transfers (#1832) --- .../components/TransferPanel/TokenButton.tsx | 62 ++++++++----------- .../TransferPanelMain/SourceNetworkBox.tsx | 23 +++++++ .../TransferPanel/TransferPanelMainInput.tsx | 7 ++- .../src/components/common/Header.tsx | 21 ++++++- .../arb-token-bridge-ui/src/pages/index.tsx | 27 ++++++-- .../arb-token-bridge-ui/src/util/index.ts | 46 ++++++++++++++ 6 files changed, 139 insertions(+), 47 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenButton.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenButton.tsx index 1d3d4065e1..c090e311a3 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenButton.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenButton.tsx @@ -4,7 +4,6 @@ import { ChevronDownIcon } from '@heroicons/react/24/outline' import { twMerge } from 'tailwind-merge' import { useAppState } from '../../state' -import { sanitizeImageSrc } from '../../util' import { TokenSearch } from '../TransferPanel/TokenSearch' import { sanitizeTokenSymbol } from '../../util/TokenUtils' import { useNativeCurrency } from '../../hooks/useNativeCurrency' @@ -17,43 +16,31 @@ import { useNetworks } from '../../hooks/useNetworks' import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' import { Transition } from '../common/Transition' -export function TokenButton(): JSX.Element { +export type TokenButtonOptions = { + symbol?: string + disabled?: boolean +} + +export function TokenButton({ + options +}: { + options?: TokenButtonOptions +}): JSX.Element { const { - app: { - selectedToken, - arbTokenBridge: { bridgeTokens }, - arbTokenBridgeLoaded - } + app: { selectedToken } } = useAppState() + const disabled = options?.disabled ?? false + const [networks] = useNetworks() const { childChainProvider } = useNetworksRelationship(networks) const nativeCurrency = useNativeCurrency({ provider: childChainProvider }) - const tokenLogo = useMemo(() => { - const selectedAddress = selectedToken?.address - if (!selectedAddress) { - return nativeCurrency.logoUrl - } - if (!arbTokenBridgeLoaded) { - return undefined - } - if (typeof bridgeTokens === 'undefined') { - return undefined - } - const logo = bridgeTokens[selectedAddress]?.logoURI - if (logo) { - return sanitizeImageSrc(logo) + const tokenSymbol = useMemo(() => { + if (typeof options?.symbol !== 'undefined') { + return options.symbol } - return undefined - }, [ - nativeCurrency, - bridgeTokens, - selectedToken?.address, - arbTokenBridgeLoaded - ]) - const tokenSymbol = useMemo(() => { if (!selectedToken) { return nativeCurrency.symbol } @@ -62,7 +49,7 @@ export function TokenButton(): JSX.Element { erc20L1Address: selectedToken.address, chainId: networks.sourceChain.id }) - }, [selectedToken, networks.sourceChain.id, nativeCurrency.symbol]) + }, [selectedToken, networks.sourceChain.id, nativeCurrency.symbol, options]) return ( <> @@ -73,6 +60,7 @@ export function TokenButton(): JSX.Element { className="arb-hover h-full w-max rounded-bl rounded-tl px-3 py-3 text-white" aria-label="Select Token" onClick={onPopoverButtonClick} + disabled={disabled} >
{/* Commenting it out until we update the token image source files to be of better quality */} @@ -90,12 +78,14 @@ export function TokenButton(): JSX.Element { {tokenSymbol} - + {!disabled && ( + + )}
diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx index b892b0e97a..10865e3aa6 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect } from 'react' +import { isTeleport } from '@/token-bridge-sdk/teleport' import { getNetworkName } from '../../../util/networks' import { NetworkButton, @@ -33,6 +34,7 @@ import { AmountQueryParamEnum } from '../../../hooks/useArbQueryParams' import { TransferReadinessRichErrorMessage } from '../useTransferReadinessUtils' import { useMaxAmount } from './useMaxAmount' import { useSetInputAmount } from '../../../hooks/TransferPanel/useSetInputAmount' +import { isExperimentalFeatureEnabled } from '../../../util' import { useDialog } from '../../common/Dialog' export function SourceNetworkBox({ @@ -145,6 +147,27 @@ export function SourceNetworkBox({ value={isMaxAmount ? '' : amount} /> + {isExperimentalFeatureEnabled('batch') && + // TODO: teleport is disabled for now but it needs to be looked into more to check whether it is or can be supported + !isTeleport({ + sourceChainId: networks.sourceChain.id, + destinationChainId: networks.destinationChain.id + }) && + selectedToken && ( + {}} + errorMessage={undefined} + value={''} + // eslint-disable-next-line + onChange={() => {}} + tokenButtonOptions={{ + symbol: nativeCurrency.symbol, + disabled: true + }} + /> + )} + {showUsdcSpecificInfo && (

Bridged USDC (USDC.e) will work but is different from Native USDC.{' '} diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx index 5eb2afdae3..9ebbee6a3d 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx @@ -1,7 +1,7 @@ import { twMerge } from 'tailwind-merge' import { useEffect, useMemo } from 'react' -import { TokenButton } from './TokenButton' +import { TokenButton, TokenButtonOptions } from './TokenButton' import { useNetworks } from '../../hooks/useNetworks' import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' import { useSelectedTokenBalances } from '../../hooks/TransferPanel/useSelectedTokenBalances' @@ -156,10 +156,11 @@ export type TransferPanelMainInputProps = errorMessage?: string | TransferReadinessRichErrorMessage | undefined maxButtonOnClick: React.ButtonHTMLAttributes['onClick'] value: string + tokenButtonOptions?: TokenButtonOptions } export function TransferPanelMainInput(props: TransferPanelMainInputProps) { - const { errorMessage, maxButtonOnClick, ...rest } = props + const { errorMessage, maxButtonOnClick, tokenButtonOptions, ...rest } = props return ( <> @@ -171,7 +172,7 @@ export function TransferPanelMainInput(props: TransferPanelMainInputProps) { : 'border-white/30 text-white' )} > - +

Arbitrum - {isTestnet && TESTNET MODE} + {isTestnet && !isExperimentalMode && ( + TESTNET MODE + )} + {isExperimentalMode && ( + + EXPERIMENTAL MODE: features may be incomplete or not work properly + + )}
{children}
diff --git a/packages/arb-token-bridge-ui/src/pages/index.tsx b/packages/arb-token-bridge-ui/src/pages/index.tsx index 345101068f..1720c741f8 100644 --- a/packages/arb-token-bridge-ui/src/pages/index.tsx +++ b/packages/arb-token-bridge-ui/src/pages/index.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react' import { GetServerSidePropsContext, GetServerSidePropsResult } from 'next' import dynamic from 'next/dynamic' +import { decodeString, encodeString } from 'use-query-params' import { registerCustomArbitrumNetwork } from '@arbitrum/sdk' import { Loader } from '../components/common/atoms/Loader' @@ -14,6 +15,7 @@ import { decodeChainQueryParam, encodeChainQueryParam } from '../hooks/useArbQueryParams' +import { sanitizeExperimentalFeaturesQueryParam } from '../util' const App = dynamic(() => import('../components/App/App'), { ssr: false, @@ -31,6 +33,7 @@ function getDestinationWithSanitizedQueryParams( sanitized: { sourceChainId: number destinationChainId: number + experiments: string | undefined }, query: GetServerSidePropsContext['query'] ) { @@ -38,7 +41,11 @@ function getDestinationWithSanitizedQueryParams( for (const key in query) { // don't copy "sourceChain" and "destinationChain" query params - if (key === 'sourceChain' || key === 'destinationChain') { + if ( + key === 'sourceChain' || + key === 'destinationChain' || + key === 'experiments' + ) { continue } @@ -52,6 +59,7 @@ function getDestinationWithSanitizedQueryParams( const encodedSource = encodeChainQueryParam(sanitized.sourceChainId) const encodedDestination = encodeChainQueryParam(sanitized.destinationChainId) + const encodedExperiments = encodeString(sanitized.experiments) if (encodedSource) { params.set('sourceChain', encodedSource) @@ -61,6 +69,10 @@ function getDestinationWithSanitizedQueryParams( } } + if (encodedExperiments) { + params.set('experiments', encodedExperiments) + } + return `/?${params.toString()}` } @@ -82,6 +94,7 @@ export function getServerSideProps({ }: GetServerSidePropsContext): GetServerSidePropsResult> { const sourceChainId = decodeChainQueryParam(query.sourceChain) const destinationChainId = decodeChainQueryParam(query.destinationChain) + const experiments = decodeString(query.experiments) // If both sourceChain and destinationChain are not present, let the client sync with Metamask if (!sourceChainId && !destinationChainId) { @@ -94,19 +107,23 @@ export function getServerSideProps({ addOrbitChainsToArbitrumSDK() // sanitize the query params - const sanitized = sanitizeQueryParams({ sourceChainId, destinationChainId }) + const sanitized = { + ...sanitizeQueryParams({ sourceChainId, destinationChainId }), + experiments: sanitizeExperimentalFeaturesQueryParam(experiments) + } // if the sanitized query params are different from the initial values, redirect to the url with sanitized query params if ( sourceChainId !== sanitized.sourceChainId || - destinationChainId !== sanitized.destinationChainId + destinationChainId !== sanitized.destinationChainId || + experiments !== sanitized.experiments ) { console.log(`[getServerSideProps] sanitizing query params`) console.log( - `[getServerSideProps] sourceChain=${sourceChainId}&destinationChain=${destinationChainId} (before)` + `[getServerSideProps] sourceChain=${sourceChainId}&destinationChain=${destinationChainId}&experiments=${experiments} (before)` ) console.log( - `[getServerSideProps] sourceChain=${sanitized.sourceChainId}&destinationChain=${sanitized.destinationChainId} (after)` + `[getServerSideProps] sourceChain=${sanitized.sourceChainId}&destinationChain=${sanitized.destinationChainId}&experiments=${sanitized.experiments} (after)` ) return { redirect: { diff --git a/packages/arb-token-bridge-ui/src/util/index.ts b/packages/arb-token-bridge-ui/src/util/index.ts index d4ee03f1be..8392214fa0 100644 --- a/packages/arb-token-bridge-ui/src/util/index.ts +++ b/packages/arb-token-bridge-ui/src/util/index.ts @@ -53,3 +53,49 @@ export const getAPIBaseUrl = () => { // Resolves: next-js-error-only-absolute-urls-are-supported in test:ci return process.env.NODE_ENV === 'test' ? 'http://localhost:3000' : '' } + +const featureFlags = ['batch'] as const + +type FeatureFlag = (typeof featureFlags)[number] + +export const isExperimentalFeatureEnabled = (flag: FeatureFlag) => { + const query = new URLSearchParams(window.location.search) + const flags = query.get('experiments') + + if (!flags) { + return false + } + + return flags.split(',').includes(flag) +} + +export const isExperimentalModeEnabled = () => { + const query = new URLSearchParams(window.location.search) + const flags = query.get('experiments') + + return flags !== null +} + +export const sanitizeExperimentalFeaturesQueryParam = ( + flags: string | null | undefined +) => { + if (!flags) { + return undefined + } + + const flagsArray = flags.split(',') + + if (flagsArray.length === 0) { + return undefined + } + + const validFlagsArray = flagsArray.filter(f => + featureFlags.includes(f as FeatureFlag) + ) + + if (validFlagsArray.length === 0) { + return undefined + } + + return validFlagsArray.join(',') +} From f5e4cd3a516588172b32b7cbb9ec700003fdf577 Mon Sep 17 00:00:00 2001 From: Dewansh Date: Wed, 14 Aug 2024 15:33:07 +0400 Subject: [PATCH 03/10] chore: upgrade posthog (#1842) --- packages/arb-token-bridge-ui/package.json | 2 +- yarn.lock | 55 +++++++++-------------- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/packages/arb-token-bridge-ui/package.json b/packages/arb-token-bridge-ui/package.json index 28a4f5ea79..c7dc36f63f 100644 --- a/packages/arb-token-bridge-ui/package.json +++ b/packages/arb-token-bridge-ui/package.json @@ -34,7 +34,7 @@ "next-query-params": "^5.0.0", "overmind": "^28.0.1", "overmind-react": "^29.0.1", - "posthog-js": "^1.57.2", + "posthog-js": "^1.155.4", "query-string": "^8.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/yarn.lock b/yarn.lock index 4e027cffbb..d68198ac6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6696,7 +6696,7 @@ fetch-retry@^5.0.3: resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.6.tgz#17d0bc90423405b7a88b74355bf364acd2a7fa56" integrity sha512-3yurQZ2hD9VISAhJJP9bpYFNQrHHBXE2JxxjY5aLEcDi46RmAzJE2OC9FAde0yis5ElW0jTTzs0zfg/Cca4XqQ== -fflate@^0.4.1: +fflate@^0.4.8: version "0.4.8" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== @@ -10430,12 +10430,14 @@ postcss@8.4.31, postcss@^8.4.23, postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" -posthog-js@^1.57.2: - version "1.68.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.68.1.tgz#eadca3db9e45287771fe3a8b4100bffe50891750" - integrity sha512-edwURtegKXIUEdjgLErJF8cfCuwj7kw8JDmomnVXp9cjwaJT8Y3BRLh5Lh81GnjwGuifp1vjOL0hD3AamtsmGg== +posthog-js@^1.155.4: + version "1.155.4" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.155.4.tgz#611a63cf95b8fa908b3b50b1043cbdcb4c2a712b" + integrity sha512-suxwAsmZGqMDXJe/RaCKI3PaDEHiuMDDhKcJklgGAg7eDnywieRkr5CoPcOOvnqTDMnuOPETr98jpYBXKUwGFQ== dependencies: - fflate "^0.4.1" + fflate "^0.4.8" + preact "^10.19.3" + web-vitals "^4.0.1" postinstall-postinstall@^2.1.0: version "2.1.0" @@ -10447,6 +10449,11 @@ preact@^10.12.0, preact@^10.5.9: resolved "https://registry.yarnpkg.com/preact/-/preact-10.15.1.tgz#a1de60c9fc0c79a522d969c65dcaddc5d994eede" integrity sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g== +preact@^10.19.3: + version "10.23.2" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.23.2.tgz#52deec92796ae0f0cc6b034d9c66e0fbc1b837dc" + integrity sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA== + prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" @@ -11852,16 +11859,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11961,7 +11959,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11975,13 +11973,6 @@ strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -12968,6 +12959,11 @@ 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.yarnpkg.com/web-vitals/-/web-vitals-4.2.3.tgz#270c4baecfbc6ec6fc15da1989e465e5f9b94fb7" + integrity sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -13159,7 +13155,7 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -13177,15 +13173,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From bf67aa842770566f361d2e027c33bb825a98d044 Mon Sep 17 00:00:00 2001 From: Fionna Chan <13184582+fionnachan@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:14:27 +0100 Subject: [PATCH 04/10] build: bump axios and elliptic (#1845) --- audit-ci.jsonc | 14 +------ package.json | 3 +- packages/arb-token-bridge-ui/package.json | 2 +- yarn.lock | 51 +++++++++++++++++------ 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/audit-ci.jsonc b/audit-ci.jsonc index df74e90f0e..52bdc29750 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -1,17 +1,5 @@ { "$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json", "low": true, - "allowlist": [ - // elliptic - // waiting for it to release a fix but low severity so we can ignore it - // https://github.com/advisories/GHSA-49q7-c7j4-3p7m - "GHSA-49q7-c7j4-3p7m", - // https://github.com/advisories/GHSA-977x-g7h5-7qgw - "GHSA-977x-g7h5-7qgw", - // https://github.com/advisories/GHSA-f7q4-pwc6-w24p - "GHSA-f7q4-pwc6-w24p", - // axios - // https://github.com/advisories/GHSA-8hc4-vh64-cxmj - "GHSA-8hc4-vh64-cxmj" - ] + "allowlist": [] } diff --git a/package.json b/package.json index b826dc8d2c..74e39a32d6 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "resolutions": { "**/@walletconnect/ethereum-provider": "2.13.1", "**/@ethersproject/providers/ws": "7.5.10", - "**/@synthetixio/synpress/ws": "8.17.1" + "**/@synthetixio/synpress/ws": "8.17.1", + "**/elliptic": "6.5.7" }, "keywords": [], "author": "", diff --git a/packages/arb-token-bridge-ui/package.json b/packages/arb-token-bridge-ui/package.json index c7dc36f63f..c4c0e8636d 100644 --- a/packages/arb-token-bridge-ui/package.json +++ b/packages/arb-token-bridge-ui/package.json @@ -22,7 +22,7 @@ "@vercel/edge-config": "^0.4.1", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", - "axios": "^1.6.6", + "axios": "^1.7.4", "boring-avatars": "^1.7.0", "cheerio": "^1.0.0-rc.12", "dayjs": "^1.11.8", diff --git a/yarn.lock b/yarn.lock index d68198ac6e..d114739aec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3916,12 +3916,12 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^1.4.0, axios@^1.6.6: - version "1.6.6" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.6.tgz#878db45401d91fe9e53aed8ac962ed93bde8dd1c" - integrity sha512-XZLZDFfXKM9U/Y/B4nNynfCRUqNyVZ4sBC/n9GDRCkq9vd2mIvKjKKsbIh1WPmHmNbg6ND7cTBY3Y2+u1G3/2Q== +axios@^1.4.0, axios@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" + integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== dependencies: - follow-redirects "^1.15.4" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -5635,10 +5635,10 @@ electron-to-chromium@^1.4.431: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.431.tgz#47990d6e43465d69aa1fbd0abdec43114946edd0" integrity sha512-m232JTVmCawA2vG+1azVxhKZ9Sv1Q//xxNv5PkP5rWxGgQE8c3CiZFrh8Xnp+d1NmNxlu3QQrGIfdeW5TtXX5w== -elliptic@6.5.4, elliptic@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== +elliptic@6.5.4, elliptic@6.5.7, elliptic@^6.5.4: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -6850,7 +6850,7 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.4: +follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -11859,7 +11859,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11959,7 +11968,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11973,6 +11982,13 @@ strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -13155,7 +13171,7 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -13173,6 +13189,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 941ae8fa8799af1bfb4504e3a612df511b09e5be Mon Sep 17 00:00:00 2001 From: Bartek Date: Thu, 15 Aug 2024 14:22:17 +0200 Subject: [PATCH 05/10] feat: query params for batched transfers (#1836) --- .../TransferPanel/TransferPanel.tsx | 5 +++-- .../TransferPanel/TransferPanelMain.tsx | 3 --- .../TransferPanelMain/SourceNetworkBox.tsx | 16 +++++++------- .../TransferPanel/TransferPanelMainInput.tsx | 21 ++----------------- .../hooks/TransferPanel/useSetInputAmount.ts | 11 +++++++++- .../src/hooks/useArbQueryParams.tsx | 1 + .../src/util/NumberUtils.ts | 14 ------------- 7 files changed, 25 insertions(+), 46 deletions(-) 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 698d4d0222..9717ea7b0b 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -152,7 +152,7 @@ export function TransferPanel() { // Both `amount` getter and setter will internally be using `useArbQueryParams` functions const [{ amount }] = useArbQueryParams() - const setAmount = useSetInputAmount() + const { setAmount, setAmount2 } = useSetInputAmount() const [tokenImportDialogProps] = useDialog() const [tokenCheckDialogProps, openTokenCheckDialog] = useDialog() @@ -200,6 +200,7 @@ export function TransferPanel() { function clearAmountInput() { // clear amount input on transfer panel setAmount('') + setAmount2('') } useImportTokenModal({ @@ -1028,7 +1029,7 @@ export function TransferPanel() { 'sm:rounded sm:border' )} > - + setAmount(e.target.value)} /> {isExperimentalFeatureEnabled('batch') && @@ -158,9 +161,8 @@ export function SourceNetworkBox({ // eslint-disable-next-line maxButtonOnClick={() => {}} errorMessage={undefined} - value={''} - // eslint-disable-next-line - onChange={() => {}} + value={amount2} + onChange={e => setAmount2(e.target.value)} tokenButtonOptions={{ symbol: nativeCurrency.symbol, disabled: true diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx index 9ebbee6a3d..e05d7e4919 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMainInput.tsx @@ -1,14 +1,11 @@ import { twMerge } from 'tailwind-merge' -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' import { TokenButton, TokenButtonOptions } from './TokenButton' import { useNetworks } from '../../hooks/useNetworks' import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' import { useSelectedTokenBalances } from '../../hooks/TransferPanel/useSelectedTokenBalances' import { useAppState } from '../../state' -import { useSetInputAmount } from '../../hooks/TransferPanel/useSetInputAmount' -import { countDecimals } from '../../util/NumberUtils' -import { useSelectedTokenDecimals } from '../../hooks/TransferPanel/useSelectedTokenDecimals' import { useBalances } from '../../hooks/useBalances' import { TransferReadinessRichErrorMessage } from './useTransferReadinessUtils' import { ExternalLink } from '../common/ExternalLink' @@ -74,17 +71,7 @@ function MaxButton(props: React.ButtonHTMLAttributes) { function TransferPanelInputField( props: React.InputHTMLAttributes ) { - const { value = '', onChange, ...rest } = props - const setAmount = useSetInputAmount() - const decimals = useSelectedTokenDecimals() - - useEffect(() => { - // if number of decimals of query param value is greater than token decimals, - // truncate the decimals and update the amount query param value - if (countDecimals(String(value)) > decimals) { - setAmount(String(value)) - } - }, [value, decimals, setAmount]) + const { value = '', ...rest } = props return ( { - onChange?.(event) - setAmount(event.target.value) - }} {...rest} /> ) diff --git a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useSetInputAmount.ts b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useSetInputAmount.ts index f12ca9534b..a9f1ba6b23 100644 --- a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useSetInputAmount.ts +++ b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useSetInputAmount.ts @@ -17,5 +17,14 @@ export function useSetInputAmount() { [decimals, setQueryParams] ) - return setAmount + const setAmount2 = useCallback( + (newAmount: string) => { + const correctDecimalsAmount = truncateExtraDecimals(newAmount, 18) + + setQueryParams({ amount2: correctDecimalsAmount }) + }, + [setQueryParams] + ) + + return { setAmount, setAmount2 } } diff --git a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx index b66553598c..18e6e38738 100644 --- a/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx +++ b/packages/arb-token-bridge-ui/src/hooks/useArbQueryParams.tsx @@ -49,6 +49,7 @@ export const useArbQueryParams = () => { sourceChain: ChainParam, destinationChain: ChainParam, amount: withDefault(AmountQueryParam, ''), // amount which is filled in Transfer panel + amount2: withDefault(AmountQueryParam, ''), // extra eth to send together with erc20 token: StringParam, // import a new token using a Dialog Box settingsOpen: withDefault(BooleanParam, false) }) diff --git a/packages/arb-token-bridge-ui/src/util/NumberUtils.ts b/packages/arb-token-bridge-ui/src/util/NumberUtils.ts index d315a289f6..0ce966667b 100644 --- a/packages/arb-token-bridge-ui/src/util/NumberUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/NumberUtils.ts @@ -116,20 +116,6 @@ export const formatAmount = ( ) } -export const countDecimals = (num: number | string) => { - if (Math.floor(Number(num)) === Number(num)) { - return 0 - } - - const decimalPart = String(num).split('.')[1] - - if (typeof decimalPart === 'undefined') { - return 0 - } - - return decimalPart.length -} - export const truncateExtraDecimals = (amount: string, decimals: number) => { const decimalPart = amount.split('.')[1] From 1db11ac8bccbf97d3921373df5b90308699e22cf Mon Sep 17 00:00:00 2001 From: Bartek Date: Thu, 15 Aug 2024 18:56:13 +0200 Subject: [PATCH 06/10] feat: error messages for batched transfers (#1840) --- .../TransferPanel/TransferPanel.tsx | 10 +- .../TransferPanel/TransferPanelMain.tsx | 8 +- .../TransferPanelMain/SourceNetworkBox.tsx | 11 +- .../TransferPanel/useTransferReadiness.ts | 224 ++++++++++++------ 4 files changed, 157 insertions(+), 96 deletions(-) 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 9717ea7b0b..fc1005a8f6 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -57,7 +57,6 @@ import { } from './TransferPanelUtils' import { useImportTokenModal } from '../../hooks/TransferPanel/useImportTokenModal' import { useTransferReadiness } from './useTransferReadiness' -import { useGasSummary } from '../../hooks/TransferPanel/useGasSummary' import { useTransactionHistory } from '../../hooks/useTransactionHistory' import { getBridgeUiConfigForChain } from '../../util/bridgeUiConfig' import { useNetworks } from '../../hooks/useNetworks' @@ -180,12 +179,7 @@ export function TransferPanel() { const [isCctp, setIsCctp] = useState(false) - const gasSummary = useGasSummary() - - const { transferReady, errorMessage } = useTransferReadiness({ - amount, - gasSummary - }) + const { transferReady } = useTransferReadiness() const { color: destinationChainUIcolor } = getBridgeUiConfigForChain( networks.destinationChain.id @@ -1029,7 +1023,7 @@ export function TransferPanel() { 'sm:rounded sm:border' )} > - + diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx index f798dd6fbb..0ba190d690 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx @@ -34,18 +34,16 @@ import { AmountQueryParamEnum, useArbQueryParams } from '../../../hooks/useArbQueryParams' -import { TransferReadinessRichErrorMessage } from '../useTransferReadinessUtils' import { useMaxAmount } from './useMaxAmount' import { useSetInputAmount } from '../../../hooks/TransferPanel/useSetInputAmount' import { isExperimentalFeatureEnabled } from '../../../util' import { useDialog } from '../../common/Dialog' +import { useTransferReadiness } from '../useTransferReadiness' export function SourceNetworkBox({ - errorMessage, customFeeTokenBalances, showUsdcSpecificInfo }: { - errorMessage: string | TransferReadinessRichErrorMessage | undefined customFeeTokenBalances: Balances showUsdcSpecificInfo: boolean }) { @@ -66,6 +64,8 @@ export function SourceNetworkBox({ const [sourceNetworkSelectionDialogProps, openSourceNetworkSelectionDialog] = useDialog() + const { errorMessages } = useTransferReadiness() + const isMaxAmount = amount === AmountQueryParamEnum.MAX // whenever the user changes the `amount` input, it should update the amount in browser query params as well @@ -145,7 +145,7 @@ export function SourceNetworkBox({
setAmount(e.target.value)} /> @@ -156,11 +156,12 @@ export function SourceNetworkBox({ sourceChainId: networks.sourceChain.id, destinationChainId: networks.destinationChain.id }) && + isDepositMode && selectedToken && ( {}} - errorMessage={undefined} + errorMessage={errorMessages?.inputAmount2} value={amount2} onChange={e => setAmount2(e.target.value)} tokenButtonOptions={{ diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts index f6a5081dcc..34899b953b 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/useTransferReadiness.ts @@ -20,18 +20,27 @@ import { getSmartContractWalletTeleportTransfersNotSupportedErrorMessage } from './useTransferReadinessUtils' import { ether } from '../../constants' -import { UseGasSummaryResult } from '../../hooks/TransferPanel/useGasSummary' +import { + UseGasSummaryResult, + useGasSummary +} from '../../hooks/TransferPanel/useGasSummary' import { isTransferDisabledToken } from '../../util/TokenTransferDisabledUtils' import { useNetworks } from '../../hooks/useNetworks' import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' import { isTeleportEnabledToken } from '../../util/TokenTeleportEnabledUtils' import { isNetwork } from '../../util/networks' import { useBalances } from '../../hooks/useBalances' +import { useArbQueryParams } from '../../hooks/useArbQueryParams' // Add chains IDs that are currently down or disabled // It will block transfers and display an info box in the transfer panel export const DISABLED_CHAIN_IDS: number[] = [] +type ErrorMessages = { + inputAmount1?: string | TransferReadinessRichErrorMessage + inputAmount2?: string | TransferReadinessRichErrorMessage +} + function sanitizeEstimatedGasFees( gasSummary: UseGasSummaryResult, options: { isSmartContractWallet: boolean; isDepositMode: boolean } @@ -87,9 +96,9 @@ function ready() { function notReady( params: { - errorMessage: string | TransferReadinessRichErrorMessage | undefined + errorMessages: ErrorMessages | undefined } = { - errorMessage: undefined + errorMessages: undefined } ) { const result: UseTransferReadinessResult = { @@ -106,16 +115,11 @@ export type UseTransferReadinessTransferReady = { export type UseTransferReadinessResult = { transferReady: UseTransferReadinessTransferReady - errorMessage?: string | TransferReadinessRichErrorMessage + errorMessages?: ErrorMessages } -export function useTransferReadiness({ - amount, - gasSummary -}: { - amount: string - gasSummary: UseGasSummaryResult -}): UseTransferReadinessResult { +export function useTransferReadiness(): UseTransferReadinessResult { + const [{ amount, amount2 }] = useArbQueryParams() const { app: { selectedToken } } = useAppState() @@ -131,6 +135,7 @@ export function useTransferReadiness({ isTeleportMode } = useNetworksRelationship(networks) + const gasSummary = useGasSummary() const { address: walletAddress } = useAccount() const { isSmartContractWallet } = useAccountType() const nativeCurrency = useNativeCurrency({ provider: childChainProvider }) @@ -207,10 +212,48 @@ export function useTransferReadiness({ }, [nativeCurrency, erc20ParentBalances]) return useMemo(() => { - if (isNaN(Number(amount)) || Number(amount) === 0) { + const { estimatedL1GasFees, estimatedL2GasFees } = sanitizeEstimatedGasFees( + gasSummary, + { + isSmartContractWallet, + isDepositMode + } + ) + + const ethBalanceFloat = isDepositMode + ? ethL1BalanceFloat + : ethL2BalanceFloat + const selectedTokenBalanceFloat = isDepositMode + ? selectedTokenL1BalanceFloat + : selectedTokenL2BalanceFloat + const customFeeTokenBalanceFloat = isDepositMode + ? customFeeTokenL1BalanceFloat + : ethL2BalanceFloat + + // No error while loading balance + if (ethBalanceFloat === null) { return notReady() } + const sendsAdditionalEth = Number(amount2) > 0 + const notEnoughEthForAdditionalEthTransfer = + Number(amount2) > + ethBalanceFloat - (estimatedL1GasFees + estimatedL2GasFees) + + if (isNaN(Number(amount)) || Number(amount) === 0) { + return notReady({ + errorMessages: { + inputAmount2: + sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer + ? getInsufficientFundsErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name + }) + : undefined + } + }) + } + if (isTransferring) { return notReady() } @@ -222,18 +265,22 @@ export function useTransferReadiness({ // native currency (ETH or custom fee token) transfers using SC wallets not enabled yet if (isSmartContractWallet && !selectedToken) { return notReady({ - errorMessage: - getSmartContractWalletNativeCurrencyTransfersNotSupportedErrorMessage( - { asset: nativeCurrency.symbol } - ) + errorMessages: { + inputAmount1: + getSmartContractWalletNativeCurrencyTransfersNotSupportedErrorMessage( + { asset: nativeCurrency.symbol } + ) + } }) } // teleport transfers using SC wallets not enabled yet if (isSmartContractWallet && isTeleportMode) { return notReady({ - errorMessage: - getSmartContractWalletTeleportTransfersNotSupportedErrorMessage() + errorMessages: { + inputAmount1: + getSmartContractWalletTeleportTransfersNotSupportedErrorMessage() + } }) } @@ -242,21 +289,6 @@ export function useTransferReadiness({ return notReady() } - const ethBalanceFloat = isDepositMode - ? ethL1BalanceFloat - : ethL2BalanceFloat - const selectedTokenBalanceFloat = isDepositMode - ? selectedTokenL1BalanceFloat - : selectedTokenL2BalanceFloat - const customFeeTokenBalanceFloat = isDepositMode - ? customFeeTokenL1BalanceFloat - : ethL2BalanceFloat - - // No error while loading balance - if (ethBalanceFloat === null) { - return notReady() - } - // ERC-20 if (selectedToken) { const selectedTokenIsWithdrawOnly = isWithdrawOnlyToken( @@ -275,12 +307,16 @@ export function useTransferReadiness({ if (isDepositMode && selectedTokenIsWithdrawOnly) { return notReady({ - errorMessage: TransferReadinessRichErrorMessage.TOKEN_WITHDRAW_ONLY + errorMessages: { + inputAmount1: TransferReadinessRichErrorMessage.TOKEN_WITHDRAW_ONLY + } }) } else if (selectedTokenIsDisabled) { return notReady({ - errorMessage: - TransferReadinessRichErrorMessage.TOKEN_TRANSFER_DISABLED + errorMessages: { + inputAmount1: + TransferReadinessRichErrorMessage.TOKEN_TRANSFER_DISABLED + } }) } else if (withdrawalDisabled(selectedToken.address)) { return notReady() @@ -294,10 +330,19 @@ export function useTransferReadiness({ // Check amount against ERC-20 balance if (Number(amount) > selectedTokenBalanceFloat) { return notReady({ - errorMessage: getInsufficientFundsErrorMessage({ - asset: selectedToken.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: getInsufficientFundsErrorMessage({ + asset: selectedToken.symbol, + chain: networks.sourceChain.name + }), + inputAmount2: + sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer + ? getInsufficientFundsErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name + }) + : undefined + } }) } } @@ -311,10 +356,12 @@ export function useTransferReadiness({ // Check amount against custom fee token balance if (Number(amount) > customFeeTokenBalanceFloat) { return notReady({ - errorMessage: getInsufficientFundsErrorMessage({ - asset: nativeCurrency.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: getInsufficientFundsErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name + }) + } }) } } @@ -322,10 +369,12 @@ export function useTransferReadiness({ // Check amount against ETH balance else if (Number(amount) > ethBalanceFloat) { return notReady({ - errorMessage: getInsufficientFundsErrorMessage({ - asset: ether.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: getInsufficientFundsErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name + }) + } }) } @@ -340,19 +389,16 @@ export function useTransferReadiness({ case 'error': return notReady({ - errorMessage: TransferReadinessRichErrorMessage.GAS_ESTIMATION_FAILURE + errorMessages: { + inputAmount1: + TransferReadinessRichErrorMessage.GAS_ESTIMATION_FAILURE + } }) case 'insufficientBalance': return notReady() case 'success': { - const { estimatedL1GasFees, estimatedL2GasFees } = - sanitizeEstimatedGasFees(gasSummary, { - isSmartContractWallet, - isDepositMode - }) - if (selectedToken) { // If depositing into a custom fee token network, gas is split between ETH and the custom fee token if (nativeCurrency.isCustom && isDepositMode) { @@ -364,20 +410,24 @@ export function useTransferReadiness({ // We have to check if there's enough ETH to cover L1 gas if (estimatedL1GasFees > ethBalanceFloat) { return notReady({ - errorMessage: getInsufficientFundsForGasFeesErrorMessage({ - asset: ether.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: getInsufficientFundsForGasFeesErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name + }) + } }) } // We have to check if there's enough of the custom fee token to cover L2 gas if (estimatedL2GasFees > customFeeTokenL1BalanceFloat) { return notReady({ - errorMessage: getInsufficientFundsForGasFeesErrorMessage({ - asset: nativeCurrency.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: getInsufficientFundsForGasFeesErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name + }) + } }) } @@ -385,12 +435,29 @@ export function useTransferReadiness({ } // Everything is paid in ETH, so we sum it up - if (estimatedL1GasFees + estimatedL2GasFees > ethBalanceFloat) { + const notEnoughEthForGasFees = + estimatedL1GasFees + estimatedL2GasFees > ethBalanceFloat + + if ( + notEnoughEthForGasFees || + (sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer) + ) { return notReady({ - errorMessage: getInsufficientFundsForGasFeesErrorMessage({ - asset: ether.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: notEnoughEthForGasFees + ? getInsufficientFundsForGasFeesErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name + }) + : undefined, + inputAmount2: + sendsAdditionalEth && notEnoughEthForAdditionalEthTransfer + ? getInsufficientFundsErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name + }) + : undefined + } }) } @@ -402,10 +469,12 @@ export function useTransferReadiness({ // Withdrawals of the custom fee token will be treated same as ETH withdrawals (in the case below) if (estimatedL1GasFees + estimatedL2GasFees > ethBalanceFloat) { return notReady({ - errorMessage: getInsufficientFundsForGasFeesErrorMessage({ - asset: ether.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: getInsufficientFundsForGasFeesErrorMessage({ + asset: ether.symbol, + chain: networks.sourceChain.name + }) + } }) } @@ -418,10 +487,12 @@ export function useTransferReadiness({ if (total > ethBalanceFloat) { return notReady({ - errorMessage: getInsufficientFundsForGasFeesErrorMessage({ - asset: nativeCurrency.symbol, - chain: networks.sourceChain.name - }) + errorMessages: { + inputAmount1: getInsufficientFundsForGasFeesErrorMessage({ + asset: nativeCurrency.symbol, + chain: networks.sourceChain.name + }) + } }) } @@ -430,6 +501,7 @@ export function useTransferReadiness({ } }, [ amount, + amount2, isTransferring, destinationAddressError, isSmartContractWallet, From 2775a01e9b3617a9d398d8917f04b74444a7799d Mon Sep 17 00:00:00 2001 From: Bartek Date: Fri, 16 Aug 2024 12:24:51 +0200 Subject: [PATCH 07/10] fix: allow USDC selection if token bridge not loaded (#1849) --- .../src/components/TransferPanel/TokenSearch.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 a583a55d07..978b99a587 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TokenSearch.tsx @@ -555,10 +555,6 @@ export function TokenSearch({ return } - if (typeof bridgeTokens === 'undefined') { - return - } - try { // Native USDC on L2 won't have a corresponding L1 address const isNativeUSDC = @@ -600,6 +596,10 @@ export function TokenSearch({ return } + if (typeof bridgeTokens === 'undefined') { + return + } + // Token not added to the bridge, so we'll handle importing it if (typeof bridgeTokens[_token.address] === 'undefined') { setTokenQueryParam(_token.address) From 92bc2b22f837c8e82ab8adb07637c8f0afe646cc Mon Sep 17 00:00:00 2001 From: Bartek Date: Fri, 16 Aug 2024 13:40:40 +0200 Subject: [PATCH 08/10] fix: set correct max amount from query params (#1850) --- .../TransferPanelMain/SourceNetworkBox.tsx | 2 -- .../TransferPanel/TransferPanelMain/useMaxAmount.ts | 13 ++++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx index 0ba190d690..26f98fd98e 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx @@ -72,8 +72,6 @@ export function SourceNetworkBox({ useEffect(() => { if (isMaxAmount && typeof maxAmount !== 'undefined') { setAmount(maxAmount) - } else { - setAmount(amount) } }, [amount, maxAmount, isMaxAmount, setAmount]) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts index d023eba854..a54ecad203 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts @@ -38,7 +38,7 @@ export function useMaxAmount({ : selectedTokenBalances.childBalance if (!tokenBalance) { - return + return undefined } // For token deposits and withdrawals, we can set the max amount, as gas fees are paid in ETH / custom fee token @@ -68,7 +68,7 @@ export function useMaxAmount({ : ethChildBalance if (!nativeCurrencyBalance) { - return + return undefined } const nativeCurrencyBalanceFormatted = utils.formatUnits( @@ -76,8 +76,15 @@ export function useMaxAmount({ nativeCurrency.decimals ) + if ( + typeof estimatedParentChainGasFees === 'undefined' || + typeof estimatedChildChainGasFees === 'undefined' + ) { + return undefined + } + const estimatedTotalGasFees = - (estimatedParentChainGasFees ?? 0) + (estimatedChildChainGasFees ?? 0) + estimatedParentChainGasFees + estimatedChildChainGasFees const maxAmount = parseFloat(nativeCurrencyBalanceFormatted) - estimatedTotalGasFees * 1.4 From a74999ea372b27e8622fadb455efc38e3785142f Mon Sep 17 00:00:00 2001 From: Bartek Date: Fri, 16 Aug 2024 15:24:00 +0200 Subject: [PATCH 09/10] feat: max amount for batched transfers (#1841) --- .../TransferPanelMain/SourceNetworkBox.tsx | 20 ++++-- .../TransferPanelMain/useMaxAmount.ts | 72 ++++++++++++------- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx index 26f98fd98e..db050e34bd 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx @@ -58,7 +58,7 @@ export function SourceNetworkBox({ const nativeCurrency = useNativeCurrency({ provider: childChainProvider }) const [{ amount, amount2 }] = useArbQueryParams() const { setAmount, setAmount2 } = useSetInputAmount() - const { maxAmount } = useMaxAmount({ + const { maxAmount, maxAmount2 } = useMaxAmount({ customFeeTokenBalances }) const [sourceNetworkSelectionDialogProps, openSourceNetworkSelectionDialog] = @@ -67,20 +67,33 @@ export function SourceNetworkBox({ const { errorMessages } = useTransferReadiness() const isMaxAmount = amount === AmountQueryParamEnum.MAX + const isMaxAmount2 = amount2 === AmountQueryParamEnum.MAX - // whenever the user changes the `amount` input, it should update the amount in browser query params as well + // covers MAX string from query params useEffect(() => { if (isMaxAmount && typeof maxAmount !== 'undefined') { setAmount(maxAmount) } }, [amount, maxAmount, isMaxAmount, setAmount]) + useEffect(() => { + if (isMaxAmount2 && typeof maxAmount2 !== 'undefined') { + setAmount2(maxAmount2) + } + }, [amount2, maxAmount2, isMaxAmount2, setAmount2]) + const maxButtonOnClick = useCallback(() => { if (typeof maxAmount !== 'undefined') { setAmount(maxAmount) } }, [maxAmount, setAmount]) + const amount2MaxButtonOnClick = useCallback(() => { + if (typeof maxAmount2 !== 'undefined') { + setAmount2(maxAmount2) + } + }, [maxAmount2, setAmount2]) + return ( <> @@ -157,8 +170,7 @@ export function SourceNetworkBox({ isDepositMode && selectedToken && ( {}} + maxButtonOnClick={amount2MaxButtonOnClick} errorMessage={errorMessages?.inputAmount2} value={amount2} onChange={e => setAmount2(e.target.value)} diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts index a54ecad203..b9c6d9a14d 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/useMaxAmount.ts @@ -31,23 +31,7 @@ export function useMaxAmount({ const { estimatedParentChainGasFees, estimatedChildChainGasFees } = useGasSummary() - const maxAmount = useMemo(() => { - if (selectedToken) { - const tokenBalance = isDepositMode - ? selectedTokenBalances.parentBalance - : selectedTokenBalances.childBalance - - if (!tokenBalance) { - return undefined - } - - // For token deposits and withdrawals, we can set the max amount, as gas fees are paid in ETH / custom fee token - return utils.formatUnits( - tokenBalance, - selectedToken?.decimals ?? defaultErc20Decimals - ) - } - + const nativeCurrencyMaxAmount = useMemo(() => { const customFeeTokenParentBalance = customFeeTokenBalances.parentBalance // For custom fee token deposits, we can set the max amount, as the fees will be paid in ETH if ( @@ -61,8 +45,7 @@ export function useMaxAmount({ ) } - // We have already handled token deposits and deposits of the custom fee token - // The remaining cases are ETH deposits, and ETH/custom fee token withdrawals (which can be handled in the same case) + // ETH deposits and ETH/custom fee token withdrawals const nativeCurrencyBalance = isDepositMode ? ethParentBalance : ethChildBalance @@ -97,18 +80,55 @@ export function useMaxAmount({ return nativeCurrencyBalanceFormatted }, [ - nativeCurrency, - ethParentBalance, + customFeeTokenBalances.parentBalance, + estimatedChildChainGasFees, + estimatedParentChainGasFees, ethChildBalance, + ethParentBalance, isDepositMode, + nativeCurrency.decimals, + nativeCurrency.isCustom + ]) + + const maxAmount = useMemo(() => { + if (selectedToken) { + const tokenBalance = isDepositMode + ? selectedTokenBalances.parentBalance + : selectedTokenBalances.childBalance + + if (!tokenBalance) { + return undefined + } + + // For token deposits and withdrawals, we can set the max amount, as gas fees are paid in ETH / custom fee token + return utils.formatUnits( + tokenBalance, + selectedToken?.decimals ?? defaultErc20Decimals + ) + } + + return nativeCurrencyMaxAmount + }, [ selectedToken, - selectedTokenBalances, - estimatedParentChainGasFees, - estimatedChildChainGasFees, - customFeeTokenBalances + isDepositMode, + nativeCurrencyMaxAmount, + selectedTokenBalances.parentBalance, + selectedTokenBalances.childBalance ]) + const maxAmount2 = useMemo(() => { + if (!isDepositMode) { + return undefined + } + if (nativeCurrency.isCustom) { + return undefined + } + + return nativeCurrencyMaxAmount + }, [isDepositMode, nativeCurrency.isCustom, nativeCurrencyMaxAmount]) + return { - maxAmount + maxAmount, + maxAmount2 } } From 647e7d8c5f62188da0ee16eceefeefc6408dd2f8 Mon Sep 17 00:00:00 2001 From: Bartek Date: Mon, 19 Aug 2024 17:02:43 +0200 Subject: [PATCH 10/10] feat: send ETH with batched transfers (#1847) --- .../TransferPanel/TransferPanel.tsx | 41 ++++++++++++++++--- .../TransferPanelMain/SourceNetworkBox.tsx | 34 +++++++-------- .../useIsBatchTransferSupported.ts | 35 ++++++++++++++++ .../token-bridge-sdk/BridgeTransferStarter.ts | 6 +++ .../token-bridge-sdk/Erc20DepositStarter.ts | 10 ++++- 5 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts 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 fc1005a8f6..ed274b56d9 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -1,7 +1,7 @@ import dayjs from 'dayjs' import { useState, useMemo } from 'react' import Tippy from '@tippyjs/react' -import { constants, utils } from 'ethers' +import { BigNumber, constants, utils } from 'ethers' import { useLatest } from 'react-use' import { useAccount, useChainId, useSigner } from 'wagmi' import { TransactionResponse } from '@ethersproject/providers' @@ -49,7 +49,10 @@ import { isUserRejectedError } from '../../util/isUserRejectedError' import { getUsdcTokenAddressFromSourceChainId } from '../../state/cctpState' import { DepositStatus, MergedTransaction } from '../../state/app/state' import { useNativeCurrency } from '../../hooks/useNativeCurrency' -import { AssetType } from '../../hooks/arbTokenBridge.types' +import { + AssetType, + DepositGasEstimates +} from '../../hooks/arbTokenBridge.types' import { ImportTokenModalStatus, getWarningTokenDescription, @@ -63,7 +66,10 @@ import { useNetworks } from '../../hooks/useNetworks' import { useNetworksRelationship } from '../../hooks/useNetworksRelationship' import { CctpTransferStarter } from '@/token-bridge-sdk/CctpTransferStarter' import { BridgeTransferStarterFactory } from '@/token-bridge-sdk/BridgeTransferStarterFactory' -import { BridgeTransfer } from '@/token-bridge-sdk/BridgeTransferStarter' +import { + BridgeTransfer, + TransferOverrides +} from '@/token-bridge-sdk/BridgeTransferStarter' import { addDepositToCache } from '../TransactionHistory/helpers' import { convertBridgeSdkToMergedTransaction, @@ -74,6 +80,7 @@ import { useSetInputAmount } from '../../hooks/TransferPanel/useSetInputAmount' import { getSmartContractWalletTeleportTransfersNotSupportedErrorMessage } from './useTransferReadinessUtils' import { useBalances } from '../../hooks/useBalances' import { captureSentryErrorWithExtraData } from '../../util/SentryUtils' +import { useIsBatchTransferSupported } from '../../hooks/TransferPanel/useIsBatchTransferSupported' const networkConnectionWarningToast = () => warningToast( @@ -124,6 +131,7 @@ export function TransferPanel() { isTeleportMode } = useNetworksRelationship(networks) const latestNetworks = useLatest(networks) + const isBatchTransferSupported = useIsBatchTransferSupported() const nativeCurrency = useNativeCurrency({ provider: childChainProvider }) @@ -149,7 +157,7 @@ export function TransferPanel() { // Link the amount state directly to the amount in query params - no need of useState // Both `amount` getter and setter will internally be using `useArbQueryParams` functions - const [{ amount }] = useArbQueryParams() + const [{ amount, amount2 }] = useArbQueryParams() const { setAmount, setAmount2 } = useSetInputAmount() @@ -856,11 +864,34 @@ export function TransferPanel() { ) } + const overrides: TransferOverrides = {} + + const isBatchTransfer = isBatchTransferSupported && Number(amount2) > 0 + + if (isBatchTransfer) { + // when sending additional ETH with ERC-20, we add the additional ETH value as maxSubmissionCost + const gasEstimates = (await bridgeTransferStarter.transferEstimateGas({ + amount: amountBigNumber, + signer + })) as DepositGasEstimates + + if (!gasEstimates.estimatedChildChainSubmissionCost) { + errorToast('Failed to estimate deposit maxSubmissionCost') + throw 'Failed to estimate deposit maxSubmissionCost' + } + + overrides.maxSubmissionCost = utils + .parseEther(amount2) + .add(gasEstimates.estimatedChildChainSubmissionCost) + overrides.excessFeeRefundAddress = destinationAddress + } + // finally, call the transfer function const transfer = await bridgeTransferStarter.transfer({ amount: amountBigNumber, signer, - destinationAddress + destinationAddress, + overrides: Object.keys(overrides).length > 0 ? overrides : undefined }) // transaction submitted callback diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx index db050e34bd..237c001d13 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanelMain/SourceNetworkBox.tsx @@ -36,9 +36,9 @@ import { } from '../../../hooks/useArbQueryParams' import { useMaxAmount } from './useMaxAmount' import { useSetInputAmount } from '../../../hooks/TransferPanel/useSetInputAmount' -import { isExperimentalFeatureEnabled } from '../../../util' import { useDialog } from '../../common/Dialog' import { useTransferReadiness } from '../useTransferReadiness' +import { useIsBatchTransferSupported } from '../../../hooks/TransferPanel/useIsBatchTransferSupported' export function SourceNetworkBox({ customFeeTokenBalances, @@ -63,6 +63,7 @@ export function SourceNetworkBox({ }) const [sourceNetworkSelectionDialogProps, openSourceNetworkSelectionDialog] = useDialog() + const isBatchTransferSupported = useIsBatchTransferSupported() const { errorMessages } = useTransferReadiness() @@ -161,25 +162,18 @@ export function SourceNetworkBox({ onChange={e => setAmount(e.target.value)} /> - {isExperimentalFeatureEnabled('batch') && - // TODO: teleport is disabled for now but it needs to be looked into more to check whether it is or can be supported - !isTeleport({ - sourceChainId: networks.sourceChain.id, - destinationChainId: networks.destinationChain.id - }) && - isDepositMode && - selectedToken && ( - setAmount2(e.target.value)} - tokenButtonOptions={{ - symbol: nativeCurrency.symbol, - disabled: true - }} - /> - )} + {isBatchTransferSupported && ( + setAmount2(e.target.value)} + tokenButtonOptions={{ + symbol: nativeCurrency.symbol, + disabled: true + }} + /> + )} {showUsdcSpecificInfo && (

diff --git a/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts new file mode 100644 index 0000000000..13c592d0cb --- /dev/null +++ b/packages/arb-token-bridge-ui/src/hooks/TransferPanel/useIsBatchTransferSupported.ts @@ -0,0 +1,35 @@ +import { useAppState } from '../../state' +import { isExperimentalFeatureEnabled } from '../../util' +import { useNativeCurrency } from '../useNativeCurrency' +import { useNetworks } from '../useNetworks' +import { useNetworksRelationship } from '../useNetworksRelationship' + +export const useIsBatchTransferSupported = () => { + const [networks] = useNetworks() + const { isDepositMode, isTeleportMode, childChainProvider } = + useNetworksRelationship(networks) + const { + app: { selectedToken } + } = useAppState() + const nativeCurrency = useNativeCurrency({ provider: childChainProvider }) + + if (!isExperimentalFeatureEnabled('batch')) { + return false + } + if (!selectedToken) { + return false + } + if (!isDepositMode) { + return false + } + // TODO: teleport is disabled for now but it needs to be looked into more to check whether it is or can be supported + if (isTeleportMode) { + return false + } + // TODO: disable custom native currency for now, check if this works + if (nativeCurrency.isCustom) { + return false + } + + return true +} diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts index 3efd4df181..7dcbafa9ef 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts @@ -48,10 +48,16 @@ export type TransferEstimateGas = { signer: Signer } +export type TransferOverrides = { + maxSubmissionCost?: BigNumber + excessFeeRefundAddress?: string +} + export type TransferProps = { amount: BigNumber signer: Signer destinationAddress?: string + overrides?: TransferOverrides } export type RequiresNativeCurrencyApprovalProps = { diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts index 7d90d2b4e9..d11dcbdcf3 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts @@ -273,7 +273,12 @@ export class Erc20DepositStarter extends BridgeTransferStarter { }) } - public async transfer({ amount, signer, destinationAddress }: TransferProps) { + public async transfer({ + amount, + signer, + destinationAddress, + overrides + }: TransferProps) { if (!this.sourceChainErc20Address) { throw Error('Erc20 token address not found') } @@ -292,7 +297,8 @@ export class Erc20DepositStarter extends BridgeTransferStarter { // the gas limit may vary by about 20k due to SSTORE (zero vs nonzero) // the 30% gas limit increase should cover the difference gasLimit: { percentIncrease: BigNumber.from(30) } - } + }, + ...overrides }) const gasLimit = await this.sourceChainProvider.estimateGas(