diff --git a/packages/app-js/src/snippets/transfer.ts b/packages/app-js/src/snippets/transfer.ts new file mode 100644 index 0000000000..bfd3c2cdb4 --- /dev/null +++ b/packages/app-js/src/snippets/transfer.ts @@ -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(); + } + });`; diff --git a/packages/apps/src/Content/index.tsx b/packages/apps/src/Content/index.tsx index 5dca3187e0..18b77fd417 100644 --- a/packages/apps/src/Content/index.tsx +++ b/packages/apps/src/Content/index.tsx @@ -49,20 +49,20 @@ class Content extends React.Component { return (
- {({ queueAction, stqueue, txqueue }: QueueProps) => [ - , - - ]} + {({ queueAction, stqueue, txqueue }: QueueProps) => ( + <> + + + + )}
); diff --git a/packages/ui-api/src/Api.tsx b/packages/ui-api/src/Api.tsx index 9c807ded08..4d9d27c431 100644 --- a/packages/ui-api/src/Api.tsx +++ b/packages/ui-api/src/Api.tsx @@ -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 { 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 { this.state = { isApiConnected: false, isApiReady: false, - api: new ApiPromise(provider), + api: new ApiPromise({ provider, signer }), setApiUrl } as State; } diff --git a/packages/ui-app/src/Status/Queue.tsx b/packages/ui-app/src/Status/Queue.tsx index e0c03e30a6..0c09664340 100644 --- a/packages/ui-app/src/Status/Queue.tsx +++ b/packages/ui-app/src/Status/Queue.tsx @@ -156,11 +156,13 @@ export default class Queue extends React.Component { 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 }); } diff --git a/packages/ui-app/src/Status/types.ts b/packages/ui-app/src/Status/types.ts index 35eff3028f..763e38dd12 100644 --- a/packages/ui-app/src/Status/types.ts +++ b/packages/ui-app/src/Status/types.ts @@ -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, status: QueueTx$Status }; @@ -58,6 +63,8 @@ export type PartialAccountInfo = { export type PartialQueueTx$Extrinsic = PartialAccountInfo & { extrinsic: SubmittableExtrinsic, + signerCallback?: SignerCallback, + signerOptions?: SignatureOptions, isUnsigned?: boolean }; diff --git a/packages/ui-app/src/index.tsx b/packages/ui-app/src/index.tsx index 5cc1ad3f22..6bce747d61 100644 --- a/packages/ui-app/src/index.tsx +++ b/packages/ui-app/src/index.tsx @@ -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, { classN } ReactDOM.render( - - - - - , + + + {({ queueExtrinsic, queueSetTxStatus }) => ( + + + + + + )} + + , rootElement ); } diff --git a/packages/ui-signer/src/ApiSigner.ts b/packages/ui-signer/src/ApiSigner.ts new file mode 100644 index 0000000000..5ed0fbe9d1 --- /dev/null +++ b/packages/ui-signer/src/ApiSigner.ts @@ -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 { + 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); + } + } +} diff --git a/packages/ui-signer/src/Modal.tsx b/packages/ui-signer/src/Modal.tsx index 7358f851e1..830807ad94 100644 --- a/packages/ui-signer/src/Modal.tsx +++ b/packages/ui-signer/src/Modal.tsx @@ -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 { return; } - queueSetTxStatus(currentItem.id, 'cancelled'); + const { id, signerCallback } = currentItem; + + queueSetTxStatus(id, 'cancelled'); + signerCallback && signerCallback(id, false); } private onSend = async (): Promise => { @@ -242,7 +247,7 @@ class Signer extends React.PureComponent { queueSetTxStatus(id, status, result, error); } - private async sendExtrinsic ({ accountId, extrinsic, id, isUnsigned }: QueueTx, password?: string): Promise { + private async sendExtrinsic ({ accountId, extrinsic, id, signerCallback, signerOptions, isUnsigned }: QueueTx, password?: string): Promise { assert(extrinsic, 'Expected an extrinsic to be supplied to sendExtrinsic'); if (!isUnsigned) { @@ -261,11 +266,13 @@ class Signer extends React.PureComponent { 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): Promise { @@ -316,6 +323,14 @@ class Signer extends React.PureComponent { queueSetTxStatus(id, 'error', {}, error); } } + + private async makeExtrinsicSignature (extrinsic: SubmittableExtrinsic, id: number, pair: KeyringPair, options: SignatureOptions, signerCallback?: SignerCallback): Promise { + console.log('makeExtrinsicSignature: extrinsic ::', extrinsic.toHex()); + + extrinsic.sign(pair, options); + + signerCallback && signerCallback(id, true); + } } export { diff --git a/packages/ui-signer/src/index.tsx b/packages/ui-signer/src/index.tsx index 442d81773f..c9e9eb4218 100644 --- a/packages/ui-signer/src/index.tsx +++ b/packages/ui-signer/src/index.tsx @@ -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 & { - Queue: React.ComponentType -}; - -class Signer extends React.PureComponent { - static Queue = Queue; - +export default class Signer extends React.PureComponent { render () { - const { children } = this.props; + const { children, className, style } = this.props; return ( - + <> {children} - {this.handleQueueProps} + {({ txqueue, queueSetTxStatus }: QueueProps) => ( + + )} - - ); - } - - private handleQueueProps = ({ txqueue, queueSetTxStatus }: QueueProps) => { - const { className, style } = this.props; - - return ( - + ); } } - -export default (Signer as SignerType);