add Signer to api (#662)

* add Signer to api

* change as commented
This commit is contained in:
Ian He
2019-02-09 07:15:54 +02:00
committed by Jaco Greeff
parent 66cf11d2ef
commit c1672e8eee
5 changed files with 110 additions and 24 deletions
+9 -1
View File
@@ -13,7 +13,7 @@ import {
HashResult, U64Result,
OnCallDefinition, OnCallFunction,
QueryableModuleStorage, QueryableStorage, QueryableStorageFunction,
SubmittableExtrinsicFunction, SubmittableExtrinsics, SubmittableModuleExtrinsics
SubmittableExtrinsicFunction, SubmittableExtrinsics, SubmittableModuleExtrinsics, Signer
} from './types';
import EventEmitter from 'eventemitter3';
@@ -107,6 +107,7 @@ export default abstract class ApiBase<CodecResult, SubscriptionResult> implement
this._rpcRx = new RpcRx(thisProvider);
this._rpc = this.decorateRpc(this._rpcRx, this.onCall);
this._rx.rpc = this.decorateRpc(this._rpcRx, rxOnCall);
this._rx.signer = options.signer;
// we only re-register the types (global) if this is not a cloned instance
if (!options.source && options.types) {
@@ -157,6 +158,13 @@ export default abstract class ApiBase<CodecResult, SubscriptionResult> implement
return this._type;
}
/**
* @description Set an external signer which will be used to sign extrinsic when account passed in is not KeyringPair
*/
setSigner (signer: Signer) {
this._rx.signer = signer;
}
/**
* @description Derived results that are injected into the API, allowing for combinations of various query results.
*
+34 -22
View File
@@ -4,13 +4,13 @@
import { KeyringPair } from '@polkadot/keyring/types';
import { Struct, Vector } from '@polkadot/types/codec';
import { Extrinsic, ExtrinsicStatus, EventRecord, Hash, Index, Method, RuntimeVersion, SignedBlock, Text } from '@polkadot/types/index';
import { AccountId, Address, Extrinsic, ExtrinsicStatus, EventRecord, Hash, Index, Method, RuntimeVersion, SignedBlock, Text } from '@polkadot/types/index';
import { AnyNumber, AnyU8a, CodecCallback } from '@polkadot/types/types';
import { ApiInterface$Rx, ApiType, OnCallDefinition } from './types';
import { ApiInterface$Rx, ApiType, OnCallDefinition, Signer } from './types';
import { Observable, of, combineLatest } from 'rxjs';
import { first, map, switchMap } from 'rxjs/operators';
import { isBn, isFunction, isNumber, isUndefined } from '@polkadot/util';
import { first, map, mergeMap, switchMap } from 'rxjs/operators';
import { isBn, isFunction, isNumber, isUndefined, assert } from '@polkadot/util';
import filterEvents from './util/filterEvents';
@@ -151,9 +151,9 @@ export default class SubmittableExtrinsic<CodecResult, SubscriptionResult> exten
return this;
}
signAndSend (account: KeyringPair, options?: Partial<SignatureOptionsPartial>): SumbitableResultResult<CodecResult, SubscriptionResult>;
signAndSend (account: KeyringPair, statusCb: StatusCb): SumbitableResultSubscription<CodecResult, SubscriptionResult>;
signAndSend (account: KeyringPair, _options?: Partial<SignatureOptionsPartial> | StatusCb, statusCb?: StatusCb): SumbitableResultResult<CodecResult, SubscriptionResult> | SumbitableResultSubscription<CodecResult, SubscriptionResult> {
signAndSend (account: KeyringPair | string | AccountId | Address, options?: Partial<SignatureOptionsPartial>): SumbitableResultResult<CodecResult, SubscriptionResult>;
signAndSend (account: KeyringPair | string | AccountId | Address, statusCb: StatusCb): SumbitableResultSubscription<CodecResult, SubscriptionResult>;
signAndSend (account: KeyringPair | string | AccountId | Address, _options?: Partial<SignatureOptionsPartial> | StatusCb, statusCb?: StatusCb): SumbitableResultResult<CodecResult, SubscriptionResult> | SumbitableResultSubscription<CodecResult, SubscriptionResult> {
let options: Partial<SignatureOptionsPartial> = {};
if (isFunction(_options)) {
@@ -163,25 +163,37 @@ export default class SubmittableExtrinsic<CodecResult, SubscriptionResult> exten
}
const isSubscription = this._noStatusCb || !!statusCb;
const isKeyringPair = isFunction((account as KeyringPair).address) && isFunction((account as KeyringPair).sign);
const address = isKeyringPair ? (account as KeyringPair).address() : account.toString();
return this._onCall(
() => (
isUndefined(options.nonce)
? this._api.query.system.accountNonce(account.address()) as Observable<Index>
? this._api.query.system.accountNonce(address) as Observable<Index>
: of(new Index(options.nonce))
).pipe(
first(),
switchMap((nonce) => {
this.sign(account, { ...options, nonce });
return isSubscription
? this.subscribeObservable()
: this.sendObservable() as any; // ???
})
),
[],
statusCb as CodecCallback,
isSubscription
).pipe(
first(),
mergeMap(async (nonce) => {
if (isKeyringPair) {
this.sign(account as KeyringPair, { ...options, nonce });
} else {
assert(this._api.signer, 'no signer exists');
await (this._api.signer as Signer).sign(this, address, {
blockHash: options.blockHash || this._api.genesisHash,
version: options.version || this._api.runtimeVersion,
era: options.era,
nonce
});
}
}),
switchMap(() => {
return isSubscription
? this.subscribeObservable()
: this.sendObservable() as any; // ???
})
),
[],
statusCb as CodecCallback,
isSubscription
) as unknown as SumbitableResultSubscription<CodecResult, SubscriptionResult>;
}
}
+11 -1
View File
@@ -5,7 +5,8 @@
import { Observable } from 'rxjs';
import { ProviderInterface } from '@polkadot/rpc-provider/types';
import { RpcRxInterface$Events } from '@polkadot/rpc-rx/types';
import { Hash, Metadata, RuntimeVersion, u64 as U64 } from '@polkadot/types/index';
import { Hash, Metadata, RuntimeVersion, u64 as U64, Extrinsic } from '@polkadot/types/index';
import { SignatureOptions } from '@polkadot/types/ExtrinsicSignature';
import { CodecArg, CodecCallback, Constructor } from '@polkadot/types/types';
import { MethodFunction } from '@polkadot/types/Method';
import { StorageFunction } from '@polkadot/types/StorageKey';
@@ -109,6 +110,10 @@ export interface ApiOptions {
* uses types not available in the base Substrate runtime.
*/
types?: { [name: string]: Constructor };
/**
* @description An external signer which will be used to sign extrinsic when account passed in is not KeyringPair
*/
signer?: Signer;
}
export interface ApiInterface$Decorated<CodecResult, SubscriptionResult> {
@@ -120,6 +125,7 @@ export interface ApiInterface$Decorated<CodecResult, SubscriptionResult> {
query: QueryableStorage<CodecResult, SubscriptionResult>;
rpc: DecoratedRpc<CodecResult, SubscriptionResult>;
tx: SubmittableExtrinsics<CodecResult, SubscriptionResult>;
signer?: Signer;
}
export type ApiInterface$Rx = ApiInterface$Decorated<RxResult, RxResult>;
@@ -134,3 +140,7 @@ export interface ApiBaseInterface<CodecResult, SubscriptionResult> extends Reado
on: (type: ApiInterface$Events, handler: (...args: Array<any>) => any) => this;
once: (type: ApiInterface$Events, handler: (...args: Array<any>) => any) => this;
}
export interface Signer {
sign (extrinsic: Extrinsic, address: string, opt: SignatureOptions): Promise<void>;
}
+43
View File
@@ -6,6 +6,7 @@ import testingPairs from '@polkadot/keyring/testingPairs';
import { randomAsHex } from '@polkadot/util-crypto';
import Api from '../../src/promise';
import SingleAccountSigner from "../util/SingleAccountSigner";
const keyring = testingPairs();
@@ -94,6 +95,48 @@ describe.skip('e2e transactions', () => {
});
});
it('makes a transfer (signAndSend via Signer)', async (done) => {
const signer = new SingleAccountSigner(keyring.dave);
api.setSigner(signer);
return api.tx.balances
.transfer('12ghjsRJpeJpUQaCQeHcBv9pRQA3tdcMxeL8cVk9JHWJGHjd', 12345)
.signAndSend(keyring.dave.address(), ({ events, status, type }) => {
console.log('Transaction status:', type);
if (type === 'Finalised') {
console.log('Completed at block hash', status.value.toHex());
console.log('Events:');
events.forEach(({ phase, event: { data, method, section } }) => {
console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString());
});
if (events.length) {
done();
}
}
});
});
it('makes a transfer (signAndSend via Signer) - sad path', async () => {
//no signer
api.setSigner();
await expect(api.tx.balances
.transfer('12ghjsRJpeJpUQaCQeHcBv9pRQA3tdcMxeL8cVk9JHWJGHjd', 12345)
.signAndSend(keyring.alice.address())).rejects.toThrow('no signer exists');
const signer = new SingleAccountSigner(keyring.dave);
api.setSigner(signer);
//no callback
await expect(api.tx.balances
.transfer('12ghjsRJpeJpUQaCQeHcBv9pRQA3tdcMxeL8cVk9JHWJGHjd', 12345)
.signAndSend(keyring.alice.address())).rejects.toThrow('does not have the keyringPair');
//with callback
await expect(api.tx.balances
.transfer('12ghjsRJpeJpUQaCQeHcBv9pRQA3tdcMxeL8cVk9JHWJGHjd', 12345)
.signAndSend(keyring.alice.address(), ({ events, status, type }) => {
})).rejects.toThrow('does not have the keyringPair');
});
it('makes a transfer (no callback)', async () => {
const hash = await api.tx.balances
.transfer('12ghjsRJpeJpUQaCQeHcBv9pRQA3tdcMxeL8cVk9JHWJGHjd', 12345)
@@ -0,0 +1,13 @@
export default class SingleAccountSigner{
constructor(keyringPair){
this.keyringPair = keyringPair;
}
async sign(extrinsic, address, opt) {
if(!this.keyringPair || String(address) !== this.keyringPair.address()){
throw new Error('does not have the keyringPair');
}
extrinsic.sign(this.keyringPair, opt);
}
}