Add Signer interface for API (#683)

* Add Signer interface for API

* Attach signer to API instance

* Integrate signer with updates (untested)

* Update with working signer integration (logging an issue still)

* Restore console hooks, feedback on signed

* Remove unneeded console.log

* Marge install
This commit is contained in:
Jaco Greeff 2019-02-12 22:29:47 +01:00 committed by GitHub
parent ce63296007
commit 61674a7989
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 62 deletions

View File

@ -0,0 +1,24 @@
// Copyright 2017-2019 @polkadot/app-123code authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export default `// transfer
const sender = '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ';
const recipient = '5GTZEKgRKX2a5hepNjwTBzR5nR6UqtEsMZ4TnL1Yuah1N2Tu';
const unsub = await api.tx.balances
.transfer(recipient,12345)
.signAndSend(sender, ({ events = [], status, type }) => {
console.log('Transaction status:', type);
if (type === 'Finalised') {
console.log('Completed at block hash', status.asFinalised.toHex());
console.log('Events:');
events.forEach(({ phase, event: { data, method, section } }) => {
console.log('\t', phase.toString(), \`: \${section}.\${method}\`, data.toString());
});
unsub();
}
});`;

View File

@ -49,20 +49,20 @@ class Content extends React.Component<Props> {
return (
<div className='apps--Content'>
<QueueConsumer>
{({ queueAction, stqueue, txqueue }: QueueProps) => [
<Component
key='content-content'
basePath={`/${name}`}
location={location}
onStatusChange={queueAction}
/>,
<Status
key='content-status'
queueAction={queueAction}
stqueue={stqueue}
txqueue={txqueue}
/>
]}
{({ queueAction, stqueue, txqueue }: QueueProps) => (
<>
<Component
basePath={`/${name}`}
location={location}
onStatusChange={queueAction}
/>
<Status
queueAction={queueAction}
stqueue={stqueue}
txqueue={txqueue}
/>
</>
)}
</QueueConsumer>
</div>
);

View File

@ -3,16 +3,18 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { ProviderInterface } from '@polkadot/rpc-provider/types';
import { QueueTx$ExtrinsicAdd, QueueTx$MessageSetStatus } from '@polkadot/ui-app/Status/types';
import { ApiProps } from './types';
import React from 'react';
import ApiPromise from '@polkadot/api/promise';
import defaults from '@polkadot/rpc-provider/defaults';
import WsProvider from '@polkadot/rpc-provider/ws';
import { WsProvider } from '@polkadot/rpc-provider';
import { InputNumber } from '@polkadot/ui-app/InputNumber';
import { formatBalance } from '@polkadot/ui-app/util';
import keyring from '@polkadot/ui-keyring';
import settings from '@polkadot/ui-settings';
import ApiSigner from '@polkadot/ui-signer/ApiSigner';
import { ChainProperties } from '@polkadot/types';
import ApiContext from './ApiContext';
@ -20,6 +22,8 @@ import { isTestChain } from './util';
type Props = {
children: React.ReactNode,
queueExtrinsic: QueueTx$ExtrinsicAdd,
queueSetTxStatus: QueueTx$MessageSetStatus,
url?: string
};
@ -33,10 +37,12 @@ export default class ApiWrapper extends React.PureComponent<Props, State> {
constructor (props: Props) {
super(props);
const { url } = props;
const { queueExtrinsic, queueSetTxStatus, url } = props;
const provider = new WsProvider(url);
const signer = new ApiSigner(queueExtrinsic, queueSetTxStatus);
const setApi = (provider: ProviderInterface): void => {
const api = new ApiPromise(provider);
const api = new ApiPromise({ provider, signer });
this.setState({ api }, () => {
this.updateSubscriptions();
@ -48,7 +54,7 @@ export default class ApiWrapper extends React.PureComponent<Props, State> {
this.state = {
isApiConnected: false,
isApiReady: false,
api: new ApiPromise(provider),
api: new ApiPromise({ provider, signer }),
setApiUrl
} as State;
}

View File

@ -156,11 +156,13 @@ export default class Queue extends React.Component<Props, State> {
return id;
}
queueExtrinsic = ({ accountId, extrinsic, isUnsigned }: PartialQueueTx$Extrinsic): number => {
queueExtrinsic = ({ accountId, extrinsic, signerCallback, signerOptions, isUnsigned }: PartialQueueTx$Extrinsic): number => {
return this.queueAdd({
accountId,
extrinsic,
isUnsigned
isUnsigned,
signerCallback,
signerOptions
});
}

View File

@ -5,6 +5,7 @@
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { RpcMethod } from '@polkadot/jsonrpc/types';
import { AccountId, Address } from '@polkadot/types';
import { SignatureOptions } from '@polkadot/types/ExtrinsicSignature';
export type Actions = 'create' | 'edit' | 'restore' | 'forget' | 'backup' | 'changePassword' | 'transfer';
@ -21,6 +22,8 @@ export type AccountInfo = {
export type QueueTx$Status = 'future' | 'ready' | 'finalised' | 'usurped' | 'dropped' | 'invalid' | 'broadcast' | 'cancelled' | 'completed' | 'error' | 'incomplete' | 'queued' | 'sending' | 'sent' | 'blocked';
export type SignerCallback = (id: number, isSigned: boolean) => void;
export type QueueTx = AccountInfo & {
error?: Error,
extrinsic?: SubmittableExtrinsic,
@ -28,6 +31,8 @@ export type QueueTx = AccountInfo & {
isUnsigned?: boolean,
result?: any,
rpc: RpcMethod,
signerCallback?: SignerCallback,
signerOptions?: SignatureOptions,
values?: Array<any>,
status: QueueTx$Status
};
@ -58,6 +63,8 @@ export type PartialAccountInfo = {
export type PartialQueueTx$Extrinsic = PartialAccountInfo & {
extrinsic: SubmittableExtrinsic,
signerCallback?: SignerCallback,
signerOptions?: SignatureOptions,
isUnsigned?: boolean
};

View File

@ -13,6 +13,9 @@ import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import { Api } from '@polkadot/ui-api/index';
import { QueueConsumer } from './Status/Context';
import Queue from './Status/Queue';
export { default as AddressMini } from './AddressMini';
export { default as AddressRow } from './AddressRow';
export { default as AddressSummary } from './AddressSummary';
@ -58,14 +61,24 @@ export default function createApp (App: React.ComponentType<BareProps>, { classN
}
ReactDOM.render(
<Api url={url}>
<HashRouter>
<App
className={className}
style={style}
/>
</HashRouter>
</Api>,
<Queue>
<QueueConsumer>
{({ queueExtrinsic, queueSetTxStatus }) => (
<Api
queueExtrinsic={queueExtrinsic}
queueSetTxStatus={queueSetTxStatus}
url={url}
>
<HashRouter>
<App
className={className}
style={style}
/>
</HashRouter>
</Api>
)}
</QueueConsumer>
</Queue>,
rootElement
);
}

View File

@ -0,0 +1,45 @@
// Copyright 2017-2019 @polkadot/ui-signer authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Signer } from '@polkadot/api/types';
import { SubmittableResult } from '@polkadot/api';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { QueueTx$ExtrinsicAdd, QueueTx$MessageSetStatus } from '@polkadot/ui-app/Status/types';
import { Hash } from '@polkadot/types';
import { SignatureOptions } from '@polkadot/types/ExtrinsicSignature';
export default class ApiSigner implements Signer {
private _queueExtrinsic: QueueTx$ExtrinsicAdd;
private _queueSetTxStatus: QueueTx$MessageSetStatus;
constructor (queueExtrinsic: QueueTx$ExtrinsicAdd, queueSetTxStatus: QueueTx$MessageSetStatus) {
this._queueExtrinsic = queueExtrinsic;
this._queueSetTxStatus = queueSetTxStatus;
}
async sign (extrinsic: SubmittableExtrinsic, accountId: string, signerOptions: SignatureOptions): Promise<number> {
return new Promise((resolve, reject) => {
this._queueExtrinsic({
accountId,
extrinsic,
signerOptions,
signerCallback: (id: number, isSigned: boolean): void => {
if (isSigned) {
resolve(id);
} else {
reject();
}
}
});
});
}
update (id: number, status: Hash | SubmittableResult): void {
if (status instanceof Hash) {
this._queueSetTxStatus(id, 'sent', status.toHex());
} else {
this._queueSetTxStatus(id, status.type.toLowerCase() as any, status);
}
}
}

View File

@ -7,7 +7,9 @@ import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { ApiProps } from '@polkadot/ui-api/types';
import { I18nProps, BareProps } from '@polkadot/ui-app/types';
import { RpcMethod } from '@polkadot/jsonrpc/types';
import { QueueTx, QueueTx$MessageSetStatus, QueueTx$Result, QueueTx$Status } from '@polkadot/ui-app/Status/types';
import { KeyringPair } from '@polkadot/keyring/types';
import { SignatureOptions } from '@polkadot/types/ExtrinsicSignature';
import { QueueTx, QueueTx$MessageSetStatus, QueueTx$Result, QueueTx$Status, SignerCallback } from '@polkadot/ui-app/Status/types';
import React from 'react';
import { Button, Modal } from '@polkadot/ui-app/index';
@ -214,7 +216,10 @@ class Signer extends React.PureComponent<Props, State> {
return;
}
queueSetTxStatus(currentItem.id, 'cancelled');
const { id, signerCallback } = currentItem;
queueSetTxStatus(id, 'cancelled');
signerCallback && signerCallback(id, false);
}
private onSend = async (): Promise<void> => {
@ -242,7 +247,7 @@ class Signer extends React.PureComponent<Props, State> {
queueSetTxStatus(id, status, result, error);
}
private async sendExtrinsic ({ accountId, extrinsic, id, isUnsigned }: QueueTx, password?: string): Promise<void> {
private async sendExtrinsic ({ accountId, extrinsic, id, signerCallback, signerOptions, isUnsigned }: QueueTx, password?: string): Promise<void> {
assert(extrinsic, 'Expected an extrinsic to be supplied to sendExtrinsic');
if (!isUnsigned) {
@ -261,11 +266,13 @@ class Signer extends React.PureComponent<Props, State> {
queueSetTxStatus(id, 'sending');
if (!isUnsigned) {
return this.makeExtrinsicCall(submittable, id, submittable.signAndSend, keyring.getPair(accountId as string));
} else {
if (isUnsigned) {
return this.makeExtrinsicCall(submittable, id, submittable.send);
} else if (signerOptions) {
return this.makeExtrinsicSignature(submittable, id, keyring.getPair(accountId as string), signerOptions, signerCallback);
}
return this.makeExtrinsicCall(submittable, id, submittable.signAndSend, keyring.getPair(accountId as string));
}
private async submitRpc ({ method, section }: RpcMethod, values: Array<any>): Promise<QueueTx$Result> {
@ -316,6 +323,14 @@ class Signer extends React.PureComponent<Props, State> {
queueSetTxStatus(id, 'error', {}, error);
}
}
private async makeExtrinsicSignature (extrinsic: SubmittableExtrinsic, id: number, pair: KeyringPair, options: SignatureOptions, signerCallback?: SignerCallback): Promise<void> {
console.log('makeExtrinsicSignature: extrinsic ::', extrinsic.toHex());
extrinsic.sign(pair, options);
signerCallback && signerCallback(id, true);
}
}
export {

View File

@ -9,7 +9,6 @@ import './index.css';
import React from 'react';
import { QueueConsumer } from '@polkadot/ui-app/Status/Context';
import Queue, { Props as QueueComponentProps } from '@polkadot/ui-app/Status/Queue';
import Modal from './Modal';
@ -17,39 +16,25 @@ type Props = BareProps & {
children: React.ReactNode
};
export type SignerType = React.ComponentType<Props> & {
Queue: React.ComponentType<QueueComponentProps>
};
class Signer extends React.PureComponent<Props> {
static Queue = Queue;
export default class Signer extends React.PureComponent<Props> {
render () {
const { children } = this.props;
const { children, className, style } = this.props;
return (
<Queue>
<>
{children}
<QueueConsumer>
{this.handleQueueProps}
{({ txqueue, queueSetTxStatus }: QueueProps) => (
<Modal
className={className}
key='signer-modal'
queue={txqueue}
queueSetTxStatus={queueSetTxStatus}
style={style}
/>
)}
</QueueConsumer>
</Queue>
);
}
private handleQueueProps = ({ txqueue, queueSetTxStatus }: QueueProps) => {
const { className, style } = this.props;
return (
<Modal
className={className}
key='signer-modal'
queue={txqueue}
queueSetTxStatus={queueSetTxStatus}
style={style}
/>
</>
);
}
}
export default (Signer as SignerType);