diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index 8c11314d..0cb1e638 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -1,9 +1,10 @@ name: Integration Test on: - push: - branches: ['main', 'master'] - pull_request: + # push: + # branches: ['main', 'master'] + # pull_request: + workflow_dispatch jobs: e2e-test: diff --git a/e2e-tests/lib/gatewayAction.ts b/e2e-tests/lib/gatewayAction.ts index 1495275f..74c69d80 100644 --- a/e2e-tests/lib/gatewayAction.ts +++ b/e2e-tests/lib/gatewayAction.ts @@ -15,6 +15,17 @@ const networkConfig = { }, } +const networkNames = { + eth: { + fromNetwork: 'Ethereum (Sepolia)', + toNetwork: 'Boba (Sepolia)', + }, + bnb: { + fromNetwork: 'BNB Testnet', + toNetwork: 'Boba BNB Testnet', + }, +} + export class GatewayAction { basePage: BasePage bridgePage: BridgePage @@ -29,11 +40,13 @@ export class GatewayAction { tokenSymbol, successWaitTime = 1000, approveAllowance = false, + networkKey = 'eth', }: { amountToBridge: string tokenSymbol: string successWaitTime?: number approveAllowance?: boolean + networkKey?: 'bnb' | 'eth' }) { await this.bridgePage.openTokenPickerAndSelect(tokenSymbol) await this.bridgePage.bridgeButtonToBeDisable() @@ -48,8 +61,7 @@ export class GatewayAction { await this.bridgePage.validateAndConfirmBridging({ amount: amountToBridge, token: tokenSymbol, - fromNetwork: 'Ethereum (Sepolia)', - toNetwork: 'Boba (Sepolia)', + ...networkNames[networkKey], estimatedTime: '13 ~ 14mins.', }) if (approveAllowance) { @@ -65,9 +77,11 @@ export class GatewayAction { async classicBridgeWithdrawal({ amountToBridge, tokenSymbol, + networkKey = 'eth', }: { amountToBridge: string tokenSymbol: string + networkKey?: 'bnb' | 'eth' }) { await this.bridgePage.openTokenPickerAndSelect(tokenSymbol) await this.bridgePage.bridgeButtonToBeDisable() @@ -82,8 +96,7 @@ export class GatewayAction { await this.bridgePage.validateAndConfirmBridging({ amount: amountToBridge, token: tokenSymbol, - fromNetwork: 'Boba (Sepolia)', - toNetwork: 'Ethereum (Sepolia)', + ...networkNames[networkKey], estimatedTime: '7 days', }) await this.bridgePage.reviewAndInitiateWithdrawal() @@ -180,7 +193,13 @@ export class GatewayAction { await this.basePage.wait(1000) } - async switchNetwork() { + async switchL2Network() { await this.basePage.clickToSwitchNetwork() } + + async addAndConnectBNBTestnet() { + await this.connectToTestnet() + await this.basePage.clickAndSwitchToBnb() + await this.basePage.wait(1000) + } } diff --git a/e2e-tests/pages/basePage.ts b/e2e-tests/pages/basePage.ts index 80da30d3..67b2a3a0 100644 --- a/e2e-tests/pages/basePage.ts +++ b/e2e-tests/pages/basePage.ts @@ -98,6 +98,27 @@ export class BasePage { await metamask.allowToAddAndSwitchNetwork() } + async clickAndSwitchToBnb() { + await this.page.getByTestId('from-network-picker').click() + await expect( + this.page.getByRole('heading', { level: 2, name: 'Select Network' }) + ).toBeVisible() + + await expect(this.page.getByText('Network Names')).toBeVisible() + + await this.page.getByTestId('selector-bnb').click() + + await this.page.waitForTimeout(1000) + + await this.page.getByTestId('switch-network-btn').click() + + await this.page.waitForTimeout(2000) + + await this.page.getByText('Connect to the Bnb Testnet network').click() + + await metamask.allowToAddAndSwitchNetwork() + } + // disconnect async disconnectMetamask() { await this.page.getByTestId('label-address').click() diff --git a/e2e-tests/pages/bridgePage.ts b/e2e-tests/pages/bridgePage.ts index e83722a9..a6cc14d4 100644 --- a/e2e-tests/pages/bridgePage.ts +++ b/e2e-tests/pages/bridgePage.ts @@ -41,7 +41,7 @@ export class BridgePage extends BasePage { estimatedTime: string }) { await expect(this.page.getByTestId('amountToRecieve')).toHaveText( - `${amount} ${token}` + `${Number(amount).toFixed(4)} ${token}` ) const estTime = await this.page @@ -55,7 +55,7 @@ export class BridgePage extends BasePage { .textContent() // TODO: update value with fee calculation. - expect(estRecievable).toBe(`${amount} ${token}`) + expect(estRecievable).toBe(`${Number(amount).toFixed(4)} ${token}`) } async clickToBridge() { diff --git a/e2e-tests/playwright.config.ts b/e2e-tests/playwright.config.ts index ac311988..872f8f37 100644 --- a/e2e-tests/playwright.config.ts +++ b/e2e-tests/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, devices } from '@playwright/test' +import { defineConfig } from '@playwright/test' export default defineConfig({ testDir: './tests', timeout: 30 * 1000, diff --git a/e2e-tests/tests/bnb.bridge.spec.ts b/e2e-tests/tests/bnb.bridge.spec.ts new file mode 100644 index 00000000..ff921fa3 --- /dev/null +++ b/e2e-tests/tests/bnb.bridge.spec.ts @@ -0,0 +1,53 @@ +import { test } from '../fixture/synpress' +import { GatewayAction } from '../lib/gatewayAction' +import { BasePage } from '../pages/basePage' + +const amountToBridge: string = '0.0001' + +test.beforeEach(async ({ page }) => { + await page.goto('/') +}) + +test.describe('Gateway BNB (Testnet)', () => { + test.describe('Classic Bridge', () => { + test.describe('Deposit', () => { + test('tBNB', async ({ page }) => { + test.setTimeout(120000) + const bridgeAction = new GatewayAction(page) + await bridgeAction.addAndConnectBNBTestnet() + await bridgeAction.classicBridgeDeposit({ + amountToBridge, + tokenSymbol: 'tBNB', + networkKey: 'bnb', + }) + }) + test('BOBA', async ({ page }) => { + test.setTimeout(120000) + const bridgeAction = new GatewayAction(page) + await bridgeAction.addAndConnectBNBTestnet() + await bridgeAction.classicBridgeDeposit({ + amountToBridge, + tokenSymbol: 'BOBA', + approveAllowance: true, + networkKey: 'bnb', + }) + }) + }) + test.describe('Withdraw', () => { + test.skip('BOBA', async ({ page }) => { + test.setTimeout(120000) + const basePage = new BasePage(page) + const bridgeAction = new GatewayAction(page) + await bridgeAction.addAndConnectBNBTestnet() + await bridgeAction.switchL2Network() + page.waitForTimeout(2000) + await basePage.connectToMetamask(true) + await bridgeAction.classicBridgeWithdrawal({ + amountToBridge: '0.00001', + tokenSymbol: 'tBNB', + networkKey: 'bnb', + }) + }) + }) + }) +}) diff --git a/e2e-tests/tests/bridging.spec.ts b/e2e-tests/tests/bridging.spec.ts index 84380e55..89dc1c26 100644 --- a/e2e-tests/tests/bridging.spec.ts +++ b/e2e-tests/tests/bridging.spec.ts @@ -18,6 +18,7 @@ test.describe('Gateway ETHEREUM (Sepolia)', () => { await bridgeAction.classicBridgeDeposit({ amountToBridge, tokenSymbol: 'ETH', + networkKey: 'eth', }) }) @@ -29,6 +30,7 @@ test.describe('Gateway ETHEREUM (Sepolia)', () => { amountToBridge, tokenSymbol: 'BOBA', approveAllowance: true, + networkKey: 'eth', }) }) }) @@ -39,7 +41,7 @@ test.describe('Gateway ETHEREUM (Sepolia)', () => { const basePage = new BasePage(page) const bridgeAction = new GatewayAction(page) await bridgeAction.connectToTestnet() // connected to L1. - await bridgeAction.switchNetwork() + await bridgeAction.switchL2Network() await basePage.disconnectMetamask() await basePage.connectToMetamask(true) await bridgeAction.classicBridgeWithdrawal({ @@ -52,12 +54,12 @@ test.describe('Gateway ETHEREUM (Sepolia)', () => { const basePage = new BasePage(page) const bridgeAction = new GatewayAction(page) await bridgeAction.connectToTestnet() // connected to L1. - await bridgeAction.switchNetwork() + await bridgeAction.switchL2Network() await basePage.disconnectMetamask() await basePage.connectToMetamask(true) await bridgeAction.classicBridgeWithdrawal({ amountToBridge, - tokenSymbol: 'ETH', + tokenSymbol: 'BOBA', }) }) }) @@ -93,7 +95,7 @@ test.describe('Gateway ETHEREUM (Sepolia)', () => { const bridgeAction = new GatewayAction(page) const basePage = new BasePage(page) await bridgeAction.connectToTestnet() - await bridgeAction.switchNetwork() + await bridgeAction.switchL2Network() await basePage.disconnectMetamask() await basePage.connectToMetamask(true) await bridgeAction.lightBridgeWithdraw({ @@ -106,7 +108,7 @@ test.describe('Gateway ETHEREUM (Sepolia)', () => { const bridgeAction = new GatewayAction(page) const basePage = new BasePage(page) await bridgeAction.connectToTestnet() - await bridgeAction.switchNetwork() + await bridgeAction.switchL2Network() await basePage.disconnectMetamask() await basePage.connectToMetamask(true) await bridgeAction.lightBridgeWithdraw({ diff --git a/package.json b/package.json index 98e7f68a..7993fdc1 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@bobanetwork/graphql-utils": "^1.1.15", "@bobanetwork/light-bridge-chains": "^1.1.0", - "@bobanetwork/register": "^0.0.25", + "@bobanetwork/register": "^0.0.26", "@bobanetwork/sdk": "1.0.7", "@cfx-kit/wallet-avatar": "^0.0.5", "@emotion/styled": "^11.11.0", @@ -91,7 +91,7 @@ "@babel/preset-react": "^7.23.3", "@babel/preset-typescript": "^7.24.1", "@sentry/types": "^8.13.0", - "@testing-library/jest-dom": "^6.1.4", + "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^12.1.2", "@testing-library/react-hooks": "^8.0.1", "@types/jest": "^29.5.1", @@ -110,7 +110,7 @@ "eslint-plugin-jsdoc": "^46.9.1", "eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react": "^7.34.4", "eslint-plugin-testing-library": "^6.2.0", "eslint-plugin-unicorn": "^50.0.1", "husky": "^8.0.3", diff --git a/src/actions/createAction.ts b/src/actions/createAction.ts index 9b1ef6a5..af430bd4 100644 --- a/src/actions/createAction.ts +++ b/src/actions/createAction.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ import * as Sentry from '@sentry/react' +import { ERROR_CODE } from 'util/constant' export const createAction = (key: string, asyncAction: () => any): any => @@ -66,8 +67,8 @@ export const createAction = errorMessage = 'Transaction rejected by user!' } } else { - errorMessage = response.reason - Sentry.captureMessage(response.reason || '') + errorMessage = response.message + Sentry.captureMessage(response.message || '') } if (response.reason?.includes('could not detect network')) { @@ -93,6 +94,13 @@ export const createAction = dispatch({ type: `UI/ERROR/UPDATE`, payload: errorMessage }) dispatch({ type: `${key}/ERROR` }) return false + } else if ( + response.hasOwnProperty('message') && + response.message.includes(ERROR_CODE) + ) { + dispatch({ type: `UI/ERROR/UPDATE`, payload: response.message }) + dispatch({ type: `${key}/ERROR` }) + return false } dispatch({ type: `${key}/SUCCESS`, payload: response }) diff --git a/src/actions/networkAction.ts b/src/actions/networkAction.ts index 149197e1..014b30b8 100644 --- a/src/actions/networkAction.ts +++ b/src/actions/networkAction.ts @@ -17,6 +17,7 @@ import networkService from 'services/networkService' import transactionService from 'services/transaction.service' import { createAction } from './createAction' import { BigNumberish } from 'ethers' +import { bridgeService } from 'services' import { lightBridgeService } from 'services/teleportation.service' export const fetchBalances = () => @@ -81,10 +82,10 @@ export const depositETHL2 = (payload) => return networkService.depositETHL2(payload) }) -//CLASSIC DEPOSIT ETH -export const depositETHAnchorageL2 = (payload) => - createAction('DEPOSIT/CREATE', () => { - return networkService.depositETHAnchorage(payload) +// Anchorage CLASSIC DEPOSIT ETH +export const depositNativeAnchorage = (payload) => + createAction('DEPOSIT_ANCHORAGE/CREATE', () => { + return bridgeService.anchorageDepositNative(payload) }) //DEPOSIT ERC20 @@ -93,7 +94,12 @@ export const depositErc20 = (payload) => export const depositErc20Anchorage = (payload) => createAction('DEPOSIT/CREATE', () => - networkService.depositERC20Anchorage(payload) + bridgeService.anchorageDepositERC20(payload) + ) + +export const depositErc20AnchorageOptimism = (payload) => + createAction('DEPOSIT/CREATE', () => + bridgeService.anchorageDepositERC20Optimism(payload) ) export const approveERC20 = ( diff --git a/src/components/alert/Alert.tsx b/src/components/alert/Alert.tsx index 1a0ecb1b..14250586 100644 --- a/src/components/alert/Alert.tsx +++ b/src/components/alert/Alert.tsx @@ -50,10 +50,10 @@ const Toast = ({ autoHideDuration={autohide ? autohide : undefined} onClose={onClose} anchorOrigin={{ - vertical: 'top', - horizontal: 'center', + vertical: 'bottom', + horizontal: 'right', }} - style={{ marginTop: 50 }} + style={{ marginBottom: 100 }} > , diff --git a/src/components/bridge/NetworkPickerList/index.tsx b/src/components/bridge/NetworkPickerList/index.tsx index 361b9e17..7f077a47 100644 --- a/src/components/bridge/NetworkPickerList/index.tsx +++ b/src/components/bridge/NetworkPickerList/index.tsx @@ -144,6 +144,7 @@ export const NetworkList: FC = ({ className={'networkItem'} selected={selected} key={`${chainDetail.label}_${layer}_${chainDetail.key}`} + data-testid={`selector-${chainDetail.key}`} onClick={() => onChainChange(chainDetail, layer)} > diff --git a/src/components/bridge/NetworkPickerList/tests/__snapshots__/networkPickerList.test.tsx.snap b/src/components/bridge/NetworkPickerList/tests/__snapshots__/networkPickerList.test.tsx.snap index 6b1c55ba..48a4f505 100644 --- a/src/components/bridge/NetworkPickerList/tests/__snapshots__/networkPickerList.test.tsx.snap +++ b/src/components/bridge/NetworkPickerList/tests/__snapshots__/networkPickerList.test.tsx.snap @@ -7,6 +7,7 @@ exports[`NetworkList Component renders NetworkList correctly 1`] = ` >
= ({ name={name} placeholder={placeholder} onChange={onChange} + onWheel={(e: any) => { + e.target.blur() + e.stopPropagation() + setTimeout(() => { + e.target.focus() + }, 0) + }} /> {buttonLabel} diff --git a/src/containers/modals/BridgeConfirmModal/index.tsx b/src/containers/modals/BridgeConfirmModal/index.tsx index 4d7a7819..c0175e28 100644 --- a/src/containers/modals/BridgeConfirmModal/index.tsx +++ b/src/containers/modals/BridgeConfirmModal/index.tsx @@ -10,6 +10,7 @@ import { selectActiveNetworkIcon, selectActiveNetworkName, selectAmountToBridge, + selectBridgeDestinationAddress, selectBridgeType, selectDestChainIdTeleportation, selectLayer, @@ -26,6 +27,7 @@ import { Item, LayerNames, } from './index.styles' +import truncateMiddle from 'truncate-middle' interface Props { open: boolean @@ -35,6 +37,7 @@ const BridgeConfirmModal: FC = ({ open }) => { const dispatch = useDispatch() const bridgeType = useSelector(selectBridgeType()) const token = useSelector(selectTokenToBridge()) + const toL2Account = useSelector(selectBridgeDestinationAddress()) const amountToBridge = useSelector(selectAmountToBridge()) const networkNames = useSelector(selectActiveNetworkName()) const activeNetworkIcon = useSelector(selectActiveNetworkIcon()) @@ -131,6 +134,14 @@ const BridgeConfirmModal: FC = ({ open }) => { {amountToUsd(amountToBridge, lookupPrice, token).toFixed(4)}) + {toL2Account && ( + + Recipient + + {truncateMiddle(toL2Account!, 6, 6, '...')} + + + )} Time {estimateTime()} diff --git a/src/containers/modals/TransactionSuccessModal/index.tsx b/src/containers/modals/TransactionSuccessModal/index.tsx index 4b16f731..b4a6623c 100644 --- a/src/containers/modals/TransactionSuccessModal/index.tsx +++ b/src/containers/modals/TransactionSuccessModal/index.tsx @@ -115,7 +115,6 @@ const TransactionSuccessModal: FC< transparent={true} onClick={() => { handleClose() - navigate('/history') }} label="Close" /> diff --git a/src/hooks/useBridge/index.test.tsx b/src/hooks/useBridge/index.test.tsx index 5e2e0e49..fd136975 100644 --- a/src/hooks/useBridge/index.test.tsx +++ b/src/hooks/useBridge/index.test.tsx @@ -12,6 +12,13 @@ import networkService from 'services/networkService' import { lightBridgeService } from 'services/teleportation.service' import useBridge from '.' +import { bridgeService } from 'services' + +jest.mock('services/bridge/bridge.services', () => { + return { + anchorageDepositERC20: jest.fn(), + } +}) jest.mock('services/teleportation.service', () => { return { @@ -25,7 +32,6 @@ jest.mock('services/networkService', () => { getAllAddresses: jest.fn(), approveERC20: jest.fn(), depositErc20: jest.fn(), - depositERC20Anchorage: jest.fn(), depositETHL2: jest.fn(), exitBOBA: jest.fn(), } @@ -167,7 +173,7 @@ describe('UseBridge Hooks', () => { }) describe('Layer 1', () => { - describe('Classic Bridge', () => { + xdescribe('Classic Bridge', () => { beforeEach(() => { ;(networkService.depositETHL2 as jest.Mock).mockResolvedValue({ message: 'success!', @@ -175,7 +181,7 @@ describe('UseBridge Hooks', () => { ;(networkService.depositErc20 as jest.Mock).mockResolvedValue({ message: 'success!', }) - ;(networkService.depositERC20Anchorage as jest.Mock).mockResolvedValue({ + ;(bridgeService.anchorageDepositERC20 as jest.Mock).mockResolvedValue({ message: 'success!', }) @@ -319,7 +325,7 @@ describe('UseBridge Hooks', () => { expect(actions).toEqual(successActionsClassic) }) - test('should invoke depositErc20Anchorage correctly and reset state on success', async () => { + test('should invoke anchorageDepositERC20 correctly and reset state on success', async () => { store = mockStore({ ...store, bridge: { @@ -339,10 +345,10 @@ describe('UseBridge Hooks', () => { let prevActions = store.getActions() await result.current.triggerSubmit() expect( - (networkService.depositERC20Anchorage as jest.Mock).mock.calls + (bridgeService.anchorageDepositERC20 as jest.Mock).mock.calls ).toHaveLength(1) expect( - (networkService.depositERC20Anchorage as jest.Mock).mock.calls[0][0] + (bridgeService.anchorageDepositERC20 as jest.Mock).mock.calls[0][0] ).toEqual({ recipient: '', L1DepositAmountWei: '1255000000000000000', @@ -352,7 +358,7 @@ describe('UseBridge Hooks', () => { expect(prevActions).toEqual(successActionsClassic) }) - test('should trigger depositErc20Anchorage correctly with reciepent address', async () => { + test('should trigger anchorageDepositERC20 correctly with reciepent address', async () => { store = mockStore({ ...store, bridge: { @@ -375,10 +381,10 @@ describe('UseBridge Hooks', () => { await result.current.triggerSubmit() expect( - (networkService.depositERC20Anchorage as jest.Mock).mock.calls + (bridgeService.anchorageDepositERC20 as jest.Mock).mock.calls ).toHaveLength(1) expect( - (networkService.depositERC20Anchorage as jest.Mock).mock.calls[0][0] + (bridgeService.anchorageDepositERC20 as jest.Mock).mock.calls[0][0] ).toEqual({ currency: '0x0000000000000000000000000000000000000006', currencyL2: '0x0000000000000000000000000000000000000032', diff --git a/src/hooks/useBridge/index.ts b/src/hooks/useBridge/index.ts index b8ff8421..e080bbb2 100644 --- a/src/hooks/useBridge/index.ts +++ b/src/hooks/useBridge/index.ts @@ -1,20 +1,22 @@ import { purgeBridgeAlert, resetBridgeAmount, + resetBridgeDestinationAddress, resetToken, } from 'actions/bridgeAction' import { approveERC20, depositErc20, depositErc20Anchorage, - depositETHAnchorageL2, + depositErc20AnchorageOptimism, depositETHL2, + depositNativeAnchorage, depositWithLightBridge, exitBOBA, } from 'actions/networkAction' import { closeModal, openError, openModal } from 'actions/uiAction' import { BRIDGE_TYPE } from 'containers/Bridging/BridgeTypeSelector' -import { BigNumberish, ethers } from 'ethers' +import { ethers } from 'ethers' import { useNetworkInfo } from 'hooks/useNetworkInfo' import { useDispatch, useSelector } from 'react-redux' import { @@ -27,11 +29,15 @@ import { selectLayer, selectTokenToBridge, } from 'selectors' -import networkService from 'services/networkService' +import { lightBridgeService } from 'services/teleportation.service' import { toWei_String } from 'util/amountConvert' import { Layer, LAYER } from 'util/constant' -import { INetwork, NetworkList } from '../../util/network/network.util' -import { lightBridgeService } from 'services/teleportation.service' +import { + INetwork, + Network, + NetworkList, + NetworkType, +} from '../../util/network/network.util' export const useBridge = () => { const dispatch = useDispatch() @@ -64,35 +70,51 @@ export const useBridge = () => { } const triggerDeposit = async (amountWei: any) => { - let receipt - if (token.address === ethers.constants.AddressZero) { - if (!!isAnchorageEnabled) { - receipt = await dispatch( - depositETHAnchorageL2({ + let reciept + if (!!isAnchorageEnabled) { + if (token.address === ethers.constants.AddressZero) { + reciept = await dispatch( + depositNativeAnchorage({ recipient: toL2Account || '', - L1DepositAmountWei: amountWei, + amount: amountWei, }) ) } else { - receipt = await dispatch( - depositETHL2({ - recipient: toL2Account || '', - value_Wei_String: amountWei, - }) - ) + if ( + activeNetwork === Network.BNB && + activeNetworkType === NetworkType.TESTNET && + token.symbol === 'BOBA' + ) { + // deposit BOBA in bnb-testnet with optimism. + reciept = await dispatch( + depositErc20AnchorageOptimism({ + recipient: toL2Account, + amount: amountWei, + currency: token.address, + }) + ) + } else { + reciept = await dispatch( + depositErc20Anchorage({ + recipient: toL2Account, + amount: amountWei, + currency: token.address, + currencyL2: token.addressL2, + }) + ) + } } } else { - if (!!isAnchorageEnabled) { - receipt = await dispatch( - depositErc20Anchorage({ + // NOTE: Below code is getting use only for BNB Mainnet. + if (token.address === ethers.constants.AddressZero) { + reciept = await dispatch( + depositETHL2({ recipient: toL2Account || '', - L1DepositAmountWei: amountWei, - currency: token.address, - currencyL2: token.addressL2, + value_Wei_String: amountWei, }) ) } else { - receipt = await dispatch( + reciept = await dispatch( depositErc20({ recipient: toL2Account || '', value_Wei_String: amountWei, @@ -102,8 +124,7 @@ export const useBridge = () => { ) } } - - return receipt + return reciept } const triggerTeleportAsset = async (amountWei, destChainId) => { @@ -164,6 +185,7 @@ export const useBridge = () => { } } dispatch(closeModal('bridgeInProgress')) + dispatch(resetBridgeDestinationAddress()) if (receipt) { dispatch(openModal({ modal: 'transactionSuccess' })) dispatch(resetToken()) diff --git a/src/hooks/useNetworkInfo/index.ts b/src/hooks/useNetworkInfo/index.ts index 7fe5dbfb..ed775030 100644 --- a/src/hooks/useNetworkInfo/index.ts +++ b/src/hooks/useNetworkInfo/index.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { selectActiveNetwork, selectActiveNetworkType } from 'selectors' -import { Network } from 'util/network/network.util' +import { Network, NetworkType } from 'util/network/network.util' export const useNetworkInfo = () => { const [isAnchorageEnabled, setIsAnchorageEnabled] = useState(false) @@ -11,14 +11,17 @@ export const useNetworkInfo = () => { const networkType = useSelector(selectActiveNetworkType()) useEffect(() => { - // NOTE: as anchorage has been shiped to ETHEREUM & SEPOLIA both. - if (network === Network.ETHEREUM) { + // NOTE: as anchorage has been shiped to ETHEREUM and BNB Testnet only. + if ( + network === Network.ETHEREUM || + (network === Network.BNB && networkType === NetworkType.TESTNET) + ) { setIsAnchorageEnabled(true) } else { setIsAnchorageEnabled(false) } - if (network === Network.BNB) { + if (network === Network.BNB && networkType === NetworkType.MAINNET) { setIsActiveNetworkBnb(true) } else { setIsActiveNetworkBnb(false) diff --git a/src/services/abi/OptimismPortal.abi.ts b/src/services/abi/OptimismPortal.abi.ts index 9b5c7d2b..f2d9e2ca 100644 --- a/src/services/abi/OptimismPortal.abi.ts +++ b/src/services/abi/OptimismPortal.abi.ts @@ -1,2 +1,4 @@ -export const OptimismPortalABI = - '[{"inputs":[{"internalType":"contract L2OutputOracle","name":"_l2Oracle","type":"address"},{"internalType":"address","name":"_guardian","type":"address"},{"internalType":"bool","name":"_paused","type":"bool"},{"internalType":"contract SystemConfig","name":"_config","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"opaqueData","type":"bytes"}],"name":"TransactionDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"withdrawalHash","type":"bytes32"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"WithdrawalFinalized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"withdrawalHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"WithdrawalProven","type":"event"},{"inputs":[],"name":"GUARDIAN","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"L2_ORACLE","outputs":[{"internalType":"contract L2OutputOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SYSTEM_CONFIG","outputs":[{"internalType":"contract SystemConfig","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint64","name":"_gasLimit","type":"uint64"},{"internalType":"bool","name":"_isCreation","type":"bool"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"depositTransaction","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"donateETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Types.WithdrawalTransaction","name":"_tx","type":"tuple"}],"name":"finalizeWithdrawalTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"finalizedWithdrawals","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_l2OutputIndex","type":"uint256"}],"name":"isOutputFinalized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"l2Sender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"_byteCount","type":"uint64"}],"name":"minimumGasLimit","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"params","outputs":[{"internalType":"uint128","name":"prevBaseFee","type":"uint128"},{"internalType":"uint64","name":"prevBoughtGas","type":"uint64"},{"internalType":"uint64","name":"prevBlockNum","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Types.WithdrawalTransaction","name":"_tx","type":"tuple"},{"internalType":"uint256","name":"_l2OutputIndex","type":"uint256"},{"components":[{"internalType":"bytes32","name":"version","type":"bytes32"},{"internalType":"bytes32","name":"stateRoot","type":"bytes32"},{"internalType":"bytes32","name":"messagePasserStorageRoot","type":"bytes32"},{"internalType":"bytes32","name":"latestBlockhash","type":"bytes32"}],"internalType":"struct Types.OutputRootProof","name":"_outputRootProof","type":"tuple"},{"internalType":"bytes[]","name":"_withdrawalProof","type":"bytes[]"}],"name":"proveWithdrawalTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"provenWithdrawals","outputs":[{"internalType":"bytes32","name":"outputRoot","type":"bytes32"},{"internalType":"uint128","name":"timestamp","type":"uint128"},{"internalType":"uint128","name":"l2OutputIndex","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]' +export const OptimismPortalABI = [ + 'function depositERC20Transaction(address _to, uint256 _mint,uint256 _value, uint64 _gasLimit,bool _isCreation,bytes memory _data) external', + 'function depositTransaction(address _to,uint256 _value,uint64 _gasLimit,bool _isCreation,bytes memory _data) external payable', +] diff --git a/src/services/bridge/bridge.services.test.ts b/src/services/bridge/bridge.services.test.ts new file mode 100644 index 00000000..02d1f864 --- /dev/null +++ b/src/services/bridge/bridge.services.test.ts @@ -0,0 +1,97 @@ +import networkService from '../networkService' +import { bridgeService, BridgeService } from './bridge.services' + +jest.mock('ethers', () => { + return { + Contract: jest.fn().mockResolvedValue(() => { + return { + connect: jest.fn().mockReturnThis(), + depositTransaction: jest.fn().mockResolvedValue({ + wait: jest.fn().mockResolvedValue('txResponse'), + }), + } + }), + } +}) + +jest.mock('../networkService', () => ({ + account: 'mockAccount', + addresses: { + OptimismPortalProxy: 'mockOptimismPortalProxy', + }, + provider: { + getSigner: jest.fn().mockReturnValue({ + sendTransaction: jest.fn().mockResolvedValue({ + wait: jest.fn().mockResolvedValue('txResponse'), + }), + }), + }, + L1Provider: 'mockL1Provider', +})) + +describe('BridgeService', () => { + test('should create an instance of BridgeService', () => { + expect(bridgeService).toBeInstanceOf(BridgeService) + }) + + describe('anchorageDepositNative', () => { + beforeEach(() => { + networkService.account = 'mockAccount' + networkService.addresses.OptimismPortalProxy = 'mockOptimismPortalProxy' + // @ts-ignore + networkService.provider.getSigner = jest.fn().mockReturnValue({ + sendTransaction: jest.fn().mockResolvedValue({ + wait: jest.fn().mockResolvedValue('txResponse'), + }), + }) + }) + + test('anchorageDepositNative should throw error if wallet not connected', async () => { + networkService.account = undefined + await expect( + bridgeService.anchorageDepositNative({ + recipient: '0xRecipient', + amount: '1000', + }) + ).resolves.toThrow('GATEWAY ERROR: wallet not connected') + }) + + test('anchorageDepositNative should throw error if Optimism portal address not provided', async () => { + networkService.addresses.OptimismPortalProxy = null + await expect( + bridgeService.anchorageDepositNative({ + recipient: '0xRecipient', + amount: '1000', + }) + ).resolves.toThrow('GATEWAY ERROR: invalid optimism portal address') + }) + + xtest('anchorageDepositNative should call depositTransaction if recipient is provided', async () => { + const result = await bridgeService.anchorageDepositNative({ + recipient: '0xRecipient', + amount: '1000', + }) + expect(result).toBe('txResponse') + }) + + test('anchorageDepositNative should call sendTransaction if recipient is not provided', async () => { + const result = await bridgeService.anchorageDepositNative({ + recipient: '', + amount: '1000', + }) + expect(result).toBe('txResponse') + }) + + test('anchorageDepositNative should handle errors and return error object', async () => { + // @ts-ignore + networkService.provider.getSigner = jest.fn().mockImplementation(() => { + throw new Error('Mock error') + }) + const result = await bridgeService.anchorageDepositNative({ + recipient: '0xRecipient', + amount: '1000', + }) + expect(result).toEqual(new Error('Mock error')) + }) + }) +}) diff --git a/src/services/bridge/bridge.services.ts b/src/services/bridge/bridge.services.ts new file mode 100644 index 00000000..47c28b38 --- /dev/null +++ b/src/services/bridge/bridge.services.ts @@ -0,0 +1,271 @@ +/* + Bridging - + Contract interaction for Deposit & Withdrawal of Tokens + - classic & light bridges. + * For Bnb L2 (Testnet and Mainnet), + * use OptimismPortal to deposit Boba and BNB. + * use L1StandardBridge to deposit ERC20 tokens + * + * For Eth L2, + * use OptimismPortal to deposit Eth + * use L1StandardBridge ERC20 tokens + * +*/ + +import { BigNumber, Contract } from 'ethers' +import { formatEther } from 'ethers/lib/utils' +import { + L1ERC20ABI, + L1StandardBridgeABI, + OptimismPortalABI, +} from 'services/abi' +import networkService from 'services/networkService' +import { ERROR_CODE } from 'util/constant' + +interface IDepositErc20 { + recipient?: string + amount: string + currency: string + currencyL2: string +} + +interface IDepositErc20Optimism { + recipient?: string + amount: string + currency: string +} + +interface IDepositNative { + recipient?: null | string + amount: string +} + +export class BridgeService { + async anchorageDepositNative({ recipient, amount }: IDepositNative) { + try { + if (!networkService.account) { + throw new Error(`${ERROR_CODE} wallet not connected`) + } + + if (!networkService.addresses.OptimismPortalProxy) { + throw new Error(`${ERROR_CODE} invalid optimism portal address`) + } + + const signer = networkService.provider?.getSigner() + + if (!signer) { + throw new Error(`${ERROR_CODE} no signer found`) + } + + const optimismContract = new Contract( + networkService.addresses.OptimismPortalProxy, + OptimismPortalABI, + networkService.L1Provider + ) + + let depositTx: any + + if (recipient) { + depositTx = await optimismContract + .connect(signer!) + .depositTransaction(recipient, 0, 100000, false, [], { + value: amount, + }) + } else { + depositTx = await signer!.sendTransaction({ + to: optimismContract.address, + value: amount, + }) + } + + const txResponse = await depositTx.wait() + + return txResponse + } catch (error) { + console.log(`BS: deposit native`, error) + return error + } + } + + async anchorageDepositERC20({ + recipient, + amount, + currency, + currencyL2, + }: IDepositErc20) { + try { + console.log(`▶▶ anchorageDepositERC20`) + if (!networkService.account) { + throw new Error(`${ERROR_CODE} wallet not connected!`) + } + + const l1StdBridgeAddress = networkService.addresses.L1StandardBridgeProxy + + if (!l1StdBridgeAddress) { + throw new Error(`${ERROR_CODE} invalid L1StandardBridge address`) + } + + const signer = networkService.provider?.getSigner() + + if (!signer) { + throw new Error(`${ERROR_CODE} no signer found!`) + } + + const bobaL1Contract = new Contract( + networkService.tokenAddresses!.BOBA?.L1, + L1ERC20ABI, + networkService.L1Provider + ) + + const bobaTokenContract = bobaL1Contract!.attach(currency) + const bobaL1Balance = await bobaTokenContract.balanceOf( + networkService.account + ) + + if (bobaL1Balance.lt(amount)) { + throw new Error(`${ERROR_CODE} insufficient L1 token balance`) + } + + const allowance_BN = await bobaTokenContract.allowance( + networkService.account, + l1StdBridgeAddress + ) + + const allowed = allowance_BN.gte(BigNumber.from(amount)) + + if (!allowed) { + const L1ApproveTx = await bobaTokenContract + .connect(signer!) + .approve(l1StdBridgeAddress, amount) + await L1ApproveTx.wait() + } + + let depositTx: any + + const l1StandardBridgeContract = new Contract( + l1StdBridgeAddress, + L1StandardBridgeABI, + networkService.L1Provider + ) + + const signedContract = l1StandardBridgeContract!.connect(signer) + + if (recipient) { + depositTx = await signedContract.depositERC20To( + currency, + currencyL2, + recipient, + amount, + 999999, + '0x' + ) + } else { + depositTx = await signedContract.depositERC20( + currency, + currencyL2, + amount, + 999999, + '0x' + ) + } + + const depositReceipt = await depositTx.wait() + + console.log(`✔ Deposit ${formatEther(amount)} tokens to L2`) + return depositReceipt + } catch (error) { + console.log(`BS: deposit ERC20`, error) + return error + } + } + + async anchorageDepositERC20Optimism({ + recipient, + amount, + currency, + }: IDepositErc20Optimism) { + try { + console.log(`▶▶ anchorageDepositERC20Optimism`) + if (!networkService.account) { + throw new Error(`${ERROR_CODE} wallet not connected!`) + } + + const opContractAddress = networkService.addresses.OptimismPortalProxy + + if (!opContractAddress) { + throw new Error(`${ERROR_CODE} invalid optimismPortalProxy address`) + } + + const signer = networkService.provider?.getSigner() + + if (!signer) { + throw new Error(`${ERROR_CODE} no signer found!`) + } + + const bobaL1Contract = new Contract( + networkService.tokenAddresses!.BOBA?.L1, + L1ERC20ABI, + networkService.L1Provider + ) + + const bobaTokenContract = bobaL1Contract!.attach(currency!) + const bobaL1Balance = await bobaTokenContract.balanceOf( + networkService.account + ) + + if (bobaL1Balance.lt(amount)) { + throw new Error(`${ERROR_CODE} insufficient L1 token balance`) + } + + const allowance_BN = await bobaTokenContract.allowance( + networkService.account, + opContractAddress + ) + + const allowed = allowance_BN.gte(BigNumber.from(amount)) + + if (!allowed) { + const L1ApproveTx = await bobaTokenContract + .connect(signer!) + .approve(opContractAddress, amount) + await L1ApproveTx.wait() + } + + let depositTx: any + + const optimismContract = new Contract( + opContractAddress, + OptimismPortalABI, + networkService.L1Provider + ) + + if (recipient) { + console.log(`deposit BOBA to `, recipient, amount) + depositTx = await optimismContract + .connect(signer!) + .depositERC20Transaction(recipient, 0, amount, 100000, false, []) + } else { + depositTx = await optimismContract + .connect(signer!) + .depositERC20Transaction( + networkService.account, + amount, + 0, + 100000, + false, + [] + ) + } + + const depositReceipt = await depositTx.wait() + + console.log(`✔ Deposited ${formatEther(amount)} tokens to L2`) + return depositReceipt + } catch (error) { + console.log(`BS: deposit ERC20`, error) + return error + } + } +} + +export const bridgeService = new BridgeService() diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 00000000..352cc77e --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1 @@ +export * from './bridge/bridge.services' diff --git a/src/services/networkService.ts b/src/services/networkService.ts index c1ca191e..647e440b 100644 --- a/src/services/networkService.ts +++ b/src/services/networkService.ts @@ -178,9 +178,14 @@ class NetworkService { this.walletService = walletService } - // NOTE: added check for anchorage for ethereum network. + // NOTE: Anchorage Bridging is enable for + // Ethereum and BNB-testnet isAnchorageEnabled() { - if (this.networkGateway === Network.ETHEREUM) { + if ( + this.networkGateway === Network.ETHEREUM || + (this.networkGateway === Network.BNB && + this.networkType === NetworkType.TESTNET) + ) { return true } return false @@ -533,20 +538,9 @@ class NetworkService { ) if (!isLimitedNetwork) { - let L1StandardBridgeAddress - - // todo remove once migrated to anchorage - if (!this.isAnchorageEnabled()) { - L1StandardBridgeAddress = this.addresses.L1StandardBridgeAddress - } else { - L1StandardBridgeAddress = this.addresses.L1StandardBridgeProxy - ? this.addresses.L1StandardBridgeProxy - : this.addresses.L1StandardBridge - } - - if (L1StandardBridgeAddress) { + if (this.addresses.L1StandardBridgeAddress) { this.L1StandardBridgeContract = new ethers.Contract( - L1StandardBridgeAddress, // uses right addressed depending on ENABLE_ANCHORAGE + this.addresses.L1StandardBridgeAddress, // uses right addressed depending on ENABLE_ANCHORAGE L1StandardBridgeABI, this.L1Provider ) @@ -1074,51 +1068,6 @@ class NetworkService { } } - /** @dev Once we fully migrated to Anchorage we might want to merge this function with depositETHL2. */ - async depositETHAnchorage({ recipient = null, L1DepositAmountWei }) { - try { - setFetchDepositTxBlock(false) - let depositTX - // TODO: make sure to evaluate and update the fallback logic. - const isFallback = false // TODO evaluate when to use fallback optimismportal - - const signer = networkService.provider?.getSigner() - - // deposit fallback via OptimismPortal - if (isFallback) { - if (recipient) { - depositTX = await networkService.OptimismPortal?.connect( - signer! - ).depositTransaction(recipient, L1DepositAmountWei, 100000, false, []) - } else { - depositTX = await signer!.sendTransaction({ - to: this.OptimismPortal?.address, - value: L1DepositAmountWei, - }) - } - } else { - // deposit preferred way via L1StandardBridge - if (recipient) { - depositTX = await this.L1StandardBridgeContract!.connect( - signer! - ).depositETHTo(recipient, 100000, [], { value: L1DepositAmountWei }) - } else { - depositTX = await this.L1StandardBridgeContract!.connect( - signer! - ).depositETH(100000, [], { value: L1DepositAmountWei }) - } - } - - setFetchDepositTxBlock(true) - const received = await depositTX.wait() - - return received - } catch (error) { - console.log('NS: depositETHAnchorage error:', error) - return error - } - } - // @note clean up on completion of anchorage migration on mainnet. //Move ETH from L1 to L2 using the standard deposit system /****** @@ -1289,84 +1238,6 @@ class NetworkService { } } - /** @dev Once we fully migrated to Anchorage we might want to merge this function with depositERC20. */ - async depositERC20Anchorage({ - recipient = null, - L1DepositAmountWei, - currency, - currencyL2, - }): Promise { - console.log(`calling anchorage erc 20 deposit`) - setFetchDepositTxBlock(false) - const signer = this.provider?.getSigner() - if (!signer) { - console.error('no signer!') - } - - const L1_ERC20_Contract = this.L1_TEST_Contract!.attach(currency) - const L2_ERC20_Contract = this.L2_TEST_Contract!.attach(currencyL2) - const L1BOBABalance = await L1_ERC20_Contract.balanceOf(this.account) - - if (L1BOBABalance.lt(L1DepositAmountWei)) { - console.error('Insufficient L1 token balance') - return - } - - const allowance_BN = await L1_ERC20_Contract.allowance( - this.account, - this.addresses.L1StandardBridgeProxy - ) - - const allowed = allowance_BN.gte(BigNumber.from(L1DepositAmountWei)) - - if (!allowed) { - const L1ApproveTx = await L1_ERC20_Contract.connect(signer!).approve( - this.addresses.L1StandardBridgeProxy, - L1DepositAmountWei - ) - await L1ApproveTx.wait() - } - - let L1DepositTx - if (recipient) { - L1DepositTx = await this.L1StandardBridgeContract!.connect( - signer! - ).depositERC20To( - currency, - currencyL2, - recipient, - L1DepositAmountWei, - 999999, - '0x' - ) - } else { - L1DepositTx = await this.L1StandardBridgeContract!.connect( - signer! - ).depositERC20(currency, currencyL2, L1DepositAmountWei, 999999, '0x') - } - const depositReceipt = await L1DepositTx.wait() - console.log( - `Deposited ${utils.formatEther( - L1DepositAmountWei - )} tokens to L1 Standard Bridge` - ) - - setFetchDepositTxBlock(true) - // As this is more over to complete check of modal as we have note on success modal we can skip this check. - // while (true) { - // const postL2BOBABalanceTmp = await L2_ERC20_Contract.balanceOf( - // this.account - // ) - - // if (!L2BOBABalance.eq(postL2BOBABalanceTmp)) { - // break - // } - // await new Promise((resolve) => setTimeout(resolve, 1000)) - // } - - return depositReceipt - } - // TODO: cleanup on migration of anchorage to mainnet. //Used to move ERC20 Tokens from L1 to L2 using the classic deposit async depositErc20({ diff --git a/src/services/teleportation.service.ts b/src/services/teleportation.service.ts index 6c6642ad..f8735ffb 100644 --- a/src/services/teleportation.service.ts +++ b/src/services/teleportation.service.ts @@ -5,7 +5,7 @@ import { } from 'util/network/network.util' import networkService from './networkService' import appService from './app.service' -import { Layer, LAYER } from 'util/constant' +import { ERROR_CODE, Layer, LAYER } from 'util/constant' import { L1ERC20ABI, L2StandardERC20ABI, TeleportationABI } from './abi' import { constants, Contract, providers } from 'ethers' import { getDestinationTokenAddress } from '@bobanetwork/light-bridge-chains' @@ -23,7 +23,7 @@ export class LightBridgeService { let lightBridgeAddress: string | undefined if (!destChainConfig) { - throw new Error(`Unknown network`) + throw new Error(`${ERROR_CODE} unknown destination network`) } const addresses = appService.fetchAddresses({ @@ -62,7 +62,7 @@ export class LightBridgeService { await this.getLightBridgeAddress({ chainId }) if (!lightBridgeAddress) { - throw new Error(`Invalid Light Bridge Address`) + throw new Error(`${ERROR_CODE} invalid teleporation address`) } const rpc = getRpcUrl({ @@ -128,13 +128,13 @@ export class LightBridgeService { const contract = await this.getLightBridgeContract(Number(destChainId)) if (!contract) { - throw new Error(`Invalid LB contract`) + throw new Error(`${ERROR_CODE} no lightbridge contract found`) } const disburserAddress = contract.disburser() if (!disburserAddress) { - throw new Error(`Invalid disburser address`) + throw new Error(`${ERROR_CODE} invalid disburser address`) } if (isNativeToken) { @@ -173,7 +173,7 @@ export class LightBridgeService { : networkService.addresses.Proxy__L2Teleportation if (!lightBridgeAddress) { - throw new Error(`Invalid LB Address`) + throw new Error(`${ERROR_CODE} invalid teleporation Address`) } const contract = new Contract( @@ -230,7 +230,7 @@ export class LightBridgeService { if (!payload.supported) { throw new Error( - `TS: Asset ${tokenAddress} not supported for chain ${destChainId}` + `${ERROR_CODE} Asset ${tokenAddress} not supported for chain ${destChainId}` ) } diff --git a/src/services/transaction.service.ts b/src/services/transaction.service.ts index f52f910b..b94a3c5c 100644 --- a/src/services/transaction.service.ts +++ b/src/services/transaction.service.ts @@ -1,23 +1,20 @@ +import { + anchorageGraphQLService, + LightBridgeAssetReceivedEvent, + LightBridgeDisbursementEvents, + lightBridgeGraphQLService, +} from '@bobanetwork/graphql-utils' +import { BobaChains } from '@bobanetwork/light-bridge-chains' import omgxWatcherAxiosInstance from 'api/omgxWatcherAxios' import { TRANSACTION_STATUS } from 'containers/history/types' import { Contract, ethers, providers } from 'ethers' -import { BobaChains } from '@bobanetwork/light-bridge-chains' -import { ethereumConfig } from 'util/network/config/ethereum' import { AllNetworkConfigs, CHAIN_ID_LIST, getRpcUrlByChainId, - Network, - NetworkType, } from 'util/network/network.util' -import { - LightBridgeAssetReceivedEvent, - LightBridgeDisbursementEvents, - anchorageGraphQLService, - lightBridgeGraphQLService, -} from '@bobanetwork/graphql-utils' -import networkService from './networkService' import { NetworkDetailChainConfig } from '../util/network/config/network-details.types' +import networkService from './networkService' import { lightBridgeService } from './teleportation.service' interface ICrossDomainMessage { @@ -47,13 +44,7 @@ class TransactionService { networkConfig = networkService.networkConfig ): Promise { const address = await networkService.provider?.getSigner().getAddress() - if ( - (networkService.networkType === NetworkType.TESTNET && - networkConfig?.L1.chainId !== ethereumConfig.Testnet.L1.chainId) || - (networkService.networkType === NetworkType.MAINNET && - networkConfig?.L1.chainId !== ethereumConfig.Mainnet.L1.chainId) || - !address - ) { + if (!address) { return [] } try { @@ -111,41 +102,6 @@ class TransactionService { } } - // fetch L0 transactions - async fetchL0Tx(networkConfig = networkService.networkConfig) { - let L0Txs = [] - if (!networkConfig || !networkConfig['OMGX_WATCHER_URL']) { - return L0Txs - } - try { - const responseL0 = await omgxWatcherAxiosInstance(networkConfig).post( - 'get.layerzero.transactions', - { - address: networkService.account, - fromRange: 0, - toRange: 1000, - } - ) - - if (responseL0.status === 201) { - L0Txs = responseL0.data.map((v) => ({ - ...v, - hash: v.tx_hash, - blockNumber: parseInt(v.block_number, 10), - timeStamp: parseInt(v.timestamp, 10), //fix bug - sometimes this is string, sometimes an integer - layer: 'L0', - chainName: networkConfig!.L1.name, - originChainId: networkConfig!.L1.chainId, - altL1: true, - })) - } - return L0Txs - } catch (error) { - console.log('TS: fetchL0Tx', error) - return L0Txs - } - } - // fetch L1 pending transactions async fetchL1PendingTx(networkConfig = networkService.networkConfig) { let txL1pending = [] @@ -192,27 +148,26 @@ class TransactionService { const allNetworksTransactions = await Promise.all( networkConfigsArray.flatMap((config) => { - // light bridge available for all networks fetch for all. const promiseCalls: Promise[] = [ this.fetchLightBridgeTransactions(config), ] // check for ethereum and invoke anchorage data. + // [sepolia, bnb tesnet, eth mainnet] if ( - [11155111, 28882, 1, 288].includes(config.L1.chainId) || - [11155111, 28882, 1, 288].includes(config.L1.chainId) + [11155111, 97, 1].includes(config.L1.chainId) || + [28882, 9728, 288].includes(config.L2.chainId) ) { promiseCalls.push(this.fetchAnchorageTransactions(config)) } - // check for bnb and invoke watcher - if ( - [97, 9728, 56, 56288].includes(config.L1.chainId) || - [97, 9728, 56, 56288].includes(config.L1.chainId) - ) { - promiseCalls.push(this.fetchL2Tx(config)) + // invoke watcher only for BNB mainnet + if ([56].includes(config.L1.chainId)) { promiseCalls.push(this.fetchL1PendingTx(config)) } + if ([56288].includes(config.L2.chainId)) { + promiseCalls.push(this.fetchL2Tx(config)) + } return promiseCalls }) diff --git a/src/util/constant.ts b/src/util/constant.ts index e06b97db..34811343 100644 --- a/src/util/constant.ts +++ b/src/util/constant.ts @@ -97,3 +97,5 @@ export const MIN_NATIVE_L1_BALANCE: number = 0.002 export const THIRD_PARTY_BRIDGES_LIST = `https://raw.githubusercontent.com/bobanetwork/gateway-data/${GATEWAY_DATA_BRANCH}/bridges/list.json` export const COIN_GECKO_URL = `https://api.coingecko.com/api/v3/` + +export const ERROR_CODE = 'GATEWAY ERROR:' diff --git a/src/util/network/config/bnb.ts b/src/util/network/config/bnb.ts index c6b1752c..a59a1a61 100644 --- a/src/util/network/config/bnb.ts +++ b/src/util/network/config/bnb.ts @@ -2,7 +2,7 @@ import { NetworkDetail } from './network-details.types' export const bnbConfig: NetworkDetail = { Testnet: { - OMGX_WATCHER_URL: `https://api-watcher.testnet.bnb.boba.network/`, + OMGX_WATCHER_URL: ``, META_TRANSACTION: `https://api-meta-transaction.testnet.bnb.boba.network/`, MM_Label: `bobaBnbTestnet`, addressManager: `0xAee1fb3f4353a9060aEC3943fE932b6Efe35CdAa`, diff --git a/yarn.lock b/yarn.lock index 31bb7a21..afbed522 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1227,10 +1227,10 @@ dotenv "^8.6.0" ethers "^5.5.4" -"@bobanetwork/register@^0.0.25": - version "0.0.25" - resolved "https://registry.yarnpkg.com/@bobanetwork/register/-/register-0.0.25.tgz#584e3bbb094a21a95e05d269510c7f3271fbc3e7" - integrity sha512-fFSttqVZsjG3ovX4fExNxwYEiVr4sybULrv08mBIIG4shyd3XPVJ6nI+Jy+Dh+jwlW69lHeQ6i20TmIKmixQeQ== +"@bobanetwork/register@^0.0.26": + version "0.0.26" + resolved "https://registry.yarnpkg.com/@bobanetwork/register/-/register-0.0.26.tgz#432539d091b1299a495f89870af87638aba2e0ca" + integrity sha512-V00j30IjlMHt0NriiyMV/Sth0magUt6rY2gEUYnknvJR9Z50L96U+Um7HqDRDLBLGZ0QTloEoMiq/E71/Mj88Q== "@bobanetwork/sdk@1.0.7": version "1.0.7" @@ -1385,14 +1385,14 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@^11.11.0", "@emotion/cache@^11.12.0", "@emotion/cache@^11.4.0": - version "11.12.0" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.12.0.tgz#ae8c735690fdc8f410f3ea849dd7d3ea4c017494" - integrity sha512-VFo/F1PthkxHwWDCcXkidyXw70eAkdiNiCzthMI2rRQjFiTvmXt8UDlv/VE1DTsd4CIEY2wQf5AnL2QiPgphlw== +"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.0.tgz#8f51748b8116691dee0408b08ad758b8d246b097" + integrity sha512-hPV345J/tH0Cwk2wnU/3PBzORQ9HeX+kQSbwI+jslzpRCHE6fSGTohswksA/Ensr8znPzwfzKZCmAM9Lmlhp7g== dependencies: "@emotion/memoize" "^0.9.0" - "@emotion/sheet" "^1.3.0" - "@emotion/utils" "^1.3.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.0" "@emotion/weak-memoize" "^0.4.0" stylis "4.2.0" @@ -1414,46 +1414,46 @@ integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== "@emotion/react@^11.8.1": - version "11.12.0" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.12.0.tgz#b9605522156d3cc917c1dfc3a09540f8620c9166" - integrity sha512-kTktYMpG8mHjLi8u6XOTMfDmQvUve/un2ZVj4khcU2KTn17ElMV8BK6QFzT8V/v2QW8013rf07Yc0ayQL3tp3w== + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.0.tgz#a9ebf827b98220255e5760dac89fa2d38ca7b43d" + integrity sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ== dependencies: "@babel/runtime" "^7.18.3" "@emotion/babel-plugin" "^11.12.0" - "@emotion/cache" "^11.12.0" - "@emotion/serialize" "^1.2.0" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.3.0" + "@emotion/cache" "^11.13.0" + "@emotion/serialize" "^1.3.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" "@emotion/weak-memoize" "^0.4.0" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.2.0.tgz#4fed8629e566676275302844f111219445cabd08" - integrity sha512-X5UWpZAhGGp5LOn7OAI9k9JjRtz7nSFhZypatADcuEd/0bECZ0DzVjPdL8hljTrAku8+TjFvWIYHMOCO/0v/Ng== +"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.0.tgz#e07cadfc967a4e7816e0c3ffaff4c6ce05cb598d" + integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA== dependencies: "@emotion/hash" "^0.9.2" "@emotion/memoize" "^0.9.0" "@emotion/unitless" "^0.9.0" - "@emotion/utils" "^1.3.0" + "@emotion/utils" "^1.4.0" csstype "^3.0.2" -"@emotion/sheet@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.3.0.tgz#b1bd1d25f5590989187470f0bbc9b962f50fc72f" - integrity sha512-vOPwbKw8fj/oSEa7CWqiKCvLZ1AeLIAApmboGP34xUyUjXalFyf+tMtgMDqP7VMevLPhUa+YWJS46cQUA+tr9A== +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== "@emotion/styled@^11.11.0": - version "11.12.0" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.12.0.tgz#829556b08ff63e7e9a46fb661913f590031f1749" - integrity sha512-2l60kuKm8OKbyFYlVutpwFGoeIoftJAHX4udQwpaO4HNoGyvxFLXiEU56/UGCyrwxr76C93eOdVnGewZeC7AEw== + version "11.13.0" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.13.0.tgz#633fd700db701472c7a5dbef54d6f9834e9fb190" + integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA== dependencies: "@babel/runtime" "^7.18.3" "@emotion/babel-plugin" "^11.12.0" "@emotion/is-prop-valid" "^1.3.0" - "@emotion/serialize" "^1.2.0" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.3.0" + "@emotion/serialize" "^1.3.0" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" "@emotion/stylis@^0.8.4": version "0.8.5" @@ -1470,15 +1470,15 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.9.0.tgz#8e5548f072bd67b8271877e51c0f95c76a66cbe2" integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== +"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" + integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== -"@emotion/utils@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.3.0.tgz#e170d859bdf5da2a705b3c83233ebe35fb7a50da" - integrity sha512-+M7u4EaX5t4bCunKTltAdGis3NFHQniikLVEQ+rPQccsX/xV4v5Etwg12paioZ9DsO+CTvimtmnjZbW85kbF8Q== +"@emotion/utils@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd" + integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== "@emotion/weak-memoize@^0.4.0": version "0.4.0" @@ -1909,24 +1909,24 @@ "@ethersproject/strings" "^5.7.0" "@floating-ui/core@^1.6.0": - version "1.6.4" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.4.tgz#0140cf5091c8dee602bff9da5ab330840ff91df6" - integrity sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA== + version "1.6.5" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.5.tgz#102335cac0d22035b04d70ca5ff092d2d1a26f2b" + integrity sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA== dependencies: - "@floating-ui/utils" "^0.2.4" + "@floating-ui/utils" "^0.2.5" "@floating-ui/dom@^1.0.1": - version "1.6.7" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.7.tgz#85d22f731fcc5b209db504478fb1df5116a83015" - integrity sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng== + version "1.6.8" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.8.tgz#45e20532b6d8a061b356a4fb336022cf2609754d" + integrity sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q== dependencies: "@floating-ui/core" "^1.6.0" - "@floating-ui/utils" "^0.2.4" + "@floating-ui/utils" "^0.2.5" -"@floating-ui/utils@^0.2.4": - version "0.2.4" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c" - integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA== +"@floating-ui/utils@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9" + integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ== "@graphql-typed-document-node/core@^3.0.0": version "3.2.0" @@ -2920,9 +2920,9 @@ integrity sha512-2drqrD2+6kgeg+W/ycmiti3G4lJrV3hGjY9PpJ3bJeXrh6T2+LxKPzlgSEnKFaeQWkXdZ4eaUbtTXVebMjb5JA== "@sentry/types@^8.13.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.18.0.tgz#501e57e19567d0ff13de0957efd2af29a0956699" - integrity sha512-5J+uOqptnmAnW3Rk31AHIqW36Wzvlo3UOM+p2wjSYGrC/PgcE47Klzr+w4UcOhN6AZqefalGd3vaUXz9NaFdRg== + version "8.20.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.20.0.tgz#f0f50c84eb768df8b55ee7b41459fec2d39d0d5e" + integrity sha512-6IP278KojOpiAA7vrd1hjhUyn26cl0n0nGsShzic5ztCVs92sTeVRnh7MTB9irDVtAbOEyt/YH6go3h+Jia1pA== "@sentry/utils@7.114.0": version "7.114.0" @@ -3249,10 +3249,10 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^6.1.4": - version "6.4.6" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz#ec1df8108651bed5475534955565bed88c6732ce" - integrity sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w== +"@testing-library/jest-dom@^6.4.6": + version "6.4.8" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz#9c435742b20c6183d4e7034f2b329d562c079daa" + integrity sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw== dependencies: "@adobe/css-tools" "^4.4.0" "@babel/runtime" "^7.9.2" @@ -3366,10 +3366,18 @@ "@types/eslint" "*" "@types/estree" "*" -"@types/eslint@*", "@types/eslint@^7.29.0 || ^8.4.1": - version "8.56.10" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d" - integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ== +"@types/eslint@*": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.0.tgz#51d4fe4d0316da9e9f2c80884f2c20ed5fb022ff" + integrity sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/eslint@^7.29.0 || ^8.4.1": + version "8.56.11" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.11.tgz#e2ff61510a3b9454b3329fe7731e3b4c6f780041" + integrity sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -3531,9 +3539,9 @@ "@types/node" "*" "@types/node@*": - version "20.14.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.11.tgz#09b300423343460455043ddd4d0ded6ac579b74b" - integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA== + version "20.14.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49" + integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ== dependencies: undici-types "~5.26.4" @@ -4585,16 +4593,6 @@ array.prototype.reduce@^1.0.6: es-object-atoms "^1.0.0" is-string "^1.0.7" -array.prototype.toreversed@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz#b989a6bf35c4c5051e1dc0325151bf8088954eba" - integrity sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - array.prototype.tosorted@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" @@ -5145,9 +5143,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001640: - version "1.0.30001642" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz#6aa6610eb24067c246d30c57f055a9d0a7f8d05f" - integrity sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA== + version "1.0.30001643" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" + integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== case-sensitive-paths-webpack-plugin@^2.4.0: version "2.4.0" @@ -6308,9 +6306,9 @@ ejs@^3.1.10, ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.820: - version "1.4.830" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.830.tgz#a11899bc3343bc28f57a87fcf83060e0d28038d4" - integrity sha512-TrPKKH20HeN0J1LHzsYLs2qwXrp8TF4nHdu4sq61ozGbzMpWhI7iIOPYPPkxeq1azMT9PZ8enPFcftbs/Npcjg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz#0d3123a9f09189b9c7ab4b5d6848d71b3c1fd0e8" + integrity sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA== elliptic@6.5.4: version "6.5.4" @@ -6368,9 +6366,9 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: once "^1.4.0" enhanced-resolve@^5.17.0: - version "5.17.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz#d037603789dd9555b89aaec7eb78845c49089bc5" - integrity sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA== + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -6724,15 +6722,14 @@ eslint-plugin-react-hooks@^4.3.0: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== -eslint-plugin-react@^7.24.0, eslint-plugin-react@^7.27.1: - version "7.34.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.4.tgz#1f0dc313a0937db7ce15fd1f6c3d77e70f3e02fb" - integrity sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA== +eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.34.4: + version "7.35.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41" + integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA== dependencies: array-includes "^3.1.8" array.prototype.findlast "^1.2.5" array.prototype.flatmap "^1.3.2" - array.prototype.toreversed "^1.1.2" array.prototype.tosorted "^1.1.4" doctrine "^2.1.0" es-iterator-helpers "^1.0.19" @@ -7992,9 +7989,9 @@ immer@^9.0.21, immer@^9.0.7: integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== immutable@^4.0.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.6.tgz#6a05f7858213238e587fb83586ffa3b4b27f0447" - integrity sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ== + version "4.3.7" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" + integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" @@ -8005,9 +8002,9 @@ import-fresh@^3.1.0, import-fresh@^3.2.1: resolve-from "^4.0.0" import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -10128,9 +10125,9 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.14: - version "2.0.17" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.17.tgz#d74bc4fec38d839eec5db2a3c9c963d4f33cb366" - integrity sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA== + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== noop-logger@^0.1.1: version "0.1.1" @@ -11045,11 +11042,11 @@ postcss-modules-values@^4.0.0: icss-utils "^5.0.0" postcss-nested@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" - integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + 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-nesting@^10.2.0: version "10.2.0" @@ -11252,7 +11249,7 @@ postcss-selector-not@^6.0.1: dependencies: postcss-selector-parser "^6.0.10" -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9, postcss-selector-parser@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38" integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg== @@ -13552,9 +13549,9 @@ typedarray@^0.0.6: integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== typescript@^5.1.6: - version "5.5.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" - integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== u2f-api@0.2.7: version "0.2.7" @@ -14463,9 +14460,9 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yaml@^2.3.4: - version "2.4.5" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e" - integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== + version "2.5.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d" + integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw== yargs-parser@^18.1.2: version "18.1.3"