Split Submittable into actual classes (#1350)

* Split Submittable into actual classes

* *sigh* Fix VSCode's "helpful" imports

* Rename option creation functions

* Extrinsic already has version embedded

* V3
This commit is contained in:
Jaco Greeff
2019-08-29 22:33:57 +02:00
committed by GitHub
parent 3fa8f9f89d
commit d905b4fef3
15 changed files with 403 additions and 343 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ISubmittableResult, SubmittableResult } from '@polkadot/api/SubmittableExtrinsic';
import { SubmittableResultImpl } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ContractABI } from './types';
@@ -10,7 +10,7 @@ import { ContractABI } from './types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiRx } from '@polkadot/api';
import { ApiRx, SubmittableResult } from '@polkadot/api';
import { createType } from '@polkadot/types';
import Abi from './Abi';
@@ -26,7 +26,7 @@ export interface BlueprintCreate {
class BlueprintCreateResult extends SubmittableResult {
public readonly contract?: RxContract;
public constructor (result: ISubmittableResult, contract?: RxContract) {
public constructor (result: SubmittableResultImpl, contract?: RxContract) {
super(result);
this.contract = contract;
+4 -4
View File
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ISubmittableResult, SubmittableResult } from '@polkadot/api/SubmittableExtrinsic';
import { SubmittableResultImpl } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ContractABI } from './types';
@@ -10,7 +10,7 @@ import { ContractABI } from './types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiRx } from '@polkadot/api';
import { ApiRx, SubmittableResult } from '@polkadot/api';
import { compactAddLength, u8aToU8a } from '@polkadot/util';
import Abi from './Abi';
@@ -39,7 +39,7 @@ export interface CodePutCode {
class CodePutCodeResult extends SubmittableResult {
public readonly blueprint?: RxBlueprint;
public constructor (result: ISubmittableResult, blueprint?: RxBlueprint) {
public constructor (result: SubmittableResultImpl, blueprint?: RxBlueprint) {
super(result);
this.blueprint = blueprint;
@@ -67,7 +67,7 @@ export default class RxCode extends RxBase {
return { signAndSend };
}
private createResult = (result: ISubmittableResult): CodePutCodeResult => {
private createResult = (result: SubmittableResultImpl): CodePutCodeResult => {
let blueprint: RxBlueprint | undefined;
if (result.isFinalized) {
+1 -2
View File
@@ -8,8 +8,7 @@ import { ContractABI, ContractABIFn, InterfaceContract, InterfaceContractCalls }
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { ApiRx } from '@polkadot/api';
import { SubmittableResult } from '@polkadot/api/SubmittableExtrinsic';
import { ApiRx, SubmittableResult } from '@polkadot/api';
import { createType } from '@polkadot/types';
import Abi from './Abi';
import RxBase from './RxBase';
-315
View File
@@ -1,315 +0,0 @@
// Copyright 2017-2019 @polkadot/api 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 { AccountId, Address, Call, ExtrinsicEra, ExtrinsicStatus, EventRecord, Hash, Header, Index } from '@polkadot/types/interfaces';
import { AnyNumber, AnyU8a, Callback, Codec, IExtrinsic, IExtrinsicEra, IKeyringPair, SignatureOptions } from '@polkadot/types/types';
import { ApiInterfaceRx, ApiTypes } from './types';
import BN from 'bn.js';
import { Observable, combineLatest, of } from 'rxjs';
import { first, map, mergeMap, switchMap, tap } from 'rxjs/operators';
import { createType, Vec } from '@polkadot/types';
import { isBn, isFunction, isNumber, isUndefined } from '@polkadot/util';
import filterEvents from './util/filterEvents';
import ApiBase from './base';
import SignerPayload from './SignerPayload';
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export interface ISubmittableResult {
readonly events: EventRecord[];
readonly status: ExtrinsicStatus;
readonly isCompleted: boolean;
readonly isError: boolean;
readonly isFinalized: boolean;
findRecord(section: string, method: string): EventRecord | undefined;
}
export type SumbitableResultResult<ApiType> =
ApiType extends 'rxjs'
? Observable<ISubmittableResult>
: Promise<Hash>;
export type SumbitableResultSubscription<ApiType> =
ApiType extends 'rxjs'
? Observable<ISubmittableResult>
: Promise<() => void>;
interface SubmittableResultValue {
events?: EventRecord[];
status: ExtrinsicStatus;
}
interface SignerOptions {
blockHash: AnyU8a;
era?: IExtrinsicEra | number;
nonce: AnyNumber;
tip?: AnyNumber;
}
// The default for 6s allowing for 5min eras. When translating this to faster blocks -
// - 4s = (10 / 15) * 5 = 3.33m
// - 2s = (10 / 30) * 5 = 1.66m
const BLOCKTIME = 6;
const ONE_MINUTE = 60 / BLOCKTIME;
const DEFAULT_MORTAL_LENGTH = 5 * ONE_MINUTE;
function isKeyringPair (account: string | IKeyringPair | AccountId | Address): account is IKeyringPair {
return isFunction((account as IKeyringPair).sign);
}
export class SubmittableResult implements ISubmittableResult {
public readonly events: EventRecord[];
public readonly status: ExtrinsicStatus;
public constructor ({ events, status }: SubmittableResultValue) {
this.events = events || [];
this.status = status;
}
public get isCompleted (): boolean {
return this.isError || this.isFinalized;
}
public get isError (): boolean {
return this.status.isDropped || this.status.isInvalid || this.status.isUsurped;
}
public get isFinalized (): boolean {
return this.status.isFinalized;
}
/**
* @description Finds an EventRecord for the specified method & section
*/
public findRecord (section: string, method: string): EventRecord | undefined {
return this.events.find(({ event }): boolean =>
event.section === section && event.method === method
);
}
}
export interface SubmittableExtrinsic<ApiType> extends IExtrinsic {
send(): SumbitableResultResult<ApiType>;
send(statusCb: Callback<ISubmittableResult>): SumbitableResultSubscription<ApiType>;
sign(account: IKeyringPair, _options: Partial<SignatureOptions>): this;
signAndSend(account: IKeyringPair | string | AccountId | Address, options?: Partial<SignerOptions>): SumbitableResultResult<ApiType>;
signAndSend(account: IKeyringPair | string | AccountId | Address, statusCb: Callback<ISubmittableResult>): SumbitableResultSubscription<ApiType>;
signAndSend(account: IKeyringPair | string | AccountId | Address, options: Partial<SignerOptions>, statusCb?: Callback<ISubmittableResult>): SumbitableResultSubscription<ApiType>;
}
export default function createSubmittableExtrinsic<ApiType> (
type: ApiTypes,
api: ApiInterfaceRx,
decorateMethod: ApiBase<ApiType>['decorateMethod'],
extrinsic: Call | Uint8Array | string
): SubmittableExtrinsic<ApiType> {
const _extrinsic = createType('Extrinsic', extrinsic, { version: api.extrinsicType }) as unknown as SubmittableExtrinsic<ApiType>;
const _noStatusCb = type === 'rxjs';
function updateSigner (updateId: number, status: Hash | ISubmittableResult): void {
if ((updateId !== -1) && api.signer && api.signer.update) {
api.signer.update(updateId, status);
}
}
function statusObservable (status: ExtrinsicStatus): Observable<ISubmittableResult> {
if (!status.isFinalized) {
return of(new SubmittableResult({ status }));
}
const blockHash = status.asFinalized;
return combineLatest([
api.rpc.chain.getBlock(blockHash),
api.query.system.events.at(blockHash) as Observable<Vec<EventRecord>>
]).pipe(
map(([signedBlock, allEvents]): SubmittableResult =>
new SubmittableResult({
events: filterEvents(_extrinsic.hash, signedBlock, allEvents),
status
})
)
);
}
function sendObservable (updateId: number = -1): Observable<Hash> {
return api.rpc.author
.submitExtrinsic(_extrinsic)
.pipe(
tap((hash): void => {
updateSigner(updateId, hash);
})
);
}
function subscribeObservable (updateId: number = -1): Observable<ISubmittableResult> {
return api.rpc.author
.submitAndWatchExtrinsic(_extrinsic)
.pipe(
switchMap((status): Observable<ISubmittableResult> =>
statusObservable(status)
),
tap((status): void => {
updateSigner(updateId, status);
})
);
}
function expandOptions (options: Partial<SignerOptions>, extras: { blockHash?: Hash; era?: ExtrinsicEra; nonce?: Index }): SignatureOptions {
return {
blockHash: api.genesisHash,
...options,
...extras,
genesisHash: api.genesisHash,
runtimeVersion: api.runtimeVersion,
version: api.extrinsicType
} as unknown as SignatureOptions;
}
function expandEraOptions (options: Partial<SignerOptions>, { header, nonce }: { header: Header | null; nonce: Index }): SignatureOptions {
if (!header) {
if (isNumber(options.era)) {
// since we have no header, it is immortal, remove any option overrides
// so we only supply the genesisHash and no era to the construction
delete options.era;
delete options.blockHash;
}
return expandOptions(options, { nonce });
}
return expandOptions(options, {
blockHash: header.hash,
era: createType('ExtrinsicEra', {
current: header.number,
period: options.era || DEFAULT_MORTAL_LENGTH
}),
nonce
});
}
function getPrelimState (address: string, options: Partial<SignerOptions>): Observable<[Index, Header | null]> {
return combineLatest([
// if we have a nonce already, don't retrieve the latest, use what is there
isUndefined(options.nonce)
? api.query.system.accountNonce<Index>(address)
: of(createType('Index', options.nonce)),
// if we have an era provided already or eraLength is <= 0 (immortal)
// don't get the latest block, just pass null, handle in mergeMap
(isUndefined(options.era) || (isNumber(options.era) && options.era > 0))
? api.rpc.chain.getHeader()
: of(null)
]);
}
const signOrigin = _extrinsic.sign;
Object.defineProperties(
_extrinsic,
{
send: {
value: function (statusCb?: Callback<ISubmittableResult>): SumbitableResultResult<ApiType> | SumbitableResultSubscription<ApiType> {
const isSubscription = _noStatusCb || !!statusCb;
return decorateMethod(isSubscription ? subscribeObservable : sendObservable)(statusCb);
}
},
sign: {
value: function (account: IKeyringPair, optionOrNonce: Partial<SignerOptions>): SubmittableExtrinsic<ApiType> {
// HACK here we actually override nonce if it was specified (backwards compat for
// the previous signature - don't let userspace break, but allow then time to upgrade)
const options: Partial<SignerOptions> = isBn(optionOrNonce) || isNumber(optionOrNonce)
? { nonce: optionOrNonce }
: optionOrNonce;
signOrigin.apply(_extrinsic, [account, expandOptions(options, {})]);
return this;
}
},
signAndSend: {
value: function (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial<SignerOptions> | Callback<ISubmittableResult>, statusCb?: Callback<ISubmittableResult>): SumbitableResultResult<ApiType> | SumbitableResultSubscription<ApiType> {
let options: Partial<SignerOptions> = {};
if (isFunction(optionsOrStatus)) {
statusCb = optionsOrStatus;
} else {
options = { ...optionsOrStatus };
}
const isSubscription = _noStatusCb || !!statusCb;
const address = isKeyringPair(account) ? account.address : account.toString();
let updateId: number | undefined;
return decorateMethod(
(): Observable<Codec> => (
getPrelimState(address, options).pipe(
first(),
mergeMap(async ([nonce, header]): Promise<void> => {
const eraOptions = expandEraOptions(options, { header, nonce });
// FIXME This is becoming real messy with all the options - way past
// "a method should fit on a single screen" stage. (Probably want to
// clean this when we remove `api.signer.sign` in the next beta cycle)
if (isKeyringPair(account)) {
this.sign(account, eraOptions);
} else if (api.signer) {
const payload = new SignerPayload({
...eraOptions,
address,
method: _extrinsic.method,
blockNumber: header ? header.number : 0
});
if (api.signer.signPayload) {
const { id, signature } = await api.signer.signPayload(payload.toPayload());
// Here we explicitly call `toPayload()` again instead of working with an object
// (reference) as passed to the signer. This means that we are sure that the
// payload data is not modified from our inputs, but the signer
_extrinsic.addSignature(address, signature, payload.toPayload());
updateId = id;
} else if (api.signer.signRaw) {
const { id, signature } = await api.signer.signRaw(payload.toRaw());
// as above, always trust our payload as the signle sourec of truth
_extrinsic.addSignature(address, signature, payload.toPayload());
updateId = id;
} else if (api.signer.sign) {
console.warn('The Signer.sign interface is deprecated and will be removed in a future version, Swap to using the Signer.signPayload interface instead.');
updateId = await api.signer.sign(_extrinsic, address, {
...eraOptions,
blockNumber: header ? header.number.toBn() : new BN(0),
genesisHash: api.genesisHash
});
} else {
throw new Error('Invalid signer interface');
}
} else {
throw new Error('no signer exists');
}
}),
switchMap((): Observable<ISubmittableResult> | Observable<Hash> => {
return isSubscription
? subscribeObservable(updateId)
: sendObservable(updateId);
})
) as Observable<Codec>) // FIXME This is wrong, SubmittableResult is _not_ a codec
)(statusCb);
}
}
}
);
return _extrinsic;
}
+7 -7
View File
@@ -3,8 +3,9 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { RpcInterface } from '@polkadot/rpc-core/jsonrpc.types';
import { Hash, RuntimeVersion } from '@polkadot/types/interfaces';
import { Call, Hash, RuntimeVersion } from '@polkadot/types/interfaces';
import { AnyFunction, CallFunction, Codec, CodecArg as Arg, ModulesWithCalls } from '@polkadot/types/types';
import { SubmittableExtrinsic } from '../submittable/types';
import { ApiInterfaceRx, ApiOptions, ApiTypes, DecorateMethodOptions, DecoratedRpc, DecoratedRpcSection, QueryableModuleStorage, QueryableStorage, QueryableStorageEntry, QueryableStorageMulti, QueryableStorageMultiArg, QueryableStorageMultiArgs, SubmittableExtrinsicFunction, SubmittableExtrinsics, SubmittableModuleExtrinsics } from '../types';
import BN from 'bn.js';
@@ -21,7 +22,7 @@ import { DEFAULT_VERSION as EXTRINSIC_DEFAULT_VERSION } from '@polkadot/types/pr
import { StorageEntry } from '@polkadot/types/primitive/StorageKey';
import { compactStripLength, u8aToHex } from '@polkadot/util';
import createSubmittable, { SubmittableExtrinsic } from '../SubmittableExtrinsic';
import { createSubmittable } from '../submittable';
import { decorateSections } from '../util/decorate';
import Events from './Events';
@@ -158,12 +159,11 @@ export default abstract class Decorate<ApiType> extends Events {
}
protected decorateExtrinsics<ApiType> (extrinsics: ModulesWithCalls, decorateMethod: Decorate<ApiType>['decorateMethod']): SubmittableExtrinsics<ApiType> {
const creator = (value: Uint8Array | string): SubmittableExtrinsic<ApiType> =>
createSubmittable(this._type, this._rx as ApiInterfaceRx, decorateMethod, value);
const creator = createSubmittable(this._type, this._rx as ApiInterfaceRx, decorateMethod);
return Object.entries(extrinsics).reduce((out, [name, section]): SubmittableExtrinsics<ApiType> => {
out[name] = Object.entries(section).reduce((out, [name, method]): SubmittableModuleExtrinsics<ApiType> => {
out[name] = this.decorateExtrinsicEntry(method, decorateMethod);
out[name] = this.decorateExtrinsicEntry(method, creator);
return out;
}, {} as unknown as SubmittableModuleExtrinsics<ApiType>);
@@ -172,9 +172,9 @@ export default abstract class Decorate<ApiType> extends Events {
}, creator as unknown as SubmittableExtrinsics<ApiType>);
}
private decorateExtrinsicEntry<ApiType> (method: CallFunction, decorateMethod: Decorate<ApiType>['decorateMethod']): SubmittableExtrinsicFunction<ApiType> {
private decorateExtrinsicEntry<ApiType> (method: CallFunction, creator: (value: Call | Uint8Array | string) => SubmittableExtrinsic<ApiType>): SubmittableExtrinsicFunction<ApiType> {
const decorated = (...params: Arg[]): SubmittableExtrinsic<ApiType> =>
createSubmittable(this._type, this._rx as ApiInterfaceRx, decorateMethod, method(...params));
creator(method(...params));
return this.decorateFunctionMeta(method, decorated as any) as SubmittableExtrinsicFunction<ApiType>;
}
+1 -1
View File
@@ -10,4 +10,4 @@ export { WsProvider } from '@polkadot/rpc-provider';
export { default as ApiPromise } from './promise';
export { default as ApiRx } from './rx';
export { default as SubmittableExtrinsic, SubmittableResult } from './SubmittableExtrinsic';
export * from './submittable';
+1 -1
View File
@@ -4,7 +4,7 @@
import { QueryableStorageEntry as QueryableStorageEntryBase, SubmittableExtrinsicFunction as SubmittableExtrinsicFunctionBase } from '../types';
import { SubmittableExtrinsic as SubmittableExtrinsicBase } from '../SubmittableExtrinsic';
import { SubmittableExtrinsic as SubmittableExtrinsicBase } from '../submittable/types';
export type QueryableStorageEntry = QueryableStorageEntryBase<'promise'>;
export type SubmittableExtrinsic = SubmittableExtrinsicBase<'promise'>;
+262
View File
@@ -0,0 +1,262 @@
/* eslint-disable no-dupe-class-members */
// Copyright 2017-2019 @polkadot/api 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 { AccountId, Address, Call, Extrinsic, ExtrinsicEra, ExtrinsicStatus, EventRecord, Hash, Header, Index } from '@polkadot/types/interfaces';
import { Callback, Codec, Constructor, IKeyringPair, SignatureOptions } from '@polkadot/types/types';
import { ApiInterfaceRx, ApiTypes, SignerResult } from '../types';
import { SignerOptions, SubmittableExtrinsic, SubmittableResultImpl, SubmitableResultResult, SubmitableResultSubscription } from './types';
import { Observable, combineLatest, of } from 'rxjs';
import { first, map, mergeMap, switchMap, tap } from 'rxjs/operators';
import { createType, ClassOf, Vec } from '@polkadot/types';
import { isBn, isFunction, isNumber, isUndefined } from '@polkadot/util';
import { filterEvents, isKeyringPair } from '../util';
import ApiBase from '../base';
import SignerPayload from '../SignerPayload';
import SubmittableResult from './Result';
type Creator<ApiType> = (extrinsic: Call | Uint8Array | string) => SubmittableExtrinsic<ApiType>;
interface SubmittableOptions<ApiType> {
api: ApiInterfaceRx;
decorateMethod: ApiBase<ApiType>['decorateMethod'];
type: ApiTypes;
}
// The default for 6s allowing for 5min eras. When translating this to faster blocks -
// - 4s = (10 / 15) * 5 = 3.33m
// - 2s = (10 / 30) * 5 = 1.66m
const BLOCKTIME = 6;
const ONE_MINUTE = 60 / BLOCKTIME;
const DEFAULT_MORTAL_LENGTH = 5 * ONE_MINUTE;
const _Extrinsic: Constructor<Extrinsic> = ClassOf('Extrinsic');
class Submittable<ApiType> extends _Extrinsic implements SubmittableExtrinsic<ApiType> {
private readonly _api: ApiInterfaceRx;
private readonly _decorateMethod: ApiBase<ApiType>['decorateMethod'];
private readonly _ignoreStatusCb: boolean;
public constructor (extrinsic: Call | Uint8Array | string, { api, decorateMethod, type }: SubmittableOptions<ApiType>) {
super(extrinsic, { version: api.extrinsicType });
this._api = api;
this._decorateMethod = decorateMethod;
this._ignoreStatusCb = type === 'rxjs';
}
// sign a transaction, returning the this to allow cianing, i.e. .sign(...).send()
public sign (account: IKeyringPair, optionsOrNonce: Partial<SignerOptions>): this {
// NOTE here we actually override nonce if it was specified (backwards compat for
// the previous signature - don't let userspace break, but allow then time to upgrade)
const options: Partial<SignerOptions> = isBn(optionsOrNonce) || isNumber(optionsOrNonce)
? { nonce: optionsOrNonce }
: optionsOrNonce;
super.sign(account, this._makeSignOptions(options, {}));
return this;
}
// signAndSend with an immediate Hash result
public signAndSend(account: IKeyringPair | string | AccountId | Address, options?: Partial<SignerOptions>): SubmitableResultResult<ApiType>;
// signAndSend with a subscription, i.e. callback provided
public signAndSend(account: IKeyringPair | string | AccountId | Address, statusCb: Callback<SubmittableResultImpl>): SubmitableResultSubscription<ApiType>;
// signAndSend with options and a callback
public signAndSend(account: IKeyringPair | string | AccountId | Address, options: Partial<SignerOptions>, statusCb?: Callback<SubmittableResultImpl>): SubmitableResultSubscription<ApiType>;
// signAndSend implementation for all 3 cases above
public signAndSend (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial<SignerOptions> | Callback<SubmittableResultImpl>, optionalStatusCb?: Callback<SubmittableResultImpl>): SubmitableResultResult<ApiType> | SubmitableResultSubscription<ApiType> {
const [options, statusCb] = this._makeSignAndSendOptions(optionsOrStatus, optionalStatusCb);
const isSubscription = this._ignoreStatusCb || !!statusCb;
const address = isKeyringPair(account) ? account.address : account.toString();
let updateId: number | undefined;
return this._decorateMethod(
(): Observable<Codec> => (
this._getPrelimState(address, options).pipe(
first(),
mergeMap(async ([nonce, header]): Promise<void> => {
const eraOptions = this._makeEraOptions(options, { header, nonce });
if (isKeyringPair(account)) {
this.sign(account, eraOptions);
} else {
updateId = await this._signViaSigner(address, eraOptions, header);
}
}),
switchMap((): Observable<SubmittableResultImpl> | Observable<Hash> => {
return isSubscription
? this._subscribeObservable(updateId)
: this._sendObservable(updateId);
})
) as Observable<Codec>) // FIXME This is wrong, SubmittableResult is _not_ a codec
)(statusCb) as SubmitableResultResult<ApiType> | SubmitableResultSubscription<ApiType>;
}
// send with an immediate Hash result
public send (): SubmitableResultResult<ApiType>;
// send with a status callback
public send (statusCb: Callback<SubmittableResultImpl>): SubmitableResultSubscription<ApiType>;
// send implementation for both immediate Hash and statusCb variants
public send (statusCb?: Callback<SubmittableResultImpl>): SubmitableResultResult<ApiType> | SubmitableResultSubscription<ApiType> {
const isSubscription = this._ignoreStatusCb || !!statusCb;
return this._decorateMethod(
isSubscription
? this._subscribeObservable
: this._sendObservable
)(statusCb);
}
private _makeSignAndSendOptions (optionsOrStatus?: Partial<SignerOptions> | Callback<SubmittableResultImpl>, statusCb?: Callback<SubmittableResultImpl>): [Partial<SignerOptions>, Callback<SubmittableResultImpl>?] {
let options: Partial<SignerOptions> = {};
if (isFunction(optionsOrStatus)) {
statusCb = optionsOrStatus;
} else {
options = { ...optionsOrStatus };
}
return [options, statusCb];
}
private async _signViaSigner (address: string, eraOptions: SignatureOptions, header: Header | null): Promise<number> {
if (!this._api.signer) {
throw new Error('no signer attached');
}
const payload = new SignerPayload({
...eraOptions,
address,
method: this.method,
blockNumber: header ? header.number : 0
});
let result: SignerResult;
if (this._api.signer.signPayload) {
result = await this._api.signer.signPayload(payload.toPayload());
} else if (this._api.signer.signRaw) {
result = await this._api.signer.signRaw(payload.toRaw());
} else {
throw new Error('Invalid signer interface, it should implement either signPayload or signRaw (or both)');
}
// Here we explicitly call `toPayload()` again instead of working with an object
// (reference) as passed to the signer. This means that we are sure that the
// payload data is not modified from our inputs, but the signer
super.addSignature(address, result.signature, payload.toPayload());
return result.id;
}
private _makeSignOptions (options: Partial<SignerOptions>, extras: { blockHash?: Hash; era?: ExtrinsicEra; nonce?: Index }): SignatureOptions {
return {
blockHash: this._api.genesisHash,
...options,
...extras,
genesisHash: this._api.genesisHash,
runtimeVersion: this._api.runtimeVersion,
version: this._api.extrinsicType
} as unknown as SignatureOptions;
}
private _makeEraOptions (options: Partial<SignerOptions>, { header, nonce }: { header: Header | null; nonce: Index }): SignatureOptions {
if (!header) {
if (isNumber(options.era)) {
// since we have no header, it is immortal, remove any option overrides
// so we only supply the genesisHash and no era to the construction
delete options.era;
delete options.blockHash;
}
return this._makeSignOptions(options, { nonce });
}
return this._makeSignOptions(options, {
blockHash: header.hash,
era: createType('ExtrinsicEra', {
current: header.number,
period: options.era || DEFAULT_MORTAL_LENGTH
}),
nonce
});
}
private _getPrelimState (address: string, options: Partial<SignerOptions>): Observable<[Index, Header | null]> {
return combineLatest([
// if we have a nonce already, don't retrieve the latest, use what is there
isUndefined(options.nonce)
? this._api.query.system.accountNonce<Index>(address)
: of(createType('Index', options.nonce)),
// if we have an era provided already or eraLength is <= 0 (immortal)
// don't get the latest block, just pass null, handle in mergeMap
(isUndefined(options.era) || (isNumber(options.era) && options.era > 0))
? this._api.rpc.chain.getHeader()
: of(null)
]);
}
private _updateSigner (updateId: number, status: Hash | SubmittableResultImpl): void {
if ((updateId !== -1) && this._api.signer && this._api.signer.update) {
this._api.signer.update(updateId, status);
}
}
private _statusObservable (status: ExtrinsicStatus): Observable<SubmittableResultImpl> {
if (!status.isFinalized) {
return of(new SubmittableResult({ status }));
}
const blockHash = status.asFinalized;
return combineLatest([
this._api.rpc.chain.getBlock(blockHash),
this._api.query.system.events.at(blockHash) as Observable<Vec<EventRecord>>
]).pipe(
map(([signedBlock, allEvents]): SubmittableResultImpl =>
new SubmittableResult({
events: filterEvents(this.hash, signedBlock, allEvents),
status
})
)
);
}
private _sendObservable (updateId: number = -1): Observable<Hash> {
return this._api.rpc.author
.submitExtrinsic(this)
.pipe(
tap((hash): void => {
this._updateSigner(updateId, hash);
})
);
}
private _subscribeObservable (updateId: number = -1): Observable<SubmittableResultImpl> {
return this._api.rpc.author
.submitAndWatchExtrinsic(this)
.pipe(
switchMap((status): Observable<SubmittableResultImpl> =>
this._statusObservable(status)
),
tap((status): void => {
this._updateSigner(updateId, status);
})
);
}
}
export default function createSubmittableExtrinsic<ApiType> (type: ApiTypes, api: ApiInterfaceRx, decorateMethod: ApiBase<ApiType>['decorateMethod']): Creator<ApiType> {
return (extrinsic: Call | Uint8Array | string): SubmittableExtrinsic<ApiType> =>
new Submittable(extrinsic, { api, decorateMethod, type });
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2017-2019 @polkadot/api 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 { ExtrinsicStatus, EventRecord } from '@polkadot/types/interfaces';
import { SubmittableResultImpl, SubmittableResultValue } from './types';
export default class SubmittableResult implements SubmittableResultImpl {
public readonly events: EventRecord[];
public readonly status: ExtrinsicStatus;
public constructor ({ events, status }: SubmittableResultValue) {
this.events = events || [];
this.status = status;
}
public get isCompleted (): boolean {
return this.isError || this.isFinalized;
}
public get isError (): boolean {
return this.status.isDropped || this.status.isInvalid || this.status.isUsurped;
}
public get isFinalized (): boolean {
return this.status.isFinalized;
}
/**
* @description Finds an EventRecord for the specified method & section
*/
public findRecord (section: string, method: string): EventRecord | undefined {
return this.events.find(({ event }): boolean =>
event.section === section && event.method === method
);
}
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2019 @polkadot/api 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 as createSubmittable } from './Extrinsic';
export { default as SubmittableResult } from './Result';
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2017-2019 @polkadot/api 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 { AccountId, Address, ExtrinsicStatus, EventRecord, Hash } from '@polkadot/types/interfaces';
import { AnyNumber, AnyU8a, Callback, IExtrinsic, IExtrinsicEra, IKeyringPair, SignatureOptions } from '@polkadot/types/types';
import { Observable } from 'rxjs';
export interface SubmittableResultImpl {
readonly events: EventRecord[];
readonly status: ExtrinsicStatus;
readonly isCompleted: boolean;
readonly isError: boolean;
readonly isFinalized: boolean;
findRecord(section: string, method: string): EventRecord | undefined;
}
export interface SubmittableResultValue {
events?: EventRecord[];
status: ExtrinsicStatus;
}
export type SubmitableResultResult<ApiType> =
ApiType extends 'rxjs'
? Observable<SubmittableResultImpl>
: Promise<Hash>;
export type SubmitableResultSubscription<ApiType> =
ApiType extends 'rxjs'
? Observable<SubmittableResultImpl>
: Promise<() => void>;
export interface SignerOptions {
blockHash: AnyU8a;
era?: IExtrinsicEra | number;
nonce: AnyNumber;
tip?: AnyNumber;
}
export interface SubmittableExtrinsic<ApiType> extends IExtrinsic {
send(): SubmitableResultResult<ApiType>;
send(statusCb: Callback<SubmittableResultImpl>): SubmitableResultSubscription<ApiType>;
sign(account: IKeyringPair, _options: Partial<SignatureOptions>): this;
signAndSend(account: IKeyringPair | string | AccountId | Address, options?: Partial<SignerOptions>): SubmitableResultResult<ApiType>;
signAndSend(account: IKeyringPair | string | AccountId | Address, statusCb: Callback<SubmittableResultImpl>): SubmitableResultSubscription<ApiType>;
signAndSend(account: IKeyringPair | string | AccountId | Address, options: Partial<SignerOptions>, statusCb?: Callback<SubmittableResultImpl>): SubmitableResultSubscription<ApiType>;
}
+5 -9
View File
@@ -3,7 +3,8 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { Hash, RuntimeVersion } from '@polkadot/types/interfaces';
import { AnyFunction, Callback, CallFunction, Codec, CodecArg, IExtrinsic, RegistryTypes, SignatureOptions } from '@polkadot/types/types';
import { AnyFunction, Callback, CallFunction, Codec, CodecArg, RegistryTypes, SignatureOptions } from '@polkadot/types/types';
import { SubmittableResultImpl, SubmittableExtrinsic } from './submittable/types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
@@ -15,7 +16,8 @@ import { Metadata, u64 } from '@polkadot/types';
import { StorageEntry } from '@polkadot/types/primitive/StorageKey';
import ApiBase from './base';
import { ISubmittableResult, SubmittableExtrinsic } from './SubmittableExtrinsic';
export * from './submittable/types';
// Prepend an element V onto the beginning of a tuple T.
// Cons<1, [2,3,4]> is [1,2,3,4]
@@ -299,12 +301,6 @@ export interface SignerResult {
}
export interface Signer {
/**
* @deprecated Implement and use signPayload and/or signRaw instead
* @description Signs an extrinsic, returning an id (>0) that can be used to retrieve updates
*/
sign?: (extrinsic: IExtrinsic, address: string, options: SignerOptions) => Promise<number>;
/**
* @description signs an extrinsic payload from a serialized form
*/
@@ -318,5 +314,5 @@ export interface Signer {
/**
* @description Receives an update for the extrinsic signed by a `signer.sign`
*/
update?: (id: number, status: Hash | ISubmittableResult) => void;
update?: (id: number, status: Hash | SubmittableResultImpl) => void;
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2019 @polkadot/api 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 * from './decorate';
export { default as filterEvents } from './filterEvents';
export { default as isKeyringPair } from './isKeyringPair';
export { default as l } from './logging';
+12
View File
@@ -0,0 +1,12 @@
// Copyright 2017-2019 @polkadot/api 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 { AccountId, Address } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { isFunction } from '@polkadot/util';
export default function isKeyringPair (account: string | IKeyringPair | AccountId | Address): account is IKeyringPair {
return isFunction((account as IKeyringPair).sign);
}
@@ -31,7 +31,7 @@ const VERSIONS: InterfaceTypes[] = [
'ExtrinsicUnknown', // v0 is unknown
'ExtrinsicV1',
'ExtrinsicV2',
'ExtrinsicV2'
'ExtrinsicV3'
];
const VERSION_UNKNOWN = VERSIONS[0];