Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dfd109991
|
||
|
|
87a0b3926c
|
@@ -8,7 +8,7 @@ import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '@polkadot/
|
|||||||
import type { Registry } from '@polkadot/types/types';
|
import type { Registry } from '@polkadot/types/types';
|
||||||
import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||||
import type { KeypairType } from '@polkadot/util-crypto/types';
|
import type { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
import type { AccountJson, AllowedPath, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountBatchExport, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestActiveTabsUrlUpdate, RequestAuthorizeApprove, RequestBatchRestore, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, RequestUpdateAuthorizedAccounts, ResponseAccountExport, ResponseAccountsExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked, ResponseType, SigningRequest } from '../types.js';
|
import type { AccountJson, AllowedPath, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountBatchExport, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestActiveTabsUrlUpdate, RequestAuthorizeApprove, RequestBatchRestore, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, RequestUpdateAuthorizedAccounts, ResponseAccountExport, ResponseAccountsExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked, ResponseType, SigningRequest } from '../types.js';
|
||||||
import type { AuthorizedAccountsDiff } from './State.js';
|
import type { AuthorizedAccountsDiff } from './State.js';
|
||||||
import type State from './State.js';
|
import type State from './State.js';
|
||||||
|
|
||||||
@@ -61,12 +61,6 @@ export default class Extension {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private accountsCreateHardware ({ accountIndex, address, addressOffset, genesisHash, hardwareType, name, type }: RequestAccountCreateHardware): boolean {
|
|
||||||
keyring.addHardware(address, hardwareType, { accountIndex, addressOffset, genesisHash, name, type });
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private accountsCreateSuri ({ genesisHash, name, password, suri, type }: RequestAccountCreateSuri): boolean {
|
private accountsCreateSuri ({ genesisHash, name, password, suri, type }: RequestAccountCreateSuri): boolean {
|
||||||
keyring.addUri(getSuri(suri, type), password, { genesisHash, name }, type);
|
keyring.addUri(getSuri(suri, type), password, { genesisHash, name }, type);
|
||||||
|
|
||||||
@@ -582,9 +576,6 @@ export default class Extension {
|
|||||||
case 'pri(accounts.create.external)':
|
case 'pri(accounts.create.external)':
|
||||||
return this.accountsCreateExternal(request as RequestAccountCreateExternal);
|
return this.accountsCreateExternal(request as RequestAccountCreateExternal);
|
||||||
|
|
||||||
case 'pri(accounts.create.hardware)':
|
|
||||||
return this.accountsCreateHardware(request as RequestAccountCreateHardware);
|
|
||||||
|
|
||||||
case 'pri(accounts.create.suri)':
|
case 'pri(accounts.create.suri)':
|
||||||
return this.accountsCreateSuri(request as RequestAccountCreateSuri);
|
return this.accountsCreateSuri(request as RequestAccountCreateSuri);
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ export type ConnectedTabsUrlResponse = string[]
|
|||||||
export interface RequestSignatures {
|
export interface RequestSignatures {
|
||||||
// private/internal requests, i.e. from a popup
|
// private/internal requests, i.e. from a popup
|
||||||
'pri(accounts.create.external)': [RequestAccountCreateExternal, boolean];
|
'pri(accounts.create.external)': [RequestAccountCreateExternal, boolean];
|
||||||
'pri(accounts.create.hardware)': [RequestAccountCreateHardware, boolean];
|
|
||||||
'pri(accounts.create.suri)': [RequestAccountCreateSuri, boolean];
|
'pri(accounts.create.suri)': [RequestAccountCreateSuri, boolean];
|
||||||
'pri(accounts.edit)': [RequestAccountEdit, boolean];
|
'pri(accounts.edit)': [RequestAccountEdit, boolean];
|
||||||
'pri(accounts.export)': [RequestAccountExport, ResponseAccountExport];
|
'pri(accounts.export)': [RequestAccountExport, ResponseAccountExport];
|
||||||
@@ -197,16 +196,6 @@ export interface RequestAccountCreateSuri {
|
|||||||
type?: KeypairType;
|
type?: KeypairType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestAccountCreateHardware {
|
|
||||||
accountIndex: number;
|
|
||||||
address: string;
|
|
||||||
addressOffset: number;
|
|
||||||
genesisHash?: HexString | null;
|
|
||||||
hardwareType: string;
|
|
||||||
name: string;
|
|
||||||
type: KeypairType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestAccountChangePassword {
|
export interface RequestAccountChangePassword {
|
||||||
address: string;
|
address: string;
|
||||||
oldPass: string;
|
oldPass: string;
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ export const PORT_EXTENSION = `${PORT_PREFIX}-extension`;
|
|||||||
export const MESSAGE_ORIGIN_PAGE = `${PORT_PREFIX}-page`;
|
export const MESSAGE_ORIGIN_PAGE = `${PORT_PREFIX}-page`;
|
||||||
export const MESSAGE_ORIGIN_CONTENT = `${PORT_PREFIX}-content`;
|
export const MESSAGE_ORIGIN_CONTENT = `${PORT_PREFIX}-content`;
|
||||||
|
|
||||||
export const ALLOWED_PATH = ['/', '/account/import-ledger', '/account/restore-json'] as const;
|
// '/account/import-ledger' is gone with the Ledger path: no hardware wallet
|
||||||
|
// speaks ML-DSA, so there is nothing for it to connect to. See quantus/extension#5.
|
||||||
|
export const ALLOWED_PATH = ['/', '/account/restore-json', '/account/track-address'] as const;
|
||||||
|
|
||||||
export const PASSWORD_EXPIRY_MIN = 15;
|
export const PASSWORD_EXPIRY_MIN = 15;
|
||||||
export const PASSWORD_EXPIRY_MS = PASSWORD_EXPIRY_MIN * 60 * 1000;
|
export const PASSWORD_EXPIRY_MS = PASSWORD_EXPIRY_MIN * 60 * 1000;
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
"@polkadot/extension-chains": "0.64.0",
|
"@polkadot/extension-chains": "0.64.0",
|
||||||
"@polkadot/extension-dapp": "0.64.0",
|
"@polkadot/extension-dapp": "0.64.0",
|
||||||
"@polkadot/extension-inject": "0.64.0",
|
"@polkadot/extension-inject": "0.64.0",
|
||||||
"@polkadot/hw-ledger": "^14.0.3",
|
|
||||||
"@polkadot/keyring": "^14.0.3",
|
"@polkadot/keyring": "^14.0.3",
|
||||||
"@polkadot/networks": "^14.0.3",
|
"@polkadot/networks": "^14.0.3",
|
||||||
"@polkadot/react-identicon": "^3.16.7",
|
"@polkadot/react-identicon": "^3.16.7",
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ describe('Account component', () => {
|
|||||||
let wrapper: ReactWrapper;
|
let wrapper: ReactWrapper;
|
||||||
const VALID_ADDRESS = 'HjoBp62cvsWDA3vtNMWxz6c9q13ReEHi9UGHK7JbZweH5g5';
|
const VALID_ADDRESS = 'HjoBp62cvsWDA3vtNMWxz6c9q13ReEHi9UGHK7JbZweH5g5';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
const mountAccountComponent = (additionalAccountProperties: Record<string, unknown>, ledgerApp: 'generic' | 'migration' | 'chainSpecific' = 'chainSpecific'): ReactWrapper => mount(
|
const mountAccountComponent = (additionalAccountProperties: Record<string, unknown>): ReactWrapper => mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SettingsContext.Provider value={{ ...settings.get(), ledgerApp }}>
|
<SettingsContext.Provider value={settings.get()}>
|
||||||
<Account
|
<Account
|
||||||
{...{ address: VALID_ADDRESS, ...additionalAccountProperties }}
|
{...{ address: VALID_ADDRESS, ...additionalAccountProperties }}
|
||||||
/>
|
/>
|
||||||
@@ -83,19 +83,16 @@ describe('Account component', () => {
|
|||||||
expect(wrapper.find('.genesisSelection').exists()).toBe(true);
|
expect(wrapper.find('.genesisSelection').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not show genesis hash selection dropsown if account is hardware', async () => {
|
// Was two tests, one per Ledger app mode: the chain-specific app could not
|
||||||
wrapper = mountAccountComponent({ isExternal: true, isHardware: true }, 'chainSpecific');
|
// sign for an arbitrary genesis hash, so the dropdown was hidden for hardware
|
||||||
wrapper.find('.settings').first().simulate('click');
|
// accounts unless the generic app was selected. With no hardware path left
|
||||||
await act(flushAllPromises);
|
// (quantus/extension#5) there is no such restriction, and the setting that
|
||||||
|
// drove it is gone.
|
||||||
expect(wrapper.find('a.menuItem').length).toBe(2);
|
//
|
||||||
expect(wrapper.find('a.menuItem').at(0).text()).toBe('Rename');
|
// isHardware survives as a meta flag — a restored JSON can still carry it —
|
||||||
expect(wrapper.find('a.menuItem').at(1).text()).toBe('Forget Account');
|
// so an account marked that way must still behave like any other.
|
||||||
expect(wrapper.find('.genesisSelection').exists()).toBe(false);
|
it('shows genesis hash selection even for an account marked hardware', async () => {
|
||||||
});
|
wrapper = mountAccountComponent({ isExternal: true, isHardware: true });
|
||||||
|
|
||||||
it('shows genesis hash selection for hardware account in generic mode', async () => {
|
|
||||||
wrapper = mountAccountComponent({ isExternal: true, isHardware: true }, 'generic');
|
|
||||||
wrapper.find('.settings').first().simulate('click');
|
wrapper.find('.settings').first().simulate('click');
|
||||||
await act(flushAllPromises);
|
await act(flushAllPromises);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import React, { useCallback, useContext, useEffect, useMemo, useState } from 're
|
|||||||
|
|
||||||
import { canDerive } from '@polkadot/extension-base/utils';
|
import { canDerive } from '@polkadot/extension-base/utils';
|
||||||
|
|
||||||
import { AccountContext, Address, Checkbox, Dropdown, Link, MenuDivider, SettingsContext } from '../../components/index.js';
|
import { AccountContext, Address, Checkbox, Dropdown, Link, MenuDivider } from '../../components/index.js';
|
||||||
import { useGenesisHashOptions, useTranslation } from '../../hooks/index.js';
|
import { useGenesisHashOptions, useTranslation } from '../../hooks/index.js';
|
||||||
import { editAccount, tieAccount } from '../../messaging.js';
|
import { editAccount, tieAccount } from '../../messaging.js';
|
||||||
import { Name } from '../../partials/index.js';
|
import { Name } from '../../partials/index.js';
|
||||||
@@ -33,10 +33,12 @@ function Account ({ address, className, genesisHash, isExternal, isHardware, isH
|
|||||||
const [editedName, setName] = useState<string | undefined | null>(name);
|
const [editedName, setName] = useState<string | undefined | null>(name);
|
||||||
const [checked, setChecked] = useState(false);
|
const [checked, setChecked] = useState(false);
|
||||||
const genesisOptions = useGenesisHashOptions();
|
const genesisOptions = useGenesisHashOptions();
|
||||||
const { ledgerApp } = useContext(SettingsContext);
|
|
||||||
const { selectedAccounts = [], setSelectedAccounts } = useContext(AccountContext);
|
const { selectedAccounts = [], setSelectedAccounts } = useContext(AccountContext);
|
||||||
const isSelected = useMemo(() => selectedAccounts?.includes(address) || false, [address, selectedAccounts]);
|
const isSelected = useMemo(() => selectedAccounts?.includes(address) || false, [address, selectedAccounts]);
|
||||||
const canEditGenesis = !isHardware || ledgerApp === 'generic';
|
// Was `!isHardware || ledgerApp === 'generic'` — the Ledger Substrate app only
|
||||||
|
// let you pin a genesis hash in its generic mode. With no hardware path left,
|
||||||
|
// there is nothing to restrict. See quantus/extension#5.
|
||||||
|
const canEditGenesis = true;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChecked(isSelected);
|
setChecked(isSelected);
|
||||||
|
|||||||
@@ -1,225 +0,0 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import type { HexString } from '@polkadot/util/types';
|
|
||||||
|
|
||||||
import { faSync } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { settings } from '@polkadot/ui-settings';
|
|
||||||
|
|
||||||
import { ActionContext, Address, Button, ButtonArea, Dropdown, SettingsContext, Switch, VerticalSpace, Warning } from '../components/index.js';
|
|
||||||
import { useLedger, useTranslation } from '../hooks/index.js';
|
|
||||||
import { createAccountHardware } from '../messaging.js';
|
|
||||||
import { Header, Name } from '../partials/index.js';
|
|
||||||
import { styled } from '../styled.js';
|
|
||||||
import ledgerChains from '../util/legerChains.js';
|
|
||||||
|
|
||||||
interface AccOption {
|
|
||||||
text: string;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NetworkOption {
|
|
||||||
text: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AVAIL: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
|
|
||||||
const SELECT_NETWORK = '';
|
|
||||||
const ALLOW_ANY_NETWORK = '__allow_any_network__';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ImportLedger ({ className }: Props): React.ReactElement {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [accountIndex, setAccountIndex] = useState<number>(0);
|
|
||||||
const [addressOffset, setAddressOffset] = useState<number>(0);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [isBusy, setIsBusy] = useState(false);
|
|
||||||
const [genesis, setGenesis] = useState<HexString | null>(null);
|
|
||||||
const [isEthereum, setIsEthereum] = useState(false);
|
|
||||||
const onAction = useContext(ActionContext);
|
|
||||||
const { ledgerApp } = useContext(SettingsContext);
|
|
||||||
const [name, setName] = useState<string | null>(null);
|
|
||||||
const isGenericLedgerApp = ledgerApp === 'generic';
|
|
||||||
const isChainSpecific = ledgerApp === 'chainSpecific';
|
|
||||||
const { address, error: ledgerError, isLoading: ledgerLoading, isLocked: ledgerLocked, refresh, type, warning: ledgerWarning } = useLedger(genesis, accountIndex, addressOffset, isEthereum);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (address) {
|
|
||||||
settings.set({ ledgerConn: 'webusb' });
|
|
||||||
}
|
|
||||||
}, [address]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isChainSpecific) {
|
|
||||||
setIsEthereum(false);
|
|
||||||
}
|
|
||||||
}, [isChainSpecific]);
|
|
||||||
|
|
||||||
const accOps = useRef(AVAIL.map((value): AccOption => ({
|
|
||||||
text: t('Account type {{index}}', { replace: { index: value } }),
|
|
||||||
value
|
|
||||||
})));
|
|
||||||
|
|
||||||
const addOps = useRef(AVAIL.map((value): AccOption => ({
|
|
||||||
text: t('Address index {{index}}', { replace: { index: value } }),
|
|
||||||
value
|
|
||||||
})));
|
|
||||||
|
|
||||||
const onNetworkChange = useCallback((value: string) => {
|
|
||||||
if (value === ALLOW_ANY_NETWORK) {
|
|
||||||
setGenesis(null);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setGenesis(value ? value as HexString : null);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const networkOps = useMemo(
|
|
||||||
() => [
|
|
||||||
...(isGenericLedgerApp
|
|
||||||
? [{ text: t('Allow use on any chain'), value: ALLOW_ANY_NETWORK }]
|
|
||||||
: [{ text: t('Select network'), value: SELECT_NETWORK }]),
|
|
||||||
...ledgerChains.map(({ displayName, genesisHash }): NetworkOption => ({
|
|
||||||
text: displayName,
|
|
||||||
value: genesisHash[0]
|
|
||||||
}))
|
|
||||||
],
|
|
||||||
[isGenericLedgerApp, t]
|
|
||||||
);
|
|
||||||
|
|
||||||
const _onSave = useCallback(
|
|
||||||
() => {
|
|
||||||
if (address && name && type) {
|
|
||||||
setIsBusy(true);
|
|
||||||
|
|
||||||
createAccountHardware(address, 'ledger', accountIndex, addressOffset, name, genesis, type)
|
|
||||||
.then(() => onAction('/'))
|
|
||||||
.catch((error: Error) => {
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
setIsBusy(false);
|
|
||||||
setError(error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[accountIndex, address, addressOffset, genesis, name, onAction, type]
|
|
||||||
);
|
|
||||||
|
|
||||||
// select element is returning a string
|
|
||||||
const _onSetAccountIndex = useCallback((value: number) => setAccountIndex(Number(value)), []);
|
|
||||||
const _onSetAddressOffset = useCallback((value: number) => setAddressOffset(Number(value)), []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header
|
|
||||||
showBackArrow
|
|
||||||
text={t('Import Ledger Account')}
|
|
||||||
/>
|
|
||||||
<div className={className}>
|
|
||||||
<Address
|
|
||||||
address={address}
|
|
||||||
genesisHash={genesis}
|
|
||||||
isExternal
|
|
||||||
isHardware
|
|
||||||
name={name}
|
|
||||||
type={type ?? undefined}
|
|
||||||
/>
|
|
||||||
<div className='ethereum-toggle'>
|
|
||||||
<Switch
|
|
||||||
checked={isEthereum}
|
|
||||||
checkedLabel={t('Ethereum Account')}
|
|
||||||
isDisabled={isChainSpecific}
|
|
||||||
onChange={setIsEthereum}
|
|
||||||
uncheckedLabel={t('ED25519 Account')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Dropdown
|
|
||||||
className='network'
|
|
||||||
label={t('Network')}
|
|
||||||
onChange={onNetworkChange}
|
|
||||||
options={networkOps}
|
|
||||||
value={genesis ?? (isGenericLedgerApp ? ALLOW_ANY_NETWORK : SELECT_NETWORK)}
|
|
||||||
/>
|
|
||||||
{!!address && !ledgerError && (
|
|
||||||
<Name
|
|
||||||
onChange={setName}
|
|
||||||
value={name || ''}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!!name && (
|
|
||||||
<>
|
|
||||||
<Dropdown
|
|
||||||
className='accountType'
|
|
||||||
isDisabled={ledgerLoading}
|
|
||||||
label={t('account type')}
|
|
||||||
onChange={_onSetAccountIndex}
|
|
||||||
options={accOps.current}
|
|
||||||
value={accountIndex}
|
|
||||||
/>
|
|
||||||
<Dropdown
|
|
||||||
className='accountIndex'
|
|
||||||
isDisabled={ledgerLoading}
|
|
||||||
label={t('address index')}
|
|
||||||
onChange={_onSetAddressOffset}
|
|
||||||
options={addOps.current}
|
|
||||||
value={addressOffset}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!!ledgerWarning && (
|
|
||||||
<Warning>
|
|
||||||
{ledgerWarning}
|
|
||||||
</Warning>
|
|
||||||
)}
|
|
||||||
{(!!error || !!ledgerError) && (
|
|
||||||
<Warning
|
|
||||||
isDanger
|
|
||||||
>
|
|
||||||
{error || ledgerError}
|
|
||||||
</Warning>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<VerticalSpace />
|
|
||||||
<ButtonArea>
|
|
||||||
{ledgerLocked
|
|
||||||
? (
|
|
||||||
<Button
|
|
||||||
isBusy={ledgerLoading || isBusy}
|
|
||||||
onClick={refresh}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faSync} />
|
|
||||||
{t('Refresh')}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<Button
|
|
||||||
isBusy={ledgerLoading || isBusy}
|
|
||||||
isDisabled={!!error || !!ledgerError || !address || (!isGenericLedgerApp && !genesis)}
|
|
||||||
onClick={_onSave}
|
|
||||||
>
|
|
||||||
{t('Import Account')}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ButtonArea>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default styled(ImportLedger)<Props>`
|
|
||||||
.refreshIcon {
|
|
||||||
margin-right: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ethereum-toggle {
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import '@polkadot/extension-mocks/chrome';
|
|
||||||
|
|
||||||
import type { ReactWrapper } from 'enzyme';
|
|
||||||
import type * as _ from '@polkadot/dev-test/globals.d.ts';
|
|
||||||
|
|
||||||
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
|
|
||||||
import enzyme from 'enzyme';
|
|
||||||
import React from 'react';
|
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import { MemoryRouter } from 'react-router';
|
|
||||||
|
|
||||||
import { Button } from '../components/index.js';
|
|
||||||
import * as messaging from '../messaging.js';
|
|
||||||
import { flushAllPromises } from '../testHelpers.js';
|
|
||||||
import ImportQr from './ImportQr.js';
|
|
||||||
|
|
||||||
const { configure, mount } = enzyme;
|
|
||||||
|
|
||||||
const mockedAccount = {
|
|
||||||
content: '12bxf6QJS5hMJgwbJMDjFot1sq93EvgQwyuPWENr9SzJfxtN',
|
|
||||||
expectedBannerChain: 'Polkadot',
|
|
||||||
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3',
|
|
||||||
isAddress: true,
|
|
||||||
name: 'My Polkadot Account'
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ScanType {
|
|
||||||
isAddress: boolean;
|
|
||||||
content: string;
|
|
||||||
genesisHash: string;
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface QrScanAddressProps {
|
|
||||||
className?: string;
|
|
||||||
onError?: (error: Error) => void;
|
|
||||||
onScan: (scanned: ScanType) => void;
|
|
||||||
size?: string | number;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
// // NOTE Required for spyOn when using @swc/jest
|
|
||||||
// // https://github.com/swc-project/swc/issues/3843
|
|
||||||
// jest.mock('../messaging', (): Record<string, unknown> => ({
|
|
||||||
// __esModule: true,
|
|
||||||
// ...jest.requireActual('../messaging')
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
|
|
||||||
configure({ adapter: new Adapter() });
|
|
||||||
|
|
||||||
const typeName = async (wrapper: ReactWrapper, value: string) => {
|
|
||||||
wrapper.find('input').first().simulate('change', { target: { value } });
|
|
||||||
await act(flushAllPromises);
|
|
||||||
wrapper.update();
|
|
||||||
};
|
|
||||||
|
|
||||||
// jest.mock('@polkadot/react-qr', () => {
|
|
||||||
// return {
|
|
||||||
// QrScanAddress: (_: QrScanAddressProps): null => {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
describe('ImportQr component', () => {
|
|
||||||
let wrapper: ReactWrapper;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
||||||
wrapper = mount(
|
|
||||||
<MemoryRouter>
|
|
||||||
<ImportQr />
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
act(() => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
||||||
(wrapper.find('QrScanAddress').first().prop('onScan') as unknown as QrScanAddressProps['onScan'])(mockedAccount);
|
|
||||||
});
|
|
||||||
await act(flushAllPromises);
|
|
||||||
wrapper.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Address component', () => {
|
|
||||||
it('shows account as external', () => {
|
|
||||||
expect(wrapper.find('Name').find('FontAwesomeIcon [data-icon="qrcode"]').exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows the correct name', () => {
|
|
||||||
expect(wrapper.find('Name span').text()).toEqual(mockedAccount.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows the correct address', () => {
|
|
||||||
expect(wrapper.find('[data-field="address"]').text()).toEqual(mockedAccount.content);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows the correct banner', () => {
|
|
||||||
expect(wrapper.find('[data-field="chain"]').text()).toEqual(mockedAccount.expectedBannerChain);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has the button enabled', () => {
|
|
||||||
expect(wrapper.find(Button).prop('isDisabled')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('displays and error and the button is disabled with a short name', async () => {
|
|
||||||
await typeName(wrapper, 'a');
|
|
||||||
|
|
||||||
expect(wrapper.find('.warning-message').first().text()).toBe('Account name is too short');
|
|
||||||
expect(wrapper.find(Button).prop('isDisabled')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has no error message and button enabled with a long name', async () => {
|
|
||||||
const longName = 'aaa';
|
|
||||||
|
|
||||||
await typeName(wrapper, 'a');
|
|
||||||
await typeName(wrapper, longName);
|
|
||||||
|
|
||||||
expect(wrapper.find('.warning-message')).toHaveLength(0);
|
|
||||||
expect(wrapper.find(Button).prop('isDisabled')).toBe(false);
|
|
||||||
expect(wrapper.find('Name span').text()).toEqual(longName);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows the external name in the input field', () => {
|
|
||||||
expect(wrapper.find('input').prop('value')).toBe(mockedAccount.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates the external account', async () => {
|
|
||||||
jest.spyOn(messaging, 'createAccountExternal').mockImplementation(() => Promise.resolve(false));
|
|
||||||
wrapper.find(Button).simulate('click');
|
|
||||||
await act(flushAllPromises);
|
|
||||||
|
|
||||||
expect(messaging.createAccountExternal).toHaveBeenCalledWith(mockedAccount.name, mockedAccount.content, mockedAccount.genesisHash);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import type { HexString } from '@polkadot/util/types';
|
|
||||||
|
|
||||||
import React, { useCallback, useContext, useState } from 'react';
|
|
||||||
|
|
||||||
import { QrScanAddress } from '@polkadot/react-qr';
|
|
||||||
|
|
||||||
import AccountNamePasswordCreation from '../components/AccountNamePasswordCreation.js';
|
|
||||||
import { ActionContext, Address, ButtonArea, NextStepButton, VerticalSpace } from '../components/index.js';
|
|
||||||
import { useTranslation } from '../hooks/index.js';
|
|
||||||
import { createAccountExternal, createAccountSuri, createSeed } from '../messaging.js';
|
|
||||||
import { Header, Name } from '../partials/index.js';
|
|
||||||
|
|
||||||
interface QrAccount {
|
|
||||||
content: string;
|
|
||||||
genesisHash: HexString | null;
|
|
||||||
isAddress: boolean;
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ImportQr (): React.ReactElement {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const onAction = useContext(ActionContext);
|
|
||||||
const [account, setAccount] = useState<QrAccount | null>(null);
|
|
||||||
const [address, setAddress] = useState<string | null>(null);
|
|
||||||
const [name, setName] = useState<string | null>(null);
|
|
||||||
const [password, setPassword] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const _setAccount = useCallback(
|
|
||||||
(qrAccount: QrAccount) => {
|
|
||||||
setAccount(qrAccount);
|
|
||||||
setName(qrAccount?.name || null);
|
|
||||||
|
|
||||||
if (qrAccount.isAddress) {
|
|
||||||
setAddress(qrAccount.content);
|
|
||||||
} else {
|
|
||||||
createSeed(undefined, qrAccount.content)
|
|
||||||
.then(({ address }) => setAddress(address))
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const _onCreate = useCallback(
|
|
||||||
(): void => {
|
|
||||||
if (account && name) {
|
|
||||||
if (account.isAddress) {
|
|
||||||
createAccountExternal(name, account.content, account.genesisHash)
|
|
||||||
.then(() => onAction('/'))
|
|
||||||
.catch((error: Error) => console.error(error));
|
|
||||||
} else if (password) {
|
|
||||||
createAccountSuri(name, password, account.content, 'sr25519', account.genesisHash)
|
|
||||||
.then(() => onAction('/'))
|
|
||||||
.catch((error: Error) => console.error(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[account, name, onAction, password]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header
|
|
||||||
showBackArrow
|
|
||||||
text={t('Scan Address Qr')}
|
|
||||||
/>
|
|
||||||
{!account && (
|
|
||||||
<div>
|
|
||||||
<QrScanAddress onScan={_setAccount} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{account && (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<Address
|
|
||||||
{...account}
|
|
||||||
address={address}
|
|
||||||
isExternal={true}
|
|
||||||
name={name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{account.isAddress
|
|
||||||
? (
|
|
||||||
<Name
|
|
||||||
isFocused
|
|
||||||
onChange={setName}
|
|
||||||
value={name || ''}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<AccountNamePasswordCreation
|
|
||||||
isBusy={false}
|
|
||||||
onCreate={_onCreate}
|
|
||||||
onNameChange={setName}
|
|
||||||
onPasswordChange={setPassword}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<VerticalSpace />
|
|
||||||
<ButtonArea>
|
|
||||||
<NextStepButton
|
|
||||||
isDisabled={!name || (!account.isAddress && !password)}
|
|
||||||
onClick={_onCreate}
|
|
||||||
>
|
|
||||||
{t('Add the account with identified address')}
|
|
||||||
</NextStepButton>
|
|
||||||
</ButtonArea>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -70,7 +70,7 @@ function Request ({ className, metaId, request, url }: Props): React.ReactElemen
|
|||||||
</Table>
|
</Table>
|
||||||
<div className='requestInfo'>
|
<div className='requestInfo'>
|
||||||
<Warning className='requestWarning'>
|
<Warning className='requestWarning'>
|
||||||
{t('This approval will add the metadata to your extension instance, allowing future requests to be decoded using this metadata. It will also allow the use of Ledger\'s Generic Polkadot App.')}
|
{t('This approval will add the metadata to your extension instance, allowing future requests to be decoded using this metadata.')}
|
||||||
</Warning>
|
</Warning>
|
||||||
<Button
|
<Button
|
||||||
className='btnAccept'
|
className='btnAccept'
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/* eslint-disable deprecation/deprecation */
|
|
||||||
|
|
||||||
import type { Chain } from '@polkadot/extension-chains/types';
|
|
||||||
import type { Ledger, LedgerGeneric } from '@polkadot/hw-ledger';
|
|
||||||
import type { ExtrinsicPayload } from '@polkadot/types/interfaces';
|
|
||||||
import type { SignerPayloadJSON } from '@polkadot/types/types';
|
|
||||||
import type { HexString } from '@polkadot/util/types';
|
|
||||||
|
|
||||||
import { faSync } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import React, { useCallback, useContext, useState } from 'react';
|
|
||||||
|
|
||||||
import { assert, objectSpread, stringShorten, u8aToHex } from '@polkadot/util';
|
|
||||||
import { decodeAddress } from '@polkadot/util-crypto';
|
|
||||||
import { merkleizeMetadata } from '@polkadot-api/merkleize-metadata';
|
|
||||||
|
|
||||||
import { Button, SettingsContext, Warning } from '../../components/index.js';
|
|
||||||
import { useLedger, useMetadata, useTranslation } from '../../hooks/index.js';
|
|
||||||
import { styled } from '../../styled.js';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
accountIndex?: number;
|
|
||||||
addressOffset?: number;
|
|
||||||
className?: string;
|
|
||||||
error: string | null;
|
|
||||||
genesisHash?: string;
|
|
||||||
isEthereum?: boolean;
|
|
||||||
onSignature?: ({ signature }: { signature: HexString }, signedTransaction?: HexString) => void;
|
|
||||||
payloadJson?: SignerPayloadJSON;
|
|
||||||
payloadExt?: ExtrinsicPayload
|
|
||||||
setError: (value: string | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMetadataProof (chain: Chain, payload: SignerPayloadJSON) {
|
|
||||||
const m = chain.definition.rawMetadata;
|
|
||||||
|
|
||||||
assert(m, 'To sign with Ledger\'s Polkadot Generic App, the metadata must be present in the extension.');
|
|
||||||
|
|
||||||
const merkleizedMetadata = merkleizeMetadata(m, {
|
|
||||||
base58Prefix: chain.ss58Format,
|
|
||||||
decimals: chain.tokenDecimals,
|
|
||||||
specVersion: chain.specVersion,
|
|
||||||
tokenSymbol: chain.tokenSymbol
|
|
||||||
});
|
|
||||||
const metadataHash = u8aToHex(merkleizedMetadata.digest());
|
|
||||||
const newPayload = objectSpread<SignerPayloadJSON>({}, payload, { metadataHash, mode: 1 });
|
|
||||||
const raw = chain.registry.createType('ExtrinsicPayload', newPayload);
|
|
||||||
|
|
||||||
return {
|
|
||||||
raw,
|
|
||||||
txMetadata: merkleizedMetadata.getProofForExtrinsicPayload(u8aToHex(raw.toU8a(true)))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesExpectedSigner (derivedAddress: string, expectedAddress: string, isEthereum: boolean): boolean {
|
|
||||||
if (isEthereum) {
|
|
||||||
return derivedAddress.toLowerCase() === expectedAddress.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return u8aToHex(decodeAddress(derivedAddress)) === u8aToHex(decodeAddress(expectedAddress));
|
|
||||||
} catch {
|
|
||||||
return derivedAddress === expectedAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function LedgerSign ({ accountIndex, addressOffset, className, error, genesisHash, isEthereum = false, onSignature, payloadExt, payloadJson, setError }: Props): React.ReactElement<Props> {
|
|
||||||
const [isBusy, setIsBusy] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { ledgerApp } = useContext(SettingsContext);
|
|
||||||
const chain = useMetadata(genesisHash);
|
|
||||||
const { address, error: ledgerError, isLoading: ledgerLoading, isLocked: ledgerLocked, ledger, refresh, type: _ledgerType, warning: ledgerWarning } = useLedger(genesisHash, accountIndex, addressOffset, isEthereum);
|
|
||||||
const expectedAddress = payloadJson?.address;
|
|
||||||
const signerMismatchError = address && expectedAddress && !matchesExpectedSigner(address, expectedAddress, isEthereum)
|
|
||||||
? t('Address mismatch: derived {{derived}}, expected {{expected}}. Check that the correct Ledger device is connected and the correct app is selected.', {
|
|
||||||
replace: {
|
|
||||||
derived: stringShorten(address, 8),
|
|
||||||
expected: stringShorten(expectedAddress, 8)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
const signerMismatchHint = signerMismatchError
|
|
||||||
? t('Possible cause: the Ledger App setting differs from the app originally used to derive this account.')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const _onRefresh = useCallback(() => {
|
|
||||||
setError(null);
|
|
||||||
refresh();
|
|
||||||
}, [refresh, setError]);
|
|
||||||
|
|
||||||
const _onSignLedger = useCallback(
|
|
||||||
(): void => {
|
|
||||||
if (signerMismatchError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ledger || !payloadJson || !onSignature || !chain || !payloadExt) {
|
|
||||||
if (!chain) {
|
|
||||||
setError('No chain information found. You may need to update/upload the metadata.');
|
|
||||||
setIsBusy(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setError(null);
|
|
||||||
setIsBusy(true);
|
|
||||||
|
|
||||||
const currApp = ledgerApp;
|
|
||||||
|
|
||||||
if (currApp === 'generic' || currApp === 'migration') {
|
|
||||||
if (!chain?.definition.rawMetadata) {
|
|
||||||
setError('No metadata found for this chain. You must upload the metadata to the extension in order to use Ledger.');
|
|
||||||
setIsBusy(false);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { raw, txMetadata } = getMetadataProof(chain, payloadJson);
|
|
||||||
const metaBuff = Buffer.from(txMetadata);
|
|
||||||
|
|
||||||
if (isEthereum) {
|
|
||||||
(ledger as LedgerGeneric).signWithMetadataEcdsa(raw.toU8a(true), accountIndex, addressOffset, { metadata: metaBuff })
|
|
||||||
.then(({ signature }) => {
|
|
||||||
const extrinsic = chain.registry.createType(
|
|
||||||
'Extrinsic',
|
|
||||||
{ method: raw.method },
|
|
||||||
{ version: 4 }
|
|
||||||
);
|
|
||||||
|
|
||||||
(ledger as LedgerGeneric).getAddressEcdsa(false, accountIndex, addressOffset)
|
|
||||||
.then(({ address }) => {
|
|
||||||
extrinsic.addSignature(`0x${address}`, signature, raw.toHex());
|
|
||||||
onSignature({ signature }, extrinsic.toHex());
|
|
||||||
})
|
|
||||||
.catch((e: Error) => {
|
|
||||||
setError(e.message);
|
|
||||||
setIsBusy(false);
|
|
||||||
});
|
|
||||||
}).catch((e: Error) => {
|
|
||||||
setError(e.message);
|
|
||||||
setIsBusy(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
(ledger as LedgerGeneric).signWithMetadata(raw.toU8a(true), accountIndex, addressOffset, { metadata: metaBuff })
|
|
||||||
.then((signature) => {
|
|
||||||
const extrinsic = chain.registry.createType(
|
|
||||||
'Extrinsic',
|
|
||||||
{ method: raw.method },
|
|
||||||
{ version: 4 }
|
|
||||||
);
|
|
||||||
|
|
||||||
(ledger as LedgerGeneric).getAddress(chain.ss58Format, false, accountIndex, addressOffset)
|
|
||||||
.then(({ address }) => {
|
|
||||||
extrinsic.addSignature(address, signature.signature, raw.toHex());
|
|
||||||
onSignature(signature, extrinsic.toHex());
|
|
||||||
})
|
|
||||||
.catch((e: Error) => {
|
|
||||||
setError(e.message);
|
|
||||||
setIsBusy(false);
|
|
||||||
});
|
|
||||||
}).catch((e: Error) => {
|
|
||||||
setError(e.message);
|
|
||||||
setIsBusy(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (currApp === 'chainSpecific') {
|
|
||||||
(ledger as Ledger).sign(payloadExt.toU8a(true), accountIndex, addressOffset)
|
|
||||||
.then((signature) => {
|
|
||||||
onSignature(signature);
|
|
||||||
}).catch((e: Error) => {
|
|
||||||
setError(e.message);
|
|
||||||
setIsBusy(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[accountIndex, addressOffset, chain, ledger, ledgerApp, onSignature, payloadJson, payloadExt, setError, signerMismatchError, isEthereum]
|
|
||||||
);
|
|
||||||
|
|
||||||
const activeError = ledgerError || error;
|
|
||||||
const hasActiveError = !!activeError;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
{!!ledgerWarning && (
|
|
||||||
<Warning>
|
|
||||||
{ledgerWarning}
|
|
||||||
</Warning>
|
|
||||||
)}
|
|
||||||
{hasActiveError && (
|
|
||||||
<Warning isDanger>
|
|
||||||
{activeError}
|
|
||||||
</Warning>
|
|
||||||
)}
|
|
||||||
{signerMismatchError && !hasActiveError && (
|
|
||||||
<Warning isDanger>
|
|
||||||
{signerMismatchError}
|
|
||||||
</Warning>
|
|
||||||
)}
|
|
||||||
{signerMismatchHint && !hasActiveError && (
|
|
||||||
<Warning>
|
|
||||||
{signerMismatchHint}
|
|
||||||
</Warning>
|
|
||||||
)}
|
|
||||||
{(ledgerLocked || hasActiveError)
|
|
||||||
? (
|
|
||||||
<Button
|
|
||||||
isBusy={isBusy || ledgerLoading}
|
|
||||||
onClick={_onRefresh}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faSync} />
|
|
||||||
{t('Refresh')}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<Button
|
|
||||||
isBusy={isBusy || ledgerLoading}
|
|
||||||
isDisabled={!!signerMismatchError}
|
|
||||||
onClick={_onSignLedger}
|
|
||||||
>
|
|
||||||
{t('Sign on Ledger')}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default styled(LedgerSign) <Props>`
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 6px 24px;
|
|
||||||
|
|
||||||
.danger {
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import type { ExtrinsicPayload } from '@polkadot/types/interfaces';
|
|
||||||
import type { HexString } from '@polkadot/util/types';
|
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useState } from 'react';
|
|
||||||
|
|
||||||
import { QrDisplayPayload, QrScanSignature } from '@polkadot/react-qr';
|
|
||||||
import { u8aWrapBytes } from '@polkadot/util';
|
|
||||||
|
|
||||||
import { Button } from '../../components/index.js';
|
|
||||||
import { useTranslation } from '../../hooks/index.js';
|
|
||||||
import { styled } from '../../styled.js';
|
|
||||||
import { CMD_MORTAL, CMD_SIGN_MESSAGE } from './Request/index.js';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
address: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
cmd: number;
|
|
||||||
genesisHash: string;
|
|
||||||
onSignature: ({ signature }: { signature: HexString }) => void;
|
|
||||||
payload: ExtrinsicPayload | string;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function Qr ({ address, className, cmd, genesisHash, onSignature, payload }: Props): React.ReactElement<Props> {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [isScanning, setIsScanning] = useState(false);
|
|
||||||
|
|
||||||
const payloadU8a = useMemo(
|
|
||||||
() => {
|
|
||||||
switch (cmd) {
|
|
||||||
case CMD_MORTAL:
|
|
||||||
return (payload as ExtrinsicPayload).toU8a();
|
|
||||||
case CMD_SIGN_MESSAGE:
|
|
||||||
return u8aWrapBytes(payload as string);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[cmd, payload]
|
|
||||||
);
|
|
||||||
|
|
||||||
const _onShowQr = useCallback(
|
|
||||||
() => setIsScanning(true),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!payloadU8a) {
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<div className='qrContainer'>
|
|
||||||
Transaction command:{cmd} not supported.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<div className='qrContainer'>
|
|
||||||
{isScanning
|
|
||||||
? <QrScanSignature onScan={onSignature} />
|
|
||||||
: (
|
|
||||||
<QrDisplayPayload
|
|
||||||
address={address}
|
|
||||||
cmd={cmd}
|
|
||||||
genesisHash={genesisHash}
|
|
||||||
payload={payloadU8a}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
{!isScanning && (
|
|
||||||
<Button
|
|
||||||
className='scanButton'
|
|
||||||
onClick={_onShowQr}
|
|
||||||
>
|
|
||||||
{t('Scan signature via camera')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default styled(Qr)<Props>`
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.qrContainer {
|
|
||||||
margin: 5px auto 10px auto;
|
|
||||||
width: 65%;
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: white solid 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scanButton {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -3,20 +3,16 @@
|
|||||||
|
|
||||||
import type { AccountJson, RequestSign } from '@polkadot/extension-base/background/types';
|
import type { AccountJson, RequestSign } from '@polkadot/extension-base/background/types';
|
||||||
import type { ExtrinsicPayload } from '@polkadot/types/interfaces';
|
import type { ExtrinsicPayload } from '@polkadot/types/interfaces';
|
||||||
import type { HexString } from '@polkadot/util/types';
|
|
||||||
|
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { isExtrinsicRequest } from '@polkadot/extension-base/utils';
|
import { isExtrinsicRequest } from '@polkadot/extension-base/utils';
|
||||||
import { TypeRegistry } from '@polkadot/types';
|
import { TypeRegistry } from '@polkadot/types';
|
||||||
|
|
||||||
import { ActionContext, Address, VerticalSpace, Warning } from '../../../components/index.js';
|
import { Address, VerticalSpace, Warning } from '../../../components/index.js';
|
||||||
import { useMetadata, useTranslation } from '../../../hooks/index.js';
|
import { useMetadata, useTranslation } from '../../../hooks/index.js';
|
||||||
import { approveSignSignature } from '../../../messaging.js';
|
|
||||||
import Bytes from '../Bytes.js';
|
import Bytes from '../Bytes.js';
|
||||||
import Extrinsic from '../Extrinsic.js';
|
import Extrinsic from '../Extrinsic.js';
|
||||||
import LedgerSign from '../LedgerSign.js';
|
|
||||||
import Qr from '../Qr.js';
|
|
||||||
import SignArea from './SignArea.js';
|
import SignArea from './SignArea.js';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -33,14 +29,10 @@ interface Data {
|
|||||||
payload: ExtrinsicPayload | null;
|
payload: ExtrinsicPayload | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CMD_MORTAL = 2;
|
|
||||||
export const CMD_SIGN_MESSAGE = 3;
|
|
||||||
|
|
||||||
// keep it global, we can and will re-use this across requests
|
// keep it global, we can and will re-use this across requests
|
||||||
const registry = new TypeRegistry();
|
const registry = new TypeRegistry();
|
||||||
|
|
||||||
export default function Request ({ account: { accountIndex, addressOffset, genesisHash: accountGenesisHash, isExternal, isHardware, type }, buttonText, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
|
export default function Request ({ account: { isExternal, isHardware }, buttonText, isFirst, request, signId, url }: Props): React.ReactElement<Props> | null {
|
||||||
const onAction = useContext(ActionContext);
|
|
||||||
const [{ hexBytes, payload }, setData] = useState<Data>({ hexBytes: null, payload: null });
|
const [{ hexBytes, payload }, setData] = useState<Data>({ hexBytes: null, payload: null });
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -55,7 +47,6 @@ export default function Request ({ account: { accountIndex, addressOffset, genes
|
|||||||
|
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
// When the chain and request are ready, configure the chain's registry.
|
// When the chain and request are ready, configure the chain's registry.
|
||||||
// This will be picked up by LedgerSign.
|
|
||||||
if (chain && isExtrinsicRequest(request)) {
|
if (chain && isExtrinsicRequest(request)) {
|
||||||
chain.registry.setSignedExtensions(request.payload.signedExtensions, chain.definition.userExtensions);
|
chain.registry.setSignedExtensions(request.payload.signedExtensions, chain.definition.userExtensions);
|
||||||
}
|
}
|
||||||
@@ -79,18 +70,6 @@ export default function Request ({ account: { accountIndex, addressOffset, genes
|
|||||||
}
|
}
|
||||||
}, [request]);
|
}, [request]);
|
||||||
|
|
||||||
const _onSignature = useCallback(
|
|
||||||
({ signature }: { signature: HexString }, signedTransaction?: HexString): void => {
|
|
||||||
approveSignSignature(signId, signature, signedTransaction)
|
|
||||||
.then(() => onAction())
|
|
||||||
.catch((error: Error): void => {
|
|
||||||
setError(error.message);
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[onAction, signId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Branch on the request itself rather than on the decoded state, so a render
|
// Branch on the request itself rather than on the decoded state, so a render
|
||||||
// that lands before the effect has caught up shows nothing instead of feeding
|
// that lands before the effect has caught up shows nothing instead of feeding
|
||||||
// the previous request's view a payload of the other shape.
|
// the previous request's view a payload of the other shape.
|
||||||
@@ -111,36 +90,16 @@ export default function Request ({ account: { accountIndex, addressOffset, genes
|
|||||||
isHardware={isHardware}
|
isHardware={isHardware}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isExternal && !isHardware
|
<Extrinsic
|
||||||
? (
|
payload={payload}
|
||||||
<Qr
|
request={json}
|
||||||
address={json.address}
|
url={url}
|
||||||
cmd={CMD_MORTAL}
|
/>
|
||||||
genesisHash={json.genesisHash}
|
{isExternal && (
|
||||||
onSignature={_onSignature}
|
<>
|
||||||
payload={payload}
|
<Warning>{t('This account is tracked only — the extension holds no key for it and cannot sign.')}</Warning>
|
||||||
/>
|
<VerticalSpace />
|
||||||
)
|
</>
|
||||||
: (
|
|
||||||
<Extrinsic
|
|
||||||
payload={payload}
|
|
||||||
request={json}
|
|
||||||
url={url}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{isHardware && (
|
|
||||||
<LedgerSign
|
|
||||||
accountIndex={accountIndex || 0}
|
|
||||||
addressOffset={addressOffset || 0}
|
|
||||||
error={error}
|
|
||||||
genesisHash={json.genesisHash}
|
|
||||||
isEthereum={type === 'ethereum'}
|
|
||||||
onSignature={_onSignature}
|
|
||||||
payloadExt={payload}
|
|
||||||
payloadJson={json}
|
|
||||||
setError={setError}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<SignArea
|
<SignArea
|
||||||
buttonText={buttonText}
|
buttonText={buttonText}
|
||||||
@@ -167,32 +126,13 @@ export default function Request ({ account: { accountIndex, addressOffset, genes
|
|||||||
isExternal={isExternal}
|
isExternal={isExternal}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isExternal && !isHardware && accountGenesisHash
|
<Bytes
|
||||||
? (
|
bytes={data}
|
||||||
<Qr
|
url={url}
|
||||||
address={address}
|
/>
|
||||||
cmd={CMD_SIGN_MESSAGE}
|
|
||||||
genesisHash={accountGenesisHash}
|
|
||||||
onSignature={_onSignature}
|
|
||||||
payload={data}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<Bytes
|
|
||||||
bytes={data}
|
|
||||||
url={url}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<VerticalSpace />
|
<VerticalSpace />
|
||||||
{isExternal && !isHardware && !accountGenesisHash && (
|
{isExternal && <>
|
||||||
<>
|
<Warning>{t('This account is tracked only — the extension holds no key for it and cannot sign.')}</Warning>
|
||||||
<Warning isDanger>{t('"Allow use on any network" is not supported to show a QR code. You must associate this account with a network.')}</Warning>
|
|
||||||
<VerticalSpace />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{isHardware && <>
|
|
||||||
<Warning>{t('Message signing is not supported for hardware wallets.')}</Warning>
|
|
||||||
<VerticalSpace />
|
<VerticalSpace />
|
||||||
</>}
|
</>}
|
||||||
<SignArea
|
<SignArea
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import Request from './Request/index.js';
|
|||||||
import Extrinsic from './Extrinsic.js';
|
import Extrinsic from './Extrinsic.js';
|
||||||
import Signing from './index.js';
|
import Signing from './index.js';
|
||||||
import { westendMetadata } from './metadataMock.js';
|
import { westendMetadata } from './metadataMock.js';
|
||||||
import Qr from './Qr.js';
|
|
||||||
import TransactionIndex from './TransactionIndex.js';
|
import TransactionIndex from './TransactionIndex.js';
|
||||||
|
|
||||||
const { configure, mount } = enzyme;
|
const { configure, mount } = enzyme;
|
||||||
@@ -211,7 +210,13 @@ describe('Signing requests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('External account', () => {
|
describe('External account', () => {
|
||||||
it('shows Qr scanner for external accounts', async () => {
|
// Was "shows Qr scanner for external accounts". That flow round-tripped a
|
||||||
|
// signature through a Parity Signer QR, a protocol no Quantus device speaks
|
||||||
|
// (quantus/extension#10). An external account here is now a tracked address:
|
||||||
|
// the extension holds no key for it and there is no signer to hand off to, so
|
||||||
|
// the right behaviour is to show the call and say so rather than display a
|
||||||
|
// QR nothing can scan.
|
||||||
|
it('shows the extrinsic and a cannot-sign warning for external accounts', async () => {
|
||||||
signRequests = [{
|
signRequests = [{
|
||||||
account: {
|
account: {
|
||||||
address: '5Cf1CGZas62RWwce3d2EPqUvSoi1txaXKd9M5w9bEFSsQtRe',
|
address: '5Cf1CGZas62RWwce3d2EPqUvSoi1txaXKd9M5w9bEFSsQtRe',
|
||||||
@@ -251,8 +256,8 @@ describe('Signing requests', () => {
|
|||||||
url: 'https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fwestend-rpc.polkadot.io#/accounts'
|
url: 'https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fwestend-rpc.polkadot.io#/accounts'
|
||||||
}];
|
}];
|
||||||
await mountComponent();
|
await mountComponent();
|
||||||
expect(wrapper.find(Extrinsic)).toHaveLength(0);
|
expect(wrapper.find(Extrinsic)).toHaveLength(1);
|
||||||
expect(wrapper.find(Qr)).toHaveLength(1);
|
expect(wrapper.text()).toContain('tracked only');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import React, { useCallback, useContext, useState } from 'react';
|
||||||
|
|
||||||
|
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
|
import { ActionContext, Address, ButtonArea, NextStepButton, VerticalSpace, Warning } from '../components/index.js';
|
||||||
|
import { useTranslation } from '../hooks/index.js';
|
||||||
|
import { createAccountExternal } from '../messaging.js';
|
||||||
|
import { Header, Name } from '../partials/index.js';
|
||||||
|
import { styled } from '../styled.js';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replaces ImportQr as the way to add an account the extension holds no key for.
|
||||||
|
//
|
||||||
|
// That flow scanned a Parity Signer QR, a protocol no Quantus device speaks
|
||||||
|
// (quantus/extension#10) — but it was also the *only* caller of
|
||||||
|
// createAccountExternal, so removing it would have taken watch-only accounts with
|
||||||
|
// it. Those are wanted independently of how the address arrives, and for Quantus
|
||||||
|
// pasting is the common case anyway: the address is on screen in another wallet,
|
||||||
|
// not on a signing device.
|
||||||
|
function TrackAddress ({ className }: Props): React.ReactElement<Props> {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const onAction = useContext(ActionContext);
|
||||||
|
const [address, setAddress] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [name, setName] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const _onChangeAddress = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
||||||
|
const value = event.target.value;
|
||||||
|
const trimmed = value.trim();
|
||||||
|
|
||||||
|
if (!trimmed) {
|
||||||
|
setAddress(null);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Round-tripping through decode/encode does two jobs: it rejects a
|
||||||
|
// mistyped address by its checksum rather than storing something that
|
||||||
|
// will never receive anything, and it normalises the prefix so an
|
||||||
|
// address pasted from a tool using a different one is stored the way the
|
||||||
|
// rest of the extension will display it.
|
||||||
|
setAddress(encodeAddress(decodeAddress(trimmed)));
|
||||||
|
setError(null);
|
||||||
|
} catch {
|
||||||
|
setAddress(null);
|
||||||
|
setError(t('This is not a valid address'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const _onCreate = useCallback(
|
||||||
|
(): void => {
|
||||||
|
if (address && name) {
|
||||||
|
// genesisHash null — "any network". A tracked address is just an
|
||||||
|
// address; nothing here knows which chain the holder uses it on.
|
||||||
|
createAccountExternal(name, address, null)
|
||||||
|
.then(() => onAction('/'))
|
||||||
|
.catch((error: Error) => {
|
||||||
|
console.error(error);
|
||||||
|
setError((error).message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[address, name, onAction]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header
|
||||||
|
showBackArrow
|
||||||
|
text={t('Track an address')}
|
||||||
|
/>
|
||||||
|
<div className={className}>
|
||||||
|
<Address
|
||||||
|
address={address}
|
||||||
|
isExternal={true}
|
||||||
|
name={name}
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
autoFocus
|
||||||
|
className='address'
|
||||||
|
onChange={_onChangeAddress}
|
||||||
|
placeholder={t('Paste the address you want to track')}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
{error && (
|
||||||
|
<Warning isDanger>{error}</Warning>
|
||||||
|
)}
|
||||||
|
<Name
|
||||||
|
onChange={setName}
|
||||||
|
value={name || ''}
|
||||||
|
/>
|
||||||
|
<Warning>
|
||||||
|
{t('This account has no key in the extension, so it cannot sign. You will see its balance and can use it as a recipient.')}
|
||||||
|
</Warning>
|
||||||
|
</div>
|
||||||
|
<VerticalSpace />
|
||||||
|
<ButtonArea>
|
||||||
|
<NextStepButton
|
||||||
|
isDisabled={!address || !name}
|
||||||
|
onClick={_onCreate}
|
||||||
|
>
|
||||||
|
{t('Add the address')}
|
||||||
|
</NextStepButton>
|
||||||
|
</ButtonArea>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(styled(TrackAddress)<Props>`
|
||||||
|
.address {
|
||||||
|
background: var(--readonlyInputBackground);
|
||||||
|
border-color: var(--inputBorderColor);
|
||||||
|
border-radius: var(--borderRadius);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
color: var(--textColor);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: var(--inputLabelFontSize);
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`);
|
||||||
@@ -29,10 +29,9 @@ import AssetHubMigration from './AssetHubMigration.js';
|
|||||||
import Export from './Export.js';
|
import Export from './Export.js';
|
||||||
import ExportAll from './ExportAll.js';
|
import ExportAll from './ExportAll.js';
|
||||||
import Forget from './Forget.js';
|
import Forget from './Forget.js';
|
||||||
import ImportLedger from './ImportLedger.js';
|
|
||||||
import ImportQr from './ImportQr.js';
|
|
||||||
import PhishingDetected from './PhishingDetected.js';
|
import PhishingDetected from './PhishingDetected.js';
|
||||||
import RestoreJson from './RestoreJson.js';
|
import RestoreJson from './RestoreJson.js';
|
||||||
|
import TrackAddress from './TrackAddress.js';
|
||||||
import Welcome from './Welcome.js';
|
import Welcome from './Welcome.js';
|
||||||
|
|
||||||
const startSettings = settings.get();
|
const startSettings = settings.get();
|
||||||
@@ -161,8 +160,7 @@ export default function Popup (): React.ReactElement {
|
|||||||
<Route path='/account/forget/:address'>{wrapWithErrorBoundary(<Forget />, 'forget-address')}</Route>
|
<Route path='/account/forget/:address'>{wrapWithErrorBoundary(<Forget />, 'forget-address')}</Route>
|
||||||
<Route path='/account/export/:address'>{wrapWithErrorBoundary(<Export />, 'export-address')}</Route>
|
<Route path='/account/export/:address'>{wrapWithErrorBoundary(<Export />, 'export-address')}</Route>
|
||||||
<Route path='/account/export-all'>{wrapWithErrorBoundary(<ExportAll />, 'export-all-address')}</Route>
|
<Route path='/account/export-all'>{wrapWithErrorBoundary(<ExportAll />, 'export-all-address')}</Route>
|
||||||
<Route path='/account/import-ledger'>{wrapWithErrorBoundary(<ImportLedger />, 'import-ledger')}</Route>
|
<Route path='/account/track-address'>{wrapWithErrorBoundary(<TrackAddress />, 'track-address')}</Route>
|
||||||
<Route path='/account/import-qr'>{wrapWithErrorBoundary(<ImportQr />, 'import-qr')}</Route>
|
|
||||||
<Route path='/account/import-seed'>{wrapWithErrorBoundary(<ImportSeed />, 'import-seed')}</Route>
|
<Route path='/account/import-seed'>{wrapWithErrorBoundary(<ImportSeed />, 'import-seed')}</Route>
|
||||||
<Route path='/account/restore-json'>{wrapWithErrorBoundary(<RestoreJson />, 'restore-json')}</Route>
|
<Route path='/account/restore-json'>{wrapWithErrorBoundary(<RestoreJson />, 'restore-json')}</Route>
|
||||||
<Route path='/account/derive/:address/locked'>{wrapWithErrorBoundary(<Derive isLocked />, 'derived-address-locked')}</Route>
|
<Route path='/account/derive/:address/locked'>{wrapWithErrorBoundary(<Derive isLocked />, 'derived-address-locked')}</Route>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
export { default as useGenesisHashOptions } from './useGenesisHashOptions.js';
|
export { default as useGenesisHashOptions } from './useGenesisHashOptions.js';
|
||||||
export { default as useIsMounted } from './useIsMounted.js';
|
export { default as useIsMounted } from './useIsMounted.js';
|
||||||
export { default as useIsPopup } from './useIsPopup.js';
|
export { default as useIsPopup } from './useIsPopup.js';
|
||||||
export { default as useLedger } from './useLedger.js';
|
|
||||||
export { default as useMetadata } from './useMetadata.js';
|
export { default as useMetadata } from './useMetadata.js';
|
||||||
export { default as useOutsideClick } from './useOutsideClick.js';
|
export { default as useOutsideClick } from './useOutsideClick.js';
|
||||||
export { default as useToast } from './useToast.js';
|
export { default as useToast } from './useToast.js';
|
||||||
|
|||||||
@@ -1,287 +0,0 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// This is to ensure the legacy `class Ledger` doesn't throw linting errors.
|
|
||||||
//
|
|
||||||
/* eslint-disable deprecation/deprecation */
|
|
||||||
|
|
||||||
import type { Network } from '@polkadot/networks/types';
|
|
||||||
import type { HexString } from '@polkadot/util/types';
|
|
||||||
import type { KeypairType } from '@polkadot/util-crypto/types';
|
|
||||||
|
|
||||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import { Ledger, LedgerGeneric } from '@polkadot/hw-ledger';
|
|
||||||
import { knownLedger } from '@polkadot/networks/defaults';
|
|
||||||
import { settings } from '@polkadot/ui-settings';
|
|
||||||
import { assert } from '@polkadot/util';
|
|
||||||
|
|
||||||
import { SettingsContext } from '../components/contexts.js';
|
|
||||||
import chains from '../util/chains.js';
|
|
||||||
import ledgerChains from '../util/legerChains.js';
|
|
||||||
import useTranslation from './useTranslation.js';
|
|
||||||
|
|
||||||
interface StateBase {
|
|
||||||
isLedgerCapable: boolean;
|
|
||||||
isLedgerEnabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State extends StateBase {
|
|
||||||
address: string | null;
|
|
||||||
error: string | null;
|
|
||||||
isLoading: boolean;
|
|
||||||
isLocked: boolean;
|
|
||||||
ledger: LedgerGeneric | Ledger | null;
|
|
||||||
refresh: () => void;
|
|
||||||
type: KeypairType | null;
|
|
||||||
warning: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNetwork (genesisHash: string): Network | undefined {
|
|
||||||
return ledgerChains.find(({ genesisHash: [hash] }) => hash === genesisHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTransportType (): 'hid' | 'webusb' {
|
|
||||||
// prefer WebHID over WebUSB.
|
|
||||||
return ('hid' in navigator) ? 'hid' : 'webusb';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getState (): StateBase {
|
|
||||||
const w = window as unknown as { USB?: unknown; hid?: unknown };
|
|
||||||
const isLedgerCapable = !!(w.hid || w.USB);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLedgerCapable,
|
|
||||||
isLedgerEnabled: isLedgerCapable && settings.ledgerConn !== 'none'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function retrieveLedger (genesis: string | null, ledgerApp: string): LedgerGeneric | Ledger {
|
|
||||||
const { isLedgerCapable } = getState();
|
|
||||||
|
|
||||||
assert(isLedgerCapable, 'Incompatible browser, only Chrome is supported');
|
|
||||||
|
|
||||||
const transport = getTransportType();
|
|
||||||
|
|
||||||
if (ledgerApp === 'generic') {
|
|
||||||
// Generic app always uses Polkadot's slip44, regardless of chain genesis.
|
|
||||||
return new LedgerGeneric(transport, 'polkadot', knownLedger['polkadot']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shouldn't happen but guard to satisfy the compiler
|
|
||||||
assert(genesis, 'Genesis hash is required to connect to the Ledger in non-generic mode');
|
|
||||||
|
|
||||||
const def = getNetwork(genesis);
|
|
||||||
|
|
||||||
assert(def, 'There is no known Ledger app available for this chain');
|
|
||||||
|
|
||||||
assert(def.slip44, 'Slip44 is not available for this network, please report an issue to update this chains slip44');
|
|
||||||
|
|
||||||
if (ledgerApp === 'migration') {
|
|
||||||
return new LedgerGeneric(transport, def.network, knownLedger[def.network]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ledgerApp === 'chainSpecific') {
|
|
||||||
return new Ledger(transport, def.network);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will never get touched since it will always hit the above two. This satisfies the compiler.
|
|
||||||
return new LedgerGeneric(transport, 'polkadot', knownLedger['polkadot']);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function useLedger (genesis?: string | null, accountIndex = 0, addressOffset = 0, isEthereum = false): State {
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [isLocked, setIsLocked] = useState(false);
|
|
||||||
const [refreshCount, setRefreshCount] = useState(0);
|
|
||||||
const [warning, setWarning] = useState<string | null>(null);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [address, setAddress] = useState<string | null>(null);
|
|
||||||
const [type, setType] = useState<KeypairType | null>(null);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { ledgerApp } = useContext(SettingsContext);
|
|
||||||
const tRef = useRef(t);
|
|
||||||
// Holds the ledger from the previous effect run so we can close its
|
|
||||||
// transport when the network changes and a new instance is created.
|
|
||||||
const prevLedgerRef = useRef<LedgerGeneric | Ledger | null>(null);
|
|
||||||
|
|
||||||
// Keep a stable reference so effects and callbacks don't depend on
|
|
||||||
// i18n function identity changes.
|
|
||||||
tRef.current = t;
|
|
||||||
|
|
||||||
const handleGetAddressError = useCallback((e: Error, genesis: string, ledgerApp: string) => {
|
|
||||||
setIsLoading(false);
|
|
||||||
const { network } = getNetwork(genesis) || { network: ledgerApp === 'generic' ? 'Polkadot' : 'unknown network' };
|
|
||||||
|
|
||||||
const warningMessage = e.message.includes('Code: 26628')
|
|
||||||
? tRef.current('Is your ledger locked?')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const errorMessage = e.message.includes('App does not seem to be open')
|
|
||||||
? tRef.current('App "{{network}}" does not seem to be open', { replace: { network } })
|
|
||||||
: e.message;
|
|
||||||
|
|
||||||
setIsLocked(true);
|
|
||||||
setWarning(warningMessage);
|
|
||||||
setError(tRef.current(
|
|
||||||
'Ledger error: {{errorMessage}}',
|
|
||||||
{ replace: { errorMessage } }
|
|
||||||
));
|
|
||||||
console.error(e);
|
|
||||||
setAddress(null);
|
|
||||||
setType(null);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { ledger, ledgerInitError } = useMemo(() => {
|
|
||||||
// undefined means no ledger connection attempt.
|
|
||||||
// null means connect using the generic app without a specific chain genesis.
|
|
||||||
// Prevents hook instances that only check isLedgerCapable or isLedgerEnabled
|
|
||||||
// from attempting a connection when ledgerApp === 'generic'.
|
|
||||||
if (genesis === undefined) {
|
|
||||||
return { ledger: null, ledgerInitError: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic app connects without a specific chain genesis (always derives from polkadot).
|
|
||||||
const canConnect = ledgerApp === 'generic' || refreshCount > 0 || !!genesis;
|
|
||||||
|
|
||||||
if (!canConnect || (ledgerApp !== 'generic' && !genesis)) {
|
|
||||||
return { ledger: null, ledgerInitError: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return { ledger: retrieveLedger(genesis ?? null, ledgerApp), ledgerInitError: null };
|
|
||||||
} catch (error) {
|
|
||||||
return { ledger: null, ledgerInitError: (error as Error).message };
|
|
||||||
}
|
|
||||||
}, [genesis, ledgerApp, refreshCount]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setError(ledgerInitError);
|
|
||||||
}, [ledgerInitError]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let isStale = false;
|
|
||||||
|
|
||||||
if (!ledger || (ledgerApp !== 'generic' && !genesis)) {
|
|
||||||
if (prevLedgerRef.current) {
|
|
||||||
prevLedgerRef.current.disconnect().catch(console.error);
|
|
||||||
prevLedgerRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsLocked(false);
|
|
||||||
setWarning(null);
|
|
||||||
setAddress(null);
|
|
||||||
setType(null);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
isStale = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const runIfCurrent = (fn: () => void): void => {
|
|
||||||
if (!isStale) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAddressError = (e: Error): void => {
|
|
||||||
if (!isStale) {
|
|
||||||
handleGetAddressError(e, genesis ?? '', ledgerApp);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
setError(null);
|
|
||||||
setWarning(null);
|
|
||||||
|
|
||||||
const prevLedger = prevLedgerRef.current;
|
|
||||||
|
|
||||||
prevLedgerRef.current = ledger;
|
|
||||||
|
|
||||||
const fetchLedgerAddress = () => {
|
|
||||||
if (isStale) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chosenNetwork = chains.find(({ genesisHash }) => genesisHash === genesis as HexString);
|
|
||||||
// Use the chain's SS58 prefix when known; fall back to 42 (substrate default).
|
|
||||||
const ss58Prefix = chosenNetwork?.ss58Format ?? 42;
|
|
||||||
|
|
||||||
if (ledgerApp === 'migration') {
|
|
||||||
// Migration app is only expected on known, mapped chains.
|
|
||||||
assert(chosenNetwork, tRef.current('This network is not available, please report an issue to update the known chains'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ledgerApp === 'generic' || ledgerApp === 'migration') {
|
|
||||||
if (isEthereum) {
|
|
||||||
(ledger as LedgerGeneric).getAddressEcdsa(false, accountIndex, addressOffset)
|
|
||||||
.then((res) => {
|
|
||||||
runIfCurrent(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsLocked(false);
|
|
||||||
setAddress(`0x${res.address}`);
|
|
||||||
setType('ethereum');
|
|
||||||
});
|
|
||||||
}).catch(onAddressError);
|
|
||||||
} else {
|
|
||||||
(ledger as LedgerGeneric).getAddress(ss58Prefix, false, accountIndex, addressOffset)
|
|
||||||
.then((res) => {
|
|
||||||
runIfCurrent(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsLocked(false);
|
|
||||||
setAddress(res.address);
|
|
||||||
setType('ed25519');
|
|
||||||
});
|
|
||||||
}).catch(onAddressError);
|
|
||||||
}
|
|
||||||
} else if (ledgerApp === 'chainSpecific') {
|
|
||||||
(ledger as Ledger).getAddress(false, accountIndex, addressOffset)
|
|
||||||
.then((res) => {
|
|
||||||
runIfCurrent(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsLocked(false);
|
|
||||||
setAddress(res.address);
|
|
||||||
setType('ed25519');
|
|
||||||
});
|
|
||||||
}).catch(onAddressError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Disconnect the previous instance before opening the new one (network or ledger app change).
|
|
||||||
if (prevLedger && prevLedger !== ledger) {
|
|
||||||
prevLedger.disconnect().catch(console.error).finally(fetchLedgerAddress);
|
|
||||||
} else {
|
|
||||||
fetchLedgerAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
isStale = true;
|
|
||||||
};
|
|
||||||
}, [accountIndex, addressOffset, genesis, handleGetAddressError, ledger, ledgerApp, isEthereum]);
|
|
||||||
|
|
||||||
const ledgerRef = useRef(ledger);
|
|
||||||
|
|
||||||
// Keep in sync so the unmount cleanup always closes the latest instance.
|
|
||||||
ledgerRef.current = ledger;
|
|
||||||
|
|
||||||
// On unmount, release the transport so the next session can open it.
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
ledgerRef.current?.disconnect().catch(console.error);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
|
||||||
if (ledger) {
|
|
||||||
// Prevent the address-fetch effect from disconnecting this instance again.
|
|
||||||
prevLedgerRef.current = null;
|
|
||||||
ledger.disconnect()
|
|
||||||
.catch(console.error)
|
|
||||||
.finally(() => setRefreshCount((c) => c + 1));
|
|
||||||
} else {
|
|
||||||
setRefreshCount((c) => c + 1);
|
|
||||||
}
|
|
||||||
}, [ledger]);
|
|
||||||
|
|
||||||
return ({ ...getState(), address, error, isLoading, isLocked, ledger, refresh, type, warning });
|
|
||||||
}
|
|
||||||
@@ -143,10 +143,6 @@ export async function createAccountExternal (name: string, address: string, gene
|
|||||||
return sendMessage('pri(accounts.create.external)', { address, genesisHash, name });
|
return sendMessage('pri(accounts.create.external)', { address, genesisHash, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAccountHardware (address: string, hardwareType: string, accountIndex: number, addressOffset: number, name: string, genesisHash: HexString | null, type: KeypairType): Promise<boolean> {
|
|
||||||
return sendMessage('pri(accounts.create.hardware)', { accountIndex, address, addressOffset, genesisHash, hardwareType, name, type });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createAccountSuri (name: string, password: string, suri: string, type?: KeypairType, genesisHash?: HexString | null): Promise<boolean> {
|
export async function createAccountSuri (name: string, password: string, suri: string, type?: KeypairType, genesisHash?: HexString | null): Promise<boolean> {
|
||||||
return sendMessage('pri(accounts.create.suri)', { genesisHash, name, password, suri, type });
|
return sendMessage('pri(accounts.create.suri)', { genesisHash, name, password, suri, type });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import { faUsb } from '@fortawesome/free-brands-svg-icons';
|
import { faCodeBranch, faEye, faFileExport, faFileUpload, faKey, faPlusCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faCodeBranch, faFileExport, faFileUpload, faKey, faPlusCircle, faQrcode } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import React, { useCallback, useContext } from 'react';
|
import React, { useCallback, useContext } from 'react';
|
||||||
|
|
||||||
import { AccountContext, Link, MediaContext, Menu, MenuDivider, MenuItem } from '../components/index.js';
|
import { AccountContext, Link, Menu, MenuDivider, MenuItem } from '../components/index.js';
|
||||||
import { useIsPopup, useLedger, useTranslation } from '../hooks/index.js';
|
import { useIsPopup, useTranslation } from '../hooks/index.js';
|
||||||
import { windowOpen } from '../messaging.js';
|
import { windowOpen } from '../messaging.js';
|
||||||
import { styled } from '../styled.js';
|
import { styled } from '../styled.js';
|
||||||
|
|
||||||
@@ -17,13 +16,10 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const jsonPath = '/account/restore-json';
|
const jsonPath = '/account/restore-json';
|
||||||
const ledgerPath = '/account/import-ledger';
|
|
||||||
|
|
||||||
function MenuAdd ({ className, reference }: Props): React.ReactElement<Props> {
|
function MenuAdd ({ className, reference }: Props): React.ReactElement<Props> {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { master } = useContext(AccountContext);
|
const { master } = useContext(AccountContext);
|
||||||
const mediaAllowed = useContext(MediaContext);
|
|
||||||
const { isLedgerCapable, isLedgerEnabled } = useLedger();
|
|
||||||
const isPopup = useIsPopup();
|
const isPopup = useIsPopup();
|
||||||
|
|
||||||
const _openJson = useCallback(
|
const _openJson = useCallback(
|
||||||
@@ -32,12 +28,6 @@ function MenuAdd ({ className, reference }: Props): React.ReactElement<Props> {
|
|||||||
}, []
|
}, []
|
||||||
);
|
);
|
||||||
|
|
||||||
const _onOpenLedgerConnect = useCallback(
|
|
||||||
(): void => {
|
|
||||||
windowOpen(ledgerPath).catch(console.error);
|
|
||||||
}, []
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
className={className}
|
className={className}
|
||||||
@@ -84,48 +74,11 @@ function MenuAdd ({ className, reference }: Props): React.ReactElement<Props> {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem className='menuItem'>
|
<MenuItem className='menuItem'>
|
||||||
<Link
|
<Link to='/account/track-address'>
|
||||||
isDisabled={!mediaAllowed}
|
<FontAwesomeIcon icon={faEye} />
|
||||||
title={!mediaAllowed
|
<span>{t('Track an address')}</span>
|
||||||
? t('Camera access must be first enabled in the settings')
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
to='/account/import-qr'
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faQrcode} />
|
|
||||||
<span>{t('Attach external QR-signer account')}</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem className='menuItem ledger'>
|
|
||||||
{isLedgerEnabled
|
|
||||||
? (
|
|
||||||
<Link
|
|
||||||
isDisabled={!isLedgerCapable}
|
|
||||||
onClick={isPopup ? _onOpenLedgerConnect : undefined}
|
|
||||||
title={ (!isLedgerCapable && t('Ledger devices can only be connected with Chrome browser')) || ''}
|
|
||||||
to={isPopup ? undefined : ledgerPath}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faUsb}
|
|
||||||
rotation={270}
|
|
||||||
/>
|
|
||||||
<span>{ t('Attach ledger account')}</span>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<Link
|
|
||||||
onClick={isPopup ? _onOpenLedgerConnect : undefined}
|
|
||||||
to={isPopup ? undefined : ledgerPath}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faUsb}
|
|
||||||
rotation={270}
|
|
||||||
/>
|
|
||||||
<span>{ t('Connect Ledger device')}</span>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ function MenuSettings ({ className, reference }: Props): React.ReactElement<Prop
|
|||||||
const isPopup = useIsPopup();
|
const isPopup = useIsPopup();
|
||||||
const languageOptions = useMemo(() => getLanguageOptions(), []);
|
const languageOptions = useMemo(() => getLanguageOptions(), []);
|
||||||
const onAction = useContext(ActionContext);
|
const onAction = useContext(ActionContext);
|
||||||
const _ledgerAppOptions = settings.availableLedgerApp;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
settings.set({ camera: camera ? 'on' : 'off' });
|
settings.set({ camera: camera ? 'on' : 'off' });
|
||||||
@@ -83,12 +82,6 @@ function MenuSettings ({ className, reference }: Props): React.ReactElement<Prop
|
|||||||
}, []
|
}, []
|
||||||
);
|
);
|
||||||
|
|
||||||
const _onChangeLedgerApp = useCallback(
|
|
||||||
(value: string): void => {
|
|
||||||
settings.set({ ledgerApp: value });
|
|
||||||
}, []
|
|
||||||
);
|
|
||||||
|
|
||||||
const _goToAuthList = useCallback(
|
const _goToAuthList = useCallback(
|
||||||
() => {
|
() => {
|
||||||
onAction('auth-list');
|
onAction('auth-list');
|
||||||
@@ -123,18 +116,6 @@ function MenuSettings ({ className, reference }: Props): React.ReactElement<Prop
|
|||||||
value={`${prefix}`}
|
value={`${prefix}`}
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
|
||||||
className='setting'
|
|
||||||
title={t('Ledger App')}
|
|
||||||
>
|
|
||||||
<Dropdown
|
|
||||||
className='dropdown'
|
|
||||||
label=''
|
|
||||||
onChange={_onChangeLedgerApp}
|
|
||||||
options={_ledgerAppOptions}
|
|
||||||
value={settings.ledgerApp}
|
|
||||||
/>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className='setting'
|
className='setting'
|
||||||
title={t('Language')}
|
title={t('Language')}
|
||||||
@@ -222,9 +203,6 @@ export default React.memo(styled(MenuSettings)<Props>`
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
&.ledger {
|
|
||||||
margin-top: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: var(--textColor);
|
color: var(--textColor);
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import { selectableNetworks } from '@polkadot/networks';
|
|
||||||
|
|
||||||
export default selectableNetworks.filter((network) => network.hasLedgerSupport);
|
|
||||||
@@ -199,5 +199,12 @@
|
|||||||
"I Understand": "",
|
"I Understand": "",
|
||||||
"Do not teleport balances to the Relay Chain unless:": "",
|
"Do not teleport balances to the Relay Chain unless:": "",
|
||||||
"Address mismatch: derived {{derived}}, expected {{expected}}. Check that the correct Ledger device is connected and the correct app is selected.": "",
|
"Address mismatch: derived {{derived}}, expected {{expected}}. Check that the correct Ledger device is connected and the correct app is selected.": "",
|
||||||
"Possible cause: the Ledger App setting differs from the app originally used to derive this account.": ""
|
"Possible cause: the Ledger App setting differs from the app originally used to derive this account.": "",
|
||||||
|
"This approval will add the metadata to your extension instance, allowing future requests to be decoded using this metadata.": "",
|
||||||
|
"Track an address": "",
|
||||||
|
"This account is tracked only — the extension holds no key for it and cannot sign.": "",
|
||||||
|
"This is not a valid address": "",
|
||||||
|
"Paste the address you want to track": "",
|
||||||
|
"This account has no key in the extension, so it cannot sign. You will see its balance and can use it as a recipient.": "",
|
||||||
|
"Add the address": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -408,108 +408,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@ledgerhq/devices@npm:8.4.7, @ledgerhq/devices@npm:^8.4.4":
|
|
||||||
version: 8.4.7
|
|
||||||
resolution: "@ledgerhq/devices@npm:8.4.7"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/errors": "npm:^6.22.0"
|
|
||||||
"@ledgerhq/logs": "npm:^6.13.0"
|
|
||||||
rxjs: "npm:^7.8.1"
|
|
||||||
semver: "npm:^7.3.5"
|
|
||||||
checksum: 10/1d49aa692831915f1a540cba2d7faba3a08e5a2d1db831e3849c7791526d6ebb85fab679585f6940d5a923abf91f2113d675445172e37b38d742c3f18952e0fc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/errors@npm:^6.19.1, @ledgerhq/errors@npm:^6.22.0":
|
|
||||||
version: 6.22.0
|
|
||||||
resolution: "@ledgerhq/errors@npm:6.22.0"
|
|
||||||
checksum: 10/6bea0c5fbb07c4f20fbc5083856646708c195c6d138dcbeed1f08f88c97536fd5059959a724332c45f17b8944aa8d8e68d850f43b33bf5b2f30d901c593e2930
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/hw-transport-node-hid-noevents@npm:^6.30.8":
|
|
||||||
version: 6.30.8
|
|
||||||
resolution: "@ledgerhq/hw-transport-node-hid-noevents@npm:6.30.8"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/devices": "npm:8.4.7"
|
|
||||||
"@ledgerhq/errors": "npm:^6.22.0"
|
|
||||||
"@ledgerhq/hw-transport": "npm:^6.31.7"
|
|
||||||
"@ledgerhq/logs": "npm:^6.13.0"
|
|
||||||
node-hid: "npm:2.1.2"
|
|
||||||
checksum: 10/b6e5795636431d06befd64cded66f300b8c81bfa8d590d56f728662e77074cb26898903be6371c1f3a720473173f6c5d223778b2ac8ad84e7b450ed64168f901
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/hw-transport-node-hid-singleton@npm:^6.31.5":
|
|
||||||
version: 6.31.8
|
|
||||||
resolution: "@ledgerhq/hw-transport-node-hid-singleton@npm:6.31.8"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/devices": "npm:8.4.7"
|
|
||||||
"@ledgerhq/errors": "npm:^6.22.0"
|
|
||||||
"@ledgerhq/hw-transport": "npm:^6.31.7"
|
|
||||||
"@ledgerhq/hw-transport-node-hid-noevents": "npm:^6.30.8"
|
|
||||||
"@ledgerhq/logs": "npm:^6.13.0"
|
|
||||||
node-hid: "npm:2.1.2"
|
|
||||||
usb: "npm:2.9.0"
|
|
||||||
checksum: 10/d79272bcb79d863f9b7934a4175e4564faea71257ec419675942e964f95ad9ae159632594f63f86e55c68572c7377848de9f3f4ecf684134beddf182f1a462cb
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/hw-transport-webhid@npm:^6.29.4":
|
|
||||||
version: 6.30.3
|
|
||||||
resolution: "@ledgerhq/hw-transport-webhid@npm:6.30.3"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/devices": "npm:8.4.7"
|
|
||||||
"@ledgerhq/errors": "npm:^6.22.0"
|
|
||||||
"@ledgerhq/hw-transport": "npm:^6.31.7"
|
|
||||||
"@ledgerhq/logs": "npm:^6.13.0"
|
|
||||||
checksum: 10/4f9ea31952fd07e366fab0acd020cabdf2346e03aaa6971805aeec8820225ffac66a57aaf375137f599ba0e9fd42f7b3febd923dd3b420b70a421588054b2135
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/hw-transport-webusb@npm:^6.29.4":
|
|
||||||
version: 6.29.7
|
|
||||||
resolution: "@ledgerhq/hw-transport-webusb@npm:6.29.7"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/devices": "npm:8.4.7"
|
|
||||||
"@ledgerhq/errors": "npm:^6.22.0"
|
|
||||||
"@ledgerhq/hw-transport": "npm:^6.31.7"
|
|
||||||
"@ledgerhq/logs": "npm:^6.13.0"
|
|
||||||
checksum: 10/2beb65383939df07c0df18e2b8d97c41e1fffbba4af9bccf26639d3814c617f47293f5a16b268340b5e64fe1f45f4b5a65ecc4edba3c502984d2fa92bed25c2b
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/hw-transport@npm:6.31.4":
|
|
||||||
version: 6.31.4
|
|
||||||
resolution: "@ledgerhq/hw-transport@npm:6.31.4"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/devices": "npm:^8.4.4"
|
|
||||||
"@ledgerhq/errors": "npm:^6.19.1"
|
|
||||||
"@ledgerhq/logs": "npm:^6.12.0"
|
|
||||||
events: "npm:^3.3.0"
|
|
||||||
checksum: 10/cf101e5b818e95e59031241d556dbec24658f54104910e414be493bc4b90b0aea50f5d4b3339a237dd0b12845bb2683c845f3a82f2ea9da4e077b68d1e1f7e48
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/hw-transport@npm:^6.31.4, @ledgerhq/hw-transport@npm:^6.31.7":
|
|
||||||
version: 6.31.7
|
|
||||||
resolution: "@ledgerhq/hw-transport@npm:6.31.7"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/devices": "npm:8.4.7"
|
|
||||||
"@ledgerhq/errors": "npm:^6.22.0"
|
|
||||||
"@ledgerhq/logs": "npm:^6.13.0"
|
|
||||||
events: "npm:^3.3.0"
|
|
||||||
checksum: 10/a8c44980fb1d795f23fe62edc6a7b910efcb2dda6b626f6ac3a6b2efd4c855d6591e9cd6fa3d7d65c89eea2418ee4b960878cc6be0d73e7e2a958d3af378f44a
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@ledgerhq/logs@npm:^6.12.0, @ledgerhq/logs@npm:^6.13.0":
|
|
||||||
version: 6.13.0
|
|
||||||
resolution: "@ledgerhq/logs@npm:6.13.0"
|
|
||||||
checksum: 10/3ac984bdc533505359cd1d8bc74175547fcfa51fef6294a9fd832e8ea88d462a385be5cfe68ccfa1b77cd68b840243e6f79c2be66fe28f123ae57c58625edf5f
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@leichtgewicht/ip-codec@npm:^2.0.1":
|
"@leichtgewicht/ip-codec@npm:^2.0.1":
|
||||||
version: 2.0.5
|
version: 2.0.5
|
||||||
resolution: "@leichtgewicht/ip-codec@npm:2.0.5"
|
resolution: "@leichtgewicht/ip-codec@npm:2.0.5"
|
||||||
@@ -1184,7 +1082,6 @@ __metadata:
|
|||||||
"@polkadot/extension-dapp": "npm:0.64.0"
|
"@polkadot/extension-dapp": "npm:0.64.0"
|
||||||
"@polkadot/extension-inject": "npm:0.64.0"
|
"@polkadot/extension-inject": "npm:0.64.0"
|
||||||
"@polkadot/extension-mocks": "npm:0.64.0"
|
"@polkadot/extension-mocks": "npm:0.64.0"
|
||||||
"@polkadot/hw-ledger": "npm:^14.0.3"
|
|
||||||
"@polkadot/keyring": "npm:^14.0.3"
|
"@polkadot/keyring": "npm:^14.0.3"
|
||||||
"@polkadot/networks": "npm:^14.0.3"
|
"@polkadot/networks": "npm:^14.0.3"
|
||||||
"@polkadot/react-identicon": "npm:^3.16.7"
|
"@polkadot/react-identicon": "npm:^3.16.7"
|
||||||
@@ -1253,35 +1150,6 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@polkadot/hw-ledger-transports@npm:14.0.3":
|
|
||||||
version: 14.0.3
|
|
||||||
resolution: "@polkadot/hw-ledger-transports@npm:14.0.3"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/hw-transport": "npm:^6.31.4"
|
|
||||||
"@ledgerhq/hw-transport-node-hid-singleton": "npm:^6.31.5"
|
|
||||||
"@ledgerhq/hw-transport-webhid": "npm:^6.29.4"
|
|
||||||
"@ledgerhq/hw-transport-webusb": "npm:^6.29.4"
|
|
||||||
"@polkadot/util": "npm:14.0.3"
|
|
||||||
tslib: "npm:^2.8.0"
|
|
||||||
dependenciesMeta:
|
|
||||||
"@ledgerhq/hw-transport-node-hid-singleton":
|
|
||||||
optional: true
|
|
||||||
checksum: 10/5cf48a41079e06ea4c46df4c4aaf2efcca786b19f9eecb0c7c0ba505a1f5a98f730518cff41e3b0fa27f09d6dc74329e2f2b982a2dd8f1c7e59db9d65b90eabe
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@polkadot/hw-ledger@npm:^14.0.3":
|
|
||||||
version: 14.0.3
|
|
||||||
resolution: "@polkadot/hw-ledger@npm:14.0.3"
|
|
||||||
dependencies:
|
|
||||||
"@polkadot/hw-ledger-transports": "npm:14.0.3"
|
|
||||||
"@polkadot/util": "npm:14.0.3"
|
|
||||||
"@zondax/ledger-substrate": "npm:1.1.1"
|
|
||||||
tslib: "npm:^2.8.0"
|
|
||||||
checksum: 10/1f4746a3fd7f311211c451ba26ff1cd11bcfc729c27bc9516e619e5df2d57a9e79e94b163d2e00448864680c7afa0d20b933932134bafa709af091db1e6d9b10
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@polkadot/keyring@https://git.lair.cafe/api/packages/quantus/npm/%40polkadot%2Fkeyring/-/14.0.3-quantus.1/keyring-14.0.3-quantus.1.tgz":
|
"@polkadot/keyring@https://git.lair.cafe/api/packages/quantus/npm/%40polkadot%2Fkeyring/-/14.0.3-quantus.1/keyring-14.0.3-quantus.1.tgz":
|
||||||
version: 14.0.3-quantus.1
|
version: 14.0.3-quantus.1
|
||||||
resolution: "@polkadot/keyring@https://git.lair.cafe/api/packages/quantus/npm/%40polkadot%2Fkeyring/-/14.0.3-quantus.1/keyring-14.0.3-quantus.1.tgz"
|
resolution: "@polkadot/keyring@https://git.lair.cafe/api/packages/quantus/npm/%40polkadot%2Fkeyring/-/14.0.3-quantus.1/keyring-14.0.3-quantus.1.tgz"
|
||||||
@@ -2649,13 +2517,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/w3c-web-usb@npm:^1.0.6":
|
|
||||||
version: 1.0.10
|
|
||||||
resolution: "@types/w3c-web-usb@npm:1.0.10"
|
|
||||||
checksum: 10/6ac6786a0788f0846a48b103ab06ca5fde5eb95674217b522420a2f6157bee3e181a961c1b7011940f497c55f4f5cc46129657d881fdd8112b48764089679ad6
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@types/ws@npm:8.5.3":
|
"@types/ws@npm:8.5.3":
|
||||||
version: 8.5.3
|
version: 8.5.3
|
||||||
resolution: "@types/ws@npm:8.5.3"
|
resolution: "@types/ws@npm:8.5.3"
|
||||||
@@ -3267,26 +3128,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@zondax/ledger-js@npm:^1.2.0":
|
|
||||||
version: 1.2.0
|
|
||||||
resolution: "@zondax/ledger-js@npm:1.2.0"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/hw-transport": "npm:6.31.4"
|
|
||||||
checksum: 10/5852b340efd421f96d4e52d0a93d4e8464604c070939dd42fade5a495010a08d730b03e1b7ab3ff0f26f13075d8a0a5226a748a41986f2ed087fca4471f24e4e
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@zondax/ledger-substrate@npm:1.1.1":
|
|
||||||
version: 1.1.1
|
|
||||||
resolution: "@zondax/ledger-substrate@npm:1.1.1"
|
|
||||||
dependencies:
|
|
||||||
"@ledgerhq/hw-transport": "npm:6.31.4"
|
|
||||||
"@zondax/ledger-js": "npm:^1.2.0"
|
|
||||||
axios: "npm:^1.8.4"
|
|
||||||
checksum: 10/039ffef55b31870f82c6b0f20b518089ad9ce0295d695f880640cb586256adacd4a326c10157ccaf82e897abde7358f9e4e02c069dac672ec90881e21d250722
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@zxcvbn-ts/core@npm:^3.0.4":
|
"@zxcvbn-ts/core@npm:^3.0.4":
|
||||||
version: 3.0.4
|
version: 3.0.4
|
||||||
resolution: "@zxcvbn-ts/core@npm:3.0.4"
|
resolution: "@zxcvbn-ts/core@npm:3.0.4"
|
||||||
@@ -3909,17 +3750,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"axios@npm:^1.8.4":
|
|
||||||
version: 1.10.0
|
|
||||||
resolution: "axios@npm:1.10.0"
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: "npm:^1.15.6"
|
|
||||||
form-data: "npm:^4.0.0"
|
|
||||||
proxy-from-env: "npm:^1.1.0"
|
|
||||||
checksum: 10/d43c80316a45611fd395743e15d16ea69a95f2b7f7095f2bb12cb78f9ca0a905194a02e52a3bf4e0db9f85fd1186d6c690410644c10ecd8bb0a468e57c2040e4
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"b4a@npm:^1.6.4":
|
"b4a@npm:^1.6.4":
|
||||||
version: 1.6.7
|
version: 1.6.7
|
||||||
resolution: "b4a@npm:1.6.7"
|
resolution: "b4a@npm:1.6.7"
|
||||||
@@ -4602,16 +4432,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"bindings@npm:^1.5.0":
|
"bl@npm:^4.1.0":
|
||||||
version: 1.5.0
|
|
||||||
resolution: "bindings@npm:1.5.0"
|
|
||||||
dependencies:
|
|
||||||
file-uri-to-path: "npm:1.0.0"
|
|
||||||
checksum: 10/593d5ae975ffba15fbbb4788fe5abd1e125afbab849ab967ab43691d27d6483751805d98cb92f7ac24a2439a8a8678cd0131c535d5d63de84e383b0ce2786133
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"bl@npm:^4.0.3, bl@npm:^4.1.0":
|
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
resolution: "bl@npm:4.1.0"
|
resolution: "bl@npm:4.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5131,13 +4952,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"chownr@npm:^1.1.1":
|
|
||||||
version: 1.1.4
|
|
||||||
resolution: "chownr@npm:1.1.4"
|
|
||||||
checksum: 10/115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"chownr@npm:^3.0.0":
|
"chownr@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "chownr@npm:3.0.0"
|
resolution: "chownr@npm:3.0.0"
|
||||||
@@ -5924,13 +5738,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"detect-libc@npm:^2.0.0":
|
|
||||||
version: 2.0.4
|
|
||||||
resolution: "detect-libc@npm:2.0.4"
|
|
||||||
checksum: 10/136e995f8c5ffbc515955b0175d441b967defd3d5f2268e89fa695e9c7170d8bed17993e31a34b04f0fad33d844a3a598e0fd519a8e9be3cad5f67662d96fee0
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"detect-node@npm:^2.0.4":
|
"detect-node@npm:^2.0.4":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "detect-node@npm:2.1.0"
|
resolution: "detect-node@npm:2.1.0"
|
||||||
@@ -7192,7 +6999,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"events@npm:^3.2.0, events@npm:^3.3.0":
|
"events@npm:^3.2.0":
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
resolution: "events@npm:3.3.0"
|
resolution: "events@npm:3.3.0"
|
||||||
checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be
|
checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be
|
||||||
@@ -7227,13 +7034,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"expand-template@npm:^2.0.3":
|
|
||||||
version: 2.0.3
|
|
||||||
resolution: "expand-template@npm:2.0.3"
|
|
||||||
checksum: 10/588c19847216421ed92befb521767b7018dc88f88b0576df98cb242f20961425e96a92cbece525ef28cc5becceae5d544ae0f5b9b5e2aa05acb13716ca5b3099
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"exponential-backoff@npm:^3.1.1":
|
"exponential-backoff@npm:^3.1.1":
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
resolution: "exponential-backoff@npm:3.1.2"
|
resolution: "exponential-backoff@npm:3.1.2"
|
||||||
@@ -7454,13 +7254,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"file-uri-to-path@npm:1.0.0":
|
|
||||||
version: 1.0.0
|
|
||||||
resolution: "file-uri-to-path@npm:1.0.0"
|
|
||||||
checksum: 10/b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"filename-reserved-regex@npm:^2.0.0":
|
"filename-reserved-regex@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "filename-reserved-regex@npm:2.0.0"
|
resolution: "filename-reserved-regex@npm:2.0.0"
|
||||||
@@ -7591,7 +7384,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.6":
|
"follow-redirects@npm:^1.0.0":
|
||||||
version: 1.15.9
|
version: 1.15.9
|
||||||
resolution: "follow-redirects@npm:1.15.9"
|
resolution: "follow-redirects@npm:1.15.9"
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -7656,13 +7449,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"fs-constants@npm:^1.0.0":
|
|
||||||
version: 1.0.0
|
|
||||||
resolution: "fs-constants@npm:1.0.0"
|
|
||||||
checksum: 10/18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"fs-extra@npm:^11.1.1":
|
"fs-extra@npm:^11.1.1":
|
||||||
version: 11.3.0
|
version: 11.3.0
|
||||||
resolution: "fs-extra@npm:11.3.0"
|
resolution: "fs-extra@npm:11.3.0"
|
||||||
@@ -7942,13 +7728,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"github-from-package@npm:0.0.0":
|
|
||||||
version: 0.0.0
|
|
||||||
resolution: "github-from-package@npm:0.0.0"
|
|
||||||
checksum: 10/2a091ba07fbce22205642543b4ea8aaf068397e1433c00ae0f9de36a3607baf5bcc14da97fbb798cfca6393b3c402031fca06d8b491a44206d6efef391c58537
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"github-url-to-object@npm:^4.0.4":
|
"github-url-to-object@npm:^4.0.4":
|
||||||
version: 4.0.6
|
version: 4.0.6
|
||||||
resolution: "github-url-to-object@npm:4.0.6"
|
resolution: "github-url-to-object@npm:4.0.6"
|
||||||
@@ -10108,7 +9887,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6":
|
"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6":
|
||||||
version: 1.2.8
|
version: 1.2.8
|
||||||
resolution: "minimist@npm:1.2.8"
|
resolution: "minimist@npm:1.2.8"
|
||||||
checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f
|
checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f
|
||||||
@@ -10191,13 +9970,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3":
|
|
||||||
version: 0.5.3
|
|
||||||
resolution: "mkdirp-classic@npm:0.5.3"
|
|
||||||
checksum: 10/3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"mkdirp@npm:^0.5.1":
|
"mkdirp@npm:^0.5.1":
|
||||||
version: 0.5.6
|
version: 0.5.6
|
||||||
resolution: "mkdirp@npm:0.5.6"
|
resolution: "mkdirp@npm:0.5.6"
|
||||||
@@ -10313,13 +10085,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"napi-build-utils@npm:^2.0.0":
|
|
||||||
version: 2.0.0
|
|
||||||
resolution: "napi-build-utils@npm:2.0.0"
|
|
||||||
checksum: 10/69adcdb828481737f1ec64440286013f6479d5b264e24d5439ba795f65293d0bb6d962035de07c65fae525ed7d2fcd0baab6891d8e3734ea792fec43918acf83
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"napi-postinstall@npm:^0.3.0":
|
"napi-postinstall@npm:^0.3.0":
|
||||||
version: 0.3.0
|
version: 0.3.0
|
||||||
resolution: "napi-postinstall@npm:0.3.0"
|
resolution: "napi-postinstall@npm:0.3.0"
|
||||||
@@ -10422,33 +10187,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"node-abi@npm:^3.3.0":
|
|
||||||
version: 3.75.0
|
|
||||||
resolution: "node-abi@npm:3.75.0"
|
|
||||||
dependencies:
|
|
||||||
semver: "npm:^7.3.5"
|
|
||||||
checksum: 10/dd87627fa4f447bda9c69dc1ec0da82e3b466790844b5bd7f7787db093dfea21dcc405588e13b5207c266472c1fda9b95060da8d70644aeb5fc31ec81dc2007c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"node-addon-api@npm:^3.0.2":
|
|
||||||
version: 3.2.1
|
|
||||||
resolution: "node-addon-api@npm:3.2.1"
|
|
||||||
dependencies:
|
|
||||||
node-gyp: "npm:latest"
|
|
||||||
checksum: 10/681b52dfa3e15b0a8e5cf283cc0d8cd5fd2a57c559ae670fcfd20544cbb32f75de7648674110defcd17ab2c76ebef630aa7d2d2f930bc7a8cc439b20fe233518
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"node-addon-api@npm:^6.0.0":
|
|
||||||
version: 6.1.0
|
|
||||||
resolution: "node-addon-api@npm:6.1.0"
|
|
||||||
dependencies:
|
|
||||||
node-gyp: "npm:latest"
|
|
||||||
checksum: 10/8eea1d4d965930a177a0508695beb0d89b4c1d80bf330646a035357a1e8fc31e0d09686e2374996e96e757b947a7ece319f98ede3146683f162597c0bcb4df90
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"node-domexception@npm:^1.0.0":
|
"node-domexception@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "node-domexception@npm:1.0.0"
|
resolution: "node-domexception@npm:1.0.0"
|
||||||
@@ -10488,17 +10226,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"node-gyp-build@npm:^4.5.0":
|
|
||||||
version: 4.8.4
|
|
||||||
resolution: "node-gyp-build@npm:4.8.4"
|
|
||||||
bin:
|
|
||||||
node-gyp-build: bin.js
|
|
||||||
node-gyp-build-optional: optional.js
|
|
||||||
node-gyp-build-test: build-test.js
|
|
||||||
checksum: 10/6a7d62289d1afc419fc8fc9bd00aa4e554369e50ca0acbc215cb91446148b75ff7e2a3b53c2c5b2c09a39d416d69f3d3237937860373104b5fe429bf30ad9ac5
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"node-gyp@npm:latest":
|
"node-gyp@npm:latest":
|
||||||
version: 11.2.0
|
version: 11.2.0
|
||||||
resolution: "node-gyp@npm:11.2.0"
|
resolution: "node-gyp@npm:11.2.0"
|
||||||
@@ -10519,20 +10246,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"node-hid@npm:2.1.2":
|
|
||||||
version: 2.1.2
|
|
||||||
resolution: "node-hid@npm:2.1.2"
|
|
||||||
dependencies:
|
|
||||||
bindings: "npm:^1.5.0"
|
|
||||||
node-addon-api: "npm:^3.0.2"
|
|
||||||
node-gyp: "npm:latest"
|
|
||||||
prebuild-install: "npm:^7.1.1"
|
|
||||||
bin:
|
|
||||||
hid-showdevices: src/show-devices.js
|
|
||||||
checksum: 10/7b424d9f049221b439cfa3a092f415b608bcd316b819ff1fb0bf5234d766348d9fdaeaafa5ccece514c045825a936cb5617080168066d16b3aaeb0e9808e38f9
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"node-releases@npm:^2.0.19":
|
"node-releases@npm:^2.0.19":
|
||||||
version: 2.0.19
|
version: 2.0.19
|
||||||
resolution: "node-releases@npm:2.0.19"
|
resolution: "node-releases@npm:2.0.19"
|
||||||
@@ -11223,28 +10936,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"prebuild-install@npm:^7.1.1":
|
|
||||||
version: 7.1.3
|
|
||||||
resolution: "prebuild-install@npm:7.1.3"
|
|
||||||
dependencies:
|
|
||||||
detect-libc: "npm:^2.0.0"
|
|
||||||
expand-template: "npm:^2.0.3"
|
|
||||||
github-from-package: "npm:0.0.0"
|
|
||||||
minimist: "npm:^1.2.3"
|
|
||||||
mkdirp-classic: "npm:^0.5.3"
|
|
||||||
napi-build-utils: "npm:^2.0.0"
|
|
||||||
node-abi: "npm:^3.3.0"
|
|
||||||
pump: "npm:^3.0.0"
|
|
||||||
rc: "npm:^1.2.7"
|
|
||||||
simple-get: "npm:^4.0.0"
|
|
||||||
tar-fs: "npm:^2.0.0"
|
|
||||||
tunnel-agent: "npm:^0.6.0"
|
|
||||||
bin:
|
|
||||||
prebuild-install: bin.js
|
|
||||||
checksum: 10/1b7e4c00d2750b532a4fc2a83ffb0c5fefa1b6f2ad071896ead15eeadc3255f5babd816949991af083cf7429e375ae8c7d1c51f73658559da36f948a020a3a11
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"precinct@npm:^8.1.0":
|
"precinct@npm:^8.1.0":
|
||||||
version: 8.3.1
|
version: 8.3.1
|
||||||
resolution: "precinct@npm:8.3.1"
|
resolution: "precinct@npm:8.3.1"
|
||||||
@@ -11403,13 +11094,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"proxy-from-env@npm:^1.1.0":
|
|
||||||
version: 1.1.0
|
|
||||||
resolution: "proxy-from-env@npm:1.1.0"
|
|
||||||
checksum: 10/f0bb4a87cfd18f77bc2fba23ae49c3b378fb35143af16cc478171c623eebe181678f09439707ad80081d340d1593cd54a33a0113f3ccb3f4bc9451488780ee23
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"psl@npm:^1.1.33":
|
"psl@npm:^1.1.33":
|
||||||
version: 1.15.0
|
version: 1.15.0
|
||||||
resolution: "psl@npm:1.15.0"
|
resolution: "psl@npm:1.15.0"
|
||||||
@@ -13361,31 +13045,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tar-fs@npm:^2.0.0":
|
|
||||||
version: 2.1.3
|
|
||||||
resolution: "tar-fs@npm:2.1.3"
|
|
||||||
dependencies:
|
|
||||||
chownr: "npm:^1.1.1"
|
|
||||||
mkdirp-classic: "npm:^0.5.2"
|
|
||||||
pump: "npm:^3.0.0"
|
|
||||||
tar-stream: "npm:^2.1.4"
|
|
||||||
checksum: 10/37fdfd3aa73f4f49c0821ef75f67647ecafd5370d2e311d9ace6ff3825ff4355014055c3d43407c6a655adf6c5bfb0cbcf93412161dad5af7110eb7d7a0c2eae
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"tar-stream@npm:^2.1.4":
|
|
||||||
version: 2.2.0
|
|
||||||
resolution: "tar-stream@npm:2.2.0"
|
|
||||||
dependencies:
|
|
||||||
bl: "npm:^4.0.3"
|
|
||||||
end-of-stream: "npm:^1.4.1"
|
|
||||||
fs-constants: "npm:^1.0.0"
|
|
||||||
inherits: "npm:^2.0.3"
|
|
||||||
readable-stream: "npm:^3.1.1"
|
|
||||||
checksum: 10/1a52a51d240c118cbcd30f7368ea5e5baef1eac3e6b793fb1a41e6cd7319296c79c0264ccc5859f5294aa80f8f00b9239d519e627b9aade80038de6f966fec6a
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"tar@npm:^7.4.3":
|
"tar@npm:^7.4.3":
|
||||||
version: 7.4.3
|
version: 7.4.3
|
||||||
resolution: "tar@npm:7.4.3"
|
resolution: "tar@npm:7.4.3"
|
||||||
@@ -13729,15 +13388,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tunnel-agent@npm:^0.6.0":
|
|
||||||
version: 0.6.0
|
|
||||||
resolution: "tunnel-agent@npm:0.6.0"
|
|
||||||
dependencies:
|
|
||||||
safe-buffer: "npm:^5.0.1"
|
|
||||||
checksum: 10/7f0d9ed5c22404072b2ae8edc45c071772affd2ed14a74f03b4e71b4dd1a14c3714d85aed64abcaaee5fec2efc79002ba81155c708f4df65821b444abb0cfade
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
|
"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
|
||||||
version: 0.4.0
|
version: 0.4.0
|
||||||
resolution: "type-check@npm:0.4.0"
|
resolution: "type-check@npm:0.4.0"
|
||||||
@@ -14117,18 +13767,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"usb@npm:2.9.0":
|
|
||||||
version: 2.9.0
|
|
||||||
resolution: "usb@npm:2.9.0"
|
|
||||||
dependencies:
|
|
||||||
"@types/w3c-web-usb": "npm:^1.0.6"
|
|
||||||
node-addon-api: "npm:^6.0.0"
|
|
||||||
node-gyp: "npm:latest"
|
|
||||||
node-gyp-build: "npm:^4.5.0"
|
|
||||||
checksum: 10/ab68d4247a24a463328e293256aec5682c12163278ab474b7243a9dba6283a44bb74f9e0ca96f1d00197bb271f294549c3a4debdbd3452a02750a4a65ff29ee6
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1":
|
"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "util-deprecate@npm:1.0.2"
|
resolution: "util-deprecate@npm:1.0.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user