feat: local fork allow fake accounts (#10342)
* fix: upgrade chopsticks * feat: expose fork in context * fix lock * fix: add account to chopsticks * feat: sign fake * fix: lint
This commit is contained in:
parent
60bdc504b6
commit
453b94f381
@ -19,6 +19,7 @@ import { BN_ZERO, isFunction } from '@polkadot/util';
|
||||
import CreateModal from '../modals/Create.js';
|
||||
import ImportModal from '../modals/Import.js';
|
||||
import Ledger from '../modals/Ledger.js';
|
||||
import Local from '../modals/LocalAdd.js';
|
||||
import Multisig from '../modals/MultisigCreate.js';
|
||||
import Proxy from '../modals/ProxiedAdd.js';
|
||||
import Qr from '../modals/Qr.js';
|
||||
@ -44,17 +45,18 @@ interface SortControls {
|
||||
sortFromMax: boolean;
|
||||
}
|
||||
|
||||
type GroupName = 'accounts' | 'hardware' | 'injected' | 'multisig' | 'proxied' | 'qr' | 'testing';
|
||||
type GroupName = 'accounts' | 'chopsticks' | 'hardware' | 'injected' | 'multisig' | 'proxied' | 'qr' | 'testing';
|
||||
|
||||
const DEFAULT_SORT_CONTROLS: SortControls = { sortBy: 'date', sortFromMax: true };
|
||||
|
||||
const STORE_FAVS = 'accounts:favorites';
|
||||
|
||||
const GROUP_ORDER: GroupName[] = ['accounts', 'injected', 'qr', 'hardware', 'proxied', 'multisig', 'testing'];
|
||||
const GROUP_ORDER: GroupName[] = ['accounts', 'injected', 'qr', 'hardware', 'proxied', 'multisig', 'testing', 'chopsticks'];
|
||||
|
||||
function groupAccounts (accounts: SortedAccount[]): Record<GroupName, string[]> {
|
||||
const ret: Record<GroupName, string[]> = {
|
||||
accounts: [],
|
||||
chopsticks: [],
|
||||
hardware: [],
|
||||
injected: [],
|
||||
multisig: [],
|
||||
@ -77,6 +79,8 @@ function groupAccounts (accounts: SortedAccount[]): Record<GroupName, string[]>
|
||||
ret.multisig.push(address);
|
||||
} else if (cryptoType === 'proxied') {
|
||||
ret.proxied.push(address);
|
||||
} else if (cryptoType === 'chopsticks') {
|
||||
ret.chopsticks.push(address);
|
||||
} else if (cryptoType === 'qr') {
|
||||
ret.qr.push(address);
|
||||
} else {
|
||||
@ -89,7 +93,7 @@ function groupAccounts (accounts: SortedAccount[]): Record<GroupName, string[]>
|
||||
|
||||
function Overview ({ className = '', onStatusChange }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api, isElectron } = useApi();
|
||||
const { api, fork, isElectron } = useApi();
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const { isIpfs } = useIpfs();
|
||||
const { isLedgerEnabled } = useLedger();
|
||||
@ -98,6 +102,7 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
const [isLedgerOpen, toggleLedger] = useToggle();
|
||||
const [isMultisigOpen, toggleMultisig] = useToggle();
|
||||
const [isProxyOpen, toggleProxy] = useToggle();
|
||||
const [isLocalOpen, toggleLocal] = useToggle();
|
||||
const [isQrOpen, toggleQr] = useToggle();
|
||||
const [favorites, toggleFavorite] = useFavorites(STORE_FAVS);
|
||||
const [balances, setBalances] = useState<Balances>({ accounts: {} });
|
||||
@ -197,6 +202,7 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
(): Record<GroupName, [React.ReactNode?, string?, number?, (() => void)?][]> => {
|
||||
const ret: Record<GroupName, [React.ReactNode?, string?, number?, (() => void)?][]> = {
|
||||
accounts: [[<>{t('accounts')}<div className='sub'>{t('all locally stored accounts')}</div></>]],
|
||||
chopsticks: [[<>{t('chopsticks')}<div className='sub'>{t('local accounts added via chopsticks fork')}</div></>]],
|
||||
hardware: [[<>{t('hardware')}<div className='sub'>{t('accounts managed via hardware devices')}</div></>]],
|
||||
injected: [[<>{t('extension')}<div className='sub'>{t('accounts available via browser extensions')}</div></>]],
|
||||
multisig: [[<>{t('multisig')}<div className='sub'>{t('on-chain multisig accounts')}</div></>]],
|
||||
@ -287,6 +293,12 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
{isLedgerOpen && (
|
||||
<Ledger onClose={toggleLedger} />
|
||||
)}
|
||||
{isLocalOpen && (
|
||||
<Local
|
||||
onClose={toggleLocal}
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
)}
|
||||
{isMultisigOpen && (
|
||||
<Multisig
|
||||
onClose={toggleMultisig}
|
||||
@ -377,6 +389,13 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{fork && (
|
||||
<Button
|
||||
icon='plus'
|
||||
label={t('Local')}
|
||||
onClick={toggleLocal}
|
||||
/>
|
||||
)}
|
||||
</Button.Group>
|
||||
</SummaryBox>
|
||||
{!isNextTick || !sortedAccounts.length
|
||||
|
||||
132
packages/page-accounts/src/modals/LocalAdd.tsx
Normal file
132
packages/page-accounts/src/modals/LocalAdd.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2017-2024 @polkadot/app-accounts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Blockchain } from '@acala-network/chopsticks-core';
|
||||
import type { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import type { ModalProps } from '../types.js';
|
||||
|
||||
import { setStorage } from '@acala-network/chopsticks-core';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, Input, InputAddressSimple, Modal } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props extends ModalProps {
|
||||
className?: string;
|
||||
onClose: () => void;
|
||||
onStatusChange: (status: ActionStatus) => void;
|
||||
}
|
||||
|
||||
interface CreateOptions {
|
||||
balance: number;
|
||||
name: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export async function createLocalAccount (chain: Blockchain, address: string, { balance, name }: CreateOptions): Promise<ActionStatus> {
|
||||
const status = { action: 'create' } as ActionStatus;
|
||||
|
||||
try {
|
||||
keyring.addExternal(address, { isLocal: true, name, tags: ['local'] });
|
||||
|
||||
await setStorage(chain, {
|
||||
System: {
|
||||
Account: [
|
||||
[[address], { data: { free: balance * 1e12 }, providers: 1 }]
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
status.account = address;
|
||||
status.status = 'success';
|
||||
status.message = 'Local account created.';
|
||||
} catch (error) {
|
||||
status.status = 'error';
|
||||
status.message = (error as Error).message;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
function LocalAdd ({ className = '', onClose, onStatusChange }: Props): React.ReactElement<Props> {
|
||||
const { fork: chain } = useApi();
|
||||
const { t } = useTranslation();
|
||||
const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
|
||||
const [address, setAdress] = useState<string | null>(null);
|
||||
const [balance, setBalance] = useState<string>('0');
|
||||
|
||||
const _createLocalAccount = useCallback(
|
||||
async () => {
|
||||
if (!address || !chain) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = { balance: balance ? Number(balance) : 0, name: name.trim() };
|
||||
const status = await createLocalAccount(chain, address, options);
|
||||
|
||||
onStatusChange(status);
|
||||
onClose();
|
||||
},
|
||||
[address, name, balance, chain, onClose, onStatusChange]
|
||||
);
|
||||
|
||||
const _onChangeName = useCallback(
|
||||
(name: string) => setName({ isNameValid: (name.trim().length >= 3), name }),
|
||||
[]
|
||||
);
|
||||
|
||||
const isValid = isNameValid && !!address;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={className}
|
||||
header={t('Add a mock account to chopsticks')}
|
||||
onClose={onClose}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns hint={t('Any address, this can override other accounts')}>
|
||||
<InputAddressSimple
|
||||
autoFocus
|
||||
label={t('address')}
|
||||
onChange={setAdress}
|
||||
placeholder={t('e.g. 5Gg3...')}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t('The name for this account')}>
|
||||
<Input
|
||||
className='full'
|
||||
isError={!isNameValid}
|
||||
label={t('name')}
|
||||
onChange={_onChangeName}
|
||||
placeholder={t('local account name')}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t('Optional. The balance for this account Default to 0.')}>
|
||||
<Input
|
||||
className='full'
|
||||
label={t('balance')}
|
||||
min={0}
|
||||
onChange={setBalance}
|
||||
placeholder={t('1000')}
|
||||
type='number'
|
||||
value={balance}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button
|
||||
icon='plus'
|
||||
isDisabled={!isValid}
|
||||
label={t('Add')}
|
||||
onClick={_createLocalAccount}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(LocalAdd);
|
||||
@ -1,11 +1,12 @@
|
||||
// Copyright 2017-2024 @polkadot/react-api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Blockchain } from '@acala-network/chopsticks-core';
|
||||
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
|
||||
import type { InjectedExtension } from '@polkadot/extension-inject/types';
|
||||
import type { ChainProperties, ChainType } from '@polkadot/types/interfaces';
|
||||
import type { KeyringStore } from '@polkadot/ui-keyring/types';
|
||||
import type { ApiProps, ApiState } from './types.js';
|
||||
import type { ApiProps, ApiState, InjectedAccountExt } from './types.js';
|
||||
|
||||
import { ChopsticksProvider, setStorage } from '@acala-network/chopsticks-core';
|
||||
import * as Sc from '@substrate/connect';
|
||||
@ -36,15 +37,6 @@ interface Props {
|
||||
store?: KeyringStore;
|
||||
}
|
||||
|
||||
interface InjectedAccountExt {
|
||||
address: string;
|
||||
meta: {
|
||||
name: string;
|
||||
source: string;
|
||||
whenCreated: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface ChainData {
|
||||
injectedAccounts: InjectedAccountExt[];
|
||||
properties: ChainProperties;
|
||||
@ -54,6 +46,11 @@ interface ChainData {
|
||||
systemVersion: string;
|
||||
}
|
||||
|
||||
interface CreateApiReturn {
|
||||
types: Record<string, Record<string, string>>;
|
||||
fork: Blockchain | null;
|
||||
}
|
||||
|
||||
export const DEFAULT_DECIMALS = statics.registry.createType('u32', 12);
|
||||
export const DEFAULT_SS58 = statics.registry.createType('u32', addressDefaults.prefix);
|
||||
export const DEFAULT_AUX = ['Aux1', 'Aux2', 'Aux3', 'Aux4', 'Aux5', 'Aux6', 'Aux7', 'Aux8', 'Aux9'];
|
||||
@ -134,7 +131,7 @@ async function retrieve (api: ApiPromise, injectedPromise: Promise<InjectedExten
|
||||
};
|
||||
}
|
||||
|
||||
async function loadOnReady (api: ApiPromise, endpoint: LinkOption | null, injectedPromise: Promise<InjectedExtension[]>, store: KeyringStore | undefined, types: Record<string, Record<string, string>>, urlIsEthereum = false): Promise<ApiState> {
|
||||
async function loadOnReady (api: ApiPromise, endpoint: LinkOption | null, fork: Blockchain | null, injectedPromise: Promise<InjectedExtension[]>, store: KeyringStore | undefined, types: Record<string, Record<string, string>>, urlIsEthereum = false): Promise<ApiState> {
|
||||
statics.registry.register(types);
|
||||
|
||||
const { injectedAccounts, properties, systemChain, systemChainType, systemName, systemVersion } = await retrieve(api, injectedPromise);
|
||||
@ -189,6 +186,7 @@ async function loadOnReady (api: ApiPromise, endpoint: LinkOption | null, inject
|
||||
apiDefaultTx,
|
||||
apiDefaultTxSudo,
|
||||
chainSS58,
|
||||
fork,
|
||||
hasInjectedAccounts: injectedAccounts.length !== 0,
|
||||
isApiReady: true,
|
||||
isDevelopment,
|
||||
@ -230,17 +228,19 @@ async function getLightProvider (chain: string): Promise<ScProvider> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
async function createApi (apiUrl: string, signer: ApiSigner, isLocalFork: boolean, onError: (error: unknown) => void): Promise<Record<string, Record<string, string>>> {
|
||||
async function createApi (apiUrl: string, signer: ApiSigner, isLocalFork: boolean, onError: (error: unknown) => void): Promise<CreateApiReturn> {
|
||||
const types = getDevTypes();
|
||||
const isLight = apiUrl.startsWith('light://');
|
||||
let provider;
|
||||
let chopsticksFork: Blockchain | null = null;
|
||||
|
||||
try {
|
||||
if (isLight) {
|
||||
provider = await getLightProvider(apiUrl.replace('light://', ''));
|
||||
} else if (isLocalFork) {
|
||||
provider = await ChopsticksProvider.fromEndpoint(apiUrl);
|
||||
await setStorage(provider.chain, {
|
||||
chopsticksFork = provider.chain;
|
||||
await setStorage(chopsticksFork, {
|
||||
System: {
|
||||
Account: [
|
||||
[['5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'], { data: { free: 1000 * 1e12 }, providers: 1 }]
|
||||
@ -267,7 +267,7 @@ async function createApi (apiUrl: string, signer: ApiSigner, isLocalFork: boolea
|
||||
onError(error);
|
||||
}
|
||||
|
||||
return types;
|
||||
return { fork: chopsticksFork, types };
|
||||
}
|
||||
|
||||
export function ApiCtxRoot ({ apiUrl, children, isElectron, store: keyringStore }: Props): React.ReactElement<Props> | null {
|
||||
@ -304,7 +304,7 @@ export function ApiCtxRoot ({ apiUrl, children, isElectron, store: keyringStore
|
||||
};
|
||||
|
||||
createApi(apiUrl, new ApiSigner(statics.registry, queuePayload, queueSetTxStatus), isLocalFork, onError)
|
||||
.then((types): void => {
|
||||
.then(({ fork, types }): void => {
|
||||
statics.api.on('connected', () => setIsApiConnected(true));
|
||||
statics.api.on('disconnected', () => setIsApiConnected(false));
|
||||
statics.api.on('error', onError);
|
||||
@ -317,7 +317,7 @@ export function ApiCtxRoot ({ apiUrl, children, isElectron, store: keyringStore
|
||||
|
||||
const urlIsEthereum = !!location.href.includes('keyring-type=ethereum');
|
||||
|
||||
loadOnReady(statics.api, apiEndpoint, injectedPromise, keyringStore, types, urlIsEthereum)
|
||||
loadOnReady(statics.api, apiEndpoint, fork, injectedPromise, keyringStore, types, urlIsEthereum)
|
||||
.then(setState)
|
||||
.catch(onError);
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2024 @polkadot/react-api authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Blockchain } from '@acala-network/chopsticks-core';
|
||||
import type React from 'react';
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { SubmittableExtrinsicFunction } from '@polkadot/api/promise/types';
|
||||
@ -15,10 +16,20 @@ export interface BareProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface InjectedAccountExt {
|
||||
address: string;
|
||||
meta: {
|
||||
name: string;
|
||||
source: string;
|
||||
whenCreated: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApiState {
|
||||
apiDefaultTx: SubmittableExtrinsicFunction;
|
||||
apiDefaultTxSudo: SubmittableExtrinsicFunction;
|
||||
chainSS58: number;
|
||||
fork: Blockchain | null;
|
||||
hasInjectedAccounts: boolean;
|
||||
isApiReady: boolean;
|
||||
isDevelopment: boolean;
|
||||
|
||||
@ -21,7 +21,9 @@ export function getAccountCryptoType (accountId: AccountIdIsh): string {
|
||||
? 'multisig'
|
||||
: current.meta.isProxied
|
||||
? 'proxied'
|
||||
: 'qr'
|
||||
: current.meta.isLocal
|
||||
? 'chopsticks'
|
||||
: 'qr'
|
||||
: current.type;
|
||||
}
|
||||
} catch {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Blockchain } from '@acala-network/chopsticks-core';
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { SubmittableExtrinsicFunction } from '@polkadot/api/promise/types';
|
||||
import type { HeaderExtended } from '@polkadot/api-derive/types';
|
||||
@ -13,6 +14,7 @@ export interface ApiState {
|
||||
apiDefaultTx: SubmittableExtrinsicFunction;
|
||||
apiDefaultTxSudo: SubmittableExtrinsicFunction;
|
||||
chainSS58: number;
|
||||
fork: Blockchain | null;
|
||||
hasInjectedAccounts: boolean;
|
||||
isApiReady: boolean;
|
||||
isDevelopment: boolean;
|
||||
|
||||
@ -13,7 +13,7 @@ import type { BN } from '@polkadot/util';
|
||||
import type { HexString } from '@polkadot/util/types';
|
||||
import type { AddressFlags, AddressProxy, QrState } from './types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { web3FromSource } from '@polkadot/extension-dapp';
|
||||
import { Button, ErrorBoundary, Modal, Output, styled, Toggle } from '@polkadot/react-components';
|
||||
@ -80,11 +80,31 @@ function unlockAccount ({ isUnlockCached, signAddress, signPassword }: AddressPr
|
||||
return null;
|
||||
}
|
||||
|
||||
async function signAndSend (queueSetTxStatus: QueueTxMessageSetStatus, currentItem: QueueTx, tx: SubmittableExtrinsic<'promise'>, pairOrAddress: KeyringPair | string, options: Partial<SignerOptions>): Promise<void> {
|
||||
async function fakeSignForChopsticks (api: ApiPromise, tx: SubmittableExtrinsic<'promise'>, sender: string): Promise<void> {
|
||||
const account = await api.query.system.account(sender);
|
||||
const options = {
|
||||
blockHash: api.genesisHash,
|
||||
genesisHash: api.genesisHash,
|
||||
nonce: account.nonce,
|
||||
runtimeVersion: api.runtimeVersion
|
||||
};
|
||||
const mockSignature = new Uint8Array(64);
|
||||
|
||||
mockSignature.fill(0xcd);
|
||||
mockSignature.set([0xde, 0xad, 0xbe, 0xef]);
|
||||
tx.signFake(sender, options);
|
||||
tx.signature.set(mockSignature);
|
||||
}
|
||||
|
||||
async function signAndSend (queueSetTxStatus: QueueTxMessageSetStatus, currentItem: QueueTx, tx: SubmittableExtrinsic<'promise'>, pairOrAddress: KeyringPair | string, options: Partial<SignerOptions>, api: ApiPromise, isMockSign: boolean): Promise<void> {
|
||||
currentItem.txStartCb && currentItem.txStartCb();
|
||||
|
||||
try {
|
||||
await tx.signAsync(pairOrAddress, options);
|
||||
if (!isMockSign) {
|
||||
await tx.signAsync(pairOrAddress, options);
|
||||
} else {
|
||||
await fakeSignForChopsticks(api, tx, pairOrAddress as string);
|
||||
}
|
||||
|
||||
console.info('sending', tx.toHex());
|
||||
|
||||
@ -101,11 +121,15 @@ async function signAndSend (queueSetTxStatus: QueueTxMessageSetStatus, currentIt
|
||||
}
|
||||
}
|
||||
|
||||
async function signAsync (queueSetTxStatus: QueueTxMessageSetStatus, { id, txFailedCb = NOOP, txStartCb = NOOP }: QueueTx, tx: SubmittableExtrinsic<'promise'>, pairOrAddress: KeyringPair | string, options: Partial<SignerOptions>): Promise<string | null> {
|
||||
async function signAsync (queueSetTxStatus: QueueTxMessageSetStatus, { id, txFailedCb = NOOP, txStartCb = NOOP }: QueueTx, tx: SubmittableExtrinsic<'promise'>, pairOrAddress: KeyringPair | string, options: Partial<SignerOptions>, api: ApiPromise, isMockSign: boolean): Promise<string | null> {
|
||||
txStartCb();
|
||||
|
||||
try {
|
||||
await tx.signAsync(pairOrAddress, options);
|
||||
if (!isMockSign) {
|
||||
await tx.signAsync(pairOrAddress, options);
|
||||
} else {
|
||||
await fakeSignForChopsticks(api, tx, pairOrAddress as string);
|
||||
}
|
||||
|
||||
return tx.toJSON();
|
||||
} catch (error) {
|
||||
@ -166,14 +190,16 @@ async function wrapTx (api: ApiPromise, currentItem: QueueTx, { isMultiCall, mul
|
||||
return tx;
|
||||
}
|
||||
|
||||
async function extractParams (api: ApiPromise, address: string, options: Partial<SignerOptions>, getLedger: () => Ledger, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial<SignerOptions>]> {
|
||||
async function extractParams (api: ApiPromise, address: string, options: Partial<SignerOptions>, getLedger: () => Ledger, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial<SignerOptions>, boolean]> {
|
||||
const pair = keyring.getPair(address);
|
||||
const { meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isProxied, source } } = pair;
|
||||
const { meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isLocal, isProxied, source } } = pair;
|
||||
|
||||
if (isHardware) {
|
||||
return ['signing', address, { ...options, signer: new LedgerSigner(api.registry, getLedger, accountOffset || 0, addressOffset || 0) }];
|
||||
return ['signing', address, { ...options, signer: new LedgerSigner(api.registry, getLedger, accountOffset || 0, addressOffset || 0) }, false];
|
||||
} else if (isLocal) {
|
||||
return ['signing', address, { ...options, signer: new AccountSigner(api.registry, pair) }, true];
|
||||
} else if (isExternal && !isProxied) {
|
||||
return ['qr', address, { ...options, signer: new QrSigner(api.registry, setQrState) }];
|
||||
return ['qr', address, { ...options, signer: new QrSigner(api.registry, setQrState) }, false];
|
||||
} else if (isInjected) {
|
||||
if (!source) {
|
||||
throw new Error(`Unable to find injected source for ${address}`);
|
||||
@ -183,12 +209,12 @@ async function extractParams (api: ApiPromise, address: string, options: Partial
|
||||
|
||||
assert(injected, `Unable to find a signer for ${address}`);
|
||||
|
||||
return ['signing', address, { ...options, signer: injected.signer }];
|
||||
return ['signing', address, { ...options, signer: injected.signer }, false];
|
||||
}
|
||||
|
||||
assert(addressEq(address, pair.address), `Unable to retrieve keypair for ${address}`);
|
||||
|
||||
return ['signing', address, { ...options, signer: new AccountSigner(api.registry, pair) }];
|
||||
return ['signing', address, { ...options, signer: new AccountSigner(api.registry, pair) }, false];
|
||||
}
|
||||
|
||||
function tryExtract (address: string | null): AddressFlags {
|
||||
@ -298,14 +324,14 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd
|
||||
const _onSend = useCallback(
|
||||
async (queueSetTxStatus: QueueTxMessageSetStatus, currentItem: QueueTx, senderInfo: AddressProxy): Promise<void> => {
|
||||
if (senderInfo.signAddress) {
|
||||
const [tx, [status, pairOrAddress, options]] = await Promise.all([
|
||||
const [tx, [status, pairOrAddress, options, isMockSign]] = await Promise.all([
|
||||
wrapTx(api, currentItem, senderInfo),
|
||||
extractParams(api, senderInfo.signAddress, { nonce: -1, tip }, getLedger, setQrState)
|
||||
]);
|
||||
|
||||
queueSetTxStatus(currentItem.id, status);
|
||||
|
||||
await signAndSend(queueSetTxStatus, currentItem, tx, pairOrAddress, options);
|
||||
await signAndSend(queueSetTxStatus, currentItem, tx, pairOrAddress, options, api, isMockSign);
|
||||
}
|
||||
},
|
||||
[api, getLedger, tip]
|
||||
@ -314,12 +340,12 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd
|
||||
const _onSign = useCallback(
|
||||
async (queueSetTxStatus: QueueTxMessageSetStatus, currentItem: QueueTx, senderInfo: AddressProxy): Promise<void> => {
|
||||
if (senderInfo.signAddress) {
|
||||
const [tx, [, pairOrAddress, options]] = await Promise.all([
|
||||
const [tx, [, pairOrAddress, options, isMockSign]] = await Promise.all([
|
||||
wrapTx(api, currentItem, senderInfo),
|
||||
extractParams(api, senderInfo.signAddress, { ...signedOptions, tip }, getLedger, setQrState)
|
||||
]);
|
||||
|
||||
setSignedTx(await signAsync(queueSetTxStatus, currentItem, tx, pairOrAddress, options));
|
||||
setSignedTx(await signAsync(queueSetTxStatus, currentItem, tx, pairOrAddress, options, api, isMockSign));
|
||||
}
|
||||
},
|
||||
[api, getLedger, signedOptions, tip]
|
||||
@ -357,6 +383,24 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd
|
||||
[_onSend, _onSendPayload, _onSign, _unlock, currentItem, isSubmit, queueSetTxStatus, senderInfo]
|
||||
);
|
||||
|
||||
const signLabel = useMemo(() => {
|
||||
if (flags.isQr) {
|
||||
return t('Sign via Qr');
|
||||
} else if (isSubmit) {
|
||||
if (flags.isLocal) {
|
||||
return t('Mock Sign and Submit');
|
||||
} else {
|
||||
return t('Sign and Submit');
|
||||
}
|
||||
} else {
|
||||
if (flags.isLocal) {
|
||||
return t('Mock Sign (no submission)');
|
||||
} else {
|
||||
return t('Sign (no submission)');
|
||||
}
|
||||
}
|
||||
}, [flags.isQr, flags.isLocal, isSubmit, t]);
|
||||
|
||||
const isAutoCapable = senderInfo.signAddress && (queueSize > 1) && isSubmit && !(flags.isHardware || flags.isMultisig || flags.isProxied || flags.isQr || flags.isUnlockable) && !isRenderError;
|
||||
|
||||
if (!isBusy && isAutoCapable && initialIsQueueSubmit) {
|
||||
@ -443,13 +487,7 @@ function TxSigned ({ className, currentItem, isQueueSubmit, queueSize, requestAd
|
||||
}
|
||||
isBusy={isBusy}
|
||||
isDisabled={!senderInfo.signAddress || isRenderError}
|
||||
label={
|
||||
flags.isQr
|
||||
? t('Sign via Qr')
|
||||
: isSubmit
|
||||
? t('Sign and Submit')
|
||||
: t('Sign (no submission)')
|
||||
}
|
||||
label={signLabel}
|
||||
onClick={_doStart}
|
||||
tabIndex={2}
|
||||
/>
|
||||
|
||||
@ -8,6 +8,7 @@ export interface AddressFlags {
|
||||
addressOffset: number;
|
||||
hardwareType?: string;
|
||||
isHardware: boolean;
|
||||
isLocal: boolean;
|
||||
isMultisig: boolean;
|
||||
isProxied: boolean;
|
||||
isQr: boolean;
|
||||
|
||||
@ -9,7 +9,7 @@ import type { AddressFlags } from './types.js';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
|
||||
const NOOP = () => undefined;
|
||||
const NO_FLAGS = { accountOffset: 0, addressOffset: 0, isHardware: false, isMultisig: false, isProxied: false, isQr: false, isUnlockable: false, threshold: 0, who: [] };
|
||||
const NO_FLAGS = { accountOffset: 0, addressOffset: 0, isHardware: false, isLocal: false, isMultisig: false, isProxied: false, isQr: false, isUnlockable: false, threshold: 0, who: [] };
|
||||
|
||||
export const UNLOCK_MINS = 15;
|
||||
|
||||
@ -43,7 +43,7 @@ export function extractExternal (accountId: string | null): AddressFlags {
|
||||
}
|
||||
|
||||
const pair = keyring.getPair(publicKey);
|
||||
const { isExternal, isHardware, isInjected, isMultisig, isProxied } = pair.meta;
|
||||
const { isExternal, isHardware, isInjected, isLocal, isMultisig, isProxied } = pair.meta;
|
||||
const isUnlockable = !isExternal && !isHardware && !isInjected;
|
||||
|
||||
if (isUnlockable) {
|
||||
@ -60,9 +60,10 @@ export function extractExternal (accountId: string | null): AddressFlags {
|
||||
addressOffset: pair.meta.addressOffset || 0,
|
||||
hardwareType: pair.meta.hardwareType as string,
|
||||
isHardware: !!isHardware,
|
||||
isLocal: !!isLocal,
|
||||
isMultisig: !!isMultisig,
|
||||
isProxied: !!isProxied,
|
||||
isQr: !!isExternal && !isMultisig && !isProxied && !isHardware && !isInjected,
|
||||
isQr: !!isExternal && !isMultisig && !isProxied && !isHardware && !isInjected && !isLocal,
|
||||
isUnlockable: isUnlockable && pair.isLocked,
|
||||
threshold: pair.meta.threshold || 0,
|
||||
who: (pair.meta.who || []).map(recodeAddress)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user