@@ -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.
|
||||
*
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user