From c0e630acb1d1a34a041a3887ac76360a45714a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Leszczyk?= Date: Mon, 3 Feb 2025 17:56:36 +0100 Subject: [PATCH 1/7] fix: token visibility (#132) --- src/contexts/UnifiedBridgeProvider.test.tsx | 3 +- src/contexts/UnifiedBridgeProvider.tsx | 163 +++++++++++--------- src/hooks/useTokensWithBalances.ts | 26 +++- src/pages/ManageTokens/AddToken.tsx | 4 +- src/pages/ManageTokens/ManageTokensList.tsx | 1 + 5 files changed, 117 insertions(+), 80 deletions(-) diff --git a/src/contexts/UnifiedBridgeProvider.test.tsx b/src/contexts/UnifiedBridgeProvider.test.tsx index 0514fc1a2..fd23ca7a4 100644 --- a/src/contexts/UnifiedBridgeProvider.test.tsx +++ b/src/contexts/UnifiedBridgeProvider.test.tsx @@ -291,6 +291,7 @@ describe('contexts/UnifiedBridgeProvider', () => { from: `0x${type}`, to: `0x${type}`, data: `0x${type}`, + value: 1500n, } as any); await signer.sign(tx, async () => '0x' as const, step); @@ -301,7 +302,7 @@ describe('contexts/UnifiedBridgeProvider', () => { type === BridgeType.AVALANCHE_BTC_AVA ? RpcMethod.BITCOIN_SIGN_TRANSACTION : RpcMethod.ETH_SEND_TRANSACTION, - params: isBtc ? tx : [tx], + params: isBtc ? tx : [{ ...tx, value: '0x5dc' }], }, { alert: { diff --git a/src/contexts/UnifiedBridgeProvider.tsx b/src/contexts/UnifiedBridgeProvider.tsx index cc8e5aae9..15b44fc2c 100644 --- a/src/contexts/UnifiedBridgeProvider.tsx +++ b/src/contexts/UnifiedBridgeProvider.tsx @@ -167,7 +167,7 @@ export function UnifiedBridgeProvider({ const evmSigner: EvmSigner = useMemo( () => ({ sign: async ( - { from, data, to }, + { from, data, to, value }, _, { currentSignature, requiredSignatures }, ) => { @@ -175,38 +175,47 @@ export function UnifiedBridgeProvider({ assert(from, UnifiedBridgeError.InvalidTxPayload); assert(data, UnifiedBridgeError.InvalidTxPayload); - const result = await request( - { - method: RpcMethod.ETH_SEND_TRANSACTION, - params: [ - { - from, - to, - data, - }, - ], - }, - { - customApprovalScreenTitle: t('Confirm Bridge'), - alert: - requiredSignatures > currentSignature - ? { - type: 'info', - title: t('This operation requires {{total}} approvals.', { - total: requiredSignatures, - }), - notice: t( - 'You will be prompted {{remaining}} more time(s).', - { - remaining: requiredSignatures - currentSignature, - }, - ), - } - : undefined, - }, - ); - - return result as `0x${string}`; + try { + const result = await request( + { + method: RpcMethod.ETH_SEND_TRANSACTION, + params: [ + { + from, + to, + data, + value: + typeof value === 'bigint' + ? `0x${value.toString(16)}` + : undefined, + }, + ], + }, + { + customApprovalScreenTitle: t('Confirm Bridge'), + alert: + requiredSignatures > currentSignature + ? { + type: 'info', + title: t('This operation requires {{total}} approvals.', { + total: requiredSignatures, + }), + notice: t( + 'You will be prompted {{remaining}} more time(s).', + { + remaining: requiredSignatures - currentSignature, + }, + ), + } + : undefined, + }, + ); + + return result as `0x${string}`; + } catch (err) { + console.error(err); + throw err; + } }, }), [request, t], @@ -219,35 +228,40 @@ export function UnifiedBridgeProvider({ _, { requiredSignatures, currentSignature }, ) => { - const result = await request( - { - method: RpcMethod.BITCOIN_SIGN_TRANSACTION, - params: { - inputs, - outputs, + try { + const result = await request( + { + method: RpcMethod.BITCOIN_SIGN_TRANSACTION, + params: { + inputs, + outputs, + }, }, - }, - { - customApprovalScreenTitle: t('Confirm Bridge'), - alert: - requiredSignatures > currentSignature - ? { - type: 'info', - title: t('This operation requires {{total}} approvals.', { - total: requiredSignatures, - }), - notice: t( - 'You will be prompted {{remaining}} more time(s).', - { - remaining: requiredSignatures - currentSignature, - }, - ), - } - : undefined, - }, - ); - - return result as `0x${string}`; + { + customApprovalScreenTitle: t('Confirm Bridge'), + alert: + requiredSignatures > currentSignature + ? { + type: 'info', + title: t('This operation requires {{total}} approvals.', { + total: requiredSignatures, + }), + notice: t( + 'You will be prompted {{remaining}} more time(s).', + { + remaining: requiredSignatures - currentSignature, + }, + ), + } + : undefined, + }, + ); + + return result as `0x${string}`; + } catch (err) { + console.error(err); + throw err; + } }, }), [request, t], @@ -624,19 +638,24 @@ export function UnifiedBridgeProvider({ const { fromAddress, toAddress, sourceChain, targetChain } = await buildParams(targetChainId); - const bridgeTransfer = await core.transferAsset({ - asset, - fromAddress, - toAddress, - amount, - sourceChain, - targetChain, - gasSettings, - }); + try { + const bridgeTransfer = await core.transferAsset({ + asset, + fromAddress, + toAddress, + amount, + sourceChain, + targetChain, + gasSettings, + }); - await trackBridgeTransfer(bridgeTransfer); + await trackBridgeTransfer(bridgeTransfer); - return bridgeTransfer.sourceTxHash; + return bridgeTransfer.sourceTxHash; + } catch (err) { + console.error(err); + throw err; + } }, [getAsset, activeNetwork, buildParams, core, trackBridgeTransfer], ); diff --git a/src/hooks/useTokensWithBalances.ts b/src/hooks/useTokensWithBalances.ts index edf47cb25..0d761d34e 100644 --- a/src/hooks/useTokensWithBalances.ts +++ b/src/hooks/useTokensWithBalances.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useSettingsContext } from '@src/contexts/SettingsProvider'; import { useBalancesContext } from '@src/contexts/BalancesProvider'; import { useAccountsContext } from '@src/contexts/AccountsProvider'; @@ -13,6 +13,7 @@ import { TokenType, TokenWithBalance } from '@avalabs/vm-module-types'; type UseTokensWithBalanceOptions = { // Requests the tokens WITH and WITHOUT balances forceShowTokensWithoutBalances?: boolean; + forceHiddenTokens?: boolean; // string array of asset symbols that are to be excluded from the result disallowedAssets?: string[]; chainId?: number; @@ -43,7 +44,8 @@ export const useTokensWithBalances = ( const { request } = useConnectionContext(); const { balances } = useBalancesContext(); - const { showTokensWithoutBalances, customTokens } = useSettingsContext(); + const { showTokensWithoutBalances, customTokens, getTokenVisibility } = + useSettingsContext(); const { accounts: { active: activeAccount }, } = useAccountsContext(); @@ -80,6 +82,17 @@ export const useTokensWithBalances = ( }, {}); }, [customTokens, network?.chainId]); + const visibleTokens = useCallback( + (tokens: TokenWithBalance[]) => { + if (options.forceHiddenTokens) { + return tokens; + } + + return tokens.filter(getTokenVisibility); + }, + [getTokenVisibility, options.forceHiddenTokens], + ); + useEffect(() => { setSelectedChainId(chainId ? chainId : network?.chainId); }, [chainId, network?.chainId]); @@ -162,7 +175,7 @@ export const useTokensWithBalances = ( networkBalances, ); - return nativeTokensFirst(Object.values(merged)); + return visibleTokens(nativeTokensFirst(Object.values(merged))); } const unfilteredTokens = Object.values(networkBalances); @@ -181,9 +194,9 @@ export const useTokensWithBalances = ( return token.balance > 0n; }); - return filteredTokens.length - ? nativeTokensFirst(filteredTokens) - : defaultResult; + return visibleTokens( + filteredTokens.length ? nativeTokensFirst(filteredTokens) : defaultResult, + ); }, [ selectedChainId, activeAccount, @@ -192,5 +205,6 @@ export const useTokensWithBalances = ( forceShowTokensWithoutBalances, showTokensWithoutBalances, allTokensWithPlaceholderBalances, + visibleTokens, ]); }; diff --git a/src/pages/ManageTokens/AddToken.tsx b/src/pages/ManageTokens/AddToken.tsx index d7b358a8a..4c8705760 100644 --- a/src/pages/ManageTokens/AddToken.tsx +++ b/src/pages/ManageTokens/AddToken.tsx @@ -91,11 +91,13 @@ export function AddToken() { params: [addressInput], }); setIsLoading(false); - setNewTokenData(undefined); if (!data) { setError(t('Not a valid ERC-20 token address.')); + setNewTokenData(undefined); + return; } + setNewTokenData(data); }; getTokenData(); diff --git a/src/pages/ManageTokens/ManageTokensList.tsx b/src/pages/ManageTokens/ManageTokensList.tsx index 424da18d0..68f92e986 100644 --- a/src/pages/ManageTokens/ManageTokensList.tsx +++ b/src/pages/ManageTokens/ManageTokensList.tsx @@ -26,6 +26,7 @@ export const ManageTokensList = ({ }: ManageTokensListProps) => { const tokensWithBalances = useTokensWithBalances({ forceShowTokensWithoutBalances: true, + forceHiddenTokens: true, }); const sortingTokens = useMemo( From 1eba870c1fc54a50c2d704f6d184ef72ad6eaec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Leszczyk?= Date: Wed, 5 Feb 2025 10:55:39 +0100 Subject: [PATCH 2/7] fix: transactions falsely flagged as suspicious (#134) --- package.json | 10 +++++----- yarn.lock | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index e8cb3a8aa..6f20ba8fc 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ "sentry": "node sentryscript.js" }, "dependencies": { - "@avalabs/avalanche-module": "1.2.0", + "@avalabs/avalanche-module": "1.2.1", "@avalabs/avalanchejs": "4.1.2-alpha.3", - "@avalabs/bitcoin-module": "1.2.0", + "@avalabs/bitcoin-module": "1.2.1", "@avalabs/bridge-unified": "4.0.1", "@avalabs/core-bridge-sdk": "3.1.0-alpha.32", "@avalabs/core-chains-sdk": "3.1.0-alpha.32", @@ -37,12 +37,12 @@ "@avalabs/core-token-prices-sdk": "3.1.0-alpha.32", "@avalabs/core-utils-sdk": "3.1.0-alpha.32", "@avalabs/core-wallets-sdk": "3.1.0-alpha.32", - "@avalabs/evm-module": "1.2.0", + "@avalabs/evm-module": "1.2.1", "@avalabs/glacier-sdk": "3.1.0-alpha.32", "@avalabs/hw-app-avalanche": "0.14.1", - "@avalabs/hvm-module": "1.2.0", + "@avalabs/hvm-module": "1.2.1", "@avalabs/types": "3.1.0-alpha.32", - "@avalabs/vm-module-types": "1.2.0", + "@avalabs/vm-module-types": "1.2.1", "@blockaid/client": "0.10.0", "@coinbase/cbpay-js": "1.6.0", "@cubist-labs/cubesigner-sdk": "0.3.28", diff --git a/yarn.lock b/yarn.lock index 7b8dad190..d19c81355 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,10 +29,10 @@ resolved "https://registry.yarnpkg.com/@apocentre/alias-sampling/-/alias-sampling-0.5.3.tgz#897ff181b48ad7b2bcb4ecf29400214888244f08" integrity sha512-7UDWIIF9hIeJqfKXkNIzkVandlwLf1FWTSdrb9iXvOP8oF544JRXQjCbiTmCv2c9n44n/FIWtehhBfNuAx2CZA== -"@avalabs/avalanche-module@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@avalabs/avalanche-module/-/avalanche-module-1.2.0.tgz#7c1b2f38bca9c32c196c6dfab0f0b52b01ee9556" - integrity sha512-6yuSCQWGCEdo4DFV24t6ipVVigdK2Ek/hk88q+mzaNJo44Xctgy9oNi5JUceDkkIpWaeV5/LS3S4P0jvy+R3MQ== +"@avalabs/avalanche-module@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@avalabs/avalanche-module/-/avalanche-module-1.2.1.tgz#e2282bfb3f3c241e29bf913f24661f5cbe74eefb" + integrity sha512-B7tOqWwFoCvxXWZyqm/Euc4k4d5INMFCoBBPtmPBBC5xos94byl/xNxfW/xjwrbbzO3mDK41skozAXPEMfacjw== dependencies: "@avalabs/avalanchejs" "4.1.2-alpha.3" "@avalabs/core-chains-sdk" "3.1.0-alpha.32" @@ -42,7 +42,7 @@ "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" "@avalabs/glacier-sdk" "3.1.0-alpha.32" "@avalabs/types" "3.1.0-alpha.32" - "@avalabs/vm-module-types" "1.2.0" + "@avalabs/vm-module-types" "1.2.1" "@metamask/rpc-errors" "6.3.0" big.js "6.2.1" bn.js "5.2.1" @@ -59,15 +59,15 @@ "@scure/base" "1.1.5" micro-eth-signer "0.7.2" -"@avalabs/bitcoin-module@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@avalabs/bitcoin-module/-/bitcoin-module-1.2.0.tgz#e1fa2fbc8ba94b9eb5f90ff5a50a005f6921003b" - integrity sha512-rVhbTZxrvEn4N1eueJ2DqoaUoahktve7DU5Da+QBx9k+vAzEKlAC4nxOf6hbdbzgAWxueiqIDYeca3nhJcpjow== +"@avalabs/bitcoin-module@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@avalabs/bitcoin-module/-/bitcoin-module-1.2.1.tgz#39e9fac9922f7ebd8d587c90cb133f49523518db" + integrity sha512-Ha2JXT2oJiUNiwutOtG7MlZZUp9nLVTYRjFAU1RFLi4EV2dj2qLmMfQqnNP4u1SmoEc6upXq8MerXnA/RrkGeA== dependencies: "@avalabs/core-coingecko-sdk" "3.1.0-alpha.32" "@avalabs/core-utils-sdk" "3.1.0-alpha.32" "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" - "@avalabs/vm-module-types" "1.2.0" + "@avalabs/vm-module-types" "1.2.1" "@metamask/rpc-errors" "6.3.0" big.js "6.2.1" bitcoinjs-lib "5.2.0" @@ -205,10 +205,10 @@ ledger-bitcoin "0.2.3" xss "1.0.14" -"@avalabs/evm-module@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@avalabs/evm-module/-/evm-module-1.2.0.tgz#27b0468b3b3550262ea8107e3032a361cbdd4e4e" - integrity sha512-k7djFv+FikdqtX/3tPOqIILp61Fe6AlpnpEXwOv1S9YngUMsOP6NiYY//NgmIsKm3MkX5hVL3hhoHLDJeGUizQ== +"@avalabs/evm-module@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@avalabs/evm-module/-/evm-module-1.2.1.tgz#06191095644da28f92f61cbf861cb2fcd42994f3" + integrity sha512-dA6x5vS86ygc4azOsmjFQESsj7+Sevc22HeiEjrxXJIJiJWPTLkNtyqSc92IQqBCHAX+Hf1cSDqAzo8alT0P/g== dependencies: "@avalabs/core-coingecko-sdk" "3.1.0-alpha.32" "@avalabs/core-etherscan-sdk" "3.1.0-alpha.32" @@ -216,7 +216,7 @@ "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" "@avalabs/glacier-sdk" "3.1.0-alpha.32" "@avalabs/types" "3.1.0-alpha.32" - "@avalabs/vm-module-types" "1.2.0" + "@avalabs/vm-module-types" "1.2.1" "@blockaid/client" "0.11.0" "@metamask/rpc-errors" "6.3.0" "@openzeppelin/contracts" "4.9.6" @@ -230,13 +230,13 @@ resolved "https://registry.yarnpkg.com/@avalabs/glacier-sdk/-/glacier-sdk-3.1.0-alpha.32.tgz#a03a130ed85f60dde076cf9feeb2a4c487aee8e4" integrity sha512-LTRdqpsiGIJVJxfcnNs/85/NnSneD3Jg+DZw6+F8AiJ1OcZftGu9Hz1GvWZhoz+9udHSbEXHEN5trws4vqTjkQ== -"@avalabs/hvm-module@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@avalabs/hvm-module/-/hvm-module-1.2.0.tgz#55c2bdc0cd4fb1cb1fe54752de14fe3867c40287" - integrity sha512-xnirW1pWWvGJvtsTGscgyOJ5t/DD9uGo9F13ZccBdHdxTnEtXcbNx4A66xkEvpYm+QkQHze9i+d3Y3l021uo9w== +"@avalabs/hvm-module@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@avalabs/hvm-module/-/hvm-module-1.2.1.tgz#236f0c794ebbc1bc499f79731c9e26c0d037e86d" + integrity sha512-kBqXG46faGqRlM3BGqYkzLwJ4afPKu7qDuWLJHajVUX7fiGvfiy7APQga7NA5MaObE7fVfBWgq1cuV9v4+aaLQ== dependencies: "@avalabs/core-utils-sdk" "3.1.0-alpha.30" - "@avalabs/vm-module-types" "1.2.0" + "@avalabs/vm-module-types" "1.2.1" "@metamask/rpc-errors" "6.3.0" "@noble/hashes" "1.5.0" hypersdk-client "0.4.16" @@ -258,10 +258,10 @@ resolved "https://registry.yarnpkg.com/@avalabs/types/-/types-3.1.0-alpha.32.tgz#bb94019e3c3121bfc66e1d5fed5a8c5c78c10fb5" integrity sha512-EwIMmTPygrMMxA8GVfBL7UNia3IycMjkPxuaRuZ8PTkW2Yr24F7GGeufBMFU69N0xTyoM8VQgMYNZ2x7stpedg== -"@avalabs/vm-module-types@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@avalabs/vm-module-types/-/vm-module-types-1.2.0.tgz#ad98f74fe7592d4e899a70c6d6c0bf1f9ec5eb80" - integrity sha512-sODtianQoKjfQWio/Sip+qVITM9DJ2+ZPLF2rfT5Po21i1u7mv5lbMXEWz8r1i2rMXAvZy9SK0nn2vrfP8ahbg== +"@avalabs/vm-module-types@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@avalabs/vm-module-types/-/vm-module-types-1.2.1.tgz#4d26e81dd228fd4d1aa9f3f3e434417fc08c531e" + integrity sha512-XR26j9oSQX/4EfdeJ9Nk/jl8QmaSXb84owAEeEjr2h5SJ/fNxHxE0l2om1TwYSHKO8zWDjAY9HliKJj25ou+0A== dependencies: "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" "@avalabs/glacier-sdk" "3.1.0-alpha.32" From fe60e3f823f84f2fc2b9d14fe6eefaf1dd67a377 Mon Sep 17 00:00:00 2001 From: bferenc <117105626+bferenc@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:54:11 +0100 Subject: [PATCH 3/7] [CP-9405] app authorization token (#109) --- .env.example | 7 + .github/workflows/create_release.yaml | 2 + .github/workflows/e2e_testing.yaml | 2 + .github/workflows/main_branch.yaml | 2 + package.json | 8 +- patches/@firebase+messaging+0.12.15.patch | 48 ++ src/background/runtime/BackgroundRuntime.ts | 3 + .../services/appcheck/AppCheckService.test.ts | 197 +++++++ .../services/appcheck/AppCheckService.ts | 187 ++++++ src/background/services/appcheck/models.ts | 28 + .../appcheck/utils/challenges/basic.test.ts | 33 ++ .../appcheck/utils/challenges/basic.ts | 53 ++ .../appcheck/utils/getHashByAlgorithm.test.ts | 26 + .../appcheck/utils/getHashByAlgorithm.ts | 15 + .../utils/registerForChallenge.test.ts | 70 +++ .../appcheck/utils/registerForChallenge.ts | 33 ++ .../appcheck/utils/solveChallenge.test.ts | 23 + .../services/appcheck/utils/solveChallenge.ts | 18 + .../appcheck/utils/verifyChallenge.test.ts | 68 +++ .../appcheck/utils/verifyChallenge.ts | 35 ++ .../services/featureFlags/models.ts | 3 + .../services/firebase/FirebaseService.test.ts | 160 ++++++ .../services/firebase/FirebaseService.ts | 103 ++++ src/background/services/firebase/models.ts | 14 + src/monitoring/sentryCaptureException.ts | 2 + yarn.lock | 536 +++++++++++++++++- 26 files changed, 1670 insertions(+), 6 deletions(-) create mode 100644 patches/@firebase+messaging+0.12.15.patch create mode 100644 src/background/services/appcheck/AppCheckService.test.ts create mode 100644 src/background/services/appcheck/AppCheckService.ts create mode 100644 src/background/services/appcheck/models.ts create mode 100644 src/background/services/appcheck/utils/challenges/basic.test.ts create mode 100644 src/background/services/appcheck/utils/challenges/basic.ts create mode 100644 src/background/services/appcheck/utils/getHashByAlgorithm.test.ts create mode 100644 src/background/services/appcheck/utils/getHashByAlgorithm.ts create mode 100644 src/background/services/appcheck/utils/registerForChallenge.test.ts create mode 100644 src/background/services/appcheck/utils/registerForChallenge.ts create mode 100644 src/background/services/appcheck/utils/solveChallenge.test.ts create mode 100644 src/background/services/appcheck/utils/solveChallenge.ts create mode 100644 src/background/services/appcheck/utils/verifyChallenge.test.ts create mode 100644 src/background/services/appcheck/utils/verifyChallenge.ts create mode 100644 src/background/services/firebase/FirebaseService.test.ts create mode 100644 src/background/services/firebase/FirebaseService.ts create mode 100644 src/background/services/firebase/models.ts diff --git a/.env.example b/.env.example index 3180d9670..362f71346 100644 --- a/.env.example +++ b/.env.example @@ -98,3 +98,10 @@ NEWSLETTER_PORTAL_ID= # Optional NEWSLETTER_FORM_ID= + +# Base64 encoded Firebase config +FIREBASE_CONFIG= + +# Required for ID token registration +# ID service URL +ID_SERVICE_URL= \ No newline at end of file diff --git a/.github/workflows/create_release.yaml b/.github/workflows/create_release.yaml index f7e853f2b..3ff05b9e3 100644 --- a/.github/workflows/create_release.yaml +++ b/.github/workflows/create_release.yaml @@ -39,6 +39,8 @@ jobs: echo NEWSLETTER_BASE_URL=${{ secrets.NEWSLETTER_BASE_URL }} >> .env.production echo NEWSLETTER_PORTAL_ID=${{ secrets.NEWSLETTER_PORTAL_ID }} >> .env.production echo NEWSLETTER_FORM_ID=${{ secrets.NEWSLETTER_FORM_ID }} >> .env.production + echo FIREBASE_CONFIG=${{ secrets.FIREBASE_CONFIG }} >> .env.production + echo ID_SERVICE_URL=${{ secrets.ID_SERVICE_URL }} >> .env.production - name: Install dependencies run: yarn setup - name: Build library diff --git a/.github/workflows/e2e_testing.yaml b/.github/workflows/e2e_testing.yaml index e312f3db6..b3d608989 100644 --- a/.github/workflows/e2e_testing.yaml +++ b/.github/workflows/e2e_testing.yaml @@ -48,6 +48,8 @@ jobs: echo NEWSLETTER_BASE_URL=${{ secrets.NEWSLETTER_BASE_URL }} >> .env.production echo NEWSLETTER_PORTAL_ID=${{ secrets.NEWSLETTER_PORTAL_ID }} >> .env.production echo NEWSLETTER_FORM_ID=${{ secrets.NEWSLETTER_FORM_ID }} >> .env.production + echo FIREBASE_CONFIG=${{ secrets.FIREBASE_CONFIG }} >> .env.production + echo ID_SERVICE_URL=${{ secrets.ID_SERVICE_URL }} >> .env.production - name: Install dependencies run: yarn setup - name: Build library diff --git a/.github/workflows/main_branch.yaml b/.github/workflows/main_branch.yaml index 7646bdfa0..08932c1e0 100644 --- a/.github/workflows/main_branch.yaml +++ b/.github/workflows/main_branch.yaml @@ -42,6 +42,8 @@ jobs: echo NEWSLETTER_BASE_URL=${{ secrets.NEWSLETTER_BASE_URL }} >> .env.production echo NEWSLETTER_PORTAL_ID=${{ secrets.NEWSLETTER_PORTAL_ID }} >> .env.production echo NEWSLETTER_FORM_ID=${{ secrets.NEWSLETTER_FORM_ID }} >> .env.production + echo FIREBASE_CONFIG=${{ secrets.FIREBASE_CONFIG }} >> .env.production + echo ID_SERVICE_URL=${{ secrets.ID_SERVICE_URL }} >> .env.production - name: Install dependencies run: yarn setup - name: Run tests diff --git a/package.json b/package.json index 6f20ba8fc..dc71d9cf7 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "start": "yarn dev", "build:inpage": "webpack --config webpack.inpage.js", "dev:inpage": "webpack -w --config webpack.inpage.js", - "build": "yarn run build:inpage --mode=production && webpack --config webpack.prod.js", - "build:alpha": "yarn run build:inpage --mode=production && webpack --config webpack.alpha.js", - "dev": "yarn run build:inpage && webpack -w --config webpack.dev.js", + "build": "yarn run patch-package && yarn run build:inpage --mode=production && webpack --config webpack.prod.js", + "build:alpha": "yarn run patch-package && yarn run build:inpage --mode=production && webpack --config webpack.alpha.js", + "dev": "yarn run patch-package && yarn run build:inpage && webpack -w --config webpack.dev.js", "lint": "eslint --fix \"src/**/*.ts*\"", "typecheck": "yarn tsc --skipLibCheck --noEmit", "postinstall": "husky install && patch-package", @@ -77,6 +77,7 @@ "eth-rpc-errors": "4.0.3", "ethers": "6.8.1", "events": "3.3.0", + "firebase": "11.1.0", "fireblocks-sdk": "5.20.0", "hypersdk-client": "0.4.17", "i18next": "21.9.2", @@ -267,6 +268,7 @@ "@avalabs/avalanche-module>@avalabs/vm-module-types>@avalabs/core-wallets-sdk>@avalabs/hw-app-avalanche>@ledgerhq/hw-app-eth>@ledgerhq/domain-service>eip55>keccak": false, "@avalabs/avalanche-module>@avalabs/vm-module-types>@avalabs/core-wallets-sdk>@ledgerhq/hw-app-btc>bitcoinjs-lib>bip32>tiny-secp256k1": false, "@avalabs/avalanche-module>@avalabs/vm-module-types>@avalabs/core-wallets-sdk>hdkey>secp256k1": false, + "firebase>@firebase/firestore>@grpc/grpc-js>@grpc/proto-loader>protobufjs": false, "@avalabs/core-bridge-sdk>@avalabs/core-wallets-sdk>@metamask/eth-sig-util>@metamask/utils>@ethereumjs/tx>@ethereumjs/common>ethereumjs-util>ethereum-cryptography>keccak": false, "@avalabs/avalanche-module>@avalabs/vm-module-types>hypersdk-client>@metamask/sdk>@metamask/sdk-communication-layer>bufferutil": false, "@avalabs/avalanche-module>@avalabs/vm-module-types>hypersdk-client>@metamask/sdk>@metamask/sdk-communication-layer>utf-8-validate": false, diff --git a/patches/@firebase+messaging+0.12.15.patch b/patches/@firebase+messaging+0.12.15.patch new file mode 100644 index 000000000..b89b3a343 --- /dev/null +++ b/patches/@firebase+messaging+0.12.15.patch @@ -0,0 +1,48 @@ +diff --git a/node_modules/@firebase/messaging/dist/esm/index.esm2017.js b/node_modules/@firebase/messaging/dist/esm/index.esm2017.js +index b4c53c4..71498b8 100644 +--- a/node_modules/@firebase/messaging/dist/esm/index.esm2017.js ++++ b/node_modules/@firebase/messaging/dist/esm/index.esm2017.js +@@ -562,11 +562,17 @@ async function getNewToken(firebaseDependencies, subscriptionOptions) { + */ + async function getPushSubscription(swRegistration, vapidKey) { + const subscription = await swRegistration.pushManager.getSubscription(); ++ + if (subscription) { +- return subscription; ++ if(!subscription.options.userVisibleOnly) { ++ return subscription; ++ } ++ ++ await subscription.unsubscribe() + } ++ + return swRegistration.pushManager.subscribe({ +- userVisibleOnly: true, ++ userVisibleOnly: false, + // Chrome <= 75 doesn't support base64-encoded VAPID key. For backward compatibility, VAPID key + // submitted to pushManager#subscribe must be of type Uint8Array. + applicationServerKey: base64ToArray(vapidKey) +diff --git a/node_modules/@firebase/messaging/dist/esm/index.sw.esm2017.js b/node_modules/@firebase/messaging/dist/esm/index.sw.esm2017.js +index 88ac597..82ee9bc 100644 +--- a/node_modules/@firebase/messaging/dist/esm/index.sw.esm2017.js ++++ b/node_modules/@firebase/messaging/dist/esm/index.sw.esm2017.js +@@ -560,11 +560,17 @@ async function getNewToken(firebaseDependencies, subscriptionOptions) { + */ + async function getPushSubscription(swRegistration, vapidKey) { + const subscription = await swRegistration.pushManager.getSubscription(); ++ + if (subscription) { +- return subscription; ++ if(!subscription.options.userVisibleOnly) { ++ return subscription; ++ } ++ ++ await subscription.unsubscribe() + } ++ + return swRegistration.pushManager.subscribe({ +- userVisibleOnly: true, ++ userVisibleOnly: false, + // Chrome <= 75 doesn't support base64-encoded VAPID key. For backward compatibility, VAPID key + // submitted to pushManager#subscribe must be of type Uint8Array. + applicationServerKey: base64ToArray(vapidKey) diff --git a/src/background/runtime/BackgroundRuntime.ts b/src/background/runtime/BackgroundRuntime.ts index 872ac2b25..d1d3073bf 100644 --- a/src/background/runtime/BackgroundRuntime.ts +++ b/src/background/runtime/BackgroundRuntime.ts @@ -6,6 +6,7 @@ import { LockService } from '@src/background/services/lock/LockService'; import { OnboardingService } from '@src/background/services/onboarding/OnboardingService'; import { ModuleManager } from '../vmModules/ModuleManager'; import { BridgeService } from '../services/bridge/BridgeService'; +import { AppCheckService } from '@src/background/services/appcheck/AppCheckService'; @singleton() export class BackgroundRuntime { @@ -16,6 +17,7 @@ export class BackgroundRuntime { // we try to fetch the bridge configs as soon as possible private bridgeService: BridgeService, private moduleManager: ModuleManager, + private appCheckService: AppCheckService, ) {} activate() { @@ -28,6 +30,7 @@ export class BackgroundRuntime { this.lockService.activate(); this.onboardingService.activate(); this.moduleManager.activate(); + this.appCheckService.activate(); } private onInstalled() { diff --git a/src/background/services/appcheck/AppCheckService.test.ts b/src/background/services/appcheck/AppCheckService.test.ts new file mode 100644 index 000000000..c1d480e38 --- /dev/null +++ b/src/background/services/appcheck/AppCheckService.test.ts @@ -0,0 +1,197 @@ +import * as Sentry from '@sentry/browser'; +import { + AppCheck, + CustomProvider, + initializeAppCheck, + setTokenAutoRefreshEnabled, +} from 'firebase/app-check'; +import { FirebaseService } from '../firebase/FirebaseService'; +import { FcmMessageEvents, FirebaseEvents } from '../firebase/models'; +import { + AppCheckService, + WAIT_FOR_CHALLENGE_ATTEMPT_COUNT, + WAIT_FOR_CHALLENGE_DELAY_MS, +} from './AppCheckService'; +import registerForChallenge from './utils/registerForChallenge'; +import { ChallengeTypes } from './models'; +import { MessagePayload } from 'firebase/messaging/sw'; +import solveChallenge from './utils/solveChallenge'; +import verifyChallenge from './utils/verifyChallenge'; + +jest.mock('@sentry/browser'); +jest.mock('firebase/app-check'); +jest.mock('./utils/registerForChallenge'); +jest.mock('./utils/verifyChallenge'); +jest.mock('./utils/solveChallenge'); + +describe('AppCheckService', () => { + let appCheckService: AppCheckService; + let firebaseService: FirebaseService; + + beforeEach(() => { + jest.resetAllMocks(); + + (Sentry.startTransaction as jest.Mock).mockReturnValue({ + finish: jest.fn(), + setStatus: jest.fn(), + startChild: jest.fn(() => ({ + finish: jest.fn(), + })), + }); + + firebaseService = { + isFcmInitialized: true, + getFirebaseApp: () => ({ name: 'test' }), + getFcmToken: jest.fn().mockReturnValue('fcmToken'), + addFcmMessageListener: jest.fn(), + addFirebaseEventListener: jest.fn(), + } as unknown as FirebaseService; + + appCheckService = new AppCheckService(firebaseService); + appCheckService.activate(); + }); + + it('subscribes for events on activation correctly', () => { + expect(firebaseService.addFcmMessageListener).toHaveBeenCalledWith( + FcmMessageEvents.ID_CHALLENGE, + expect.any(Function), + ); + + expect(firebaseService.addFirebaseEventListener).toHaveBeenCalledTimes(2); + expect(firebaseService.addFirebaseEventListener).toHaveBeenNthCalledWith( + 1, + FirebaseEvents.FCM_INITIALIZED, + expect.any(Function), + ); + expect(firebaseService.addFirebaseEventListener).toHaveBeenNthCalledWith( + 2, + FirebaseEvents.FCM_TERMINATED, + expect.any(Function), + ); + }); + + const appCheckMock = { app: { name: 'test' } } as AppCheck; + + beforeEach(() => { + jest.useFakeTimers(); + jest.mocked(initializeAppCheck).mockReturnValue(appCheckMock); + + // simulate FCM_INITIALIZED event + jest.mocked(firebaseService.addFirebaseEventListener).mock.calls[0]?.[1](); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('initializes appcheck correctly', () => { + expect(setTokenAutoRefreshEnabled).not.toHaveBeenCalled(); + expect(initializeAppCheck).toHaveBeenCalledWith( + { name: 'test' }, + { + provider: expect.any(CustomProvider), + isTokenAutoRefreshEnabled: true, + }, + ); + + // simulate FCM_INITIALIZED event (second time) + jest.mocked(firebaseService.addFirebaseEventListener).mock.calls[0]?.[1](); + + expect(initializeAppCheck).toHaveBeenCalledTimes(1); + expect(setTokenAutoRefreshEnabled).toHaveBeenCalledWith(appCheckMock, true); + }); + + it('terminates appcheck correctly', () => { + expect(setTokenAutoRefreshEnabled).not.toHaveBeenCalled(); + expect(initializeAppCheck).toHaveBeenCalledWith( + { name: 'test' }, + { + provider: expect.any(CustomProvider), + isTokenAutoRefreshEnabled: true, + }, + ); + + // simulate FCM_TERMINATED event + jest.mocked(firebaseService.addFirebaseEventListener).mock.calls[1]?.[1](); + + expect(setTokenAutoRefreshEnabled).toHaveBeenCalledWith( + appCheckMock, + false, + ); + }); + + describe('getToken', () => { + it('throws when FCM is not initialized', async () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + firebaseService.isFcmInitialized = false; + await expect( + jest.mocked(CustomProvider).mock.calls[0]?.[0].getToken(), + ).rejects.toThrow('fcm is not initialized'); + }); + + it('throws when FCM token is missing', async () => { + jest.mocked(firebaseService.getFcmToken).mockReturnValueOnce(undefined); + await expect( + jest.mocked(CustomProvider).mock.calls[0]?.[0].getToken(), + ).rejects.toThrow('fcm token is missing'); + }); + + it('throws a timeout error when challenge is not received in time', async () => { + jest + .mocked(CustomProvider) + .mock.calls[0]?.[0].getToken() + .catch((err) => { + expect(err).toBe('timeout'); + }); + + for (let i = 0; i <= WAIT_FOR_CHALLENGE_ATTEMPT_COUNT; i++) { + jest.advanceTimersByTime(WAIT_FOR_CHALLENGE_DELAY_MS); + await Promise.resolve(); + } + }); + + it('generates a token correctly', async () => { + jest.mocked(crypto.randomUUID).mockReturnValue('1-2-3-4-5'); + jest.mocked(solveChallenge).mockResolvedValueOnce('solution'); + jest + .mocked(verifyChallenge) + .mockResolvedValueOnce({ token: 'token', exp: 1234 }); + + const promise = jest.mocked(CustomProvider).mock.calls[0]?.[0].getToken(); + + // trigger ID_CHALLENGE event + jest.mocked(firebaseService.addFcmMessageListener).mock.calls[0]?.[1]({ + data: { + requestId: crypto.randomUUID(), + registrationId: 'registrationId', + type: ChallengeTypes.BASIC, + event: FcmMessageEvents.ID_CHALLENGE, + details: '{}', + }, + } as unknown as MessagePayload); + + await Promise.resolve(); + jest.advanceTimersByTime(1000); + await Promise.resolve(); + + await expect(promise).resolves.toStrictEqual({ + token: 'token', + expireTimeMillis: 1234, + }); + + expect(registerForChallenge).toHaveBeenCalledWith({ + token: 'fcmToken', + requestId: crypto.randomUUID(), + }); + expect(solveChallenge).toHaveBeenCalledWith({ + type: ChallengeTypes.BASIC, + challengeDetails: '{}', + }); + expect(verifyChallenge).toHaveBeenCalledWith({ + registrationId: 'registrationId', + solution: 'solution', + }); + }); + }); +}); diff --git a/src/background/services/appcheck/AppCheckService.ts b/src/background/services/appcheck/AppCheckService.ts new file mode 100644 index 000000000..a8c3e7df7 --- /dev/null +++ b/src/background/services/appcheck/AppCheckService.ts @@ -0,0 +1,187 @@ +import * as Sentry from '@sentry/browser'; +import { + AppCheck, + CustomProvider, + getToken, + initializeAppCheck, + setTokenAutoRefreshEnabled, +} from 'firebase/app-check'; +import { MessagePayload } from 'firebase/messaging/sw'; +import { singleton } from 'tsyringe'; +import { FirebaseService } from '../firebase/FirebaseService'; +import { FcmMessageEvents, FirebaseEvents } from '../firebase/models'; +import { AppCheckRegistrationChallenge, ChallengeRequest } from './models'; +import registerForChallenge from './utils/registerForChallenge'; +import verifyChallenge from './utils/verifyChallenge'; +import solveChallenge from './utils/solveChallenge'; + +export const WAIT_FOR_CHALLENGE_ATTEMPT_COUNT = 10; +export const WAIT_FOR_CHALLENGE_DELAY_MS = 500; + +// Implementation based on https://github.com/ava-labs/core-id-service/blob/main/docs/extension-appcheck-attestation.md +@singleton() +export class AppCheckService { + #appCheck?: AppCheck; + #lastChallengeRequest?: ChallengeRequest; + + constructor(private firebaseService: FirebaseService) {} + + activate(): void { + this.firebaseService.addFcmMessageListener( + FcmMessageEvents.ID_CHALLENGE, + (payload) => this.#handleMessage(payload), + ); + + this.firebaseService.addFirebaseEventListener( + FirebaseEvents.FCM_INITIALIZED, + () => this.#startAppcheck(), + ); + + this.firebaseService.addFirebaseEventListener( + FirebaseEvents.FCM_TERMINATED, + () => this.#stopAppcheck(), + ); + } + + async getAppcheckToken(forceRefresh = false) { + if (!this.firebaseService.isFcmInitialized || !this.#appCheck) { + return; + } + + return getToken(this.#appCheck, forceRefresh); + } + + #startAppcheck() { + if (this.#appCheck) { + setTokenAutoRefreshEnabled(this.#appCheck, true); + return; + } + + this.#appCheck = initializeAppCheck(this.firebaseService.getFirebaseApp(), { + provider: new CustomProvider({ + getToken: async () => { + const sentryTracker = Sentry.startTransaction({ + name: `AppCheckService: getToken`, + }); + + try { + if (!this.firebaseService.isFcmInitialized) { + throw new Error('fcm is not initialized'); + } + + const fcmToken = this.firebaseService.getFcmToken(); + + if (!fcmToken) { + throw new Error('fcm token is missing'); + } + + this.#lastChallengeRequest = { + id: crypto.randomUUID(), + tracker: sentryTracker, + }; + + const waitForChallenge = () => + new Promise<{ registrationId: string; solution: string }>( + (res, rej) => { + let remainingAttempts = WAIT_FOR_CHALLENGE_ATTEMPT_COUNT; + + const timer = setInterval(() => { + const registrationId = + this.#lastChallengeRequest?.registrationId; + const solution = this.#lastChallengeRequest?.solution; + + remainingAttempts--; + + if (registrationId && solution) { + clearInterval(timer); + res({ registrationId, solution }); + } else if (remainingAttempts < 1) { + clearInterval(timer); + rej('timeout'); + } + }, WAIT_FOR_CHALLENGE_DELAY_MS); + }, + ); + + try { + const registrationSpan = sentryTracker.startChild({ + name: 'registerForChallenge', + }); + await registerForChallenge({ + token: fcmToken, + requestId: this.#lastChallengeRequest.id, + }); + registrationSpan.finish(); + + const waitForChallengeSpan = sentryTracker.startChild({ + name: 'waitForChallenge', + }); + const { registrationId, solution } = await waitForChallenge(); + waitForChallengeSpan.finish(); + + const verifyChallengeSpan = sentryTracker.startChild({ + name: 'verifyChallenge', + }); + const { token, exp: expireTimeMillis } = await verifyChallenge({ + registrationId, + solution, + }); + verifyChallengeSpan.finish(); + + sentryTracker.setStatus('success'); + + return { + token, + expireTimeMillis, + }; + } catch (err) { + this.#lastChallengeRequest = undefined; + throw err; + } + } catch (err) { + sentryTracker.setStatus('error'); + throw err; + } finally { + sentryTracker.finish(); + } + }, + }), + isTokenAutoRefreshEnabled: true, + }); + } + + #stopAppcheck() { + if (this.#appCheck) { + setTokenAutoRefreshEnabled(this.#appCheck, false); + } + } + + #handleMessage(payload: MessagePayload) { + const event = payload.data?.event; + + if (event !== FcmMessageEvents.ID_CHALLENGE) { + throw new Error(`Unsupported event: "${event}"`); + } + + return this.#handleIdChallenge(payload); + } + + async #handleIdChallenge(payload: MessagePayload) { + const challenge = payload.data as AppCheckRegistrationChallenge; + + if (challenge.requestId !== this.#lastChallengeRequest?.id) { + return; + } + + this.#lastChallengeRequest.registrationId = challenge.registrationId; + + const solveChallengeSpan = this.#lastChallengeRequest.tracker.startChild({ + name: 'solveChallenge', + }); + this.#lastChallengeRequest.solution = await solveChallenge({ + type: challenge.type, + challengeDetails: challenge.details, + }); + solveChallengeSpan.finish(); + } +} diff --git a/src/background/services/appcheck/models.ts b/src/background/services/appcheck/models.ts new file mode 100644 index 000000000..b46f7024a --- /dev/null +++ b/src/background/services/appcheck/models.ts @@ -0,0 +1,28 @@ +import { FcmMessageEvents } from '../firebase/models'; +import * as Sentry from '@sentry/browser'; + +export type ChallengeRequest = { + id: string; + tracker: Sentry.Transaction; + registrationId?: string; + solution?: string; +}; + +export type ChallengeSolver = (details: string) => string | Promise; + +export enum Algorithm { + SHA256 = 'SHA256', + SHA512 = 'SHA512', +} + +export enum ChallengeTypes { + BASIC = 'BASIC', +} + +export type AppCheckRegistrationChallenge = { + requestId: string; + registrationId: string; + type: ChallengeTypes; + event: FcmMessageEvents.ID_CHALLENGE; + details: string; +}; diff --git a/src/background/services/appcheck/utils/challenges/basic.test.ts b/src/background/services/appcheck/utils/challenges/basic.test.ts new file mode 100644 index 000000000..a3764d51b --- /dev/null +++ b/src/background/services/appcheck/utils/challenges/basic.test.ts @@ -0,0 +1,33 @@ +import { Algorithm } from '../../models'; +import getHashByAlgorithm from '../getHashByAlgorithm'; +import solveBasicChallenge from './basic'; + +jest.mock('../getHashByAlgorithm'); + +describe('basic', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('throws when params are incorrect', () => { + expect(() => solveBasicChallenge('{}')).toThrow( + 'invalid basic challenge details', + ); + }); + + it('solves the challenge correctly', () => { + jest + .mocked(getHashByAlgorithm) + .mockReturnValueOnce('0123') + .mockReturnValueOnce('00123') + .mockReturnValueOnce('000123'); + + const details = { + token: '123', + difficulty: 3, + algorithm: Algorithm.SHA256, + }; + + expect(solveBasicChallenge(JSON.stringify(details))).toBe('000123.3'); + }); +}); diff --git a/src/background/services/appcheck/utils/challenges/basic.ts b/src/background/services/appcheck/utils/challenges/basic.ts new file mode 100644 index 000000000..179199aa7 --- /dev/null +++ b/src/background/services/appcheck/utils/challenges/basic.ts @@ -0,0 +1,53 @@ +import Joi from 'joi'; +import { Algorithm } from '../../models'; +import getHashByAlgorithm from '../getHashByAlgorithm'; + +type BasicChallengeDetails = { + token: string; + difficulty: number; + algorithm: Algorithm; +}; + +const challengeDetailsSchema = Joi.object({ + token: Joi.string().required(), + difficulty: Joi.number().required(), + algorithm: Joi.string() + .valid(...Object.values(Algorithm)) + .required(), +}).required(); + +const _calculate = ({ + token, + difficulty, + algorithm, +}: { + token: string; + difficulty: number; + algorithm: Algorithm; +}) => { + let nonce = 0; + let hash = ''; + + while (hash.substring(0, difficulty) !== Array(difficulty + 1).join('0')) { + hash = getHashByAlgorithm(algorithm, `${token}${++nonce}`); + } + + return { hash, nonce }; +}; + +const solveBasicChallenge = (details: string) => { + const parsedDetails = JSON.parse(details) as BasicChallengeDetails; + + const { error: schemaValidationError } = + challengeDetailsSchema.validate(parsedDetails); + + if (schemaValidationError) { + throw new Error('invalid basic challenge details'); + } + + const result = _calculate({ ...parsedDetails }); + + return `${result.hash}.${result.nonce}`; +}; + +export default solveBasicChallenge; diff --git a/src/background/services/appcheck/utils/getHashByAlgorithm.test.ts b/src/background/services/appcheck/utils/getHashByAlgorithm.test.ts new file mode 100644 index 000000000..73bed58ee --- /dev/null +++ b/src/background/services/appcheck/utils/getHashByAlgorithm.test.ts @@ -0,0 +1,26 @@ +import { Algorithm } from '../models'; +import getHashByAlgorithm from './getHashByAlgorithm'; + +describe('getHashByAlgorithm', () => { + const cases: Record = { + [Algorithm.SHA256]: + '0d45f5fd462b8c70bffb10021ac1bcff3f58f29b1faf7568595095427d42812c', + [Algorithm.SHA512]: + 'de91078a5b1eb16864d1db7baffa09ab367e6ac301fa61a484dfd7095def57de8bedfa2ba415f59d7e76753c807d0fc5b0e8963d1fd1992b6936ba9383c4975e', + }; + + it('throws for unsupported algorithms', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + expect(() => getHashByAlgorithm('unknown', 'core')).toThrow( + 'unsupported algorithm "unknown"', + ); + }); + + it.each(Object.entries(cases))( + 'uses the correct algorithm', + (algorithm, expected) => { + expect(getHashByAlgorithm(algorithm as Algorithm, 'core')).toBe(expected); + }, + ); +}); diff --git a/src/background/services/appcheck/utils/getHashByAlgorithm.ts b/src/background/services/appcheck/utils/getHashByAlgorithm.ts new file mode 100644 index 000000000..63013f37a --- /dev/null +++ b/src/background/services/appcheck/utils/getHashByAlgorithm.ts @@ -0,0 +1,15 @@ +import { createHash } from 'crypto'; +import { Algorithm } from '../models'; + +const getHashByAlgorithm = (algorithm: Algorithm, data: string) => { + switch (algorithm) { + case Algorithm.SHA256: + return createHash('sha256').update(data).digest('hex'); + case Algorithm.SHA512: + return createHash('sha512').update(data).digest('hex'); + default: + throw new Error(`unsupported algorithm "${algorithm}"`); + } +}; + +export default getHashByAlgorithm; diff --git a/src/background/services/appcheck/utils/registerForChallenge.test.ts b/src/background/services/appcheck/utils/registerForChallenge.test.ts new file mode 100644 index 000000000..028f5fb78 --- /dev/null +++ b/src/background/services/appcheck/utils/registerForChallenge.test.ts @@ -0,0 +1,70 @@ +import registerForChallenge from './registerForChallenge'; + +describe('registerForChallenge', () => { + const realEnv = process.env; + const realFetch = global.fetch; + const params = { + token: 'token', + requestId: 'requestId', + }; + + beforeEach(() => { + jest.resetAllMocks(); + + global.fetch = jest.fn(); + process.env = { + ...realEnv, + ID_SERVICE_URL: 'https://id.com', + }; + }); + + afterAll(() => { + global.fetch = realFetch; + process.env = realEnv; + }); + + it('throws when ID_SERVICE_URL is missing', async () => { + delete process.env.ID_SERVICE_URL; + + expect(registerForChallenge(params)).rejects.toThrow( + 'ID_SERVICE_URL is missing', + ); + }); + + it('throws when the challenge registration fails', async () => { + jest.mocked(global.fetch).mockResolvedValueOnce({ + ok: false, + statusText: 'internal error', + } as Response); + + expect(registerForChallenge(params)).rejects.toThrow( + 'challenge registration error: "internal error"', + ); + }); + + it('registers for challenge successfully', async () => { + chrome.runtime.getManifest = jest + .fn() + .mockReturnValueOnce({ version: '0.0.1' }); + jest.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + } as Response); + + expect(registerForChallenge(params)).resolves.toBeUndefined(); + expect(global.fetch).toHaveBeenCalledWith( + 'https://id.com/v1/ext/register', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-App-Type': 'extension', + 'X-App-Version': '0.0.1', + }, + body: JSON.stringify({ + token: 'token', + requestId: 'requestId', + }), + }, + ); + }); +}); diff --git a/src/background/services/appcheck/utils/registerForChallenge.ts b/src/background/services/appcheck/utils/registerForChallenge.ts new file mode 100644 index 000000000..da8cd2e3e --- /dev/null +++ b/src/background/services/appcheck/utils/registerForChallenge.ts @@ -0,0 +1,33 @@ +type Params = { + token: string; + requestId: string; +}; + +const registerForChallenge = async ({ token, requestId }: Params) => { + const url = process.env.ID_SERVICE_URL; + + if (!url) { + throw new Error('ID_SERVICE_URL is missing'); + } + + const registerResponse = await fetch(`${url}/v1/ext/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-App-Type': 'extension', + 'X-App-Version': chrome.runtime.getManifest().version, + }, + body: JSON.stringify({ + token, + requestId, + }), + }); + + if (!registerResponse.ok) { + throw new Error( + `challenge registration error: "${registerResponse.statusText}"`, + ); + } +}; + +export default registerForChallenge; diff --git a/src/background/services/appcheck/utils/solveChallenge.test.ts b/src/background/services/appcheck/utils/solveChallenge.test.ts new file mode 100644 index 000000000..df506b7f7 --- /dev/null +++ b/src/background/services/appcheck/utils/solveChallenge.test.ts @@ -0,0 +1,23 @@ +import { ChallengeTypes } from '../models'; +import solveBasicChallenge from './challenges/basic'; +import solveChallenge from './solveChallenge'; + +jest.mock('./challenges/basic'); + +describe('solveChallenge', () => { + const solution = 'solution'; + const challengeDetails = '{}'; + + beforeEach(() => { + jest.resetAllMocks(); + + jest.mocked(solveBasicChallenge).mockReturnValueOnce(solution); + }); + + it('solves basic challenges correctly', async () => { + expect( + solveChallenge({ type: ChallengeTypes.BASIC, challengeDetails }), + ).resolves.toBe(solution); + expect(solveBasicChallenge).toHaveBeenCalledWith(challengeDetails); + }); +}); diff --git a/src/background/services/appcheck/utils/solveChallenge.ts b/src/background/services/appcheck/utils/solveChallenge.ts new file mode 100644 index 000000000..9708416c5 --- /dev/null +++ b/src/background/services/appcheck/utils/solveChallenge.ts @@ -0,0 +1,18 @@ +import { ChallengeSolver, ChallengeTypes } from '../models'; +import solveBasicChallenge from './challenges/basic'; + +type Params = { + type: ChallengeTypes; + challengeDetails: string; +}; + +const CHALLENGE_MAP: Record = { + [ChallengeTypes.BASIC]: solveBasicChallenge, +}; + +const solveChallenge = async ({ type, challengeDetails }: Params) => { + const solver = CHALLENGE_MAP[type]; + return solver(challengeDetails); +}; + +export default solveChallenge; diff --git a/src/background/services/appcheck/utils/verifyChallenge.test.ts b/src/background/services/appcheck/utils/verifyChallenge.test.ts new file mode 100644 index 000000000..53986b363 --- /dev/null +++ b/src/background/services/appcheck/utils/verifyChallenge.test.ts @@ -0,0 +1,68 @@ +import verifyChallenge from './verifyChallenge'; + +describe('verifyChallenge', () => { + const realEnv = process.env; + const realFetch = global.fetch; + const params = { + registrationId: 'registrationId', + solution: 'solution', + }; + + beforeEach(() => { + jest.resetAllMocks(); + + global.fetch = jest.fn(); + process.env = { + ...realEnv, + ID_SERVICE_URL: 'https://id.com', + }; + }); + + afterAll(() => { + global.fetch = realFetch; + process.env = realEnv; + }); + + it('throws when ID_SERVICE_URL is missing', async () => { + delete process.env.ID_SERVICE_URL; + + expect(verifyChallenge(params)).rejects.toThrow( + 'ID_SERVICE_URL is missing', + ); + }); + + it('throws when the challenge verification fails', async () => { + jest.mocked(global.fetch).mockResolvedValueOnce({ + ok: false, + statusText: 'internal error', + } as Response); + + expect(verifyChallenge(params)).rejects.toThrow( + 'challenge verification error: "internal error"', + ); + }); + + it('verifies the challenge solution correctly', async () => { + chrome.runtime.getManifest = jest + .fn() + .mockReturnValueOnce({ version: '0.0.1' }); + jest.mocked(global.fetch).mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValueOnce({ foo: 'bar' }), + } as unknown as Response); + + expect(verifyChallenge(params)).resolves.toStrictEqual({ foo: 'bar' }); + expect(global.fetch).toHaveBeenCalledWith('https://id.com/v1/ext/verify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-App-Type': 'extension', + 'X-App-Version': '0.0.1', + }, + body: JSON.stringify({ + registrationId: 'registrationId', + solution: 'solution', + }), + }); + }); +}); diff --git a/src/background/services/appcheck/utils/verifyChallenge.ts b/src/background/services/appcheck/utils/verifyChallenge.ts new file mode 100644 index 000000000..4da120623 --- /dev/null +++ b/src/background/services/appcheck/utils/verifyChallenge.ts @@ -0,0 +1,35 @@ +type Params = { + registrationId: string; + solution: string; +}; + +const verifyChallenge = async ({ registrationId, solution }: Params) => { + const url = process.env.ID_SERVICE_URL; + + if (!url) { + throw new Error('ID_SERVICE_URL is missing'); + } + + const verifyChallengeResponse = await fetch(`${url}/v1/ext/verify`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-App-Type': 'extension', + 'X-App-Version': chrome.runtime.getManifest().version, + }, + body: JSON.stringify({ + registrationId, + solution, + }), + }); + + if (!verifyChallengeResponse.ok) { + throw new Error( + `challenge verification error: "${verifyChallengeResponse.statusText}"`, + ); + } + + return verifyChallengeResponse.json(); +}; + +export default verifyChallenge; diff --git a/src/background/services/featureFlags/models.ts b/src/background/services/featureFlags/models.ts index 60c266733..100037948 100644 --- a/src/background/services/featureFlags/models.ts +++ b/src/background/services/featureFlags/models.ts @@ -45,6 +45,7 @@ export enum FeatureGates { BLOCKAID_TRANSACTION_SCAN = 'blockaid-transaction-scan', BLOCKAID_JSONRPC_SCAN = 'blockaid-jsonrpc-scan', HALLIDAY_BRIDGE_BANNER = 'halliday-bridge-banner', + FIREBASE_CLOUD_MESSAGING = 'firebase-cloud-messaging', ONE_CLICK_SWAP = 'one-click-swap', } @@ -97,6 +98,7 @@ export const DISABLED_FLAG_VALUES: FeatureFlags = { [FeatureGates.BLOCKAID_TRANSACTION_SCAN]: false, [FeatureGates.BLOCKAID_JSONRPC_SCAN]: false, [FeatureGates.HALLIDAY_BRIDGE_BANNER]: false, + [FeatureGates.FIREBASE_CLOUD_MESSAGING]: false, [FeatureGates.ONE_CLICK_SWAP]: false, }; @@ -148,6 +150,7 @@ export const DEFAULT_FLAGS: FeatureFlags = { [FeatureGates.BLOCKAID_TRANSACTION_SCAN]: true, [FeatureGates.BLOCKAID_JSONRPC_SCAN]: true, [FeatureGates.HALLIDAY_BRIDGE_BANNER]: true, + [FeatureGates.FIREBASE_CLOUD_MESSAGING]: true, [FeatureGates.ONE_CLICK_SWAP]: true, }; diff --git a/src/background/services/firebase/FirebaseService.test.ts b/src/background/services/firebase/FirebaseService.test.ts new file mode 100644 index 000000000..61731ea04 --- /dev/null +++ b/src/background/services/firebase/FirebaseService.test.ts @@ -0,0 +1,160 @@ +import { FirebaseApp, initializeApp } from 'firebase/app'; +import { FeatureFlagService } from '../featureFlags/FeatureFlagService'; +import { FirebaseService } from './FirebaseService'; +import { + getMessaging, + Messaging, + NextFn, + onBackgroundMessage, +} from 'firebase/messaging/sw'; +import { + FeatureFlagEvents, + FeatureFlags, + FeatureGates, +} from '../featureFlags/models'; +import { deleteToken, getToken, MessagePayload } from 'firebase/messaging'; +import { FcmMessageEvents, FirebaseEvents } from './models'; + +jest.mock('firebase/app'); +jest.mock('firebase/messaging'); +jest.mock('firebase/messaging/sw'); + +describe('FirebaseService', () => { + const realEnv = process.env; + const featureFlagService = { + addListener: jest.fn(), + } as unknown as FeatureFlagService; + const appMock = { name: 'test' } as FirebaseApp; + const messagingMock = { app: appMock } as Messaging; + + beforeEach(() => { + jest.resetAllMocks(); + jest.mocked(initializeApp).mockReturnValue(appMock); + jest.mocked(getMessaging).mockReturnValue(messagingMock); + + process.env = { + ...realEnv, + FIREBASE_CONFIG: Buffer.from(JSON.stringify({ foo: 'bar' })).toString( + 'base64', + ), + }; + }); + + afterAll(() => { + process.env = realEnv; + }); + + it('throws when FIREBASE_CONFIG is missing', () => { + delete process.env.FIREBASE_CONFIG; + + expect(() => new FirebaseService(featureFlagService)).toThrow( + 'FIREBASE_CONFIG is missing', + ); + }); + + it('initializes correctly', () => { + const firebaseService = new FirebaseService(featureFlagService); + + expect(firebaseService.getFirebaseApp()).toBe(appMock); + expect(firebaseService.isFcmInitialized).toBe(false); + expect(firebaseService.getFcmToken()).toBeUndefined(); + expect(initializeApp).toHaveBeenCalledWith({ foo: 'bar' }); + expect(getMessaging).toHaveBeenCalledWith(appMock); + expect(onBackgroundMessage).toHaveBeenCalledWith( + messagingMock, + expect.any(Function), + ); + expect(featureFlagService.addListener).toHaveBeenCalledWith( + FeatureFlagEvents.FEATURE_FLAG_UPDATED, + expect.any(Function), + ); + }); + + describe('FCM', () => { + const fcmTokenMock = 'fcmToken'; + + beforeEach(() => { + jest.mocked(getToken).mockResolvedValue(fcmTokenMock); + }); + + it('initializes FCM when the feature is enabled', async () => { + const initializedEventListener = jest.fn(); + const firebaseService = new FirebaseService(featureFlagService); + + firebaseService.addFirebaseEventListener( + FirebaseEvents.FCM_INITIALIZED, + initializedEventListener, + ); + + // simulate FEATURE_FLAG_UPDATED event + await expect( + jest.mocked(featureFlagService.addListener).mock.calls[0]?.[1]({ + [FeatureGates.FIREBASE_CLOUD_MESSAGING]: true, + } as FeatureFlags), + ).resolves.toBeUndefined(); + + expect(firebaseService.isFcmInitialized).toBe(true); + expect(firebaseService.getFcmToken()).toBe(fcmTokenMock); + expect(getMessaging).toHaveBeenCalledWith(appMock); + expect(getToken).toHaveBeenCalledWith(messagingMock, { + serviceWorkerRegistration: globalThis.registration, + }); + expect(initializedEventListener).toHaveBeenCalledTimes(1); + }); + + it('terminates FCM when the feature is disabled', async () => { + const terminatedEventListener = jest.fn(); + const firebaseService = new FirebaseService(featureFlagService); + + firebaseService.addFirebaseEventListener( + FirebaseEvents.FCM_TERMINATED, + terminatedEventListener, + ); + + // simulate FEATURE_FLAG_UPDATED event (enabled) + await expect( + jest.mocked(featureFlagService.addListener).mock.calls[0]?.[1]({ + [FeatureGates.FIREBASE_CLOUD_MESSAGING]: true, + } as FeatureFlags), + ).resolves.toBeUndefined(); + + // simulate FEATURE_FLAG_UPDATED event (disabled) + await expect( + jest.mocked(featureFlagService.addListener).mock.calls[0]?.[1]({ + [FeatureGates.FIREBASE_CLOUD_MESSAGING]: false, + } as FeatureFlags), + ).resolves.toBeUndefined(); + + expect(firebaseService.isFcmInitialized).toBe(false); + expect(firebaseService.getFcmToken()).toBeUndefined(); + expect(getMessaging).toHaveBeenCalledWith(appMock); + expect(deleteToken).toHaveBeenCalledWith(messagingMock); + expect(terminatedEventListener).toHaveBeenCalledTimes(1); + }); + + it.each(Object.values(FcmMessageEvents))( + 'emits incoming messages to the listeners correctly', + async (event) => { + const messageMock = { + data: { + event, + foo: 'bar', + }, + } as unknown as MessagePayload; + const eventListener = jest.fn(); + const firebaseService = new FirebaseService(featureFlagService); + + firebaseService.addFcmMessageListener(event, eventListener); + + // simulate incoming message + ( + jest.mocked(onBackgroundMessage).mock + .calls[0]?.[1] as NextFn + )(messageMock); + + expect(eventListener).toHaveBeenCalledTimes(1); + expect(eventListener).toHaveBeenCalledWith(messageMock); + }, + ); + }); +}); diff --git a/src/background/services/firebase/FirebaseService.ts b/src/background/services/firebase/FirebaseService.ts new file mode 100644 index 000000000..57e0a42ae --- /dev/null +++ b/src/background/services/firebase/FirebaseService.ts @@ -0,0 +1,103 @@ +import { EventEmitter } from 'events'; +import { FirebaseApp, initializeApp } from 'firebase/app'; +import { deleteToken, getToken } from 'firebase/messaging'; +import { + getMessaging, + MessagePayload, + onBackgroundMessage, +} from 'firebase/messaging/sw'; +import { singleton } from 'tsyringe'; +import { FcmMessageEvents, FirebaseEvents, FcmMessageListener } from './models'; +import sentryCaptureException, { + SentryExceptionTypes, +} from '@src/monitoring/sentryCaptureException'; +import { FeatureFlagService } from '../featureFlags/FeatureFlagService'; +import { FeatureFlagEvents, FeatureGates } from '../featureFlags/models'; + +@singleton() +export class FirebaseService { + #app: FirebaseApp; + #isFcmInitialized = false; + #fcmToken?: string; + #firebaseEventEmitter = new EventEmitter(); + #fcmMessageEventEmitter = new EventEmitter(); + + constructor(private featureFlagService: FeatureFlagService) { + if (!process.env.FIREBASE_CONFIG) { + throw new Error('FIREBASE_CONFIG is missing'); + } + + this.#app = initializeApp( + JSON.parse(Buffer.from(process.env.FIREBASE_CONFIG, 'base64').toString()), + ); + + onBackgroundMessage(getMessaging(this.#app), (payload) => { + this.#handleMessage(payload); + }); + + this.featureFlagService.addListener( + FeatureFlagEvents.FEATURE_FLAG_UPDATED, + async (featureFlags) => { + try { + if ( + this.#isFcmInitialized && + !featureFlags[FeatureGates.FIREBASE_CLOUD_MESSAGING] + ) { + await deleteToken(getMessaging(this.#app)); + + this.#isFcmInitialized = false; + this.#fcmToken = undefined; + this.#firebaseEventEmitter.emit(FirebaseEvents.FCM_TERMINATED); + return; + } + + if ( + !this.#isFcmInitialized && + featureFlags[FeatureGates.FIREBASE_CLOUD_MESSAGING] + ) { + this.#fcmToken = await getToken(getMessaging(this.#app), { + serviceWorkerRegistration: globalThis.registration, + }); + + this.#isFcmInitialized = true; + this.#firebaseEventEmitter.emit(FirebaseEvents.FCM_INITIALIZED); + return; + } + } catch (err) { + sentryCaptureException(err as Error, SentryExceptionTypes.FIREBASE); + } + }, + ); + } + + get isFcmInitialized() { + return this.#isFcmInitialized; + } + + getFirebaseApp() { + return this.#app; + } + + getFcmToken() { + return this.#fcmToken; + } + + addFirebaseEventListener( + event: FirebaseEvents, + callback: () => T, + ) { + this.#firebaseEventEmitter.on(event, callback); + } + + addFcmMessageListener(event: FcmMessageEvents, listener: FcmMessageListener) { + this.#fcmMessageEventEmitter.on(event, listener); + } + + async #handleMessage(payload: MessagePayload) { + const event = payload.data?.event ?? ''; + + if (Object.values(FcmMessageEvents).includes(event as FcmMessageEvents)) { + this.#fcmMessageEventEmitter.emit(event, payload); + } + } +} diff --git a/src/background/services/firebase/models.ts b/src/background/services/firebase/models.ts new file mode 100644 index 000000000..292188ba6 --- /dev/null +++ b/src/background/services/firebase/models.ts @@ -0,0 +1,14 @@ +import { MessagePayload } from 'firebase/messaging'; + +export enum FirebaseEvents { + FCM_INITIALIZED = 'FCM_INITIALIZED', + FCM_TERMINATED = 'FCM_TERMINATED', +} + +export enum FcmMessageEvents { + ID_CHALLENGE = 'ID_CHALLENGE', +} + +export type FcmMessageListener = ( + payload: MessagePayload, +) => unknown | Promise; diff --git a/src/monitoring/sentryCaptureException.ts b/src/monitoring/sentryCaptureException.ts index 3b651f285..c79e34e22 100644 --- a/src/monitoring/sentryCaptureException.ts +++ b/src/monitoring/sentryCaptureException.ts @@ -30,6 +30,8 @@ export enum SentryExceptionTypes { VM_MODULES = 'vmModules', ONBOARDING = 'onboarding', + + FIREBASE = 'firebase', } // wrapper to make error reporting contexts unfirom accross the codebase diff --git a/yarn.lock b/yarn.lock index d19c81355..bc4cfcd97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3344,6 +3344,396 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@firebase/analytics-compat@0.2.16": + version "0.2.16" + resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.16.tgz#a84513988358494ef6f80ef626d4198da2b62381" + integrity sha512-Q/s+u/TEMSb2EDJFQMGsOzpSosybBl8HuoSEMyGZ99+0Pu7SIR9MPDGUjc8PKiCFQWDJ3QXxgqh1d/rujyAMbA== + dependencies: + "@firebase/analytics" "0.10.10" + "@firebase/analytics-types" "0.8.3" + "@firebase/component" "0.6.11" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/analytics-types@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f" + integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg== + +"@firebase/analytics@0.10.10": + version "0.10.10" + resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.10.tgz#68492c7ec3a341fd6caf3f89cbd9e7e7ad374140" + integrity sha512-Psdo7c9g2SLAYh6u1XRA+RZ7ab2JfBVuAt/kLzXkhKZL/gS2cQUCMsOW5p0RIlDPRKqpdNSmvujd2TeRWLKOkQ== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/installations" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/app-check-compat@0.3.17": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.17.tgz#ec0e0b52c4c5ea7cb6d976f9d1f892e97d19febc" + integrity sha512-a/eadrGsY0MVCBPhrNbKUhoYpms4UKTYLKO7nswwSFVsm3Rw6NslQQCNLfvljcDqP4E7alQDRGJXjkxd/5gJ+Q== + dependencies: + "@firebase/app-check" "0.8.10" + "@firebase/app-check-types" "0.5.3" + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/app-check-interop-types@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz#ed9c4a4f48d1395ef378f007476db3940aa5351a" + integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A== + +"@firebase/app-check-types@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5" + integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng== + +"@firebase/app-check@0.8.10": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.10.tgz#7fa10c9242ecc181a0f45a68bfc268e12729ff02" + integrity sha512-DWFfxxif/t+Ow4MmRUevDX+A3hVxm1rUf6y5ZP4sIomfnVCO1NNahqtsv9rb1/tKGkTeoVT40weiTS/WjQG1mA== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/app-compat@0.2.47": + version "0.2.47" + resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.47.tgz#a223c21ec8b5a3ef59aabdfa5f19baf37a2078e8" + integrity sha512-TdEWGDp6kSwuO1mxiM2Fe39eLWygfyzqTZcoU3aPV0viqqphPCbBBnVjPbFJErZ4+yaS7uCWXEbFEP9m5/COKA== + dependencies: + "@firebase/app" "0.10.17" + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/app-types@0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.3.tgz#8408219eae9b1fb74f86c24e7150a148460414ad" + integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw== + +"@firebase/app@0.10.17": + version "0.10.17" + resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.10.17.tgz#d66df53c903cf38fe8bf15f1f5278d08b1564a62" + integrity sha512-53sIYyAnYEPIZdaxuyq5OST7j4KBc2pqmktz+tEb1BIUSbXh8Gp4k/o6qzLelLpm4ngrBz7SRN0PZJqNRAyPog== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/auth-compat@0.5.16": + version "0.5.16" + resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.16.tgz#ef365d2594c0706688abefb8dd61f32a677625f6" + integrity sha512-YlYwJMBqAyv0ESy3jDUyshMhZlbUiwAm6B6+uUmigNDHU+uq7j4SFiDJEZlFFIz397yBzKn06SUdqutdQzGnCA== + dependencies: + "@firebase/auth" "1.8.1" + "@firebase/auth-types" "0.12.3" + "@firebase/component" "0.6.11" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/auth-interop-types@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz#176a08686b0685596ff03d7879b7e4115af53de0" + integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA== + +"@firebase/auth-types@0.12.3": + version "0.12.3" + resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.12.3.tgz#650e54a36060b5ea012075ddbd3cb26566334c41" + integrity sha512-Zq9zI0o5hqXDtKg6yDkSnvMCMuLU6qAVS51PANQx+ZZX5xnzyNLEBO3GZgBUPsV5qIMFhjhqmLDxUqCbnAYy2A== + +"@firebase/auth@1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.8.1.tgz#8b508328f60bb069a11de8c7e1e4e39e01d5a796" + integrity sha512-LX9N/Cf5Z35r5yqm2+5M3+2bRRe/+RFaa/+u4HDni7TA27C/Xm4XHLKcWcLg1BzjrS4zngSaBEOSODvp6RFOqQ== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/component@0.6.11": + version "0.6.11" + resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.11.tgz#228a2ff5a6b0e5970b84d4dd298bf6ed0483018e" + integrity sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug== + dependencies: + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/data-connect@0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.1.3.tgz#a8184a2ece8a78b24240100b91ac4721e622cd4e" + integrity sha512-FbAQpWNHownJx1VTCQI4ydbWGOZmSWXoFlirQn3ItHqsLJYSywqxSgDafzvyooifFh3J/2WqaM8y9hInnPcsTw== + dependencies: + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/database-compat@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.0.1.tgz#063c4bff74782337117280fbf5b73b463a9a0638" + integrity sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/database" "1.0.10" + "@firebase/database-types" "1.0.7" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/database-types@1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.7.tgz#8a0819ca8c486fc967d3a9021a008c8f969576bf" + integrity sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A== + dependencies: + "@firebase/app-types" "0.9.3" + "@firebase/util" "1.10.2" + +"@firebase/database@1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.10.tgz#e23044d5fd2cdfd07e7bef57fd80643db07af33d" + integrity sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + faye-websocket "0.11.4" + tslib "^2.1.0" + +"@firebase/firestore-compat@0.3.40": + version "0.3.40" + resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.40.tgz#3f4cfc2d25d2f25d9925cdf5903c0b49bfdaeebc" + integrity sha512-18HopMN811KYBc9Ptpr1Rewwio0XF09FF3jc5wtV6rGyAs815SlFFw5vW7ZeLd43zv9tlEc2FzM0H+5Vr9ZRxw== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/firestore" "4.7.5" + "@firebase/firestore-types" "3.0.3" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/firestore-types@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1" + integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q== + +"@firebase/firestore@4.7.5": + version "4.7.5" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.7.5.tgz#a34d215b28b2888841ed5e06fa6db2ae2246098a" + integrity sha512-OO3rHvjC07jL2ITN255xH/UzCVSvh6xG8oTzQdFScQvFbcm1fjCL1hgAdpDZcx3vVcKMV+6ktr8wbllkB8r+FQ== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + "@firebase/webchannel-wrapper" "1.0.3" + "@grpc/grpc-js" "~1.9.0" + "@grpc/proto-loader" "^0.7.8" + tslib "^2.1.0" + +"@firebase/functions-compat@0.3.17": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.17.tgz#d24b46ed5f1ccc2b8a0bcbad1e6b4fa29c290be2" + integrity sha512-oj2XV8YsJYutyPCRYUfbN6swmfrL6zar0/qtqZsKT7P7btOiYRl+lD6fxtQaT+pKE5YgOBGZW//kLPZfY0jWhw== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/functions" "0.12.0" + "@firebase/functions-types" "0.6.3" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/functions-types@0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654" + integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg== + +"@firebase/functions@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.12.0.tgz#0e0a4fe0669596cfdd4e40775995acc1e7b368e9" + integrity sha512-plTtzY/nT0jOgHzT0vB9qch4FpHFOhCnR8HhYBqqdArG6GOQMIruKZbiTyLybO8bcaaNgQ6kSm9yohGUwxHcIw== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.6.11" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/installations-compat@0.2.11": + version "0.2.11" + resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.11.tgz#8bd9e3fbcf9a7fabcfa2386264be6a224567cdb5" + integrity sha512-SHRgw5LTa6v8LubmJZxcOCwEd1MfWQPUtKdiuCx2VMWnapX54skZd1PkQg0K4l3k+4ujbI2cn7FE6Li9hbChBw== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/installations" "0.6.11" + "@firebase/installations-types" "0.5.3" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/installations-types@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f" + integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA== + +"@firebase/installations@0.6.11": + version "0.6.11" + resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.11.tgz#0ac38af96ec737f458aa93d07e0499bd1dde9bdd" + integrity sha512-w8fY8mw6fxJzsZM2ufmTtomopXl1+bn/syYon+Gpn+0p0nO1cIUEVEFrFazTLaaL9q1CaVhc3HmseRTsI3igAA== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/util" "1.10.2" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/logger@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.4.tgz#29e8379d20fd1149349a195ee6deee4573a86f48" + integrity sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g== + dependencies: + tslib "^2.1.0" + +"@firebase/messaging-compat@0.2.15": + version "0.2.15" + resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.15.tgz#12244bb96e60078709e196b533b3e73089faaac3" + integrity sha512-mEKKASRvRWq1aBNHgioGsOYR2c5nBZpO7k90K794zjKe0WkGNf0k7PLs5SlCf8FKnzumEkhTAp/SjYxovuxa8A== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/messaging" "0.12.15" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/messaging-interop-types@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e" + integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q== + +"@firebase/messaging@0.12.15": + version "0.12.15" + resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.15.tgz#37afdb6adbbd79278d430884f323311599bf6529" + integrity sha512-Bz+qvWNEwEWAbYtG4An8hgcNco6NWNoNLuLbGVwPL2fAoCF1zz+dcaBp+iTR2+K199JyRyDT9yDPAXhNHNDaKQ== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/installations" "0.6.11" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.10.2" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/performance-compat@0.2.11": + version "0.2.11" + resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.11.tgz#35799c62d4c477d4b82d9824359a2bd88386db12" + integrity sha512-DqeNBy51W2xzlklyC7Ht9JQ94HhTA08PCcM4MDeyG/ol3fqum/+YgtHWQ2IQuduqH9afETthZqLwCZiSgY7hiA== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/performance" "0.6.11" + "@firebase/performance-types" "0.2.3" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/performance-types@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb" + integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ== + +"@firebase/performance@0.6.11": + version "0.6.11" + resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.11.tgz#c07c0e30117f9b2890d8995259e07424f6d9e265" + integrity sha512-FlkJFeqLlIeh5T4Am3uE38HVzggliDIEFy/fErEc1faINOUFCb6vQBEoNZGaXvRnTR8lh3X/hP7tv37C7BsK9g== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/installations" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/remote-config-compat@0.2.11": + version "0.2.11" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.11.tgz#5c3660acf673b1787837cc7eba3a07bc958989f5" + integrity sha512-zfIjpwPrGuIOZDmduukN086qjhZ1LnbJi/iYzgua+2qeTlO0XdlE1v66gJPwygGB3TOhT0yb9EiUZ3nBNttMqg== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/remote-config" "0.4.11" + "@firebase/remote-config-types" "0.3.3" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/remote-config-types@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.3.3.tgz#8105382aabf0ee94607a6081889af122d305dd0d" + integrity sha512-YlRI9CHxrk3lpQuFup9N1eohpwdWayKZUNZ/YeQ0PZoncJ66P32UsKUKqVXOaieTjJIOh7yH8JEzRdht5s+d6g== + +"@firebase/remote-config@0.4.11": + version "0.4.11" + resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.11.tgz#7d664e12148c3135c177739020c754aa41d6e542" + integrity sha512-9z0rgKuws2nj+7cdiqF+NY1QR4na6KnuOvP+jQvgilDOhGtKOcCMq5XHiu66i73A9kFhyU6QQ2pHXxcmaq1pBw== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/installations" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/storage-compat@0.3.14": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.14.tgz#1779f44c517bb6d8d93245ac53a9bc1ecb2e1225" + integrity sha512-Ok5FmXJiapaNAOQ8W8qppnfwgP8540jw2B8M0c4TFZqF4BD+CoKBxW0dRtOuLNGadLhzqqkDZZZtkexxrveQqA== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/storage" "0.13.4" + "@firebase/storage-types" "0.8.3" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/storage-types@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d" + integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg== + +"@firebase/storage@0.13.4": + version "0.13.4" + resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.13.4.tgz#83a3b638ffb8dbb8cb4f58d25c0961dfebc7cd9d" + integrity sha512-b1KaTTRiMupFurIhpGIbReaWev0k5O3ouTHkAPcEssT+FvU3q/1JwzvkX4+ZdB60Fc43Mbp8qQ1gWfT0Z2FP9Q== + dependencies: + "@firebase/component" "0.6.11" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/util@1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.10.2.tgz#4dbb565cfbdf51b4fb2081c5093dba3037d49a35" + integrity sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q== + dependencies: + tslib "^2.1.0" + +"@firebase/vertexai@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@firebase/vertexai/-/vertexai-1.0.2.tgz#e9104361e88531d9f6f7c9f25ea64287f0a061b9" + integrity sha512-4dC9m2nD0tkfKJT5v+i27tELrmUePjFXW3CDAxhVHUEv647B2R7kqpGQnyPkNEeaXkCr76THe7GGg35EWn4lDw== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/component" "0.6.11" + "@firebase/logger" "0.4.4" + "@firebase/util" "1.10.2" + tslib "^2.1.0" + +"@firebase/webchannel-wrapper@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz#a73bab8eb491d7b8b7be2f0e6c310647835afe83" + integrity sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ== + "@floating-ui/core@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.3.tgz#b6aa0827708d70971c8679a16cf680a515b8a52a" @@ -3395,6 +3785,24 @@ lodash "^4.17.20" replace-in-file "^6.1.0" +"@grpc/grpc-js@~1.9.0": + version "1.9.15" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.15.tgz#433d7ac19b1754af690ea650ab72190bd700739b" + integrity sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ== + dependencies: + "@grpc/proto-loader" "^0.7.8" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.7.8": + version "0.7.13" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.13.tgz#f6a44b2b7c9f7b609f5748c6eac2d420e37670cf" + integrity sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.5" + yargs "^17.7.2" + "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -5183,6 +5591,59 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -6261,6 +6722,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== +"@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "22.7.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.6.tgz#3ec3e2b071e136cd11093c19128405e1d1f92f33" + integrity sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw== + dependencies: + undici-types "~6.19.2" + "@types/node@^12.12.6": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -12032,7 +12500,7 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -faye-websocket@^0.11.3: +faye-websocket@0.11.4, faye-websocket@^0.11.3: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== @@ -12211,6 +12679,40 @@ find-yarn-workspace-root@^2.0.0: dependencies: micromatch "^4.0.2" +firebase@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-11.1.0.tgz#7fce26e8220f9490bf2a32469b7c5034462024c2" + integrity sha512-3OoNW3vBXmBLYJvcwbPCwfluptbDVp2zZYjrfHPVFAXfPgmyy/LWjidt+Sw2WNvRelsG0v++WN2Wor6J3OwDRg== + dependencies: + "@firebase/analytics" "0.10.10" + "@firebase/analytics-compat" "0.2.16" + "@firebase/app" "0.10.17" + "@firebase/app-check" "0.8.10" + "@firebase/app-check-compat" "0.3.17" + "@firebase/app-compat" "0.2.47" + "@firebase/app-types" "0.9.3" + "@firebase/auth" "1.8.1" + "@firebase/auth-compat" "0.5.16" + "@firebase/data-connect" "0.1.3" + "@firebase/database" "1.0.10" + "@firebase/database-compat" "2.0.1" + "@firebase/firestore" "4.7.5" + "@firebase/firestore-compat" "0.3.40" + "@firebase/functions" "0.12.0" + "@firebase/functions-compat" "0.3.17" + "@firebase/installations" "0.6.11" + "@firebase/installations-compat" "0.2.11" + "@firebase/messaging" "0.12.15" + "@firebase/messaging-compat" "0.2.15" + "@firebase/performance" "0.6.11" + "@firebase/performance-compat" "0.2.11" + "@firebase/remote-config" "0.4.11" + "@firebase/remote-config-compat" "0.2.11" + "@firebase/storage" "0.13.4" + "@firebase/storage-compat" "0.3.14" + "@firebase/util" "1.10.2" + "@firebase/vertexai" "1.0.2" + fireblocks-sdk@5.20.0: version "5.20.0" resolved "https://registry.yarnpkg.com/fireblocks-sdk/-/fireblocks-sdk-5.20.0.tgz#675e7a18d151b4f2e58be9473cd588905b042fd1" @@ -13428,7 +13930,7 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -idb@^7.0.1: +idb@7.1.1, idb@^7.0.1: version "7.1.1" resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== @@ -15934,6 +16436,11 @@ loglevelnext@^1.0.1: es6-symbol "^3.1.1" object.assign "^4.1.0" +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -18513,6 +19020,24 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== +protobufjs@^7.2.5: + version "7.4.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" + integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -21600,6 +22125,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -23313,7 +23843,7 @@ yargs@^17.0.0, yargs@^17.2.1, yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" -yargs@^17.5.1: +yargs@^17.5.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From 6a1a7b6c6f64d04998cf0de3687213a78fcda51e Mon Sep 17 00:00:00 2001 From: vvava <94397450+vvava@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:00:23 +0100 Subject: [PATCH 4/7] feat: scroll to the active account (#131) --- src/pages/Accounts/components/AccountItem.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pages/Accounts/components/AccountItem.tsx b/src/pages/Accounts/components/AccountItem.tsx index e47c3731e..dbd64ef0e 100644 --- a/src/pages/Accounts/components/AccountItem.tsx +++ b/src/pages/Accounts/components/AccountItem.tsx @@ -13,6 +13,7 @@ import { ForwardedRef, forwardRef, useCallback, + useEffect, useMemo, useRef, useState, @@ -79,6 +80,18 @@ export const AccountItem = forwardRef( const address = network ? getAddressForChain(network.chainId, account) : ''; const [cardHovered, setCardHovered] = useState(false); const itemRef = useRef(null); + const firstPageload = useRef(true); + + useEffect(() => { + if (isActive) { + const behavior = firstPageload.current ? 'instant' : 'smooth'; + itemRef?.current?.scrollIntoView({ + block: 'center', + behavior, + }); + } + firstPageload.current = false; + }, [isActive]); const toggle = useCallback( (accountId: string) => { From ade9695216e5bd66f27647835a77b7be5c00f3d6 Mon Sep 17 00:00:00 2001 From: Gergely Lovas <36536513+gergelylovas@users.noreply.github.com> Date: Fri, 7 Feb 2025 22:48:46 +0100 Subject: [PATCH 5/7] feat: add EIP-2930 support - CP-9843 (#130) --- package.json | 12 +++--- yarn.lock | 101 ++++++++++++++++++++++++++------------------------- 2 files changed, 58 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index dc71d9cf7..399211ad7 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ "sentry": "node sentryscript.js" }, "dependencies": { - "@avalabs/avalanche-module": "1.2.1", + "@avalabs/avalanche-module": "1.4.0", "@avalabs/avalanchejs": "4.1.2-alpha.3", - "@avalabs/bitcoin-module": "1.2.1", + "@avalabs/bitcoin-module": "1.4.0", "@avalabs/bridge-unified": "4.0.1", "@avalabs/core-bridge-sdk": "3.1.0-alpha.32", "@avalabs/core-chains-sdk": "3.1.0-alpha.32", @@ -37,12 +37,12 @@ "@avalabs/core-token-prices-sdk": "3.1.0-alpha.32", "@avalabs/core-utils-sdk": "3.1.0-alpha.32", "@avalabs/core-wallets-sdk": "3.1.0-alpha.32", - "@avalabs/evm-module": "1.2.1", + "@avalabs/evm-module": "1.4.0", "@avalabs/glacier-sdk": "3.1.0-alpha.32", "@avalabs/hw-app-avalanche": "0.14.1", - "@avalabs/hvm-module": "1.2.1", + "@avalabs/hvm-module": "1.4.0", "@avalabs/types": "3.1.0-alpha.32", - "@avalabs/vm-module-types": "1.2.1", + "@avalabs/vm-module-types": "1.4.0", "@blockaid/client": "0.10.0", "@coinbase/cbpay-js": "1.6.0", "@cubist-labs/cubesigner-sdk": "0.3.28", @@ -75,7 +75,7 @@ "date-fns": "2.28.0", "eth-json-rpc-middleware": "8.0.1", "eth-rpc-errors": "4.0.3", - "ethers": "6.8.1", + "ethers": "6.13.5", "events": "3.3.0", "firebase": "11.1.0", "fireblocks-sdk": "5.20.0", diff --git a/yarn.lock b/yarn.lock index bc4cfcd97..35942daad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + "@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" @@ -29,10 +34,10 @@ resolved "https://registry.yarnpkg.com/@apocentre/alias-sampling/-/alias-sampling-0.5.3.tgz#897ff181b48ad7b2bcb4ecf29400214888244f08" integrity sha512-7UDWIIF9hIeJqfKXkNIzkVandlwLf1FWTSdrb9iXvOP8oF544JRXQjCbiTmCv2c9n44n/FIWtehhBfNuAx2CZA== -"@avalabs/avalanche-module@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@avalabs/avalanche-module/-/avalanche-module-1.2.1.tgz#e2282bfb3f3c241e29bf913f24661f5cbe74eefb" - integrity sha512-B7tOqWwFoCvxXWZyqm/Euc4k4d5INMFCoBBPtmPBBC5xos94byl/xNxfW/xjwrbbzO3mDK41skozAXPEMfacjw== +"@avalabs/avalanche-module@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@avalabs/avalanche-module/-/avalanche-module-1.4.0.tgz#3c47d1e82dd379f1262713195f2adb6d56edba5d" + integrity sha512-diqLClfZ8obhy9g3ijINgkWRVVatfPW9JQ+aqlbjB4QPuR+r38/YMrhha0cTK2ZTadu9L9q/aJleTbjdPLhW6Q== dependencies: "@avalabs/avalanchejs" "4.1.2-alpha.3" "@avalabs/core-chains-sdk" "3.1.0-alpha.32" @@ -42,7 +47,7 @@ "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" "@avalabs/glacier-sdk" "3.1.0-alpha.32" "@avalabs/types" "3.1.0-alpha.32" - "@avalabs/vm-module-types" "1.2.1" + "@avalabs/vm-module-types" "1.4.0" "@metamask/rpc-errors" "6.3.0" big.js "6.2.1" bn.js "5.2.1" @@ -59,15 +64,15 @@ "@scure/base" "1.1.5" micro-eth-signer "0.7.2" -"@avalabs/bitcoin-module@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@avalabs/bitcoin-module/-/bitcoin-module-1.2.1.tgz#39e9fac9922f7ebd8d587c90cb133f49523518db" - integrity sha512-Ha2JXT2oJiUNiwutOtG7MlZZUp9nLVTYRjFAU1RFLi4EV2dj2qLmMfQqnNP4u1SmoEc6upXq8MerXnA/RrkGeA== +"@avalabs/bitcoin-module@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@avalabs/bitcoin-module/-/bitcoin-module-1.4.0.tgz#8af9508c7937220e1325e6147a89e241d3bff5fe" + integrity sha512-CsMXW/qsnHYXvUhUr6FbpBABoy7pjKonYGX5cGFskeiE7dLXgZ6o5mi+/MSrqCxpG0LUfDmJTEKIF8F+2I8grA== dependencies: "@avalabs/core-coingecko-sdk" "3.1.0-alpha.32" "@avalabs/core-utils-sdk" "3.1.0-alpha.32" "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" - "@avalabs/vm-module-types" "1.2.1" + "@avalabs/vm-module-types" "1.4.0" "@metamask/rpc-errors" "6.3.0" big.js "6.2.1" bitcoinjs-lib "5.2.0" @@ -205,18 +210,19 @@ ledger-bitcoin "0.2.3" xss "1.0.14" -"@avalabs/evm-module@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@avalabs/evm-module/-/evm-module-1.2.1.tgz#06191095644da28f92f61cbf861cb2fcd42994f3" - integrity sha512-dA6x5vS86ygc4azOsmjFQESsj7+Sevc22HeiEjrxXJIJiJWPTLkNtyqSc92IQqBCHAX+Hf1cSDqAzo8alT0P/g== +"@avalabs/evm-module@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@avalabs/evm-module/-/evm-module-1.4.0.tgz#7f9959cc3769462c929986192cbb64e67562823c" + integrity sha512-B6hpQQ0o3fLWp+NwSpLJaG5OehOkJ3cGU2UDo/Yis4ikDcxfFED97WXcs5bWpNheL3fImsIjA1ozIsbGvKVQ9A== dependencies: + "@avalabs/core-chains-sdk" "3.1.0-alpha.32" "@avalabs/core-coingecko-sdk" "3.1.0-alpha.32" "@avalabs/core-etherscan-sdk" "3.1.0-alpha.32" "@avalabs/core-utils-sdk" "3.1.0-alpha.32" "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" "@avalabs/glacier-sdk" "3.1.0-alpha.32" "@avalabs/types" "3.1.0-alpha.32" - "@avalabs/vm-module-types" "1.2.1" + "@avalabs/vm-module-types" "1.4.0" "@blockaid/client" "0.11.0" "@metamask/rpc-errors" "6.3.0" "@openzeppelin/contracts" "4.9.6" @@ -230,13 +236,13 @@ resolved "https://registry.yarnpkg.com/@avalabs/glacier-sdk/-/glacier-sdk-3.1.0-alpha.32.tgz#a03a130ed85f60dde076cf9feeb2a4c487aee8e4" integrity sha512-LTRdqpsiGIJVJxfcnNs/85/NnSneD3Jg+DZw6+F8AiJ1OcZftGu9Hz1GvWZhoz+9udHSbEXHEN5trws4vqTjkQ== -"@avalabs/hvm-module@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@avalabs/hvm-module/-/hvm-module-1.2.1.tgz#236f0c794ebbc1bc499f79731c9e26c0d037e86d" - integrity sha512-kBqXG46faGqRlM3BGqYkzLwJ4afPKu7qDuWLJHajVUX7fiGvfiy7APQga7NA5MaObE7fVfBWgq1cuV9v4+aaLQ== +"@avalabs/hvm-module@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@avalabs/hvm-module/-/hvm-module-1.4.0.tgz#e2244458bdd629f884241ee168138a2b8765e11b" + integrity sha512-ACF90VcB0fabU4D3E0Xb+9cePPRSNE+sL73+OLZDFV8Fn/KHwWga61zNLLeSHVeW0iYhHCHqo730aKvB0w9G7Q== dependencies: "@avalabs/core-utils-sdk" "3.1.0-alpha.30" - "@avalabs/vm-module-types" "1.2.1" + "@avalabs/vm-module-types" "1.4.0" "@metamask/rpc-errors" "6.3.0" "@noble/hashes" "1.5.0" hypersdk-client "0.4.16" @@ -258,10 +264,10 @@ resolved "https://registry.yarnpkg.com/@avalabs/types/-/types-3.1.0-alpha.32.tgz#bb94019e3c3121bfc66e1d5fed5a8c5c78c10fb5" integrity sha512-EwIMmTPygrMMxA8GVfBL7UNia3IycMjkPxuaRuZ8PTkW2Yr24F7GGeufBMFU69N0xTyoM8VQgMYNZ2x7stpedg== -"@avalabs/vm-module-types@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@avalabs/vm-module-types/-/vm-module-types-1.2.1.tgz#4d26e81dd228fd4d1aa9f3f3e434417fc08c531e" - integrity sha512-XR26j9oSQX/4EfdeJ9Nk/jl8QmaSXb84owAEeEjr2h5SJ/fNxHxE0l2om1TwYSHKO8zWDjAY9HliKJj25ou+0A== +"@avalabs/vm-module-types@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@avalabs/vm-module-types/-/vm-module-types-1.4.0.tgz#bf819ace354c2f2ea66212f0fd4f4970331b3038" + integrity sha512-udByYqsngI/8b4p7kLpNdMtaUJW9afMb3YNo8JKWUwvUjXaqOxzWf8b3RNmZqQywwm5kO42U18Ln1xdKXy0pyg== dependencies: "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" "@avalabs/glacier-sdk" "3.1.0-alpha.32" @@ -6717,10 +6723,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.0.tgz#1cbca7c67ba9ff80675ada496f54a919e8c4c145" integrity sha512-In+/vAdT+kkHigDSN9lUDDmzsIyKn5efDcwmGGnBtZVHnBv1oDVn6vC0ckic9FxSm+X0BGJGECpI1ZOg60E21g== -"@types/node@18.15.13": - version "18.15.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" - integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "22.7.6" @@ -12242,18 +12250,18 @@ ethers@5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" -ethers@6.8.1: - version "6.8.1" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.8.1.tgz#ee2a1a39b5f62a13678f90ccd879175391d0a2b4" - integrity sha512-iEKm6zox5h1lDn6scuRWdIdFJUCGg3+/aQWu0F4K0GVyEZiktFkqrJbRjTn1FlYEPz7RKA707D6g5Kdk6j7Ljg== +ethers@6.13.5: + version "6.13.5" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.5.tgz#8c1d6ac988ac08abc3c1d8fabbd4b8b602851ac4" + integrity sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ== dependencies: - "@adraffy/ens-normalize" "1.10.0" + "@adraffy/ens-normalize" "1.10.1" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" - "@types/node" "18.15.13" + "@types/node" "22.7.5" aes-js "4.0.0-beta.5" - tslib "2.4.0" - ws "8.5.0" + tslib "2.7.0" + ws "8.17.1" ethjs-unit@0.1.6: version "0.1.6" @@ -21874,10 +21882,10 @@ tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0: version "2.5.0" @@ -23682,10 +23690,10 @@ ws@8.13.0, ws@^8.11.0, ws@^8.13.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +ws@8.17.1, ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== ws@^3.0.0: version "3.3.3" @@ -23701,11 +23709,6 @@ ws@^7.4.6, ws@^7.5.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@~8.17.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" - integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== - xhr-request-promise@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" From 801c5490fa39a8b0033e711e13f5c710f532c2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Leszczyk?= Date: Mon, 10 Feb 2025 13:55:58 +0100 Subject: [PATCH 6/7] feat: improved c-chain fees estimation (#137) --- package.json | 24 +-- src/components/common/CustomFees.tsx | 97 +++++---- src/components/common/CustomGasSettings.tsx | 67 ++++--- src/localization/locales/en/translation.json | 2 +- src/pages/Accounts/Accounts.tsx | 5 +- src/pages/Accounts/components/AccountItem.tsx | 2 +- .../ApproveAction/BridgeTransferAsset.tsx | 2 +- .../ApproveAction/hooks/useFeeCustomizer.tsx | 2 +- .../hooks/useGetTransaction.tsx | 187 ------------------ src/utils/calculateGasAndFees.ts | 1 + yarn.lock | 121 +++++++++--- 11 files changed, 215 insertions(+), 295 deletions(-) delete mode 100644 src/pages/SignTransaction/hooks/useGetTransaction.tsx diff --git a/package.json b/package.json index 399211ad7..2844f9a32 100644 --- a/package.json +++ b/package.json @@ -24,24 +24,24 @@ }, "dependencies": { "@avalabs/avalanche-module": "1.4.0", - "@avalabs/avalanchejs": "4.1.2-alpha.3", + "@avalabs/avalanchejs": "4.2.0-alpha.1", "@avalabs/bitcoin-module": "1.4.0", "@avalabs/bridge-unified": "4.0.1", - "@avalabs/core-bridge-sdk": "3.1.0-alpha.32", - "@avalabs/core-chains-sdk": "3.1.0-alpha.32", - "@avalabs/core-coingecko-sdk": "3.1.0-alpha.32", - "@avalabs/core-covalent-sdk": "3.1.0-alpha.32", - "@avalabs/core-etherscan-sdk": "3.1.0-alpha.32", + "@avalabs/core-bridge-sdk": "3.1.0-alpha.34", + "@avalabs/core-chains-sdk": "3.1.0-alpha.34", + "@avalabs/core-coingecko-sdk": "3.1.0-alpha.34", + "@avalabs/core-covalent-sdk": "3.1.0-alpha.34", + "@avalabs/core-etherscan-sdk": "3.1.0-alpha.34", "@avalabs/core-k2-components": "4.18.0-alpha.53", - "@avalabs/core-snowtrace-sdk": "3.1.0-alpha.32", - "@avalabs/core-token-prices-sdk": "3.1.0-alpha.32", - "@avalabs/core-utils-sdk": "3.1.0-alpha.32", - "@avalabs/core-wallets-sdk": "3.1.0-alpha.32", + "@avalabs/core-snowtrace-sdk": "3.1.0-alpha.34", + "@avalabs/core-token-prices-sdk": "3.1.0-alpha.34", + "@avalabs/core-utils-sdk": "3.1.0-alpha.34", + "@avalabs/core-wallets-sdk": "3.1.0-alpha.34", "@avalabs/evm-module": "1.4.0", - "@avalabs/glacier-sdk": "3.1.0-alpha.32", + "@avalabs/glacier-sdk": "3.1.0-alpha.34", "@avalabs/hw-app-avalanche": "0.14.1", "@avalabs/hvm-module": "1.4.0", - "@avalabs/types": "3.1.0-alpha.32", + "@avalabs/types": "3.1.0-alpha.34", "@avalabs/vm-module-types": "1.4.0", "@blockaid/client": "0.10.0", "@coinbase/cbpay-js": "1.6.0", diff --git a/src/components/common/CustomFees.tsx b/src/components/common/CustomFees.tsx index eb85e308d..6019134c2 100644 --- a/src/components/common/CustomFees.tsx +++ b/src/components/common/CustomFees.tsx @@ -6,6 +6,7 @@ import { Network, NetworkVMType } from '@avalabs/core-chains-sdk'; import { formatUnits, parseUnits } from 'ethers'; import { useTranslation } from 'react-i18next'; import { TokenType } from '@avalabs/vm-module-types'; +import { TokenUnit } from '@avalabs/core-utils-sdk'; import { FeeRate, NetworkFee, @@ -19,6 +20,7 @@ import { GearIcon, IconButton, Stack, + Tooltip, Typography, styled, } from '@avalabs/core-k2-components'; @@ -29,7 +31,6 @@ import { } from '@src/components/common/approval/ApprovalSection'; import { useLiveBalance } from '@src/hooks/useLiveBalance'; import { CustomGasSettings } from './CustomGasSettings'; -import { TokenUnit } from '@avalabs/core-utils-sdk'; export interface CustomGasFeesProps { maxFeePerGas: bigint; @@ -54,9 +55,9 @@ export interface CustomGasFeesProps { } export enum GasFeeModifier { + SLOW = 'SLOW', NORMAL = 'NORMAL', FAST = 'FAST', - INSTANT = 'INSTANT', CUSTOM = 'CUSTOM', } @@ -189,7 +190,7 @@ export function CustomFees({ const [customGasLimit, setCustomGasLimit] = useState(); const gasLimit = customGasLimit || limit; const [customFee, setCustomFee] = useState( - networkFee?.low, + networkFee?.medium, ); const [newFees, setNewFees] = useState< ReturnType @@ -206,8 +207,8 @@ export function CustomFees({ const [showEditGasLimit, setShowEditGasLimit] = useState(false); const [selectedFee, setSelectedFee] = useState( networkFee?.isFixedFee - ? GasFeeModifier.NORMAL - : selectedGasFeeModifier || GasFeeModifier.NORMAL, + ? GasFeeModifier.SLOW + : selectedGasFeeModifier || GasFeeModifier.SLOW, ); useLiveBalance(POLLED_BALANCES); // Make sure we always use the latest native balance. @@ -281,11 +282,11 @@ export function CustomFees({ } setSelectedFee(modifier); switch (modifier) { - case GasFeeModifier.FAST: { + case GasFeeModifier.NORMAL: { handleGasChange(networkFee.medium, modifier); break; } - case GasFeeModifier.INSTANT: { + case GasFeeModifier.FAST: { handleGasChange(networkFee.high, modifier); break; } @@ -295,7 +296,7 @@ export function CustomFees({ } break; default: - handleGasChange(networkFee.low, GasFeeModifier.NORMAL); + handleGasChange(networkFee.low, GasFeeModifier.SLOW); } }, [handleGasChange, networkFee, customFee], @@ -320,7 +321,7 @@ export function CustomFees({ } if (networkFee.isFixedFee) { - updateGasFee(GasFeeModifier.NORMAL); + updateGasFee(GasFeeModifier.SLOW); } else { updateGasFee(selectedGasFeeModifier); } @@ -328,19 +329,30 @@ export function CustomFees({ const feeAmount = useMemo(() => { if (!network?.networkToken) { - return '-'; + return { + rounded: '-', + precise: '', + }; } if (typeof estimatedFee === 'bigint') { - return new TokenUnit( + const unit = new TokenUnit( estimatedFee, network?.networkToken.decimals, network?.networkToken.symbol, - ).toString(); + ); + + return { + rounded: unit.toDisplay(), + precise: unit.toString(), + }; } - return newFees.fee; - }, [network?.networkToken, estimatedFee, newFees.fee]); + return { + rounded: newFees.feeUnit.toDisplay(), + precise: newFees.feeUnit.toString(), + }; + }, [network?.networkToken, estimatedFee, newFees.feeUnit]); if (!networkFee) { return null; @@ -401,20 +413,20 @@ export function CustomFees({ }} > { - handleModifierClick(GasFeeModifier.NORMAL); + handleModifierClick(GasFeeModifier.SLOW); }} > - {t('Normal')} + {t('Slow')} {formatGasPrice( @@ -426,22 +438,22 @@ export function CustomFees({ {!networkFee.isFixedFee && ( <> { - handleModifierClick(GasFeeModifier.FAST); + handleModifierClick(GasFeeModifier.NORMAL); }} > - {t('Fast')} + {t('Normal')} {formatGasPrice( @@ -451,22 +463,22 @@ export function CustomFees({ { - handleModifierClick(GasFeeModifier.INSTANT); + handleModifierClick(GasFeeModifier.FAST); }} > - {t('Instant')} + {t('Fast')} {formatGasPrice( @@ -515,7 +527,10 @@ export function CustomFees({ }} onBlur={(e) => { if (e.target.value === '') { - handleGasChange(networkFee.low, GasFeeModifier.CUSTOM); + handleGasChange( + networkFee.medium, + GasFeeModifier.CUSTOM, + ); } }} /> @@ -541,16 +556,24 @@ export function CustomFees({ > {t('Fee Amount')} - - {feeAmount} {network?.networkToken.symbol} - + + + + ~ + + + + {feeAmount.rounded} {network?.networkToken.symbol} + + + ( ); -const FeeAmount = ({ value, fiatValue, tokenSymbol }) => ( - - - - - {value} - - - {tokenSymbol} - +const FeeAmount = ({ decimals, value, fiatValue, tokenSymbol }) => { + const unit = new TokenUnit(value, decimals, tokenSymbol); + + return ( + + + + + ~ + + + + {unit.toDisplay()} + + + + {tokenSymbol} + + + {fiatValue && ( + + {fiatValue} + + )} - {fiatValue && ( - - {fiatValue} - - )} - -); + ); +}; export function CustomGasSettings({ feeDisplayDecimals, @@ -239,9 +249,7 @@ export function CustomGasSettings({ autoFocus fullWidth type={'number'} - value={ - Number(customMaxPriorityFeePerGas) / 10 ** feeDisplayDecimals - } + value={formatUnits(customMaxPriorityFeePerGas, feeDisplayDecimals)} onChange={(evt) => { setCustomMaxPriorityFeePerGas( parseUnits(evt.currentTarget.value || '0', feeDisplayDecimals), @@ -327,7 +335,8 @@ export function CustomGasSettings({ - {activeAccountBalance?.sum + {typeof activeAccountBalance?.sum === 'number' ? currencyFormatter(activeAccountBalance.sum) - : '...'} + : ''} diff --git a/src/pages/Accounts/components/AccountItem.tsx b/src/pages/Accounts/components/AccountItem.tsx index dbd64ef0e..65e013894 100644 --- a/src/pages/Accounts/components/AccountItem.tsx +++ b/src/pages/Accounts/components/AccountItem.tsx @@ -86,7 +86,7 @@ export const AccountItem = forwardRef( if (isActive) { const behavior = firstPageload.current ? 'instant' : 'smooth'; itemRef?.current?.scrollIntoView({ - block: 'center', + block: 'nearest', behavior, }); } diff --git a/src/pages/ApproveAction/BridgeTransferAsset.tsx b/src/pages/ApproveAction/BridgeTransferAsset.tsx index 658d8b205..bea46df2c 100644 --- a/src/pages/ApproveAction/BridgeTransferAsset.tsx +++ b/src/pages/ApproveAction/BridgeTransferAsset.tsx @@ -38,7 +38,7 @@ export function BridgeTransferAsset({ const { displayData } = action; const [gasSettings, setGasSettings] = useState({}); const [selectedGasFee, setSelectedGasFee] = useState( - GasFeeModifier.INSTANT, + GasFeeModifier.FAST, ); const tokenPrice = displayData?.token?.priceInCurrency; diff --git a/src/pages/ApproveAction/hooks/useFeeCustomizer.tsx b/src/pages/ApproveAction/hooks/useFeeCustomizer.tsx index efe46f6be..28bd05a60 100644 --- a/src/pages/ApproveAction/hooks/useFeeCustomizer.tsx +++ b/src/pages/ApproveAction/hooks/useFeeCustomizer.tsx @@ -83,7 +83,7 @@ export function useFeeCustomizer({ const [isCalculatingFee, setIsCalculatingFee] = useState(false); const [gasFeeModifier, setGasFeeModifier] = useState( - GasFeeModifier.NORMAL, + GasFeeModifier.SLOW, ); const isFeeSelectorEnabled = Boolean(action?.displayData.networkFeeSelector); diff --git a/src/pages/SignTransaction/hooks/useGetTransaction.tsx b/src/pages/SignTransaction/hooks/useGetTransaction.tsx deleted file mode 100644 index 0cc7868a4..000000000 --- a/src/pages/SignTransaction/hooks/useGetTransaction.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { useCallback, useMemo, useEffect, useState } from 'react'; -import { calculateGasAndFees } from '@src/utils/calculateGasAndFees'; -import { GasFeeModifier } from '@src/components/common/CustomFees'; - -import { useNetworkFeeContext } from '@src/contexts/NetworkFeeProvider'; -import { useNativeTokenPrice } from '@src/hooks/useTokenPrice'; -import { useNetworkContext } from '@src/contexts/NetworkProvider'; -import { NetworkFee } from '@src/background/services/networkFee/models'; -import { Network } from '@avalabs/core-chains-sdk'; -import { useFeatureFlagContext } from '@src/contexts/FeatureFlagsProvider'; -import { useDialog } from '@src/contexts/DialogContextProvider'; -import { FeatureGates } from '@src/background/services/featureFlags/models'; -import { useApproveAction } from '@src/hooks/useApproveAction'; -import { Transaction } from '@src/background/services/wallet/handlers/eth_sendTransaction/models'; -import { ActionStatus } from '@src/background/services/actions/models'; -import { getNetworkCaipId } from '@src/utils/caipConversion'; - -export function useGetTransaction(requestId: string) { - // Target network of the transaction defined by the chainId param. May differ from the active one. - const [network, setNetwork] = useState(undefined); - const [networkFee, setNetworkFee] = useState(null); - const { updateAction, action } = useApproveAction(requestId); - const tokenPrice = useNativeTokenPrice(network); - const { getNetworkFee } = useNetworkFeeContext(); - const { getNetwork, network: activeNetwork } = useNetworkContext(); - const { featureFlags } = useFeatureFlagContext(); - const [customGas, setCustomGas] = useState<{ - gasLimit?: number; - maxFeePerGas: bigint; - maxPriorityFeePerGas?: bigint; - } | null>(null); - const [showRawTransactionData, setShowRawTransactionData] = useState(false); - const [selectedGasFee, setSelectedGasFee] = useState( - GasFeeModifier.NORMAL, - ); - const { showDialog, clearDialog } = useDialog(); - const [hasTransactionError, setHasTransactionError] = useState(false); - - useEffect(() => { - if (!customGas || !action) { - return; - } - - const feeDisplayValues = calculateGasAndFees({ - maxFeePerGas: customGas.maxFeePerGas, - gasLimit: - customGas?.gasLimit ?? action.displayData.displayValues.gas.gasLimit, - tokenPrice, - tokenDecimals: network?.networkToken.decimals, - }); - - const displayValues = { - ...action.displayData.displayValues, - gas: { - gasLimit: - feeDisplayValues.gasLimit || - action.displayData.displayValues.gas.gasLimit, - maxFeePerGas: - feeDisplayValues.maxFeePerGas || - action.displayData.displayValues.gas.maxFeePerGas, - maxPriorityFeePerGas: - customGas.maxPriorityFeePerGas || - action.displayData.displayValues.gas.maxPriorityFeePerGas, - }, - }; - const updatedDisplayData: Transaction = { - ...action.displayData, - displayValues, - txParams: { - ...action.displayData.txParams, - gas: feeDisplayValues.gasLimit, - maxFeePerGas: feeDisplayValues.maxFeePerGas - ? `0x${feeDisplayValues.maxFeePerGas.toString(16)}` - : action.displayData.txParams.maxFeePerGas, - maxPriorityFeePerGas: customGas.maxPriorityFeePerGas - ? `0x${customGas.maxPriorityFeePerGas.toString(16)}` - : action.displayData.txParams.maxPriorityFeePerGas, - }, - }; - - // Only keep updating if the action wasn't already submitted/cancelled - if (action.status === ActionStatus.PENDING) { - updateAction({ - id: action.actionId, - status: ActionStatus.PENDING, - displayData: updatedDisplayData, - }); - } - // keeping `action` out of here to prevent infinite loops - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - customGas, - network?.networkToken.decimals, - tokenPrice, - updateAction, - action?.status, - ]); - - const setCustomFee = useCallback( - (values: { - customGasLimit?: number; - maxFeePerGas: bigint; - maxPriorityFeePerGas?: bigint; - feeType: GasFeeModifier; - }) => { - setCustomGas((currentGas) => ({ - gasLimit: values.customGasLimit ?? currentGas?.gasLimit, - maxFeePerGas: values.maxFeePerGas ?? currentGas?.maxFeePerGas, - maxPriorityFeePerGas: - values.maxPriorityFeePerGas ?? currentGas?.maxPriorityFeePerGas, - })); - setSelectedGasFee(values.feeType); - }, - [], - ); - - useEffect(() => { - const updateNetworkAndFees = async () => { - if (network?.chainId) { - setNetworkFee(await getNetworkFee(getNetworkCaipId(network))); - } - }; - - updateNetworkAndFees(); - }, [getNetworkFee, network]); - - useEffect(() => { - const chainId = parseInt(action?.displayData?.chainId ?? ''); - - if (!featureFlags[FeatureGates.SENDTRANSACTION_CHAIN_ID_SUPPORT]) { - if (action && activeNetwork && chainId !== activeNetwork?.chainId) { - setHasTransactionError(true); - } else { - setNetwork(activeNetwork); - } - } else { - setNetwork(getNetwork(chainId)); - } - }, [ - getNetwork, - showDialog, - clearDialog, - featureFlags, - activeNetwork, - setHasTransactionError, - action, - ]); - - return useMemo(() => { - const feeDisplayValues = - networkFee && - action?.displayData.displayValues?.gas.gasLimit && - calculateGasAndFees({ - maxFeePerGas: customGas?.maxFeePerGas ?? networkFee.low.maxFeePerGas, - gasLimit: - customGas?.gasLimit ?? action?.displayData.displayValues.gas.gasLimit, - tokenPrice, - tokenDecimals: network?.networkToken.decimals, - }); - - return { - transaction: action, - ...feeDisplayValues, - updateTransaction: updateAction, - setCustomFee, - showRawTransactionData, - setShowRawTransactionData, - selectedGasFee, - network, - networkFee, - hasTransactionError, - setHasTransactionError, - }; - }, [ - networkFee, - action, - customGas?.maxFeePerGas, - customGas?.gasLimit, - tokenPrice, - network, - updateAction, - setCustomFee, - showRawTransactionData, - selectedGasFee, - hasTransactionError, - ]); -} diff --git a/src/utils/calculateGasAndFees.ts b/src/utils/calculateGasAndFees.ts index af311e0f5..f37130d3c 100644 --- a/src/utils/calculateGasAndFees.ts +++ b/src/utils/calculateGasAndFees.ts @@ -45,6 +45,7 @@ export function calculateGasAndFees({ return { maxFeePerGas: maxFeePerGas, gasLimit: gasLimit || 0, + feeUnit: fee, fee: fee.toDisplay(), bnFee, feeUSD: price diff --git a/yarn.lock b/yarn.lock index 35942daad..08fdb345d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -64,6 +64,17 @@ "@scure/base" "1.1.5" micro-eth-signer "0.7.2" +"@avalabs/avalanchejs@4.2.0-alpha.1": + version "4.2.0-alpha.1" + resolved "https://registry.yarnpkg.com/@avalabs/avalanchejs/-/avalanchejs-4.2.0-alpha.1.tgz#a8cdd7b1c182f69548a91c83d7f76a63d9a3c98a" + integrity sha512-Lwg466Ii+5fS1QQ6KnjxjGfo8pSV0pvY1ITYPW+PRlFJcHT15ZwC4HKq4c5PaIQ1hV/g6y/J2jKCrOck8gIXRQ== + dependencies: + "@noble/curves" "1.3.0" + "@noble/hashes" "1.3.3" + "@noble/secp256k1" "2.0.0" + "@scure/base" "1.1.5" + micro-eth-signer "0.7.2" + "@avalabs/bitcoin-module@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@avalabs/bitcoin-module/-/bitcoin-module-1.4.0.tgz#8af9508c7937220e1325e6147a89e241d3bff5fe" @@ -93,14 +104,14 @@ viem "2.11.1" zod "3.23.8" -"@avalabs/core-bridge-sdk@3.1.0-alpha.32": - version "3.1.0-alpha.32" - resolved "https://registry.yarnpkg.com/@avalabs/core-bridge-sdk/-/core-bridge-sdk-3.1.0-alpha.32.tgz#e2252e6ffe8525a5a66eee4ffaa1e5a4662502fc" - integrity sha512-6VP1nOuNsg5ZdrVSYTDbB1/3seWSfn2rNSBJ0xLEmxgWEozfppdSqDnuIQ382zxy6eu3/74rrndNDH+zr+3UMw== +"@avalabs/core-bridge-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-bridge-sdk/-/core-bridge-sdk-3.1.0-alpha.34.tgz#3c93f3ce155ce45ad11787c71e93f12342ebdfd9" + integrity sha512-9z2LUT96/q5TPWMwA67NCycZFwOCONSK4BEStCUx49rpxU7SNZu8AeNgqE4Vuw0SuIbx2MHF05zd+MjDH7oT4w== dependencies: - "@avalabs/core-coingecko-sdk" "3.1.0-alpha.32" - "@avalabs/core-utils-sdk" "3.1.0-alpha.32" - "@avalabs/core-wallets-sdk" "3.1.0-alpha.32" + "@avalabs/core-coingecko-sdk" "3.1.0-alpha.34" + "@avalabs/core-utils-sdk" "3.1.0-alpha.34" + "@avalabs/core-wallets-sdk" "3.1.0-alpha.34" "@avalabs/core-chains-sdk@3.1.0-alpha.32": version "3.1.0-alpha.32" @@ -109,6 +120,13 @@ dependencies: "@avalabs/core-utils-sdk" "3.1.0-alpha.32" +"@avalabs/core-chains-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-chains-sdk/-/core-chains-sdk-3.1.0-alpha.34.tgz#f3d7193722b6b0ff97d5e6d1afe36fcfb3863580" + integrity sha512-fyeT60FAOZEaAuYVoEUIvrMwKEpEQsGtbZQOcgZKbYqessOtaXVtKG7LdfhLq/tbEap6N73a7C9cqpPVCXvYWg== + dependencies: + "@avalabs/core-utils-sdk" "3.1.0-alpha.34" + "@avalabs/core-coingecko-sdk@3.1.0-alpha.32": version "3.1.0-alpha.32" resolved "https://registry.yarnpkg.com/@avalabs/core-coingecko-sdk/-/core-coingecko-sdk-3.1.0-alpha.32.tgz#9e8f72adb85fc953d328136efb84f5575590f7de" @@ -116,12 +134,19 @@ dependencies: "@avalabs/core-utils-sdk" "3.1.0-alpha.32" -"@avalabs/core-covalent-sdk@3.1.0-alpha.32": - version "3.1.0-alpha.32" - resolved "https://registry.yarnpkg.com/@avalabs/core-covalent-sdk/-/core-covalent-sdk-3.1.0-alpha.32.tgz#83c493d29163ede1fc982416e07baf5b81edee6f" - integrity sha512-1o8pj0azAz3qeTUUDQg+sFLZa8AyMQ3bPrt72hYkCQ//NVq5Qo2IOo7yBk87NgaQQoF09mkdfB2LbJUD+cqIbA== +"@avalabs/core-coingecko-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-coingecko-sdk/-/core-coingecko-sdk-3.1.0-alpha.34.tgz#4f18f78e84dc52a5e21015124b5d81e7d4e1dbf2" + integrity sha512-M4EfPdtVsZO6iAkIEOswaCiG+8h9rk4xI/H+QYC6PrDV8AdXlYHxUZIHhWJnzqNEkn8NYhhW2hF72aCqj0LhJw== dependencies: - "@avalabs/core-utils-sdk" "3.1.0-alpha.32" + "@avalabs/core-utils-sdk" "3.1.0-alpha.34" + +"@avalabs/core-covalent-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-covalent-sdk/-/core-covalent-sdk-3.1.0-alpha.34.tgz#c0a95725984a422df77e2b5070393b67501df429" + integrity sha512-+LYIEG9kVnm7OGsKNec+Sq8e2fTnIQz2WkeporWPGRs79VawIl4bA/1U42BK8ABvlNRtyr0GEL7aRHikx9d9tA== + dependencies: + "@avalabs/core-utils-sdk" "3.1.0-alpha.34" "@avalabs/core-etherscan-sdk@3.1.0-alpha.32": version "3.1.0-alpha.32" @@ -130,6 +155,13 @@ dependencies: "@avalabs/core-utils-sdk" "3.1.0-alpha.32" +"@avalabs/core-etherscan-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-etherscan-sdk/-/core-etherscan-sdk-3.1.0-alpha.34.tgz#d1fd7a8ec00ce7af9c8621a293d75bf51796417d" + integrity sha512-M4+stSq8ccon86xJwvWWC5+SYaEDrele9yoJob1UaezLx4YfrrX/DLOsEKGURl2utI9RnLrmZCmbREXAKzd4Ww== + dependencies: + "@avalabs/core-utils-sdk" "3.1.0-alpha.34" + "@avalabs/core-k2-components@4.18.0-alpha.53": version "4.18.0-alpha.53" resolved "https://registry.yarnpkg.com/@avalabs/core-k2-components/-/core-k2-components-4.18.0-alpha.53.tgz#a7a06a613b7a33706adb0817a06462f73d4324a7" @@ -153,20 +185,20 @@ react-hotkeys-hook "4.4.3" uuid "9.0.1" -"@avalabs/core-snowtrace-sdk@3.1.0-alpha.32": - version "3.1.0-alpha.32" - resolved "https://registry.yarnpkg.com/@avalabs/core-snowtrace-sdk/-/core-snowtrace-sdk-3.1.0-alpha.32.tgz#80508406c76964fb827bf81d426ae1ffb6476858" - integrity sha512-oLiBpOe4XJFqUYbpbTavzsKgyf118wlhFfZMQaRtZgovDE/639fbI8C0ABHkC/xt6fT3sUgTsNiWcrRwdgo6+w== +"@avalabs/core-snowtrace-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-snowtrace-sdk/-/core-snowtrace-sdk-3.1.0-alpha.34.tgz#eb748199a3587ffc4d186d2f6bb4738ec62ea340" + integrity sha512-d4Ts9VQ7iQDBZyA7so+rMH0Oiv5/O4FMdxdVTUu878G+AXfL+dyLtpxFOMDvmZTl4OUlgw9SDCJKwryKm74FpA== dependencies: - "@avalabs/core-utils-sdk" "3.1.0-alpha.32" + "@avalabs/core-utils-sdk" "3.1.0-alpha.34" -"@avalabs/core-token-prices-sdk@3.1.0-alpha.32": - version "3.1.0-alpha.32" - resolved "https://registry.yarnpkg.com/@avalabs/core-token-prices-sdk/-/core-token-prices-sdk-3.1.0-alpha.32.tgz#a8dd1aa6810ab0dae86cedf297e319c6ee7e86c6" - integrity sha512-uSLj1C0i9zhekTE6ivzqg4ZHBglPZJ6B21a+qyzheMK6x9YFcjJ5XrZxN+X8EYVkrlHP8OJWU820w34GsNqZFQ== +"@avalabs/core-token-prices-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-token-prices-sdk/-/core-token-prices-sdk-3.1.0-alpha.34.tgz#1c27113d89e627f2469d791e60f2f02184a06d98" + integrity sha512-ctIgnONpmV7LIY9kIT2FTswWvRvZFy3Gd6aID9w0cfOMcrF5IlfK4zTDuC7V1wR02PCEALlRdItIy8MPlIx4lQ== dependencies: - "@avalabs/core-coingecko-sdk" "3.1.0-alpha.32" - "@avalabs/core-utils-sdk" "3.1.0-alpha.32" + "@avalabs/core-coingecko-sdk" "3.1.0-alpha.34" + "@avalabs/core-utils-sdk" "3.1.0-alpha.34" "@avalabs/core-utils-sdk@3.1.0-alpha.30": version "3.1.0-alpha.30" @@ -186,6 +218,15 @@ "@hpke/core" "1.2.5" is-ipfs "6.0.2" +"@avalabs/core-utils-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-utils-sdk/-/core-utils-sdk-3.1.0-alpha.34.tgz#e3deb829370f96b83d84b6bd1e414d8f823c7db5" + integrity sha512-r3zs9NVTA/qExAGzS4ZqXB9N4b2zNz8tFAxfzLqXv0SJiyUKUBhBc9HLOn5FB4F4/BdTKOEmjTouxEhNkL/BEw== + dependencies: + "@avalabs/avalanchejs" "4.2.0-alpha.1" + "@hpke/core" "1.2.5" + is-ipfs "6.0.2" + "@avalabs/core-wallets-sdk@3.1.0-alpha.32": version "3.1.0-alpha.32" resolved "https://registry.yarnpkg.com/@avalabs/core-wallets-sdk/-/core-wallets-sdk-3.1.0-alpha.32.tgz#a4bed57479380bc46528bdabf251cd70ca2e10a8" @@ -210,6 +251,30 @@ ledger-bitcoin "0.2.3" xss "1.0.14" +"@avalabs/core-wallets-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/core-wallets-sdk/-/core-wallets-sdk-3.1.0-alpha.34.tgz#07b3b639f8eba37e8fe560aecf9ca05d8a878c30" + integrity sha512-v1TIunj8gJnyqvzOYdtJFIN64QgIUJ/6ZLWpdJxbHzrWlGBPzDX291/MQtFOuDfbUNVmRnmYmuiH0JN28JgHgA== + dependencies: + "@avalabs/avalanchejs" "4.2.0-alpha.1" + "@avalabs/core-chains-sdk" "3.1.0-alpha.34" + "@avalabs/glacier-sdk" "3.1.0-alpha.34" + "@avalabs/hw-app-avalanche" "0.14.1" + "@ledgerhq/hw-app-btc" "10.2.4" + "@ledgerhq/hw-app-eth" "6.36.1" + "@ledgerhq/hw-transport" "6.30.6" + "@metamask/eth-sig-util" "7.0.2" + "@openzeppelin/contracts" "4.9.6" + bip32 "2.0.6" + bip32-path "0.4.2" + bip39 "3.1.0" + bitcoinjs-lib "5.2.0" + coinselect "3.1.13" + create-hash "1.2.0" + hdkey "2.0.1" + ledger-bitcoin "0.2.3" + xss "1.0.14" + "@avalabs/evm-module@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@avalabs/evm-module/-/evm-module-1.4.0.tgz#7f9959cc3769462c929986192cbb64e67562823c" @@ -236,6 +301,11 @@ resolved "https://registry.yarnpkg.com/@avalabs/glacier-sdk/-/glacier-sdk-3.1.0-alpha.32.tgz#a03a130ed85f60dde076cf9feeb2a4c487aee8e4" integrity sha512-LTRdqpsiGIJVJxfcnNs/85/NnSneD3Jg+DZw6+F8AiJ1OcZftGu9Hz1GvWZhoz+9udHSbEXHEN5trws4vqTjkQ== +"@avalabs/glacier-sdk@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/glacier-sdk/-/glacier-sdk-3.1.0-alpha.34.tgz#bc2b20bba5b1d5d6d970e4ed9989da37a7d3247a" + integrity sha512-LtMvlWuLwhlKcc82bUFQgXeLjP2QOu3zh7UAn1AgkEOM3+XbX5hZ+I8gP0RxOHk3mzAsJOGIrpEXRIznYBvZIw== + "@avalabs/hvm-module@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@avalabs/hvm-module/-/hvm-module-1.4.0.tgz#e2244458bdd629f884241ee168138a2b8765e11b" @@ -264,6 +334,11 @@ resolved "https://registry.yarnpkg.com/@avalabs/types/-/types-3.1.0-alpha.32.tgz#bb94019e3c3121bfc66e1d5fed5a8c5c78c10fb5" integrity sha512-EwIMmTPygrMMxA8GVfBL7UNia3IycMjkPxuaRuZ8PTkW2Yr24F7GGeufBMFU69N0xTyoM8VQgMYNZ2x7stpedg== +"@avalabs/types@3.1.0-alpha.34": + version "3.1.0-alpha.34" + resolved "https://registry.yarnpkg.com/@avalabs/types/-/types-3.1.0-alpha.34.tgz#f560f7e512e4a11f0a94ad2718aa7ce57367200e" + integrity sha512-v2/PAO+3QMk+xoubHzE810ZbZsIGXk+05eTXZvrNQy5KQjoPS02h11/EOSMGhpuAPg3jNCMLKQrIVkmKO/NFIw== + "@avalabs/vm-module-types@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@avalabs/vm-module-types/-/vm-module-types-1.4.0.tgz#bf819ace354c2f2ea66212f0fd4f4970331b3038" From 4f3e4dae00464bd178df505545b21523624ca048 Mon Sep 17 00:00:00 2001 From: Ryan W Date: Tue, 11 Feb 2025 10:38:38 -0500 Subject: [PATCH 7/7] fix: remove more files to fix ci automation (#144) --- .github/workflows/e2e_testing.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/e2e_testing.yaml b/.github/workflows/e2e_testing.yaml index b3d608989..b56578e0e 100644 --- a/.github/workflows/e2e_testing.yaml +++ b/.github/workflows/e2e_testing.yaml @@ -59,6 +59,9 @@ jobs: rm -fv ./dist/images/core-ext-hero-hq.webm rm -fv ./dist/images/onboarding-background.svg rm -fv ./dist/images/onboarding-background.png + rm -fv ./dist/images/keystone/keystone_onboarding_step_1.png + rm -fv ./dist/images/keystone/keystone_onboarding_step_2.png + rm -fv ./dist/images/keystone/keystone_onboarding_step_3.png - name: Add extension key to manifest file run: | echo $(cat ./dist/manifest.json | jq '.key = "${{ secrets.EXTENSION_PUBLIC_KEY }}"') > ./dist/manifest.json