Pull in vuepress foir docs (as opposed to gitbook) (#742)
* Pull in latest @polkadot/dev, cleanup existing *.html * Cleanups (old gitbook interfaces) * Bump, WIP * Ready for testing * Update examples * Include source * Cleanup links * Adjust links * Add typedoc exclusions * Default classes from class files * Adjust imports * Adjust config * Re-add types split * Update links to substrate interfaces * Anchors for metadata * Adjust links * Restore HTML (to be deleted on CI) * Bump deps * Re-gen yarn.lock * Remove a in links * Adjust interfaces
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
# @polkadot/api-derive
|
||||
|
||||
Common functions used across Polkadot, derived from RPC calls and storage queries.
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Observable, from } from 'rxjs';
|
||||
import ApiRx from '@polkadot/api/rx';
|
||||
import ApiRx from '@polkadot/api/rx/Api';
|
||||
import { ApiInterface$Rx } from '@polkadot/api/types';
|
||||
import MockProvider from '@polkadot/rpc-provider/mock';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import BN from 'bn.js';
|
||||
import ApiRx from '@polkadot/api/rx';
|
||||
import ApiRx from '@polkadot/api/rx/Api';
|
||||
import { ApiInterface$Rx } from '@polkadot/api/types';
|
||||
import { BlockNumber } from '@polkadot/types/index';
|
||||
import { WsProvider } from '@polkadot/rpc-provider/index';
|
||||
|
||||
@@ -91,3 +91,8 @@ Some of the users of the API (let us know if you are missing from the list), inc
|
||||
|
||||
- [Polkadot-JS UI](https://github.com/polkadot-js/apps) A user-interface that allows you to make transactions, query the network or participate in actions on the network such as referendums and staking
|
||||
- [Polkabot](https://gitlab.com/Polkabot) Polkabot is a Matrix chatbot that keeps an eye on the Polkadot network. You can see Polkabot in action in https://matrix.to/#/#polkadot-network-status:matrix.org
|
||||
- [Polkawallet.io](https://polkawallet.io) and [Polkawallet (Github)](https://github.com/polkawallet-io/polkawallet-RN/) A mobile wallet for the Polkadot network to manage funds and make transactions, available on both Androind and iOS
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"@polkadot/rpc-rx": "^0.45.14",
|
||||
"@polkadot/storage": "^0.45.14",
|
||||
"@polkadot/types": "^0.45.14",
|
||||
"@polkadot/util-crypto": "^0.34.24"
|
||||
"@polkadot/util-crypto": "^0.34.27"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
// 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 { ProviderInterface } from '@polkadot/rpc-provider/types';
|
||||
import { CodecArg, CodecCallback } from '@polkadot/types/types';
|
||||
import { RxResult } from '../rx/types';
|
||||
import { ApiOptions, OnCallFunction } from '../types';
|
||||
import { ApiPromiseInterface, CodecResult, SubscriptionResult } from './types';
|
||||
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { catchError, first, tap } from 'rxjs/operators';
|
||||
import { isFunction, assert } from '@polkadot/util';
|
||||
|
||||
import ApiBase from '../Base';
|
||||
import Combinator, { CombinatorCallback, CombinatorFunction } from './Combinator';
|
||||
|
||||
/**
|
||||
* # @polkadot/api/promise
|
||||
*
|
||||
* ## Overview
|
||||
*
|
||||
* @name ApiPromise
|
||||
*
|
||||
* @description
|
||||
* ApiPromise is a standard JavaScript wrapper around the RPC and interfaces on the Polkadot network. As a full Promise-based, all interface calls return Promises, including the static `.create(...)`. Subscription calls utilise `(value) => {}` callbacks to pass through the latest values.
|
||||
*
|
||||
* The API is well suited to real-time applications where either the single-shot state is needed or use is to be made of the subscription-based features of Polkadot (and Substrate) clients.
|
||||
*
|
||||
* @see [[ApiRx]]
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* Making rpc calls -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import ApiPromise from '@polkadot/api/promise';
|
||||
*
|
||||
* // initialise via static create
|
||||
* const api = await ApiPromise.create();
|
||||
*
|
||||
* // make a subscription to the network head
|
||||
* api.rpc.chain.subscribeNewHead((header) => {
|
||||
* console.log(`Chain is at #${header.blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Subscribing to chain state -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ApiPromise, WsProvider } from '@polkadot/api';
|
||||
*
|
||||
* // initialise a provider with a specific endpoint
|
||||
* const provider = new WsProvider('wss://example.com:9944')
|
||||
*
|
||||
* // initialise via isReady & new with specific provider
|
||||
* const api = await new ApiPromise(provider).isReady;
|
||||
*
|
||||
* // retrieve the block target time
|
||||
* const blockPeriod = await api.query.timestamp.blockPeriod().toNumber();
|
||||
* let last = 0;
|
||||
*
|
||||
* // subscribe to the current block timestamp, updates automatically (callback provided)
|
||||
* api.query.timestamp.now((timestamp) => {
|
||||
* const elapsed = last
|
||||
* ? `, ${timestamp.toNumber() - last}s since last`
|
||||
* : '';
|
||||
*
|
||||
* last = timestamp.toNumber();
|
||||
* console.log(`timestamp ${timestamp}${elapsed} (${blockPeriod}s target)`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Submitting a transaction -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import ApiPromise from '@polkadot/api/promise';
|
||||
*
|
||||
* ApiPromise.create().then((api) => {
|
||||
* const nonce = await api.query.system.accountNonce(keyring.alice.address());
|
||||
*
|
||||
* api.tx.balances
|
||||
* // create transfer
|
||||
* transfer(keyring.bob.address(), 12345)
|
||||
* // sign the transcation
|
||||
* .sign(keyring.alice, { nonce })
|
||||
* // send the transaction (optional status callback)
|
||||
* .send((status) => {
|
||||
* console.log(`current status ${status.type}`);
|
||||
* })
|
||||
* // retrieve the submitted extrinsic hash
|
||||
* .then((hash) => {
|
||||
* console.log(`submitted with hash ${hash}`);
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export default class ApiPromise extends ApiBase<CodecResult, SubscriptionResult> implements ApiPromiseInterface {
|
||||
private _isReadyPromise: Promise<ApiPromise>;
|
||||
|
||||
/**
|
||||
* @description Creates an ApiPromise instance using the supplied provider. Returns an Promise containing the actual Api instance.
|
||||
*
|
||||
* @param options options that is passed to the class contructor. Can be either [[ApiOptions]] or a
|
||||
* provider (see the constructor arguments)
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/api/promise';
|
||||
*
|
||||
* Api.create().then(async (api) => {
|
||||
* const timestamp = await api.query.timestamp.now();
|
||||
*
|
||||
* console.log(`lastest block timestamp ${timestamp}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
static create (options: ApiOptions | ProviderInterface = {}): Promise<ApiPromise> {
|
||||
return new ApiPromise(options).isReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates an instance of the ApiPromise class
|
||||
*
|
||||
* @param options Options to create an instance. This can be either [[ApiOptions]] or
|
||||
* an [[WsProvider]].
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/api/promise';
|
||||
*
|
||||
* new Api().isReady.then((api) => {
|
||||
* api.rpc.subscribeNewHead((header) => {
|
||||
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
constructor (options?: ApiOptions | ProviderInterface) {
|
||||
super(options, 'promise');
|
||||
|
||||
this._isReadyPromise = new Promise((resolveReady) =>
|
||||
super.once('ready', () =>
|
||||
resolveReady(this)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Promise that returns the first time we are connected and loaded
|
||||
*/
|
||||
get isReady (): Promise<ApiPromise> {
|
||||
return this._isReadyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a clone of this ApiPromise instance (new underlying provider connection)
|
||||
*/
|
||||
clone (): ApiPromise {
|
||||
return new ApiPromise({
|
||||
...this._options,
|
||||
source: this
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates a combinator that can be used to combine the latest results from multiple subscriptions
|
||||
* @param fns An array of function to combine, each in the form of `(cb: (value: void)) => void`
|
||||
* @param callback A callback that will return an Array of all the values this combinator has been applied to
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const address = '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFacT7';
|
||||
*
|
||||
* // combines values from balance & nonce as it updates
|
||||
* api.combineLatest([
|
||||
* api.rpc.chain.subscribeNewHead,
|
||||
* [api.query.balances.freeBalance, address],
|
||||
* (cb) => api.query.system.accountNonce(address, cb)
|
||||
* ], ([head, balance, nonce]) => {
|
||||
* console.log(`#${head.number}: You have ${balance} units, with ${nonce} transactions sent`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async combineLatest (fns: Array<CombinatorFunction | [CombinatorFunction, ...Array<any>]>, callback: CombinatorCallback): SubscriptionResult {
|
||||
const combinator = new Combinator(fns, callback);
|
||||
|
||||
return (): void => {
|
||||
combinator.unsubscribe();
|
||||
};
|
||||
}
|
||||
|
||||
protected onCall (method: OnCallFunction<RxResult, RxResult>, params: Array<CodecArg> = [], callback?: CodecCallback, needsCallback?: boolean): CodecResult | SubscriptionResult {
|
||||
// When we need a subscription, ensure that a valid callback is actually passed
|
||||
assert(!needsCallback || isFunction(callback), 'Expected a callback to be passed with subscriptions');
|
||||
|
||||
if (!callback) {
|
||||
return method(...params).pipe(first()).toPromise();
|
||||
}
|
||||
|
||||
// FIXME TSLint shouts that type assertion is unnecessary, but tsc shouts
|
||||
// when I remove it...
|
||||
// tslint:disable-next-line
|
||||
return new Promise((resolve, reject) => {
|
||||
let isCompleted = false;
|
||||
const subscription = method(...params)
|
||||
.pipe(
|
||||
// if we find an error (invalid params, etc), reject the promise
|
||||
catchError((error) => {
|
||||
if (!isCompleted) {
|
||||
isCompleted = true;
|
||||
|
||||
reject(error);
|
||||
}
|
||||
|
||||
// we don't want to continue, so empty observable it is
|
||||
return EMPTY;
|
||||
}),
|
||||
// upon the first result, resolve the with the unsub function
|
||||
tap(() => {
|
||||
if (!isCompleted) {
|
||||
isCompleted = true;
|
||||
|
||||
resolve(() => subscription.unsubscribe());
|
||||
}
|
||||
})
|
||||
)
|
||||
.subscribe(callback);
|
||||
}) as SubscriptionResult;
|
||||
}
|
||||
}
|
||||
@@ -2,242 +2,6 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ProviderInterface } from '@polkadot/rpc-provider/types';
|
||||
import { CodecArg, CodecCallback } from '@polkadot/types/types';
|
||||
import { RxResult } from '../rx/types';
|
||||
import { ApiOptions, OnCallFunction } from '../types';
|
||||
import { ApiPromiseInterface, CodecResult, SubscriptionResult } from './types';
|
||||
import { default as ApiPromise } from './Api';
|
||||
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { catchError, first, tap } from 'rxjs/operators';
|
||||
import { isFunction, assert } from '@polkadot/util';
|
||||
|
||||
import ApiBase from '../Base';
|
||||
import Combinator, { CombinatorCallback, CombinatorFunction } from './Combinator';
|
||||
|
||||
/**
|
||||
* # @polkadot/api/promise
|
||||
*
|
||||
* ## Overview
|
||||
*
|
||||
* @name ApiPromise
|
||||
*
|
||||
* @description
|
||||
* ApiPromise is a standard JavaScript wrapper around the RPC and interfaces on the Polkadot network. As a full Promise-based, all interface calls return Promises, including the static `.create(...)`. Subscription calls utilise `(value) => {}` callbacks to pass through the latest values.
|
||||
*
|
||||
* The API is well suited to real-time applications where either the single-shot state is needed or use is to be made of the subscription-based features of Polkadot (and Substrate) clients.
|
||||
*
|
||||
* @see [[ApiRx]]
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* Making rpc calls -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import ApiPromise from '@polkadot/api/promise';
|
||||
*
|
||||
* // initialise via static create
|
||||
* const api = await ApiPromise.create();
|
||||
*
|
||||
* // make a subscription to the network head
|
||||
* api.rpc.chain.subscribeNewHead((header) => {
|
||||
* console.log(`Chain is at #${header.blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Subscribing to chain state -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ApiPromise } from '@polkadot/api';
|
||||
* import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
*
|
||||
* // initialise a provider with a specific endpoint
|
||||
* const provider = new WsProvider('wss://example.com:9944')
|
||||
*
|
||||
* // initialise via isReady & new with specific provider
|
||||
* const api = await new ApiPromise(provider).isReady;
|
||||
*
|
||||
* // retrieve the block target time
|
||||
* const blockPeriod = await api.query.timestamp.blockPeriod().toNumber();
|
||||
* let last = 0;
|
||||
*
|
||||
* // subscribe to the current block timestamp, updates automatically (callback provided)
|
||||
* api.query.timestamp.now((timestamp) => {
|
||||
* const elapsed = last
|
||||
* ? `, ${timestamp.toNumber() - last}s since last`
|
||||
* : '';
|
||||
*
|
||||
* last = timestamp.toNumber();
|
||||
* console.log(`timestamp ${timestamp}${elapsed} (${blockPeriod}s target)`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Submitting a transaction -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import ApiPromise from '@polkadot/api/promise';
|
||||
*
|
||||
* ApiPromise.create().then((api) => {
|
||||
* const nonce = await api.query.system.accountNonce(keyring.alice.address());
|
||||
*
|
||||
* api.tx.balances
|
||||
* // create transfer
|
||||
* transfer(keyring.bob.address(), 12345)
|
||||
* // sign the transcation
|
||||
* .sign(keyring.alice, { nonce })
|
||||
* // send the transaction (optional status callback)
|
||||
* .send((status) => {
|
||||
* console.log(`current status ${status.type}`);
|
||||
* })
|
||||
* // retrieve the submitted extrinsic hash
|
||||
* .then((hash) => {
|
||||
* console.log(`submitted with hash ${hash}`);
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export default class ApiPromise extends ApiBase<CodecResult, SubscriptionResult> implements ApiPromiseInterface {
|
||||
private _isReadyPromise: Promise<ApiPromise>;
|
||||
|
||||
/**
|
||||
* @description Creates an ApiPromise instance using the supplied provider. Returns an Promise containing the actual Api instance.
|
||||
*
|
||||
* @param options options that is passed to the class contructor. Can be either [[ApiOptions]] or a
|
||||
* provider (see the constructor arguments)
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/api/promise';
|
||||
*
|
||||
* Api.create().then(async (api) => {
|
||||
* const timestamp = await api.query.timestamp.now();
|
||||
*
|
||||
* console.log(`lastest block timestamp ${timestamp}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
static create (options: ApiOptions | ProviderInterface = {}): Promise<ApiPromise> {
|
||||
return new ApiPromise(options).isReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates an instance of the ApiPromise class
|
||||
*
|
||||
* @param options Options to create an instance. This can be either [[ApiOptions]] or
|
||||
* an [[HttpProvider]] or [[WsProvider]]. In the case of [[HttpProvider]] subscriptions
|
||||
* are not supported, only latest values are returned.
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/api/promise';
|
||||
*
|
||||
* new Api().isReady.then((api) => {
|
||||
* api.rpc.subscribeNewHead((header) => {
|
||||
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||
* });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
constructor (options?: ApiOptions | ProviderInterface) {
|
||||
super(options, 'promise');
|
||||
|
||||
this._isReadyPromise = new Promise((resolveReady) =>
|
||||
super.once('ready', () =>
|
||||
resolveReady(this)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Promise that returns the first time we are connected and loaded
|
||||
*/
|
||||
get isReady (): Promise<ApiPromise> {
|
||||
return this._isReadyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a clone of this ApiPromise instance (new underlying provider connection)
|
||||
*/
|
||||
clone (): ApiPromise {
|
||||
return new ApiPromise({
|
||||
...this._options,
|
||||
source: this
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates a combinator that can be used to combine the latest results from multiple subscriptions
|
||||
* @param fns An array of function to combine, each in the form of `(cb: (value: void)) => void`
|
||||
* @param callback A callback that will return an Array of all the values this combinator has been applied to
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const address = '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFacT7';
|
||||
*
|
||||
* // combines values from balance & nonce as it updates
|
||||
* api.combineLatest([
|
||||
* api.rpc.chain.subscribeNewHead,
|
||||
* [api.query.balances.freeBalance, address],
|
||||
* (cb) => api.query.system.accountNonce(address, cb)
|
||||
* ], ([head, balance, nonce]) => {
|
||||
* console.log(`#${head.number}: You have ${balance} units, with ${nonce} transactions sent`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async combineLatest (fns: Array<CombinatorFunction | [CombinatorFunction, ...Array<any>]>, callback: CombinatorCallback): SubscriptionResult {
|
||||
const combinator = new Combinator(fns, callback);
|
||||
|
||||
return (): void => {
|
||||
combinator.unsubscribe();
|
||||
};
|
||||
}
|
||||
|
||||
protected onCall (method: OnCallFunction<RxResult, RxResult>, params: Array<CodecArg> = [], callback?: CodecCallback, needsCallback?: boolean): CodecResult | SubscriptionResult {
|
||||
// When we need a subscription, ensure that a valid callback is actually passed
|
||||
assert(!needsCallback || isFunction(callback), 'Expected a callback to be passed with subscriptions');
|
||||
|
||||
if (!callback) {
|
||||
return method(...params).pipe(first()).toPromise();
|
||||
}
|
||||
|
||||
// FIXME TSLint shouts that type assertion is unnecessary, but tsc shouts
|
||||
// when I remove it...
|
||||
// tslint:disable-next-line
|
||||
return new Promise((resolve, reject) => {
|
||||
let isCompleted = false;
|
||||
const subscription = method(...params)
|
||||
.pipe(
|
||||
// if we find an error (invalid params, etc), reject the promise
|
||||
catchError((error) => {
|
||||
if (!isCompleted) {
|
||||
isCompleted = true;
|
||||
|
||||
reject(error);
|
||||
}
|
||||
|
||||
// we don't want to continue, so empty observable it is
|
||||
return EMPTY;
|
||||
}),
|
||||
// upon the first result, resolve the with the unsub function
|
||||
tap(() => {
|
||||
if (!isCompleted) {
|
||||
isCompleted = true;
|
||||
|
||||
resolve(() => subscription.unsubscribe());
|
||||
}
|
||||
})
|
||||
)
|
||||
.subscribe(callback);
|
||||
}) as SubscriptionResult;
|
||||
}
|
||||
}
|
||||
export default ApiPromise;
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
// 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 { ProviderInterface } from '@polkadot/rpc-provider/types';
|
||||
import { CodecArg } from '@polkadot/types/types';
|
||||
import { ApiRxInterface, RxResult } from './types';
|
||||
import { ApiOptions, OnCallFunction } from '../types';
|
||||
|
||||
import { Observable, from } from 'rxjs';
|
||||
|
||||
import ApiBase from '../Base';
|
||||
|
||||
/**
|
||||
* # @polkadot/api/rx
|
||||
*
|
||||
* ## Overview
|
||||
*
|
||||
* @name ApiRx
|
||||
*
|
||||
* @description
|
||||
* ApiRx is a powerfull RxJS Observable wrapper around the RPC and interfaces on the Polkadot network. As a full Observable API, all interface calls return RxJS Observables, including the static `.create(...)`. In the same fashion and subscription-based methods return long-running Observables that update with the latest values.
|
||||
*
|
||||
* The API is well suited to real-time applications where the latest state is needed, unlocking the subscription-based features of Polkadot (and Substrate) clients. Some familiarity with RxJS is a requirement to use the API, however just understanding `.subscribe` and `.pipe` on Observables will unlock full-scale use thereof.
|
||||
*
|
||||
* @see [[ApiPromise]]
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* Making rpc calls -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import ApiRx from '@polkadot/api/rx';
|
||||
*
|
||||
* // initialise via Promise & static create
|
||||
* const api = await ApiRx.create().toPromise();
|
||||
*
|
||||
* // make a call to retrieve the current network head
|
||||
* api.rpc.chain.subscribeNewHead().subscribe((header) => {
|
||||
* console.log(`Chain is at #${header.blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Subscribing to chain state -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { combineLatest } from 'rxjs';
|
||||
* import { pairwise, switchMap } from 'rxjs/operators';
|
||||
* import { ApiRx, WsProvider } from '@polkadot/api';
|
||||
*
|
||||
*
|
||||
* // initialise a provider with a specific endpoint
|
||||
* const provider = new WsProvider('wss://example.com:9944')
|
||||
*
|
||||
* // initialise via isReady & new with specific provider
|
||||
* new ApiRx(provider)
|
||||
* .isReady
|
||||
* .pipe(
|
||||
* switchMap((api) =>
|
||||
* combineLatest([
|
||||
* api.query.timestamp.blockPeriod(),
|
||||
* api.query.timestamp.now().pipe(pairwise())
|
||||
* ])
|
||||
* )
|
||||
* )
|
||||
* .subscribe(([blockPeriod, timestamp]) => {
|
||||
* const elapsed = timestamp[1].toNumber() - timestamp[0].toNumber();
|
||||
* console.log(`timestamp ${timestamp[1]} \nelapsed ${elapsed} \n(${blockPeriod}s target)`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Submitting a transaction -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { first, switchMap } from 'rxjs/operators';
|
||||
* import ApiRx from '@polkadot/api/rx';
|
||||
*
|
||||
* // import the test keyring (already has dev keys for Alice, Bob, Charlie, Eve & Ferdie)
|
||||
* import testingPairs from '@polkadot/keyring/testingPairs';
|
||||
* const keyring = testingPairs();
|
||||
*
|
||||
* // get api via Promise
|
||||
* const api = await ApiRx.create().toPromise();
|
||||
*
|
||||
* // retrieve nonce for the account
|
||||
* api.query.system
|
||||
* .accountNonce(keyring.alice.address())
|
||||
* .pipe(
|
||||
* first(),
|
||||
* // pipe nonce into transfer
|
||||
* switchMap((nonce) =>
|
||||
* api.tx.balances
|
||||
* // create transfer
|
||||
* .transfer(keyring.bob.address(), 12345)
|
||||
* // sign the transcation
|
||||
* .sign(keyring.alice, { nonce })
|
||||
* // send the transaction
|
||||
* .send()
|
||||
* )
|
||||
* )
|
||||
* // subscribe to overall result
|
||||
* .subscribe(({ status, type }) => {
|
||||
* if (type === 'Finalised') {
|
||||
* console.log('Completed at block hash', status.asFinalised.toHex());
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export default class ApiRx extends ApiBase<RxResult, RxResult> implements ApiRxInterface {
|
||||
private _isReadyRx: Observable<ApiRx>;
|
||||
|
||||
/**
|
||||
* @description Creates an ApiRx instance using the supplied provider. Returns an Observable containing the actual Api instance.
|
||||
*
|
||||
* @param options options that is passed to the class contructor. Can be either [[ApiOptions]] or [[WsProvider]]
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { switchMap } from 'rxjs/operators';
|
||||
* import Api from '@polkadot/api/rx';
|
||||
*
|
||||
* Api.create()
|
||||
* .pipe(
|
||||
* switchMap((api) =>
|
||||
* api.rpc.chain.subscribeNewHead()
|
||||
* ))
|
||||
* .subscribe((header) => {
|
||||
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
static create (options?: ApiOptions | ProviderInterface): Observable<ApiRx> {
|
||||
return new ApiRx(options).isReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Create an instance of the ApiRx class
|
||||
*
|
||||
* @param options Options to create an instance. Can be either [[ApiOptions]] or [[WsProvider]]
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { switchMap } from 'rxjs/operators';
|
||||
* import Api from '@polkadot/api/rx';
|
||||
*
|
||||
* new Api().isReady
|
||||
* .pipe(
|
||||
* switchMap((api) =>
|
||||
* api.rpc.chain.subscribeNewHead()
|
||||
* ))
|
||||
* .subscribe((header) => {
|
||||
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
constructor (provider?: ApiOptions | ProviderInterface) {
|
||||
super(provider, 'rxjs');
|
||||
|
||||
this._isReadyRx = from(
|
||||
// convinced you can observable from an event, however my mind groks this form better
|
||||
new Promise((resolveReady) =>
|
||||
super.on('ready', () =>
|
||||
resolveReady(this)
|
||||
)
|
||||
)
|
||||
) as Observable<ApiRx>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Observable that carries the connected state for the provider. Results in a boolean flag that is true/false based on the connectivity.
|
||||
*/
|
||||
get isConnected (): Observable<boolean> {
|
||||
return this._rpcRx.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Observable that returns the first time we are connected and loaded
|
||||
*/
|
||||
get isReady (): Observable<ApiRx> {
|
||||
return this._isReadyRx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a clone of this ApiRx instance (new underlying provider connection)
|
||||
*/
|
||||
clone (): ApiRx {
|
||||
return new ApiRx({
|
||||
...this._options,
|
||||
source: this
|
||||
});
|
||||
}
|
||||
|
||||
protected onCall (method: OnCallFunction<RxResult, RxResult>, params: Array<CodecArg> = []): RxResult {
|
||||
return method(...params);
|
||||
}
|
||||
}
|
||||
@@ -2,205 +2,6 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ProviderInterface } from '@polkadot/rpc-provider/types';
|
||||
import { CodecArg } from '@polkadot/types/types';
|
||||
import { ApiRxInterface, RxResult } from './types';
|
||||
import { ApiOptions, OnCallFunction } from '../types';
|
||||
import { default as ApiRx } from './Api';
|
||||
|
||||
import { Observable, from } from 'rxjs';
|
||||
|
||||
import ApiBase from '../Base';
|
||||
|
||||
/**
|
||||
* # @polkadot/api/rx
|
||||
*
|
||||
* ## Overview
|
||||
*
|
||||
* @name ApiRx
|
||||
*
|
||||
* @description
|
||||
* ApiRx is a powerfull RxJS Observable wrapper around the RPC and interfaces on the Polkadot network. As a full Observable API, all interface calls return RxJS Observables, including the static `.create(...)`. In the same fashion and subscription-based methods return long-running Observables that update with the latest values.
|
||||
*
|
||||
* The API is well suited to real-time applications where the latest state is needed, unlocking the subscription-based features of Polkadot (and Substrate) clients. Some familiarity with RxJS is a requirement to use the API, however just understanding `.subscribe` and `.pipe` on Observables will unlock full-scale use thereof.
|
||||
*
|
||||
* @see [[ApiPromise]]
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* Making rpc calls -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import ApiRx from '@polkadot/api/rx';
|
||||
*
|
||||
* // initialise via Promise & static create
|
||||
* const api = await ApiRx.create().toPromise();
|
||||
*
|
||||
* // make a call to retrieve the current network head
|
||||
* api.rpc.chain.subscribeNewHead().subscribe((header) => {
|
||||
* console.log(`Chain is at #${header.blockNumber}`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Subscribing to chain state -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { combineLatest } from 'rxjs';
|
||||
* import { pairwise, switchMap } from 'rxjs/operators';
|
||||
* import { ApiRx } from '@polkadot/api';
|
||||
* import { WsProvider } from '@polkadot/rpc-provider';
|
||||
*
|
||||
*
|
||||
* // initialise a provider with a specific endpoint
|
||||
* const provider = new WsProvider('wss://example.com:9944')
|
||||
*
|
||||
* // initialise via isReady & new with specific provider
|
||||
* new ApiRx(provider)
|
||||
* .isReady
|
||||
* .pipe(
|
||||
* switchMap((api) =>
|
||||
* combineLatest([
|
||||
* api.query.timestamp.blockPeriod(),
|
||||
* api.query.timestamp.now().pipe(pairwise())
|
||||
* ])
|
||||
* )
|
||||
* )
|
||||
* .subscribe(([blockPeriod, timestamp]) => {
|
||||
* const elapsed = timestamp[1].toNumber() - timestamp[0].toNumber();
|
||||
* console.log(`timestamp ${timestamp[1]} \nelapsed ${elapsed} \n(${blockPeriod}s target)`);
|
||||
* });
|
||||
* ```
|
||||
* <BR>
|
||||
*
|
||||
* Submitting a transaction -
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { first, switchMap } from 'rxjs/operators';
|
||||
* import ApiRx from '@polkadot/api/rx';
|
||||
*
|
||||
* // import the test keyring (already has dev keys for Alice, Bob, Charlie, Eve & Ferdie)
|
||||
* import testingPairs from '@polkadot/keyring/testingPairs';
|
||||
* const keyring = testingPairs();
|
||||
*
|
||||
* // get api via Promise
|
||||
* const api = await ApiRx.create().toPromise();
|
||||
*
|
||||
* // retrieve nonce for the account
|
||||
* api.query.system
|
||||
* .accountNonce(keyring.alice.address())
|
||||
* .pipe(
|
||||
* first(),
|
||||
* // pipe nonce into transfer
|
||||
* switchMap((nonce) =>
|
||||
* api.tx.balances
|
||||
* // create transfer
|
||||
* .transfer(keyring.bob.address(), 12345)
|
||||
* // sign the transcation
|
||||
* .sign(keyring.alice, { nonce })
|
||||
* // send the transaction
|
||||
* .send()
|
||||
* )
|
||||
* )
|
||||
* // subscribe to overall result
|
||||
* .subscribe(({ status, type }) => {
|
||||
* if (type === 'Finalised') {
|
||||
* console.log('Completed at block hash', status.asFinalised.toHex());
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export default class ApiRx extends ApiBase<RxResult, RxResult> implements ApiRxInterface {
|
||||
private _isReadyRx: Observable<ApiRx>;
|
||||
|
||||
/**
|
||||
* @description Creates an ApiRx instance using the supplied provider. Returns an Observable containing the actual Api instance.
|
||||
*
|
||||
* @param options options that is passed to the class contructor. Can be either [[ApiOptions]] or [[WsProvider]]
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { switchMap } from 'rxjs/operators';
|
||||
* import Api from '@polkadot/api/rx';
|
||||
*
|
||||
* Api.create()
|
||||
* .pipe(
|
||||
* switchMap((api) =>
|
||||
* api.rpc.chain.subscribeNewHead()
|
||||
* ))
|
||||
* .subscribe((header) => {
|
||||
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
static create (options?: ApiOptions | ProviderInterface): Observable<ApiRx> {
|
||||
return new ApiRx(options).isReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Create an instance of the ApiRx class
|
||||
*
|
||||
* @param options Options to create an instance. Can be either [[ApiOptions]] or [[WsProvider]]
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { switchMap } from 'rxjs/operators';
|
||||
* import Api from '@polkadot/api/rx';
|
||||
*
|
||||
* new Api().isReady
|
||||
* .pipe(
|
||||
* switchMap((api) =>
|
||||
* api.rpc.chain.subscribeNewHead()
|
||||
* ))
|
||||
* .subscribe((header) => {
|
||||
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
constructor (provider?: ApiOptions | ProviderInterface) {
|
||||
super(provider, 'rxjs');
|
||||
|
||||
this._isReadyRx = from(
|
||||
// convinced you can observable from an event, however my mind groks this form better
|
||||
new Promise((resolveReady) =>
|
||||
super.on('ready', () =>
|
||||
resolveReady(this)
|
||||
)
|
||||
)
|
||||
) as Observable<ApiRx>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Observable that carries the connected state for the provider. Results in a boolean flag that is true/false based on the connectivity.
|
||||
*/
|
||||
get isConnected (): Observable<boolean> {
|
||||
return this._rpcRx.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Observable that returns the first time we are connected and loaded
|
||||
*/
|
||||
get isReady (): Observable<ApiRx> {
|
||||
return this._isReadyRx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a clone of this ApiRx instance (new underlying provider connection)
|
||||
*/
|
||||
clone (): ApiRx {
|
||||
return new ApiRx({
|
||||
...this._options,
|
||||
source: this
|
||||
});
|
||||
}
|
||||
|
||||
protected onCall (method: OnCallFunction<RxResult, RxResult>, params: Array<CodecArg> = []): RxResult {
|
||||
return method(...params);
|
||||
}
|
||||
}
|
||||
export default ApiRx;
|
||||
|
||||
@@ -65,3 +65,7 @@ api.chain
|
||||
console.error('error subscribing:', error);
|
||||
});
|
||||
```
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -33,6 +33,6 @@
|
||||
"@polkadot/jsonrpc": "^0.45.14",
|
||||
"@polkadot/rpc-provider": "^0.45.14",
|
||||
"@polkadot/types": "^0.45.14",
|
||||
"@polkadot/util": "^0.34.24"
|
||||
"@polkadot/util": "^0.34.27"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { RpcSection, RpcMethod } from '@polkadot/jsonrpc/types';
|
||||
import { RpcInterface, RpcInterface$Method, RpcInterface$Section } from './types';
|
||||
|
||||
import interfaces from '@polkadot/jsonrpc/index';
|
||||
import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
import { WsProvider } from '@polkadot/rpc-provider/index';
|
||||
import { Codec } from '@polkadot/types/types';
|
||||
import { Option, StorageChangeSet, StorageKey, Vector, createClass, createType } from '@polkadot/types/index';
|
||||
import { ExtError, assert, isFunction, isNull, logger } from '@polkadot/util';
|
||||
|
||||
@@ -39,3 +39,7 @@ const version = await provider.send('chain_getBlockHash', []);
|
||||
|
||||
console.log('latest block Hash', hash);
|
||||
```
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/rpc-provider#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"@polkadot/keyring": "^0.34.24",
|
||||
"@polkadot/keyring": "^0.34.27",
|
||||
"@polkadot/storage": "^0.45.14",
|
||||
"@polkadot/util": "^0.34.24",
|
||||
"@polkadot/util-crypto": "^0.34.24",
|
||||
"@polkadot/util": "^0.34.27",
|
||||
"@polkadot/util-crypto": "^0.34.27",
|
||||
"@types/nock": "^9.3.1",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
// Copyright 2017-2019 @polkadot/rpc-provider 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 { JsonRpcResponse, ProviderInterface, ProviderInterface$Callback, ProviderInterface$Emitted, ProviderInterface$EmitCb } from '../types';
|
||||
|
||||
import './polyfill';
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { assert, isNull, isUndefined, logger } from '@polkadot/util';
|
||||
|
||||
import Coder from '../coder';
|
||||
import defaults from '../defaults';
|
||||
|
||||
type CallbackHandler = (error?: null | Error, value?: any) => void;
|
||||
|
||||
type SubscriptionHandler = {
|
||||
callback: CallbackHandler,
|
||||
type: string
|
||||
};
|
||||
|
||||
type WsState$Awaiting = {
|
||||
callback: CallbackHandler,
|
||||
method: string,
|
||||
params: Array<any>,
|
||||
subscription?: SubscriptionHandler
|
||||
};
|
||||
|
||||
type WsState$Subscription = SubscriptionHandler & {
|
||||
method: string,
|
||||
params: Array<any>
|
||||
};
|
||||
|
||||
interface WSProviderInterface extends ProviderInterface {
|
||||
connect (): void;
|
||||
}
|
||||
|
||||
const l = logger('api-ws');
|
||||
|
||||
/**
|
||||
* # @polkadot/rpc-provider/ws
|
||||
*
|
||||
* @name WsProvider
|
||||
*
|
||||
* @description The WebSocket Provider allows sending requests using WebSocket to a WebSocket RPC server TCP port. Unlike the [[HttpProvider]], it does support subscriptions and allows listening to events such as new blocks or balance changes.
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/api/promise';
|
||||
* import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
*
|
||||
* const provider = new WsProvider('ws://127.0.0.1:9944');
|
||||
* const api = new Api(provider);
|
||||
* ```
|
||||
*
|
||||
* @see [[HttpProvider]]
|
||||
*/
|
||||
export default class WsProvider implements WSProviderInterface {
|
||||
private _eventemitter: EventEmitter;
|
||||
private _isConnected: boolean = false;
|
||||
private autoConnect: boolean;
|
||||
private coder: Coder;
|
||||
private endpoint: string;
|
||||
private handlers: {
|
||||
[index: string]: WsState$Awaiting
|
||||
};
|
||||
private queued: {
|
||||
[index: string]: string
|
||||
};
|
||||
private subscriptions: {
|
||||
[index: string]: WsState$Subscription
|
||||
};
|
||||
private websocket: WebSocket | null;
|
||||
|
||||
/**
|
||||
* @param {string} endpoint The endpoint url. Usually `ws://ip:9944` or `wss://ip:9944`
|
||||
* @param {boolean} autoConnect Whether to connect automatically or not.
|
||||
*/
|
||||
constructor (endpoint: string = defaults.WS_URL, autoConnect: boolean = true) {
|
||||
assert(/^(wss|ws):\/\//.test(endpoint), `Endpoint should start with 'ws://', received '${endpoint}'`);
|
||||
|
||||
this._eventemitter = new EventEmitter();
|
||||
this.autoConnect = autoConnect;
|
||||
this.coder = new Coder();
|
||||
this.endpoint = endpoint;
|
||||
this.handlers = {};
|
||||
this.queued = {};
|
||||
this.subscriptions = {};
|
||||
this.websocket = null;
|
||||
|
||||
if (autoConnect) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary `true` when this provider supports subscriptions
|
||||
*/
|
||||
get hasSubscriptions (): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a clone of the object
|
||||
*/
|
||||
clone (): WsProvider {
|
||||
return new WsProvider(this.endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Manually connect
|
||||
* @description The [[WsProvider]] connects automatically by default, however if you decided otherwise, you may
|
||||
* connect manually using this method.
|
||||
*/
|
||||
connect (): void {
|
||||
try {
|
||||
this.websocket = new WebSocket(this.endpoint);
|
||||
|
||||
this.websocket.onclose = this.onSocketClose;
|
||||
this.websocket.onerror = this.onSocketError;
|
||||
this.websocket.onmessage = this.onSocketMessage;
|
||||
this.websocket.onopen = this.onSocketOpen;
|
||||
} catch (error) {
|
||||
l.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Manually disconnect from the connection, clearing autoconnect logic
|
||||
*/
|
||||
disconnect (): void {
|
||||
if (isNull(this.websocket)) {
|
||||
throw new Error('Cannot disconnect on a non-open websocket');
|
||||
}
|
||||
|
||||
// switch off autoConnect, we are in manual mode now
|
||||
this.autoConnect = false;
|
||||
|
||||
// 1000 - Normal closure; the connection successfully completed
|
||||
this.websocket.close(1000);
|
||||
this.websocket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Whether the node is connected or not.
|
||||
* @return {boolean} true if connected
|
||||
*/
|
||||
isConnected (): boolean {
|
||||
return this._isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Listens on events after having subscribed using the [[subscribe]] function.
|
||||
* @param {ProviderInterface$Emitted} type Event
|
||||
* @param {ProviderInterface$EmitCb} sub Callback
|
||||
*/
|
||||
on (type: ProviderInterface$Emitted, sub: ProviderInterface$EmitCb): void {
|
||||
this._eventemitter.on(type, sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Send JSON data using WebSockets to configured HTTP Endpoint or queue.
|
||||
* @param method The RPC methods to execute
|
||||
* @param params Encoded paramaters as appliucable for the method
|
||||
* @param subscription Subscription details (internally used)
|
||||
*/
|
||||
async send (method: string, params: Array<any>, subscription?: SubscriptionHandler): Promise<any> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
try {
|
||||
const json = this.coder.encodeJson(method, params);
|
||||
const id = this.coder.getId();
|
||||
const callback = (error?: Error | null, result?: any) => {
|
||||
error
|
||||
? reject(error)
|
||||
: resolve(result);
|
||||
};
|
||||
|
||||
l.debug(() => ['calling', method, params, json, !!subscription]);
|
||||
|
||||
this.handlers[id] = {
|
||||
callback,
|
||||
method,
|
||||
params,
|
||||
subscription
|
||||
};
|
||||
|
||||
if (this.isConnected() && !isNull(this.websocket)) {
|
||||
this.websocket.send(json);
|
||||
} else {
|
||||
this.queued[id] = json;
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name subscribe
|
||||
* @summary Allows subscribing to a specific event.
|
||||
* @param {string} type Subscription type
|
||||
* @param {string} method Subscription method
|
||||
* @param {Array<any>} params Parameters
|
||||
* @param {ProviderInterface$Callback} callback Callback
|
||||
* @return {Promise<number>} Promise resolving to the dd of the subscription you can use with [[unsubscribe]].
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const provider = new WsProvider('ws://127.0.0.1:9944');
|
||||
* const rpc = new Rpc(provider);
|
||||
*
|
||||
* rpc.state.subscribeStorage([[storage.balances.freeBalance, <Address>]], (_, values) => {
|
||||
* console.log(values)
|
||||
* }).then((subscriptionId) => {
|
||||
* console.log('balance changes subscription id: ', subscriptionId)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
async subscribe (type: string, method: string, params: Array<any>, callback: ProviderInterface$Callback): Promise<number> {
|
||||
const id = await this.send(method, params, { callback, type });
|
||||
|
||||
return id as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Allows unsubscribing to subscriptions made with [[subscribe]].
|
||||
*/
|
||||
async unsubscribe (type: string, method: string, id: number): Promise<boolean> {
|
||||
const subscription = `${type}::${id}`;
|
||||
|
||||
// FIXME This now could happen with re-subscriptions. The issue is that with a re-sub
|
||||
// the assigned id now does not match what the API user originally received. It has
|
||||
// a slight complication in solving - since we cannot rely on the send id, but rather
|
||||
// need to find the actual subscription id to map it
|
||||
if (isUndefined(this.subscriptions[subscription])) {
|
||||
l.debug(() => `Unable to find active subscription=${subscription}`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
delete this.subscriptions[subscription];
|
||||
|
||||
const result = await this.send(method, [id]);
|
||||
|
||||
return result as boolean;
|
||||
}
|
||||
|
||||
private emit (type: ProviderInterface$Emitted, ...args: Array<any>): void {
|
||||
this._eventemitter.emit(type, ...args);
|
||||
}
|
||||
|
||||
private onSocketClose = (event: CloseEvent): void => {
|
||||
if (this.autoConnect) {
|
||||
l.error(`disconnected from ${this.endpoint}::${event.code}: ${event.reason}`);
|
||||
}
|
||||
|
||||
this._isConnected = false;
|
||||
this.emit('disconnected');
|
||||
|
||||
if (this.autoConnect) {
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private onSocketError = (error: Event): void => {
|
||||
l.debug(() => ['socket error', error]);
|
||||
this.emit('error', error);
|
||||
}
|
||||
|
||||
private onSocketMessage = (message: MessageEvent): void => {
|
||||
l.debug(() => ['received', message.data]);
|
||||
|
||||
const response: JsonRpcResponse = JSON.parse(message.data as string);
|
||||
|
||||
return isUndefined(response.method)
|
||||
? this.onSocketMessageResult(response)
|
||||
: this.onSocketMessageSubscribe(response);
|
||||
}
|
||||
|
||||
private onSocketMessageResult = (response: JsonRpcResponse): void => {
|
||||
l.debug(() => ['handling: response =', response, 'id =', response.id]);
|
||||
|
||||
const handler = this.handlers[response.id];
|
||||
|
||||
if (!handler) {
|
||||
l.debug(() => `Unable to find handler for id=${response.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { method, params, subscription } = handler;
|
||||
const result = this.coder.decodeResponse(response);
|
||||
|
||||
if (subscription) {
|
||||
this.subscriptions[`${subscription.type}::${result}`] = {
|
||||
...subscription,
|
||||
method,
|
||||
params
|
||||
};
|
||||
}
|
||||
|
||||
handler.callback(null, result);
|
||||
} catch (error) {
|
||||
handler.callback(error, undefined);
|
||||
}
|
||||
|
||||
delete this.handlers[response.id];
|
||||
}
|
||||
|
||||
private onSocketMessageSubscribe = (response: JsonRpcResponse): void => {
|
||||
const subscription = `${response.method}::${response.params.subscription}`;
|
||||
|
||||
l.debug(() => ['handling: response =', response, 'subscription =', subscription]);
|
||||
|
||||
const handler = this.subscriptions[subscription];
|
||||
|
||||
if (!handler) {
|
||||
l.debug(() => `Unable to find handler for subscription=${subscription}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = this.coder.decodeResponse(response);
|
||||
|
||||
handler.callback(null, result);
|
||||
} catch (error) {
|
||||
handler.callback(error, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private onSocketOpen = (): boolean => {
|
||||
assert(!isNull(this.websocket), 'WebSocket cannot be null in onOpen');
|
||||
|
||||
l.debug(() => ['connected to', this.endpoint]);
|
||||
|
||||
this._isConnected = true;
|
||||
this.emit('connected');
|
||||
|
||||
this.sendQueue();
|
||||
this.resubscribe();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private resubscribe (): void {
|
||||
const subscriptions = this.subscriptions;
|
||||
|
||||
this.subscriptions = {};
|
||||
|
||||
Object.keys(subscriptions).forEach(async (id) => {
|
||||
const { callback, method, params, type } = subscriptions[id];
|
||||
|
||||
// only re-create subscriptions which are not in author (only area where
|
||||
// transactions are created, i.e. submissions such as 'author_submitAndWatchExtrinsic'
|
||||
// are not included (and will not be re-broadcast)
|
||||
if (type.startsWith('author_')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.subscribe(type, method, params, callback);
|
||||
} catch (error) {
|
||||
l.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sendQueue (): void {
|
||||
Object.keys(this.queued).forEach((id) => {
|
||||
try {
|
||||
// @ts-ignore we have done the websocket check in onSocketOpen, if an issue, will catch it
|
||||
this.websocket.send(
|
||||
this.queued[id]
|
||||
);
|
||||
|
||||
delete this.queued[id];
|
||||
} catch (error) {
|
||||
l.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,387 +2,6 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { JsonRpcResponse, ProviderInterface, ProviderInterface$Callback, ProviderInterface$Emitted, ProviderInterface$EmitCb } from '../types';
|
||||
import { default as WsProvider } from './Provider';
|
||||
|
||||
import './polyfill';
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { assert, isNull, isUndefined, logger } from '@polkadot/util';
|
||||
|
||||
import Coder from '../coder';
|
||||
import defaults from '../defaults';
|
||||
|
||||
type CallbackHandler = (error?: null | Error, value?: any) => void;
|
||||
|
||||
type SubscriptionHandler = {
|
||||
callback: CallbackHandler,
|
||||
type: string
|
||||
};
|
||||
|
||||
type WsState$Awaiting = {
|
||||
callback: CallbackHandler,
|
||||
method: string,
|
||||
params: Array<any>,
|
||||
subscription?: SubscriptionHandler
|
||||
};
|
||||
|
||||
type WsState$Subscription = SubscriptionHandler & {
|
||||
method: string,
|
||||
params: Array<any>
|
||||
};
|
||||
|
||||
interface WSProviderInterface extends ProviderInterface {
|
||||
connect (): void;
|
||||
}
|
||||
|
||||
const l = logger('api-ws');
|
||||
|
||||
/**
|
||||
* # @polkadot/rpc-provider/ws
|
||||
*
|
||||
* @name WsProvider
|
||||
*
|
||||
* @description The WebSocket Provider allows sending requests using WebSocket to a WebSocket RPC server TCP port. Unlike the [[HttpProvider]], it does support subscriptions and allows listening to events such as new blocks or balance changes.
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/api/promise';
|
||||
* import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
*
|
||||
* const provider = new WsProvider('ws://127.0.0.1:9944');
|
||||
* const api = new Api(provider);
|
||||
* ```
|
||||
*
|
||||
* @see [[HttpProvider]]
|
||||
*/
|
||||
export default class WsProvider implements WSProviderInterface {
|
||||
private _eventemitter: EventEmitter;
|
||||
private _isConnected: boolean = false;
|
||||
private autoConnect: boolean;
|
||||
private coder: Coder;
|
||||
private endpoint: string;
|
||||
private handlers: {
|
||||
[index: string]: WsState$Awaiting
|
||||
};
|
||||
private queued: {
|
||||
[index: string]: string
|
||||
};
|
||||
private subscriptions: {
|
||||
[index: string]: WsState$Subscription
|
||||
};
|
||||
private websocket: WebSocket | null;
|
||||
|
||||
/**
|
||||
* @param {string} endpoint The endpoint url. Usually `ws://ip:9944` or `wss://ip:9944`
|
||||
* @param {boolean} autoConnect Whether to connect automatically or not.
|
||||
*/
|
||||
constructor (endpoint: string = defaults.WS_URL, autoConnect: boolean = true) {
|
||||
assert(/^(wss|ws):\/\//.test(endpoint), `Endpoint should start with 'ws://', received '${endpoint}'`);
|
||||
|
||||
this._eventemitter = new EventEmitter();
|
||||
this.autoConnect = autoConnect;
|
||||
this.coder = new Coder();
|
||||
this.endpoint = endpoint;
|
||||
this.handlers = {};
|
||||
this.queued = {};
|
||||
this.subscriptions = {};
|
||||
this.websocket = null;
|
||||
|
||||
if (autoConnect) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary `true` when this provider supports subscriptions
|
||||
*/
|
||||
get hasSubscriptions (): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns a clone of the object
|
||||
*/
|
||||
clone (): WsProvider {
|
||||
return new WsProvider(this.endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Manually connect
|
||||
* @description The [[WsProvider]] connects automatically by default, however if you decided otherwise, you may
|
||||
* connect manually using this method.
|
||||
*/
|
||||
connect (): void {
|
||||
try {
|
||||
this.websocket = new WebSocket(this.endpoint);
|
||||
|
||||
this.websocket.onclose = this.onSocketClose;
|
||||
this.websocket.onerror = this.onSocketError;
|
||||
this.websocket.onmessage = this.onSocketMessage;
|
||||
this.websocket.onopen = this.onSocketOpen;
|
||||
} catch (error) {
|
||||
l.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Manually disconnect from the connection, clearing autoconnect logic
|
||||
*/
|
||||
disconnect (): void {
|
||||
if (isNull(this.websocket)) {
|
||||
throw new Error('Cannot disconnect on a non-open websocket');
|
||||
}
|
||||
|
||||
// switch off autoConnect, we are in manual mode now
|
||||
this.autoConnect = false;
|
||||
|
||||
// 1000 - Normal closure; the connection successfully completed
|
||||
this.websocket.close(1000);
|
||||
this.websocket = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Whether the node is connected or not.
|
||||
* @return {boolean} true if connected
|
||||
*/
|
||||
isConnected (): boolean {
|
||||
return this._isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Listens on events after having subscribed using the [[subscribe]] function.
|
||||
* @param {ProviderInterface$Emitted} type Event
|
||||
* @param {ProviderInterface$EmitCb} sub Callback
|
||||
*/
|
||||
on (type: ProviderInterface$Emitted, sub: ProviderInterface$EmitCb): void {
|
||||
this._eventemitter.on(type, sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Send JSON data using WebSockets to configured HTTP Endpoint or queue.
|
||||
* @param method The RPC methods to execute
|
||||
* @param params Encoded paramaters as appliucable for the method
|
||||
* @param subscription Subscription details (internally used)
|
||||
*/
|
||||
async send (method: string, params: Array<any>, subscription?: SubscriptionHandler): Promise<any> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
try {
|
||||
const json = this.coder.encodeJson(method, params);
|
||||
const id = this.coder.getId();
|
||||
const callback = (error?: Error | null, result?: any) => {
|
||||
error
|
||||
? reject(error)
|
||||
: resolve(result);
|
||||
};
|
||||
|
||||
l.debug(() => ['calling', method, params, json, !!subscription]);
|
||||
|
||||
this.handlers[id] = {
|
||||
callback,
|
||||
method,
|
||||
params,
|
||||
subscription
|
||||
};
|
||||
|
||||
if (this.isConnected() && !isNull(this.websocket)) {
|
||||
this.websocket.send(json);
|
||||
} else {
|
||||
this.queued[id] = json;
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name subscribe
|
||||
* @summary Allows subscribing to a specific event.
|
||||
* @param {string} type Subscription type
|
||||
* @param {string} method Subscription method
|
||||
* @param {Array<any>} params Parameters
|
||||
* @param {ProviderInterface$Callback} callback Callback
|
||||
* @return {Promise<number>} Promise resolving to the dd of the subscription you can use with [[unsubscribe]].
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const provider = new WsProvider('ws://127.0.0.1:9944');
|
||||
* const rpc = new Rpc(provider);
|
||||
*
|
||||
* rpc.state.subscribeStorage([[storage.balances.freeBalance, <Address>]], (_, values) => {
|
||||
* console.log(values)
|
||||
* }).then((subscriptionId) => {
|
||||
* console.log('balance changes subscription id: ', subscriptionId)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
async subscribe (type: string, method: string, params: Array<any>, callback: ProviderInterface$Callback): Promise<number> {
|
||||
const id = await this.send(method, params, { callback, type });
|
||||
|
||||
return id as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Allows unsubscribing to subscriptions made with [[subscribe]].
|
||||
*/
|
||||
async unsubscribe (type: string, method: string, id: number): Promise<boolean> {
|
||||
const subscription = `${type}::${id}`;
|
||||
|
||||
// FIXME This now could happen with re-subscriptions. The issue is that with a re-sub
|
||||
// the assigned id now does not match what the API user originally received. It has
|
||||
// a slight complication in solving - since we cannot rely on the send id, but rather
|
||||
// need to find the actual subscription id to map it
|
||||
if (isUndefined(this.subscriptions[subscription])) {
|
||||
l.debug(() => `Unable to find active subscription=${subscription}`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
delete this.subscriptions[subscription];
|
||||
|
||||
const result = await this.send(method, [id]);
|
||||
|
||||
return result as boolean;
|
||||
}
|
||||
|
||||
private emit (type: ProviderInterface$Emitted, ...args: Array<any>): void {
|
||||
this._eventemitter.emit(type, ...args);
|
||||
}
|
||||
|
||||
private onSocketClose = (event: CloseEvent): void => {
|
||||
if (this.autoConnect) {
|
||||
l.error(`disconnected from ${this.endpoint}::${event.code}: ${event.reason}`);
|
||||
}
|
||||
|
||||
this._isConnected = false;
|
||||
this.emit('disconnected');
|
||||
|
||||
if (this.autoConnect) {
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private onSocketError = (error: Event): void => {
|
||||
l.debug(() => ['socket error', error]);
|
||||
this.emit('error', error);
|
||||
}
|
||||
|
||||
private onSocketMessage = (message: MessageEvent): void => {
|
||||
l.debug(() => ['received', message.data]);
|
||||
|
||||
const response: JsonRpcResponse = JSON.parse(message.data as string);
|
||||
|
||||
return isUndefined(response.method)
|
||||
? this.onSocketMessageResult(response)
|
||||
: this.onSocketMessageSubscribe(response);
|
||||
}
|
||||
|
||||
private onSocketMessageResult = (response: JsonRpcResponse): void => {
|
||||
l.debug(() => ['handling: response =', response, 'id =', response.id]);
|
||||
|
||||
const handler = this.handlers[response.id];
|
||||
|
||||
if (!handler) {
|
||||
l.debug(() => `Unable to find handler for id=${response.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { method, params, subscription } = handler;
|
||||
const result = this.coder.decodeResponse(response);
|
||||
|
||||
if (subscription) {
|
||||
this.subscriptions[`${subscription.type}::${result}`] = {
|
||||
...subscription,
|
||||
method,
|
||||
params
|
||||
};
|
||||
}
|
||||
|
||||
handler.callback(null, result);
|
||||
} catch (error) {
|
||||
handler.callback(error, undefined);
|
||||
}
|
||||
|
||||
delete this.handlers[response.id];
|
||||
}
|
||||
|
||||
private onSocketMessageSubscribe = (response: JsonRpcResponse): void => {
|
||||
const subscription = `${response.method}::${response.params.subscription}`;
|
||||
|
||||
l.debug(() => ['handling: response =', response, 'subscription =', subscription]);
|
||||
|
||||
const handler = this.subscriptions[subscription];
|
||||
|
||||
if (!handler) {
|
||||
l.debug(() => `Unable to find handler for subscription=${subscription}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = this.coder.decodeResponse(response);
|
||||
|
||||
handler.callback(null, result);
|
||||
} catch (error) {
|
||||
handler.callback(error, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private onSocketOpen = (): boolean => {
|
||||
assert(!isNull(this.websocket), 'WebSocket cannot be null in onOpen');
|
||||
|
||||
l.debug(() => ['connected to', this.endpoint]);
|
||||
|
||||
this._isConnected = true;
|
||||
this.emit('connected');
|
||||
|
||||
this.sendQueue();
|
||||
this.resubscribe();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private resubscribe (): void {
|
||||
const subscriptions = this.subscriptions;
|
||||
|
||||
this.subscriptions = {};
|
||||
|
||||
Object.keys(subscriptions).forEach(async (id) => {
|
||||
const { callback, method, params, type } = subscriptions[id];
|
||||
|
||||
// only re-create subscriptions which are not in author (only area where
|
||||
// transactions are created, i.e. submissions such as 'author_submitAndWatchExtrinsic'
|
||||
// are not included (and will not be re-broadcast)
|
||||
if (type.startsWith('author_')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.subscribe(type, method, params, callback);
|
||||
} catch (error) {
|
||||
l.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private sendQueue (): void {
|
||||
Object.keys(this.queued).forEach((id) => {
|
||||
try {
|
||||
// @ts-ignore we have done the websocket check in onSocketOpen, if an issue, will catch it
|
||||
this.websocket.send(
|
||||
this.queued[id]
|
||||
);
|
||||
|
||||
delete this.queued[id];
|
||||
} catch (error) {
|
||||
l.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export default WsProvider;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# @polkadot/rpc-rx
|
||||
|
||||
An RxJs wrapper around the [@polkadot/rpc-core](../rpc-core).
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# @polkadot/extrinsics
|
||||
|
||||
Base extrinsic definitions & codecs
|
||||
Base extrinsic definitions & codecs
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"@polkadot/types": "^0.45.14",
|
||||
"@polkadot/util": "^0.34.24"
|
||||
"@polkadot/util": "^0.34.27"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,3 +18,7 @@ As methods are added, simply adding the name, inputs & output will prepare it fo
|
||||
|
||||
- Add the method to the correct file in [src/rpc/](src/rpc/) (Input/Output types as cross-referenced from the canonical implementation and match one-to-one)
|
||||
- Should a new type be required, add it to the type list, [src/types.js](src/types.js) (Required for TSLint type checking)
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# @polkadot/storage
|
||||
|
||||
An base interface to the storage layer
|
||||
|
||||
## Classes
|
||||
|
||||
[Classes](SUMMARY.md)
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/type-storage#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"@polkadot/keyring": "^0.34.24",
|
||||
"@polkadot/keyring": "^0.34.27",
|
||||
"@polkadot/types": "^0.45.14",
|
||||
"@polkadot/util": "^0.34.24",
|
||||
"@polkadot/util-crypto": "^0.34.24"
|
||||
"@polkadot/util": "^0.34.27",
|
||||
"@polkadot/util-crypto": "^0.34.27"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/types#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"@polkadot/keyring": "^0.34.24",
|
||||
"@polkadot/util": "^0.34.24",
|
||||
"@polkadot/keyring": "^0.34.27",
|
||||
"@polkadot/util": "^0.34.27",
|
||||
"core-js": "^2.6.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import extrinsicsFromMeta from '@polkadot/extrinsics/fromMetadata';
|
||||
import createType from '../codec/createType';
|
||||
import Method from '../primitive/Method';
|
||||
|
||||
import Metadata from './index';
|
||||
import Metadata from './Metadata';
|
||||
import latestParsed from './v1/latest.substrate.v1.json';
|
||||
import rpcData from './static';
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2017-2019 @polkadot/types 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 { isHex, hexToU8a, u8aConcat } from '@polkadot/util';
|
||||
|
||||
import { MAGIC_ERROR, MAGIC_NUMBER } from './MagicNumber';
|
||||
import MetadataVersioned from './MetadataVersioned';
|
||||
|
||||
/**
|
||||
* @name Metadata
|
||||
* @description
|
||||
* The versioned runtime metadata as a decoded structure
|
||||
*/
|
||||
export default class Metadata extends MetadataVersioned {
|
||||
constructor (value?: Uint8Array | string) {
|
||||
super(Metadata.decodeMetadata(value));
|
||||
}
|
||||
|
||||
// first we try and parse using the versioned structure, if this does fail,
|
||||
// we adjust with the magic number and a manual version and re-try. As soon as
|
||||
// we remove support for V0, we will just do a new here
|
||||
private static decodeMetadata (_value: Uint8Array | string = new Uint8Array()): MetadataVersioned {
|
||||
const value = isHex(_value)
|
||||
? hexToU8a(_value)
|
||||
: _value;
|
||||
|
||||
try {
|
||||
return new MetadataVersioned(value);
|
||||
} catch (error) {
|
||||
if (error.code !== MAGIC_ERROR) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return new MetadataVersioned(
|
||||
u8aConcat(
|
||||
MAGIC_NUMBER.toU8a(), // manually add the magic number
|
||||
Uint8Array.from([0]), // add the version for the original
|
||||
value // the actual data as retrieved
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,43 +2,6 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { isHex, hexToU8a, u8aConcat } from '@polkadot/util';
|
||||
import { default as Metadata } from './Metadata';
|
||||
|
||||
import { MAGIC_ERROR, MAGIC_NUMBER } from './MagicNumber';
|
||||
import MetadataVersioned from './MetadataVersioned';
|
||||
|
||||
/**
|
||||
* @name Metadata
|
||||
* @description
|
||||
* The versioned runtime metadata as a decoded structure
|
||||
*/
|
||||
export default class Metadata extends MetadataVersioned {
|
||||
constructor (value?: Uint8Array | string) {
|
||||
super(Metadata.decodeMetadata(value));
|
||||
}
|
||||
|
||||
// first we try and parse using the versioned structure, if this does fail,
|
||||
// we adjust with the magic number and a manual version and re-try. As soon as
|
||||
// we remove support for V0, we will just do a new here
|
||||
private static decodeMetadata (_value: Uint8Array | string = new Uint8Array()): MetadataVersioned {
|
||||
const value = isHex(_value)
|
||||
? hexToU8a(_value)
|
||||
: _value;
|
||||
|
||||
try {
|
||||
return new MetadataVersioned(value);
|
||||
} catch (error) {
|
||||
if (error.code !== MAGIC_ERROR) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return new MetadataVersioned(
|
||||
u8aConcat(
|
||||
MAGIC_NUMBER.toU8a(), // manually add the magic number
|
||||
Uint8Array.from([0]), // add the version for the original
|
||||
value // the actual data as retrieved
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
export default Metadata;
|
||||
|
||||
@@ -7,7 +7,7 @@ import extrinsicsFromMeta from '@polkadot/extrinsics/fromMetadata';
|
||||
import createType from '../../codec/createType';
|
||||
import Method from '../../primitive/Method';
|
||||
|
||||
import Metadata from './index';
|
||||
import Metadata from './Metadata';
|
||||
import latestParsed from './latest.substrate.json';
|
||||
import rpcData from './static';
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2017-2019 @polkadot/types 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 { MetadataInterface } from '../types';
|
||||
|
||||
import { hexToU8a, isHex, isU8a } from '@polkadot/util';
|
||||
|
||||
import Compact from '../../codec/Compact';
|
||||
import Struct from '../../codec/Struct';
|
||||
import Vector from '../../codec/Vector';
|
||||
import { flattenUniq, validateTypes } from '../util';
|
||||
import { OuterDispatchMetadata, OuterDispatchCall } from './Calls';
|
||||
import { OuterEventMetadata, OuterEventMetadataEvent } from './Events';
|
||||
import { RuntimeModuleMetadata } from './Modules';
|
||||
|
||||
// Decodes the runtime metadata as passed through from the `state_getMetadata` call. This
|
||||
// file is probably best understood from the bottom-up, i.e. start reading right at the
|
||||
// end and work up. (Just so we don't use before definition)
|
||||
|
||||
/**
|
||||
* @name MetadataV0
|
||||
* @description
|
||||
* The runtime metadata as a decoded structure
|
||||
*/
|
||||
export default class MetadataV0 extends Struct implements MetadataInterface {
|
||||
constructor (value?: any) {
|
||||
super({
|
||||
outerEvent: OuterEventMetadata,
|
||||
modules: Vector.with(RuntimeModuleMetadata),
|
||||
outerDispatch: OuterDispatchMetadata
|
||||
}, MetadataV0.decodeMetadata(value));
|
||||
}
|
||||
|
||||
static decodeMetadata (value: string | Uint8Array | object): object | Uint8Array {
|
||||
if (isHex(value)) {
|
||||
// We receive this as an hex in the JSON output from the Node.
|
||||
// Convert to u8a and use the U8a version to do the actual parsing.
|
||||
return MetadataV0.decodeMetadata(hexToU8a(value));
|
||||
} else if (isU8a(value)) {
|
||||
// HACK 13 Oct 2018 - For current running BBQ nodes, Metadata is not properly
|
||||
// encoded, it does not have a length prefix. For latest substrate master, it
|
||||
// is properly encoded. Here we pull the prefix, check it agianst the length -
|
||||
// if matches, then we have the length, otherwise we assume it is an older node
|
||||
// and use the whole buffer
|
||||
const [offset, length] = Compact.decodeU8a(value);
|
||||
|
||||
return value.length === (offset + length.toNumber())
|
||||
? value.subarray(offset)
|
||||
: value;
|
||||
}
|
||||
|
||||
// Decode as normal struct
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Wrapped [[OuterDispatchCall]]
|
||||
*/
|
||||
get calls (): Vector<OuterDispatchCall> {
|
||||
return (this.get('outerDispatch') as OuterDispatchMetadata).calls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Wrapped [[OuterEventMetadataEvent]]
|
||||
*/
|
||||
get events (): Vector<OuterEventMetadataEvent> {
|
||||
return (this.get('outerEvent') as OuterEventMetadata).events;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Wrapped [[RuntimeModuleMetadata]]
|
||||
*/
|
||||
get modules (): Vector<RuntimeModuleMetadata> {
|
||||
return this.get('modules') as Vector<RuntimeModuleMetadata>;
|
||||
}
|
||||
|
||||
private get argNames () {
|
||||
return this.modules.map((module) =>
|
||||
module.module.call.functions.map((fn) =>
|
||||
fn.arguments.map((argument) => argument.type.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get eventNames () {
|
||||
return this.events.map((module) =>
|
||||
module.events.map((event) =>
|
||||
event.arguments.map((argument) => argument.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get storageNames () {
|
||||
return this.modules.map((module) =>
|
||||
module.storage.isSome
|
||||
? module.storage.unwrap().functions.map((fn) =>
|
||||
fn.type.isMap
|
||||
? [fn.type.asMap.key.toString(), fn.type.asMap.value.toString()]
|
||||
: [fn.type.asType.toString()]
|
||||
)
|
||||
: []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Helper to retrieve a list of all type that are found, sorted and de-deuplicated
|
||||
*/
|
||||
getUniqTypes (throwError: boolean): Array<string> {
|
||||
const types = flattenUniq([this.argNames, this.eventNames, this.storageNames]);
|
||||
|
||||
validateTypes(types, throwError);
|
||||
|
||||
return types;
|
||||
}
|
||||
}
|
||||
@@ -2,115 +2,6 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { MetadataInterface } from '../types';
|
||||
import { default as MetadataV0 } from './Metadata';
|
||||
|
||||
import { hexToU8a, isHex, isU8a } from '@polkadot/util';
|
||||
|
||||
import Compact from '../../codec/Compact';
|
||||
import Struct from '../../codec/Struct';
|
||||
import Vector from '../../codec/Vector';
|
||||
import { flattenUniq, validateTypes } from '../util';
|
||||
import { OuterDispatchMetadata, OuterDispatchCall } from './Calls';
|
||||
import { OuterEventMetadata, OuterEventMetadataEvent } from './Events';
|
||||
import { RuntimeModuleMetadata } from './Modules';
|
||||
|
||||
// Decodes the runtime metadata as passed through from the `state_getMetadata` call. This
|
||||
// file is probably best understood from the bottom-up, i.e. start reading right at the
|
||||
// end and work up. (Just so we don't use before definition)
|
||||
|
||||
/**
|
||||
* @name MetadataV0
|
||||
* @description
|
||||
* The runtime metadata as a decoded structure
|
||||
*/
|
||||
export default class MetadataV0 extends Struct implements MetadataInterface {
|
||||
constructor (value?: any) {
|
||||
super({
|
||||
outerEvent: OuterEventMetadata,
|
||||
modules: Vector.with(RuntimeModuleMetadata),
|
||||
outerDispatch: OuterDispatchMetadata
|
||||
}, MetadataV0.decodeMetadata(value));
|
||||
}
|
||||
|
||||
static decodeMetadata (value: string | Uint8Array | object): object | Uint8Array {
|
||||
if (isHex(value)) {
|
||||
// We receive this as an hex in the JSON output from the Node.
|
||||
// Convert to u8a and use the U8a version to do the actual parsing.
|
||||
return MetadataV0.decodeMetadata(hexToU8a(value));
|
||||
} else if (isU8a(value)) {
|
||||
// HACK 13 Oct 2018 - For current running BBQ nodes, Metadata is not properly
|
||||
// encoded, it does not have a length prefix. For latest substrate master, it
|
||||
// is properly encoded. Here we pull the prefix, check it agianst the length -
|
||||
// if matches, then we have the length, otherwise we assume it is an older node
|
||||
// and use the whole buffer
|
||||
const [offset, length] = Compact.decodeU8a(value);
|
||||
|
||||
return value.length === (offset + length.toNumber())
|
||||
? value.subarray(offset)
|
||||
: value;
|
||||
}
|
||||
|
||||
// Decode as normal struct
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Wrapped [[OuterDispatchCall]]
|
||||
*/
|
||||
get calls (): Vector<OuterDispatchCall> {
|
||||
return (this.get('outerDispatch') as OuterDispatchMetadata).calls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Wrapped [[OuterEventMetadataEvent]]
|
||||
*/
|
||||
get events (): Vector<OuterEventMetadataEvent> {
|
||||
return (this.get('outerEvent') as OuterEventMetadata).events;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Wrapped [[RuntimeModuleMetadata]]
|
||||
*/
|
||||
get modules (): Vector<RuntimeModuleMetadata> {
|
||||
return this.get('modules') as Vector<RuntimeModuleMetadata>;
|
||||
}
|
||||
|
||||
private get argNames () {
|
||||
return this.modules.map((module) =>
|
||||
module.module.call.functions.map((fn) =>
|
||||
fn.arguments.map((argument) => argument.type.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get eventNames () {
|
||||
return this.events.map((module) =>
|
||||
module.events.map((event) =>
|
||||
event.arguments.map((argument) => argument.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get storageNames () {
|
||||
return this.modules.map((module) =>
|
||||
module.storage.isSome
|
||||
? module.storage.unwrap().functions.map((fn) =>
|
||||
fn.type.isMap
|
||||
? [fn.type.asMap.key.toString(), fn.type.asMap.value.toString()]
|
||||
: [fn.type.asType.toString()]
|
||||
)
|
||||
: []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Helper to retrieve a list of all type that are found, sorted and de-deuplicated
|
||||
*/
|
||||
getUniqTypes (throwError: boolean): Array<string> {
|
||||
const types = flattenUniq([this.argNames, this.eventNames, this.storageNames]);
|
||||
|
||||
validateTypes(types, throwError);
|
||||
|
||||
return types;
|
||||
}
|
||||
}
|
||||
export default MetadataV0;
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2017-2019 @polkadot/types 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 { MetadataInterface } from '../types';
|
||||
|
||||
import Option from '../../codec/Option';
|
||||
import Struct from '../../codec/Struct';
|
||||
import Vector from '../../codec/Vector';
|
||||
import Text from '../../primitive/Text';
|
||||
import { flattenUniq, validateTypes } from '../util';
|
||||
|
||||
import { MetadataCall } from './Calls';
|
||||
import { MetadataEvent } from './Events';
|
||||
import { MetadataStorage } from './Storage';
|
||||
|
||||
/**
|
||||
* @name MetadataModule
|
||||
* @description
|
||||
* The definition of a module in the system
|
||||
*/
|
||||
export class MetadataModule extends Struct {
|
||||
constructor (value?: any) {
|
||||
super({
|
||||
name: Text,
|
||||
prefix: Text,
|
||||
storage: Option.with(Vector.with(MetadataStorage)),
|
||||
calls: Option.with(Vector.with(MetadataCall)),
|
||||
events: Option.with(Vector.with(MetadataEvent))
|
||||
}, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module calls
|
||||
*/
|
||||
get calls (): Option<Vector<MetadataCall>> {
|
||||
return this.get('calls') as Option<Vector<MetadataCall>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module events
|
||||
*/
|
||||
get events (): Option<Vector<MetadataEvent>> {
|
||||
return this.get('events') as Option<Vector<MetadataEvent>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module name
|
||||
*/
|
||||
get name (): Text {
|
||||
return this.get('name') as Text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module prefix
|
||||
*/
|
||||
get prefix (): Text {
|
||||
return this.get('prefix') as Text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the associated module storage
|
||||
*/
|
||||
get storage (): Option<Vector<MetadataStorage>> {
|
||||
return this.get('storage') as Option<Vector<MetadataStorage>>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name MetadataV1
|
||||
* @description
|
||||
* The runtime metadata as a decoded structure
|
||||
*/
|
||||
export default class MetadataV1 extends Struct implements MetadataInterface {
|
||||
constructor (value?: any) {
|
||||
super({
|
||||
modules: Vector.with(MetadataModule)
|
||||
}, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The associated modules for this structure
|
||||
*/
|
||||
get modules (): Vector<MetadataModule> {
|
||||
return this.get('modules') as Vector<MetadataModule>;
|
||||
}
|
||||
|
||||
private get callNames () {
|
||||
return this.modules.map((mod) =>
|
||||
mod.calls.isNone
|
||||
? []
|
||||
: mod.calls.unwrap().map((fn) =>
|
||||
fn.args.map((arg) => arg.type.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get eventNames () {
|
||||
return this.modules.map((mod) =>
|
||||
mod.events.isNone
|
||||
? []
|
||||
: mod.events.unwrap().map((event) =>
|
||||
event.args.map((arg) => arg.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get storageNames () {
|
||||
return this.modules.map((mod) =>
|
||||
mod.storage.isNone
|
||||
? []
|
||||
: mod.storage.unwrap().map((fn) =>
|
||||
fn.type.isMap
|
||||
? [fn.type.asMap.key.toString(), fn.type.asMap.value.toString()]
|
||||
: [fn.type.asType.toString()]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Helper to retrieve a list of all type that are found, sorted and de-deuplicated
|
||||
*/
|
||||
getUniqTypes (throwError: boolean): Array<string> {
|
||||
const types = flattenUniq([this.callNames, this.eventNames, this.storageNames]);
|
||||
|
||||
validateTypes(types, throwError);
|
||||
|
||||
return types;
|
||||
}
|
||||
}
|
||||
@@ -2,129 +2,10 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { MetadataInterface } from '../types';
|
||||
import { default as MetadataV1, MetadataModule as MetadataModuleV1 } from './Metadata';
|
||||
|
||||
import Option from '../../codec/Option';
|
||||
import Struct from '../../codec/Struct';
|
||||
import Vector from '../../codec/Vector';
|
||||
import Text from '../../primitive/Text';
|
||||
import { flattenUniq, validateTypes } from '../util';
|
||||
export default MetadataV1;
|
||||
|
||||
import { MetadataCall } from './Calls';
|
||||
import { MetadataEvent } from './Events';
|
||||
import { MetadataStorage } from './Storage';
|
||||
|
||||
/**
|
||||
* @name MetadataModule
|
||||
* @description
|
||||
* The definition of a module in the system
|
||||
*/
|
||||
export class MetadataModule extends Struct {
|
||||
constructor (value?: any) {
|
||||
super({
|
||||
name: Text,
|
||||
prefix: Text,
|
||||
storage: Option.with(Vector.with(MetadataStorage)),
|
||||
calls: Option.with(Vector.with(MetadataCall)),
|
||||
events: Option.with(Vector.with(MetadataEvent))
|
||||
}, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module calls
|
||||
*/
|
||||
get calls (): Option<Vector<MetadataCall>> {
|
||||
return this.get('calls') as Option<Vector<MetadataCall>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module events
|
||||
*/
|
||||
get events (): Option<Vector<MetadataEvent>> {
|
||||
return this.get('events') as Option<Vector<MetadataEvent>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module name
|
||||
*/
|
||||
get name (): Text {
|
||||
return this.get('name') as Text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the module prefix
|
||||
*/
|
||||
get prefix (): Text {
|
||||
return this.get('prefix') as Text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description the associated module storage
|
||||
*/
|
||||
get storage (): Option<Vector<MetadataStorage>> {
|
||||
return this.get('storage') as Option<Vector<MetadataStorage>>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name MetadataV1
|
||||
* @description
|
||||
* The runtime metadata as a decoded structure
|
||||
*/
|
||||
export default class MetadataV1 extends Struct implements MetadataInterface {
|
||||
constructor (value?: any) {
|
||||
super({
|
||||
modules: Vector.with(MetadataModule)
|
||||
}, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The associated modules for this structure
|
||||
*/
|
||||
get modules (): Vector<MetadataModule> {
|
||||
return this.get('modules') as Vector<MetadataModule>;
|
||||
}
|
||||
|
||||
private get callNames () {
|
||||
return this.modules.map((mod) =>
|
||||
mod.calls.isNone
|
||||
? []
|
||||
: mod.calls.unwrap().map((fn) =>
|
||||
fn.args.map((arg) => arg.type.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get eventNames () {
|
||||
return this.modules.map((mod) =>
|
||||
mod.events.isNone
|
||||
? []
|
||||
: mod.events.unwrap().map((event) =>
|
||||
event.args.map((arg) => arg.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private get storageNames () {
|
||||
return this.modules.map((mod) =>
|
||||
mod.storage.isNone
|
||||
? []
|
||||
: mod.storage.unwrap().map((fn) =>
|
||||
fn.type.isMap
|
||||
? [fn.type.asMap.key.toString(), fn.type.asMap.value.toString()]
|
||||
: [fn.type.asType.toString()]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Helper to retrieve a list of all type that are found, sorted and de-deuplicated
|
||||
*/
|
||||
getUniqTypes (throwError: boolean): Array<string> {
|
||||
const types = flattenUniq([this.callNames, this.eventNames, this.storageNames]);
|
||||
|
||||
validateTypes(types, throwError);
|
||||
|
||||
return types;
|
||||
}
|
||||
}
|
||||
export {
|
||||
MetadataModuleV1
|
||||
};
|
||||
|
||||
@@ -6,8 +6,8 @@ import { OuterDispatchCall, OuterDispatchMetadata } from '../v0/Calls';
|
||||
import { EventMetadata, OuterEventMetadata, OuterEventMetadataEvent } from '../v0/Events';
|
||||
import { CallMetadata, FunctionMetadata, RuntimeModuleMetadata, ModuleMetadata, StorageFunctionMetadata, StorageMetadata } from '../v0/Modules';
|
||||
|
||||
import MetadataV0 from '../v0';
|
||||
import MetadataV1, { MetadataModule as MetadataModuleV1 } from './index';
|
||||
import MetadataV0 from '../v0/Metadata';
|
||||
import MetadataV1, { MetadataModuleV1 } from './index';
|
||||
|
||||
function storageV0 (mod: MetadataModuleV1): StorageMetadata | null {
|
||||
if (mod.storage.isNone) {
|
||||
|
||||
@@ -11,8 +11,8 @@ import Metadata from '../Metadata';
|
||||
import rpcdata from '../Metadata/static';
|
||||
import MetadataV0 from '../Metadata/v0';
|
||||
|
||||
const ANCHOR_TOP = `<a id='top' style='text-decoration: none;'>`;
|
||||
const LINK_BACK_TO_TOP = `<a href='#top' style='float: right; font-size: 1.6rem; font-weight: bold;'>Back To Top</a>`;
|
||||
const ANCHOR_TOP = '';
|
||||
const LINK_BACK_TO_TOP = '';
|
||||
|
||||
const DESC_EXTRINSICS = '\n\n_The following sections contain Extrinsics methods are part of the default Substrate runtime._\n';
|
||||
const DESC_EVENTS = '\n\nEvents are emitted for certain operations on the runtime. The following sections describe the events that are part of the default Substrate runtime.\n';
|
||||
@@ -24,7 +24,7 @@ function sectionLink (sectionName: string) {
|
||||
}
|
||||
|
||||
function generateSectionHeader (md: string, sectionName: string) {
|
||||
return `${md}\n___\n${LINK_BACK_TO_TOP}\n\n### <a id='${sectionName}'></a>${sectionName}\n`;
|
||||
return `${md}\n___\n${LINK_BACK_TO_TOP}\n\n### ${sectionName}\n`;
|
||||
}
|
||||
|
||||
function addRpc () {
|
||||
|
||||
Reference in New Issue
Block a user