diff --git a/.eslintignore b/.eslintignore index 8ce1b0402c..1f57e4e553 100644 --- a/.eslintignore +++ b/.eslintignore @@ -15,4 +15,5 @@ LICENSE dist build vite.config.ts -public/lottie/player.js \ No newline at end of file +public/lottie/player.js +CHANGELOG.md \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 0a2c0477a4..fa1f6e75e7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -32,6 +32,7 @@ // NOTE: These rules are being reviewed and comments justifying their deactivation will be // added. "react/require-default-props": "off", + "jsx-a11y/control-has-associated-label": "off", "react/no-access-state-in-setstate": "off", "react/destructuring-assignment": "off", "react/function-component-definition": "off", diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..597f5068b6 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.6.1 diff --git a/.prettierignore b/.prettierignore index bb33007fd9..958e230439 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,4 +7,5 @@ **/tsconfig.json dist src/img/**/* -public/lottie/player.js \ No newline at end of file +public/lottie/player.js +CHANGELOG.md \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json index bfa6c96ee7..ab66cb8ebd 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -3,5 +3,6 @@ "trailingComma": "es5", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "endOfLine": "auto" } diff --git a/package.json b/package.json index ee973efc6e..e903715dac 100644 --- a/package.json +++ b/package.json @@ -21,24 +21,24 @@ "visualizer": "vite-bundle-visualizer" }, "dependencies": { - "@dotlottie/player-component": "^2.7.0", + "@dotlottie/player-component": "^2.7.2", "@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/free-brands-svg-icons": "^6.4.2", "@fortawesome/free-regular-svg-icons": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@ledgerhq/hw-transport-webhid": "^6.27.19", - "@polkadot-cloud/assets": "^0.1.32", - "@polkadot-cloud/core": "^1.0.30", - "@polkadot-cloud/react": "^0.1.102", - "@polkadot-cloud/utils": "^0.0.23", + "@ledgerhq/hw-transport-webhid": "^6.27.20", + "@polkadot-cloud/assets": "^0.1.34", + "@polkadot-cloud/core": "^1.0.46", + "@polkadot-cloud/react": "^0.1.129", + "@polkadot-cloud/utils": "^0.0.25", "@polkadot/api": "^10.10.1", "@polkadot/keyring": "^12.1.1", "@polkadot/rpc-provider": "^10.9.1", "@polkadot/util": "^12.4.2", "@polkadot/util-crypto": "12.5.1", "@polkawatch/ddp-client": "^2.0.8", - "@substrate/connect": "^0.7.34", + "@substrate/connect": "^0.7.35", "@zondax/ledger-substrate": "^0.41.3", "bignumber.js": "^9.1.2", "bn.js": "^5.2.1", @@ -47,33 +47,33 @@ "chroma-js": "^2.4.2", "date-fns": "^2.29.3", "framer-motion": "^10.16.3", - "i18next": "^23.6.0", - "i18next-browser-languagedetector": "^7.1.0", + "i18next": "^23.7.6", + "i18next-browser-languagedetector": "^7.2.0", "lodash.throttle": "^4.1.1", "qrcode-generator": "1.4.4", - "rc-slider": "^10.3.1", + "rc-slider": "^10.4.0", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.11", "react-helmet": "^6.1.0", - "react-i18next": "^13.3.1", + "react-i18next": "^13.4.1", "react-qr-reader": "^2.2.1", - "react-router-dom": "^6.17.0", + "react-router-dom": "^6.18.0", "react-scroll": "^1.9.0", "styled-components": "^6.1.0" }, "devDependencies": { "@ledgerhq/logs": "^6.10.1", - "@types/chroma-js": "^2.4.0", - "@types/lodash.throttle": "^4.1.7", - "@types/react": "^18.2.33", - "@types/react-dom": "^18.2.14", - "@types/react-helmet": "^6.1.8", - "@types/react-qr-reader": "^2.1.6", - "@types/react-scroll": "^1.8.9", - "@typescript-eslint/eslint-plugin": "^6.9.1", - "@typescript-eslint/parser": "^6.9.1", + "@types/chroma-js": "^2.4.3", + "@types/lodash.throttle": "^4.1.9", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@types/react-helmet": "^6.1.9", + "@types/react-qr-reader": "^2.1.7", + "@types/react-scroll": "^1.8.10", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", "@vitejs/plugin-react-swc": "^3.4.0", "eslint": "^8.52.0", "eslint-config-airbnb": "^19.0.4", @@ -81,19 +81,19 @@ "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.0", - "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-prefer-arrow-functions": "^3.2.4", "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-unused-imports": "^3.0.0", "gh-pages": "^6.0.0", - "prettier": "^3.0.3", - "prettier-plugin-organize-imports": "^3.2.3", + "prettier": "^3.1.0", + "prettier-plugin-organize-imports": "^3.2.4", "sass": "^1.69.5", "typescript": "^5.2.2", "vite": "^4.4.11", - "vite-bundle-visualizer": "^0.10.0", + "vite-bundle-visualizer": "^0.11.0", "vite-plugin-checker": "^0.6.2", "vite-plugin-eslint": "^1.8.1", "vite-plugin-svgr": "^4.1.0", diff --git a/src/Providers.tsx b/src/Providers.tsx index 275ee99b65..5c42a8dbfa 100644 --- a/src/Providers.tsx +++ b/src/Providers.tsx @@ -11,8 +11,8 @@ import { import { ExtrinsicsProvider } from 'contexts/Extrinsics'; import { FastUnstakeProvider } from 'contexts/FastUnstake'; import { FiltersProvider } from 'contexts/Filters'; -import { LedgerHardwareProvider } from 'contexts/Hardware/Ledger'; -import { VaultHardwareProvider } from 'contexts/Hardware/Vault'; +import { LedgerHardwareProvider } from 'contexts/Hardware/Ledger/LedgerHardware'; +import { VaultAccountsProvider } from 'contexts/Hardware/Vault/VaultAccounts'; import { HelpProvider } from 'contexts/Help'; import { IdentitiesProvider } from 'contexts/Identities'; import { MenuProvider } from 'contexts/Menu'; @@ -50,6 +50,8 @@ import { DappName } from 'consts'; import { ImportedAccountsProvider } from 'contexts/Connect/ImportedAccounts'; import { PoolPerformanceProvider } from 'contexts/Pools/PoolPerformance'; import { registerSaEvent } from 'Utils'; +import { LedgerAccountsProvider } from 'contexts/Hardware/Ledger/LedgerAccounts'; +import { ExternalAccountsProvider } from 'contexts/Connect/ExternalAccounts'; // Embed providers from hook. export const Providers = () => { @@ -65,7 +67,7 @@ export const Providers = () => { FiltersProvider, NotificationsProvider, PluginsProvider, - VaultHardwareProvider, + VaultAccountsProvider, LedgerHardwareProvider, ExtensionsProvider, [ @@ -84,14 +86,16 @@ export const Providers = () => { }, }, ], + LedgerAccountsProvider, + ExternalAccountsProvider, OtherAccountsProvider, ImportedAccountsProvider, - HelpProvider, + ProxiesProvider, NetworkMetricsProvider, + HelpProvider, SubscanProvider, PolkawatchProvider, IdentitiesProvider, - ProxiesProvider, BalancesProvider, BondedProvider, StakingProvider, diff --git a/src/Router.tsx b/src/Router.tsx index b37103b38f..e0986470b9 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -36,9 +36,11 @@ import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { SideMenuMaximisedWidth } from 'consts'; +import { useTheme } from 'styled-components'; export const RouterInner = () => { const { t } = useTranslation(); + const { mode } = useTheme(); const { network } = useNetwork(); const { pathname, search } = useLocation(); const { accounts } = useImportedAccounts(); @@ -75,6 +77,15 @@ export const RouterInner = () => { }); }, []); + // Update body background to `--background-default` upon theme change. + useEffect(() => { + const elem = document.querySelector('.core-entry'); + if (elem) { + document.getElementsByTagName('body')[0].style.backgroundColor = + getComputedStyle(elem).getPropertyValue('--background-default'); + } + }, [mode]); + // Open default account modal if url var present and accounts initialised. useEffect(() => { if (accountsInitialised) { diff --git a/src/canvas/ManageNominations/index.tsx b/src/canvas/ManageNominations/index.tsx index 9a04d337e3..8e8ca9aec0 100644 --- a/src/canvas/ManageNominations/index.tsx +++ b/src/canvas/ManageNominations/index.tsx @@ -25,6 +25,7 @@ import type { NominationSelection, NominationSelectionWithResetCounter, } from 'library/GenerateNominations/types'; +import { useBondedPools } from 'contexts/Pools/BondedPools'; import { RevertPrompt } from './Prompts/RevertPrompt'; import { CanvasSubmitTxFooter, ManageNominationsWrapper } from './Wrappers'; @@ -42,6 +43,7 @@ export const ManageNominations = () => { const { addNotification } = useNotifications(); const { selectedActivePool } = useActivePools(); const { openPromptWith, closePrompt } = usePrompt(); + const { updatePoolNominations } = useBondedPools(); const controller = getBondedAccount(activeAccount); const { maxNominations } = consts; const bondFor = options?.bondFor || 'nominator'; @@ -128,7 +130,16 @@ export const ManageNominations = () => { callbackSubmit: () => { setCanvasStatus('closing'); }, - callbackInBlock: () => {}, + callbackInBlock: () => { + if (isPool) { + // Upate bonded pool targets if updating pool nominations. + if (selectedActivePool?.id) + updatePoolNominations( + selectedActivePool.id, + newNominations.nominations.map((n) => n.address) + ); + } + }, }); // Valid if there are between 1 and `maxNominations` nominations. diff --git a/src/config/help.ts b/src/config/help.ts index caadcb7d93..c053410438 100644 --- a/src/config/help.ts +++ b/src/config/help.ts @@ -156,6 +156,7 @@ export const HelpConfig: HelpItems = [ 'Ledger Rejected Transaction', 'Ledger Request Timeout', 'Open App On Ledger', + 'Ledger App Not on Latest Runtime Version', 'Wrong Transaction', ], external: [], diff --git a/src/config/ledger.ts b/src/config/ledger.ts index 597b0183ef..4e2f5d405a 100644 --- a/src/config/ledger.ts +++ b/src/config/ledger.ts @@ -1,7 +1,7 @@ // Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { LedgerApp } from 'contexts/Hardware/types'; +import type { LedgerApp } from 'contexts/Hardware/Ledger/types'; import KusamaSVG from 'img/appIcons/kusama.svg?react'; import PolkadotSVG from 'img/appIcons/polkadot.svg?react'; diff --git a/src/consts.ts b/src/consts.ts index ff7511dfd8..bcd6e3c1e6 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -8,7 +8,7 @@ import type { Plugin } from 'types'; /* * Global Constants */ -export const AppVersion = '1.0.8'; +export const AppVersion = '1.1.2'; export const DappName = 'Polkadot Staking Dashboard'; export const PolkadotUrl = 'https://polkadot.network/features/staking/'; export const DefaultNetwork = 'polkadot'; diff --git a/src/contexts/Api/defaults.ts b/src/contexts/Api/defaults.ts index 2e7afaa837..c53e04ce51 100644 --- a/src/contexts/Api/defaults.ts +++ b/src/contexts/Api/defaults.ts @@ -4,9 +4,13 @@ import { stringToU8a } from '@polkadot/util'; import BigNumber from 'bignumber.js'; -import type { APIConstants, APIContextInterface } from 'contexts/Api/types'; +import type { + APIChainState, + APIConstants, + APIContextInterface, +} from 'contexts/Api/types'; -export const consts: APIConstants = { +export const defaultConsts: APIConstants = { bondDuration: new BigNumber(0), maxNominations: new BigNumber(0), sessionsPerEra: new BigNumber(0), @@ -20,10 +24,18 @@ export const consts: APIConstants = { poolsPalletId: stringToU8a('0'), }; +export const defaultChainState: APIChainState = { + chain: null, + version: { + specVersion: 0, + }, + ss58Prefix: 0, +}; + export const defaultApiContext: APIContextInterface = { api: null, - consts, - chainState: undefined, + consts: defaultConsts, + chainState: defaultChainState, isReady: false, apiStatus: 'disconnected', isLightClient: false, diff --git a/src/contexts/Api/index.tsx b/src/contexts/Api/index.tsx index 2bb80f79a9..cb40080845 100644 --- a/src/contexts/Api/index.tsx +++ b/src/contexts/Api/index.tsx @@ -25,7 +25,11 @@ import type { } from 'contexts/Api/types'; import type { AnyApi } from 'types'; import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; -import * as defaults from './defaults'; +import { + defaultApiContext, + defaultChainState, + defaultConsts, +} from './defaults'; export const APIProvider = ({ children, network }: APIProviderProps) => { // Store povider instance. @@ -34,7 +38,8 @@ export const APIProvider = ({ children, network }: APIProviderProps) => { ); // Store chain state. - const [chainState, setchainState] = useState(undefined); + const [chainState, setchainState] = + useState(defaultChainState); // Store the active RPC provider. const initialRpcEndpoint = () => { @@ -48,9 +53,8 @@ export const APIProvider = ({ children, network }: APIProviderProps) => { return NetworkList[network].endpoints.defaultRpcEndpoint; }; - const [rpcEndpoint, setRpcEndpointState] = useState( - initialRpcEndpoint() - ); + const [rpcEndpoint, setRpcEndpointState] = + useState(initialRpcEndpoint()); // Store whether in light client mode. const [isLightClient, setIsLightClient] = useState( @@ -61,7 +65,7 @@ export const APIProvider = ({ children, network }: APIProviderProps) => { const [api, setApi] = useState(null); // Store network constants. - const [consts, setConsts] = useState(defaults.consts); + const [consts, setConsts] = useState(defaultConsts); // Store API connection status. const [apiStatus, setApiStatus] = useState('disconnected'); @@ -88,8 +92,8 @@ export const APIProvider = ({ children, network }: APIProviderProps) => { const handleApiSwitch = () => { setApi(null); - setConsts(defaults.consts); - setchainState(undefined); + setConsts(defaultConsts); + setchainState(defaultChainState); }; // Handle connect to API. @@ -134,22 +138,17 @@ export const APIProvider = ({ children, network }: APIProviderProps) => { const newChainState = await Promise.all([ newApi.rpc.system.chain(), - newApi.rpc.system.version(), + newApi.consts.system.version, newApi.consts.system.ss58Prefix, ]); // check that chain values have been fetched before committing to state. // could be expanded to check supported chains. - if ( - newChainState.every((c) => { - return !!c?.toHuman(); - }) - ) { + if (newChainState.every((c) => !!c?.toHuman())) { const chain = newChainState[0]?.toString(); - const version = newChainState[1]?.toString(); + const version = newChainState[1]?.toJSON(); const ss58Prefix = Number(newChainState[2]?.toString()); - // set fetched chain state in storage. setchainState({ chain, version, ss58Prefix }); } @@ -301,8 +300,6 @@ export const APIProvider = ({ children, network }: APIProviderProps) => { ); }; -export const APIContext = createContext( - defaults.defaultApiContext -); +export const APIContext = createContext(defaultApiContext); export const useApi = () => useContext(APIContext); diff --git a/src/contexts/Api/types.ts b/src/contexts/Api/types.ts index e5e23c4792..cc8346b1db 100644 --- a/src/contexts/Api/types.ts +++ b/src/contexts/Api/types.ts @@ -5,7 +5,7 @@ import type { ApiPromise } from '@polkadot/api'; import type { U8aLike } from '@polkadot/util/types'; import type BigNumber from 'bignumber.js'; import type { ReactNode } from 'react'; -import type { Network, NetworkName } from '../../types'; +import type { AnyJson, Network, NetworkName } from '../../types'; export type ApiStatus = 'connecting' | 'connected' | 'disconnected'; @@ -32,13 +32,11 @@ export interface APIConstants { poolsPalletId: U8aLike; } -export type APIChainState = - | { - chain: string; - version: string; - ss58Prefix: number; - } - | undefined; +export type APIChainState = { + chain: string | null; + version: AnyJson; + ss58Prefix: number; +}; export interface APIContextInterface { api: ApiPromise | null; diff --git a/src/contexts/Balances/index.tsx b/src/contexts/Balances/index.tsx index 21a7181feb..56078ded55 100644 --- a/src/contexts/Balances/index.tsx +++ b/src/contexts/Balances/index.tsx @@ -16,6 +16,7 @@ import type { AnyApi, MaybeAddress } from 'types'; import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import { useNetwork } from 'contexts/Network'; import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useExternalAccounts } from 'contexts/Connect/ExternalAccounts'; import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; import { getLedger } from './Utils'; import * as defaults from './defaults'; @@ -37,9 +38,9 @@ export const BalancesProvider = ({ }) => { const { api, isReady } = useApi(); const { network } = useNetwork(); - const { accounts } = useImportedAccounts(); - const { getAccount } = useImportedAccounts(); - const { addExternalAccount } = useOtherAccounts(); + const { accounts, getAccount } = useImportedAccounts(); + const { addOrReplaceOtherAccount } = useOtherAccounts(); + const { addExternalAccount } = useExternalAccounts(); const [balances, setBalances] = useState([]); const balancesRef = useRef(balances); @@ -102,7 +103,8 @@ export const BalancesProvider = ({ // add stash as external account if not present if (!getAccount(stash.toString())) { - addExternalAccount(stash.toString(), 'system'); + const result = addExternalAccount(stash.toString(), 'system'); + if (result) addOrReplaceOtherAccount(result.account, result.type); } setStateWithRef( diff --git a/src/contexts/Bonded/index.tsx b/src/contexts/Bonded/index.tsx index 212d0ecc65..3dafa240bf 100644 --- a/src/contexts/Bonded/index.tsx +++ b/src/contexts/Bonded/index.tsx @@ -15,6 +15,7 @@ import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import { useNetwork } from 'contexts/Network'; import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import { useExternalAccounts } from 'contexts/Connect/ExternalAccounts'; import * as defaults from './defaults'; import type { BondedAccount, BondedContextInterface } from './types'; @@ -22,7 +23,8 @@ export const BondedProvider = ({ children }: { children: React.ReactNode }) => { const { network } = useNetwork(); const { api, isReady } = useApi(); const { accounts } = useImportedAccounts(); - const { addExternalAccount } = useOtherAccounts(); + const { addExternalAccount } = useExternalAccounts(); + const { addOrReplaceOtherAccount } = useOtherAccounts(); // Balance accounts state. const [bondedAccounts, setBondedAccounts] = useState([]); @@ -107,7 +109,8 @@ export const BondedProvider = ({ children }: { children: React.ReactNode }) => { // add bonded (controller) account as external account if not presently imported if (newController) { if (accounts.find((s) => s.address === newController) === undefined) { - addExternalAccount(newController, 'system'); + const result = addExternalAccount(newController, 'system'); + if (result) addOrReplaceOtherAccount(result.account, result.type); } } diff --git a/src/contexts/Connect/ExternalAccounts/Utils.ts b/src/contexts/Connect/ExternalAccounts/Utils.ts new file mode 100644 index 0000000000..c469f63e15 --- /dev/null +++ b/src/contexts/Connect/ExternalAccounts/Utils.ts @@ -0,0 +1,64 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { ExternalAccount } from '@polkadot-cloud/react/types'; +import { localStorageOrDefault } from '@polkadot-cloud/utils'; +import type { NetworkName } from 'types'; + +// Check whether an external account exists in local storage. +export const externalAccountExistsLocal = ( + address: string, + network: NetworkName +) => + getLocalExternalAccounts().find( + (l) => l.address === address && l.network === network + ); + +// Gets local external accounts from local storage. Ensure that only `user` accounts are returned. +export const getLocalExternalAccounts = (network?: NetworkName) => { + let localAccounts = localStorageOrDefault( + 'external_accounts', + [], + true + ) as ExternalAccount[]; + if (network) + localAccounts = localAccounts.filter( + (l) => l.network === network && l.addedBy !== 'system' + ); + return localAccounts; +}; + +// Adds a local external account to local storage. +export const addLocalExternalAccount = (account: ExternalAccount) => { + localStorage.setItem( + 'external_accounts', + JSON.stringify(getLocalExternalAccounts().concat(account)) + ); +}; + +// Updates a local external account with the provided `addedBy` property. +export const updateLocalExternalAccount = (entry: ExternalAccount) => { + localStorage.setItem( + 'external_accounts', + JSON.stringify( + getLocalExternalAccounts().map((a) => + a.address === entry.address ? entry : a + ) + ) + ); +}; + +// Removes supplied external cccounts from local storage. +export const removeLocalExternalAccounts = ( + network: NetworkName, + accounts: ExternalAccount[] +) => { + if (!accounts.length) return; + + const updatedAccounts = getLocalExternalAccounts(network).filter( + (a) => + accounts.find((b) => b.address === a.address && a.network === network) === + undefined + ); + localStorage.setItem('external_accounts', JSON.stringify(updatedAccounts)); +}; diff --git a/src/contexts/Connect/ExternalAccounts/defaults.ts b/src/contexts/Connect/ExternalAccounts/defaults.ts new file mode 100644 index 0000000000..180facdeb4 --- /dev/null +++ b/src/contexts/Connect/ExternalAccounts/defaults.ts @@ -0,0 +1,11 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { ExternalAccountsContextInterface } from './types'; + +export const defaultExternalAccountsContext: ExternalAccountsContextInterface = + { + addExternalAccount: (a, b) => null, + forgetExternalAccounts: (a) => {}, + }; diff --git a/src/contexts/Connect/ExternalAccounts/index.tsx b/src/contexts/Connect/ExternalAccounts/index.tsx new file mode 100644 index 0000000000..acb8f7b550 --- /dev/null +++ b/src/contexts/Connect/ExternalAccounts/index.tsx @@ -0,0 +1,109 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useContext, type ReactNode, createContext } from 'react'; +import Keyring from '@polkadot/keyring'; +import { useNetwork } from 'contexts/Network'; +import { ellipsisFn } from '@polkadot-cloud/utils'; +import type { + ExternalAccount, + ExternalAccountAddedBy, +} from '@polkadot-cloud/react/types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import type { + AddExternalAccountResult, + ExternalAccountImportType, + ExternalAccountsContextInterface, +} from './types'; +import { defaultExternalAccountsContext } from './defaults'; +import { + addLocalExternalAccount, + externalAccountExistsLocal, + removeLocalExternalAccounts, +} from './Utils'; + +export const ExternalAccountsContext = + createContext( + defaultExternalAccountsContext + ); + +export const ExternalAccountsProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const { + network, + networkData: { ss58 }, + } = useNetwork(); + const { activeAccount, setActiveAccount } = useActiveAccounts(); + + // Adds an external account (non-wallet) to accounts. + const addExternalAccount = ( + address: string, + addedBy: ExternalAccountAddedBy + ): AddExternalAccountResult | null => { + // ensure account is formatted correctly. + const keyring = new Keyring(); + keyring.setSS58Format(ss58); + + let newEntry = { + address: keyring.addFromAddress(address).address, + network, + name: ellipsisFn(address), + source: 'external', + addedBy, + }; + + const existsLocal = externalAccountExistsLocal(newEntry.address, network); + + // Whether the account needs to remain imported as a system account. + const toSystem = + existsLocal && addedBy === 'system' && existsLocal.addedBy !== 'system'; + + let isImported: boolean = true; + let importType: ExternalAccountImportType = 'new'; + + if (!existsLocal) { + // Only add `user` accounts to localStorage. + if (addedBy === 'user') addLocalExternalAccount(newEntry); + } else if (toSystem) { + // If account is being added by `system`, but is already imported, update it to be a system + // account. `system` accounts are not persisted to local storage. + // + // update the entry to a system account. + newEntry = { ...newEntry, addedBy: 'system' }; + importType = 'replace'; + } else isImported = false; + + return isImported + ? { + type: importType, + account: newEntry, + } + : null; + }; + + // Get any external accounts and remove from localStorage. + const forgetExternalAccounts = (forget: ExternalAccount[]) => { + if (!forget.length) return; + removeLocalExternalAccounts( + network, + forget.filter((i) => 'network' in i) as ExternalAccount[] + ); + + // If the currently active account is being forgotten, disconnect. + if (forget.find((a) => a.address === activeAccount) !== undefined) + setActiveAccount(null); + }; + + return ( + + {children} + + ); +}; + +export const useExternalAccounts = () => useContext(ExternalAccountsContext); diff --git a/src/contexts/Connect/ExternalAccounts/types.ts b/src/contexts/Connect/ExternalAccounts/types.ts new file mode 100644 index 0000000000..ed5ef1f3b7 --- /dev/null +++ b/src/contexts/Connect/ExternalAccounts/types.ts @@ -0,0 +1,25 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { + ExternalAccount, + ExternalAccountAddedBy, +} from '@polkadot-cloud/react/types'; + +export interface ExternalAccountsContextInterface { + addExternalAccount: ( + a: string, + addedBy: ExternalAccountAddedBy + ) => AddExternalAccountResult | null; + forgetExternalAccounts: (a: ExternalAccount[]) => void; +} + +export interface AddExternalAccountResult { + account: ExternalAccount; + type: ExternalAccountImportType; +} + +export type ExternalAccountImportType = 'new' | 'replace'; diff --git a/src/contexts/Connect/OtherAccounts/defaults.ts b/src/contexts/Connect/OtherAccounts/defaults.ts index 7629215a2b..526acd1574 100644 --- a/src/contexts/Connect/OtherAccounts/defaults.ts +++ b/src/contexts/Connect/OtherAccounts/defaults.ts @@ -5,12 +5,11 @@ import type { OtherAccountsContextInterface } from './types'; export const defaultOtherAccountsContext: OtherAccountsContextInterface = { - addExternalAccount: (a, b) => {}, addOtherAccounts: (a) => {}, + addOrReplaceOtherAccount: (account, type) => {}, renameOtherAccount: (a, n) => {}, importLocalOtherAccounts: (n) => {}, forgetOtherAccounts: (a) => {}, - forgetExternalAccounts: (a) => {}, otherAccounts: [], accountsInitialised: false, }; diff --git a/src/contexts/Connect/OtherAccounts/index.tsx b/src/contexts/Connect/OtherAccounts/index.tsx index b4ce6519e5..423d204f22 100644 --- a/src/contexts/Connect/OtherAccounts/index.tsx +++ b/src/contexts/Connect/OtherAccounts/index.tsx @@ -13,21 +13,15 @@ import { getLocalVaultAccounts, } from 'contexts/Hardware/Utils'; import type { AnyFunction, MaybeAddress, NetworkName } from 'types'; -import { ellipsisFn, setStateWithRef } from '@polkadot-cloud/utils'; +import { setStateWithRef } from '@polkadot-cloud/utils'; import { useNetwork } from 'contexts/Network'; import { useActiveAccounts } from 'contexts/ActiveAccounts'; -import Keyring from '@polkadot/keyring'; -import type { - ExternalAccount, - ImportedAccount, -} from '@polkadot-cloud/react/types'; -import { - getActiveAccountLocal, - getLocalExternalAccounts, - removeLocalExternalAccounts, -} from '../Utils'; +import type { ImportedAccount } from '@polkadot-cloud/react/types'; +import { getActiveAccountLocal } from '../Utils'; import type { OtherAccountsContextInterface } from './types'; import { defaultOtherAccountsContext } from './defaults'; +import { getLocalExternalAccounts } from '../ExternalAccounts/Utils'; +import type { ExternalAccountImportType } from '../ExternalAccounts/types'; export const OtherAccountsContext = createContext(defaultOtherAccountsContext); @@ -113,9 +107,8 @@ export const OtherAccountsProvider = ({ ); // set active account for networkData. - if (activeAccountInSet) { - setActiveAccount(activeAccountInSet?.address || null); - } + if (activeAccountInSet) setActiveAccount(activeAccountInSet.address); + // add accounts to imported. addOtherAccounts(localAccounts); } @@ -137,75 +130,43 @@ export const OtherAccountsProvider = ({ ); }; - // Adds an external account (non-wallet) to accounts. - const addExternalAccount = (address: string, addedBy: string) => { - // ensure account is formatted correctly - const keyring = new Keyring(); - keyring.setSS58Format(ss58); - const formatted = keyring.addFromAddress(address).address; - - const newAccount = { - address: formatted, - network, - name: ellipsisFn(address), - source: 'external', - addedBy, - }; - - // get all external accounts from localStorage. - const localExternalAccounts = getLocalExternalAccounts(); - const existsLocal = localExternalAccounts.find( - (l) => l.address === address && l.network === network - ); + // Unsubscribe all account subscriptions. + const unsubscribe = () => { + Object.values(unsubs.current).forEach((unsub) => { + unsub(); + }); + }; - // check that address is not sitting in imported accounts (currently cannot check which - // network). - const existsImported = otherAccountsRef.current.find( - (a) => a.address === address + // Add other accounts to context state. + const addOtherAccounts = (account: ImportedAccount[]) => { + setStateWithRef( + [...otherAccountsRef.current].concat(account), + setOtherAccounts, + otherAccountsRef ); - - // add external account if not there already. - if (!existsLocal && !existsImported) { - localStorage.setItem( - 'external_accounts', - JSON.stringify(localExternalAccounts.concat(newAccount)) - ); - - // add external account to imported accounts - addOtherAccounts([newAccount]); - } else if (existsLocal && existsLocal.addedBy !== 'system') { - // the external account needs to change to `system` so it cannot be removed. This will replace - // the whole entry. - localStorage.setItem( - 'external_accounts', - JSON.stringify( - localExternalAccounts.map((item) => - item.address !== address ? item : newAccount - ) - ) - ); - // re-sync account state. - setStateWithRef( - [...otherAccountsRef.current].map((item) => - item.address !== newAccount.address ? item : newAccount - ), - setOtherAccounts, - otherAccountsRef - ); - } }; - // Get any external accounts and remove from localStorage. - const forgetExternalAccounts = (forget: ImportedAccount[]) => { - if (!forget.length) return; - removeLocalExternalAccounts( - network, - forget.filter((i) => 'network' in i) as ExternalAccount[] + // Replace other account with new entry. + const replaceOtherAccount = (account: ImportedAccount) => { + setStateWithRef( + [...otherAccountsRef.current].map((item) => + item.address !== account.address ? item : account + ), + setOtherAccounts, + otherAccountsRef ); + }; - // If the currently active account is being forgotten, disconnect. - if (forget.find((a) => a.address === activeAccount) !== undefined) - setActiveAccount(null); + // Add or replace other account with an entry. + const addOrReplaceOtherAccount = ( + account: ImportedAccount, + type: ExternalAccountImportType + ) => { + if (type === 'new') { + addOtherAccounts([account]); + } else if (type === 'replace') { + replaceOtherAccount(account); + } }; // Re-sync other accounts on network switch. Waits for `injectedWeb3` to be injected. @@ -218,22 +179,6 @@ export const OtherAccountsProvider = ({ return () => unsubscribe(); }, [network, checkingInjectedWeb3]); - // Unsubscrbe all account subscriptions. - const unsubscribe = () => { - Object.values(unsubs.current).forEach((unsub) => { - unsub(); - }); - }; - - // Add other accounts to context state. - const addOtherAccounts = (a: ImportedAccount[]) => { - setStateWithRef( - [...otherAccountsRef.current].concat(a), - setOtherAccounts, - otherAccountsRef - ); - }; - // Once extensions are fully initialised, fetch accounts from other sources. useEffectIgnoreInitial(() => { if (extensionAccountsSynced) { @@ -259,12 +204,11 @@ export const OtherAccountsProvider = ({ return ( void; addOtherAccounts: (a: ImportedAccount[]) => void; + addOrReplaceOtherAccount: ( + a: ImportedAccount, + type: ExternalAccountImportType + ) => void; renameOtherAccount: (a: MaybeAddress, n: string) => void; importLocalOtherAccounts: (g: (n: NetworkName) => ImportedAccount[]) => void; forgetOtherAccounts: (a: ImportedAccount[]) => void; - forgetExternalAccounts: (a: ImportedAccount[]) => void; accountsInitialised: boolean; otherAccounts: ImportedAccount[]; } diff --git a/src/contexts/Connect/Utils.ts b/src/contexts/Connect/Utils.ts index 70b293fc8b..92910629af 100644 --- a/src/contexts/Connect/Utils.ts +++ b/src/contexts/Connect/Utils.ts @@ -3,33 +3,9 @@ import Keyring from '@polkadot/keyring'; import { localStorageOrDefault } from '@polkadot-cloud/utils'; -import type { - ExtensionAccount, - ExternalAccount, -} from '@polkadot-cloud/react/types'; import type { NetworkName } from 'types'; -// adds an extension to local `active_extensions` -export const addToLocalExtensions = (id: string) => { - const localExtensions = localStorageOrDefault( - `active_extensions`, - [], - true - ); - if (Array.isArray(localExtensions)) { - if (!localExtensions.includes(id)) { - localExtensions.push(id); - localStorage.setItem( - 'active_extensions', - JSON.stringify(localExtensions) - ); - } - } -}; - -// account utils - -// gets local `activeAccount` for a network +// Gets local `activeAccount` for a network. export const getActiveAccountLocal = (network: NetworkName, ss58: number) => { const keyring = new Keyring(); keyring.setSS58Format(ss58); @@ -40,61 +16,14 @@ export const getActiveAccountLocal = (network: NetworkName, ss58: number) => { return account; }; -// gets local external accounts, formatting their addresses -// using active network ss58 format. -export const getLocalExternalAccounts = (network?: NetworkName) => { - let localAccounts = localStorageOrDefault( - 'external_accounts', - [], - true - ) as ExternalAccount[]; - if (network) { - localAccounts = localAccounts.filter((l) => l.network === network); - } - return localAccounts; -}; - -// gets accounts that exist in local `external_accounts` -export const getInExternalAccounts = ( - accounts: ExtensionAccount[], - network: NetworkName -) => { - const localExternalAccounts = getLocalExternalAccounts(network); - - return ( - localExternalAccounts.filter( - (a) => (accounts || []).find((b) => b.address === a.address) !== undefined - ) || [] - ); -}; - -// removes supplied accounts from local `external_accounts`. -export const removeLocalExternalAccounts = ( - network: NetworkName, - accounts: ExternalAccount[] -) => { - if (!accounts.length) return; - - let localExternalAccounts = getLocalExternalAccounts(network); - localExternalAccounts = localExternalAccounts.filter( - (a) => - accounts.find((b) => b.address === a.address && a.network === network) === - undefined - ); - localStorage.setItem( - 'external_accounts', - JSON.stringify(localExternalAccounts) - ); -}; - +// Formats an address with the supplied ss58 prefix. export const formatAccountSs58 = (address: string, ss58: number) => { try { const keyring = new Keyring(); keyring.setSS58Format(ss58); const formatted = keyring.addFromAddress(address).address; - if (formatted !== address) { - return formatted; - } + if (formatted !== address) return formatted; + return null; } catch (e) { return null; diff --git a/src/contexts/Hardware/Ledger.tsx b/src/contexts/Hardware/Ledger.tsx deleted file mode 100644 index 32628b4b77..0000000000 --- a/src/contexts/Hardware/Ledger.tsx +++ /dev/null @@ -1,556 +0,0 @@ -// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: GPL-3.0-only - -import TransportWebHID from '@ledgerhq/hw-transport-webhid'; -import { u8aToBuffer } from '@polkadot/util'; -import { localStorageOrDefault, setStateWithRef } from '@polkadot-cloud/utils'; -import { newSubstrateApp } from '@zondax/ledger-substrate'; -import React, { useEffect, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import type { LedgerAccount } from '@polkadot-cloud/react/types'; -import type { AnyFunction, AnyJson, MaybeString } from 'types'; -import { useNetwork } from 'contexts/Network'; -import { - getLocalLedgerAccounts, - getLocalLedgerAddresses, - isLocalNetworkAddress, -} from './Utils'; -import { - LEDGER_DEFAULT_ACCOUNT, - LEDGER_DEFAULT_CHANGE, - LEDGER_DEFAULT_INDEX, - TOTAL_ALLOWED_STATUS_CODES, - defaultFeedback, - defaultLedgerHardwareContext, -} from './defaults'; -import type { - FeedbackMessage, - LedgerAddress, - LedgerHardwareContextInterface, - LedgerResponse, - LedgerStatusCode, - LedgerTask, - PairingStatus, -} from './types'; - -export const LedgerHardwareProvider = ({ - children, -}: { - children: React.ReactNode; -}) => { - const { t } = useTranslation('modals'); - const { network } = useNetwork(); - - const [ledgerAccounts, setLedgerAccountsState] = useState( - getLocalLedgerAccounts(network) - ); - const ledgerAccountsRef = useRef(ledgerAccounts); - - // Store whether the device has been paired. - const [isPaired, setIsPairedState] = useState('unknown'); - const isPairedRef = useRef(isPaired); - - // Store whether an import is in process. - const [isExecuting, setIsExecutingState] = useState(false); - const isExecutingRef = useRef(isExecuting); - - // Store status codes received from Ledger device. - const [statusCodes, setStatusCodes] = useState([]); - const statusCodesRef = useRef(statusCodes); - - // Get the default message to display, set when a failed loop has happened. - const [feedback, setFeedbackState] = - useState(defaultFeedback); - - const feedbackRef = useRef(feedback); - - // Store the latest successful response from an attempted `executeLedgerLoop`. - const [transportResponse, setTransportResponse] = useState(null); - - // Whether pairing is in progress: protects against re-renders & duplicate attempts. - const pairInProgress = useRef(false); - - // Whether a ledger-loop is in progress: protects against re-renders & duplicate attempts. - const ledgerLoopInProgress = useRef(false); - - // The ledger transport interface. - const ledgerTransport = useRef(null); - - // Refresh imported ledger accounts on network change. - useEffect(() => { - setStateWithRef( - getLocalLedgerAccounts(network), - setLedgerAccountsState, - ledgerAccountsRef - ); - }, [network]); - - // Handles errors that occur during `executeLedgerLoop` and `pairDevice` calls. - const handleErrors = (appName: string, err: AnyJson) => { - // reset any in-progress calls. - ledgerLoopInProgress.current = false; - pairInProgress.current = false; - - // execution failed - no longer executing. - setIsExecuting(false); - - // close any open device connections. - if (ledgerTransport.current?.device?.opened) { - ledgerTransport.current?.device?.close(); - } - - // format error message. - err = String(err); - if (err === 'Error: Timeout') { - // only set default message here - maintain previous status code. - setFeedback(t('ledgerRequestTimeout'), 'Ledger Request Timeout'); - handleNewStatusCode('failure', 'DeviceTimeout'); - } else if (err.startsWith('Error: Call nesting not supported')) { - setFeedback(t('missingNesting')); - handleNewStatusCode('failure', 'NestingNotSupported'); - } else if ( - err.startsWith('Error: TransportError: Invalid channel') || - err.startsWith('Error: InvalidStateError') - ) { - // occurs when tx was approved outside of active channel. - setFeedback(t('queuedTransactionRejected'), 'Wrong Transaction'); - handleNewStatusCode('failure', 'WrongTransaction'); - } else if ( - err.startsWith('TransportOpenUserCancelled') || - err.startsWith('Error: Ledger Device is busy') - ) { - // occurs when the device is not connected. - setFeedback(t('connectLedgerToContinue')); - handleNewStatusCode('failure', 'DeviceNotConnected'); - } else if (err.startsWith('Error: LockedDeviceError')) { - // occurs when the device is connected but not unlocked. - setFeedback(t('unlockLedgerToContinue')); - handleNewStatusCode('failure', 'DeviceLocked'); - } else if (err.startsWith('Error: Transaction rejected')) { - // occurs when user rejects a transaction. - setFeedback( - t('transactionRejectedPending'), - 'Ledger Rejected Transaction' - ); - handleNewStatusCode('failure', 'TransactionRejected'); - } else if (err.startsWith('Error: Unknown Status Code: 28161')) { - // occurs when the required app is not open. - handleNewStatusCode('failure', 'AppNotOpenContinue'); - setFeedback(t('openAppOnLedger', { appName }), 'Open App On Ledger'); - } else { - // miscellanous errors - assume app is not open or ready. - setFeedback(t('openAppOnLedger', { appName }), 'Open App On Ledger'); - handleNewStatusCode('failure', 'AppNotOpen'); - } - }; - - // Timeout function for hanging tasks. Used for tasks that require no input from the device, such - // as getting an address that does not require confirmation. - const withTimeout = (millis: AnyFunction, promise: AnyFunction) => { - const timeout = new Promise((_, reject) => - setTimeout(async () => { - ledgerTransport.current?.device?.close(); - reject(Error('Timeout')); - }, millis) - ); - return Promise.race([promise, timeout]); - }; - - // Attempt to pair a device. - // - // Trigger a one-time connection to the device to determine if it is available. If the device - // needs to be paired, a browser prompt will open. If cancelled, an error will be thrown. - const pairDevice = async () => { - try { - // return `paired` if pairing is already in progress. - if (pairInProgress.current) { - return isPairedRef.current === 'paired'; - } - // set pairing in progress. - pairInProgress.current = true; - - // remove any previously stored status codes. - resetStatusCodes(); - - // close any open connections. - if (ledgerTransport.current?.device?.opened) { - await ledgerTransport.current?.device?.close(); - } - // establish a new connection with device. - ledgerTransport.current = await TransportWebHID.create(); - setIsPaired('paired'); - pairInProgress.current = false; - return true; - } catch (err) { - pairInProgress.current = false; - handleErrors('', err); - return false; - } - }; - - // Connects to a Ledger device to perform a task. This is the main execute function that handles - // all Ledger tasks, along with errors that occur during the process. - const executeLedgerLoop = async ( - appName: string, - tasks: LedgerTask[], - options?: AnyJson - ) => { - try { - // do not execute again if already in progress. - if (ledgerLoopInProgress.current) { - return; - } - - // set ledger loop in progress. - ledgerLoopInProgress.current = true; - - // test for tasks and execute them. This is designed such that `result` will only store the - // result of one task. This will have to be refactored if we ever need to execute multiple - // tasks at once. - let result = null; - if (tasks.includes('get_address')) { - result = await handleGetAddress(appName, options?.accountIndex || 0); - } else if (tasks.includes('sign_tx')) { - const uid = options?.uid || 0; - const index = options?.accountIndex || 0; - const payload = options?.payload || ''; - - result = await handleSignTx(appName, uid, index, payload); - } - - // a populated result indicates a successful execution. Set the transport response state for - // other components to respond to via useEffect. - if (result) { - setTransportResponse({ - ack: 'success', - options, - ...result, - }); - } - ledgerLoopInProgress.current = false; - } catch (err) { - handleErrors(appName, err); - } - }; - - // Gets an app address on device. - const handleGetAddress = async (appName: string, index: number) => { - const substrateApp = newSubstrateApp(ledgerTransport.current, appName); - const { deviceModel } = ledgerTransport.current; - const { id, productName } = deviceModel; - - setTransportResponse({ - ack: 'success', - statusCode: 'GettingAddress', - body: null, - }); - setFeedback(t('gettingAddress')); - - if (!ledgerTransport.current?.device?.opened) { - await ledgerTransport.current?.device?.open(); - } - const result: AnyJson = await withTimeout( - 3000, - substrateApp.getAddress( - LEDGER_DEFAULT_ACCOUNT + index, - LEDGER_DEFAULT_CHANGE, - LEDGER_DEFAULT_INDEX + 0, - false - ) - ); - - await ledgerTransport.current?.device?.close(); - - const error = result?.error_message; - if (error) { - if (!error.startsWith('No errors')) { - throw new Error(error); - } - } - - if (!(result instanceof Error)) { - setFeedback(t('successfullyFetchedAddress')); - - return { - statusCode: 'ReceivedAddress', - device: { id, productName }, - body: [result], - }; - } - return undefined; - }; - - // Signs a payload on device. - const handleSignTx = async ( - appName: string, - uid: number, - index: number, - payload: AnyJson - ) => { - const substrateApp = newSubstrateApp(ledgerTransport.current, appName); - const { deviceModel } = ledgerTransport.current; - const { id, productName } = deviceModel; - - setTransportResponse({ - ack: 'success', - statusCode: 'SigningPayload', - body: null, - }); - - setFeedback(t('approveTransactionLedger')); - - if (!ledgerTransport.current?.device?.opened) { - await ledgerTransport.current?.device?.open(); - } - const result = await substrateApp.sign( - LEDGER_DEFAULT_ACCOUNT + index, - LEDGER_DEFAULT_CHANGE, - LEDGER_DEFAULT_INDEX + 0, - u8aToBuffer(payload.toU8a(true)) - ); - - setFeedback(t('signedTransactionSuccessfully')); - await ledgerTransport.current?.device?.close(); - - const error = result?.error_message; - if (error) { - if (!error.startsWith('No errors')) { - throw new Error(error); - } - } - - if (!(result instanceof Error)) { - return { - statusCode: 'SignedPayload', - device: { id, productName }, - body: { - uid, - sig: result.signature, - }, - }; - } - return undefined; - }; - - // Handle an incoming new status code and persist to state. - const handleNewStatusCode = (ack: string, statusCode: LedgerStatusCode) => { - const newStatusCodes = [{ ack, statusCode }, ...statusCodes]; - - // Remove last status code if there are more than allowed number of status codes. - if (newStatusCodes.length > TOTAL_ALLOWED_STATUS_CODES) { - newStatusCodes.pop(); - } - setStateWithRef(newStatusCodes, setStatusCodes, statusCodesRef); - }; - - // Check if a Ledger address exists in imported addresses. - const ledgerAccountExists = (address: string) => - !!getLocalLedgerAccounts().find((a) => - isLocalNetworkAddress(network, a, address) - ); - - const addLedgerAccount = (address: string, index: number) => { - let newLedgerAccounts = getLocalLedgerAccounts(); - - const ledgerAddress = getLocalLedgerAddresses().find((a) => - isLocalNetworkAddress(network, a, address) - ); - - if ( - ledgerAddress && - !newLedgerAccounts.find((a) => isLocalNetworkAddress(network, a, address)) - ) { - const account = { - address, - network, - name: ledgerAddress.name, - source: 'ledger', - index, - }; - - // update the full list of local ledger accounts with new entry. - newLedgerAccounts = [...newLedgerAccounts].concat(account); - localStorage.setItem( - 'ledger_accounts', - JSON.stringify(newLedgerAccounts) - ); - - // store only those accounts on the current network in state. - setStateWithRef( - newLedgerAccounts.filter((a) => a.network === network), - setLedgerAccountsState, - ledgerAccountsRef - ); - - return account; - } - return null; - }; - - const removeLedgerAccount = (address: string) => { - let newLedgerAccounts = getLocalLedgerAccounts(); - - newLedgerAccounts = newLedgerAccounts.filter((a) => { - if (a.address !== address) { - return true; - } - if (a.network !== network) { - return true; - } - return false; - }); - if (!newLedgerAccounts.length) { - localStorage.removeItem('ledger_accounts'); - } else { - localStorage.setItem( - 'ledger_accounts', - JSON.stringify(newLedgerAccounts) - ); - } - setStateWithRef( - newLedgerAccounts.filter((a) => a.network === network), - setLedgerAccountsState, - ledgerAccountsRef - ); - }; - - // Gets an imported address along with its Ledger metadata. - const getLedgerAccount = (address: string) => { - const localLedgerAccounts = getLocalLedgerAccounts(); - - if (!localLedgerAccounts) { - return null; - } - return ( - localLedgerAccounts.find((a) => - isLocalNetworkAddress(network, a, address) - ) ?? null - ); - }; - - // Renames an imported ledger account. - const renameLedgerAccount = (address: string, newName: string) => { - let newLedgerAccounts = getLocalLedgerAccounts(); - - newLedgerAccounts = newLedgerAccounts.map((a) => - isLocalNetworkAddress(network, a, address) - ? { - ...a, - name: newName, - } - : a - ); - renameLocalLedgerAddress(address, newName); - localStorage.setItem('ledger_accounts', JSON.stringify(newLedgerAccounts)); - setStateWithRef( - newLedgerAccounts.filter((a) => a.network === network), - setLedgerAccountsState, - ledgerAccountsRef - ); - }; - - // Renames a record from local ledger addresses. - const renameLocalLedgerAddress = (address: string, name: string) => { - const localLedger = ( - localStorageOrDefault('ledger_addresses', [], true) as LedgerAddress[] - )?.map((i) => - !(i.address === address && i.network === network) - ? i - : { - ...i, - name, - } - ); - - if (localLedger) { - localStorage.setItem('ledger_addresses', JSON.stringify(localLedger)); - } - }; - - const getTransport = () => { - return ledgerTransport.current; - }; - - const getIsExecuting = () => { - return isExecutingRef.current; - }; - - const getStatusCodes = () => { - return statusCodesRef.current; - }; - - const getFeedback = () => { - return feedbackRef.current; - }; - - const setFeedback = (message: MaybeString, helpKey: MaybeString = null) => { - setStateWithRef({ message, helpKey }, setFeedbackState, feedbackRef); - }; - - const resetFeedback = () => { - setStateWithRef(defaultFeedback, setFeedbackState, feedbackRef); - }; - - const setIsPaired = (p: PairingStatus) => { - setStateWithRef(p, setIsPairedState, isPairedRef); - }; - - const setIsExecuting = (val: boolean) => { - setStateWithRef(val, setIsExecutingState, isExecutingRef); - }; - - const resetStatusCodes = () => { - setStateWithRef([], setStatusCodes, statusCodesRef); - }; - - const handleUnmount = () => { - // reset refs - ledgerLoopInProgress.current = false; - pairInProgress.current = false; - // reset state - resetStatusCodes(); - setIsExecuting(false); - resetFeedback(); - // close transport - if (getTransport()?.device?.opened) { - getTransport().device.close(); - } - }; - - return ( - - {children} - - ); -}; - -export const LedgerHardwareContext = - React.createContext( - defaultLedgerHardwareContext - ); - -export const useLedgerHardware = () => React.useContext(LedgerHardwareContext); diff --git a/src/contexts/Hardware/Ledger/LedgerAccounts.tsx b/src/contexts/Hardware/Ledger/LedgerAccounts.tsx new file mode 100644 index 0000000000..c5172da5f7 --- /dev/null +++ b/src/contexts/Hardware/Ledger/LedgerAccounts.tsx @@ -0,0 +1,176 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { ReactNode } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import { useNetwork } from 'contexts/Network'; +import { ellipsisFn, setStateWithRef } from '@polkadot-cloud/utils'; +import type { LedgerAccount } from '@polkadot-cloud/react/types'; +import { useNotifications } from 'contexts/Notifications'; +import { useTranslation } from 'react-i18next'; +import type { LedgerAccountsContextInterface } from './types'; +import { defaultLedgerAccountsContext } from './defaults'; +import { + getLocalLedgerAccounts, + getLocalLedgerAddresses, + isLocalNetworkAddress, + renameLocalLedgerAddress, +} from '../Utils'; + +export const LedgerAccountsContext = + React.createContext( + defaultLedgerAccountsContext + ); + +export const LedgerAccountsProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const { t } = useTranslation('modals'); + const { network } = useNetwork(); + const { addNotification } = useNotifications(); + + // Store the fetched ledger accounts. + const [ledgerAccounts, setLedgerAccountsState] = useState( + getLocalLedgerAccounts(network) + ); + const ledgerAccountsRef = useRef(ledgerAccounts); + + // Check if a Ledger address exists in imported addresses. + const ledgerAccountExists = (address: string) => + !!getLocalLedgerAccounts().find((a) => + isLocalNetworkAddress(network, a, address) + ); + + // Adds a ledger address to the list of fetched addresses. + const addLedgerAccount = (address: string, index: number) => { + let newLedgerAccounts = getLocalLedgerAccounts(); + + const ledgerAddress = getLocalLedgerAddresses().find((a) => + isLocalNetworkAddress(network, a, address) + ); + + if ( + ledgerAddress && + !newLedgerAccounts.find((a) => isLocalNetworkAddress(network, a, address)) + ) { + const account = { + address, + network, + name: ledgerAddress.name, + source: 'ledger', + index, + }; + + // update the full list of local ledger accounts with new entry. + newLedgerAccounts = [...newLedgerAccounts].concat(account); + localStorage.setItem( + 'ledger_accounts', + JSON.stringify(newLedgerAccounts) + ); + + // store only those accounts on the current network in state. + setStateWithRef( + newLedgerAccounts.filter((a) => a.network === network), + setLedgerAccountsState, + ledgerAccountsRef + ); + + addNotification({ + title: t('ledgerAccountImported'), + subtitle: t('ledgerImportedAccount', { account: ellipsisFn(address) }), + }); + + return account; + } + return null; + }; + + // Removes a Ledger account from state and local storage. + const removeLedgerAccount = (address: string, notify: boolean = true) => { + const newLedgerAccounts = getLocalLedgerAccounts().filter((a) => { + if (a.address !== address) return true; + if (a.network !== network) return true; + return false; + }); + + if (!newLedgerAccounts.length) localStorage.removeItem('ledger_accounts'); + else + localStorage.setItem( + 'ledger_accounts', + JSON.stringify(newLedgerAccounts) + ); + + setStateWithRef( + newLedgerAccounts.filter((a) => a.network === network), + setLedgerAccountsState, + ledgerAccountsRef + ); + + if (notify) { + addNotification({ + title: t('ledgerAccountRemoved'), + subtitle: t('ledgerRemovedAccount', { account: ellipsisFn(address) }), + }); + } + }; + + // Renames an imported ledger account. + const renameLedgerAccount = (address: string, newName: string) => { + let newLedgerAccounts = getLocalLedgerAccounts(); + + newLedgerAccounts = newLedgerAccounts.map((a) => + isLocalNetworkAddress(network, a, address) + ? { + ...a, + name: newName, + } + : a + ); + renameLocalLedgerAddress(address, newName, network); + localStorage.setItem('ledger_accounts', JSON.stringify(newLedgerAccounts)); + setStateWithRef( + newLedgerAccounts.filter((a) => a.network === network), + setLedgerAccountsState, + ledgerAccountsRef + ); + }; + + // Gets an imported address along with its Ledger metadata. + const getLedgerAccount = (address: string) => { + const localLedgerAccounts = getLocalLedgerAccounts(); + if (!localLedgerAccounts) return null; + return ( + localLedgerAccounts.find((a) => + isLocalNetworkAddress(network, a, address) + ) ?? null + ); + }; + + // Refresh imported ledger accounts on network change. + useEffect(() => { + setStateWithRef( + getLocalLedgerAccounts(network), + setLedgerAccountsState, + ledgerAccountsRef + ); + }, [network]); + + return ( + + {children} + + ); +}; + +export const useLedgerAccounts = () => React.useContext(LedgerAccountsContext); diff --git a/src/contexts/Hardware/Ledger/LedgerHardware.tsx b/src/contexts/Hardware/Ledger/LedgerHardware.tsx new file mode 100644 index 0000000000..bf2bf58eb4 --- /dev/null +++ b/src/contexts/Hardware/Ledger/LedgerHardware.tsx @@ -0,0 +1,291 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { setStateWithRef } from '@polkadot-cloud/utils'; +import type { ReactNode } from 'react'; +import React, { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { AnyJson, MaybeString } from 'types'; +import { useApi } from 'contexts/Api'; +import { getLedgerErrorType } from '../Utils'; +import { defaultFeedback, defaultLedgerHardwareContext } from './defaults'; +import type { + FeedbackMessage, + HandleErrorFeedback, + LedgerHardwareContextInterface, + LedgerResponse, + LedgerStatusCode, +} from './types'; +import { Ledger } from './static/ledger'; + +export const LedgerHardwareContext = + React.createContext( + defaultLedgerHardwareContext + ); + +export const LedgerHardwareProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const { t } = useTranslation('modals'); + const { specVersion } = useApi().chainState.version; + + // Store whether a Ledger device task is in progress. + const [isExecuting, setIsExecutingState] = useState(false); + const isExecutingRef = useRef(isExecuting); + const getIsExecuting = () => isExecutingRef.current; + const setIsExecuting = (val: boolean) => + setStateWithRef(val, setIsExecutingState, isExecutingRef); + + // Store the latest status code received from a Ledger device. + const [statusCode, setStatusCodeState] = useState( + null + ); + const statusCodeRef = useRef(statusCode); + const getStatusCode = () => statusCodeRef.current; + const setStatusCode = (ack: string, newStatusCode: LedgerStatusCode) => { + setStateWithRef( + { ack, statusCode: newStatusCode }, + setStatusCodeState, + statusCodeRef + ); + }; + const resetStatusCode = () => + setStateWithRef(null, setStatusCodeState, statusCodeRef); + + // Store the feedback message to display as the Ledger device is being interacted with. + const [feedback, setFeedbackState] = + useState(defaultFeedback); + const feedbackRef = useRef(feedback); + const getFeedback = () => feedbackRef.current; + const setFeedback = (message: MaybeString, helpKey: MaybeString = null) => + setStateWithRef({ message, helpKey }, setFeedbackState, feedbackRef); + const resetFeedback = () => + setStateWithRef(defaultFeedback, setFeedbackState, feedbackRef); + + // Set feedback message and status code together. + const setStatusFeedback = ({ + code, + helpKey, + message, + }: HandleErrorFeedback) => { + setStatusCode('failure', code); + setFeedback(message, helpKey); + }; + + // Stores whether the Ledger device version has been checked. Used when signing transactions, not + // when addresses are being imported. + const [integrityChecked, setIntegrityChecked] = useState(false); + + // Store the latest successful device response. + const [transportResponse, setTransportResponse] = useState(null); + + // Whether the Ledger device metadata is for a different runtime. + const runtimesInconsistent = useRef(false); + + // Checks whether runtime version is inconsistent with device metadata. + const checkRuntimeVersion = async (appName: string) => { + try { + setIsExecuting(true); + const { app } = await Ledger.initialise(appName); + const result = await Ledger.getVersion(app); + + if (Ledger.isError(result)) { + throw new Error(result.error_message); + } + setIsExecuting(false); + resetFeedback(); + + if (result.minor < specVersion) runtimesInconsistent.current = true; + setIntegrityChecked(true); + } catch (err) { + handleErrors(appName, err); + } + }; + + // Gets an address from Ledger device. + const handleGetAddress = async (appName: string, accountIndex: number) => { + try { + setIsExecuting(true); + const { app, productName } = await Ledger.initialise(appName); + const result = await Ledger.getAddress(app, accountIndex); + + if (Ledger.isError(result)) { + throw new Error(result.error_message); + } + setIsExecuting(false); + setFeedback(t('successfullyFetchedAddress')); + setTransportResponse({ + ack: 'success', + statusCode: 'ReceivedAddress', + options: { + accountIndex, + }, + device: { productName }, + body: [result], + }); + } catch (err) { + handleErrors(appName, err); + } + }; + + // Signs a payload on Ledger device. + const handleSignTx = async ( + appName: string, + uid: number, + index: number, + payload: AnyJson + ) => { + try { + setIsExecuting(true); + const { app, productName } = await Ledger.initialise(appName); + setFeedback(t('approveTransactionLedger')); + + const result = await Ledger.signPayload(app, index, payload); + + if (Ledger.isError(result)) { + throw new Error(result.error_message); + } + setIsExecuting(false); + setFeedback(t('signedTransactionSuccessfully')); + setTransportResponse({ + statusCode: 'SignedPayload', + device: { productName }, + body: { + uid, + sig: result.signature, + }, + }); + } catch (err) { + handleErrors(appName, err); + } + }; + + // Handles errors that occur during device calls. + const handleErrors = (appName: string, err: unknown) => { + // Update feedback and status code state based on error received. + switch (getLedgerErrorType(String(err))) { + // Occurs when the device does not respond to a request within the timeout period. + case 'timeout': + setStatusFeedback({ + message: t('ledgerRequestTimeout'), + helpKey: 'Ledger Request Timeout', + code: 'DeviceTimeout', + }); + break; + // Occurs when a method in a all is not supported by the device. + case 'methodNotSupported': + setStatusFeedback({ + message: t('methodNotSupported'), + code: 'MethodNotSupported', + }); + break; + // Occurs when one or more of nested calls being signed does not support nesting. + case 'nestingNotSupported': + setStatusFeedback({ + message: t('missingNesting'), + code: 'NestingNotSupported', + }); + break; + // Cccurs when the device is not connected. + case 'deviceNotConnected': + setStatusFeedback({ + message: t('connectLedgerToContinue'), + code: 'DeviceNotConnected', + }); + break; + // Occurs when tx was approved outside of active channel. + case 'outsideActiveChannel': + setStatusFeedback({ + message: t('queuedTransactionRejected'), + helpKey: 'Wrong Transaction', + code: 'WrongTransaction', + }); + break; + // Occurs when the device is already in use. + case 'deviceBusy': + setStatusFeedback({ + message: t('ledgerDeviceBusy'), + code: 'DeviceBusy', + }); + break; + // Occurs when the device is locked. + case 'deviceLocked': + setStatusFeedback({ + message: t('unlockLedgerToContinue'), + code: 'DeviceLocked', + }); + break; + // Occurs when the app (e.g. Polkadot) is not open. + case 'appNotOpen': + setStatusFeedback({ + message: t('openAppOnLedger', { appName }), + helpKey: 'Open App On Ledger', + code: 'TransactionRejected', + }); + break; + // Occurs when a user rejects a transaction. + case 'transactionRejected': + setStatusFeedback({ + message: t('transactionRejectedPending'), + helpKey: 'Ledger Rejected Transaction', + code: 'AppNotOpen', + }); + break; + // Handle all other errors. + default: + setFeedback(t('openAppOnLedger', { appName }), 'Open App On Ledger'); + setStatusCode('failure', 'AppNotOpen'); + } + + // Reset refs. + runtimesInconsistent.current = false; + // Reset state. + setIsExecuting(false); + }; + + // Helper to reset ledger state when a task is completed or cancelled. + const handleResetLedgerTask = () => { + setIsExecuting(false); + resetStatusCode(); + resetFeedback(); + setIntegrityChecked(false); + runtimesInconsistent.current = false; + }; + + // Helper to reset ledger state when the a overlay connecting to the Ledger device unmounts. + const handleUnmount = () => { + Ledger.unmount(); + handleResetLedgerTask(); + }; + + return ( + + {children} + + ); +}; + +export const useLedgerHardware = () => React.useContext(LedgerHardwareContext); diff --git a/src/contexts/Hardware/Ledger/defaults.ts b/src/contexts/Hardware/Ledger/defaults.ts new file mode 100644 index 0000000000..8236dc1aee --- /dev/null +++ b/src/contexts/Hardware/Ledger/defaults.ts @@ -0,0 +1,47 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { + LedgerAccountsContextInterface, + LedgerHardwareContextInterface, +} from './types'; + +export const TotalAllowedStatusCodes = 50; + +export const defaultFeedback = { + message: null, + helpKey: null, +}; + +export const defaultLedgerHardwareContext: LedgerHardwareContextInterface = { + transportResponse: null, + integrityChecked: false, + setIntegrityChecked: (checked) => {}, + checkRuntimeVersion: async (appName) => new Promise((resolve) => resolve()), + setStatusCode: (a, s) => {}, + setIsExecuting: (b) => {}, + getIsExecuting: () => false, + getStatusCode: () => null, + resetStatusCode: () => {}, + getFeedback: () => defaultFeedback, + setFeedback: (s, h) => {}, + resetFeedback: () => {}, + handleUnmount: () => {}, + handleErrors: (appName, err) => {}, + handleGetAddress: (appName, accountIndex) => + new Promise((resolve) => resolve()), + handleSignTx: (appName, uid, index, payload) => + new Promise((resolve) => resolve()), + handleResetLedgerTask: () => {}, + runtimesInconsistent: false, +}; + +export const defaultLedgerAccountsContext: LedgerAccountsContextInterface = { + ledgerAccountExists: (a) => false, + addLedgerAccount: (a, i) => null, + removeLedgerAccount: (a, n) => {}, + renameLedgerAccount: (a, n) => {}, + getLedgerAccount: (a) => null, + ledgerAccounts: [], +}; diff --git a/src/contexts/Hardware/Ledger/static/ledger.ts b/src/contexts/Hardware/Ledger/static/ledger.ts new file mode 100644 index 0000000000..d45520edae --- /dev/null +++ b/src/contexts/Hardware/Ledger/static/ledger.ts @@ -0,0 +1,104 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import TransportWebHID from '@ledgerhq/hw-transport-webhid'; +import type { AnyJson } from '@polkadot-cloud/react/types'; +import { newSubstrateApp, type SubstrateApp } from '@zondax/ledger-substrate'; +import type { AnyFunction } from 'types'; +import { u8aToBuffer } from '@polkadot/util'; + +const LEDGER_DEFAULT_ACCOUNT = 0x80000000; +const LEDGER_DEFAULT_CHANGE = 0x80000000; +const LEDGER_DEFAULT_INDEX = 0x80000000; + +export class Ledger { + // The ledger device transport. `null` when not actively in use. + static transport: AnyJson | null; + + // Whether the device is currently paired. + static isPaired: boolean = false; + + // Initialise ledger transport, initialise app, and return with device info. + static initialise = async (appName: string) => { + this.transport = await TransportWebHID.create(); + const app = newSubstrateApp(Ledger.transport, appName); + const { productName } = this.transport.device; + return { app, productName }; + }; + + // Ensure transport is closed. + static ensureClosed = async () => { + if (this.transport?.device?.opened) await this.transport?.close(); + }; + + // Ensure transport is open. + static ensureOpen = async () => { + if (!this.transport?.device?.opened) await this.transport?.open(); + }; + + // Check if a response is an error. + static isError = (result: AnyJson) => { + const error = result?.error_message; + if (error) if (!error.startsWith('No errors')) return true; + return false; + }; + + // Gets device runtime version. + static getVersion = async (app: SubstrateApp) => { + await this.ensureOpen(); + const result: AnyJson = await this.withTimeout(3000, app.getVersion()); + await this.ensureClosed(); + return result; + }; + + // Gets an address from transport. + static getAddress = async (app: SubstrateApp, index: number) => { + await this.ensureOpen(); + const result = await this.withTimeout( + 3000, + app.getAddress( + LEDGER_DEFAULT_ACCOUNT + index, + LEDGER_DEFAULT_CHANGE, + LEDGER_DEFAULT_INDEX + 0, + false + ) + ); + await this.ensureClosed(); + return result; + }; + + // Signs a payload on device. + static signPayload = async ( + app: SubstrateApp, + index: number, + payload: AnyJson + ) => { + await this.ensureOpen(); + const result = await app.sign( + LEDGER_DEFAULT_ACCOUNT + index, + LEDGER_DEFAULT_CHANGE, + LEDGER_DEFAULT_INDEX + 0, + u8aToBuffer(payload.toU8a(true)) + ); + await this.ensureClosed(); + return result; + }; + + // Helper to time out a promise after a specified number of milliseconds. + static withTimeout = (ms: number, promise: AnyFunction) => { + const timeout = new Promise((_, reject) => + setTimeout(async () => { + this.transport?.close(); + reject(Error('Timeout')); + }, ms) + ); + return Promise.race([promise, timeout]); + }; + + // Reset ledger on unmount. + static unmount = async () => { + await this.transport?.close(); + this.transport = null; + this.isPaired = false; + }; +} diff --git a/src/contexts/Hardware/types.ts b/src/contexts/Hardware/Ledger/types.ts similarity index 66% rename from src/contexts/Hardware/types.ts rename to src/contexts/Hardware/Ledger/types.ts index 0785085953..c5c9bbcf6c 100644 --- a/src/contexts/Hardware/types.ts +++ b/src/contexts/Hardware/Ledger/types.ts @@ -1,36 +1,43 @@ // Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { LedgerAccount, VaultAccount } from '@polkadot-cloud/react/types'; +import type { LedgerAccount } from '@polkadot-cloud/react/types'; import type { FunctionComponent, SVGProps } from 'react'; import type { AnyJson, MaybeString, NetworkName } from 'types'; export type LedgerHardwareContextInterface = { - pairDevice: () => Promise; - setIsPaired: (v: PairingStatus) => void; + integrityChecked: boolean; + setIntegrityChecked: (checked: boolean) => void; + checkRuntimeVersion: (appName: string) => Promise; transportResponse: AnyJson; - executeLedgerLoop: ( - appName: string, - tasks: LedgerTask[], - options?: AnyJson - ) => Promise; - handleNewStatusCode: (ack: string, statusCode: LedgerStatusCode) => void; + setStatusCode: (ack: string, statusCode: LedgerStatusCode) => void; setIsExecuting: (v: boolean) => void; - resetStatusCodes: () => void; + getStatusCode: () => LedgerResponse | null; + resetStatusCode: () => void; getIsExecuting: () => boolean; - getStatusCodes: () => LedgerResponse[]; - getTransport: () => AnyJson; + getFeedback: () => FeedbackMessage; + setFeedback: (s: MaybeString, helpKey?: MaybeString) => void; + resetFeedback: () => void; + handleUnmount: () => void; + handleErrors: (appName: string, err: unknown) => void; + runtimesInconsistent: boolean; + handleGetAddress: (appName: string, accountIndex: number) => Promise; + handleSignTx: ( + appName: string, + uid: number, + index: number, + payload: AnyJson + ) => Promise; + handleResetLedgerTask: () => void; +}; + +export type LedgerAccountsContextInterface = { ledgerAccountExists: (a: string) => boolean; addLedgerAccount: (a: string, i: number) => LedgerAccount | null; - removeLedgerAccount: (a: string) => void; + removeLedgerAccount: (a: string, n?: boolean) => void; renameLedgerAccount: (a: string, name: string) => void; getLedgerAccount: (a: string) => LedgerAccount | null; - isPaired: PairingStatus; ledgerAccounts: LedgerAccount[]; - getFeedback: () => FeedbackMessage; - setFeedback: (s: MaybeString, helpKey?: MaybeString) => void; - resetFeedback: () => void; - handleUnmount: () => void; }; export interface FeedbackMessage { @@ -43,7 +50,9 @@ export type LedgerStatusCode = | 'ReceivedAddress' | 'SigningPayload' | 'SignedPayload' + | 'DeviceBusy' | 'DeviceTimeout' + | 'MethodNotSupported' | 'NestingNotSupported' | 'WrongTransaction' | 'DeviceNotConnected' @@ -77,11 +86,8 @@ export type LedgerApp = { Icon: FunctionComponent>; }; -export type VaultHardwareContextInterface = { - vaultAccountExists: (a: string) => boolean; - addVaultAccount: (a: string, i: number) => LedgerAccount | null; - removeVaultAccount: (a: string) => void; - renameVaultAccount: (a: string, name: string) => void; - getVaultAccount: (a: string) => LedgerAccount | null; - vaultAccounts: VaultAccount[]; -}; +export interface HandleErrorFeedback { + message: MaybeString; + helpKey?: MaybeString; + code: LedgerStatusCode; +} diff --git a/src/contexts/Hardware/Utils.tsx b/src/contexts/Hardware/Utils.tsx index c78de28dfc..93e4071e82 100644 --- a/src/contexts/Hardware/Utils.tsx +++ b/src/contexts/Hardware/Utils.tsx @@ -5,7 +5,39 @@ import { localStorageOrDefault } from '@polkadot-cloud/utils'; import { LedgerApps } from 'config/ledger'; import type { MaybeString } from 'types'; import type { LedgerAccount, VaultAccount } from '@polkadot-cloud/react/types'; -import type { LedgerAddress } from './types'; +import type { LedgerAddress } from './Ledger/types'; + +// Ledger error keyed by type of error. +const LedgerErrorsByType = { + timeout: ['Error: Timeout'], + methodNotSupported: ['Error: Method not supported'], + nestingNotSupported: ['Error: Call nesting not supported'], + outsideActiveChannel: ['Error: TransportError: Invalid channel'], + deviceNotConnected: ['TransportOpenUserCancelled'], + deviceBusy: ['Error: Ledger Device is busy', 'InvalidStateError'], + deviceLocked: ['Error: LockedDeviceError'], + transactionRejected: ['Error: Transaction rejected'], + appNotOpen: ['Error: Unknown Status Code: 28161'], +}; + +// Determine type of error returned by Ledger. +export const getLedgerErrorType = (err: string) => { + let errorType = null; + Object.entries(LedgerErrorsByType).every(([type, errors]) => { + let found = false; + errors.every((e) => { + if (err.startsWith(e)) { + errorType = type; + found = true; + return false; + } + return true; + }); + if (found) return false; + return true; + }); + return errorType || 'misc'; +}; // Gets ledger app from local storage, fallback to first entry. export const getLedgerApp = (network: string) => { @@ -26,7 +58,7 @@ export const getLocalLedgerAddresses = (network?: string) => { }; // Gets imported Ledger accounts from local storage. -export const getLocalLedgerAccounts = (network?: string) => { +export const getLocalLedgerAccounts = (network?: string): LedgerAccount[] => { const localAddresses = localStorageOrDefault( 'ledger_accounts', [], @@ -38,6 +70,27 @@ export const getLocalLedgerAccounts = (network?: string) => { : localAddresses; }; +// Renames a record from local ledger addresses. +export const renameLocalLedgerAddress = ( + address: string, + name: string, + network: string +) => { + const localLedger = ( + localStorageOrDefault('ledger_addresses', [], true) as LedgerAddress[] + )?.map((i) => + !(i.address === address && i.network === network) + ? i + : { + ...i, + name, + } + ); + if (localLedger) { + localStorage.setItem('ledger_addresses', JSON.stringify(localLedger)); + } +}; + // Gets imported Vault accounts from local storage. export const getLocalVaultAccounts = (network?: string) => { const localAddresses = localStorageOrDefault( diff --git a/src/contexts/Hardware/Vault.tsx b/src/contexts/Hardware/Vault/VaultAccounts.tsx similarity index 84% rename from src/contexts/Hardware/Vault.tsx rename to src/contexts/Hardware/Vault/VaultAccounts.tsx index 35acc77792..5d6c714c7d 100644 --- a/src/contexts/Hardware/Vault.tsx +++ b/src/contexts/Hardware/Vault/VaultAccounts.tsx @@ -2,17 +2,29 @@ // SPDX-License-Identifier: GPL-3.0-only import { ellipsisFn, setStateWithRef } from '@polkadot-cloud/utils'; -import React, { useEffect, useRef, useState } from 'react'; +import type { ReactNode } from 'react'; +import React, { + createContext, + useContext, + useEffect, + useRef, + useState, +} from 'react'; import type { VaultAccount } from '@polkadot-cloud/react/types'; import { useNetwork } from 'contexts/Network'; -import { getLocalVaultAccounts, isLocalNetworkAddress } from './Utils'; -import { defaultVaultHardwareContext } from './defaults'; -import type { VaultHardwareContextInterface } from './types'; +import { getLocalVaultAccounts, isLocalNetworkAddress } from '../Utils'; +import type { VaultAccountsContextInterface } from './types'; +import { defaultVaultAccountsContext } from './defaults'; -export const VaultHardwareProvider = ({ +export const VaultAccountsContext = + createContext(defaultVaultAccountsContext); + +export const useVaultAccounts = () => useContext(VaultAccountsContext); + +export const VaultAccountsProvider = ({ children, }: { - children: React.ReactNode; + children: ReactNode; }) => { const { network } = useNetwork(); @@ -131,7 +143,7 @@ export const VaultHardwareProvider = ({ }, [network]); return ( - {children} - + ); }; - -export const VaultHardwareContext = - React.createContext( - defaultVaultHardwareContext - ); - -export const useVaultHardware = () => React.useContext(VaultHardwareContext); diff --git a/src/contexts/Hardware/Vault/defaults.ts b/src/contexts/Hardware/Vault/defaults.ts new file mode 100644 index 0000000000..8c7e293005 --- /dev/null +++ b/src/contexts/Hardware/Vault/defaults.ts @@ -0,0 +1,14 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import type { VaultAccountsContextInterface } from './types'; + +export const defaultVaultAccountsContext: VaultAccountsContextInterface = { + vaultAccountExists: (a) => false, + addVaultAccount: (a, i) => null, + removeVaultAccount: (a) => {}, + renameVaultAccount: (a, n) => {}, + getVaultAccount: (a) => null, + vaultAccounts: [], +}; diff --git a/src/contexts/Hardware/Vault/types.ts b/src/contexts/Hardware/Vault/types.ts new file mode 100644 index 0000000000..31d089f590 --- /dev/null +++ b/src/contexts/Hardware/Vault/types.ts @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { LedgerAccount, VaultAccount } from '@polkadot-cloud/react/types'; + +export type VaultAccountsContextInterface = { + vaultAccountExists: (a: string) => boolean; + addVaultAccount: (a: string, i: number) => LedgerAccount | null; + removeVaultAccount: (a: string) => void; + renameVaultAccount: (a: string, name: string) => void; + getVaultAccount: (a: string) => LedgerAccount | null; + vaultAccounts: VaultAccount[]; +}; diff --git a/src/contexts/Hardware/defaults.ts b/src/contexts/Hardware/defaults.ts deleted file mode 100644 index af4bab8bef..0000000000 --- a/src/contexts/Hardware/defaults.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: GPL-3.0-only -/* eslint-disable @typescript-eslint/no-unused-vars */ - -import type { - LedgerHardwareContextInterface, - VaultHardwareContextInterface, -} from './types'; - -export const TOTAL_ALLOWED_STATUS_CODES = 50; -export const LEDGER_DEFAULT_ACCOUNT = 0x80000000; -export const LEDGER_DEFAULT_CHANGE = 0x80000000; -export const LEDGER_DEFAULT_INDEX = 0x80000000; - -export const defaultFeedback = { - message: null, - helpKey: null, -}; - -export const defaultLedgerHardwareContext: LedgerHardwareContextInterface = { - transportResponse: null, - pairDevice: async () => new Promise((resolve) => resolve(false)), - executeLedgerLoop: async (a, s, o) => new Promise((resolve) => resolve()), - setIsPaired: (v) => {}, - handleNewStatusCode: (a, s) => {}, - setIsExecuting: (b) => {}, - resetStatusCodes: () => {}, - getIsExecuting: () => false, - getStatusCodes: () => [], - getTransport: () => null, - ledgerAccountExists: (a) => false, - addLedgerAccount: (a, i) => null, - removeLedgerAccount: (a) => {}, - renameLedgerAccount: (a, n) => {}, - getLedgerAccount: (a) => null, - isPaired: 'unknown', - ledgerAccounts: [], - getFeedback: () => defaultFeedback, - setFeedback: (s, h) => {}, - resetFeedback: () => {}, - handleUnmount: () => {}, -}; - -export const defaultVaultHardwareContext: VaultHardwareContextInterface = { - vaultAccountExists: (a) => false, - addVaultAccount: (a, i) => null, - removeVaultAccount: (a) => {}, - renameVaultAccount: (a, n) => {}, - getVaultAccount: (a) => null, - vaultAccounts: [], -}; diff --git a/src/contexts/Migrate/index.tsx b/src/contexts/Migrate/index.tsx index 4f5713ff4e..138a83870d 100644 --- a/src/contexts/Migrate/index.tsx +++ b/src/contexts/Migrate/index.tsx @@ -8,6 +8,8 @@ import { useApi } from 'contexts/Api'; import { useUi } from 'contexts/UI'; import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { localStorageOrDefault } from '@polkadot-cloud/utils'; +import type { ExternalAccount } from '@polkadot-cloud/react/types'; export const MigrateProvider = ({ children, @@ -44,6 +46,22 @@ export const MigrateProvider = ({ localStorage.removeItem(`${n.name}_active_proxy`); }); + // Removes `system` added external accounts from local storage. + const removeSystemExternalAccounts = () => { + const current = localStorageOrDefault('external_accounts', [], true); + if (!current.length) return; + + const updated = + (current as ExternalAccount[])?.filter((a) => a.addedBy !== 'system') || + []; + + if (!updated.length) { + localStorage.removeItem('external_accounts'); + } else { + localStorage.setItem('external_accounts', JSON.stringify(updated)); + } + }; + useEffectIgnoreInitial(() => { if (isReady && !isNetworkSyncing && !done) { // Carry out migrations if local version is different to current version. @@ -64,6 +82,11 @@ export const MigrateProvider = ({ // Remove legacy local active proxy records. removeDeprecatedActiveProxies(); + // Added in 1.1.2 + // + // Remove local `system` external accounts. + removeSystemExternalAccounts(); + // Finally, // // Update local version to current app version. @@ -71,7 +94,7 @@ export const MigrateProvider = ({ setDone(true); } } - }, [isNetworkSyncing]); + }, [isReady, isNetworkSyncing]); return ( {children} diff --git a/src/contexts/NetworkMetrics/index.tsx b/src/contexts/NetworkMetrics/index.tsx index 725c3f24a0..e7e067d7b8 100644 --- a/src/contexts/NetworkMetrics/index.tsx +++ b/src/contexts/NetworkMetrics/index.tsx @@ -101,7 +101,7 @@ export const NetworkMetricsProvider = ({ // initiate subscription, add to unsubs. await Promise.all([subscribeToMetrics(), subscribeToActiveEra()]).then( - (u: any) => { + (u) => { unsubsRef.current = unsubsRef.current.concat(u); } ); diff --git a/src/contexts/Pools/ActivePools/index.tsx b/src/contexts/Pools/ActivePools/index.tsx index a0fa72a39d..e5878959d2 100644 --- a/src/contexts/Pools/ActivePools/index.tsx +++ b/src/contexts/Pools/ActivePools/index.tsx @@ -91,6 +91,7 @@ export const ActivePoolsProvider = ({ const p = membership?.poolId ? String(membership.poolId) : '0'; return String(a.id) === p; }) || null; + const getSelectedActivePool = () => activePoolsRef.current.find((a) => a.id === Number(selectedPoolId)) || null; diff --git a/src/contexts/Pools/BondedPools/defaults.ts b/src/contexts/Pools/BondedPools/defaults.ts index 0074e1771c..6da43eb4d0 100644 --- a/src/contexts/Pools/BondedPools/defaults.ts +++ b/src/contexts/Pools/BondedPools/defaults.ts @@ -5,7 +5,6 @@ import type { BondedPoolsContextState } from '../types'; export const defaultBondedPoolsContext: BondedPoolsContextState = { - fetchPoolsMetaBatch: (k, v: [], r) => {}, queryBondedPool: (p) => {}, getBondedPool: (p) => null, updateBondedPools: (p) => {}, @@ -16,7 +15,9 @@ export const defaultBondedPoolsContext: BondedPoolsContextState = { getAccountRoles: (w) => null, getAccountPools: (w) => null, replacePoolRoles: (p, e) => {}, - poolSearchFilter: (l, k, v) => {}, + poolSearchFilter: (l, v) => {}, bondedPools: [], - meta: {}, + poolsMetaData: {}, + poolsNominations: {}, + updatePoolNominations: (id, nominations) => {}, }; diff --git a/src/contexts/Pools/BondedPools/index.tsx b/src/contexts/Pools/BondedPools/index.tsx index df6330040e..9975a3ff7a 100644 --- a/src/contexts/Pools/BondedPools/index.tsx +++ b/src/contexts/Pools/BondedPools/index.tsx @@ -2,18 +2,21 @@ // SPDX-License-Identifier: GPL-3.0-only import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; -import { setStateWithRef, shuffle } from '@polkadot-cloud/utils'; +import { rmCommas, shuffle } from '@polkadot-cloud/utils'; import React, { useRef, useState } from 'react'; import type { BondedPool, BondedPoolsContextState, MaybePool, NominationStatuses, + PoolNominations, } from 'contexts/Pools/types'; import { useStaking } from 'contexts/Staking'; -import type { AnyApi, AnyMetaBatch, Fn, MaybeAddress } from 'types'; +import type { AnyApi, MaybeAddress, Sync } from 'types'; import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import { useNetwork } from 'contexts/Network'; +import { useNetworkMetrics } from 'contexts/NetworkMetrics'; +import type { AnyJson } from '@polkadot-cloud/react/types'; import { useApi } from '../../Api'; import { usePoolsConfig } from '../PoolsConfig'; import { defaultBondedPoolsContext } from './defaults'; @@ -25,43 +28,88 @@ export const BondedPoolsProvider = ({ }) => { const { network } = useNetwork(); const { api, isReady } = useApi(); + const { activeEra } = useNetworkMetrics(); const { createAccounts, stats } = usePoolsConfig(); const { getNominationsStatusFromTargets } = useStaking(); const { lastPoolId } = stats; - // Stores the meta data batches for pool lists. - const [poolMetaBatches, setPoolMetaBatch]: AnyMetaBatch = useState({}); - const poolMetaBatchesRef = useRef(poolMetaBatches); - - // Stores the meta batch subscriptions for pool lists. - const poolSubs = useRef>({}); - // Store bonded pools. const [bondedPools, setBondedPools] = useState([]); - const unsubscribe = () => { - Object.values(poolSubs.current).map((batch: Fn[]) => - Object.entries(batch).map(([, v]) => v()) - ); - setBondedPools([]); - }; + // Track the sync status of `bondedPools`. + const bondedPoolsSynced = useRef('unsynced'); - // fetch all bonded pool entries - const fetchBondedPools = async () => { - if (!api) return; + // Store bonded pools metadata. + const [poolsMetaData, setPoolsMetadata] = useState>( + {} + ); + + // Store bonded pools nominations. + const [poolsNominations, setPoolsNominations] = useState< + Record + >({}); - const result = await api.query.nominationPools.bondedPools.entries(); - let exposures = result.map(([_keys, _val]: AnyApi) => { - const id = _keys.toHuman()[0]; - const pool = _val.toHuman(); - return getPoolWithAddresses(id, pool); + // Fetch all bonded pool entries and their metadata. + const fetchBondedPools = async () => { + if (!api || bondedPoolsSynced.current !== 'unsynced') return; + bondedPoolsSynced.current = 'syncing'; + + const ids: number[] = []; + + // Fetch bonded pools entries. + const bondedPoolsMulti = + await api.query.nominationPools.bondedPools.entries(); + let exposures = bondedPoolsMulti.map(([keys, val]: AnyApi) => { + const id = keys.toHuman()[0]; + ids.push(id); + return getPoolWithAddresses(id, val.toHuman()); }); exposures = shuffle(exposures); setBondedPools(exposures); + + // Fetch pools metadata. + const metadataMulti = await api.query.nominationPools.metadata.multi(ids); + setPoolsMetadata( + Object.fromEntries( + metadataMulti.map((m, i) => [ids[i], String(m.toHuman())]) + ) + ); + + bondedPoolsSynced.current = 'synced'; + }; + + // Fetches pool nominations and updates state. + const fetchPoolsNominations = async () => { + if (!api) return; + + const ids: number[] = []; + const nominationsMulti = await api.query.staking.nominators.multi( + bondedPools.map(({ addresses, id }) => { + ids.push(id); + return addresses.stash; + }) + ); + setPoolsNominations(formatPoolsNominations(nominationsMulti, ids)); }; - // queries a bonded pool and injects ID and addresses to a result. + // Format raw pool nominations data. + const formatPoolsNominations = (raw: AnyJson, ids: number[]) => + Object.fromEntries( + raw.map((n: AnyJson, i: number) => { + const human = n.toHuman() as PoolNominations; + if (!human) return [ids[i], null]; + return [ + ids[i], + { + ...human, + submittedIn: rmCommas(human.submittedIn), + }, + ]; + }) + ); + + // Queries a bonded pool and injects ID and addresses to a result. const queryBondedPool = async (id: number) => { if (!api) return null; @@ -79,137 +127,23 @@ export const BondedPoolsProvider = ({ }; }; - /* - Fetches a new batch of pool metadata. - Fetches the metadata of a pool that we assume to be a string. - structure: - { - key: { - [ - { - metadata: [], - } - ] - }, - }; - */ - const fetchPoolsMetaBatch = async ( - key: string, - p: AnyMetaBatch, - refetch = false - ) => { - if (!isReady || !api || !p.length) return; - - if (!refetch) { - // if already exists, do not re-fetch - if (poolMetaBatchesRef.current[key] !== undefined) { - return; - } - } else { - // tidy up if existing batch exists - const updatedPoolMetaBatches: AnyMetaBatch = { - ...poolMetaBatchesRef.current, - }; - delete updatedPoolMetaBatches[key]; - setStateWithRef( - updatedPoolMetaBatches, - setPoolMetaBatch, - poolMetaBatchesRef - ); - - if (poolSubs.current[key] !== undefined) { - for (const unsub of poolSubs.current[key]) { - unsub(); - } - } - } - - // aggregate pool ids and addresses - const ids = []; - const addresses = []; - for (const pool of p) { - ids.push(Number(pool.id)); - - if (pool?.addresses?.stash) { - addresses.push(pool.addresses.stash); - } - } - - // store batch ids - const batchesUpdated = Object.assign(poolMetaBatchesRef.current); - batchesUpdated[key] = {}; - batchesUpdated[key].ids = ids; - setStateWithRef( - { ...batchesUpdated }, - setPoolMetaBatch, - poolMetaBatchesRef - ); - - const subscribeToMetadata = async (_ids: AnyApi) => { - const unsub = await api.query.nominationPools.metadata.multi( - _ids, - (_metadata: AnyApi) => { - const metadata = []; - for (let i = 0; i < _metadata.length; i++) { - metadata.push(_metadata[i].toHuman()); - } - const updated = Object.assign(poolMetaBatchesRef.current); - updated[key].metadata = metadata; - - setStateWithRef({ ...updated }, setPoolMetaBatch, poolMetaBatchesRef); - } - ); - return unsub; - }; - - const subscribeToNominations = async (_addresses: AnyApi) => { - const unsub = await api.query.staking.nominators.multi( - _addresses, - (_nominations: AnyApi) => { - const nominations = []; - for (let i = 0; i < _nominations.length; i++) { - nominations.push(_nominations[i].toHuman()); - } - const updated = Object.assign(poolMetaBatchesRef.current); - updated[key].nominations = nominations; - setStateWithRef({ ...updated }, setPoolMetaBatch, poolMetaBatchesRef); - } - ); - return unsub; - }; - - // initiate subscriptions - await Promise.all([ - subscribeToMetadata(ids), - subscribeToNominations(addresses), - ]).then((unsubs: Fn[]) => { - addMetaBatchUnsubs(key, unsubs); - }); - }; - - /* - * Get bonded pool nomination statuses - */ + // Get bonded pool nomination statuses const getPoolNominationStatus = ( nominator: MaybeAddress, nomination: MaybeAddress ) => { const pool = bondedPools.find((p: any) => p.addresses.stash === nominator); - if (!pool) { - return 'waiting'; - } - // get pool targets from nominations metadata - const batchIndex = bondedPools.indexOf(pool); - const nominations = poolMetaBatches.bonded_pools?.nominations ?? []; - const targets = nominations[batchIndex]?.targets ?? []; + if (!pool) return 'waiting'; - const target = targets.find((t: string) => t === nomination); + // get pool targets from nominations metadata + const nominations = poolsNominations[pool.id]; + const targets = nominations ? nominations.targets : []; + const target = targets.find((t) => t === nomination); const nominationStatus = getNominationsStatusFromTargets(nominator, [ target, ]); - return getPoolNominationStatusCode(nominationStatus); }; @@ -233,18 +167,6 @@ export const BondedPoolsProvider = ({ return status; }; - /* - * Helper: to add mataBatch unsubs by key. - */ - const addMetaBatchUnsubs = (key: string, unsubs: Fn[]) => { - const newUnsubs = poolSubs.current; - const newUnsubItem = newUnsubs[key] ?? []; - - newUnsubItem.push(...unsubs); - newUnsubs[key] = newUnsubItem; - poolSubs.current = newUnsubs; - }; - /* * Helper: to add addresses to pool record. */ @@ -255,69 +177,40 @@ export const BondedPoolsProvider = ({ }); const getBondedPool = (poolId: MaybePool) => - bondedPools.find((p) => p.id === String(poolId)) ?? null; + bondedPools.find((p) => p.id === poolId) ?? null; /* - * poolSearchFilter - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that match - * the search term. - * Returns the updated filtered list. + * poolSearchFilter Iterates through the supplied list and refers to the meta batch of the list to + * filter those list items that match the search term. Returns the updated filtered list. */ - const poolSearchFilter = ( - list: any, - batchKey: string, - searchTerm: string - ) => { - const meta = poolMetaBatchesRef.current; - - if (meta[batchKey] === undefined) { - return list; - } + const poolSearchFilter = (list: any, searchTerm: string) => { const filteredList: any = []; for (const pool of list) { - const batchIndex = meta[batchKey].ids?.indexOf(Number(pool.id)) ?? -1; - - // if we cannot derive data, fallback to include pool in filtered list - if (batchIndex === -1) { + // If pool metadata has not yet been synced, include the pool in results. + if (!Object.values(poolsMetaData).length) { filteredList.push(pool); continue; } - const ids = meta[batchKey].ids ?? false; - const metadatas = meta[batchKey]?.metadata ?? false; - - if (!metadatas || !ids) { - filteredList.push(pool); - continue; - } - - const id = ids[batchIndex] ?? 0; const address = pool?.addresses?.stash ?? ''; - const metadata = metadatas[batchIndex] ?? ''; - + const metadata = poolsMetaData[pool.id] || ''; const metadataAsBytes = u8aToString(u8aUnwrapBytes(metadata)); const metadataSearch = ( metadataAsBytes === '' ? metadata : metadataAsBytes ).toLowerCase(); - if (String(id).includes(searchTerm.toLowerCase())) { + if (pool.id.includes(searchTerm.toLowerCase())) filteredList.push(pool); + if (address.toLowerCase().includes(searchTerm.toLowerCase())) filteredList.push(pool); - } - if (address.toLowerCase().includes(searchTerm.toLowerCase())) { - filteredList.push(pool); - } - if (metadataSearch.includes(searchTerm.toLowerCase())) { + if (metadataSearch.includes(searchTerm.toLowerCase())) filteredList.push(pool); - } } return filteredList; }; const updateBondedPools = (updatedPools: BondedPool[]) => { if (!updatedPools) return; - setBondedPools( bondedPools.map( (original) => @@ -326,6 +219,22 @@ export const BondedPoolsProvider = ({ ); }; + const updatePoolNominations = (id: number, newTargets: string[]) => { + const newPoolsNominations = { ...poolsNominations }; + + let record = newPoolsNominations?.[id]; + if (record !== null) { + record.targets = newTargets; + } else { + record = { + submittedIn: activeEra.index.toString(), + targets: newTargets, + suppressed: false, + }; + } + setPoolsNominations(newPoolsNominations); + }; + const removeFromBondedPools = (id: number) => { setBondedPools(bondedPools.filter((b: BondedPool) => b.id !== id)); }; @@ -409,7 +318,7 @@ export const BondedPoolsProvider = ({ // replaces the pool roles from roleEdits const replacePoolRoles = (poolId: number, roleEdits: any) => { - let pool = bondedPools.find((b) => String(b.id) === String(poolId)) || null; + let pool = bondedPools.find((b) => b.id === poolId) || null; if (!pool) return; @@ -422,9 +331,7 @@ export const BondedPoolsProvider = ({ }; const newBondedPools = [ - ...bondedPools.map((b) => - String(b.id) === String(poolId) && pool !== null ? pool : b - ), + ...bondedPools.map((b) => (b.id === poolId && pool !== null ? pool : b)), ]; setBondedPools(newBondedPools); @@ -432,28 +339,27 @@ export const BondedPoolsProvider = ({ // Clear existing state for network refresh. useEffectIgnoreInitial(() => { + bondedPoolsSynced.current = 'unsynced'; setBondedPools([]); - setStateWithRef({}, setPoolMetaBatch, poolMetaBatchesRef); + setPoolsMetadata({}); + setPoolsNominations({}); }, [network]); // Initial setup for fetching bonded pools. useEffectIgnoreInitial(() => { - if (isReady) fetchBondedPools(); - return () => { - unsubscribe(); - }; - }, [network, isReady, lastPoolId]); + if (isReady && lastPoolId) fetchBondedPools(); + }, [bondedPools, isReady, lastPoolId]); - // After bonded pools have synced, fetch metabatch. + // Re-fetch bonded pools nominations when active era changes or when `bondedPools` update. useEffectIgnoreInitial(() => { - if (bondedPools.length) - fetchPoolsMetaBatch('bonded_pools', bondedPools, true); - }, [bondedPools]); + if (!activeEra.index.isZero() && bondedPools.length) { + fetchPoolsNominations(); + } + }, [activeEra.index, bondedPools.length]); return ( {children} diff --git a/src/contexts/Pools/PoolMembers/index.tsx b/src/contexts/Pools/PoolMembers/index.tsx index bc3e9af98c..4772c53691 100644 --- a/src/contexts/Pools/PoolMembers/index.tsx +++ b/src/contexts/Pools/PoolMembers/index.tsx @@ -97,7 +97,7 @@ export const PoolMembersProvider = ({ }; const getMembersOfPoolFromNode = (poolId: number) => - poolMembersNode.filter((p: any) => p.poolId === String(poolId)) ?? null; + poolMembersNode.filter((p) => p.poolId === poolId) ?? null; // queries a pool member and formats to `PoolMember`. const queryPoolMember = async (who: MaybeAddress) => { diff --git a/src/contexts/Pools/types.ts b/src/contexts/Pools/types.ts index f8c068cff4..a89bb1cfae 100644 --- a/src/contexts/Pools/types.ts +++ b/src/contexts/Pools/types.ts @@ -60,7 +60,6 @@ export interface PoolMembership { // BondedPool types export interface BondedPoolsContextState { - fetchPoolsMetaBatch: (k: string, v: [], r?: boolean) => void; queryBondedPool: (p: number) => any; getBondedPool: (p: number) => BondedPool | null; updateBondedPools: (p: BondedPool[]) => void; @@ -71,9 +70,11 @@ export interface BondedPoolsContextState { getAccountRoles: (w: MaybeAddress) => any; getAccountPools: (w: MaybeAddress) => any; replacePoolRoles: (poolId: number, roleEdits: AnyJson) => void; - poolSearchFilter: (l: any, k: string, v: string) => void; + poolSearchFilter: (l: any, v: string) => void; bondedPools: BondedPool[]; - meta: AnyMetaBatch; + poolsMetaData: Record; + poolsNominations: Record; + updatePoolNominations: (id: number, nominations: string[]) => void; } export interface ActivePool { @@ -87,7 +88,7 @@ export interface ActivePool { export interface BondedPool { addresses: PoolAddresses; - id: number | string; + id: number; memberCounter: string; points: string; roles: { @@ -108,6 +109,12 @@ export interface BondedPool { }; } +export type PoolNominations = { + submittedIn: string; + suppressed: boolean; + targets: string[]; +} | null; + export type NominationStatuses = Record; export interface ActivePoolsContextState { diff --git a/src/contexts/Proxies/defaults.ts b/src/contexts/Proxies/defaults.ts index f7aaab8608..f45dec2591 100644 --- a/src/contexts/Proxies/defaults.ts +++ b/src/contexts/Proxies/defaults.ts @@ -2,13 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only /* eslint-disable @typescript-eslint/no-unused-vars */ -import type { ProxiesContextInterface } from './type'; +import type { ProxiesContextInterface } from './types'; export const defaultProxiesContext: ProxiesContextInterface = { getDelegates: (a) => undefined, getProxyDelegate: (x, y) => null, getProxiedAccounts: (a) => [], handleDeclareDelegate: (a) => new Promise((resolve) => resolve([])), + formatProxiesToDelegates: () => ({}), proxies: [], - delegates: {}, }; diff --git a/src/contexts/Proxies/index.tsx b/src/contexts/Proxies/index.tsx index 3289ada39e..feb28ecaf5 100644 --- a/src/contexts/Proxies/index.tsx +++ b/src/contexts/Proxies/index.tsx @@ -21,6 +21,7 @@ import { useNetwork } from 'contexts/Network'; import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import { useExternalAccounts } from 'contexts/Connect/ExternalAccounts'; import * as defaults from './defaults'; import type { Delegates, @@ -29,7 +30,7 @@ import type { ProxiesContextInterface, Proxy, ProxyDelegate, -} from './type'; +} from './types'; export const ProxiesProvider = ({ children, @@ -39,14 +40,45 @@ export const ProxiesProvider = ({ const { network } = useNetwork(); const { api, isReady } = useApi(); const { accounts } = useImportedAccounts(); - const { addExternalAccount } = useOtherAccounts(); + const { addExternalAccount } = useExternalAccounts(); + const { addOrReplaceOtherAccount } = useOtherAccounts(); const { activeProxy, setActiveProxy, activeAccount } = useActiveAccounts(); - // store the proxy accounts of each imported account. + // Store the proxy accounts of each imported account. const [proxies, setProxies] = useState([]); const proxiesRef = useRef(proxies); const unsubs = useRef>({}); + // Reformats proxies into a list of delegates. + const formatProxiesToDelegates = () => { + // Reformat proxiesRef.current into a list of delegates. + const newDelegates: Delegates = {}; + for (const proxy of proxiesRef.current) { + const { delegator } = proxy; + // checking if delegator is not null to keep types happy. + if (!delegator) continue; + + // get each delegate of this proxy record. + for (const { delegate, proxyType } of proxy.delegates) { + const item = { + delegator, + proxyType, + }; + // check if this delegate exists in `newDelegates`. + if (Object.keys(newDelegates).includes(delegate)) { + // append delegator to the existing delegate record if it exists. + newDelegates[delegate].push(item); + } else { + // create a new delegate record if it does not yet exist in `newDelegates`. + newDelegates[delegate] = [item]; + } + } + } + return newDelegates; + }; + + const delegates = formatProxiesToDelegates(); + // Handle the syncing of accounts on accounts change. const handleSyncAccounts = () => { // Sync removed accounts. @@ -58,8 +90,10 @@ export const ProxiesProvider = ({ removed?.forEach((address) => { // if delegates still exist for removed account, re-add the account as a read only system // account. - if (delegatesRef.current[address]) { - addExternalAccount(address, 'system'); + if (delegates[address]) { + const importResult = addExternalAccount(address, 'system'); + if (importResult) + addOrReplaceOtherAccount(importResult.account, importResult.type); } else { const unsub = unsubs.current[address]; if (unsub) unsub(); @@ -89,10 +123,6 @@ export const ProxiesProvider = ({ handleExistingAccounts(); }; - // store the delegates and the corresponding delegators - const [delegates, setDelegates] = useState({}); - const delegatesRef = useRef(delegates); - const subscribeToProxies = async (address: string) => { if (!api) return undefined; @@ -136,41 +166,66 @@ export const ProxiesProvider = ({ return unsub; }; - // Gets the delegates of the given account + // Gets the delegates of the given account. const getDelegates = (address: MaybeAddress): Proxy | undefined => proxiesRef.current.find(({ delegator }) => delegator === address) || undefined; - // Gets delegators and proxy types for the given delegate address - const getProxiedAccounts = (address: MaybeAddress) => { - const delegate = delegatesRef.current[address || '']; - if (!delegate) { - return []; - } - const proxiedAccounts: ProxiedAccounts = delegate + // Gets delegators and proxy types for the given delegate address. + const getProxiedAccounts = (address: MaybeAddress): ProxiedAccounts => { + const delegate = delegates[address || '']; + if (!delegate) return []; + + return delegate .filter(({ proxyType }) => isSupportedProxy(proxyType)) .map(({ delegator, proxyType }) => ({ address: delegator, name: ellipsisFn(delegator), proxyType, })); - return proxiedAccounts; + }; + + // Queries the chain to check if the given delegator & delegate pair is valid proxy. Used when a + // proxy account is being manually declared. + const handleDeclareDelegate = async (delegator: string) => { + if (!api) return []; + + const result: AnyApi = (await api.query.proxy.proxies(delegator)).toHuman(); + + let addDelegatorAsExternal = false; + for (const { delegate: newDelegate } of result[0] || []) { + if ( + accounts.find(({ address }) => address === newDelegate) && + !delegates[newDelegate] + ) { + subscribeToProxies(delegator); + addDelegatorAsExternal = true; + } + } + if (addDelegatorAsExternal) { + const importResult = addExternalAccount(delegator, 'system'); + if (importResult) + addOrReplaceOtherAccount(importResult.account, importResult.type); + } + + return []; }; // Gets the delegates and proxy type of an account, if any. const getProxyDelegate = ( delegator: MaybeAddress, delegate: MaybeAddress - ): ProxyDelegate | null => - proxiesRef.current - .find((p) => p.delegator === delegator) - ?.delegates.find((d) => d.delegate === delegate) ?? null; + ): ProxyDelegate | null => { + return ( + proxiesRef.current + .find((p) => p.delegator === delegator) + ?.delegates.find((d) => d.delegate === delegate) ?? null + ); + }; // Subscribe new accounts to proxies, and remove accounts that are no longer imported. useEffectIgnoreInitial(() => { - if (isReady) { - handleSyncAccounts(); - } + if (isReady) handleSyncAccounts(); }, [accounts, isReady, network]); // If active proxy has not yet been set, check local storage `activeProxy` & set it as active @@ -193,7 +248,9 @@ export const ProxiesProvider = ({ const { address, proxyType } = JSON.parse(localActiveProxy); // Add proxy address as external account if not imported. if (!accounts.find((a) => a.address === address)) { - addExternalAccount(address, 'system'); + const importResult = addExternalAccount(address, 'system'); + if (importResult) + addOrReplaceOtherAccount(importResult.account, importResult.type); } const isActive = ( @@ -219,74 +276,19 @@ export const ProxiesProvider = ({ }, [network]); const unsubAll = () => { - for (const unsub of Object.values(unsubs.current)) { - unsub(); - } + for (const unsub of Object.values(unsubs.current)) unsub(); unsubs.current = {}; }; - // Listens to `proxies` state updates and reformats the data into a list of delegates. - useEffectIgnoreInitial(() => { - // Reformat proxiesRef.current into a list of delegates. - const newDelegates: Delegates = {}; - for (const proxy of proxiesRef.current) { - const { delegator } = proxy; - - // checking if delegator is not null to keep types happy. - if (delegator) { - // get each delegate of this proxy record. - for (const { delegate, proxyType } of proxy.delegates) { - const item = { - delegator, - proxyType, - }; - // check if this delegate exists in `newDelegates`. - if (Object.keys(newDelegates).includes(delegate)) { - // append delegator to the existing delegate record if it exists. - newDelegates[delegate].push(item); - } else { - // create a new delegate record if it does not yet exist in `newDelegates`. - newDelegates[delegate] = [item]; - } - } - } - } - - setStateWithRef(newDelegates, setDelegates, delegatesRef); - }, [proxiesRef.current]); - - // Queries the chain to check if the given delegator & delegate pair is valid proxy. - const handleDeclareDelegate = async (delegator: string) => { - if (!api) return []; - - const result: AnyApi = (await api.query.proxy.proxies(delegator)).toHuman(); - - let addDelegatorAsExternal = false; - for (const { delegate: newDelegate } of result[0] || []) { - if ( - accounts.find(({ address }) => address === newDelegate) && - !delegatesRef.current[newDelegate] - ) { - subscribeToProxies(delegator); - addDelegatorAsExternal = true; - } - } - if (addDelegatorAsExternal) { - addExternalAccount(delegator, 'system'); - } - - return []; - }; - return ( {children} diff --git a/src/contexts/Proxies/type.ts b/src/contexts/Proxies/types.ts similarity index 96% rename from src/contexts/Proxies/type.ts rename to src/contexts/Proxies/types.ts index 9bff84464d..2a0432c4ac 100644 --- a/src/contexts/Proxies/type.ts +++ b/src/contexts/Proxies/types.ts @@ -46,6 +46,6 @@ export interface ProxiesContextInterface { getProxyDelegate: (x: MaybeAddress, y: MaybeAddress) => ProxyDelegate | null; getProxiedAccounts: (a: MaybeAddress) => ProxiedAccounts; handleDeclareDelegate: (delegator: string) => Promise; + formatProxiesToDelegates: () => Delegates; proxies: Proxies; - delegates: Delegates; } diff --git a/src/contexts/Themes/index.tsx b/src/contexts/Themes/index.tsx index d0d68ba69f..b994859c10 100644 --- a/src/contexts/Themes/index.tsx +++ b/src/contexts/Themes/index.tsx @@ -27,7 +27,7 @@ export const ThemesProvider = ({ children }: { children: React.ReactNode }) => { initialTheme = localThemeRaw as Theme; } - // the theme mode + // The current theme mode const [theme, setTheme] = React.useState(initialTheme); const themeRef = useRef(theme); diff --git a/src/contexts/TransferOptions/Utils.ts b/src/contexts/TransferOptions/Utils.ts new file mode 100644 index 0000000000..37da1ec072 --- /dev/null +++ b/src/contexts/TransferOptions/Utils.ts @@ -0,0 +1,68 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { MaybeAddress } from '@polkadot-cloud/react/types'; +import { unitToPlanck } from '@polkadot-cloud/utils'; +import BigNumber from 'bignumber.js'; +import type { BalanceLock, UnlockChunk } from 'contexts/Balances/types'; +import type { NetworkName } from 'types'; + +// Gets the total unlocking and unlocked amount. +export const getUnlocking = (chunks: UnlockChunk[], thisEra: BigNumber) => { + let totalUnlocking = new BigNumber(0); + let totalUnlocked = new BigNumber(0); + + for (const { value, era } of chunks) + if (thisEra.isGreaterThan(era)) { + totalUnlocked = totalUnlocked.plus(value); + } else { + totalUnlocking = totalUnlocking.plus(value); + } + return { totalUnlocking, totalUnlocked }; +}; + +// Gets the total locked amount from an account's locks. +export const getLocked = (locks: BalanceLock[]) => + locks?.reduce((prev, { amount }) => prev.plus(amount), new BigNumber(0)) || + new BigNumber(0); + +// Gets the largest lock balance, dictating the total amount of unavailable funds from locks. +export const getMaxLock = (locks: BalanceLock[]) => + locks.reduce( + (prev, current) => + prev.amount.isGreaterThan(current.amount) ? prev : current, + { amount: new BigNumber(0) } + )?.amount || new BigNumber(0); + +// Get the local storage record for an account reserve balance. +export const getLocalFeeReserve = ( + address: MaybeAddress, + defaultReserve: number, + { network, units }: { network: NetworkName; units: number } +) => { + const reserves = JSON.parse(localStorage.getItem('reserve_balances') ?? '{}'); + return new BigNumber( + reserves?.[network]?.[address || ''] ?? + unitToPlanck(String(defaultReserve), units) + ); +}; + +// Sets the local storage record fro an account reserve balance. +export const setLocalFeeReserve = ( + address: MaybeAddress, + amount: BigNumber, + network: NetworkName +) => { + if (!address) return; + try { + const newReserves = JSON.parse( + localStorage.getItem('reserve_balances') ?? '{}' + ); + const networkReserves = newReserves?.[network] ?? {}; + networkReserves[address] = amount.toString(); + newReserves[network] = networkReserves; + localStorage.setItem('reserve_balances', JSON.stringify(newReserves)); + } catch (e) { + localStorage.removeItem('reserve_balances'); + } +}; diff --git a/src/contexts/TransferOptions/defaults.ts b/src/contexts/TransferOptions/defaults.ts index d30021aeda..c726cf2ef3 100644 --- a/src/contexts/TransferOptions/defaults.ts +++ b/src/contexts/TransferOptions/defaults.ts @@ -5,14 +5,16 @@ import BigNumber from 'bignumber.js'; import type { TransferOptions, TransferOptionsContextInterface } from './types'; -export const defaultBondedContext: TransferOptionsContextInterface = { - getTransferOptions: (a) => transferOptions, +export const defaultTransferOptionsContext: TransferOptionsContextInterface = { + getTransferOptions: (a) => defaultTransferOptions, setFeeReserveBalance: (r) => {}, feeReserve: new BigNumber(0), }; -export const transferOptions: TransferOptions = { +export const defaultTransferOptions: TransferOptions = { freeBalance: new BigNumber(0), + transferrableBalance: new BigNumber(0), + balanceTxFees: new BigNumber(0), edReserved: new BigNumber(0), nominate: { active: new BigNumber(0), @@ -20,14 +22,13 @@ export const transferOptions: TransferOptions = { totalUnlocked: new BigNumber(0), totalPossibleBond: new BigNumber(0), totalAdditionalBond: new BigNumber(0), - totalUnlockChuncks: 0, + totalUnlockChunks: 0, }, pool: { active: new BigNumber(0), totalUnlocking: new BigNumber(0), totalUnlocked: new BigNumber(0), totalPossibleBond: new BigNumber(0), - totalAdditionalBond: new BigNumber(0), - totalUnlockChuncks: 0, + totalUnlockChunks: 0, }, }; diff --git a/src/contexts/TransferOptions/index.tsx b/src/contexts/TransferOptions/index.tsx index 19c68955ee..0f5f4dbd04 100644 --- a/src/contexts/TransferOptions/index.tsx +++ b/src/contexts/TransferOptions/index.tsx @@ -1,7 +1,6 @@ // Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { unitToPlanck } from '@polkadot-cloud/utils'; import BigNumber from 'bignumber.js'; import React, { useState } from 'react'; import { useApi } from 'contexts/Api'; @@ -13,8 +12,25 @@ import type { MaybeAddress } from 'types'; import { useEffectIgnoreInitial } from '@polkadot-cloud/react/hooks'; import { useNetwork } from 'contexts/Network'; import { useActiveAccounts } from 'contexts/ActiveAccounts'; -import * as defaults from './defaults'; import type { TransferOptions, TransferOptionsContextInterface } from './types'; +import { + getMaxLock, + getLocalFeeReserve, + getUnlocking, + setLocalFeeReserve, +} from './Utils'; +import { + defaultTransferOptions, + defaultTransferOptionsContext, +} from './defaults'; + +export const TransferOptionsContext = + React.createContext( + defaultTransferOptionsContext + ); + +export const useTransferOptions = () => + React.useContext(TransferOptionsContext); export const TransferOptionsProvider = ({ children, @@ -22,178 +38,120 @@ export const TransferOptionsProvider = ({ children: React.ReactNode; }) => { const { consts } = useApi(); + const { getAccount } = useBonded(); + const { activeEra } = useNetworkMetrics(); + const { membership } = usePoolMemberships(); + const { activeAccount } = useActiveAccounts(); const { - networkData: { name, units, defaultFeeReserve }, + network, + networkData: { units, defaultFeeReserve }, } = useNetwork(); - const { activeEra } = useNetworkMetrics(); const { getStashLedger, getBalance, getLocks } = useBalances(); - const { getAccount } = useBonded(); - const { membership } = usePoolMemberships(); const { existentialDeposit } = consts; - const { activeAccount } = useActiveAccounts(); - - // Get the local storage rcord for an account reserve balance. - const getFeeReserveLocalStorage = (address: MaybeAddress) => { - const reserves = JSON.parse( - localStorage.getItem('reserve_balances') ?? '{}' - ); - return new BigNumber( - reserves?.[name]?.[address || ''] ?? - unitToPlanck(String(defaultFeeReserve), units) - ); - }; // A user-configurable reserve amount to be used to pay for transaction fees. const [feeReserve, setFeeReserve] = useState( - getFeeReserveLocalStorage(activeAccount) + getLocalFeeReserve(activeAccount, defaultFeeReserve, { network, units }) ); - // Update an account's reserve amount on account or network change. - useEffectIgnoreInitial(() => { - setFeeReserve(getFeeReserveLocalStorage(activeAccount)); - }, [activeAccount, name]); - // Get the bond and unbond amounts available to the user const getTransferOptions = (address: MaybeAddress): TransferOptions => { - const account = getAccount(address); - if (account === null) { - return defaults.transferOptions; - } - const balance = getBalance(address); - const ledger = getStashLedger(address); - const locks = getLocks(address); - - const { free } = balance; - const { active, total, unlocking } = ledger; + if (getAccount(address) === null) return defaultTransferOptions; - const totalLocked = - locks?.reduce( - (prev, { amount }) => prev.plus(amount), - new BigNumber(0) - ) || new BigNumber(0); + const { free, frozen } = getBalance(address); + const { active, total, unlocking } = getStashLedger(address); + const locks = getLocks(address); + const maxLock = getMaxLock(locks); // Calculate a forced amount of free balance that needs to be reserved to keep the account // alive. Deducts `locks` from free balance reserve needed. - const edReserved = BigNumber.max(existentialDeposit.minus(totalLocked), 0); + const edReserved = BigNumber.max(existentialDeposit.minus(maxLock), 0); // Total free balance after `edReserved` is subtracted. - const freeMinusReserve = BigNumber.max(free.minus(edReserved), 0); - - // calculate total balance locked - const maxLockBalance = - locks.reduce( - (prev, current) => { - return prev.amount.isGreaterThan(current.amount) ? prev : current; - }, - { amount: new BigNumber(0) } - )?.amount || new BigNumber(0); - - const poolBalance = membership?.balance; - const activePool = poolBalance || new BigNumber(0); - - // total amount actively unlocking - let totalUnlocking = new BigNumber(0); - let totalUnlocked = new BigNumber(0); - for (const u of unlocking) { - const { value, era } = u; - if (activeEra.index.isGreaterThan(era)) { - totalUnlocked = totalUnlocked.plus(value); - } else { - totalUnlocking = totalUnlocking.plus(value); - } - } - - // free balance after `total` ledger amount. + const freeMinusReserve = BigNumber.max( + free.minus(edReserved).minus(feeReserve), + 0 + ); + + // Free balance that can be transferred. + const transferrableBalance = BigNumber.max( + freeMinusReserve.minus(frozen), + 0 + ); + + // Gree balance to pay for tsx fees. Does not factor `feeReserve`. + const balanceTxFees = BigNumber.max( + free.minus(edReserved).minus(frozen), + 0 + ); + + // Staking specific balances. + // + // Total amount unlocking and unlocked. + const { totalUnlocking, totalUnlocked } = getUnlocking( + unlocking, + activeEra.index + ); + + // Free balance to stake after `total` (total staked) ledger amount. const freeBalance = BigNumber.max(freeMinusReserve.minus(total), 0); - const nominateOptions = () => { - // total possible balance that can be bonded + // Get nominator-specific balances. + const nominatorBalances = () => { const totalPossibleBond = BigNumber.max( - freeMinusReserve - .minus(totalUnlocking) - .minus(totalUnlocked) - .minus(feeReserve), + freeMinusReserve.minus(totalUnlocking).minus(totalUnlocked), 0 ); - - // total additional balance that can be bonded. - const totalAdditionalBond = totalPossibleBond.minus(active); - return { active, totalUnlocking, totalUnlocked, totalPossibleBond, - totalAdditionalBond, - totalUnlockChuncks: unlocking.length, + totalAdditionalBond: BigNumber.max(totalPossibleBond.minus(active), 0), + totalUnlockChunks: unlocking.length, }; }; - const poolOptions = () => { + // Get pool-member-specific balances. + const poolBalances = () => { const unlockingPool = membership?.unlocking || []; + const { + totalUnlocking: totalUnlockingPool, + totalUnlocked: totalUnlockedPool, + } = getUnlocking(unlockingPool, activeEra.index); - // total possible balance that can be bonded - const totalPossibleBondPool = BigNumber.max( - freeMinusReserve.minus(maxLockBalance).minus(feeReserve), - new BigNumber(0) - ); - - // total additional balance that can be bonded. - const totalAdditionalBondPool = totalPossibleBondPool; - - let totalUnlockingPool = new BigNumber(0); - let totalUnlockedPool = new BigNumber(0); - for (const u of unlockingPool) { - const { value, era } = u; - if (activeEra.index.isGreaterThan(era)) { - totalUnlockedPool = totalUnlockedPool.plus(value); - } else { - totalUnlockingPool = totalUnlockingPool.plus(value); - } - } return { - active: activePool, + active: membership?.balance || new BigNumber(0), totalUnlocking: totalUnlockingPool, totalUnlocked: totalUnlockedPool, - totalPossibleBond: totalPossibleBondPool, - totalAdditionalBond: totalAdditionalBondPool, - totalUnlockChuncks: unlockingPool.length, + totalPossibleBond: BigNumber.max(freeMinusReserve.minus(maxLock), 0), + totalUnlockChunks: unlockingPool.length, }; }; return { freeBalance, + transferrableBalance, + balanceTxFees, edReserved, - nominate: nominateOptions(), - pool: poolOptions(), + nominate: nominatorBalances(), + pool: poolBalances(), }; }; // Updates account's reserve amount in state and in local storage. const setFeeReserveBalance = (amount: BigNumber) => { if (!activeAccount) return; - setFeeReserveLocalStorage(amount); + setLocalFeeReserve(activeAccount, amount, network); setFeeReserve(amount); }; - // Update the local storage record for account reserve balances. - const setFeeReserveLocalStorage = (amount: BigNumber) => { - if (!activeAccount) return; - - try { - const newReserves = JSON.parse( - localStorage.getItem('reserve_balances') ?? '{}' - ); - const newReservesNetwork = newReserves?.[name] ?? {}; - newReservesNetwork[activeAccount] = amount.toString(); - - newReserves[name] = newReservesNetwork; - localStorage.setItem('reserve_balances', JSON.stringify(newReserves)); - } catch (e) { - // corrupted local storage record - remove it. - localStorage.removeItem('reserve_balances'); - } - }; + // Update an account's reserve amount on account or network change. + useEffectIgnoreInitial(() => { + setFeeReserve( + getLocalFeeReserve(activeAccount, defaultFeeReserve, { network, units }) + ); + }, [activeAccount, network]); return ( ); }; - -export const TransferOptionsContext = - React.createContext( - defaults.defaultBondedContext - ); - -export const useTransferOptions = () => - React.useContext(TransferOptionsContext); diff --git a/src/contexts/TransferOptions/types.ts b/src/contexts/TransferOptions/types.ts index fe8a1ea136..8a64200aef 100644 --- a/src/contexts/TransferOptions/types.ts +++ b/src/contexts/TransferOptions/types.ts @@ -12,6 +12,8 @@ export interface TransferOptionsContextInterface { export interface TransferOptions { freeBalance: BigNumber; + transferrableBalance: BigNumber; + balanceTxFees: BigNumber; edReserved: BigNumber; nominate: { active: BigNumber; @@ -19,14 +21,13 @@ export interface TransferOptions { totalUnlocked: BigNumber; totalPossibleBond: BigNumber; totalAdditionalBond: BigNumber; - totalUnlockChuncks: number; + totalUnlockChunks: number; }; pool: { active: BigNumber; totalUnlocking: BigNumber; totalUnlocked: BigNumber; totalPossibleBond: BigNumber; - totalAdditionalBond: BigNumber; - totalUnlockChuncks: number; + totalUnlockChunks: number; }; } diff --git a/src/contexts/TxMeta/index.tsx b/src/contexts/TxMeta/index.tsx index efac8cbdb3..5704dfb8a2 100644 --- a/src/contexts/TxMeta/index.tsx +++ b/src/contexts/TxMeta/index.tsx @@ -45,8 +45,8 @@ export const TxMetaProvider = ({ children }: { children: React.ReactNode }) => { const txSignatureRef = React.useRef(txSignature); useEffectIgnoreInitial(() => { - const { freeBalance } = getTransferOptions(sender); - setNotEnoughFunds(freeBalance.minus(txFees).isLessThan(0)); + const { balanceTxFees } = getTransferOptions(sender); + setNotEnoughFunds(balanceTxFees.minus(txFees).isLessThan(0)); }, [txFees, sender]); const resetTxFees = () => { diff --git a/src/img/ledgerLogo.svg b/src/img/ledgerLogo.svg deleted file mode 100644 index 32b2408ed8..0000000000 --- a/src/img/ledgerLogo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/library/Account/Pool.tsx b/src/library/Account/Pool.tsx index 0c416f7832..9cdfd4f1df 100644 --- a/src/library/Account/Pool.tsx +++ b/src/library/Account/Pool.tsx @@ -3,12 +3,9 @@ import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; import { ellipsisFn, remToUnit } from '@polkadot-cloud/utils'; -import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useApi } from 'contexts/Api'; import { useBondedPools } from 'contexts/Pools/BondedPools'; import { Polkicon } from '@polkadot-cloud/react'; -import { useActiveAccounts } from 'contexts/ActiveAccounts'; import { Wrapper } from './Wrapper'; import type { AccountProps } from './types'; @@ -20,44 +17,15 @@ export const Account = ({ fontSize = '1.05rem', }: AccountProps) => { const { t } = useTranslation('library'); - const { isReady } = useApi(); - const { activeAccount } = useActiveAccounts(); - const { fetchPoolsMetaBatch, meta } = useBondedPools(); + const { poolsMetaData } = useBondedPools(); - // is this the initial fetch - const [fetched, setFetched] = useState(false); - - const batchKey = 'pool_header'; - - // refetch when pool or active account changes - useEffect(() => { - setFetched(false); - }, [activeAccount, pool]); - - // configure pool list when network is ready to fetch - useEffect(() => { - if (isReady) { - setFetched(true); - - if (!fetched) { - getPoolMeta(); - } - } - }, [isReady, fetched]); - - // handle pool list bootstrapping - const getPoolMeta = () => { - const pools: any = [{ id: pool.id }]; - fetchPoolsMetaBatch(batchKey, pools, true); - }; - - const metaBatch = meta[batchKey]; - const metaData = metaBatch?.metadata?.[0]; - const syncing = metaData === undefined; + const syncing = !Object.values(poolsMetaData).length; // display value const defaultDisplay = ellipsisFn(pool.addresses.stash); - let display = syncing ? t('syncing') : metaData ?? defaultDisplay; + let display = syncing + ? t('syncing') + : poolsMetaData[pool.id] ?? defaultDisplay; // check if super identity has been byte encoded const displayAsBytes = u8aToString(u8aUnwrapBytes(display)); diff --git a/src/library/BarChart/BondedChart.tsx b/src/library/BarChart/BondedChart.tsx index 983c8168cd..5914275928 100644 --- a/src/library/BarChart/BondedChart.tsx +++ b/src/library/BarChart/BondedChart.tsx @@ -45,13 +45,13 @@ export const BondedChart = ({ ) : new BigNumber(0); - const freeBalance = free.decimalPlaces(3); + const freeToBond = free.decimalPlaces(3); const remaining = new BigNumber(100).minus(graphActive).minus(graphUnlocking); const graphFree = greaterThanZero(remaining) ? BigNumber.max( remaining, - freeBalance.isGreaterThan(MinimumLowerBound) ? MinimumNoNZeroPercent : 0 + freeToBond.isGreaterThan(MinimumLowerBound) ? MinimumNoNZeroPercent : 0 ) : new BigNumber(0); @@ -93,7 +93,7 @@ export const BondedChart = ({ dataClass="d4" widthPercent={Number(graphFree.toFixed(2))} flexGrow={0} - label={`${freeBalance.toFormat()} ${unit}`} + label={`${freeToBond.toFormat()} ${unit}`} forceShow={inactive && totalUnlocking.isZero()} /> diff --git a/src/library/EstimatedTxFee/index.tsx b/src/library/EstimatedTxFee/index.tsx index 6308a02ee8..7d1c94dc5d 100644 --- a/src/library/EstimatedTxFee/index.tsx +++ b/src/library/EstimatedTxFee/index.tsx @@ -42,11 +42,6 @@ export const EstimatedTxFeeInner = ({ format }: EstimatedTxFeeProps) => { export class EstimatedTxFee extends React.Component { static contextType: Context = TxMetaContext; - componentDidMount(): void { - const { resetTxFees } = this.context as TxMetaContextInterface; - resetTxFees(); - } - componentWillUnmount(): void { const { resetTxFees } = this.context as TxMetaContextInterface; resetTxFees(); diff --git a/src/library/Form/Bond/BondFeedback.tsx b/src/library/Form/Bond/BondFeedback.tsx index a4a2e21867..163b2fec48 100644 --- a/src/library/Form/Bond/BondFeedback.tsx +++ b/src/library/Form/Bond/BondFeedback.tsx @@ -45,18 +45,15 @@ export const BondFeedback = ({ const defaultBondStr = defaultBond ? String(defaultBond) : ''; // get bond options for either staking or pooling. - const freeBalanceBn = + const availableBalance = bondFor === 'nominator' ? allTransferOptions.nominate.totalAdditionalBond - : allTransferOptions.pool.totalAdditionalBond; + : allTransferOptions.transferrableBalance; - // if we are bonding, subtract tx fees from bond amount - const freeBondAmount = !disableTxFeeUpdate - ? BigNumber.max(freeBalanceBn.minus(txFees), 0) - : freeBalanceBn; - - // the default bond balance - const freeBalance = planckToUnit(freeBondAmount, units); + // the default bond balance. If we are bonding, subtract tx fees from bond amount. + const freeToBond = !disableTxFeeUpdate + ? BigNumber.max(availableBalance.minus(txFees), 0) + : availableBalance; // store errors const [errors, setErrors] = useState([]); @@ -73,9 +70,7 @@ export const BondFeedback = ({ const [bondDisabled, setBondDisabled] = useState(false); // bond minus tx fees if too much - const enoughToCoverTxFees = freeBondAmount - .minus(bondBn) - .isGreaterThan(txFees); + const enoughToCoverTxFees = freeToBond.minus(bondBn).isGreaterThan(txFees); const bondAfterTxFees = enoughToCoverTxFees ? bondBn @@ -96,8 +91,8 @@ export const BondFeedback = ({ // update max bond after txFee sync useEffect(() => { if (!disableTxFeeUpdate) { - if (bondBn.isGreaterThan(freeBondAmount)) { - setBond({ bond: String(freeBalance) }); + if (bondBn.isGreaterThan(freeToBond)) { + setBond({ bond: String(freeToBond) }); } } }, [txFees]); @@ -124,13 +119,13 @@ export const BondFeedback = ({ const decimals = bond.bond.toString().split('.')[1]?.length ?? 0; // bond errors - if (freeBondAmount.isZero()) { + if (freeToBond.isZero()) { disabled = true; newErrors.push(`${t('noFree', { unit })}`); } // bond amount must not surpass freeBalalance - if (bondBn.isGreaterThan(freeBondAmount)) { + if (bondBn.isGreaterThan(freeToBond)) { newErrors.push(t('moreThanBalance')); } @@ -150,7 +145,7 @@ export const BondFeedback = ({ } if (inSetup || joiningPool) { - if (freeBondAmount.isLessThan(minBondBn)) { + if (freeToBond.isLessThan(minBondBn)) { disabled = true; newErrors.push(`${t('notMeet')} ${minBondUnit} ${unit}.`); } @@ -184,7 +179,7 @@ export const BondFeedback = ({ syncing={syncing} disabled={bondDisabled} setters={setters} - freeBalance={freeBalance} + freeToBond={planckToUnit(freeToBond, units)} disableTxFeeUpdate={disableTxFeeUpdate} /> diff --git a/src/library/Form/Bond/BondInput.tsx b/src/library/Form/Bond/BondInput.tsx index cc8f5835f0..322d21c12a 100644 --- a/src/library/Form/Bond/BondInput.tsx +++ b/src/library/Form/Bond/BondInput.tsx @@ -14,7 +14,7 @@ export const BondInput = ({ setters = [], disabled, defaultValue, - freeBalance, + freeToBond, disableTxFeeUpdate = false, value = '0', syncing = false, @@ -62,7 +62,7 @@ export const BondInput = ({ // available funds as jsx. const availableFundsJsx = (

- {syncing ? '...' : `${freeBalance.toFormat()} ${unit} ${t('available')}`} + {syncing ? '...' : `${freeToBond.toFormat()} ${unit} ${t('available')}`}

); @@ -88,10 +88,10 @@ export const BondInput = ({
{ - setLocalBond(freeBalance.toString()); - updateParentState(freeBalance.toString()); + setLocalBond(freeToBond.toString()); + updateParentState(freeToBond.toString()); }} />
diff --git a/src/library/Form/ClaimPermissionInput/index.tsx b/src/library/Form/ClaimPermissionInput/index.tsx index 04720dc468..85027071fa 100644 --- a/src/library/Form/ClaimPermissionInput/index.tsx +++ b/src/library/Form/ClaimPermissionInput/index.tsx @@ -56,8 +56,8 @@ export const ClaimPermissionInput = ({ const newClaimPermission = val ? claimPermissionConfig[0].value : current === undefined - ? undefined - : 'Permissioned'; + ? undefined + : 'Permissioned'; setSelected(newClaimPermission); onChange(newClaimPermission); diff --git a/src/library/Form/types.ts b/src/library/Form/types.ts index 45617ae9b4..1ff6c27e3d 100644 --- a/src/library/Form/types.ts +++ b/src/library/Form/types.ts @@ -49,7 +49,7 @@ export interface BondFeedbackProps { } export interface BondInputProps { - freeBalance: BigNumber; + freeToBond: BigNumber; value: string; defaultValue: string; syncing?: boolean; diff --git a/src/library/Graphs/PayoutLine.tsx b/src/library/Graphs/PayoutLine.tsx index 6b72b35a89..3cf3405dcb 100644 --- a/src/library/Graphs/PayoutLine.tsx +++ b/src/library/Graphs/PayoutLine.tsx @@ -91,8 +91,8 @@ export const PayoutLine = ({ const color = notStaking ? colors.primary[mode] : !poolingOnly - ? colors.primary[mode] - : colors.secondary[mode]; + ? colors.primary[mode] + : colors.secondary[mode]; // configure graph options const options = { diff --git a/src/library/Graphs/Utils.ts b/src/library/Graphs/Utils.ts index 2c75cb8582..c64b799486 100644 --- a/src/library/Graphs/Utils.ts +++ b/src/library/Graphs/Utils.ts @@ -79,9 +79,7 @@ export const calculateDailyPayouts = ( const thisDay = startOfDay(fromUnixTime(payout.block_timestamp)); // initialise current day if first payout. - if (p === 1) { - curDay = thisDay; - } + if (p === 1) curDay = thisDay; // handle surpassed maximum days. if (daysPassed(thisDay, fromDate) >= maxDays) { @@ -93,7 +91,7 @@ export const calculateDailyPayouts = ( break; } - // get day difference between cursor and currentpayout. + // get day difference between cursor and current payout. const daysDiff = daysPassed(thisDay, curDay); // handle new day. @@ -119,8 +117,11 @@ export const calculateDailyPayouts = ( curPayout.amount = curPayout.amount.plus(payout.amount); } - // if only 1 payout exists, exit early here. - if (payouts.length === 1) { + // if only 1 payout exists, or at the last unresolved payout, exit here. + if ( + payouts.length === 1 || + (p === payouts.length && !curPayout.amount.isZero()) + ) { dailyPayouts.push({ amount: planckToUnit(curPayout.amount, units), event_id: getEventId(curPayout), diff --git a/src/library/Headers/Connected.tsx b/src/library/Headers/Connected.tsx index 241e77a7d0..74ec1e50a7 100644 --- a/src/library/Headers/Connected.tsx +++ b/src/library/Headers/Connected.tsx @@ -39,8 +39,8 @@ export const Connected = () => { isNetworkSyncing ? undefined : isNominating() - ? 'Nominator' - : undefined + ? 'Nominator' + : undefined } format="name" /> diff --git a/src/library/Hooks/useBondGreatestFee/index.tsx b/src/library/Hooks/useBondGreatestFee/index.tsx index 49dffb0bba..a47fbecd9a 100644 --- a/src/library/Hooks/useBondGreatestFee/index.tsx +++ b/src/library/Hooks/useBondGreatestFee/index.tsx @@ -20,7 +20,7 @@ export const useBondGreatestFee = ({ bondFor }: Props) => { () => getTransferOptions(activeAccount), [activeAccount] ); - const { freeBalance } = transferOptions; + const { transferrableBalance } = transferOptions; // store the largest possible tx fees for bonding. const [largestTxFee, setLargestTxFee] = useState(new BigNumber(0)); @@ -38,7 +38,10 @@ export const useBondGreatestFee = ({ bondFor }: Props) => { // estimate the largest possible tx fee based on users free balance. const txLargestFee = async () => { - const bond = BigNumber.max(freeBalance.minus(feeReserve), 0).toString(); + const bond = BigNumber.max( + transferrableBalance.minus(feeReserve), + 0 + ).toString(); let tx = null; if (!api) { diff --git a/src/library/Hooks/useLedgerLoop/index.tsx b/src/library/Hooks/useLedgerLoop/index.tsx deleted file mode 100644 index a8fece5af8..0000000000 --- a/src/library/Hooks/useLedgerLoop/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: GPL-3.0-only - -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; -import { getLedgerApp } from 'contexts/Hardware/Utils'; -import { useTxMeta } from 'contexts/TxMeta'; -import { useNetwork } from 'contexts/Network'; -import type { LederLoopProps } from './types'; - -export const useLedgerLoop = ({ tasks, options, mounted }: LederLoopProps) => { - const { setIsPaired, getIsExecuting, getStatusCodes, executeLedgerLoop } = - useLedgerHardware(); - const { network } = useNetwork(); - const { getTxPayload, getPayloadUid } = useTxMeta(); - const { appName } = getLedgerApp(network); - - // Connect to Ledger device and perform necessary tasks. - // - // The tasks sent to the device depend on the current state of the import process. - const handleLedgerLoop = async () => { - // If the import modal is no longer open, cancel execution. - if (!mounted()) { - return; - } - // If the app is not open on-device, or device is not connected, cancel execution. - // If we are to explore auto looping via an interval, this may wish to use `determineStatusFromCode` instead. - if (['DeviceNotConnected'].includes(getStatusCodes()[0]?.statusCode)) { - setIsPaired('unpaired'); - } else { - // Get task options and execute the loop. - const uid = getPayloadUid(); - const accountIndex = options?.accountIndex ? options.accountIndex() : 0; - const payload = await getTxPayload(); - if (getIsExecuting()) { - await executeLedgerLoop(appName, tasks, { - uid, - accountIndex, - payload, - }); - } - } - }; - - return { handleLedgerLoop }; -}; diff --git a/src/library/Hooks/useLedgerLoop/types.ts b/src/library/Hooks/useLedgerLoop/types.ts deleted file mode 100644 index 2e4b5aa817..0000000000 --- a/src/library/Hooks/useLedgerLoop/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: GPL-3.0-only - -import type { LedgerTask } from 'contexts/Hardware/types'; -import type { AnyJson } from 'types'; - -export interface LederLoopProps { - tasks: LedgerTask[]; - options: { - uid?: number; - accountIndex?: () => number; - payload?: () => Promise; - }; - mounted: () => boolean; -} diff --git a/src/library/Hooks/usePoolFilters/index.tsx b/src/library/Hooks/usePoolFilters/index.tsx index 07e87b87bd..e8dff2f588 100644 --- a/src/library/Hooks/usePoolFilters/index.tsx +++ b/src/library/Hooks/usePoolFilters/index.tsx @@ -8,27 +8,20 @@ import type { AnyFunction, AnyJson } from 'types'; export const usePoolFilters = () => { const { t } = useTranslation('library'); - const { meta } = useBondedPools(); + const { poolsNominations } = useBondedPools(); const { getNominationsStatusFromTargets } = useStaking(); const { getPoolNominationStatusCode } = useBondedPools(); /* - * include active pools. - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that are - * actively nominating. + * Include active pools. * Returns the updated filtered list. */ - const includeActive = (list: any, batchKey: string) => { - // get pool targets from nominations meta batch - const nominations = meta[batchKey]?.nominations ?? []; - if (!nominations) { - return list; - } - let i = -1; + const includeActive = (list: any) => { + if (!Object.keys(poolsNominations).length) return list; + const filteredList = list.filter((p: BondedPool) => { - i++; - const targets = nominations[i]?.targets ?? []; + const nominations = poolsNominations[p.id]; + const targets = nominations?.targets || []; const status = getPoolNominationStatusCode( getNominationsStatusFromTargets(p.addresses.stash, targets) ); @@ -38,22 +31,15 @@ export const usePoolFilters = () => { }; /* - * dont include active pools. - * Iterates through the supplied list and refers to the meta - * batch of the list to filter those list items that are - * actively nominating. + * Dont include active pools. * Returns the updated filtered list. */ - const excludeActive = (list: any, batchKey: string) => { - // get pool targets from nominations meta batch - const nominations = meta[batchKey]?.nominations ?? []; - if (!nominations) { - return list; - } - let i = -1; + const excludeActive = (list: any) => { + if (!Object.keys(poolsNominations).length) return list; + const filteredList = list.filter((p: BondedPool) => { - i++; - const targets = nominations[i]?.targets ?? []; + const nominations = poolsNominations[p.id]; + const targets = nominations?.targets || []; const status = getPoolNominationStatusCode( getNominationsStatusFromTargets(p.addresses.stash, targets) ); @@ -135,20 +121,19 @@ export const usePoolFilters = () => { const applyFilter = ( includes: string[] | null, excludes: string[] | null, - list: AnyJson, - batchKey: string + list: AnyJson ) => { if (!excludes && !includes) { return list; } if (includes) { for (const fn of getFiltersFromKey(includes, 'include')) { - list = fn(list, batchKey); + list = fn(list); } } if (excludes) { for (const fn of getFiltersFromKey(excludes, 'exclude')) { - list = fn(list, batchKey); + list = fn(list); } } return list; diff --git a/src/library/Hooks/useSubmitExtrinsic/index.tsx b/src/library/Hooks/useSubmitExtrinsic/index.tsx index f695e9d7b2..519e63de1b 100644 --- a/src/library/Hooks/useSubmitExtrinsic/index.tsx +++ b/src/library/Hooks/useSubmitExtrinsic/index.tsx @@ -9,7 +9,7 @@ import { DappName, ManualSigners } from 'consts'; import { useApi } from 'contexts/Api'; import { useExtensions } from '@polkadot-cloud/react/hooks'; import { useExtrinsics } from 'contexts/Extrinsics'; -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; import { useNotifications } from 'contexts/Notifications'; import { useTxMeta } from 'contexts/TxMeta'; import type { AnyApi, AnyJson } from 'types'; @@ -35,9 +35,8 @@ export const useSubmitExtrinsic = ({ const { extensionsStatus } = useExtensions(); const { addNotification } = useNotifications(); const { isProxySupported } = useProxySupported(); + const { handleResetLedgerTask } = useLedgerHardware(); const { addPending, removePending } = useExtrinsics(); - const { setIsExecuting, resetStatusCodes, resetFeedback } = - useLedgerHardware(); const { getAccount, requiresManualSign } = useImportedAccounts(); const { txFees, @@ -197,21 +196,14 @@ export const useSubmitExtrinsic = ({ setSubmitting(false); }; - const resetLedgerTx = () => { - setIsExecuting(false); - resetStatusCodes(); - resetFeedback(); - }; const resetManualTx = () => { resetTx(); - resetLedgerTx(); + handleResetLedgerTask(); }; const onError = (type?: string) => { resetTx(); - if (type === 'ledger') { - resetLedgerTx(); - } + if (type === 'ledger') handleResetLedgerTask(); removePending(nonce); addNotification({ title: t('cancelled'), diff --git a/src/library/List/SearchInput.tsx b/src/library/List/SearchInput.tsx index 77c3969b30..edb991f3e5 100644 --- a/src/library/List/SearchInput.tsx +++ b/src/library/List/SearchInput.tsx @@ -12,7 +12,7 @@ export const SearchInput = ({ ) => handleChange(e)} /> diff --git a/src/library/ListItem/Labels/EraStatus.tsx b/src/library/ListItem/Labels/EraStatus.tsx index 13c84374f6..75ac70ad86 100644 --- a/src/library/ListItem/Labels/EraStatus.tsx +++ b/src/library/ListItem/Labels/EraStatus.tsx @@ -24,10 +24,10 @@ export const EraStatus = ({ noMargin, status, totalStake }: EraStatusProps) => { {isSyncing || erasStakersSyncing ? t('syncing') : validatorStatus !== 'waiting' - ? `${t('listItemActive')} / ${planckToUnit(totalStake, units) - .integerValue() - .toFormat()} ${unit}` - : capitalizeFirstLetter(t(`${validatorStatus}`) ?? '')} + ? `${t('listItemActive')} / ${planckToUnit(totalStake, units) + .integerValue() + .toFormat()} ${unit}` + : capitalizeFirstLetter(t(`${validatorStatus}`) ?? '')} ); diff --git a/src/library/ListItem/Labels/PoolBonded.tsx b/src/library/ListItem/Labels/PoolBonded.tsx index e2a12a473c..c88ea23b45 100644 --- a/src/library/ListItem/Labels/PoolBonded.tsx +++ b/src/library/ListItem/Labels/PoolBonded.tsx @@ -15,26 +15,18 @@ import { ValidatorStatusWrapper } from 'library/ListItem/Wrappers'; import type { Pool } from 'library/Pool/types'; import { useNetwork } from 'contexts/Network'; -export const PoolBonded = ({ - pool, - batchKey, - batchIndex, -}: { - pool: Pool; - batchKey: string; - batchIndex: number; -}) => { +export const PoolBonded = ({ pool }: { pool: Pool }) => { const { t } = useTranslation('library'); const { networkData: { units, unit }, } = useNetwork(); - const { meta, getPoolNominationStatusCode } = useBondedPools(); + const { getPoolNominationStatusCode, poolsNominations } = useBondedPools(); const { eraStakers, getNominationsStatusFromTargets } = useStaking(); const { addresses, points } = pool; // get pool targets from nominations meta batch - const nominations = meta[batchKey]?.nominations ?? []; - const targets = nominations[batchIndex]?.targets ?? []; + const nominations = poolsNominations[pool.id]; + const targets = nominations?.targets || []; // store nomination status in state const [nominationsStatus, setNominationsStatus] = @@ -54,8 +46,7 @@ export const PoolBonded = ({ if ( targets.length && nominationsStatus === null && - eraStakers.stakers.length && - nominations.length + eraStakers.stakers.length ) { handleNominationsStatus(); } @@ -65,7 +56,7 @@ export const PoolBonded = ({ // recalculate nominations status useEffect(() => { handleNominationsStatus(); - }, [meta, pool, eraStakers.stakers.length]); + }, [pool, eraStakers.stakers.length, Object.keys(poolsNominations).length]); // calculate total bonded pool amount const poolBonded = planckToUnit(new BigNumber(rmCommas(points)), units); @@ -82,8 +73,8 @@ export const PoolBonded = ({ {nominationStatus === null || !eraStakers.stakers.length ? `${t('syncing')}...` : targets.length - ? capitalizeFirstLetter(t(`${nominationStatus}`) ?? '') - : t('notNominating')} + ? capitalizeFirstLetter(t(`${nominationStatus}`) ?? '') + : t('notNominating')} {' / '} {t('bonded')}: {poolBonded.decimalPlaces(3).toFormat()} {unit} diff --git a/src/library/ListItem/Labels/PoolIdentity.tsx b/src/library/ListItem/Labels/PoolIdentity.tsx index b9111f1d6d..642b7d94fd 100644 --- a/src/library/ListItem/Labels/PoolIdentity.tsx +++ b/src/library/ListItem/Labels/PoolIdentity.tsx @@ -8,21 +8,15 @@ import { IdentityWrapper } from 'library/ListItem/Wrappers'; import type { PoolIdentityProps } from '../types'; export const PoolIdentity = ({ - pool, - batchKey, - batchIndex, + pool: { addresses, id }, }: PoolIdentityProps) => { - const { meta } = useBondedPools(); - const { addresses } = pool; + const { poolsMetaData } = useBondedPools(); + const metadataSynced = Object.values(poolsMetaData).length > 0 ?? false; - // get metadata from pools metabatch - const metadata = meta[batchKey]?.metadata ?? []; - - // aggregate synced status - const metadataSynced = metadata.length > 0 ?? false; - - // pool display name - const display = determinePoolDisplay(addresses.stash, metadata[batchIndex]); + const display = determinePoolDisplay( + addresses.stash, + poolsMetaData[Number(id)] + ); return ( diff --git a/src/library/ListItem/types.ts b/src/library/ListItem/types.ts index 0a1967deab..c6808b504b 100644 --- a/src/library/ListItem/types.ts +++ b/src/library/ListItem/types.ts @@ -25,8 +25,6 @@ export interface IdentityProps { } export interface PoolIdentityProps { - batchIndex: number; - batchKey: string; pool: BondedPool; } diff --git a/src/library/NetworkBar/index.tsx b/src/library/NetworkBar/index.tsx index b2e245674e..0e0c1fc4e4 100644 --- a/src/library/NetworkBar/index.tsx +++ b/src/library/NetworkBar/index.tsx @@ -93,8 +93,8 @@ export const NetworkBar = () => { prices.change < 0 ? ' neg' : prices.change > 0 - ? ' pos' - : '' + ? ' pos' + : '' }`} > {prices.change < 0 ? '' : prices.change > 0 ? '+' : ''} diff --git a/src/library/Nominations/index.tsx b/src/library/Nominations/index.tsx index 7729e76e06..d0413452bd 100644 --- a/src/library/Nominations/index.tsx +++ b/src/library/Nominations/index.tsx @@ -130,14 +130,14 @@ export const Nominations = ({ )} - {nominated === null || isSyncing ? ( + {isSyncing ? ( {`${t('nominate.syncing')}...`} ) : !nominator ? ( {t('nominate.notNominating')}. - ) : nominated.length > 0 ? ( + ) : (nominated?.length || 0) > 0 ? ( { +export const Pool = ({ pool }: PoolProps) => { const { t } = useTranslation('library'); const { memberCounter, addresses, id, state } = pool; const { isPoolSyncing } = useUi(); - const { meta } = useBondedPools(); const { validators } = useValidators(); const { setActiveTab } = usePoolsTabs(); const { openModal } = useOverlay().modal; const { membership } = usePoolMemberships(); + const { poolsNominations } = useBondedPools(); const { activeAccount } = useActiveAccounts(); const { addNotification } = useNotifications(); const { isReadOnlyAccount } = useImportedAccounts(); @@ -52,10 +52,10 @@ export const Pool = ({ pool, batchKey, batchIndex }: PoolProps) => { const currentCommission = getCurrentCommission(id); // get metadata from pools metabatch - const nominations = meta[batchKey]?.nominations ?? []; + const nominations = poolsNominations[pool.id]; // get pool targets from nominations metadata - const targets = nominations[batchIndex]?.targets ?? []; + const targets = nominations?.targets || []; // extract validator entries from pool targets const targetValidators = validators.filter(({ address }) => @@ -126,11 +126,7 @@ export const Pool = ({ pool, batchKey, batchIndex }: PoolProps) => {
- +
@@ -155,11 +151,7 @@ export const Pool = ({ pool, batchKey, batchIndex }: PoolProps) => { - + {displayJoin && ( diff --git a/src/library/Pool/types.ts b/src/library/Pool/types.ts index acaf7ae0b0..d720ac34e0 100644 --- a/src/library/Pool/types.ts +++ b/src/library/Pool/types.ts @@ -6,8 +6,6 @@ import type { DisplayFor } from 'types'; export interface PoolProps { pool: Pool; - batchKey: string; - batchIndex: number; } export interface Pool { diff --git a/src/library/PoolList/Default.tsx b/src/library/PoolList/Default.tsx index 9c470d015b..139ae4925a 100644 --- a/src/library/PoolList/Default.tsx +++ b/src/library/PoolList/Default.tsx @@ -33,7 +33,6 @@ import type { PoolListProps } from './types'; export const PoolList = ({ allowMoreCols, pagination, - batchKey = '', disableThrottle, allowSearch, pools, @@ -52,7 +51,7 @@ export const PoolList = ({ const { listFormat, setListFormat } = usePoolList(); const { getFilters, setMultiFilters, getSearchTerm, setSearchTerm } = useFilters(); - const { fetchPoolsMetaBatch, poolSearchFilter, meta } = useBondedPools(); + const { poolSearchFilter, poolsNominations } = useBondedPools(); const includes = getFilters('include', 'pools'); const excludes = getFilters('exclude', 'pools'); @@ -101,16 +100,15 @@ export const PoolList = ({ setPoolsDefault(pools); setListPools(pools); setFetched(true); - fetchPoolsMetaBatch(batchKey, pools, true); }; // handle filter / order update const handlePoolsFilterUpdate = ( filteredPools: any = Object.assign(poolsDefault) ) => { - filteredPools = applyFilter(includes, excludes, filteredPools, batchKey); + filteredPools = applyFilter(includes, excludes, filteredPools); if (searchTerm) { - filteredPools = poolSearchFilter(filteredPools, batchKey, searchTerm); + filteredPools = poolSearchFilter(filteredPools, searchTerm); } setListPools(filteredPools); setPage(1); @@ -120,8 +118,8 @@ export const PoolList = ({ const handleSearchChange = (e: React.FormEvent) => { const newValue = e.currentTarget.value; let filteredPools = Object.assign(poolsDefault); - filteredPools = applyFilter(includes, excludes, filteredPools, batchKey); - filteredPools = poolSearchFilter(filteredPools, batchKey, newValue); + filteredPools = applyFilter(includes, excludes, filteredPools); + filteredPools = poolSearchFilter(filteredPools, newValue); // ensure no duplicates filteredPools = filteredPools.filter( @@ -160,10 +158,10 @@ export const PoolList = ({ // List ui changes / validator changes trigger re-render of list. useEffect(() => { // only filter when pool nominations have been synced. - if (!isSyncing && meta[batchKey]?.nominations) { + if (!isSyncing && Object.keys(poolsNominations).length) { handlePoolsFilterUpdate(); } - }, [isSyncing, includes, excludes, meta]); + }, [isSyncing, includes, excludes, Object.keys(poolsNominations).length]); // Scroll to top of the window on every filter. useEffect(() => { @@ -262,11 +260,7 @@ export const PoolList = ({ }, }} > - + ))} diff --git a/src/library/PoolList/types.ts b/src/library/PoolList/types.ts index e99b0de132..e80be52056 100644 --- a/src/library/PoolList/types.ts +++ b/src/library/PoolList/types.ts @@ -11,7 +11,6 @@ export interface PoolListProps { allowMoreCols?: boolean; allowSearch?: boolean; pagination?: boolean; - batchKey?: string; disableThrottle?: boolean; refetchOnListUpdate?: string; allowListFormat?: boolean; diff --git a/src/library/SelectItems/Wrapper.ts b/src/library/SelectItems/Wrapper.ts index b7f6fb9b90..a6cec152f5 100644 --- a/src/library/SelectItems/Wrapper.ts +++ b/src/library/SelectItems/Wrapper.ts @@ -98,8 +98,8 @@ export const Wrapper = styled.div<{ props.$hoverBorder ? 'var(--accent-color-primary)' : props.$selected - ? 'var(--accent-color-primary)' - : 'var(--border-primary-color)'}; + ? 'var(--accent-color-primary)' + : 'var(--border-primary-color)'}; } > button { diff --git a/src/library/SideMenu/index.tsx b/src/library/SideMenu/index.tsx index ea17a6e3d6..33fef8f92e 100644 --- a/src/library/SideMenu/index.tsx +++ b/src/library/SideMenu/index.tsx @@ -69,8 +69,8 @@ export const SideMenu = () => { apiStatus === 'connecting' ? 'warning' : apiStatus === 'connected' - ? 'success' - : 'danger'; + ? 'success' + : 'danger'; return ( @@ -121,6 +121,7 @@ export const SideMenu = () => { - {mode === 'dark' ? ( - ) : ( - )} diff --git a/src/library/SubmitTx/Default.tsx b/src/library/SubmitTx/Default.tsx index cf5dacfc96..590378b281 100644 --- a/src/library/SubmitTx/Default.tsx +++ b/src/library/SubmitTx/Default.tsx @@ -26,7 +26,7 @@ export const Default = ({ submitting || !valid || !accountHasSigner(submitAddress) || !txFeesValid; return ( - <> +
@@ -42,6 +42,6 @@ export const Default = ({ pulse={!disabled} />
- +
); }; diff --git a/src/library/SubmitTx/ManualSign/Ledger.tsx b/src/library/SubmitTx/ManualSign/Ledger.tsx deleted file mode 100644 index 7be7f889ce..0000000000 --- a/src/library/SubmitTx/ManualSign/Ledger.tsx +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors -// SPDX-License-Identifier: GPL-3.0-only - -import { faSquarePen } from '@fortawesome/free-solid-svg-icons'; -import { ButtonHelp, ButtonSubmit } from '@polkadot-cloud/react'; -import React, { useEffect, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; -import type { LedgerResponse } from 'contexts/Hardware/types'; -import { useHelp } from 'contexts/Help'; -import { useTxMeta } from 'contexts/TxMeta'; -import { EstimatedTxFee } from 'library/EstimatedTxFee'; -import { useLedgerLoop } from 'library/Hooks/useLedgerLoop'; -import { useOverlay } from '@polkadot-cloud/react/hooks'; -import { useActiveAccounts } from 'contexts/ActiveAccounts'; -import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; -import type { LedgerAccount } from '@polkadot-cloud/react/types'; -import type { SubmitProps } from '../types'; - -export const Ledger = ({ - uid, - onSubmit, - submitting, - valid, - submitText, - buttons, - customEvent, - submitAddress, - displayFor, -}: SubmitProps & { buttons?: React.ReactNode[] }) => { - const { t } = useTranslation('library'); - const { - pairDevice, - transportResponse, - setIsExecuting, - resetStatusCodes, - getIsExecuting, - handleNewStatusCode, - isPaired, - getStatusCodes, - getFeedback, - setFeedback, - handleUnmount, - } = useLedgerHardware(); - const { openHelp } = useHelp(); - const { setModalResize } = useOverlay().modal; - const { activeAccount } = useActiveAccounts(); - const { accountHasSigner } = useImportedAccounts(); - const { getAccount } = useImportedAccounts(); - const { txFeesValid, setTxSignature, getTxSignature } = useTxMeta(); - - const getAddressIndex = () => { - return (getAccount(activeAccount) as LedgerAccount)?.index || 0; - }; - - // Ledger loop needs to keep track of whether this component is mounted. If it is unmounted then - // the loop will cancel & ledger metadata will be cleared up. isMounted needs to be given as a - // function so the interval fetches the real value. - const isMounted = useRef(true); - const getIsMounted = () => isMounted.current; - - const { handleLedgerLoop } = useLedgerLoop({ - tasks: ['sign_tx'], - options: { - accountIndex: getAddressIndex, - }, - mounted: getIsMounted, - }); - - // Handle new Ledger status report. - const handleLedgerStatusResponse = (response: LedgerResponse) => { - if (!response) return; - const { ack, statusCode, body } = response; - - if (statusCode === 'SignedPayload') { - if (uid !== body.uid) { - // UIDs do not match, so this is not the transaction we are waiting for. - setFeedback(t('wrongTransaction'), 'Wrong Transaction'); - resetStatusCodes(); - setTxSignature(null); - } else { - // Important: only set the signature (and therefore trigger the transaction submission) if - // UIDs match. - handleNewStatusCode(ack, statusCode); - setTxSignature(body.sig); - resetStatusCodes(); - } - setIsExecuting(false); - } else { - handleNewStatusCode(ack, statusCode); - } - }; - - // Resize modal on content change. - useEffect(() => { - setModalResize(); - }, [isPaired, getStatusCodes()]); - - // Listen for new Ledger status reports. - useEffect(() => { - if (getIsExecuting()) { - handleLedgerStatusResponse(transportResponse); - } - }, [transportResponse]); - - // Tidy up context state when this component is no longer mounted. - useEffect(() => { - return () => { - isMounted.current = false; - handleUnmount(); - }; - }, []); - - // Get the latest Ledger loop feedback. - const feedback = getFeedback(); - - // Help key based on Ledger status. - const helpKey = feedback?.helpKey; - - // The state under which submission is disabled. - const disabled = - submitting || !valid || !accountHasSigner(submitAddress) || !txFeesValid; - - return ( - <> -
- - {valid ? ( -

- {feedback?.message || t('submitTransaction')} - {helpKey ? ( - openHelp(helpKey)} - background="secondary" - /> - ) : null} -

- ) : ( -

...

- )} -
-
- {buttons} - {getTxSignature() !== null || submitting ? ( - onSubmit(customEvent)} - disabled={disabled} - pulse={!(disabled || getIsExecuting())} - /> - ) : ( - { - const paired = await pairDevice(); - if (paired) { - setIsExecuting(true); - handleLedgerLoop(); - } - }} - disabled={disabled || getIsExecuting()} - pulse={!(disabled || getIsExecuting())} - /> - )} -
- - ); -}; diff --git a/src/library/SubmitTx/ManualSign/Ledger/Submit.tsx b/src/library/SubmitTx/ManualSign/Ledger/Submit.tsx new file mode 100644 index 0000000000..cc50a5c703 --- /dev/null +++ b/src/library/SubmitTx/ManualSign/Ledger/Submit.tsx @@ -0,0 +1,88 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { faUsb } from '@fortawesome/free-brands-svg-icons'; +import { faSquarePen } from '@fortawesome/free-solid-svg-icons'; +import { ButtonSubmit } from '@polkadot-cloud/react'; +import type { LedgerAccount } from '@polkadot-cloud/react/types'; +import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; +import { getLedgerApp } from 'contexts/Hardware/Utils'; +import { useNetwork } from 'contexts/Network'; +import { useTxMeta } from 'contexts/TxMeta'; +import type { LedgerSubmitProps } from 'library/SubmitTx/types'; +import { useTranslation } from 'react-i18next'; + +export const Submit = ({ + displayFor, + submitting, + submitText, + onSubmit, + disabled, +}: LedgerSubmitProps) => { + const { t } = useTranslation('library'); + const { + handleSignTx, + getIsExecuting, + integrityChecked, + checkRuntimeVersion, + } = useLedgerHardware(); + const { network } = useNetwork(); + const { getTxSignature } = useTxMeta(); + const { getAccount } = useImportedAccounts(); + const { activeAccount } = useActiveAccounts(); + const { getTxPayload, getPayloadUid } = useTxMeta(); + const { appName } = getLedgerApp(network); + + const getAddressIndex = () => { + return (getAccount(activeAccount) as LedgerAccount)?.index || 0; + }; + + // Handle transaction submission + const handleTxSubmit = async () => { + const uid = getPayloadUid(); + const accountIndex = getAddressIndex(); + const payload = await getTxPayload(); + await handleSignTx(appName, uid, accountIndex, payload); + }; + + // Check device runtime version. + const handleCheckRuntimeVersion = async () => { + await checkRuntimeVersion(appName); + }; + + // Is the transaction ready to be submitted? + const txReady = (getTxSignature() !== null && integrityChecked) || submitting; + + // Button `onClick` handler depends whether integrityChecked and whether tx has been submitted. + const handleOnClick = !integrityChecked + ? handleCheckRuntimeVersion + : txReady + ? onSubmit + : handleTxSubmit; + + // Determine button text. + const text = !integrityChecked + ? t('confirm') + : txReady + ? submitText || '' + : getIsExecuting() + ? t('signing') + : t('sign'); + + // Button icon. + const icon = !integrityChecked ? faUsb : faSquarePen; + + return ( + + ); +}; diff --git a/src/library/SubmitTx/ManualSign/Ledger/index.tsx b/src/library/SubmitTx/ManualSign/Ledger/index.tsx new file mode 100644 index 0000000000..ff69e8a66c --- /dev/null +++ b/src/library/SubmitTx/ManualSign/Ledger/index.tsx @@ -0,0 +1,179 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { ButtonHelp, useEffectIgnoreInitial } from '@polkadot-cloud/react'; +import type { ReactNode } from 'react'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; +import type { LedgerResponse } from 'contexts/Hardware/Ledger/types'; +import { useHelp } from 'contexts/Help'; +import { useTxMeta } from 'contexts/TxMeta'; +import { EstimatedTxFee } from 'library/EstimatedTxFee'; +import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faCircleExclamation } from '@fortawesome/free-solid-svg-icons'; +import { useNetwork } from 'contexts/Network'; +import { getLedgerApp } from 'contexts/Hardware/Utils'; +import type { SubmitProps } from '../../types'; +import { Submit } from './Submit'; + +export const Ledger = ({ + uid, + onSubmit, + submitting, + valid, + submitText, + buttons, + submitAddress, + displayFor, +}: SubmitProps & { buttons?: ReactNode[] }) => { + const { t } = useTranslation('library'); + const { + setFeedback, + getFeedback, + integrityChecked, + handleUnmount, + getIsExecuting, + getStatusCode, + resetStatusCode, + runtimesInconsistent, + transportResponse, + setStatusCode, + } = useLedgerHardware(); + const { openHelp } = useHelp(); + const { network } = useNetwork(); + const { txFeesValid } = useTxMeta(); + const { setTxSignature } = useTxMeta(); + const { setModalResize } = useOverlay().modal; + const { accountHasSigner } = useImportedAccounts(); + const { appName } = getLedgerApp(network); + + // Handle new Ledger status report. + const handleLedgerStatusResponse = (response: LedgerResponse) => { + if (!response) return; + const { ack, statusCode, body } = response; + + if (statusCode === 'SignedPayload') { + if (uid !== body.uid) { + // UIDs do not match, so this is not the transaction we are waiting for. + setFeedback(t('wrongTransaction'), 'Wrong Transaction'); + setTxSignature(null); + } else { + // Important: only set the signature (and therefore trigger the transaction submission) if + // UIDs match. + setStatusCode(ack, statusCode); + setTxSignature(body.sig); + } + // Reset state pertaining to this transaction. + resetStatusCode(); + } else { + setStatusCode(ack, statusCode); + } + }; + + // Get the latest Ledger loop feedback. + const feedback = getFeedback(); + + // The state under which submission is disabled. + const disabled = + !accountHasSigner(submitAddress) || + !valid || + submitting || + !txFeesValid || + getIsExecuting(); + + // Resize modal on content change. + useEffect(() => { + setModalResize(); + }, [ + integrityChecked, + valid, + submitting, + txFeesValid, + getStatusCode(), + getIsExecuting(), + ]); + + // Listen for new Ledger status reports. + useEffectIgnoreInitial(() => { + handleLedgerStatusResponse(transportResponse); + }, [transportResponse]); + + // Tidy up context state when this component is no longer mounted. + useEffect(() => { + return () => { + handleUnmount(); + }; + }, []); + + return ( + <> +
+ +
+ + {runtimesInconsistent && ( +
+
+

+ {t('ledgerAppOutOfDate', { appName })} + + openHelp('Ledger App Not on Latest Runtime Version') + } + /> +

+
+
+ )} + +
+
+ {valid ? ( + <> +

+ {!valid ? ( + '...' + ) : ( + <> + + {feedback?.message + ? feedback.message + : !integrityChecked + ? t('ledgerConnectAndConfirm') + : `${t('deviceVerified')}. ${t('submitTransaction')}`} + + )} + {feedback?.helpKey && ( + { + if (feedback?.helpKey) openHelp(feedback.helpKey); + }} + /> + )} +

+ + ) : ( +

...

+ )} +
+
+ {buttons} + +
+
+ + ); +}; diff --git a/src/library/SubmitTx/ManualSign/Vault/index.tsx b/src/library/SubmitTx/ManualSign/Vault/index.tsx index e7c0875d7d..377458d223 100644 --- a/src/library/SubmitTx/ManualSign/Vault/index.tsx +++ b/src/library/SubmitTx/ManualSign/Vault/index.tsx @@ -31,7 +31,7 @@ export const Vault = ({ submitting || !valid || !accountHasSigner(submitAddress) || !txFeesValid; return ( - <> +
{valid ?

{t('submitTransaction')}

:

...

} @@ -65,6 +65,6 @@ export const Vault = ({ /> )}
- +
); }; diff --git a/src/library/SubmitTx/types.ts b/src/library/SubmitTx/types.ts index ee70723f8c..ded0a65bf2 100644 --- a/src/library/SubmitTx/types.ts +++ b/src/library/SubmitTx/types.ts @@ -26,3 +26,11 @@ export interface SubmitProps { export interface SignerPromptProps { submitAddress: MaybeAddress; } + +export interface LedgerSubmitProps { + onSubmit: () => void; + submitting: boolean; + displayFor?: DisplayFor; + disabled: boolean; + submitText?: string; +} diff --git a/src/locale/cn/help.json b/src/locale/cn/help.json index b162cd38bd..84dc7a4c98 100644 --- a/src/locale/cn/help.json +++ b/src/locale/cn/help.json @@ -126,6 +126,14 @@ "得到的收益金额取决于提名人和验证人自己在那个Era绑定了多少{NETWORK_UNIT}." ] ], + "ledgerAppNotOnLatestRuntimeVersion": [ + "Ledger App非最新运行时版本", + [ + "当您的Ledger应用程序未配置为最新的运行时元数据时,会出现此消息. 您仍然可以尝试提交您的交易,但可能会失败", + "若要防止此警告,请将您的Ledger设备更新为最新版本", + "运行时升级部署和Ledger应用程序更新之间通常会有延迟。当应用程序更新可用时, 您将能够在Ledger Live中更新应用程序" + ] + ], "ledgerHardwareWallets": [ "Ledger硬件钱包", [ diff --git a/src/locale/cn/library.json b/src/locale/cn/library.json index 9055db1d9b..6fc92282f9 100644 --- a/src/locale/cn/library.json +++ b/src/locale/cn/library.json @@ -52,6 +52,7 @@ "dayPoolPerformance": "天内提名池表现", "destroying": "销毁中", "destroyingPools": "正在销毁提名池", + "deviceVerified": "设备已验证", "disclaimer": "免责声明", "disconnected": "已断开", "displayingValidators": "正在显示 {{count}} 个验证人", @@ -90,6 +91,8 @@ "insertPayoutAddress": "输入收益到账地址", "invalid": "地址无效", "join": "加入提名池", + "ledgerAppOutOfDate": "您的{{appName}} Ledger应用程序未配置为最新运行时版本", + "ledgerConnectAndConfirm": "连接您的Ledger设备并确认其已被连接", "legalDisclosures": "法律论述", "listItemActive": "活跃", "locked": "己锁", diff --git a/src/locale/cn/modals.json b/src/locale/cn/modals.json index 982b7c3ed6..9ccf2e9dac 100644 --- a/src/locale/cn/modals.json +++ b/src/locale/cn/modals.json @@ -118,7 +118,14 @@ "joinPool": "加入池", "leavePool": "离开池", "ledgerAccount": "Ledger帐户", + "ledgerAccountFetched": "己获得Ledger账户", + "ledgerAccountImported": "已导入Ledger账户", + "ledgerAccountRemoved": "已删除Ledger账户", "ledgerAccounts": "正在显示Ledger帐户", + "ledgerDeviceBusy": "Ledger设备当前正被用于其他用途", + "ledgerFetchedAccount": "己获得Ledger账户 {{account}}", + "ledgerImportedAccount": "已导入Ledger账户 {{account}}", + "ledgerRemovedAccount": "已删除Ledger账户 {{account}}", "ledgerRequestTimeout": "Ledger 请求超时.请再试一次", "ledgerWillBeReset": "您的Ledger帐户列表将被重置,并且所有导入的帐户都将被删除", "lightClient": "轻客户端", @@ -129,6 +136,7 @@ "managePool": "管理池", "maxCommission": "最高佣金值", "maximumCommissionUpdated": "最高佣金值最新值", + "methodNotSupported": "调用方法不受支持,无法签署交易", "minDelayBetweenUpdates": "更新之间的最小延迟", "minutes": "分钟", "missingNesting": "该调用缺少内置支持,无法签署交易", diff --git a/src/locale/en/help.json b/src/locale/en/help.json index 6e510fe69f..d922d453c9 100644 --- a/src/locale/en/help.json +++ b/src/locale/en/help.json @@ -127,6 +127,14 @@ "The payout amounts received depend on how much {NETWORK_UNIT} the nominators, and validators themselves, had bonded for that era." ] ], + "ledgerAppNotOnLatestRuntimeVersion": [ + "Ledger App Not on Latest Runtime Version", + [ + "This message occurs when your Ledger app is not configured to the latest runtime metadata. You may still attempt to submit your transaction, but it may fail.", + "To prevent this warning, update your Ledger device to the latest version.", + "There is usually a delay between runtime upgrades deploying and Ledger app updates. When an app update is available, you will be able to update your app in Ledger Live." + ] + ], "ledgerHardwareWallets": [ "Ledger Hardware Wallets", [ diff --git a/src/locale/en/library.json b/src/locale/en/library.json index c1115ce4ab..6b826c2144 100644 --- a/src/locale/en/library.json +++ b/src/locale/en/library.json @@ -52,6 +52,7 @@ "dayPoolPerformance": "Day Pool Performance", "destroying": "Destroying", "destroyingPools": "Destroying Pools", + "deviceVerified": "Device verified", "disclaimer": "Disclaimer", "disconnected": "Disconnected", "displayingValidators_one": "Displaying {{count}} Validator", @@ -92,6 +93,8 @@ "insertPayoutAddress": "Insert a payout address", "invalid": "Address Invalid", "join": "Join", + "ledgerAppOutOfDate": "Your {{appName}} Ledger app is not configured to the latest runtime version", + "ledgerConnectAndConfirm": "Connect your Ledger device and confirm it is connected.", "legalDisclosures": "Legal Disclosures", "listItemActive": "Active", "locked": "Locked", diff --git a/src/locale/en/modals.json b/src/locale/en/modals.json index 74afe08f76..64be479534 100644 --- a/src/locale/en/modals.json +++ b/src/locale/en/modals.json @@ -127,8 +127,15 @@ "joinPool": "Join Pool", "leavePool": "Leave Pool", "ledgerAccount": "Ledger Account", + "ledgerAccountFetched": "Ledger Account Fetched", + "ledgerAccountImported": "Ledger Account Imported", + "ledgerAccountRemoved": "Ledger Account Removed", "ledgerAccounts_one": "Displaying {{count}} Ledger Account", "ledgerAccounts_other": " Displaying {{count}} Ledger Accounts", + "ledgerDeviceBusy": "The Ledger device is currently being used for something else.", + "ledgerFetchedAccount": "Fetched Ledger Account {{account}}", + "ledgerImportedAccount": "Imported Ledger Account {{account}}", + "ledgerRemovedAccount": "Removed Ledger Account {{account}}", "ledgerRequestTimeout": "The Ledger request timed out. Please try again.", "ledgerWillBeReset": "Your Ledger address list will be reset, and any imported accounts will be removed from the dashboard.", "lightClient": "Light Client", @@ -139,6 +146,7 @@ "managePool": "Manage Pool", "maxCommission": "Max Commission", "maximumCommissionUpdated": "Maximum Commission Updated", + "methodNotSupported": "Call method not supported, cannot sign.", "minDelayBetweenUpdates": "Min Delay Between Updates", "minutes": "Minutes", "missingNesting": "Call missing nesting support, cannot sign.", diff --git a/src/modals/Accounts/Account.tsx b/src/modals/Accounts/Account.tsx index 51b4d8bdd0..d055fedb74 100644 --- a/src/modals/Accounts/Account.tsx +++ b/src/modals/Accounts/Account.tsx @@ -6,7 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ellipsisFn, planckToUnit } from '@polkadot-cloud/utils'; import { useTranslation } from 'react-i18next'; import { ExtensionIcons } from '@polkadot-cloud/assets/extensions'; -import LedgerSVG from '@polkadot-cloud/assets/extensions/svg/ledger.svg?react'; +import LedgerSVG from '@polkadot-cloud/assets/extensions/svg/ledgersquare.svg?react'; import PolkadotVaultSVG from '@polkadot-cloud/assets/extensions/svg/polkadotvault.svg?react'; import { Polkicon } from '@polkadot-cloud/react'; import { useTransferOptions } from 'contexts/TransferOptions'; @@ -36,7 +36,7 @@ export const AccountButton = ({ const { setModalStatus } = useOverlay().modal; const { units, unit } = useNetwork().networkData; const { getTransferOptions } = useTransferOptions(); - const { freeBalance } = getTransferOptions(address || ''); + const { transferrableBalance } = getTransferOptions(address || ''); // Accumulate account data. const meta = getAccount(address || ''); @@ -50,8 +50,8 @@ export const AccountButton = ({ meta?.source === 'ledger' ? LedgerSVG : meta?.source === 'vault' - ? PolkadotVaultSVG - : ExtensionIcons[meta?.source || ''] || undefined; + ? PolkadotVaultSVG + : ExtensionIcons[meta?.source || ''] || undefined; // Determine if this account is active (active account or proxy). const isActive = @@ -125,7 +125,7 @@ export const AccountButton = ({
- {`${t('free')}: ${planckToUnit(freeBalance, units) + {`${t('free')}: ${planckToUnit(transferrableBalance, units) .decimalPlaces(3) .toFormat()} ${unit}`} diff --git a/src/modals/Accounts/Wrappers.ts b/src/modals/Accounts/Wrappers.ts index 01407c1aa1..51923b5de9 100644 --- a/src/modals/Accounts/Wrappers.ts +++ b/src/modals/Accounts/Wrappers.ts @@ -95,8 +95,14 @@ export const AccountWrapper = styled.div` font-size: 0.9rem; } .delegator { - width: 1rem; + width: 1.1rem; z-index: 0; + + > div { + width: 2rem; + height: 2rem; + top: 0.25rem; + } } .identicon { z-index: 1; diff --git a/src/modals/Accounts/types.ts b/src/modals/Accounts/types.ts index 8584ec8439..23d0cd6333 100644 --- a/src/modals/Accounts/types.ts +++ b/src/modals/Accounts/types.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only import type { PoolMembership } from 'contexts/Pools/types'; -import type { Proxy } from 'contexts/Proxies/type'; +import type { Proxy } from 'contexts/Proxies/types'; import type { MaybeAddress } from 'types'; export interface AccountItemProps { diff --git a/src/modals/Bond/index.tsx b/src/modals/Bond/index.tsx index 81104962d5..08037ed45f 100644 --- a/src/modals/Bond/index.tsx +++ b/src/modals/Bond/index.tsx @@ -41,14 +41,16 @@ export const Bond = () => { const { bondFor } = options; const isStaking = bondFor === 'nominator'; const isPooling = bondFor === 'pool'; - const { nominate, pool } = getTransferOptions(activeAccount); + const { nominate, transferrableBalance } = getTransferOptions(activeAccount); - const freeBalanceBn = - bondFor === 'nominator' + const freeToBond = planckToUnit( + (bondFor === 'nominator' ? nominate.totalAdditionalBond - : pool.totalAdditionalBond; + : transferrableBalance + ).minus(feeReserve), + units + ); - const freeBalance = planckToUnit(freeBalanceBn.minus(feeReserve), units); const largestTxFee = useBondGreatestFee({ bondFor }); // calculate any unclaimed pool rewards. @@ -58,7 +60,7 @@ export const Bond = () => { // local bond value. const [bond, setBond] = useState<{ bond: string }>({ - bond: freeBalance.toString(), + bond: freeToBond.toString(), }); // bond valid. @@ -68,7 +70,7 @@ export const Bond = () => { const [feedbackErrors, setFeedbackErrors] = useState([]); // bond minus tx fees. - const enoughToCoverTxFees: boolean = freeBalance + const enoughToCoverTxFees: boolean = freeToBond .minus(bond.bond) .isGreaterThan(planckToUnit(largestTxFee, units)); @@ -86,8 +88,8 @@ export const Bond = () => { // update bond value on task change. useEffect(() => { - setBond({ bond: freeBalance.toString() }); - }, [freeBalance.toString()]); + setBond({ bond: freeToBond.toString() }); + }, [freeToBond.toString()]); // determine whether this is a pool or staking transaction. const determineTx = (bondToSubmit: BigNumber) => { @@ -99,8 +101,8 @@ export const Bond = () => { const bondAsString = !bondValid ? '0' : bondToSubmit.isNaN() - ? '0' - : bondToSubmit.toString(); + ? '0' + : bondToSubmit.toString(); if (isPooling) { tx = api.tx.nominationPools.bondExtra({ diff --git a/src/modals/ClaimPayouts/Forms.tsx b/src/modals/ClaimPayouts/Forms.tsx index 98de20ecbd..a3bfd30a57 100644 --- a/src/modals/ClaimPayouts/Forms.tsx +++ b/src/modals/ClaimPayouts/Forms.tsx @@ -5,6 +5,7 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; import { ActionItem, ButtonSubmitInvert, + ModalPadding, ModalWarnings, } from '@polkadot-cloud/react'; import { planckToUnit } from '@polkadot-cloud/utils'; @@ -124,7 +125,7 @@ export const Forms = forwardRef( return (
-
+ {warnings.length > 0 ? ( {warnings.map((text, i) => ( @@ -141,7 +142,7 @@ export const Forms = forwardRef( />

{t('afterClaiming')}

-
+ acc.plus(cur), - new BigNumber(0) - ); - + const totalPayout = getTotalPayout(unclaimedPayout); const numPayouts = Object.values(unclaimedPayout).length; return ( @@ -33,7 +29,7 @@ export const Item = ({

- Era {era}: {numPayouts} + Era {era}:{' '} {t('pendingPayout', { count: numPayouts, })} diff --git a/src/modals/ClaimPayouts/Overview.tsx b/src/modals/ClaimPayouts/Overview.tsx index f1da7cc7e4..4eb2e58c8a 100644 --- a/src/modals/ClaimPayouts/Overview.tsx +++ b/src/modals/ClaimPayouts/Overview.tsx @@ -1,38 +1,43 @@ // Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { ModalNotes } from '@polkadot-cloud/react'; -import { forwardRef } from 'react'; +import { ModalNotes, ModalPadding } from '@polkadot-cloud/react'; +import type { Ref } from 'react'; +import { Fragment, forwardRef } from 'react'; import { usePayouts } from 'contexts/Payouts'; import { useTranslation } from 'react-i18next'; import { Item } from './Item'; import { ContentWrapper } from './Wrappers'; import type { OverviewProps } from './types'; +import { getTotalPayout } from './Utils'; export const Overview = forwardRef( - ({ setSection, setPayouts }: OverviewProps, ref: any) => { + ({ setSection, setPayouts }: OverviewProps, ref: Ref) => { const { t } = useTranslation('modals'); const { unclaimedPayouts } = usePayouts(); return ( -
+ {Object.entries(unclaimedPayouts || {}).map( - ([era, unclaimedPayout]: any, i: number) => ( - - ) + ([era, unclaimedPayout], i) => + getTotalPayout(unclaimedPayout).isZero() ? ( + + ) : ( + + ) )}

{t('claimsOnBehalf')}

{t('notToClaim')}

-
+
); } diff --git a/src/modals/ClaimPayouts/Utils.ts b/src/modals/ClaimPayouts/Utils.ts new file mode 100644 index 0000000000..3294145c46 --- /dev/null +++ b/src/modals/ClaimPayouts/Utils.ts @@ -0,0 +1,13 @@ +// Copyright 2023 @paritytech/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import BigNumber from 'bignumber.js'; +import type { EraUnclaimedPayouts } from 'contexts/Payouts/types'; + +export const getTotalPayout = ( + unclaimedPayout: EraUnclaimedPayouts +): BigNumber => + Object.values(unclaimedPayout).reduce( + (acc: BigNumber, cur: string) => acc.plus(cur), + new BigNumber(0) + ); diff --git a/src/modals/ClaimPayouts/Wrappers.ts b/src/modals/ClaimPayouts/Wrappers.ts index a449d8d90c..ab314645f5 100644 --- a/src/modals/ClaimPayouts/Wrappers.ts +++ b/src/modals/ClaimPayouts/Wrappers.ts @@ -4,17 +4,10 @@ import styled from 'styled-components'; export const ContentWrapper = styled.div` - border-radius: 1rem; display: flex; flex-flow: column nowrap; - flex-basis: 50%; - flex-grow: 0; - flex-shrink: 1; - height: fit-content; - - .padding { - padding: 0 1rem; - } + border-radius: 1rem; + width: 100%; > div:last-child { margin-bottom: 0; diff --git a/src/modals/ClaimPayouts/index.tsx b/src/modals/ClaimPayouts/index.tsx index 9dc9048520..6af9431fda 100644 --- a/src/modals/ClaimPayouts/index.tsx +++ b/src/modals/ClaimPayouts/index.tsx @@ -21,7 +21,7 @@ export const ClaimPayouts = () => { const { t } = useTranslation('modals'); const { notEnoughFunds } = useTxMeta(); const { unclaimedPayouts } = usePayouts(); - const { setModalHeight } = useOverlay().modal; + const { setModalHeight, modalMaxHeight } = useOverlay().modal; // Active modal section. const [section, setSectionState] = useState(0); @@ -70,6 +70,9 @@ export const ClaimPayouts = () => { </ModalFixedTitle> <ModalMotionTwoSection + style={{ + maxHeight: modalMaxHeight - (headerRef.current?.clientHeight || 0), + }} animate={sectionRef.current === 0 ? 'home' : 'next'} transition={{ duration: 0.5, @@ -85,17 +88,21 @@ export const ClaimPayouts = () => { }, }} > - <Overview - setSection={setSection} - setPayouts={setPayouts} - ref={overviewRef} - /> - <Forms - ref={formsRef} - payouts={payouts} - setPayouts={setPayouts} - setSection={setSection} - /> + <div className="section"> + <Overview + setSection={setSection} + setPayouts={setPayouts} + ref={overviewRef} + /> + </div> + <div className="section"> + <Forms + ref={formsRef} + payouts={payouts} + setPayouts={setPayouts} + setSection={setSection} + /> + </div> </ModalMotionTwoSection> </ModalSection> ); diff --git a/src/modals/Connect/Extension.tsx b/src/modals/Connect/Extension.tsx index 9f283a53eb..95770fb55e 100644 --- a/src/modals/Connect/Extension.tsx +++ b/src/modals/Connect/Extension.tsx @@ -11,7 +11,7 @@ import { useExtensionAccounts, } from '@polkadot-cloud/react/hooks'; import { useNotifications } from 'contexts/Notifications'; -import { ExtensionIcons } from '@polkadot-cloud/assets/extensions'; +import { getExtensionIcon } from '@polkadot-cloud/assets/extensions'; import { ExtensionInner } from './Wrappers'; import type { ExtensionProps } from './types'; @@ -43,7 +43,8 @@ export const Extension = ({ meta, size, flag }: ExtensionProps) => { } }; - const Icon = ExtensionIcons[id || ''] || undefined; + const Icon = getExtensionIcon(id); + // determine message to be displayed based on extension status. let statusJsx; switch (extensionsStatus[id]) { @@ -82,7 +83,7 @@ export const Extension = ({ meta, size, flag }: ExtensionProps) => { ) : null} <div className="row icon"> - <Icon style={{ width: size, height: size }} /> + {Icon && <Icon style={{ width: size, height: size }} />} </div> <div className="status"> {flag && flag} diff --git a/src/modals/Connect/Ledger.tsx b/src/modals/Connect/Ledger.tsx index 0129bb3e22..89336e406a 100644 --- a/src/modals/Connect/Ledger.tsx +++ b/src/modals/Connect/Ledger.tsx @@ -17,7 +17,7 @@ import { import { inChrome } from '@polkadot-cloud/utils'; import React from 'react'; import { useHelp } from 'contexts/Help'; -import LedgerLogoSVG from 'img/ledgerLogo.svg?react'; +import LedgerLogoSVG from '@polkadot-cloud/assets/extensions/svg/ledger.svg?react'; import { useOverlay } from '@polkadot-cloud/react/hooks'; import { useNetwork } from 'contexts/Network'; @@ -40,7 +40,7 @@ export const Ledger = (): React.ReactElement => { <ButtonHelp onClick={() => openHelp('Ledger Hardware Wallets')} /> </div> <div className="row"> - <LedgerLogoSVG className="logo mono" /> + <LedgerLogoSVG className="logo" /> </div> <div className="row margin"> <ButtonText diff --git a/src/modals/Connect/Proxies.tsx b/src/modals/Connect/Proxies.tsx index fad0651e5e..af683a23b3 100644 --- a/src/modals/Connect/Proxies.tsx +++ b/src/modals/Connect/Proxies.tsx @@ -29,16 +29,17 @@ import type { ListWithInputProps } from './types'; export const Proxies = ({ setInputOpen, inputOpen }: ListWithInputProps) => { const { t } = useTranslation('modals'); const { openHelp } = useHelp(); - const { accounts } = useImportedAccounts(); - const { getAccount } = useImportedAccounts(); - const { delegates, handleDeclareDelegate } = useProxies(); + const { accounts, getAccount } = useImportedAccounts(); + const { handleDeclareDelegate, formatProxiesToDelegates } = useProxies(); // Filter delegates to only show those who are imported in the dashboard. + const delegates = formatProxiesToDelegates(); const importedDelegates = Object.fromEntries( Object.entries(delegates).filter(([delegate]) => accounts.find((a) => a.address === delegate) ) ); + return ( <> <ActionWithButton> diff --git a/src/modals/Connect/ReadOnly.tsx b/src/modals/Connect/ReadOnly.tsx index ba916f7504..f8200382f9 100644 --- a/src/modals/Connect/ReadOnly.tsx +++ b/src/modals/Connect/ReadOnly.tsx @@ -20,6 +20,7 @@ import { useOverlay } from '@polkadot-cloud/react/hooks'; import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; import type { ExternalAccount } from '@polkadot-cloud/react/types'; +import { useExternalAccounts } from 'contexts/Connect/ExternalAccounts'; import { ActionWithButton, ManualAccount, @@ -32,7 +33,8 @@ export const ReadOnly = ({ setInputOpen, inputOpen }: ListWithInputProps) => { const { openHelp } = useHelp(); const { accounts } = useImportedAccounts(); const { setModalResize } = useOverlay().modal; - const { forgetExternalAccounts, addExternalAccount } = useOtherAccounts(); + const { addExternalAccount, forgetExternalAccounts } = useExternalAccounts(); + const { forgetOtherAccounts, addOrReplaceOtherAccount } = useOtherAccounts(); // get all external accounts const externalAccountsOnly = accounts.filter( @@ -44,11 +46,13 @@ export const ReadOnly = ({ setInputOpen, inputOpen }: ListWithInputProps) => { ({ addedBy }) => addedBy === 'user' ); - // forget account - const forgetAccount = (account: ExternalAccount) => { + const handleForgetExternalAccount = (account: ExternalAccount) => { forgetExternalAccounts([account]); + // forget the account from state only if it has not replaced by a `system` external account. + if (account.addedBy === 'user') forgetOtherAccounts([account]); setModalResize(); }; + return ( <> <ActionWithButton> @@ -77,7 +81,10 @@ export const ReadOnly = ({ setInputOpen, inputOpen }: ListWithInputProps) => { resetOnSuccess defaultLabel={t('inputAddress')} successCallback={async (value: string) => { - addExternalAccount(value, 'user'); + const result = addExternalAccount(value, 'user'); + if (result) + addOrReplaceOtherAccount(result.account, result.type); + return true; }} /> @@ -96,9 +103,7 @@ export const ReadOnly = ({ setInputOpen, inputOpen }: ListWithInputProps) => { </div> <ButtonSecondary text={t('forget')} - onClick={() => { - forgetAccount(a); - }} + onClick={() => handleForgetExternalAccount(a)} /> </ManualAccount> ))} diff --git a/src/modals/Connect/index.tsx b/src/modals/Connect/index.tsx index cfe0379b95..12e67906db 100644 --- a/src/modals/Connect/index.tsx +++ b/src/modals/Connect/index.tsx @@ -35,8 +35,13 @@ export const Connect = () => { const { extensionsStatus } = useExtensions(); const { replaceModal, setModalHeight, modalMaxHeight } = useOverlay().modal; - const web = ExtensionsArray.filter((a) => a.id !== 'polkadot-js'); - const pjs = ExtensionsArray.filter((a) => a.id === 'polkadot-js'); + const inNova = !!window?.walletExtension?.isNovaWallet || false; + + // If in Nova Wallet, only display it in extension options, otherwise, remove developer tool extensions from web options. + const developerTools = ['polkadot-js']; + const web = !inNova + ? ExtensionsArray.filter((a) => !developerTools.includes(a.id)) + : ExtensionsArray.filter((a) => a.id === 'polkadot-js'); const installed = web.filter((a) => Object.keys(extensionsStatus).find((key) => key === a.id) @@ -81,6 +86,48 @@ export const Connect = () => { }; }, []); + // Hardware connect options JSX. + const ConnectHardwareJSX = ( + <> + <ActionItem text={t('hardware')} /> + <ExtensionsWrapper> + <SelectItems layout="two-col"> + {[Vault, Ledger].map((Item: AnyFunction, i: number) => ( + <Item key={`hardware_item_${i}`} /> + ))} + </SelectItems> + </ExtensionsWrapper> + </> + ); + + // Web extension connect options JSX. + const ConnectExtensionsJSX = ( + <> + <ActionItem text={t('web')} /> + <ExtensionsWrapper> + <SelectItems layout="two-col"> + {installed.concat(other).map((extension, i) => ( + <Extension key={`extension_item_${i}`} meta={extension} /> + ))} + </SelectItems> + </ExtensionsWrapper> + </> + ); + + // Display hardware before extensions. + // If in Nova Wallet, display extensions before hardware. + const ConnectCombinedJSX = !inNova ? ( + <> + {ConnectHardwareJSX} + {ConnectExtensionsJSX} + </> + ) : ( + <> + {ConnectExtensionsJSX} + {ConnectHardwareJSX} + </> + ); + return ( <> <ModalSection type="carousel"> @@ -143,32 +190,24 @@ export const Connect = () => { > <div className="section"> <ModalPadding horizontalOnly ref={homeRef}> - <ActionItem text={t('hardware')} /> - <ExtensionsWrapper> - <SelectItems layout="two-col"> - {[Vault, Ledger].map((Item: AnyFunction, i: number) => ( - <Item key={`hardware_item_${i}`} /> - ))} - </SelectItems> - </ExtensionsWrapper> - - <ActionItem text={t('web')} /> - <ExtensionsWrapper> - <SelectItems layout="two-col"> - {installed.concat(other).map((extension, i) => ( - <Extension key={`extension_item_${i}`} meta={extension} /> - ))} - </SelectItems> - </ExtensionsWrapper> - - <ActionItem text={t('developerTools')} /> - <ExtensionsWrapper> - <SelectItems layout="two-col"> - {pjs.map((extension, i) => ( - <Extension key={`extension_item_${i}`} meta={extension} /> - ))} - </SelectItems> - </ExtensionsWrapper> + {ConnectCombinedJSX} + {!inNova && ( + <> + <ActionItem text={t('developerTools')} /> + <ExtensionsWrapper> + <SelectItems layout="two-col"> + {ExtensionsArray.filter( + (a) => a.id === 'polkadot-js' + ).map((extension, i) => ( + <Extension + key={`extension_item_${i}`} + meta={extension} + /> + ))} + </SelectItems> + </ExtensionsWrapper> + </> + )} </ModalPadding> </div> <div className="section"> diff --git a/src/modals/ImportLedger/Addresses.tsx b/src/modals/ImportLedger/Addresses.tsx index 09124a9b04..bb046a7e8e 100644 --- a/src/modals/ImportLedger/Addresses.tsx +++ b/src/modals/ImportLedger/Addresses.tsx @@ -5,7 +5,7 @@ import { faArrowDown } from '@fortawesome/free-solid-svg-icons'; import { ButtonText, HardwareAddress, Polkicon } from '@polkadot-cloud/react'; import { ellipsisFn, unescape } from '@polkadot-cloud/utils'; import { useTranslation } from 'react-i18next'; -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; import { getLocalLedgerAddresses } from 'contexts/Hardware/Utils'; import { usePrompt } from 'contexts/Prompt'; import { Confirm } from 'library/Import/Confirm'; @@ -14,23 +14,22 @@ import { AddressesWrapper } from 'library/Import/Wrappers'; import type { AnyJson } from 'types'; import { useNetwork } from 'contexts/Network'; import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; +import { useLedgerAccounts } from 'contexts/Hardware/Ledger/LedgerAccounts'; -export const Addresess = ({ addresses, handleLedgerLoop }: AnyJson) => { +export const Addresess = ({ addresses, onGetAddress }: AnyJson) => { const { t } = useTranslation('modals'); const { network } = useNetwork(); + const { getIsExecuting } = useLedgerHardware(); + const isExecuting = getIsExecuting(); + const { openPromptWith } = usePrompt(); + const { renameOtherAccount } = useOtherAccounts(); const { - getIsExecuting, ledgerAccountExists, - renameLedgerAccount, + getLedgerAccount, addLedgerAccount, removeLedgerAccount, - setIsExecuting, - getLedgerAccount, - pairDevice, - } = useLedgerHardware(); - const isExecuting = getIsExecuting(); - const { openPromptWith } = usePrompt(); - const { renameOtherAccount } = useOtherAccounts(); + renameLedgerAccount, + } = useLedgerAccounts(); const source = 'ledger'; const renameHandler = (address: string, newName: string) => { @@ -101,12 +100,7 @@ export const Addresess = ({ addresses, handleLedgerLoop }: AnyJson) => { text={isExecuting ? t('gettingAccount') : t('getAnotherAccount')} disabled={isExecuting} onClick={async () => { - // re-pair the device if it has been disconnected. - const paired = await pairDevice(); - if (paired) { - setIsExecuting(true); - handleLedgerLoop(); - } + await onGetAddress(); }} /> </div> diff --git a/src/modals/ImportLedger/Manage.tsx b/src/modals/ImportLedger/Manage.tsx index 7eef81fb0e..fc26844224 100644 --- a/src/modals/ImportLedger/Manage.tsx +++ b/src/modals/ImportLedger/Manage.tsx @@ -3,11 +3,11 @@ import { HardwareStatusBar } from '@polkadot-cloud/react'; import { useTranslation } from 'react-i18next'; -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; import { getLedgerApp } from 'contexts/Hardware/Utils'; import { useHelp } from 'contexts/Help'; import { usePrompt } from 'contexts/Prompt'; -import LedgerSVG from '@polkadot-cloud/assets/extensions/svg/ledger.svg?react'; +import LedgerSVG from '@polkadot-cloud/assets/extensions/svg/ledgersquare.svg?react'; import { Heading } from 'library/Import/Heading'; import type { AnyJson } from 'types'; import { useOverlay } from '@polkadot-cloud/react/hooks'; @@ -17,17 +17,16 @@ import { Reset } from './Reset'; export const Manage = ({ addresses, - handleLedgerLoop, + onGetAddress, removeLedgerAddress, }: AnyJson) => { const { t } = useTranslation(); + const { openHelp } = useHelp(); const { network } = useNetwork(); - const { setIsExecuting, getIsExecuting, resetStatusCodes, getFeedback } = - useLedgerHardware(); const { openPromptWith } = usePrompt(); const { replaceModal } = useOverlay().modal; - const { openHelp } = useHelp(); - + const { handleResetLedgerTask, getIsExecuting, getFeedback } = + useLedgerHardware(); const { appName, Icon } = getLedgerApp(network); const isExecuting = getIsExecuting(); @@ -42,23 +41,22 @@ export const Manage = ({ <> <Heading connectTo="Ledger" - title={appName} Icon={Icon} - disabled={!addresses.length} + title={appName} handleReset={() => { openPromptWith( <Reset removeLedgerAddress={removeLedgerAddress} />, 'small' ); }} + disabled={!addresses.length} /> <Addresess addresses={addresses} - handleLedgerLoop={handleLedgerLoop} removeLedgerAddress={removeLedgerAddress} + onGetAddress={onGetAddress} /> <HardwareStatusBar - show Icon={LedgerSVG} text={feedback?.message || fallbackMessage} help={ @@ -70,13 +68,11 @@ export const Manage = ({ : undefined } inProgress={isExecuting} - handleCancel={() => { - setIsExecuting(false); - resetStatusCodes(); - }} + handleCancel={() => handleResetLedgerTask()} handleDone={() => replaceModal({ key: 'Connect', options: { disableScroll: true } }) } + show t={{ tDone: t('done', { ns: 'library' }), tCancel: t('cancel', { ns: 'library' }), diff --git a/src/modals/ImportLedger/Reset.tsx b/src/modals/ImportLedger/Reset.tsx index 667999963b..c345ddb791 100644 --- a/src/modals/ImportLedger/Reset.tsx +++ b/src/modals/ImportLedger/Reset.tsx @@ -4,9 +4,8 @@ import { registerSaEvent } from 'Utils'; import { ButtonMono, ButtonMonoInvert } from '@polkadot-cloud/react'; import { useTranslation } from 'react-i18next'; -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; import { getLocalLedgerAddresses } from 'contexts/Hardware/Utils'; -import type { LedgerAddress } from 'contexts/Hardware/types'; +import type { LedgerAddress } from 'contexts/Hardware/Ledger/types'; import { usePrompt } from 'contexts/Prompt'; import { ConfirmWrapper } from 'library/Import/Wrappers'; import type { AnyJson } from 'types'; @@ -14,6 +13,7 @@ import { useOverlay } from '@polkadot-cloud/react/hooks'; import { useOtherAccounts } from 'contexts/Connect/OtherAccounts'; import type { LedgerAccount } from '@polkadot-cloud/react/types'; import { useNetwork } from 'contexts/Network'; +import { useLedgerAccounts } from 'contexts/Hardware/Ledger/LedgerAccounts'; export const Reset = ({ removeLedgerAddress }: AnyJson) => { const { t } = useTranslation('modals'); @@ -21,12 +21,12 @@ export const Reset = ({ removeLedgerAddress }: AnyJson) => { const { setStatus } = usePrompt(); const { replaceModal } = useOverlay().modal; const { forgetOtherAccounts } = useOtherAccounts(); - const { ledgerAccounts, removeLedgerAccount } = useLedgerHardware(); + const { removeLedgerAccount, ledgerAccounts } = useLedgerAccounts(); const removeAccounts = () => { // Remove imported Ledger accounts. ledgerAccounts.forEach((account: LedgerAccount) => { - removeLedgerAccount(account.address); + removeLedgerAccount(account.address, false); }); forgetOtherAccounts(ledgerAccounts); diff --git a/src/modals/ImportLedger/Splash.tsx b/src/modals/ImportLedger/Splash.tsx index 9edd8fc95f..72b329222a 100644 --- a/src/modals/ImportLedger/Splash.tsx +++ b/src/modals/ImportLedger/Splash.tsx @@ -5,56 +5,38 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; import { ButtonHelp, ButtonSecondary } from '@polkadot-cloud/react'; import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; import { useHelp } from 'contexts/Help'; import { useTheme } from 'contexts/Themes'; -import LogoSVG from 'img/ledgerLogo.svg?react'; +import LedgerLogoSvg from '@polkadot-cloud/assets/extensions/svg/ledger.svg?react'; import type { AnyFunction } from 'types'; import { useOverlay } from '@polkadot-cloud/react/hooks'; import { SplashWrapper } from './Wrappers'; -export const Splash = ({ handleLedgerLoop }: AnyFunction) => { +export const Splash = ({ onGetAddress }: AnyFunction) => { const { t } = useTranslation('modals'); - const { - getStatusCodes, - isPaired, - getIsExecuting, - setIsExecuting, - pairDevice, - getFeedback, - } = useLedgerHardware(); + const { getStatusCode, getIsExecuting, getFeedback } = useLedgerHardware(); const { mode } = useTheme(); const { openHelp } = useHelp(); const { replaceModal, setModalResize } = useOverlay().modal; - const statusCodes = getStatusCodes(); + const statusCode = getStatusCode(); const initFetchAddress = async () => { - const paired = await pairDevice(); - if (paired) { - setIsExecuting(true); - handleLedgerLoop(); - } + await onGetAddress(); }; const fallbackMessage = t('checking'); const feedback = getFeedback(); const helpKey = feedback?.helpKey; - // Initialise listeners for Ledger IO. - useEffect(() => { - if (isPaired !== 'paired') { - pairDevice(); - } - }, []); - - // Once the device is paired, start `handleLedgerLoop`. + // Automatically fetch first address. useEffect(() => { initFetchAddress(); - }, [isPaired]); + }, []); - // Resize modal on new message - useEffect(() => setModalResize(), [statusCodes, feedback]); + // Resize modal on new message. + useEffect(() => setModalResize(), [statusCode, feedback]); return ( <> @@ -72,7 +54,7 @@ export const Splash = ({ handleLedgerLoop }: AnyFunction) => { </div> <SplashWrapper> <div className="icon"> - <LogoSVG + <LedgerLogoSvg style={{ transform: 'scale(0.6)' }} opacity={mode === 'dark' ? 0.5 : 0.1} /> @@ -96,7 +78,7 @@ export const Splash = ({ handleLedgerLoop }: AnyFunction) => { <div className="button"> <ButtonSecondary text={ - statusCodes[0]?.statusCode === 'DeviceNotConnected' + statusCode?.statusCode === 'DeviceNotConnected' ? t('continue') : t('tryAgain') } diff --git a/src/modals/ImportLedger/index.tsx b/src/modals/ImportLedger/index.tsx index 5796a5f766..e787541e14 100644 --- a/src/modals/ImportLedger/index.tsx +++ b/src/modals/ImportLedger/index.tsx @@ -3,52 +3,39 @@ import { registerSaEvent } from 'Utils'; import { ellipsisFn, setStateWithRef } from '@polkadot-cloud/utils'; -import React, { useEffect, useRef, useState } from 'react'; -import { useLedgerHardware } from 'contexts/Hardware/Ledger'; -import { getLocalLedgerAddresses } from 'contexts/Hardware/Utils'; -import type { LedgerAddress, LedgerResponse } from 'contexts/Hardware/types'; -import { useLedgerLoop } from 'library/Hooks/useLedgerLoop'; +import type { FC } from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; +import { getLedgerApp, getLocalLedgerAddresses } from 'contexts/Hardware/Utils'; +import type { + LedgerAddress, + LedgerResponse, +} from 'contexts/Hardware/Ledger/types'; import type { AnyJson } from 'types'; -import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { + useEffectIgnoreInitial, + useOverlay, +} from '@polkadot-cloud/react/hooks'; import { useNetwork } from 'contexts/Network'; +import { useNotifications } from 'contexts/Notifications'; +import { useTranslation } from 'react-i18next'; import { Manage } from './Manage'; import { Splash } from './Splash'; -export const ImportLedger: React.FC = () => { +export const ImportLedger: FC = () => { + const { t } = useTranslation('modals'); const { network } = useNetwork(); const { setModalResize } = useOverlay().modal; const { transportResponse, - getIsExecuting, - setIsExecuting, - resetStatusCodes, - handleNewStatusCode, - isPaired, - getStatusCodes, + resetStatusCode, + setStatusCode, + getStatusCode, handleUnmount, + handleGetAddress, } = useLedgerHardware(); - - // Gets the next non-imported address index. - const getNextAddressIndex = () => { - if (!addressesRef.current.length) { - return 0; - } - return addressesRef.current[addressesRef.current.length - 1].index + 1; - }; - - // Ledger loop needs to keep track of whether this component is mounted. If it is unmounted then - // the loop will cancel & ledger metadata will be cleared up. isMounted needs to be given as a - // function so the interval fetches the real value. - const isMounted = useRef(true); - const getIsMounted = () => isMounted.current; - - const { handleLedgerLoop } = useLedgerLoop({ - tasks: ['get_address'], - options: { - accountIndex: getNextAddressIndex, - }, - mounted: getIsMounted, - }); + const { addNotification } = useNotifications(); + const { appName } = getLedgerApp(network); // Store addresses retreived from Ledger device. Defaults to local addresses. const [addresses, setAddresses] = useState<LedgerAddress[]>( @@ -56,16 +43,22 @@ export const ImportLedger: React.FC = () => { ); const addressesRef = useRef(addresses); + // Gets the next non-imported address index. + const getNextAddressIndex = () => { + if (!addressesRef.current.length) return 0; + return addressesRef.current[addressesRef.current.length - 1].index + 1; + }; + + const onGetAddress = async () => { + await handleGetAddress(appName, getNextAddressIndex()); + }; + const removeLedgerAddress = (address: string) => { let newLedgerAddresses = getLocalLedgerAddresses(); newLedgerAddresses = newLedgerAddresses.filter((a) => { - if (a.address !== address) { - return true; - } - if (a.network !== network) { - return true; - } + if (a.address !== address) return true; + if (a.network !== network) return true; return false; }); if (!newLedgerAddresses.length) { @@ -97,7 +90,7 @@ export const ImportLedger: React.FC = () => { if (!response) return; const { ack, statusCode, body, options } = response; - handleNewStatusCode(ack, statusCode); + setStatusCode(ack, statusCode); if (statusCode === 'ReceivedAddress') { const newAddress = body.map(({ pubKey, address }: LedgerAddress) => ({ @@ -111,46 +104,45 @@ export const ImportLedger: React.FC = () => { // update the full list of local ledger addresses with new entry. const newAddresses = getLocalLedgerAddresses() .filter((a: AnyJson) => { - if (a.address !== newAddress.address) { - return true; - } - if (a.network !== network) { - return true; - } + if (a.address !== newAddress[0].address) return true; + if (a.network !== network) return true; return false; }) .concat(newAddress); localStorage.setItem('ledger_addresses', JSON.stringify(newAddresses)); - setIsExecuting(false); - // store only those accounts on the current network in state. setStateWithRef( newAddresses.filter((a) => a.network === network), setAddresses, addressesRef ); - resetStatusCodes(); + resetStatusCode(); registerSaEvent(`${network.toLowerCase()}_ledger_account_fetched`); + + // trigger notification. + addNotification({ + title: t('ledgerAccountFetched'), + subtitle: t('ledgerFetchedAccount', { + account: ellipsisFn(newAddress[0].address), + }), + }); } }; // Resize modal on content change. useEffect(() => { setModalResize(); - }, [isPaired, getStatusCodes(), addressesRef.current]); + }, [getStatusCode(), addressesRef.current]); // Listen for new Ledger status reports. - useEffect(() => { - if (getIsExecuting()) { - handleLedgerStatusResponse(transportResponse); - } + useEffectIgnoreInitial(() => { + handleLedgerStatusResponse(transportResponse); }, [transportResponse]); // Tidy up context state when this component is no longer mounted. useEffect(() => { return () => { - isMounted.current = false; handleUnmount(); }; }, []); @@ -158,12 +150,12 @@ export const ImportLedger: React.FC = () => { return ( <> {!addressesRef.current.length ? ( - <Splash handleLedgerLoop={handleLedgerLoop} /> + <Splash onGetAddress={onGetAddress} /> ) : ( <Manage addresses={addressesRef.current} removeLedgerAddress={removeLedgerAddress} - handleLedgerLoop={handleLedgerLoop} + onGetAddress={onGetAddress} /> )} </> diff --git a/src/modals/ImportVault/Reader.tsx b/src/modals/ImportVault/Reader.tsx index 2897bef97e..af9127302b 100644 --- a/src/modals/ImportVault/Reader.tsx +++ b/src/modals/ImportVault/Reader.tsx @@ -6,7 +6,7 @@ import { ButtonSecondary } from '@polkadot-cloud/react'; import { isValidAddress } from '@polkadot-cloud/utils'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useVaultHardware } from 'contexts/Hardware/Vault'; +import { useVaultAccounts } from 'contexts/Hardware/Vault/VaultAccounts'; import { usePrompt } from 'contexts/Prompt'; import { QRViewerWrapper } from 'library/Import/Wrappers'; import { QrScanSignature } from 'library/QRCode/ScanSignature'; @@ -23,7 +23,7 @@ export const Reader = () => { const { addOtherAccounts } = useOtherAccounts(); const { setStatus: setPromptStatus } = usePrompt(); const { addVaultAccount, vaultAccountExists, vaultAccounts } = - useVaultHardware(); + useVaultAccounts(); // Store data from QR Code scanner. const [qrData, setQrData] = useState<any>(undefined); @@ -61,12 +61,12 @@ export const Reader = () => { qrData === undefined ? `${t('waitingForQRCode')}` : isValidAddress(qrData) - ? formatAccountSs58(qrData, ss58) - ? `${t('differentNetworkAddress')}` - : vaultAccountExists(qrData) - ? `${t('accountAlreadyImported')}` - : `${t('addressReceived')}` - : `${t('invalidAddress')}` + ? formatAccountSs58(qrData, ss58) + ? `${t('differentNetworkAddress')}` + : vaultAccountExists(qrData) + ? `${t('accountAlreadyImported')}` + : `${t('addressReceived')}` + : `${t('invalidAddress')}` ); }, [qrData]); diff --git a/src/modals/ImportVault/index.tsx b/src/modals/ImportVault/index.tsx index e8ac1487a7..ea4c8d0db1 100644 --- a/src/modals/ImportVault/index.tsx +++ b/src/modals/ImportVault/index.tsx @@ -11,7 +11,7 @@ import { } from '@polkadot-cloud/react'; import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { useVaultHardware } from 'contexts/Hardware/Vault'; +import { useVaultAccounts } from 'contexts/Hardware/Vault/VaultAccounts'; import { usePrompt } from 'contexts/Prompt'; import PolkadotVaultSVG from '@polkadot-cloud/assets/extensions/svg/polkadotvault.svg?react'; import { Confirm } from 'library/Import/Confirm'; @@ -37,7 +37,7 @@ export const ImportVault = () => { addVaultAccount, removeVaultAccount, getVaultAccount, - } = useVaultHardware(); + } = useVaultAccounts(); const { setModalResize } = useOverlay().modal; const source = 'vault'; diff --git a/src/modals/JoinPool/index.tsx b/src/modals/JoinPool/index.tsx index 10fee24f7f..2e2e052d56 100644 --- a/src/modals/JoinPool/index.tsx +++ b/src/modals/JoinPool/index.tsx @@ -46,13 +46,15 @@ export const JoinPool = () => { const { id: poolId, setActiveTab } = options; - const { totalPossibleBond, totalAdditionalBond } = - getTransferOptions(activeAccount).pool; + const { + pool: { totalPossibleBond }, + transferrableBalance, + } = getTransferOptions(activeAccount); const largestTxFee = useBondGreatestFee({ bondFor: 'pool' }); // if we are bonding, subtract tx fees from bond amount - const freeBondAmount = BigNumber.max(totalAdditionalBond.minus(txFees), 0); + const freeBondAmount = BigNumber.max(transferrableBalance.minus(txFees), 0); // local bond value const [bond, setBond] = useState<{ bond: string }>({ diff --git a/src/modals/ManageFastUnstake/index.tsx b/src/modals/ManageFastUnstake/index.tsx index 1e3fe4ca14..a898906b71 100644 --- a/src/modals/ManageFastUnstake/index.tsx +++ b/src/modals/ManageFastUnstake/index.tsx @@ -48,12 +48,11 @@ export const ManageFastUnstake = () => { const { checked } = meta; const controller = getBondedAccount(activeAccount); const allTransferOptions = getTransferOptions(activeAccount); - const { nominate, freeBalance } = allTransferOptions; - const { totalUnlockChuncks } = nominate; + const { nominate, transferrableBalance } = allTransferOptions; + const { totalUnlockChunks } = nominate; - const enoughForDeposit = freeBalance - .minus(feeReserve) - .isGreaterThanOrEqualTo(fastUnstakeDeposit); + const enoughForDeposit = + transferrableBalance.isGreaterThanOrEqualTo(fastUnstakeDeposit); // valid to submit transaction const [valid, setValid] = useState<boolean>(false); @@ -64,16 +63,16 @@ export const ManageFastUnstake = () => { ((!isFastUnstaking && enoughForDeposit && isExposed === false && - totalUnlockChuncks === 0) || + totalUnlockChunks === 0) || isFastUnstaking) ); }, [ isExposed, fastUnstakeErasToCheckPerBlock, - totalUnlockChuncks, + totalUnlockChunks, isFastUnstaking, fastUnstakeDeposit, - freeBalance, + transferrableBalance, feeReserve, ]); @@ -123,10 +122,10 @@ export const ManageFastUnstake = () => { ); } - if (totalUnlockChuncks > 0) { + if (totalUnlockChunks > 0) { warnings.push( `${t('fastUnstakeWarningUnlocksActive', { - count: totalUnlockChuncks, + count: totalUnlockChunks, })} ${t('fastUnstakeWarningUnlocksActiveMore')}` ); } diff --git a/src/modals/ManagePool/Forms/ClaimCommission.tsx b/src/modals/ManagePool/Forms/ClaimCommission.tsx index 2abe71ed60..90e0b2e9a0 100644 --- a/src/modals/ManagePool/Forms/ClaimCommission.tsx +++ b/src/modals/ManagePool/Forms/ClaimCommission.tsx @@ -6,6 +6,7 @@ import { ActionItem, ButtonSubmitInvert, ModalNotes, + ModalPadding, ModalWarnings, } from '@polkadot-cloud/react'; import { greaterThanZero, planckToUnit, rmCommas } from '@polkadot-cloud/utils'; @@ -70,7 +71,7 @@ export const ClaimCommission = ({ setSection }: any) => { return ( <> - <div className="padding"> + <ModalPadding horizontalOnly> {warnings.length > 0 ? ( <ModalWarnings withMargin> {warnings.map((text, i) => ( @@ -87,7 +88,7 @@ export const ClaimCommission = ({ setSection }: any) => { <ModalNotes> <p>{t('sentToCommissionPayee')}</p> </ModalNotes> - </div> + </ModalPadding> <SubmitTx valid={valid} buttons={[ diff --git a/src/modals/ManagePool/Forms/Commission.tsx b/src/modals/ManagePool/Forms/Commission.tsx index 123b1475f3..46b4d2d014 100644 --- a/src/modals/ManagePool/Forms/Commission.tsx +++ b/src/modals/ManagePool/Forms/Commission.tsx @@ -6,6 +6,7 @@ import { ActionItem, ButtonHelp, ButtonSubmitInvert, + ModalPadding, ModalWarnings, } from '@polkadot-cloud/react'; import { rmCommas } from '@polkadot-cloud/utils'; @@ -79,14 +80,12 @@ export const Commission = ({ setSection, incrementCalculateHeight }: any) => { const [commission, setCommission] = useState<number>(initialCommission); // Max commission enabled. - const [maxCommissionEnabled, setMaxCommissionEnabled] = useState<boolean>( - !!maxCommissionSet - ); + const [maxCommissionEnabled, setMaxCommissionEnabled] = + useState<boolean>(!!maxCommissionSet); // Change rate enabled. - const [changeRateEnabled, setChangeRateEnabled] = useState<boolean>( - !!changeRateSet - ); + const [changeRateEnabled, setChangeRateEnabled] = + useState<boolean>(!!changeRateSet); // Store the commission payee. const [payee, setPayee] = useState<MaybeAddress>(initialPayee); @@ -448,7 +447,7 @@ export const Commission = ({ setSection, incrementCalculateHeight }: any) => { return ( <> - <div className="padding"> + <ModalPadding horizontalOnly> {warnings.length > 0 ? ( <ModalWarnings withMargin> {warnings.map((text, i) => ( @@ -634,7 +633,7 @@ export const Commission = ({ setSection, incrementCalculateHeight }: any) => { </p> </SliderWrapper> )} - </div> + </ModalPadding> <SubmitTx valid={valid} buttons={[ diff --git a/src/modals/ManagePool/Forms/LeavePool.tsx b/src/modals/ManagePool/Forms/LeavePool.tsx index f9e79b5c71..0af498e586 100644 --- a/src/modals/ManagePool/Forms/LeavePool.tsx +++ b/src/modals/ManagePool/Forms/LeavePool.tsx @@ -5,6 +5,7 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; import { ActionItem, ButtonSubmitInvert, + ModalPadding, ModalWarnings, } from '@polkadot-cloud/react'; import { @@ -118,7 +119,7 @@ export const LeavePool = ({ setSection }: any) => { return ( <> - <div className="padding"> + <ModalPadding horizontalOnly> {warnings.length > 0 ? ( <ModalWarnings withMargin> {warnings.map((text, i) => ( @@ -133,7 +134,7 @@ export const LeavePool = ({ setSection }: any) => { valueKey="bondDurationFormatted" deps={[bondDuration]} /> - </div> + </ModalPadding> <SubmitTx valid={bondValid} buttons={[ diff --git a/src/modals/ManagePool/Forms/SetClaimPermission.tsx b/src/modals/ManagePool/Forms/SetClaimPermission.tsx index 0bf3035701..874bf0c3aa 100644 --- a/src/modals/ManagePool/Forms/SetClaimPermission.tsx +++ b/src/modals/ManagePool/Forms/SetClaimPermission.tsx @@ -2,7 +2,11 @@ // SPDX-License-Identifier: GPL-3.0-only import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { ButtonSubmitInvert, ModalWarnings } from '@polkadot-cloud/react'; +import { + ButtonSubmitInvert, + ModalPadding, + ModalWarnings, +} from '@polkadot-cloud/react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; @@ -72,7 +76,7 @@ export const SetClaimPermission = ({ setSection, section }: any) => { return ( <> - <div className="padding"> + <ModalPadding horizontalOnly> {warnings.length > 0 ? ( <ModalWarnings withMargin> {warnings.map((text, i) => ( @@ -90,7 +94,7 @@ export const SetClaimPermission = ({ setSection, section }: any) => { setClaimPermission(val); }} /> - </div> + </ModalPadding> <SubmitTx valid={valid && claimPermission !== membership?.claimPermission} buttons={[ diff --git a/src/modals/ManagePool/Forms/SetMetadata.tsx b/src/modals/ManagePool/Forms/SetMetadata.tsx index 1ed361ca57..166eae4be6 100644 --- a/src/modals/ManagePool/Forms/SetMetadata.tsx +++ b/src/modals/ManagePool/Forms/SetMetadata.tsx @@ -3,7 +3,11 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; import { u8aToString, u8aUnwrapBytes } from '@polkadot/util'; -import { ButtonSubmitInvert, ModalWarnings } from '@polkadot-cloud/react'; +import { + ButtonSubmitInvert, + ModalPadding, + ModalWarnings, +} from '@polkadot-cloud/react'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useApi } from 'contexts/Api'; @@ -22,7 +26,7 @@ export const SetMetadata = ({ setSection, section }: any) => { const { setModalStatus } = useOverlay().modal; const { activeAccount } = useActiveAccounts(); const { isOwner, selectedActivePool } = useActivePools(); - const { bondedPools, meta } = useBondedPools(); + const { bondedPools, poolsMetaData } = useBondedPools(); const { getSignerWarnings } = useSignerWarnings(); const poolId = selectedActivePool?.id; @@ -39,9 +43,7 @@ export const SetMetadata = ({ setSection, section }: any) => { ({ addresses }) => addresses.stash === selectedActivePool?.addresses.stash ); if (pool) { - const metadataBatch = meta.bonded_pools?.metadata ?? []; - const batchIndex = bondedPools.indexOf(pool); - setMetadata(u8aToString(u8aUnwrapBytes(metadataBatch[batchIndex]))); + setMetadata(u8aToString(u8aUnwrapBytes(poolsMetaData[Number(pool.id)]))); } }, [section]); @@ -80,7 +82,7 @@ export const SetMetadata = ({ setSection, section }: any) => { return ( <> - <div className="padding"> + <ModalPadding horizontalOnly> {warnings.length > 0 ? ( <ModalWarnings withMargin> {warnings.map((text, i) => ( @@ -99,7 +101,7 @@ export const SetMetadata = ({ setSection, section }: any) => { value={metadata ?? ''} /> <p>{t('storedOnChain')}</p> - </div> + </ModalPadding> <SubmitTx valid={valid} buttons={[ diff --git a/src/modals/ManagePool/Forms/SetState.tsx b/src/modals/ManagePool/Forms/SetState.tsx index 2262eb9f41..0b88df13f2 100644 --- a/src/modals/ManagePool/Forms/SetState.tsx +++ b/src/modals/ManagePool/Forms/SetState.tsx @@ -5,6 +5,7 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; import { ActionItem, ButtonSubmitInvert, + ModalPadding, ModalWarnings, } from '@polkadot-cloud/react'; import { useEffect, useState } from 'react'; @@ -129,7 +130,7 @@ export const SetState = ({ setSection, task }: any) => { return ( <> - <div className="padding"> + <ModalPadding horizontalOnly> {warnings.length > 0 ? ( <ModalWarnings withMargin> {warnings.map((text, i) => ( @@ -139,7 +140,7 @@ export const SetState = ({ setSection, task }: any) => { ) : null} {content.title} {content.message} - </div> + </ModalPadding> <SubmitTx valid={valid} buttons={[ diff --git a/src/modals/ManagePool/Tasks.tsx b/src/modals/ManagePool/Tasks.tsx index 4b65f14a3f..d678f4cf7a 100644 --- a/src/modals/ManagePool/Tasks.tsx +++ b/src/modals/ManagePool/Tasks.tsx @@ -26,124 +26,122 @@ export const Tasks = forwardRef(({ setSection, setTask }: any, ref: any) => { return ( <ContentWrapper> - <div className="padding"> - <div className="items" ref={ref} style={{ paddingBottom: '1.5rem' }}> - <div style={{ paddingBottom: '0.75rem' }}> - {poolDestroying && <Warning text={t('beingDestroyed')} />} - </div> - {isOwner() && ( - <> - {globalMaxCommission > 0 && ( - <> - <ButtonOption - onClick={() => { - setSection(1); - setTask('claim_commission'); - }} - > - <div> - <h3>{t('claimCommission')}</h3> - <p>{t('claimOutstandingCommission')}</p> - </div> - </ButtonOption> - <ButtonOption - onClick={() => { - setSection(1); - setTask('manage_commission'); - }} - > - <div> - <h3>{t('manageCommission')}</h3> - <p>{t('updatePoolCommission')}</p> - </div> - </ButtonOption> - </> - )} - </> - )} - <ButtonOption - onClick={() => { - setSection(1); - setTask('set_claim_permission'); - }} - > - <div> - <h3>{t('updateClaimPermission')}</h3> - <p>{t('updateWhoClaimRewards')}</p> - </div> - </ButtonOption> - - {isOwner() && ( - <ButtonOption - disabled={poolDestroying} - onClick={() => { - setSection(1); - setTask('set_pool_metadata'); - }} - > - <div> - <h3>{t('renamePool')}</h3> - <p>{t('updateName')}</p> - </div> - </ButtonOption> - )} - {(isOwner() || isBouncer()) && ( - <> - {poolLocked ? ( + <div className="items" ref={ref} style={{ paddingBottom: '1.5rem' }}> + <div style={{ paddingBottom: '0.75rem' }}> + {poolDestroying && <Warning text={t('beingDestroyed')} />} + </div> + {isOwner() && ( + <> + {globalMaxCommission > 0 && ( + <> <ButtonOption - disabled={poolDestroying} onClick={() => { setSection(1); - setTask('unlock_pool'); + setTask('claim_commission'); }} > <div> - <h3>{t('unlockPool')}</h3> - <p>{t('allowToJoin')}</p> + <h3>{t('claimCommission')}</h3> + <p>{t('claimOutstandingCommission')}</p> </div> </ButtonOption> - ) : ( <ButtonOption - disabled={poolDestroying} onClick={() => { setSection(1); - setTask('lock_pool'); + setTask('manage_commission'); }} > <div> - <h3>{t('lockPool')}</h3> - <p>{t('stopJoiningPool')}</p> + <h3>{t('manageCommission')}</h3> + <p>{t('updatePoolCommission')}</p> </div> </ButtonOption> - )} + </> + )} + </> + )} + <ButtonOption + onClick={() => { + setSection(1); + setTask('set_claim_permission'); + }} + > + <div> + <h3>{t('updateClaimPermission')}</h3> + <p>{t('updateWhoClaimRewards')}</p> + </div> + </ButtonOption> + + {isOwner() && ( + <ButtonOption + disabled={poolDestroying} + onClick={() => { + setSection(1); + setTask('set_pool_metadata'); + }} + > + <div> + <h3>{t('renamePool')}</h3> + <p>{t('updateName')}</p> + </div> + </ButtonOption> + )} + {(isOwner() || isBouncer()) && ( + <> + {poolLocked ? ( + <ButtonOption + disabled={poolDestroying} + onClick={() => { + setSection(1); + setTask('unlock_pool'); + }} + > + <div> + <h3>{t('unlockPool')}</h3> + <p>{t('allowToJoin')}</p> + </div> + </ButtonOption> + ) : ( <ButtonOption disabled={poolDestroying} onClick={() => { setSection(1); - setTask('destroy_pool'); + setTask('lock_pool'); }} > <div> - <h3>{t('destroyPool')}</h3> - <p>{t('changeToDestroy')}</p> + <h3>{t('lockPool')}</h3> + <p>{t('stopJoiningPool')}</p> </div> </ButtonOption> - </> - )} - {isMember() && !isDepositor() && active?.isGreaterThan(0) && ( + )} <ButtonOption + disabled={poolDestroying} onClick={() => { setSection(1); - setTask('leave_pool'); + setTask('destroy_pool'); }} > <div> - <h3>{t('leavePool')}</h3> - <p>{t('unbondFundsLeavePool')}</p> + <h3>{t('destroyPool')}</h3> + <p>{t('changeToDestroy')}</p> </div> </ButtonOption> - )} - </div> + </> + )} + {isMember() && !isDepositor() && active?.isGreaterThan(0) && ( + <ButtonOption + onClick={() => { + setSection(1); + setTask('leave_pool'); + }} + > + <div> + <h3>{t('leavePool')}</h3> + <p>{t('unbondFundsLeavePool')}</p> + </div> + </ButtonOption> + )} </div> </ContentWrapper> ); diff --git a/src/modals/ManagePool/Wrappers.ts b/src/modals/ManagePool/Wrappers.ts index 2683239755..8feb9748f8 100644 --- a/src/modals/ManagePool/Wrappers.ts +++ b/src/modals/ManagePool/Wrappers.ts @@ -4,26 +4,10 @@ import styled from 'styled-components'; export const ContentWrapper = styled.div` - border-radius: 1rem; display: flex; flex-flow: column nowrap; - flex-basis: 50%; - min-width: 50%; - height: auto; - flex-grow: 1; - - .padding { - padding: 0 1rem 1rem 1rem; - - h2 { - margin-bottom: 0.5rem; - } - - input { - font-family: InterBold, sans-serif; - margin-top: 0.5rem; - } - } + border-radius: 1rem; + width: 100%; .items { position: relative; @@ -31,8 +15,6 @@ export const ContentWrapper = styled.div` border-bottom: none; width: auto; border-radius: 0.75rem; - overflow: hidden; - overflow-y: auto; z-index: 1; width: 100%; diff --git a/src/modals/ManagePool/index.tsx b/src/modals/ManagePool/index.tsx index cc7ee659eb..4964bbc4ea 100644 --- a/src/modals/ManagePool/index.tsx +++ b/src/modals/ManagePool/index.tsx @@ -4,6 +4,7 @@ import { ModalFixedTitle, ModalMotionTwoSection, + ModalPadding, ModalSection, } from '@polkadot-cloud/react'; import { useEffect, useRef, useState } from 'react'; @@ -12,14 +13,16 @@ import { useActivePools } from 'contexts/Pools/ActivePools'; import { Title } from 'library/Modal/Title'; import { useTxMeta } from 'contexts/TxMeta'; import { useOverlay } from '@polkadot-cloud/react/hooks'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; import { Forms } from './Forms'; import { Tasks } from './Tasks'; export const ManagePool = () => { const { t } = useTranslation('modals'); const { notEnoughFunds } = useTxMeta(); - const { setModalHeight } = useOverlay().modal; + const { integrityChecked } = useLedgerHardware(); const { isOwner, selectedActivePool } = useActivePools(); + const { setModalHeight, modalMaxHeight } = useOverlay().modal; // modal task const [task, setTask] = useState<string>(); @@ -37,8 +40,7 @@ export const ManagePool = () => { const tasksRef = useRef<HTMLDivElement>(null); const formsRef = useRef<HTMLDivElement>(null); - // Resize modal on state change. - useEffect(() => { + const refreshModalHeight = () => { let height = headerRef.current?.clientHeight || 0; if (section === 0) { height += tasksRef.current?.clientHeight || 0; @@ -46,7 +48,13 @@ export const ManagePool = () => { height += formsRef.current?.clientHeight || 0; } setModalHeight(height); + }; + + // Resize modal on state change. + useEffect(() => { + refreshModalHeight(); }, [ + integrityChecked, section, task, notEnoughFunds, @@ -54,6 +62,13 @@ export const ManagePool = () => { selectedActivePool?.bondedPool?.state, ]); + useEffect(() => { + window.addEventListener('resize', refreshModalHeight); + return () => { + window.removeEventListener('resize', refreshModalHeight); + }; + }, []); + return ( <ModalSection type="carousel"> <ModalFixedTitle ref={headerRef}> @@ -63,6 +78,9 @@ export const ManagePool = () => { /> </ModalFixedTitle> <ModalMotionTwoSection + style={{ + maxHeight: modalMaxHeight - (headerRef.current?.clientHeight || 0), + }} animate={section === 0 ? 'home' : 'next'} transition={{ duration: 0.5, @@ -78,14 +96,21 @@ export const ManagePool = () => { }, }} > - <Tasks setSection={setSection} setTask={setTask} ref={tasksRef} /> - <Forms - setSection={setSection} - task={task} - section={section} - ref={formsRef} - incrementCalculateHeight={incrementCalculateHeight} - /> + <div className="section"> + <ModalPadding horizontalOnly> + <Tasks setSection={setSection} setTask={setTask} ref={tasksRef} /> + </ModalPadding> + </div> + + <div className="section"> + <Forms + setSection={setSection} + task={task} + section={section} + ref={formsRef} + incrementCalculateHeight={incrementCalculateHeight} + /> + </div> </ModalMotionTwoSection> </ModalSection> ); diff --git a/src/modals/Networks/Wrapper.ts b/src/modals/Networks/Wrapper.ts index 0c3826700b..24f213b5c1 100644 --- a/src/modals/Networks/Wrapper.ts +++ b/src/modals/Networks/Wrapper.ts @@ -148,6 +148,18 @@ export const ConnectionsWrapper = styled.div` padding-right: 1rem; } + .provider { + padding: 0 0.25rem; + display: flex; + align-items: center; + + @media (max-width: ${SectionFullWidthThreshold - 400}px) { + position: relative; + top: -0.5rem; + margin-bottom: 1rem; + } + } + @media (max-width: ${SectionFullWidthThreshold - 400}px) { flex-basis: 100%; &:first-child { diff --git a/src/modals/Networks/index.tsx b/src/modals/Networks/index.tsx index 2fc4db7963..44a68cfe6f 100644 --- a/src/modals/Networks/index.tsx +++ b/src/modals/Networks/index.tsx @@ -101,13 +101,7 @@ export const Networks = () => { <h4 className="selected">{t('selected')}</h4> )} </ConnectionButton> - <div - style={{ - padding: '0 0.25rem', - display: 'flex', - alignItems: 'center', - }} - > + <div className="provider"> <p>{t('provider')}:</p> <ButtonTertiary text={rpcEndpoint} diff --git a/src/modals/UnlockChunks/Forms.tsx b/src/modals/UnlockChunks/Forms.tsx index 131dc0ebbf..14ddbc3977 100644 --- a/src/modals/UnlockChunks/Forms.tsx +++ b/src/modals/UnlockChunks/Forms.tsx @@ -5,6 +5,7 @@ import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; import { ActionItem, ButtonSubmitInvert, + ModalPadding, ModalWarnings, } from '@polkadot-cloud/react'; import { planckToUnit, rmCommas } from '@polkadot-cloud/utils'; @@ -125,7 +126,7 @@ export const Forms = forwardRef( return ( <ContentWrapper> <div ref={ref}> - <div className="padding"> + <ModalPadding horizontalOnly> {warnings.length > 0 ? ( <ModalWarnings withMargin> {warnings.map((text, i) => ( @@ -157,7 +158,7 @@ export const Forms = forwardRef( </> )} </div> - </div> + </ModalPadding> <SubmitTx fromController={isStaking} valid={valid} diff --git a/src/modals/UnlockChunks/Overview.tsx b/src/modals/UnlockChunks/Overview.tsx index cdd235215e..2d40f8f490 100644 --- a/src/modals/UnlockChunks/Overview.tsx +++ b/src/modals/UnlockChunks/Overview.tsx @@ -3,7 +3,7 @@ import { faCheckCircle, faClock } from '@fortawesome/free-regular-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ButtonSubmit, ModalNotes } from '@polkadot-cloud/react'; +import { ButtonSubmit, ModalNotes, ModalPadding } from '@polkadot-cloud/react'; import { planckToUnit } from '@polkadot-cloud/utils'; import BigNumber from 'bignumber.js'; import { getUnixTime } from 'date-fns'; @@ -62,7 +62,7 @@ export const Overview = forwardRef( return ( <ContentWrapper> - <div className="padding" ref={ref}> + <ModalPadding horizontalOnly ref={ref}> <StatsWrapper> <StatWrapper> <div className="inner"> @@ -140,7 +140,7 @@ export const Overview = forwardRef( <p> {isStaking ? ` ${t('rebondUnlock')}` : null}</p> {!isStaking ? <p>{t('unlockChunk')}</p> : null} </ModalNotes> - </div> + </ModalPadding> </ContentWrapper> ); } diff --git a/src/modals/UnlockChunks/Wrappers.ts b/src/modals/UnlockChunks/Wrappers.ts index 304d36900b..db6f19e162 100644 --- a/src/modals/UnlockChunks/Wrappers.ts +++ b/src/modals/UnlockChunks/Wrappers.ts @@ -4,15 +4,10 @@ import styled from 'styled-components'; export const ContentWrapper = styled.div` - border-radius: 1rem; display: flex; flex-flow: column nowrap; - flex-basis: 50%; - flex: 1; - - .padding { - padding: 0 1rem; - } + border-radius: 1rem; + width: 100%; > div:last-child { margin-bottom: 0; diff --git a/src/modals/UnlockChunks/index.tsx b/src/modals/UnlockChunks/index.tsx index b4f124e0df..5775f960e7 100644 --- a/src/modals/UnlockChunks/index.tsx +++ b/src/modals/UnlockChunks/index.tsx @@ -15,6 +15,7 @@ import { Title } from 'library/Modal/Title'; import { useTxMeta } from 'contexts/TxMeta'; import { useOverlay } from '@polkadot-cloud/react/hooks'; import { useActiveAccounts } from 'contexts/ActiveAccounts'; +import { useLedgerHardware } from 'contexts/Hardware/Ledger/LedgerHardware'; import { Forms } from './Forms'; import { Overview } from './Overview'; @@ -23,11 +24,13 @@ export const UnlockChunks = () => { const { config: { options }, setModalHeight, + modalMaxHeight, } = useOverlay().modal; const { notEnoughFunds } = useTxMeta(); const { getStashLedger } = useBalances(); const { activeAccount } = useActiveAccounts(); const { getPoolUnlocking } = useActivePools(); + const { integrityChecked } = useLedgerHardware(); const { bondFor } = options || {}; // get the unlocking per bondFor @@ -89,7 +92,14 @@ export const UnlockChunks = () => { // resize modal on state change useEffect(() => { setModalHeight(getModalHeight()); - }, [task, calculateHeight, notEnoughFunds, sectionRef.current, unlocking]); + }, [ + task, + calculateHeight, + notEnoughFunds, + sectionRef.current, + unlocking, + integrityChecked, + ]); // resize this modal on window resize useEffect(() => { @@ -105,6 +115,9 @@ export const UnlockChunks = () => { <Title title={t('unlocks')} fixed /> </ModalFixedTitle> <ModalMotionTwoSection + style={{ + maxHeight: modalMaxHeight - (headerRef.current?.clientHeight || 0), + }} animate={sectionRef.current === 0 ? 'home' : 'next'} transition={{ duration: 0.5, @@ -120,21 +133,25 @@ export const UnlockChunks = () => { }, }} > - <Overview - unlocking={unlocking} - bondFor={bondFor} - setSection={setSection} - setUnlock={setUnlock} - setTask={setTask} - ref={overviewRef} - /> - <Forms - incrementCalculateHeight={incrementCalculateHeight} - setSection={setSection} - unlock={unlock} - task={task} - ref={formsRef} - /> + <div className="section"> + <Overview + unlocking={unlocking} + bondFor={bondFor} + setSection={setSection} + setUnlock={setUnlock} + setTask={setTask} + ref={overviewRef} + /> + </div> + <div className="section"> + <Forms + incrementCalculateHeight={incrementCalculateHeight} + setSection={setSection} + unlock={unlock} + task={task} + ref={formsRef} + /> + </div> </ModalMotionTwoSection> </ModalSection> ); diff --git a/src/modals/UpdatePayee/index.tsx b/src/modals/UpdatePayee/index.tsx index 2b4865c34f..35f5000dcb 100644 --- a/src/modals/UpdatePayee/index.tsx +++ b/src/modals/UpdatePayee/index.tsx @@ -79,10 +79,10 @@ export const UpdatePayee = () => { const payeeToSubmit = !isComplete() ? 'Staked' : selected.destination === 'Account' - ? { - Account: selected.account, - } - : selected.destination; + ? { + Account: selected.account, + } + : selected.destination; tx = api.tx.staking.setPayee(payeeToSubmit); return tx; diff --git a/src/pages/Nominate/Active/ManageBond.tsx b/src/pages/Nominate/Active/ManageBond.tsx index 6b7805fea3..2ea40e9406 100644 --- a/src/pages/Nominate/Active/ManageBond.tsx +++ b/src/pages/Nominate/Active/ManageBond.tsx @@ -46,7 +46,7 @@ export const ManageBond = () => { const allTransferOptions = getTransferOptions(activeAccount); const { freeBalance, edReserved } = allTransferOptions; - const { totalUnlocking, totalUnlocked, totalUnlockChuncks } = + const { totalUnlocking, totalUnlocked, totalUnlockChunks } = allTransferOptions.nominate; const totalFree = BigNumber.max( 0, @@ -111,11 +111,15 @@ export const ManageBond = () => { onClick={() => openModal({ key: 'UnlockChunks', - options: { bondFor: 'nominator', disableWindowResize: true }, + options: { + bondFor: 'nominator', + disableWindowResize: true, + disableScroll: true, + }, size: 'sm', }) } - text={String(totalUnlockChuncks ?? 0)} + text={String(totalUnlockChunks ?? 0)} /> </ButtonRow> </CardHeaderWrapper> diff --git a/src/pages/Nominate/Active/Status/NominationStatus.tsx b/src/pages/Nominate/Active/Status/NominationStatus.tsx index 3760c0c130..b1219d5ad5 100644 --- a/src/pages/Nominate/Active/Status/NominationStatus.tsx +++ b/src/pages/Nominate/Active/Status/NominationStatus.tsx @@ -88,28 +88,28 @@ export const NominationStatus = ({ !showButtons ? [] : !inSetup() - ? !isUnstaking - ? [unstakeButton] - : [] - : isNetworkSyncing - ? [] - : [ - { - title: startTitle, - icon: faChevronCircleRight, - transform: 'grow-1', - disabled: - !isReady || - isReadOnlyAccount(activeAccount) || - !activeAccount, - onClick: () => { - registerSaEvent( - `${network.toLowerCase()}_nominate_setup_button_pressed` - ); - setOnNominatorSetup(true); - }, - }, - ] + ? !isUnstaking + ? [unstakeButton] + : [] + : isNetworkSyncing + ? [] + : [ + { + title: startTitle, + icon: faChevronCircleRight, + transform: 'grow-1', + disabled: + !isReady || + isReadOnlyAccount(activeAccount) || + !activeAccount, + onClick: () => { + registerSaEvent( + `${network.toLowerCase()}_nominate_setup_button_pressed` + ); + setOnNominatorSetup(true); + }, + }, + ] } buttonType={buttonType} /> diff --git a/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx b/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx index 8fc09bd1e8..1de876680a 100644 --- a/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx +++ b/src/pages/Nominate/Active/Status/UnclaimedPayoutsStatus.tsx @@ -15,10 +15,10 @@ import { useImportedAccounts } from 'contexts/Connect/ImportedAccounts'; export const UnclaimedPayoutsStatus = () => { const { t } = useTranslation(); - const { isReady } = useApi(); const { networkData: { units }, } = useNetwork(); + const { isReady } = useApi(); const { openModal } = useOverlay().modal; const { unclaimedPayouts } = usePayouts(); const { activeAccount } = useActiveAccounts(); @@ -44,7 +44,8 @@ export const UnclaimedPayoutsStatus = () => { ), }} buttons={ - Object.keys(unclaimedPayouts || {}).length > 0 + Object.keys(unclaimedPayouts || {}).length > 0 && + !totalUnclaimed.isZero() ? [ { title: t('claim', { ns: 'modals' }), @@ -57,6 +58,7 @@ export const UnclaimedPayoutsStatus = () => { size: 'sm', options: { disableWindowResize: true, + disableScroll: true, }, }), }, diff --git a/src/pages/Nominate/Active/UnstakePrompts.tsx b/src/pages/Nominate/Active/UnstakePrompts.tsx index 8cf09fc07c..25f2776f3c 100644 --- a/src/pages/Nominate/Active/UnstakePrompts.tsx +++ b/src/pages/Nominate/Active/UnstakePrompts.tsx @@ -23,7 +23,7 @@ export const UnstakePrompts = () => { const { isNetworkSyncing } = useUi(); const { isFastUnstaking, isUnstaking, getFastUnstakeText } = useUnstaking(); const { getTransferOptions } = useTransferOptions(); - const { active, totalUnlockChuncks, totalUnlocked, totalUnlocking } = + const { active, totalUnlockChunks, totalUnlocked, totalUnlocking } = getTransferOptions(activeAccount).nominate; const annuncementBorderColor = colors.secondary[mode]; @@ -51,11 +51,11 @@ export const UnstakePrompts = () => { {isFastUnstaking ? t('nominate.unstakePromptInQueue') : !canWithdrawUnlocks - ? t('nominate.unstakePromptWaitingForUnlocks') - : `${t('nominate.unstakePromptReadyToWithdraw')} ${t( - 'nominate.unstakePromptRevert', - { unit } - )}`} + ? t('nominate.unstakePromptWaitingForUnlocks') + : `${t('nominate.unstakePromptReadyToWithdraw')} ${t( + 'nominate.unstakePromptRevert', + { unit } + )}`} </h4> <ButtonRow yMargin> {isFastUnstaking ? ( @@ -73,7 +73,7 @@ export const UnstakePrompts = () => { text={ canWithdrawUnlocks ? t('nominate.unlocked') - : String(totalUnlockChuncks ?? 0) + : String(totalUnlockChunks ?? 0) } disabled={false} onClick={() => @@ -83,6 +83,7 @@ export const UnstakePrompts = () => { bondFor: 'nominator', poolClosure: true, disableWindowResize: true, + disableScroll: true, }, size: 'sm', }) diff --git a/src/pages/Overview/BalanceChart.tsx b/src/pages/Overview/BalanceChart.tsx index abc589aca8..2e5a4d1a07 100644 --- a/src/pages/Overview/BalanceChart.tsx +++ b/src/pages/Overview/BalanceChart.tsx @@ -34,6 +34,7 @@ export const BalanceChart = () => { }, } = useNetwork(); const prices = usePrices(); + // const { consts } = useApi(); const { plugins } = usePlugins(); const { isNetworkSyncing } = useUi(); const { openModal } = useOverlay().modal; @@ -107,18 +108,21 @@ export const BalanceChart = () => { : new BigNumber(0); // available balance data - const fundsLocked = planckToUnit( - BigNumber.max(frozen.minus(lockStakingAmount), 0), - units - ); + const fundsLockedPlank = BigNumber.max(frozen.minus(lockStakingAmount), 0); + const fundsLocked = planckToUnit(fundsLockedPlank, units); let fundsReserved = planckToUnit(edReserved.plus(feeReserve), units); + const fundsFree = planckToUnit( BigNumber.max( - allTransferOptions.freeBalance.minus(feeReserve).minus(fundsLocked), + allTransferOptions.freeBalance + .minus(fundsReserved) + .minus(feeReserve) + .minus(fundsLockedPlank), 0 ), units ); + // available balance percentages const graphLocked = greaterThanZero(fundsLocked) ? fundsLocked.dividedBy(graphAvailable.multipliedBy(0.01)) @@ -265,10 +269,10 @@ export const BalanceChart = () => { isNetworkSyncing ? undefined : !feeReserve.isZero() && !edReserved.isZero() - ? faCheckDouble - : feeReserve.isZero() && edReserved.isZero() - ? undefined - : faCheck + ? faCheckDouble + : feeReserve.isZero() && edReserved.isZero() + ? undefined + : faCheck } iconTransform="shrink-1" disabled={ diff --git a/src/pages/Overview/StakeStatus/Tips/index.tsx b/src/pages/Overview/StakeStatus/Tips/index.tsx index 0055ecda0d..453c3940be 100644 --- a/src/pages/Overview/StakeStatus/Tips/index.tsx +++ b/src/pages/Overview/StakeStatus/Tips/index.tsx @@ -96,9 +96,8 @@ export const Tips = () => { }, []); // store the current amount of allowed items on display - const [itemsPerPage, setItemsPerPageState] = useState<number>( - getItemsPerPage() - ); + const [itemsPerPage, setItemsPerPageState] = + useState<number>(getItemsPerPage()); const itemsPerPageRef = useRef(itemsPerPage); // store the current page diff --git a/src/pages/Payouts/PayoutList/index.tsx b/src/pages/Payouts/PayoutList/index.tsx index 9d9143f051..70dc454034 100644 --- a/src/pages/Payouts/PayoutList/index.tsx +++ b/src/pages/Payouts/PayoutList/index.tsx @@ -143,15 +143,15 @@ export const PayoutListInner = ({ p.event_id === 'PaidOut' ? t('payouts.poolClaim') : p.event_id === 'Rewarded' - ? t('payouts.payout') - : p.event_id; + ? t('payouts.payout') + : p.event_id; const labelClass = p.event_id === 'PaidOut' ? 'claim' : p.event_id === 'Rewarded' - ? 'reward' - : undefined; + ? 'reward' + : undefined; // get validator if it exists const validator = validators.find( @@ -159,15 +159,13 @@ export const PayoutListInner = ({ ); // get pool if it exists - const pool = bondedPools.find( - ({ id }) => String(id) === String(p.pool_id) - ); + const pool = bondedPools.find(({ id }) => id === p.pool_id); const batchIndex = validator ? validators.indexOf(validator) : pool - ? bondedPools.indexOf(pool) - : 0; + ? bondedPools.indexOf(pool) + : 0; return ( <motion.div @@ -220,11 +218,7 @@ export const PayoutListInner = ({ {label === t('payouts.poolClaim') && ( <> {pool ? ( - <PoolIdentity - batchKey="bonded_pools" - batchIndex={batchIndex} - pool={pool} - /> + <PoolIdentity pool={pool} /> ) : ( <h4> {t('payouts.fromPool')} {p.pool_id} diff --git a/src/pages/Pools/Home/ClosurePrompts.tsx b/src/pages/Pools/Home/ClosurePrompts.tsx index f8b4e92e4b..2bf24b9f03 100644 --- a/src/pages/Pools/Home/ClosurePrompts.tsx +++ b/src/pages/Pools/Home/ClosurePrompts.tsx @@ -27,7 +27,7 @@ export const ClosurePrompts = () => { const { getTransferOptions } = useTransferOptions(); const { state, memberCounter } = selectedActivePool?.bondedPool || {}; - const { active, totalUnlockChuncks } = getTransferOptions(activeAccount).pool; + const { active, totalUnlockChunks } = getTransferOptions(activeAccount).pool; const targets = poolNominations?.targets ?? []; const annuncementBorderColor = colors.secondary[mode]; @@ -43,7 +43,7 @@ export const ClosurePrompts = () => { // depositor can withdraw & close pool const depositorCanWithdraw = - active.toNumber() === 0 && totalUnlockChuncks === 0 && !targets.length; + active.toNumber() === 0 && totalUnlockChunks === 0 && !targets.length; return ( <> @@ -59,10 +59,10 @@ export const ClosurePrompts = () => { {targets.length > 0 ? t('pools.stopNominating') : depositorCanWithdraw - ? t('pools.closePool') - : depositorCanUnbond - ? t('pools.unbondYourFunds') - : t('pools.withdrawUnlock')} + ? t('pools.closePool') + : depositorCanUnbond + ? t('pools.unbondYourFunds') + : t('pools.withdrawUnlock')} </h4> <ButtonRow yMargin> <ButtonPrimary @@ -85,7 +85,7 @@ export const ClosurePrompts = () => { text={ depositorCanWithdraw ? t('pools.unlocked') - : String(totalUnlockChuncks ?? 0) + : String(totalUnlockChunks ?? 0) } disabled={isPoolSyncing || !isBonding()} onClick={() => @@ -95,6 +95,7 @@ export const ClosurePrompts = () => { bondFor: 'pool', poolClosure: true, disableWindowResize: true, + disableScroll: true, }, size: 'sm', }) diff --git a/src/pages/Pools/Home/Favorites/index.tsx b/src/pages/Pools/Home/Favorites/index.tsx index 62a91638d8..f704e0c6a4 100644 --- a/src/pages/Pools/Home/Favorites/index.tsx +++ b/src/pages/Pools/Home/Favorites/index.tsx @@ -49,12 +49,7 @@ export const PoolFavorites = () => { isReady && (favoritesList.length > 0 ? ( <PoolListProvider> - <PoolList - batchKey="favorite_pools" - pools={favoritesList} - allowMoreCols - pagination - /> + <PoolList pools={favoritesList} allowMoreCols pagination /> </PoolListProvider> ) : ( <ListStatusHeader>{t('pools.noFavorites')}</ListStatusHeader> diff --git a/src/pages/Pools/Home/ManageBond.tsx b/src/pages/Pools/Home/ManageBond.tsx index 620508f268..40c0ec5b21 100644 --- a/src/pages/Pools/Home/ManageBond.tsx +++ b/src/pages/Pools/Home/ManageBond.tsx @@ -40,12 +40,9 @@ export const ManageBond = () => { const allTransferOptions = getTransferOptions(activeAccount); const { - active, - totalUnlocking, - totalUnlocked, - totalUnlockChuncks, - totalAdditionalBond, - } = allTransferOptions.pool; + pool: { active, totalUnlocking, totalUnlocked, totalUnlockChunks }, + transferrableBalance, + } = allTransferOptions; const { state } = selectedActivePool?.bondedPool || {}; @@ -108,11 +105,15 @@ export const ManageBond = () => { onClick={() => openModal({ key: 'UnlockChunks', - options: { bondFor: 'pool', disableWindowResize: true }, + options: { + bondFor: 'pool', + disableWindowResize: true, + disableScroll: true, + }, size: 'sm', }) } - text={String(totalUnlockChuncks ?? 0)} + text={String(totalUnlockChunks ?? 0)} /> </ButtonRow> </CardHeaderWrapper> @@ -120,7 +121,7 @@ export const ManageBond = () => { active={planckToUnit(active, units)} unlocking={planckToUnit(totalUnlocking, units)} unlocked={planckToUnit(totalUnlocked, units)} - free={planckToUnit(totalAdditionalBond, units)} + free={planckToUnit(transferrableBalance, units)} inactive={active.isZero()} /> </> diff --git a/src/pages/Pools/Home/Status/MembershipStatus.tsx b/src/pages/Pools/Home/Status/MembershipStatus.tsx index 95496c118e..442a0b05ea 100644 --- a/src/pages/Pools/Home/Status/MembershipStatus.tsx +++ b/src/pages/Pools/Home/Status/MembershipStatus.tsx @@ -28,9 +28,9 @@ export const MembershipStatus = ({ const { openModal } = useOverlay().modal; const { activeAccount } = useActiveAccounts(); const { label, buttons } = useStatusButtons(); - const { bondedPools, meta } = useBondedPools(); const { isReadOnlyAccount } = useImportedAccounts(); const { getTransferOptions } = useTransferOptions(); + const { bondedPools, poolsMetaData } = useBondedPools(); const { selectedActivePool, isOwner, isBouncer, isMember } = useActivePools(); const { active } = getTransferOptions(activeAccount).pool; @@ -41,15 +41,13 @@ export const MembershipStatus = ({ if (selectedActivePool) { const pool = bondedPools.find( - (p: any) => p.addresses.stash === selectedActivePool.addresses.stash + (p) => p.addresses.stash === selectedActivePool.addresses.stash ); if (pool) { // Determine pool membership display. - const metadata = meta.bonded_pools?.metadata ?? []; - const batchIndex = bondedPools.indexOf(pool); membershipDisplay = determinePoolDisplay( selectedActivePool.addresses.stash, - metadata[batchIndex] + poolsMetaData[Number(pool.id)] ); } @@ -67,7 +65,7 @@ export const MembershipStatus = ({ onClick: () => openModal({ key: 'ManagePool', - options: { disableWindowResize: true }, + options: { disableWindowResize: true, disableScroll: true }, size: 'sm', }), }); diff --git a/src/pages/Pools/Home/Status/PoolStatus.tsx b/src/pages/Pools/Home/Status/PoolStatus.tsx index ac0cb6ea18..ad5ecfd39b 100644 --- a/src/pages/Pools/Home/Status/PoolStatus.tsx +++ b/src/pages/Pools/Home/Status/PoolStatus.tsx @@ -40,21 +40,21 @@ export const PoolStatus = () => { poolState === 'Blocked' ? `${t('pools.locked')} / ` : poolState === 'Destroying' - ? `${t('pools.destroying')} / ` - : ''; + ? `${t('pools.destroying')} / ` + : ''; // Determine pool status - right side. const poolStatusRight = isPoolSyncing ? t('pools.inactivePoolNotNominating') : !poolNominating - ? t('pools.inactivePoolNotNominating') - : nominees.active.length - ? `${t('pools.nominatingAnd')} ${ - earningRewards - ? t('pools.earningRewards') - : t('pools.notEarningRewards') - }` - : t('pools.waitingForActiveNominations'); + ? t('pools.inactivePoolNotNominating') + : nominees.active.length + ? `${t('pools.nominatingAnd')} ${ + earningRewards + ? t('pools.earningRewards') + : t('pools.notEarningRewards') + }` + : t('pools.waitingForActiveNominations'); return ( <Stat diff --git a/src/pages/Pools/Home/index.tsx b/src/pages/Pools/Home/index.tsx index f9de8fda5a..4334b1232b 100644 --- a/src/pages/Pools/Home/index.tsx +++ b/src/pages/Pools/Home/index.tsx @@ -146,7 +146,6 @@ export const HomeInner = () => { <CardWrapper> <PoolListProvider> <PoolList - batchKey="bonded_pools" pools={bondedPools} defaultFilters={{ includes: ['active'], diff --git a/yarn.lock b/yarn.lock index d33227e0ce..5551ebc62f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -160,7 +160,7 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== -"@babel/runtime@^7.10.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.4", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5": +"@babel/runtime@^7.10.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== @@ -210,11 +210,43 @@ "@polkadot/extension-inject" "^0.46.5" "@polkadot/types-augment" "^10.9.1" -"@dotlottie/player-component@^2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@dotlottie/player-component/-/player-component-2.7.0.tgz#b7f5edfa9a54a0d99eb5cd85b94f0aebcd9270b3" - integrity sha512-UZiBEViLVSjXGRZKHgFDRAFHettwKWJgh3eBriGY4ljNfaKxbItx/+KWVpUgPQxDGmIQt1ouMyDeQEZGwonCjQ== +"@chainsafe/metamask-polkadot-types@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@chainsafe/metamask-polkadot-types/-/metamask-polkadot-types-0.6.0.tgz#e6e950024c363e076ba6727c3b1b8b57eb6c29b0" + integrity sha512-DhLNF2LcybGMOs3aBMh5iIAqEw0/a2W6mb5FccOGVTYQqT6W/lcwKC5Ves4KmpGbH/DtNJdbC0L9YMS0F8bXEg== dependencies: + "@polkadot/api" "^10.9.1" + +"@dotlottie/common@0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@dotlottie/common/-/common-0.7.3.tgz#67bd59da7615d621ead13636ca2df9571819b474" + integrity sha512-ZKERcyhyqstxDK+BbNACAbptoxlX/canUEtPfNhegBoTn1o/MHoD2JZuuIgIV/OGqX1oxXW7yYgb2BTuauQugQ== + dependencies: + "@dotlottie/dotlottie-js" "0.6.0" + "@lottiefiles/relottie" "1.0.0" + "@lottiefiles/relottie-style" "0.4.1" + "@preact/signals-core" "^1.2.3" + howler "^2.2.3" + lottie-web "^5.12.2" + xstate "^4.38.1" + +"@dotlottie/dotlottie-js@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@dotlottie/dotlottie-js/-/dotlottie-js-0.6.0.tgz#38ba7c17ae87fd6187e2a48dd7c6a47b1c7b686f" + integrity sha512-hyyTkVcSeeMl8fYrkqnlqyMVk//4UAQAGHTIaFGQvyDvl8DScgUGe+oTJ4jrnNDS9QCGrAxDrYmk9CEjsH8PYQ== + dependencies: + browser-image-hash "0.0.5" + fflate "0.8.0" + sharp "0.32.5" + sharp-phash "2.1.0" + valibot "^0.13.1" + +"@dotlottie/player-component@^2.7.2": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@dotlottie/player-component/-/player-component-2.7.2.tgz#b0570fa5600cf770623c848dfa2be9d1b7d0784d" + integrity sha512-rJEinqJSML8kjIK7fSUGHEM4uTUlbHeBsKUorgWBjA0bYQNLtebccoHtC/TZ7PTDxopWLE6FUOJOaFmGDXv8AQ== + dependencies: + "@dotlottie/common" "0.7.3" lit "^2.7.5" "@emotion/is-prop-valid@^0.8.2": @@ -368,10 +400,10 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== -"@eslint/eslintrc@^2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" - integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== +"@eslint/eslintrc@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.3.tgz#797470a75fe0fbd5a53350ee715e85e87baff22d" + integrity sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -383,10 +415,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.52.0": - version "8.52.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.52.0.tgz#78fe5f117840f69dc4a353adf9b9cd926353378c" - integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA== +"@eslint/js@8.53.0": + version "8.53.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" + integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== "@fortawesome/fontawesome-common-types@6.4.2": version "6.4.2" @@ -442,6 +474,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== +"@humanwhocodes/momoa@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/momoa/-/momoa-2.0.3.tgz#126944d3b564064760568237cab00acb16fe896c" + integrity sha512-SytjS6gJk+LXSWFuEm0V9ASdgxlX/BDq6A+6gfh7TaHM90xppBydjcM3SFaziZP4ikKmhUOhPkDi2KktzElnQQ== + "@humanwhocodes/object-schema@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" @@ -491,44 +528,45 @@ resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== -"@ledgerhq/devices@^8.0.7": - version "8.0.7" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.0.7.tgz#206434dbd8a097529bbfc95f5eef94c2923c7578" - integrity sha512-BbPyET52lXnVs7CxJWrGYqmtGdbGzj+XnfCqLsDnA7QYr1CZREysxmie+Rr6BKpNDBRVesAovXjtaVaZOn+upw== +"@ledgerhq/devices@^8.0.8": + version "8.0.8" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.0.8.tgz#cd233eb54a044913160c9183be9fb22adae4e071" + integrity sha512-0j7E8DY2jeSSATc8IJk+tXDZ9u+Z7tXxB8I4TzXrfV/8A5exMh/K1IwX6Jt1zlw1wre4CT9MV4mzUs3M/TE7lg== dependencies: - "@ledgerhq/errors" "^6.14.0" - "@ledgerhq/logs" "^6.10.1" - rxjs "6" + "@ledgerhq/errors" "^6.15.0" + "@ledgerhq/logs" "^6.11.0" + rxjs "^7.8.1" semver "^7.3.5" -"@ledgerhq/errors@^6.14.0": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.14.0.tgz#0bf253983773ef12eebce2091f463bc719223b37" - integrity sha512-ZWJw2Ti6Dq1Ott/+qYqJdDWeZm16qI3VNG5rFlb0TQ3UcAyLIQZbnnzzdcVVwVeZiEp66WIpINd/pBdqsHVyOA== - -"@ledgerhq/hw-transport-webhid@^6.27.19": - version "6.27.19" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.27.19.tgz#5a655b497258d94ec6494db7b56e17dd0c610638" - integrity sha512-RMnktayqqLE2uFQDw9TKoW+WSP8KnT0ElKcIISf3sXVrzHD2y0moPk/wXOzGfi+cgN4uiKy86UD/5mgz3wlm6Q== - dependencies: - "@ledgerhq/devices" "^8.0.7" - "@ledgerhq/errors" "^6.14.0" - "@ledgerhq/hw-transport" "^6.28.8" - "@ledgerhq/logs" "^6.10.1" - -"@ledgerhq/hw-transport@^6.27.1", "@ledgerhq/hw-transport@^6.28.8": - version "6.28.8" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.28.8.tgz#f99a5c71c5c09591e9bfb1b970c42aafbe81351f" - integrity sha512-XxQVl4htd018u/M66r0iu5nlHi+J6QfdPsORzDF6N39jaz+tMqItb7tUlXM/isggcuS5lc7GJo7NOuJ8rvHZaQ== - dependencies: - "@ledgerhq/devices" "^8.0.7" - "@ledgerhq/errors" "^6.14.0" +"@ledgerhq/errors@^6.15.0": + version "6.15.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.15.0.tgz#45cda73915f695cc072cb8a99650830bc5dc6668" + integrity sha512-6xaw5/mgoht62TnL3rXsaQYEFwpnXyNDk1AOSJksIjFHx9bHUnkyVmrnGQDj0JLzi+E7bHEgTrpCs8wpeDh9jA== + +"@ledgerhq/hw-transport-webhid@^6.27.20": + version "6.27.20" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.27.20.tgz#87f83e42ea0e4cb5c6e87dbfc6e5e45d125eb68f" + integrity sha512-zNZgTTpbPFBHgzQRqVl3+Y0ySOFkEIGxxHT1y+AgSxRmoLgfzsBQgYy6z3jZZyJQ92B8Tl95hAvMm9vo7IqxWA== + dependencies: + "@ledgerhq/devices" "^8.0.8" + "@ledgerhq/errors" "^6.15.0" + "@ledgerhq/hw-transport" "^6.29.0" + "@ledgerhq/logs" "^6.11.0" + +"@ledgerhq/hw-transport@^6.27.1", "@ledgerhq/hw-transport@^6.29.0": + version "6.29.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.29.0.tgz#2b85f39d90b093930f0c7bfc513b29eb47ba97fa" + integrity sha512-WQfzxt3EnnbOmzZVYiCgSmNsqafBOFQn40awvUPY2IZviJRs23/1ANPHAo76bzPV88+Qk0+1wZlcnIanGN6fFA== + dependencies: + "@ledgerhq/devices" "^8.0.8" + "@ledgerhq/errors" "^6.15.0" + "@ledgerhq/logs" "^6.11.0" events "^3.3.0" -"@ledgerhq/logs@^6.10.1": - version "6.10.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.10.1.tgz#5bd16082261d7364eabb511c788f00937dac588d" - integrity sha512-z+ILK8Q3y+nfUl43ctCPuR4Y2bIxk/ooCQFwZxhtci1EhAtMDzMAx2W25qx8G1PPL9UUOdnUax19+F0OjXoj4w== +"@ledgerhq/logs@^6.10.1", "@ledgerhq/logs@^6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.11.0.tgz#0d28e7edcf71548506f4304686cba480ba91bbcf" + integrity sha512-HHK9y4GGe4X7CXbRUCh7z8Mp+WggpJn1dmUjmuk1rNugESF6o8nAOnXA+BxwtRRNV3CgNJR3Wxdos4J9qV0Zsg== "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": version "1.1.2" @@ -542,6 +580,72 @@ dependencies: "@lit-labs/ssr-dom-shim" "^1.0.0" +"@lottiefiles/last-builder@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lottiefiles/last-builder/-/last-builder-1.0.0.tgz#b31ada3c6041272cdf0c2d20a33e88aa4da374c8" + integrity sha512-j0kg/eBTfgkUR9BoOY+fcXpIAYpWynYblmR9K+s+S7b7teWAdZa8gyiTFpxmLBc5JA5SBHyQyolKVytMGc+LBw== + dependencies: + "@lottiefiles/last" "1.0.0" + unist-builder "3.0.0" + +"@lottiefiles/last@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lottiefiles/last/-/last-1.0.0.tgz#1fdfe5d126269d8d53798e58d39b0c3f8edbe4c9" + integrity sha512-kYN6WNglPtVDnupTiYiQCmExJS5PO4MFMeT495fd2RXcbXcWuIz2nVrzuJtwDXhSvsqydIxeTnEkrs6WchV2dg== + +"@lottiefiles/lottie-style-sheets@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@lottiefiles/lottie-style-sheets/-/lottie-style-sheets-0.0.1.tgz#5757632cc6e74257b76b80050019165f533ba8c6" + integrity sha512-K8NUc8R2qZfMwBY9HwMbD9pdIH7VgrAXEe+RP3PgQg/0pTlhGC9yjZCmr/2mCe7EB1R1GA0e+ZKPs89PtOX5Vg== + dependencies: + postcss "8.4.24" + unist-builder "3.0.1" + unist-util-visit "4.1.2" + +"@lottiefiles/relottie-parse@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lottiefiles/relottie-parse/-/relottie-parse-1.0.0.tgz#ca2fc8e52eb02ebe2d5fa1933f05b8ba7a5842bd" + integrity sha512-V1Ykdx82wqsO5dcuNJtofOe/BaObLHpBj2h0iWY2MkaePvgL98OR+sfSbi7oHtsfOOc1OefSrdfK1wwJJtvbpA== + dependencies: + "@humanwhocodes/momoa" "2.0.3" + "@lottiefiles/last" "1.0.0" + "@lottiefiles/last-builder" "1.0.0" + lodash.merge "4.6.2" + unist-util-is "5.1.1" + vfile "5.3.7" + +"@lottiefiles/relottie-stringify@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lottiefiles/relottie-stringify/-/relottie-stringify-1.0.0.tgz#642de90b8e0b353d9488ea2c4f22c5681c4e35cf" + integrity sha512-A97WD9bzF9HuonmdDnf2mvVbTWd9feqOCWXq8bKHoICoJ999j5IQXplMtxO9cShJmAFjii/SdV31n5THcwjcyw== + dependencies: + "@lottiefiles/last" "1.0.0" + +"@lottiefiles/relottie-style@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@lottiefiles/relottie-style/-/relottie-style-0.4.1.tgz#f75f71e364c1ad753a619fa5713e1b6843ef99ea" + integrity sha512-JJ2PjuJBwiJr6x/86sLsNJGqZyM5o+NU0GURxGGtm8yLZn9SjNczKf7bFrk2kRmqi927/4PrwQDVTFukAnnCmA== + dependencies: + "@lottiefiles/lottie-style-sheets" "0.0.1" + colord "2.9.3" + gradient-parser "1.0.2" + parsel-js "1.1.1" + postcss "8.4.21" + postcss-values-parser "6.0.2" + unist-util-is "5.2.1" + unist-util-visit "4.1.2" + vfile "5.3.7" + +"@lottiefiles/relottie@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@lottiefiles/relottie/-/relottie-1.0.0.tgz#10b2aa6cbebcb63ae91b414b5c6a9b537a14a9fa" + integrity sha512-UXE1XaiQBvdrEihjIUIE71cCmvVUH1uwrs5am/ERIlatqfZ2IhMS3Z1zarg9wdQ1f+YRDigqLVI2sKiGm8oqXg== + dependencies: + "@lottiefiles/last" "1.0.0" + "@lottiefiles/relottie-parse" "1.0.0" + "@lottiefiles/relottie-stringify" "1.0.0" + unified "10.1.2" + "@noble/curves@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" @@ -587,40 +691,46 @@ picocolors "^1.0.0" tslib "^2.6.0" -"@polkadot-cloud/assets@0.1.32", "@polkadot-cloud/assets@^0.1.32": - version "0.1.32" - resolved "https://registry.yarnpkg.com/@polkadot-cloud/assets/-/assets-0.1.32.tgz#b619e24db310ac9a23e4bb20a64245f096b73072" - integrity sha512-TC8m6RFbHtJ5omW4VuIugKp1u7YzGBIgG9vGVEXkeSMWKwgzvkrxn7qpcjcelu1XO8ujb/+64dnhIpuhdj4e9g== +"@polkadot-cloud/assets@0.1.34", "@polkadot-cloud/assets@^0.1.34": + version "0.1.34" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/assets/-/assets-0.1.34.tgz#a88c7066228d2e9229a026e9f86436ceae357737" + integrity sha512-qsXlinOgVlChbMTdBXCDaQM59jDZWrKKMizR7LR8qL2yhzLh0Ohouf0POURhuCsJ1zUjHwbsZg+4ezsKZBM+nA== -"@polkadot-cloud/core@^1.0.30": - version "1.0.30" - resolved "https://registry.yarnpkg.com/@polkadot-cloud/core/-/core-1.0.30.tgz#cc1e3f3477f402b8842338ed3e2ea4bfe8b19a23" - integrity sha512-hn3oo/JK17W5alwXPxL5M0zmZidihjklEsYv7hXF7XqWZP9musntdn5LydzHz0J7m05OTnH+9tw/GNusvqBaSg== +"@polkadot-cloud/core@^1.0.46": + version "1.0.46" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/core/-/core-1.0.46.tgz#7ba170e1ea67fd52d9663acd4f82ade99f6080f0" + integrity sha512-7trYsOIStrpo2dYZ+kF+q+vO/I1jeH3SvsIjKkqp8M8w7hgaWAyRqrMXk0fvbYGeOnZ/QihWAxJZUEpfy4VHuA== -"@polkadot-cloud/react@^0.1.102": - version "0.1.102" - resolved "https://registry.yarnpkg.com/@polkadot-cloud/react/-/react-0.1.102.tgz#ed492d5c0dafb46947ed34aa0c84ec5bacd87f52" - integrity sha512-4RF1j34iMqgSVRReBm2noj23uihvxpgetg49FKbch+YmtSJByBJY4NLrMFgvmIBug3PqBdg+yL0Em7G7emevYg== +"@polkadot-cloud/react@^0.1.129": + version "0.1.129" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/react/-/react-0.1.129.tgz#4a85fc69bc20708a867158685257a68d6a5a68e4" + integrity sha512-qLOKJtcKu6RJ9BJDAd4TLPRe8iS7WAeK/Mt+3VeO7B7LBB7+DIut+E/djGoNUlsLSAkYCn4mmyWMS+fWm0XBxg== dependencies: "@chainsafe/metamask-polkadot-adapter" "^0.5.1" + "@chainsafe/metamask-polkadot-types" "^0.6.0" "@fortawesome/fontawesome-svg-core" "^6.4.2" "@fortawesome/free-brands-svg-icons" "^6.4.2" "@fortawesome/free-regular-svg-icons" "^6.4.2" "@fortawesome/free-solid-svg-icons" "^6.4.2" "@fortawesome/react-fontawesome" "^0.2.0" - "@polkadot-cloud/assets" "0.1.32" - "@polkadot-cloud/core" "^1.0.30" - "@polkadot-cloud/utils" "^0.0.23" + "@ledgerhq/hw-transport-webhid" "^6.27.20" + "@polkadot-cloud/assets" "0.1.34" + "@polkadot-cloud/core" "^1.0.46" + "@polkadot-cloud/utils" "^0.0.25" "@polkadot/keyring" "^12.5.1" "@polkadot/util" "^12.5.1" "@polkadot/util-crypto" "^12.5.1" + "@zondax/ledger-substrate" "^0.41.3" + buffer "^6.0.3" framer-motion "^10.15.0" + qrcode-generator "^1.4.4" react-error-boundary "^4.0.11" + react-qr-reader "^2.2.1" -"@polkadot-cloud/utils@^0.0.23": - version "0.0.23" - resolved "https://registry.yarnpkg.com/@polkadot-cloud/utils/-/utils-0.0.23.tgz#eb703f74c7f05f763f7624727ba83589131ae1c5" - integrity sha512-IWqtn/cBgxletCH+MqBwZhxPxEbFLhfCU6L4RmCSCoes0bC1xUHMtFevPiJuaEkXQTdFvOnosA8BbSI01/GUuA== +"@polkadot-cloud/utils@^0.0.25": + version "0.0.25" + resolved "https://registry.yarnpkg.com/@polkadot-cloud/utils/-/utils-0.0.25.tgz#8c4f8a9a64942f4397a6368c2093ddd4feab2b4a" + integrity sha512-UlNEH8SYseJT6ZBNljE4dNYrRecCgbc7MMn2+Des6AXXUJb88HPQ39hSE1aJwDiq43wrnff2BQadnxqV9orf7g== dependencies: "@polkadot/keyring" "^12.5.1" "@polkadot/util" "^12.5.1" @@ -971,10 +1081,43 @@ dependencies: axios "^0.21.4" -"@remix-run/router@1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.10.0.tgz#e2170dc2049b06e65bbe883adad0e8ddf8291278" - integrity sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw== +"@preact/signals-core@^1.2.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.5.0.tgz#5d34db4d3c242c93e1cefb7ce8b2d10ecbdbfa79" + integrity sha512-U2diO1Z4i1n2IoFgMYmRdHWGObNrcuTRxyNEn7deSq2cru0vj0583HYQZHsAqcs7FE+hQyX3mjIV7LAfHCvy8w== + +"@remix-run/router@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.11.0.tgz#e0e45ac3fff9d8a126916f166809825537e9f955" + integrity sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ== + +"@rgba-image/common@^0.1.0", "@rgba-image/common@^0.1.13": + version "0.1.13" + resolved "https://registry.yarnpkg.com/@rgba-image/common/-/common-0.1.13.tgz#f775a1db042dd0dd23749207daa041a9d37f2e61" + integrity sha512-AnOBmBpjSgcymTuVhTGy+RB4FfmEQqR2GeJY3d3xfvR9fl3HfhzwgVqopuh3bKSAT6KRpJr7wNmug0qr3oI7bA== + +"@rgba-image/copy@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@rgba-image/copy/-/copy-0.1.3.tgz#bb822ca57b4102b70fa64f56eb3f2cf0895e827e" + integrity sha512-fscJhpp8YtVELGIwQsv1Pj6BEN4PEWAlMJ6a/HWzYxzVr3y/dut4BUrqeWRKiKeRXAGqaV6QxkBxAgYMQYZEvw== + dependencies: + "@rgba-image/common" "^0.1.13" + +"@rgba-image/create-image@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@rgba-image/create-image/-/create-image-0.1.1.tgz#873ee0ffa2d1a62565845874a7a83c4e53ae1851" + integrity sha512-ndExUNyi9Ooa/OZqiJS53vYrQ48FX7MDmMrEslDxhsorDsXpeKI9w689r4AYhT9CF9KZlBe8SmI++3BwSvvwAQ== + dependencies: + "@rgba-image/common" "^0.1.0" + +"@rgba-image/lanczos@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@rgba-image/lanczos/-/lanczos-0.1.1.tgz#e0d3810d6137e417c36e43d9c70c42c43dfc9214" + integrity sha512-MSGGU7BZmEbg1xHtNp+StARoN7R38zJnFgSEvSzB710nXsHGEaJt//z2VnPfRQTtKSKUXEnp95JSuqDlXTBrYA== + dependencies: + "@rgba-image/common" "^0.1.13" + "@rgba-image/copy" "^0.1.2" + "@rgba-image/create-image" "^0.1.1" "@rollup/pluginutils@^4.2.1": version "4.2.1" @@ -1016,13 +1159,13 @@ "@substrate/connect-extension-protocol" "^1.0.1" smoldot "2.0.1" -"@substrate/connect@^0.7.34": - version "0.7.34" - resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.7.34.tgz#e1b74125a3bf04b0037ec6f71852c943db1dd16f" - integrity sha512-APxn6PlxM+V+bJU4NSwD4WcBivqoNAQPVZAFxrM4Qf0aExHNBmB9j+uISyyc6ZbM84bN7/PWh4xd+3Wy7xEZMA== +"@substrate/connect@^0.7.35": + version "0.7.35" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.7.35.tgz#853d8ff50717a8c9ee8f219d11a86e61a54b88b8" + integrity sha512-Io8vkalbwaye+7yXfG1Nj52tOOoJln2bMlc7Q9Yy3vEWqZEVkgKmcPVzbwV0CWL3QD+KMPDA2Dnw/X7EdwgoLw== dependencies: "@substrate/connect-extension-protocol" "^1.0.1" - smoldot "2.0.6" + smoldot "2.0.7" "@substrate/ss58-registry@^1.43.0": version "1.43.0" @@ -1112,74 +1255,74 @@ "@svgr/hast-util-to-babel-ast" "8.0.0" svg-parser "^2.0.4" -"@swc/core-darwin-arm64@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.95.tgz#e6b6363fc0a22ee3cd9a63130d2042d5027aae2c" - integrity sha512-VAuBAP3MNetO/yBIBzvorUXq7lUBwhfpJxYViSxyluMwtoQDhE/XWN598TWMwMl1ZuImb56d7eUsuFdjgY7pJw== - -"@swc/core-darwin-x64@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.95.tgz#7911a03f4e0f9359710d3d6ad1dba7b5569efe5d" - integrity sha512-20vF2rvUsN98zGLZc+dsEdHvLoCuiYq/1B+TDeE4oolgTFDmI1jKO+m44PzWjYtKGU9QR95sZ6r/uec0QC5O4Q== - -"@swc/core-linux-arm-gnueabihf@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.95.tgz#95a2c9fc6849df9f1944957669c82c559d65b24f" - integrity sha512-oEudEM8PST1MRNGs+zu0cx5i9uP8TsLE4/L9HHrS07Ck0RJ3DCj3O2fU832nmLe2QxnAGPwBpSO9FntLfOiWEQ== - -"@swc/core-linux-arm64-gnu@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.95.tgz#1914d42041469582e3cc56619890edbcc54e83d6" - integrity sha512-pIhFI+cuC1aYg+0NAPxwT/VRb32f2ia8oGxUjQR6aJg65gLkUYQzdwuUmpMtFR2WVf7WVFYxUnjo4UyMuyh3ng== - -"@swc/core-linux-arm64-musl@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.95.tgz#8d73822a5807575a572d6a2d6cb64587a9f19ce6" - integrity sha512-ZpbTr+QZDT4OPJfjPAmScqdKKaT+wGurvMU5AhxLaf85DuL8HwUwwlL0n1oLieLc47DwIJEMuKQkYhXMqmJHlg== - -"@swc/core-linux-x64-gnu@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.95.tgz#80467727ec11da3de49e6be2abf735964a808483" - integrity sha512-n9SuHEFtdfSJ+sHdNXNRuIOVprB8nbsz+08apKfdo4lEKq6IIPBBAk5kVhPhkjmg2dFVHVo4Tr/OHXM1tzWCCw== - -"@swc/core-linux-x64-musl@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.95.tgz#675a53ac037846bd1bb9840a95ebcb5289265d3b" - integrity sha512-L1JrVlsXU3LC0WwmVnMK9HrOT2uhHahAoPNMJnZQpc18a0paO9fqifPG8M/HjNRffMUXR199G/phJsf326UvVg== - -"@swc/core-win32-arm64-msvc@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.95.tgz#7f0b5d0d0a090c5c625bbc54ffaf427d861c068a" - integrity sha512-YaP4x/aZbUyNdqCBpC2zL8b8n58MEpOUpmOIZK6G1SxGi+2ENht7gs7+iXpWPc0sy7X3YPKmSWMAuui0h8lgAA== - -"@swc/core-win32-ia32-msvc@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.95.tgz#06e2778549a37f0b505b24fd8f40c1c038e29f3e" - integrity sha512-w0u3HI916zT4BC/57gOd+AwAEjXeUlQbGJ9H4p/gzs1zkSHtoDQghVUNy3n/ZKp9KFod/95cA8mbVF9t1+6epQ== - -"@swc/core-win32-x64-msvc@1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.95.tgz#40f6b131e84ba6ed97f516edf0f9d5a766c0da64" - integrity sha512-5RGnMt0S6gg4Gc6QtPUJ3Qs9Un4sKqccEzgH/tj7V/DVTJwKdnBKxFZfgQ34OR2Zpz7zGOn889xwsFVXspVWNA== +"@swc/core-darwin-arm64@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.96.tgz#7c1c4245ce3f160a5b36a48ed071e3061a839e1d" + integrity sha512-8hzgXYVd85hfPh6mJ9yrG26rhgzCmcLO0h1TIl8U31hwmTbfZLzRitFQ/kqMJNbIBCwmNH1RU2QcJnL3d7f69A== + +"@swc/core-darwin-x64@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.96.tgz#4720ff897ca3f22fe77d0be688968161480c80f0" + integrity sha512-mFp9GFfuPg+43vlAdQZl0WZpZSE8sEzqL7sr/7Reul5McUHP0BaLsEzwjvD035ESfkY8GBZdLpMinblIbFNljQ== + +"@swc/core-linux-arm-gnueabihf@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.96.tgz#2c238ae00b13918ac058b132a31dc57dbcf94e39" + integrity sha512-8UEKkYJP4c8YzYIY/LlbSo8z5Obj4hqcv/fUTHiEePiGsOddgGf7AWjh56u7IoN/0uEmEro59nc1ChFXqXSGyg== + +"@swc/core-linux-arm64-gnu@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.96.tgz#be2e84506b9761b561fb9a341e587f8594a8e55d" + integrity sha512-c/IiJ0s1y3Ymm2BTpyC/xr6gOvoqAVETrivVXHq68xgNms95luSpbYQ28rqaZC8bQC8M5zdXpSc0T8DJu8RJGw== + +"@swc/core-linux-arm64-musl@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.96.tgz#22c9ce17bd923ae358760e668ca33c90210c2ae5" + integrity sha512-i5/UTUwmJLri7zhtF6SAo/4QDQJDH2fhYJaBIUhrICmIkRO/ltURmpejqxsM/ye9Jqv5zG7VszMC0v/GYn/7BQ== + +"@swc/core-linux-x64-gnu@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.96.tgz#c17c072e338341c0ac3507a31ab2a36d16d79c98" + integrity sha512-USdaZu8lTIkm4Yf9cogct/j5eqtdZqTgcTib4I+NloUW0E/hySou3eSyp3V2UAA1qyuC72ld1otXuyKBna0YKQ== + +"@swc/core-linux-x64-musl@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.96.tgz#eb74594a48b4e9cabdce7f5525b3b946f8d6dd16" + integrity sha512-QYErutd+G2SNaCinUVobfL7jWWjGTI0QEoQ6hqTp7PxCJS/dmKmj3C5ZkvxRYcq7XcZt7ovrYCTwPTHzt6lZBg== + +"@swc/core-win32-arm64-msvc@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.96.tgz#6f7c0d20d80534b0676dc6761904288c16e93857" + integrity sha512-hjGvvAduA3Un2cZ9iNP4xvTXOO4jL3G9iakhFsgVhpkU73SGmK7+LN8ZVBEu4oq2SUcHO6caWvnZ881cxGuSpg== + +"@swc/core-win32-ia32-msvc@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.96.tgz#47bb24ef2e4c81407a6786649246983cc69e7854" + integrity sha512-Far2hVFiwr+7VPCM2GxSmbh3ikTpM3pDombE+d69hkedvYHYZxtTF+2LTKl/sXtpbUnsoq7yV/32c9R/xaaWfw== + +"@swc/core-win32-x64-msvc@1.3.96": + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.96.tgz#c796e3df7afe2875d227c74add16a7d09c77d8bd" + integrity sha512-4VbSAniIu0ikLf5mBX81FsljnfqjoVGleEkCQv4+zRlyZtO3FHoDPkeLVoy6WRlj7tyrRcfUJ4mDdPkbfTO14g== "@swc/core@^1.3.95": - version "1.3.95" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.95.tgz#2743b8460e6f29385e3dbe49f3f66277ab233536" - integrity sha512-PMrNeuqIusq9DPDooV3FfNEbZuTu5jKAc04N3Hm6Uk2Fl49cqElLFQ4xvl4qDmVDz97n3n/C1RE0/f6WyGPEiA== + version "1.3.96" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.96.tgz#f04d58b227ceed2fee6617ce2cdddf21d0803f96" + integrity sha512-zwE3TLgoZwJfQygdv2SdCK9mRLYluwDOM53I+dT6Z5ZvrgVENmY3txvWDvduzkV+/8IuvrRbVezMpxcojadRdQ== dependencies: "@swc/counter" "^0.1.1" "@swc/types" "^0.1.5" optionalDependencies: - "@swc/core-darwin-arm64" "1.3.95" - "@swc/core-darwin-x64" "1.3.95" - "@swc/core-linux-arm-gnueabihf" "1.3.95" - "@swc/core-linux-arm64-gnu" "1.3.95" - "@swc/core-linux-arm64-musl" "1.3.95" - "@swc/core-linux-x64-gnu" "1.3.95" - "@swc/core-linux-x64-musl" "1.3.95" - "@swc/core-win32-arm64-msvc" "1.3.95" - "@swc/core-win32-ia32-msvc" "1.3.95" - "@swc/core-win32-x64-msvc" "1.3.95" + "@swc/core-darwin-arm64" "1.3.96" + "@swc/core-darwin-x64" "1.3.96" + "@swc/core-linux-arm-gnueabihf" "1.3.96" + "@swc/core-linux-arm64-gnu" "1.3.96" + "@swc/core-linux-arm64-musl" "1.3.96" + "@swc/core-linux-x64-gnu" "1.3.96" + "@swc/core-linux-x64-musl" "1.3.96" + "@swc/core-win32-arm64-msvc" "1.3.96" + "@swc/core-win32-ia32-msvc" "1.3.96" + "@swc/core-win32-x64-msvc" "1.3.96" "@swc/counter@^0.1.1": version "0.1.2" @@ -1192,143 +1335,148 @@ integrity sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw== "@types/bn.js@^5.1.1": - version "5.1.4" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.4.tgz#0853d5f92dfdbc8fe1ae60700411a60845fa7d27" - integrity sha512-ZtBd9L8hVtoBpPMSWfbwjC4dhQtJdlPS+e1A0Rydb7vg7bDcUwiRklPx24sMYtXcmAMST/k0Wze7JLbNU/5SkA== + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" + integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== dependencies: "@types/node" "*" "@types/chai-subset@^1.3.3": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.4.tgz#7938fa929dd12db451457e4d6faa27bcd599a729" - integrity sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg== + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.5.tgz#3fc044451f26985f45625230a7f22284808b0a9a" + integrity sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A== dependencies: "@types/chai" "*" "@types/chai@*", "@types/chai@^4.3.5": - version "4.3.9" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.9.tgz#144d762491967db8c6dea38e03d2206c2623feec" - integrity sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg== + version "4.3.10" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.10.tgz#2ad2959d1767edee5b0e4efb1a0cd2b500747317" + integrity sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg== -"@types/chroma-js@^2.4.0": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.4.2.tgz#5c57e9f9ce5343f134e376fb76e07fd3271f150f" - integrity sha512-gbiHvCuBS9aXkE3OEDfS69bscNLTYtbbx2TQf6WyOu+4eCH1AH1gPSiDGF2UzwkRFAbqKNsC5F0mY0xcaEHCbg== +"@types/chroma-js@^2.4.3": + version "2.4.3" + resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.4.3.tgz#2f10bd16483a87d8ead68764fc439550e9c4cbb6" + integrity sha512-1ly5ly/7S/YF8aD7MxUQnFOZxdegimuOunJl0xDsLlguu5JrwuSTVGVH3UpIUlh6YauI0RMNT4cqjBonhgbdIQ== "@types/eslint@^8.4.5": - version "8.44.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.6.tgz#60e564551966dd255f4c01c459f0b4fb87068603" - integrity sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw== + version "8.44.7" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.7.tgz#430b3cc96db70c81f405e6a08aebdb13869198f5" + integrity sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ== dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*", "@types/estree@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.4.tgz#d9748f5742171b26218516cf1828b8eafaf8a9fa" - integrity sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw== + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/json-schema@*", "@types/json-schema@^7.0.12": - version "7.0.14" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.14.tgz#74a97a5573980802f32c8e47b663530ab3b6b7d1" - integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw== + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/lodash.throttle@^4.1.7": - version "4.1.8" - resolved "https://registry.yarnpkg.com/@types/lodash.throttle/-/lodash.throttle-4.1.8.tgz#9d1a630d6e413a32030be1dda040ab913b80d57a" - integrity sha512-EJT8Wg9HLcrsaTlFJ+wmolrGMCC/WBmqOISNi1y9hukgp15cYnfO435X1ReUl0VTIAYnRailHqSZEmzLJb5fiQ== +"@types/lodash.throttle@^4.1.9": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz#f17a6ae084f7c0117bd7df145b379537bc9615c5" + integrity sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g== dependencies: "@types/lodash" "*" "@types/lodash@*": - version "4.14.200" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149" - integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q== + version "4.14.201" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.201.tgz#76f47cb63124e806824b6c18463daf3e1d480239" + integrity sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ== "@types/node@*": - version "20.8.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.9.tgz#646390b4fab269abce59c308fc286dcd818a2b08" - integrity sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg== + version "20.9.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.0.tgz#bfcdc230583aeb891cf51e73cfdaacdd8deae298" + integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw== dependencies: undici-types "~5.26.4" "@types/prop-types@*": - version "15.7.9" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d" - integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g== + version "15.7.10" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.10.tgz#892afc9332c4d62a5ea7e897fe48ed2085bbb08a" + integrity sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A== -"@types/react-dom@^18.2.14": - version "18.2.14" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539" - integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ== +"@types/react-dom@^18.2.15": + version "18.2.15" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.15.tgz#921af67f9ee023ac37ea84b1bc0cc40b898ea522" + integrity sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg== dependencies: "@types/react" "*" -"@types/react-helmet@^6.1.8": - version "6.1.8" - resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.8.tgz#92942afbf620435602de1f500cd9b47d3c09a218" - integrity sha512-UyJFvbGWO8xKvfCPFTt/DG/vsgkMqyXbUQAa1pSPco1Whw85Z3ypMEqoHtCDfoW4Qu8XgJp63jyXEhOa4te5Kw== +"@types/react-helmet@^6.1.9": + version "6.1.9" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.9.tgz#e79e0def2ad4047cb67e83c5be7cfb3d2121615a" + integrity sha512-nuOeTefP4yPTWHvjGksCBKb/4hsgJxSX7aSTjTIDFXJIkZ6Wo2Y4/cmE1FO9OlYBrHjKOer/0zLwY7s4qiQBtw== dependencies: "@types/react" "*" -"@types/react-qr-reader@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@types/react-qr-reader/-/react-qr-reader-2.1.6.tgz#72ebc45530bd40aabfa1aa978c6fb58b674fdee8" - integrity sha512-KF3WXsCUczlVasxLTiXNy0bO3043g/qWyYdklFK2xyZuqVyQZyzAY5Cg0+55DZ1WFmyQoL5eIqWndnlMk6RyWg== +"@types/react-qr-reader@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@types/react-qr-reader/-/react-qr-reader-2.1.7.tgz#1472a47ddbc0f96ddd246309f35826f01113837e" + integrity sha512-6K6DQeqP7c2oohcfvBpExlOawVsB2/C+7ZZL/fkCkNzYYAKDJnNHnuP3F5ChMl0mpoYEdqkqkllxqfM0VslEiw== dependencies: "@types/react" "*" -"@types/react-scroll@^1.8.9": - version "1.8.9" - resolved "https://registry.yarnpkg.com/@types/react-scroll/-/react-scroll-1.8.9.tgz#690dcde92442e0083027eb7f285f36de372c675d" - integrity sha512-FvfY7wmN7UqwStZFny6riMMnx56rdTBshE3irmoaZPeIEgBRvt1R9/lDNPQ3CVhm5OPJLHKDSvMk4c3JSwoY0g== +"@types/react-scroll@^1.8.10": + version "1.8.10" + resolved "https://registry.yarnpkg.com/@types/react-scroll/-/react-scroll-1.8.10.tgz#585a5c4bd0654434f3e55a08e94ed2e048bae7c7" + integrity sha512-RD4Z7grbdNGOKwKnUBKar6zNxqaW3n8m9QSrfvljW+gmkj1GArb8AFBomVr6xMOgHPD3v1uV3BrIf01py57daQ== dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.2.33": - version "18.2.33" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.33.tgz#055356243dc4350a9ee6c6a2c07c5cae12e38877" - integrity sha512-v+I7S+hu3PIBoVkKGpSYYpiBT1ijqEzWpzQD62/jm4K74hPpSP7FF9BnKG6+fg2+62weJYkkBWDJlZt5JO/9hg== +"@types/react@*", "@types/react@^18.2.37": + version "18.2.37" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.37.tgz#0f03af69e463c0f19a356c2660dbca5d19c44cae" + integrity sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" csstype "^3.0.2" "@types/scheduler@*": - version "0.16.5" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.5.tgz#4751153abbf8d6199babb345a52e1eb4167d64af" - integrity sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw== + version "0.16.6" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.6.tgz#eb26db6780c513de59bee0b869ef289ad3068711" + integrity sha512-Vlktnchmkylvc9SnwwwozTv04L/e1NykF5vgoQ0XTmI8DD+wxfjQuHuvHS3p0r2jz2x2ghPs2h1FVeDirIteWA== "@types/semver@^7.5.0": - version "7.5.4" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff" - integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ== + version "7.5.5" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.5.tgz#deed5ab7019756c9c90ea86139106b0346223f35" + integrity sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg== "@types/stylis@^4.0.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.2.tgz#baabb6b3aa6787e90a6bd6cd75cd8fb9a4f256a3" - integrity sha512-Rm17MsTpQQP5Jq4BF7CdrxJsDufoiL/q5IbJZYZmOZAJALyijgF7BzLgobXUqraNcQdqFYLYGeglDp6QzaxPpg== + version "4.2.3" + resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.3.tgz#0dff504fc23487a02a29209b162249070e83a0da" + integrity sha512-86XLCVEmWagiUEbr2AjSbeY4qHN9jMm3pgM3PuBYfLIbT0MpDSnA3GA/4W7KoH/C/eeK77kNaeIxZzjhKYIBgw== "@types/trusted-types@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.5.tgz#5cac7e7df3275bb95f79594f192d97da3b4fd5fe" - integrity sha512-I3pkr8j/6tmQtKV/ZzHtuaqYSQvyjGRKH4go60Rr0IDLlFxuRT5V32uvB1mecM5G1EVAUyF/4r4QZ1GHgz+mxA== + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.6.tgz#d12451beaeb9c3838f12024580dc500b7e88b0ad" + integrity sha512-HYtNooPvUY9WAVRBr4u+4Qa9fYD1ze2IUlAD3HoA6oehn1taGwBx3Oa52U4mTslTS+GAExKpaFu39Y5xUEwfjg== + +"@types/unist@^2.0.0": + version "2.0.10" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc" + integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA== -"@typescript-eslint/eslint-plugin@^6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz#d8ce497dc0ed42066e195c8ecc40d45c7b1254f4" - integrity sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg== +"@typescript-eslint/eslint-plugin@^6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.11.0.tgz#52aae65174ff526576351f9ccd41cea01001463f" + integrity sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.9.1" - "@typescript-eslint/type-utils" "6.9.1" - "@typescript-eslint/utils" "6.9.1" - "@typescript-eslint/visitor-keys" "6.9.1" + "@typescript-eslint/scope-manager" "6.11.0" + "@typescript-eslint/type-utils" "6.11.0" + "@typescript-eslint/utils" "6.11.0" + "@typescript-eslint/visitor-keys" "6.11.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -1336,72 +1484,72 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.9.1.tgz#4f685f672f8b9580beb38d5fb99d52fc3e34f7a3" - integrity sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg== +"@typescript-eslint/parser@^6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.11.0.tgz#9640d9595d905f3be4f278bf515130e6129b202e" + integrity sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ== dependencies: - "@typescript-eslint/scope-manager" "6.9.1" - "@typescript-eslint/types" "6.9.1" - "@typescript-eslint/typescript-estree" "6.9.1" - "@typescript-eslint/visitor-keys" "6.9.1" + "@typescript-eslint/scope-manager" "6.11.0" + "@typescript-eslint/types" "6.11.0" + "@typescript-eslint/typescript-estree" "6.11.0" + "@typescript-eslint/visitor-keys" "6.11.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz#e96afeb9a68ad1cd816dba233351f61e13956b75" - integrity sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg== +"@typescript-eslint/scope-manager@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz#621f603537c89f4d105733d949aa4d55eee5cea8" + integrity sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A== dependencies: - "@typescript-eslint/types" "6.9.1" - "@typescript-eslint/visitor-keys" "6.9.1" + "@typescript-eslint/types" "6.11.0" + "@typescript-eslint/visitor-keys" "6.11.0" -"@typescript-eslint/type-utils@6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz#efd5db20ed35a74d3c7d8fba51b830ecba09ce32" - integrity sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg== +"@typescript-eslint/type-utils@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz#d0b8b1ab6c26b974dbf91de1ebc5b11fea24e0d1" + integrity sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA== dependencies: - "@typescript-eslint/typescript-estree" "6.9.1" - "@typescript-eslint/utils" "6.9.1" + "@typescript-eslint/typescript-estree" "6.11.0" + "@typescript-eslint/utils" "6.11.0" debug "^4.3.4" ts-api-utils "^1.0.1" -"@typescript-eslint/types@6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.9.1.tgz#a6cfc20db0fcedcb2f397ea728ef583e0ee72459" - integrity sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ== +"@typescript-eslint/types@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.11.0.tgz#8ad3aa000cbf4bdc4dcceed96e9b577f15e0bf53" + integrity sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA== -"@typescript-eslint/typescript-estree@6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz#8c77910a49a04f0607ba94d78772da07dab275ad" - integrity sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw== +"@typescript-eslint/typescript-estree@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz#7b52c12a623bf7f8ec7f8a79901b9f98eb5c7990" + integrity sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ== dependencies: - "@typescript-eslint/types" "6.9.1" - "@typescript-eslint/visitor-keys" "6.9.1" + "@typescript-eslint/types" "6.11.0" + "@typescript-eslint/visitor-keys" "6.11.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.9.1.tgz#763da41281ef0d16974517b5f0d02d85897a1c1e" - integrity sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA== +"@typescript-eslint/utils@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.11.0.tgz#11374f59ef4cea50857b1303477c08aafa2ca604" + integrity sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.9.1" - "@typescript-eslint/types" "6.9.1" - "@typescript-eslint/typescript-estree" "6.9.1" + "@typescript-eslint/scope-manager" "6.11.0" + "@typescript-eslint/types" "6.11.0" + "@typescript-eslint/typescript-estree" "6.11.0" semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.9.1": - version "6.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz#6753a9225a0ba00459b15d6456b9c2780b66707d" - integrity sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw== +"@typescript-eslint/visitor-keys@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz#d991538788923f92ec40d44389e7075b359f3458" + integrity sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ== dependencies: - "@typescript-eslint/types" "6.9.1" + "@typescript-eslint/types" "6.11.0" eslint-visitor-keys "^3.4.1" "@ungap/structured-clone@^1.2.0": @@ -1541,7 +1689,7 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^5.1.3: +aria-query@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== @@ -1644,15 +1792,15 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== -ast-types-flow@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== async@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== asynciterator.prototype@^1.0.0: version "1.0.0" @@ -1666,10 +1814,10 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -axe-core@^4.6.2: - version "4.8.2" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae" - integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g== +axe-core@=4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" + integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== axios@^0.21.4: version "0.21.4" @@ -1678,13 +1826,23 @@ axios@^0.21.4: dependencies: follow-redirects "^1.14.0" -axobject-query@^3.1.1: +axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== dependencies: dequal "^2.0.3" +b4a@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" + integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw== + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -1722,9 +1880,9 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -"bip32-ed25519@git+https://github.com/Zondax/bip32-ed25519.git": +"bip32-ed25519@https://github.com/Zondax/bip32-ed25519": version "0.0.4" - resolved "git+https://github.com/Zondax/bip32-ed25519.git#0949df01b5c93885339bc28116690292088f6134" + resolved "https://github.com/Zondax/bip32-ed25519#0949df01b5c93885339bc28116690292088f6134" dependencies: bn.js "^5.1.1" elliptic "^6.4.1" @@ -1747,6 +1905,15 @@ bip39@^3.0.4: dependencies: "@noble/hashes" "^1.2.0" +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blakejs@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" @@ -1789,6 +1956,15 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +browser-image-hash@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/browser-image-hash/-/browser-image-hash-0.0.5.tgz#a8ca06f64b71fa2e10ebc7335bb8e5f6dd4025ff" + integrity sha512-j+rsA1L3vL8k8ji4pFPFAOU/wN/hegwk1eoMshFk3OtjzEzdDrT9Dz94OkLc43NhWGck2a9t5eQQok6zjJSPHQ== + dependencies: + "@rgba-image/lanczos" "^0.1.0" + decimal.js "^10.2.0" + wasm-imagemagick "^1.2.3" + browserslist@^4.21.9: version "4.22.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" @@ -1822,6 +1998,14 @@ bs58check@<3.0.0: create-hash "^1.1.0" safe-buffer "^5.1.2" +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -1867,9 +2051,9 @@ camelize@^1.0.0: integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== caniuse-lite@^1.0.30001541: - version "1.0.30001558" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001558.tgz#d2c6e21fdbfe83817f70feab902421a19b7983ee" - integrity sha512-/Et7DwLqpjS47JPEcz6VnxU9PwcIdVi0ciLXRWBQdj1XFye68pSQYpV0QtPTfUKWuOaEig+/Vez2l74eDc1tPQ== + version "1.0.30001561" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz#752f21f56f96f1b1a52e97aae98c57c562d5d9da" + integrity sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw== chai@^4.3.10: version "4.3.10" @@ -1930,6 +2114,11 @@ check-error@^1.0.3: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chroma-js@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-2.4.2.tgz#dffc214ed0c11fa8eefca2c36651d8e57cbfb2b0" @@ -1976,11 +2165,32 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +colord@2.9.3: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + commander@^11.0.0: version "11.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" @@ -2091,6 +2301,18 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" +decimal.js@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-eql@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" @@ -2098,6 +2320,11 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -2154,6 +2381,11 @@ dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +detect-libc@^2.0.0, detect-libc@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + diff-sequences@^29.4.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -2189,9 +2421,9 @@ dot-case@^3.0.4: tslib "^2.0.3" electron-to-chromium@^1.4.535: - version "1.4.570" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.570.tgz#5fb79061ead248a411bc8532da34d1dbf6ae23c1" - integrity sha512-5GxH0PLSIfXKOUMMHMCT4M0olwj1WwAxsQHzVW5Vh3kbsvGw8b4k7LHQmTLC2aRhsgFzrF57XJomca4XLc/WHA== + version "1.4.578" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.578.tgz#7a3510f333bcd55e87882799ebeb7518d6ab4d95" + integrity sha512-V0ZhSu1BQZKfG0yNEL6Dadzik8E1vAzfpVOapdSiT9F6yapEJ3Bk+4tZ4SMPdWiUchCgnM/ByYtBzp5ntzDMIA== elliptic@^6.4.1: version "6.5.4" @@ -2221,6 +2453,13 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enhanced-resolve@^5.12.0: version "5.15.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" @@ -2241,6 +2480,13 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + es-abstract@^1.22.1: version "1.22.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" @@ -2286,7 +2532,7 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" -es-iterator-helpers@^1.0.12: +es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.0.15: version "1.0.15" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== @@ -2457,27 +2703,27 @@ eslint-plugin-import@^2.29.0: semver "^6.3.1" tsconfig-paths "^3.14.2" -eslint-plugin-jsx-a11y@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" - integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== +eslint-plugin-jsx-a11y@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz#2fa9c701d44fcd722b7c771ec322432857fcbad2" + integrity sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA== dependencies: - "@babel/runtime" "^7.20.7" - aria-query "^5.1.3" - array-includes "^3.1.6" - array.prototype.flatmap "^1.3.1" - ast-types-flow "^0.0.7" - axe-core "^4.6.2" - axobject-query "^3.1.1" + "@babel/runtime" "^7.23.2" + aria-query "^5.3.0" + array-includes "^3.1.7" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "=4.7.0" + axobject-query "^3.2.1" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" - has "^1.0.3" - jsx-ast-utils "^3.3.3" - language-tags "=1.0.5" + es-iterator-helpers "^1.0.15" + hasown "^2.0.0" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" minimatch "^3.1.2" - object.entries "^1.1.6" - object.fromentries "^2.0.6" - semver "^6.3.0" + object.entries "^1.1.7" + object.fromentries "^2.0.7" eslint-plugin-prefer-arrow-functions@^3.2.4: version "3.2.4" @@ -2545,14 +2791,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.52.0: - version "8.52.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.52.0.tgz#d0cd4a1fac06427a61ef9242b9353f36ea7062fc" - integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg== + version "8.53.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce" + integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.52.0" + "@eslint/eslintrc" "^2.1.3" + "@eslint/js" "8.53.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2666,6 +2912,16 @@ execa@^7.1.1: signal-exit "^3.0.7" strip-final-newline "^3.0.0" +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2676,10 +2932,15 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== +fast-fifo@^1.1.0, fast-fifo@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -2712,6 +2973,11 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" +fflate@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.0.tgz#f93ad1dcbe695a25ae378cf2386624969a7cda32" + integrity sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -2807,6 +3073,11 @@ framer-motion@^10.15.0, framer-motion@^10.16.3: optionalDependencies: "@emotion/is-prop-valid" "^0.8.2" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^11.1.0, fs-extra@^11.1.1: version "11.1.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" @@ -2904,6 +3175,11 @@ gh-pages@^6.0.0: fs-extra "^11.1.1" globby "^6.1.0" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -2989,6 +3265,11 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +gradient-parser@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/gradient-parser/-/gradient-parser-1.0.2.tgz#d283b80390386e2613c992bb0e5abb259aedf25f" + integrity sha512-gR6nY33xC9yJoH4wGLQtZQMXDi6RI3H37ERu7kQCVUzlXjNedpZM7xcA489Opwbq0BSGohtWGsWsntupmxelMg== + graphemer@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" @@ -3033,11 +3314,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" - integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== - hash-base@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" @@ -3071,6 +3347,11 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +howler@^2.2.3: + version "2.2.4" + resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.4.tgz#bd3df4a4f68a0118a51e4bd84a2bfc2e93e6e5a1" + integrity sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w== + html-parse-stringify@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" @@ -3088,21 +3369,21 @@ human-signals@^4.3.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== -i18next-browser-languagedetector@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.1.0.tgz#01876fac51f86b78975e79b48ccb62e2313a2d7d" - integrity sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA== +i18next-browser-languagedetector@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz#de0321cba6881be37d82e20e4d6f05aa75f6e37f" + integrity sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA== dependencies: - "@babel/runtime" "^7.19.4" + "@babel/runtime" "^7.23.2" -i18next@^23.6.0: - version "23.6.0" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.6.0.tgz#c6e996cfd3fef0bf60be3b7c581c35338dba5a71" - integrity sha512-z0Cxr0MGkt+kli306WS4nNNM++9cgt2b2VCMprY92j+AIab/oclgPxdwtTZVLP1zn5t5uo8M6uLsZmYrcjr3HA== +i18next@^23.7.6: + version "23.7.6" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.7.6.tgz#7328e76c899052d5d33d930164612dd21e575f74" + integrity sha512-O66BhXBw0fH4bEJMA0/klQKPEbcwAp5wjXEL803pdAynNbg2f4qhLIYlNHJyE7icrL6XmSZKPYaaXwy11kJ6YQ== dependencies: - "@babel/runtime" "^7.22.5" + "@babel/runtime" "^7.23.2" -ieee754@^1.2.1: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3143,6 +3424,11 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + internal-slot@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" @@ -3166,6 +3452,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-async-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" @@ -3195,6 +3486,11 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -3289,6 +3585,11 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -3340,6 +3641,11 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: dependencies: which-typed-array "^1.1.11" +is-url-superb@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-url-superb/-/is-url-superb-4.0.0.tgz#b54d1d2499bb16792748ac967aa3ecb41a33a8c2" + integrity sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA== + is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" @@ -3461,7 +3767,7 @@ jsqr@^1.2.0: resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.4.0.tgz#8efb8d0a7cc6863cb6d95116b9069123ce9eb2d1" integrity sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A== -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== @@ -3478,17 +3784,17 @@ keyv@^4.5.3: dependencies: json-buffer "3.0.1" -language-subtag-registry@~0.3.2: +language-subtag-registry@^0.3.20: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== -language-tags@=1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" - integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== dependencies: - language-subtag-registry "~0.3.2" + language-subtag-registry "^0.3.20" levn@^0.4.1: version "0.4.1" @@ -3552,7 +3858,7 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.merge@^4.6.2: +lodash.merge@4.6.2, lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== @@ -3574,6 +3880,11 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lottie-web@^5.12.2: + version "5.12.2" + resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.12.2.tgz#579ca9fe6d3fd9e352571edd3c0be162492f68e5" + integrity sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg== + loupe@^2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" @@ -3653,6 +3964,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -3670,11 +3986,16 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mlly@^1.2.0, mlly@^1.4.0: version "1.4.2" resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" @@ -3700,10 +4021,15 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.4, nanoid@^3.3.6: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== natural-compare@^1.4.0: version "1.4.0" @@ -3719,14 +4045,26 @@ no-case@^3.0.4: tslib "^2.0.3" nock@^13.3.4: - version "13.3.7" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.7.tgz#27c8a2709d602a0570e45d3f182db342c6bd5551" - integrity sha512-z3voRxo6G0JxqCsjuzERh1ReFC4Vp2b7JpSgcMJB6jnJbUszf88awAeQLIID2UNMwbMh9/Zm5sFscagj0QYHEg== + version "13.3.8" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.8.tgz#7adf3c66f678b02ef0a78d5697ae8bc2ebde0142" + integrity sha512-96yVFal0c/W1lG7mmfRe7eO+hovrhJYd2obzzOZ90f6fjpeU/XNvd9cYHZKZAQJumDfhXgoTpkpJ9pvMj+hqHw== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" propagate "^2.0.0" +node-abi@^3.3.0: + version "3.51.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.51.0.tgz#970bf595ef5a26a271307f8a4befa02823d4e87d" + integrity sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA== + dependencies: + semver "^7.3.5" + +node-addon-api@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== + node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" @@ -3790,7 +4128,7 @@ object.assign@^4.1.2, object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.5, object.entries@^1.1.6: +object.entries@^1.1.5, object.entries@^1.1.6, object.entries@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.7.tgz#2b47760e2a2e3a752f39dd874655c61a7f03c131" integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== @@ -3835,7 +4173,7 @@ object.values@^1.1.6, object.values@^1.1.7: define-properties "^1.2.0" es-abstract "^1.22.1" -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -3922,6 +4260,11 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -3944,6 +4287,11 @@ parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parsel-js@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/parsel-js/-/parsel-js-1.1.1.tgz#261250dbfbe38bf951ca630d272dc9de3a097c20" + integrity sha512-OYBQnHBNEPLjpxogjr+1lwL6J7cc1zlBeAsfVQSFBVQchKWl3IRZivczr4EpCozXCnXVLUkB+qY79WRReGPu0Q== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -4032,6 +4380,33 @@ postcss-value-parser@^4.0.2: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== +postcss-values-parser@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz#636edc5b86c953896f1bb0d7a7a6615df00fb76f" + integrity sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw== + dependencies: + color-name "^1.1.4" + is-url-superb "^4.0.0" + quote-unquote "^1.0.0" + +postcss@8.4.21: + version "8.4.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" + integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@8.4.24: + version "8.4.24" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" + integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + postcss@^8.4.27, postcss@^8.4.31: version "8.4.31" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" @@ -4041,6 +4416,24 @@ postcss@^8.4.27, postcss@^8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" +prebuild-install@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -4053,15 +4446,15 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier-plugin-organize-imports@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz#6b0141ac71f7ee9a673ce83e95456319e3a7cf0d" - integrity sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg== +prettier-plugin-organize-imports@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz#77967f69d335e9c8e6e5d224074609309c62845e" + integrity sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog== -prettier@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" - integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== +prettier@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.0.tgz#c6d16474a5f764ea1a4a373c593b779697744d5e" + integrity sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== pretty-format@^29.5.0: version "29.7.0" @@ -4086,12 +4479,20 @@ propagate@^2.0.0: resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qrcode-generator@1.4.4: +qrcode-generator@1.4.4, qrcode-generator@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/qrcode-generator/-/qrcode-generator-1.4.4.tgz#63f771224854759329a99048806a53ed278740e7" integrity sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw== @@ -4101,23 +4502,43 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -rc-slider@^10.3.1: - version "10.3.1" - resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.3.1.tgz#345e818975f4bb61b66340799af8cfccad7c8ad7" - integrity sha512-XszsZLkbjcG9ogQy/zUC0n2kndoKUAnY/Vnk1Go5Gx+JJQBz0Tl15d5IfSiglwBUZPS9vsUJZkfCmkIZSqWbcA== +queue-tick@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" + integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== + +quote-unquote@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/quote-unquote/-/quote-unquote-1.0.0.tgz#67a9a77148effeaf81a4d428404a710baaac8a0b" + integrity sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg== + +rc-slider@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.4.0.tgz#efc016583fdea5f5dfb4f3dc61b6755a19e5f453" + integrity sha512-ZlpWjFhOlEf0w4Ng31avFBkXNNBj60NAcTPaIoiCxBkJ29wOtHSPMqv9PZeEoqmx64bpJkgK7kPa47HG4LPzww== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.5" rc-util "^5.27.0" rc-util@^5.27.0: - version "5.38.0" - resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.38.0.tgz#18a3d1c26ba3c43fabfbe6303e825dabd9e5f4f0" - integrity sha512-yV/YBNdFn+edyBpBdCqkPE29Su0jWcHNgwx2dJbRqMrMfrUcMJUjCRV+ZPhcvWyKFJ63GzEerPrz9JIVo0zXmA== + version "5.38.1" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.38.1.tgz#4915503b89855f5c5cd9afd4c72a7a17568777bb" + integrity sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng== dependencies: "@babel/runtime" "^7.18.3" react-is "^18.2.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-chartjs-2@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz#43c1e3549071c00a1a083ecbd26c1ad34d385f5d" @@ -4153,10 +4574,10 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" -react-i18next@^13.3.1: - version "13.3.1" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.3.1.tgz#9b072bf4dd4cafb028e92315a8a1415f8034bdca" - integrity sha512-JAtYREK879JXaN9GdzfBI4yJeo/XyLeXWUsRABvYXiFUakhZJ40l+kaTo+i+A/3cKIED41kS/HAbZ5BzFtq/Og== +react-i18next@^13.4.1: + version "13.4.1" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.4.1.tgz#cc1fc0422b4652524c8f2f7856fa9b3db4c4dfae" + integrity sha512-z02JvLbt6Gavbuhr4CBOI6vasLypo+JSLvMgUOGeOMPv1g6spngfAb9jWAPwvuavPlKYU4dro9yRduflwyBeyA== dependencies: "@babel/runtime" "^7.22.5" html-parse-stringify "^3.0.1" @@ -4180,20 +4601,20 @@ react-qr-reader@^2.2.1: prop-types "^15.7.2" webrtc-adapter "^7.2.1" -react-router-dom@^6.17.0: - version "6.17.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.17.0.tgz#ea73f89186546c1cf72b10fcb7356d874321b2ad" - integrity sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ== +react-router-dom@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.18.0.tgz#0a50c167209d6e7bd2ed9de200a6579ea4fb1dca" + integrity sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw== dependencies: - "@remix-run/router" "1.10.0" - react-router "6.17.0" + "@remix-run/router" "1.11.0" + react-router "6.18.0" -react-router@6.17.0: - version "6.17.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.17.0.tgz#7b680c4cefbc425b57537eb9c73bedecbdc67c1e" - integrity sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA== +react-router@6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.18.0.tgz#32e2bedc318e095a48763b5ed7758e54034cd36a" + integrity sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg== dependencies: - "@remix-run/router" "1.10.0" + "@remix-run/router" "1.11.0" react-scroll@^1.9.0: version "1.9.0" @@ -4215,7 +4636,7 @@ react@^18.2.0: dependencies: loose-envify "^1.1.0" -readable-stream@^3.6.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -4295,7 +4716,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -4355,13 +4776,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@6: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - rxjs@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" @@ -4458,6 +4872,25 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== +sharp-phash@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sharp-phash/-/sharp-phash-2.1.0.tgz#6ff2ed62b51837828fbe7097d43e921903740e14" + integrity sha512-9JYWr4tiKpjRA5Mi0qHn6LP2evS+GjdRVGjDFOSnO761m5Pavkpm83SyzauO2Ntt7znVqTn7J3XTUwHjRPAonw== + +sharp@0.32.5: + version "0.32.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.5.tgz#9ddc78ead6446094f51e50355a2d4ec6e7220cd4" + integrity sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ== + dependencies: + color "^4.2.3" + detect-libc "^2.0.2" + node-addon-api "^6.1.0" + prebuild-install "^7.1.1" + semver "^7.5.4" + simple-get "^4.0.1" + tar-fs "^3.0.4" + tunnel-agent "^0.6.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -4489,6 +4922,27 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0, simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -4501,10 +4955,10 @@ smoldot@2.0.1: dependencies: ws "^8.8.1" -smoldot@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.6.tgz#9e24cb4ab2a308c639cf4db1e95daf7066f319eb" - integrity sha512-/FQ6urmnG1TQuIu15NB9lXeNS0MX5f57vkX4RoBuVA+gAq4bcF1k2nMb40tfKCBW9PKyfEf478DN1YUMVOlWjg== +smoldot@2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.7.tgz#407efd6bbb82a074612db4d056d631d8d615f442" + integrity sha512-VAOBqEen6vises36/zgrmAT1GWk2qE3X8AGnO7lmQFdskbKx8EovnwS22rtPAG+Y1Rk23/S22kDJUdPANyPkBA== dependencies: ws "^8.8.1" @@ -4521,21 +4975,63 @@ snake-case@^3.0.4: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== + source-map@^0.7.4: version "0.7.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== +stack-generator@^2.0.5: + version "2.0.10" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" + integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== + dependencies: + stackframe "^1.3.4" + stackback@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-gps@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" + integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== + dependencies: + source-map "0.5.6" + stackframe "^1.3.4" + +stacktrace-js@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + std-env@^3.3.3: version "3.4.3" resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910" integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q== +streamx@^2.15.0: + version "2.15.2" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.2.tgz#680eacebdc9c43ede7362c2e6695b34dd413c741" + integrity sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg== + dependencies: + fast-fifo "^1.1.0" + queue-tick "^1.0.1" + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -4621,6 +5117,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strip-literal@^1.0.1: version "1.3.0" resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-1.3.0.tgz#db3942c2ec1699e6836ad230090b84bb458e3a07" @@ -4636,9 +5137,9 @@ strip-outer@^1.0.1: escape-string-regexp "^1.0.2" styled-components@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.0.tgz#228e3ab9c1ee1daa4b0a06aae30df0ed14fda274" - integrity sha512-VWNfYYBuXzuLS/QYEeoPgMErP26WL+dX9//rEh80B2mmlS1yRxRxuL5eax4m6ybYEUoHWlTy2XOU32767mlMkg== + version "6.1.1" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.1.tgz#a5414ada07fb1c17b96a26a05369daa4e2ad55e5" + integrity sha512-cpZZP5RrKRIClBW5Eby4JM1wElLVP4NQrJbJ0h10TidTyJf4SIIwa3zLXOoPb4gJi8MsJ8mjq5mu2IrEhZIAcQ== dependencies: "@emotion/is-prop-valid" "^1.2.1" "@emotion/unitless" "^0.8.0" @@ -4692,6 +5193,45 @@ tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-fs@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.4.tgz#a21dc60a2d5d9f55e0089ccd78124f1d3771dbbf" + integrity sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w== + dependencies: + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^3.1.5" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar-stream@^3.1.5: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.6.tgz#6520607b55a06f4a2e2e04db360ba7d338cc5bab" + integrity sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -4722,6 +5262,13 @@ titleize@^3.0.0: resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -4741,6 +5288,11 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +trough@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876" + integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== + ts-api-utils@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" @@ -4761,16 +5313,18 @@ tsconfig-paths@^3.14.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.5.3, tslib@^2.6.0, tslib@^2.6.1, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -4862,10 +5416,73 @@ 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== +unified@10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df" + integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q== + dependencies: + "@types/unist" "^2.0.0" + bail "^2.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^5.0.0" + +unist-builder@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04" + integrity sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ== + dependencies: + "@types/unist" "^2.0.0" + +unist-builder@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.1.tgz#258b89dcadd3c973656b2327b347863556907f58" + integrity sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-is@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236" + integrity sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ== + +unist-util-is@5.2.1, unist-util-is@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9" + integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-stringify-position@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d" + integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-visit-parents@^5.1.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb" + integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + +unist-util-visit@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2" + integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.1.1" + universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== untildify@^4.0.0: version "4.0.0" @@ -4892,13 +5509,37 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -vite-bundle-visualizer@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/vite-bundle-visualizer/-/vite-bundle-visualizer-0.10.0.tgz#bdeafe5f8e69eb4c157174ae8d852279272e3010" - integrity sha512-11AwKlkhvw6jjiGbTiCZqBSGg/FQDLc0mVcoLWVov2jU/Ban67l+Sk4Fa0Iyctb5sObqg/dA28HkKCEmSRjw9g== +valibot@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/valibot/-/valibot-0.13.1.tgz#34196211c5b4293829a9e4e6b0cf29a9227f410b" + integrity sha512-SG2W1RHqE2LShl3p6tyERt6I+G6PQa9ZFVfkyNKXz01HBzL+tBeH5kXw/5AQeAzPJSjI3djVGBl1CyozA1kyBQ== + +vfile-message@^3.0.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea" + integrity sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^3.0.0" + +vfile@5.3.7, vfile@^5.0.0: + version "5.3.7" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7" + integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + +vite-bundle-visualizer@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/vite-bundle-visualizer/-/vite-bundle-visualizer-0.11.0.tgz#1508a7f5cffcba8c094134cbb7d769167d32cb90" + integrity sha512-YKp4AYO/jfCPihL0FPC/kL6lARk+Mi8ohHCYab9cmx5eOmtW8tG6SXOxjaPPjPrf8EwWKi/QcN5xNsLZVlLzUQ== dependencies: cac "^6.7.14" rollup-plugin-visualizer "^5.9.2" + tmp "^0.2.1" vite-node@0.34.6: version "0.34.6" @@ -5052,6 +5693,14 @@ vscode-uri@^3.0.2: resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f" integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== +wasm-imagemagick@^1.2.3: + version "1.2.8" + resolved "https://registry.yarnpkg.com/wasm-imagemagick/-/wasm-imagemagick-1.2.8.tgz#218e3d5cad9ccc2ebef30ad01fbe090814b38f33" + integrity sha512-V7u80n7g+iAoV7sYgQKGSdG59J6/aSMGO0DDK0zxKnwOGjmVXyjP0yU4tX4cMrfC0t/Wk3I8TX7cmdbFQOYHpg== + dependencies: + p-map "^2.0.0" + stacktrace-js "^2.0.0" + web-streams-polyfill@^3.0.3: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" @@ -5156,6 +5805,11 @@ ws@^8.14.1, ws@^8.8.1: resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== +xstate@^4.38.1: + version "4.38.3" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075" + integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"