Expose signed transactions (#2203)
* sign offline WIP * signing transactions from extrinsics screen * signing tx from accounts screen * added translations * removed unnecessary value * removed console logs * removed unused import * used new signAsync method * fixed types * removed unused imports * added missing translations * code review changes * fixed signing with local account * added sign toggle * improvements for QR
This commit is contained in:
parent
2ecb735ca4
commit
be7e293d28
@ -10,11 +10,12 @@ import { useTranslation } from './translate';
|
||||
interface Props {
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
label?: string;
|
||||
onClick: () => void;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
export default function ButtonCancel ({ className, isDisabled, onClick, tabIndex }: Props): React.ReactElement<Props> {
|
||||
export default function ButtonCancel ({ className, isDisabled, label, onClick, tabIndex }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@ -23,7 +24,7 @@ export default function ButtonCancel ({ className, isDisabled, onClick, tabIndex
|
||||
icon='cancel'
|
||||
isDisabled={isDisabled}
|
||||
isNegative
|
||||
label={t('Cancel')}
|
||||
label={label || t('Cancel')}
|
||||
onClick={onClick}
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
|
||||
@ -19,7 +19,9 @@ interface ModalProps extends BareProps {
|
||||
}
|
||||
|
||||
interface ActionsProps extends BareProps {
|
||||
cancelLabel?: string;
|
||||
children: React.ReactNode;
|
||||
withOr?: boolean;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@ -42,12 +44,12 @@ function Modal (props: ModalProps): React.ReactElement<ModalProps> {
|
||||
);
|
||||
}
|
||||
|
||||
function Actions ({ className, children, onCancel }: ActionsProps): React.ReactElement<ActionsProps> {
|
||||
function Actions ({ cancelLabel, className, children, withOr = true, onCancel }: ActionsProps): React.ReactElement<ActionsProps> {
|
||||
return (
|
||||
<SUIModal.Actions>
|
||||
<Button.Group className={className}>
|
||||
<ButtonCancel onClick={onCancel} />
|
||||
<Button.Or />
|
||||
<ButtonCancel label={cancelLabel} onClick={onCancel} />
|
||||
{withOr && <Button.Or />}
|
||||
{children}
|
||||
</Button.Group>
|
||||
</SUIModal.Actions>
|
||||
|
||||
@ -62,7 +62,7 @@ class TxButtonInner extends React.PureComponent<InnerProps> {
|
||||
}
|
||||
|
||||
protected send = (): void => {
|
||||
const { accountId, api, extrinsic: propsExtrinsic, isUnsigned, onClick, onFailed, onStart, onSuccess, onUpdate, params = [], queueExtrinsic, tx = '', withSpinner = false } = this.props;
|
||||
const { accountId, api, extrinsic: propsExtrinsic, isUnsigned, onClick, onFailed, onStart, onSuccess, onUpdate, params = [], queueExtrinsic, tx = '', withSpinner = true } = this.props;
|
||||
let extrinsic: any;
|
||||
|
||||
if (propsExtrinsic) {
|
||||
|
||||
@ -2,14 +2,19 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { SignerOptions, SignerResult } from '@polkadot/api/types';
|
||||
import { SignerOptions, SignerResult, Signer as ApiSigner } from '@polkadot/api/types';
|
||||
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { I18nProps, BareProps } from '@polkadot/react-components/types';
|
||||
import { RpcMethod } from '@polkadot/jsonrpc/types';
|
||||
import { KeyringPair } from '@polkadot/keyring/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { QueueTx, QueueTxMessageSetStatus, QueueTxResult, QueueTxStatus } from '@polkadot/react-components/Status/types';
|
||||
import {
|
||||
QueueTx,
|
||||
QueueTxMessageSetStatus,
|
||||
QueueTxResult,
|
||||
QueueTxStatus
|
||||
} from '@polkadot/react-components/Status/types';
|
||||
import { SignerPayloadJSON } from '@polkadot/types/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
@ -17,7 +22,7 @@ import React from 'react';
|
||||
import { SubmittableResult } from '@polkadot/api';
|
||||
import { web3FromSource } from '@polkadot/extension-dapp';
|
||||
import { createType } from '@polkadot/types';
|
||||
import { Button, InputBalance, Modal, Toggle, ErrorBoundary } from '@polkadot/react-components';
|
||||
import { Button, InputBalance, Modal, Toggle, Output, ErrorBoundary, InputNumber } from '@polkadot/react-components';
|
||||
import { registry } from '@polkadot/react-api';
|
||||
import { withApi, withMulti, withObservable } from '@polkadot/react-api/hoc';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
@ -40,25 +45,32 @@ interface Props extends I18nProps, ApiProps, BaseProps {
|
||||
}
|
||||
|
||||
interface State {
|
||||
accountNonce?: string;
|
||||
blocks: string;
|
||||
currentItem?: QueueTx;
|
||||
isQrScanning: boolean;
|
||||
isQrVisible: boolean;
|
||||
isRenderError: boolean;
|
||||
isSendable: boolean;
|
||||
isSubmit: boolean;
|
||||
isV2?: boolean;
|
||||
nonce?: string;
|
||||
password: string;
|
||||
qrAddress: string;
|
||||
qrPayload: Uint8Array;
|
||||
qrResolve?: (result: SignerResult) => void;
|
||||
qrReject?: (error: Error) => void;
|
||||
showTip: boolean;
|
||||
signedTx?: string;
|
||||
tip?: BN;
|
||||
unlockError?: string | null;
|
||||
}
|
||||
|
||||
let qrId = 0;
|
||||
|
||||
function extractExternal (accountId?: string | null): { isExternal: boolean; isHardware: boolean; hardwareType?: string } {
|
||||
function extractExternal (
|
||||
accountId?: string | null
|
||||
): { isExternal: boolean; isHardware: boolean; hardwareType?: string } {
|
||||
if (!accountId) {
|
||||
return { isExternal: false, isHardware: false };
|
||||
}
|
||||
@ -83,7 +95,11 @@ function extractExternal (accountId?: string | null): { isExternal: boolean; isH
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async function makeExtrinsicSignature (payload: SignerPayloadJSON, { id, signerCb }: QueueTx, pair: KeyringPair): Promise<void> {
|
||||
async function makeExtrinsicSignature (
|
||||
payload: SignerPayloadJSON,
|
||||
{ id, signerCb }: QueueTx,
|
||||
pair: KeyringPair
|
||||
): Promise<void> {
|
||||
console.log('makeExtrinsicSignature: payload ::', JSON.stringify(payload));
|
||||
|
||||
const result = createType(registry, 'ExtrinsicPayload', payload, { version: payload.version }).sign(pair);
|
||||
@ -93,20 +109,30 @@ async function makeExtrinsicSignature (payload: SignerPayloadJSON, { id, signerC
|
||||
}
|
||||
}
|
||||
|
||||
class Signer extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
isQrScanning: false,
|
||||
isQrVisible: false,
|
||||
isRenderError: false,
|
||||
isSendable: false,
|
||||
password: '',
|
||||
qrAddress: '',
|
||||
qrPayload: new Uint8Array(),
|
||||
showTip: false,
|
||||
unlockError: null
|
||||
};
|
||||
const initialState: State = {
|
||||
accountNonce: undefined,
|
||||
blocks: '50',
|
||||
isQrScanning: false,
|
||||
isQrVisible: false,
|
||||
isRenderError: false,
|
||||
isSendable: false,
|
||||
isSubmit: true,
|
||||
nonce: undefined,
|
||||
password: '',
|
||||
qrAddress: '',
|
||||
qrPayload: new Uint8Array(),
|
||||
showTip: false,
|
||||
signedTx: '',
|
||||
unlockError: null
|
||||
};
|
||||
|
||||
public static getDerivedStateFromProps ({ allAccounts, api, queue }: Props, { currentItem, password, unlockError }: State): Partial<State> {
|
||||
class Signer extends React.PureComponent<Props, State> {
|
||||
public state: State = initialState;
|
||||
|
||||
public static getDerivedStateFromProps (
|
||||
{ allAccounts, api, queue }: Props,
|
||||
{ currentItem, password, unlockError }: State
|
||||
): Partial<State> {
|
||||
let isV2: boolean;
|
||||
try {
|
||||
isV2 = !!api.tx.session.setKeys;
|
||||
@ -118,12 +144,9 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
const isSame =
|
||||
!!nextItem &&
|
||||
!!currentItem &&
|
||||
(
|
||||
(!nextItem.accountId && !currentItem.accountId) ||
|
||||
(
|
||||
(nextItem.accountId && nextItem.accountId.toString()) === (currentItem.accountId && currentItem.accountId.toString())
|
||||
)
|
||||
);
|
||||
((!nextItem.accountId && !currentItem.accountId) ||
|
||||
(nextItem.accountId && nextItem.accountId.toString()) ===
|
||||
(currentItem.accountId && currentItem.accountId.toString()));
|
||||
|
||||
let isSendable = !!nextItem && !!nextItem.isUnsigned;
|
||||
|
||||
@ -147,11 +170,15 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
public async componentDidUpdate (): Promise<void> {
|
||||
const { currentItem } = this.state;
|
||||
const { accountNonce, currentItem, isSubmit } = this.state;
|
||||
|
||||
if (currentItem && currentItem.status === 'queued' && !(currentItem.extrinsic || currentItem.payload)) {
|
||||
return this.sendRpc(currentItem);
|
||||
}
|
||||
|
||||
if (!isSubmit && currentItem?.accountId && accountNonce == null) {
|
||||
this.updateNonce();
|
||||
}
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
@ -164,9 +191,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<Modal className={`ui--signer-Signer ${className}`}>
|
||||
<ErrorBoundary onError={this.onRenderError}>
|
||||
{this.renderContent()}
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary onError={this.onRenderError}>{this.renderContent()}</ErrorBoundary>
|
||||
{this.renderButtons()}
|
||||
</Modal>
|
||||
);
|
||||
@ -174,7 +199,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
|
||||
private renderButtons (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { currentItem, isQrScanning, isQrVisible, isRenderError, isSendable } = this.state;
|
||||
const { currentItem, isQrScanning, isQrVisible, isRenderError, isSendable, isSubmit, signedTx } = this.state;
|
||||
|
||||
if (!currentItem) {
|
||||
return null;
|
||||
@ -183,25 +208,16 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
const { isExternal, isHardware, hardwareType } = extractExternal(currentItem.accountId);
|
||||
|
||||
return (
|
||||
<Modal.Actions
|
||||
onCancel={
|
||||
isQrVisible
|
||||
? this.onCancelQr
|
||||
: this.onCancel
|
||||
}
|
||||
>
|
||||
{!isRenderError && (!isQrVisible || !isQrScanning) && (
|
||||
<Modal.Actions cancelLabel={signedTx ? t('Close') : undefined} withOr={!signedTx} onCancel={isQrVisible ? this.onCancelQr : signedTx ? this.onCancelSign : this.onCancel}>
|
||||
{!isRenderError && (!isQrVisible || !isQrScanning) && !signedTx && (
|
||||
<>
|
||||
{!currentItem.isUnsigned && this.renderSignToggle()}
|
||||
<Button.Or />
|
||||
<Button
|
||||
className='ui--signer-Signer-Submit'
|
||||
isDisabled={!isSendable}
|
||||
isPrimary
|
||||
onClick={
|
||||
isQrVisible
|
||||
? this.activateQrScanning
|
||||
: this.onSend
|
||||
}
|
||||
onClick={isQrVisible ? this.activateQrScanning : this.onSend}
|
||||
tabIndex={2}
|
||||
label={
|
||||
isQrVisible
|
||||
@ -212,17 +228,11 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
? t('Sign via {{hardwareType}}', { replace: { hardwareType: hardwareType || 'hardware' } })
|
||||
: isExternal
|
||||
? t('Sign via Qr')
|
||||
: t('Sign and Submit')
|
||||
}
|
||||
icon={
|
||||
isQrVisible
|
||||
? 'qrcode'
|
||||
: currentItem.isUnsigned
|
||||
? 'sign-in'
|
||||
: isExternal
|
||||
? 'qrcode'
|
||||
: 'sign-in'
|
||||
: isSubmit
|
||||
? t('Sign and Submit')
|
||||
: t('Sign (no submission)')
|
||||
}
|
||||
icon={isQrVisible ? 'qrcode' : currentItem.isUnsigned ? 'sign-in' : isExternal ? 'qrcode' : 'sign-in'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@ -231,7 +241,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderContent (): React.ReactNode {
|
||||
const { currentItem, isQrScanning, isQrVisible, isSendable, qrAddress, qrPayload, tip } = this.state;
|
||||
const { currentItem, isQrScanning, isQrVisible, isSendable, isSubmit, qrAddress, qrPayload, tip } = this.state;
|
||||
|
||||
if (!currentItem) {
|
||||
return null;
|
||||
@ -245,30 +255,22 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
tip={tip}
|
||||
value={currentItem}
|
||||
>
|
||||
{
|
||||
isQrVisible
|
||||
? (
|
||||
<Qr
|
||||
address={qrAddress}
|
||||
isScanning={isQrScanning}
|
||||
onSignature={this.addQrSignature}
|
||||
payload={qrPayload}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{this.renderTip()}
|
||||
{this.renderUnlock()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{isQrVisible ? (
|
||||
<Qr address={qrAddress} isScanning={isQrScanning} onSignature={this.addQrSignature} payload={qrPayload} />
|
||||
) : (
|
||||
<>
|
||||
{this.renderTip()}
|
||||
{this.renderUnlock()}
|
||||
{!isSubmit && this.renderSignFields()}
|
||||
</>
|
||||
)}
|
||||
</Transaction>
|
||||
);
|
||||
}
|
||||
|
||||
private renderTip (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { currentItem, isSendable, isV2, showTip } = this.state;
|
||||
const { currentItem, isSendable, isV2, showTip, signedTx } = this.state;
|
||||
|
||||
if (!isV2 || !isSendable || !currentItem || currentItem.isUnsigned) {
|
||||
return null;
|
||||
@ -283,6 +285,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
? t('Include an optional tip for faster processing')
|
||||
: t('Do not include a tip for the block author')
|
||||
}
|
||||
isDisabled={!!signedTx}
|
||||
onChange={this.onShowTip}
|
||||
value={showTip}
|
||||
/>
|
||||
@ -291,6 +294,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
defaultValue={new BN(0)}
|
||||
help={t('Add a tip to this extrinsic, paying the block author for greater priority')}
|
||||
isZeroable
|
||||
isDisabled={!!signedTx}
|
||||
onChange={this.onChangeTip}
|
||||
label={t('Tip (optional)')}
|
||||
/>
|
||||
@ -299,20 +303,69 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderSignToggle (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { isSubmit, isQrVisible, isQrScanning } = this.state;
|
||||
|
||||
return <Toggle
|
||||
className='signToggle'
|
||||
label={
|
||||
isSubmit
|
||||
? t('Sign and Submit')
|
||||
: t('Sign (no submission)')
|
||||
}
|
||||
isDisabled={isQrVisible || isQrScanning}
|
||||
onChange={this.onToggleSign}
|
||||
value={isSubmit}
|
||||
/>;
|
||||
}
|
||||
|
||||
private renderSignFields (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { accountNonce, blocks, isSubmit, signedTx } = this.state;
|
||||
|
||||
if (isSubmit || accountNonce == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<br />
|
||||
<InputNumber isZeroable={true} label={t('Nonce')} labelExtra={t('Current account nonce: {{accountNonce}}', { replace: { accountNonce } })} isDisabled={!!signedTx} value={accountNonce} onChange={this.onChangeNonce} />
|
||||
<InputNumber isZeroable={true} label={t('Lifetime (# of blocks)')} labelExtra={t('Set to 0 to make transaction immortal')} isDisabled={!!signedTx} value={blocks} onChange={this.onChangeBlocks} />
|
||||
{!!signedTx && <Output
|
||||
label={t('Signed transaction')}
|
||||
value={signedTx}
|
||||
withCopy
|
||||
/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private onRenderError = (): void => {
|
||||
this.setState({ isRenderError: true });
|
||||
}
|
||||
};
|
||||
|
||||
private onShowTip = (showTip: boolean): void => {
|
||||
this.setState({ showTip });
|
||||
};
|
||||
|
||||
private onToggleSign = (isSubmit: boolean): void => {
|
||||
this.setState({ isSubmit });
|
||||
}
|
||||
|
||||
private onChangeNonce = (value?: BN): void => {
|
||||
this.setState({ nonce: value ? value.toString() : '0' });
|
||||
}
|
||||
|
||||
private onChangeBlocks = (value?: BN): void => {
|
||||
this.setState({ blocks: value ? value.toString() : '0' });
|
||||
}
|
||||
|
||||
private renderUnlock (): React.ReactNode {
|
||||
const { currentItem, isSendable, password, unlockError } = this.state;
|
||||
|
||||
const { isExternal } = currentItem
|
||||
? extractExternal(currentItem.accountId)
|
||||
: { isExternal: false };
|
||||
const { isExternal } = currentItem ? extractExternal(currentItem.accountId) : { isExternal: false };
|
||||
|
||||
if (!isSendable || !currentItem || currentItem.isUnsigned || isExternal) {
|
||||
return null;
|
||||
@ -364,25 +417,41 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
password,
|
||||
unlockError: null
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onChangeTip = (tip?: BN): void => {
|
||||
this.setState({ tip });
|
||||
}
|
||||
};
|
||||
|
||||
private onCancelQr = (): void => {
|
||||
this.setState({
|
||||
isQrScanning: false,
|
||||
isQrVisible: false
|
||||
}, (): void => {
|
||||
const { qrReject } = this.state;
|
||||
this.setState(
|
||||
{
|
||||
isQrScanning: false,
|
||||
isQrVisible: false
|
||||
},
|
||||
(): void => {
|
||||
const { qrReject } = this.state;
|
||||
|
||||
qrReject && qrReject(new Error('cancelled'));
|
||||
qrReject && qrReject(new Error('cancelled'));
|
||||
|
||||
this.onCancel();
|
||||
});
|
||||
this.onCancel();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
private onCancelSign = (): void => {
|
||||
const { queueSetTxStatus } = this.props;
|
||||
const { currentItem } = this.state;
|
||||
const { id, txSuccessCb } = currentItem as QueueTx;
|
||||
queueSetTxStatus(id, 'completed');
|
||||
|
||||
if (isFunction(txSuccessCb)) {
|
||||
txSuccessCb({} as any);
|
||||
}
|
||||
|
||||
this.setState(initialState);
|
||||
}
|
||||
|
||||
private onCancel = (): void => {
|
||||
const { queueSetTxStatus } = this.props;
|
||||
const { currentItem } = this.state;
|
||||
@ -403,7 +472,9 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
if (isFunction(txFailedCb)) {
|
||||
txFailedCb(null);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState(initialState);
|
||||
};
|
||||
|
||||
private onSend = async (): Promise<void> => {
|
||||
const { currentItem, password } = this.state;
|
||||
@ -414,7 +485,19 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
return this.sendExtrinsic(currentItem, password);
|
||||
}
|
||||
};
|
||||
|
||||
private updateNonce = async (): Promise<void> => {
|
||||
const { currentItem } = this.state;
|
||||
let accountNonce: string | undefined;
|
||||
if (currentItem?.accountId) {
|
||||
accountNonce = (await this.props.api.rpc.account.nextIndex(currentItem.accountId)).toString();
|
||||
} else {
|
||||
accountNonce = undefined;
|
||||
}
|
||||
|
||||
this.setState({ accountNonce, nonce: accountNonce });
|
||||
};
|
||||
|
||||
private signQrPayload = (payload: SignerPayloadJSON): Promise<SignerResult> => {
|
||||
return new Promise((resolve, reject): void => {
|
||||
@ -426,25 +509,28 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
qrReject: reject
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private addQrSignature = ({ signature }: { signature: string }): void => {
|
||||
this.setState(({ qrResolve }: State): Pick<State, never> => {
|
||||
qrResolve && qrResolve({
|
||||
id: ++qrId,
|
||||
signature
|
||||
});
|
||||
this.setState(
|
||||
({ qrResolve }: State): Pick<State, never> => {
|
||||
qrResolve &&
|
||||
qrResolve({
|
||||
id: ++qrId,
|
||||
signature
|
||||
});
|
||||
|
||||
return {
|
||||
isQrScanning: false,
|
||||
isQrVisible: false
|
||||
};
|
||||
});
|
||||
}
|
||||
return {
|
||||
isQrScanning: false,
|
||||
isQrVisible: false
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
private activateQrScanning = (): void => {
|
||||
this.setState({ isQrScanning: true });
|
||||
}
|
||||
};
|
||||
|
||||
private sendRpc = async ({ id, rpc, values = [] }: QueueTx): Promise<void> => {
|
||||
if (!rpc) {
|
||||
@ -458,10 +544,10 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
const { error, result, status } = await this.submitRpc(rpc, values);
|
||||
|
||||
queueSetTxStatus(id, status, result, error);
|
||||
}
|
||||
};
|
||||
|
||||
private async sendExtrinsic (queueTx: QueueTx, password?: string): Promise<void> {
|
||||
const { isV2, showTip, tip } = this.state;
|
||||
const { isV2, isSubmit, showTip, tip } = this.state;
|
||||
|
||||
const { accountId, extrinsic, payload, isUnsigned } = queueTx;
|
||||
|
||||
@ -480,7 +566,7 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
return makeExtrinsicSignature(
|
||||
{
|
||||
...payload,
|
||||
...((isV2 && showTip && tip && !payload.tip) ? { tip: tip.toString() } : {})
|
||||
...(isV2 && showTip && tip && !payload.tip ? { tip: tip.toString() } : {})
|
||||
},
|
||||
queueTx,
|
||||
keyring.getPair(accountId as string)
|
||||
@ -492,17 +578,20 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
assert(submittable, 'Expected an extrinsic to be supplied to sendExtrinsic');
|
||||
|
||||
return isUnsigned
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
? this.makeExtrinsicCall(submittable, queueTx, submittable.send)
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
: this.makeExtrinsicCall(submittable, queueTx, submittable.signAndSend, keyring.getPair(accountId as string));
|
||||
? this.makeExtrinsicCall(submittable, queueTx, submittable.send.bind(submittable))
|
||||
: isSubmit
|
||||
? this.makeExtrinsicCall(submittable, queueTx, submittable.signAndSend.bind(submittable), keyring.getPair(accountId as string))
|
||||
: this.makeSignedTransaction(submittable, queueTx, keyring.getPair(accountId as string));
|
||||
}
|
||||
|
||||
private async submitRpc ({ method, section }: RpcMethod, values: any[]): Promise<QueueTxResult> {
|
||||
const { api } = this.props;
|
||||
|
||||
try {
|
||||
assert(isFunction((api.rpc as any)[section] && (api.rpc as any)[section][method]), `api.rpc.${section}.${method} does not exist`);
|
||||
assert(
|
||||
isFunction((api.rpc as any)[section] && (api.rpc as any)[section][method]),
|
||||
`api.rpc.${section}.${method} does not exist`
|
||||
);
|
||||
|
||||
const result = await (api.rpc as any)[section][method](...values);
|
||||
|
||||
@ -522,7 +611,12 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
private async makeExtrinsicCall (extrinsic: SubmittableExtrinsic, { id, txFailedCb, txSuccessCb, txStartCb, txUpdateCb }: QueueTx, extrinsicCall: (...params: any[]) => any, pair?: KeyringPair): Promise<void> {
|
||||
private async makeExtrinsicCall (
|
||||
extrinsic: SubmittableExtrinsic,
|
||||
{ id, txFailedCb, txSuccessCb, txStartCb, txUpdateCb }: QueueTx,
|
||||
extrinsicCall: (...params: any[]) => any,
|
||||
pair?: KeyringPair
|
||||
): Promise<void> {
|
||||
const { api, queueSetTxStatus } = this.props;
|
||||
const { isV2, showTip, tip } = this.state;
|
||||
|
||||
@ -531,7 +625,10 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
const params = [];
|
||||
|
||||
if (pair) {
|
||||
const { address, meta: { isExternal, isHardware, isInjected, source } } = pair;
|
||||
const {
|
||||
address,
|
||||
meta: { isExternal, isHardware, isInjected, source }
|
||||
} = pair;
|
||||
|
||||
queueSetTxStatus(id, 'signing');
|
||||
|
||||
@ -564,37 +661,40 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
const unsubscribe = await extrinsicCall.apply(extrinsic, [...params, async (result: SubmittableResult): Promise<void> => {
|
||||
if (!result || !result.status) {
|
||||
return;
|
||||
const unsubscribe = await extrinsicCall.apply(extrinsic, [
|
||||
...params,
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async (result: SubmittableResult): Promise<void> => {
|
||||
if (!result || !result.status) {
|
||||
return;
|
||||
}
|
||||
|
||||
const status = result.status.type.toLowerCase() as QueueTxStatus;
|
||||
|
||||
console.log('makeExtrinsicCall: updated status ::', JSON.stringify(result));
|
||||
queueSetTxStatus(id, status, result);
|
||||
|
||||
if (isFunction(txUpdateCb)) {
|
||||
txUpdateCb(result);
|
||||
}
|
||||
|
||||
if (result.status.isFinalized) {
|
||||
unsubscribe();
|
||||
|
||||
result.events
|
||||
.filter(({ event: { section } }): boolean => section === 'system')
|
||||
.forEach(({ event: { method } }): void => {
|
||||
if (isFunction(txFailedCb) && method === 'ExtrinsicFailed') {
|
||||
txFailedCb(result);
|
||||
} else if (isFunction(txSuccessCb) && method === 'ExtrinsicSuccess') {
|
||||
txSuccessCb(result);
|
||||
}
|
||||
});
|
||||
} else if (result.isError && isFunction(txFailedCb)) {
|
||||
txFailedCb(result);
|
||||
}
|
||||
}
|
||||
|
||||
const status = result.status.type.toLowerCase() as QueueTxStatus;
|
||||
|
||||
console.log('makeExtrinsicCall: updated status ::', JSON.stringify(result));
|
||||
queueSetTxStatus(id, status, result);
|
||||
|
||||
if (isFunction(txUpdateCb)) {
|
||||
txUpdateCb(result);
|
||||
}
|
||||
|
||||
if (result.status.isFinalized) {
|
||||
unsubscribe();
|
||||
|
||||
result.events
|
||||
.filter(({ event: { section } }): boolean => section === 'system')
|
||||
.forEach(({ event: { method } }): void => {
|
||||
if (isFunction(txFailedCb) && method === 'ExtrinsicFailed') {
|
||||
txFailedCb(result);
|
||||
} else if (isFunction(txSuccessCb) && method === 'ExtrinsicSuccess') {
|
||||
txSuccessCb(result);
|
||||
}
|
||||
});
|
||||
} else if (result.isError && isFunction(txFailedCb)) {
|
||||
txFailedCb(result);
|
||||
}
|
||||
}]);
|
||||
]);
|
||||
|
||||
queueSetTxStatus(id, 'sending');
|
||||
} catch (error) {
|
||||
@ -606,11 +706,62 @@ class Signer extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async makeSignedTransaction (
|
||||
extrinsic: SubmittableExtrinsic,
|
||||
{ id, txFailedCb, txStartCb }: QueueTx,
|
||||
pair: KeyringPair
|
||||
): Promise<void> {
|
||||
const { queueSetTxStatus } = this.props;
|
||||
const { isV2, blocks, nonce, showTip, tip } = this.state;
|
||||
|
||||
console.log('makeSignedTransaction: extrinsic ::', extrinsic.toHex());
|
||||
|
||||
const {
|
||||
address,
|
||||
meta: { isExternal, isHardware, isInjected, source }
|
||||
} = pair;
|
||||
let signer: ApiSigner | undefined;
|
||||
|
||||
if (isFunction(txStartCb)) {
|
||||
txStartCb();
|
||||
}
|
||||
|
||||
// set the signer
|
||||
if (isHardware) {
|
||||
signer = ledgerSigner;
|
||||
} else if (isExternal) {
|
||||
queueSetTxStatus(id, 'qr');
|
||||
signer = { signPayload: this.signQrPayload };
|
||||
} else if (isInjected) {
|
||||
const injected = await web3FromSource(source);
|
||||
signer = injected?.signer;
|
||||
}
|
||||
|
||||
assert(signer || pair, `Unable to find a signer for ${address}`);
|
||||
|
||||
try {
|
||||
await extrinsic.signAsync((signer ? address : pair) as any, {
|
||||
era: +blocks as any,
|
||||
nonce: +(nonce || 0),
|
||||
signer,
|
||||
tip: (showTip && isV2 && tip) ? tip : undefined
|
||||
});
|
||||
const signedTx = extrinsic.toJSON()?.toString();
|
||||
|
||||
console.log('makeSignedTransaction: result ::', signedTx);
|
||||
|
||||
this.setState({ signedTx });
|
||||
} catch (e) {
|
||||
queueSetTxStatus(id, 'error', undefined, e);
|
||||
if (isFunction(txFailedCb)) {
|
||||
txFailedCb(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
Signer
|
||||
};
|
||||
export { Signer };
|
||||
|
||||
export default withMulti(
|
||||
Signer,
|
||||
|
||||
@ -66,4 +66,9 @@ export default styled(Signer)`
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.signToggle {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user