Swap to Mortal transactions by default (#1081)

* Start of mortal tx

* Swap to Mortal transcations by default

* Update SubmittableExtrinsic.ts

* WIP (pre master merge)

* Remove eraLength, allow era to specify

* Oops, linting.... it happens in the best of families

* WIP debugging immortals

* Re-surrect with working meta, and e2e tests

* Update docs/examples/promise/07_make_transfer_with_allowed_block_permissions_only/index.js

Co-Authored-By: Amaury Martiny <amaury.martiny@protonmail.com>

* Update docs/examples/promise/07_make_transfer_with_allowed_block_permissions_only/index.js

Co-Authored-By: Amaury Martiny <amaury.martiny@protonmail.com>

* Remove useless birth/death calls
This commit is contained in:
Jaco Greeff
2019-07-09 15:56:10 +02:00
committed by GitHub
co-authored by Amaury Martiny
parent be0139dfce
commit 2eee6cfea7
9 changed files with 170 additions and 119 deletions
@@ -22,22 +22,18 @@ async function main () {
// get current block
const signedBlock = await api.rpc.chain.getBlock();
// get current block height
// get current block height and hash
const currentHeight = signedBlock.block.header.number;
const blockHash = signedBlock.block.header.hash;
const exERA = new ExtrinsicEra({ current: currentHeight, period: 10 });
// get the beggining of the current era
const eraBirth = exERA.asMortalEra.birth(currentHeight.toNumber());
// get the hash of the beginning of the current era
const eraHash = await api.rpc.chain.getBlockHash(eraBirth);
// construct a mortal era
const era = new ExtrinsicEra({ current: currentHeight, period: 10 });
// Create an extrinsic, transferring 12345 units to Bob
const transfer = api.tx.balances.transfer(BOB, 12345);
// Sign and send the transaction using our account with a nonce and the length of blocks the transaction is valid for
const hash = await transfer.signAndSend(alice, { blockHash: eraHash, era: exERA, nonce });
// Sign and send the transaction using our account with a nonce and the length of blocks the transaction is valid for
const hash = await transfer.signAndSend(alice, { blockHash, era, nonce });
console.log('Transfer sent with hash', hash.toHex());
}
+60 -18
View File
@@ -2,11 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId, Address, ExtrinsicStatus, EventRecord, getTypeRegistry, Hash, Index, Method, SignedBlock, Vector } from '@polkadot/types';
import { Callback, Codec, IExtrinsic, IKeyringPair, SignatureOptions } from '@polkadot/types/types';
import { AccountId, Address, ExtrinsicStatus, EventRecord, getTypeRegistry, Hash, Header, Index, Method, SignedBlock, Vector, ExtrinsicEra } from '@polkadot/types';
import { AnyNumber, AnyU8a, Callback, Codec, IExtrinsic, IExtrinsicEra, IKeyringPair, SignatureOptions } from '@polkadot/types/types';
import { ApiInterface$Rx, ApiTypes, Signer } from './types';
import { Observable, of, combineLatest } from 'rxjs';
import { Observable, combineLatest, of } from 'rxjs';
import { first, map, mergeMap, switchMap, tap } from 'rxjs/operators';
import { assert, isBn, isFunction, isNumber, isUndefined } from '@polkadot/util';
@@ -38,6 +38,15 @@ type SubmittableResultValue = {
status: ExtrinsicStatus;
};
type SignerOptions = {
blockHash: AnyU8a,
era?: IExtrinsicEra | number,
nonce: AnyNumber
};
// pick a default - in the case of 4s blocktimes, this translates to 60 seconds
const DEFAULT_MORTAL_LENGTH = 15;
export class SubmittableResult implements ISubmittableResult {
readonly events: Array<EventRecord>;
readonly status: ExtrinsicStatus;
@@ -76,11 +85,11 @@ export interface SubmittableExtrinsic<ApiType> extends IExtrinsic {
sign (account: IKeyringPair, _options: Partial<SignatureOptions>): this;
signAndSend (account: IKeyringPair | string | AccountId | Address, options?: Partial<SignatureOptions>): SumbitableResultResult<ApiType>;
signAndSend (account: IKeyringPair | string | AccountId | Address, options?: Partial<SignerOptions>): SumbitableResultResult<ApiType>;
signAndSend (account: IKeyringPair | string | AccountId | Address, statusCb: Callback<ISubmittableResult>): SumbitableResultSubscription<ApiType>;
signAndSend (account: IKeyringPair | string | AccountId | Address, options: Partial<SignatureOptions>, statusCb: Callback<ISubmittableResult>): SumbitableResultSubscription<ApiType>;
signAndSend (account: IKeyringPair | string | AccountId | Address, options: Partial<SignerOptions>, statusCb?: Callback<ISubmittableResult>): SumbitableResultSubscription<ApiType>;
}
export default function createSubmittableExtrinsic<ApiType> (
@@ -150,14 +159,37 @@ export default function createSubmittableExtrinsic<ApiType> (
);
}
function expandOptions (options: Partial<SignatureOptions>): SignatureOptions {
return {
function expandOptions (options: Partial<SignerOptions>): SignatureOptions {
return options = {
blockHash: api.genesisHash,
version: api.runtimeVersion,
...options
} as SignatureOptions;
}
function setupEraOptions (header: Header | null, options: Partial<SignerOptions>): Partial<SignatureOptions> {
if (!header) {
if (isNumber(options.era)) {
// since we have no header, it is immortal, remove any option overrides
// so we only supply the genesisHash and no era to the construction
delete options.era;
delete options.blockHash;
}
return {};
}
const { blockNumber, hash } = header;
return {
blockHash: hash,
era: new ExtrinsicEra({
current: blockNumber,
period: options.era || DEFAULT_MORTAL_LENGTH
})
};
}
const signOrigin = _extrinsic.sign;
Object.defineProperties(
@@ -171,10 +203,10 @@ export default function createSubmittableExtrinsic<ApiType> (
}
},
sign: {
value: function (account: IKeyringPair, _options: Partial<SignatureOptions>): SubmittableExtrinsic<ApiType> {
value: function (account: IKeyringPair, _options: Partial<SignerOptions>): SubmittableExtrinsic<ApiType> {
// HACK here we actually override nonce if it was specified (backwards compat for
// the previous signature - don't let userspace break, but allow then time to upgrade)
const options: Partial<SignatureOptions> = isBn(_options) || isNumber(_options)
const options: Partial<SignerOptions> = isBn(_options) || isNumber(_options)
? { nonce: _options as any as number }
: _options;
@@ -184,13 +216,13 @@ export default function createSubmittableExtrinsic<ApiType> (
}
},
signAndSend: {
value: function (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial<SignatureOptions> | Callback<ISubmittableResult>, statusCb?: Callback<ISubmittableResult>): SumbitableResultResult<ApiType> | SumbitableResultSubscription<ApiType> {
let options: Partial<SignatureOptions> = {};
value: function (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial<SignerOptions> | Callback<ISubmittableResult>, statusCb?: Callback<ISubmittableResult>): SumbitableResultResult<ApiType> | SumbitableResultSubscription<ApiType> {
let options: Partial<SignerOptions> = {};
if (isFunction(optionsOrStatus)) {
statusCb = optionsOrStatus;
} else {
options = optionsOrStatus || {};
options = { ...optionsOrStatus };
}
const isSubscription = _noStatusCb || !!statusCb;
@@ -200,19 +232,29 @@ export default function createSubmittableExtrinsic<ApiType> (
return decorateMethod(
() => ((
isUndefined(options.nonce)
? api.query.system.accountNonce<Index>(address)
: of(new Index(options.nonce))
combineLatest([
// if we have a nonce already, don't retrieve the latest, use what is there
isUndefined(options.nonce)
? api.query.system.accountNonce<Index>(address)
: of(new Index(options.nonce)),
// if we have an era provided already or eraLength is <= 0 (immortal)
// don't get the latest block, just pass null, handle in mergeMap
(isUndefined(options.era) || (isNumber(options.era) && options.era > 0))
? api.rpc.chain.getHeader() as Observable<Header>
: of(null)
])
).pipe(
first(),
mergeMap(async (nonce) => {
mergeMap(async ([nonce, header]) => {
const eraOptions = setupEraOptions(header, options);
if (isKeyringPair) {
this.sign(account as IKeyringPair, { ...options, nonce });
this.sign(account as IKeyringPair, { ...options, ...eraOptions, nonce });
} else {
assert(api.signer, 'no signer exists');
updateId = await (api.signer as Signer).sign(_extrinsic, address, {
...expandOptions({ ...options, nonce }),
...expandOptions({ ...options, ...eraOptions, nonce }),
genesisHash: api.genesisHash
});
}
+50 -42
View File
@@ -79,20 +79,20 @@ describe.skip('Promise e2e transactions', () => {
.send(logEvents(done));
});
it('makes a transfer (signAndSend)', async (done) => {
it('makes a transfer (signAndSend, immortal)', async (done) => {
return api.tx.balances
.transfer(keyring.eve.address, 12345)
.signAndSend(keyring.dave, logEvents(done));
.signAndSend(keyring.charlie, { era: 0 }, logEvents(done));
});
it('makes a transfer (signAndSend via Signer)', async (done) => {
const signer = new SingleAccountSigner(keyring.dave) as Signer;
const signer = new SingleAccountSigner(keyring.charlie) as Signer;
api.setSigner(signer);
return api.tx.balances
.transfer(keyring.eve.address, 12345)
.signAndSend(keyring.dave.address, logEvents(done));
.signAndSend(keyring.charlie.address, logEvents(done));
});
it('makes a transfer (signAndSend via Signer) with undefined Signer', async () => {
@@ -141,18 +141,19 @@ describe.skip('Promise e2e transactions', () => {
expect(hash.toHex()).toHaveLength(66);
});
it('makes a transfer, and uses new balance to transfers to new', async (done) => {
// this one is slightly difficult with the current testnet config - CantPay
it.skip('makes a transfer, and uses new balance to transfers to new', async (done) => {
const pair = new Keyring().addFromUri('testing123', {}, 'ed25519');
function doOne (cb: any) {
return api.tx.balances
.transfer(pair.address, 123456)
.transfer(pair.address, 1234567)
.signAndSend(keyring.dave, logEvents(cb));
}
function doTwo (cb: any) {
return api.tx.balances
.transfer(keyring.alice.address, 12345)
.transfer(keyring.alice.address, 1111111)
.signAndSend(pair, logEvents(cb));
}
@@ -162,44 +163,51 @@ describe.skip('Promise e2e transactions', () => {
});
});
it('makes a transfer with ERA (signAndSend)', async (done) => {
const nonce = await api.query.system.accountNonce(keyring.dave.address) as Index;
const signedBlock = await api.rpc.chain.getBlock();
const currentHeight = (signedBlock as SignedBlock).block.header.number;
const exERA = new ExtrinsicEra({ current: currentHeight, period: 10 });
const eraBirth = exERA.asMortalEra.birth(currentHeight.toNumber());
const eraDeath = exERA.asMortalEra.death(currentHeight.toNumber());
console.log('STARTED AT :' + eraBirth + ' EXPIRED AT :' + eraDeath);
const eraHash = await api.rpc.chain.getBlockHash(eraBirth);
const ex = api.tx.balances.transfer(keyring.eve.address, 12345);
const hash = await ex.signAndSend(keyring.dave, { blockHash: eraHash, era: exERA, nonce } as any);
describe('eras', () => {
it('makes a transfer (specified era)', async (done) => {
const signedBlock = await api.rpc.chain.getBlock() as SignedBlock;
const currentHeight = signedBlock.block.header.number;
const exERA = new ExtrinsicEra({ current: currentHeight, period: 10 });
const ex = api.tx.balances.transfer(keyring.eve.address, 12345);
const hash = await ex.signAndSend(keyring.charlie, {
blockHash: signedBlock.block.header.hash,
era: exERA
});
expect(hash.toHex()).toHaveLength(66);
done();
});
expect(hash.toHex()).toHaveLength(66);
done();
});
it('makes a transfer with ERA (signAndSend) with invalid time', async (done) => {
const nonce = await api.query.system.accountNonce(keyring.alice.address) as Index;
const signedBlock = await api.rpc.chain.getBlock();
const currentHeight = (signedBlock as SignedBlock).block.header.number;
const exERA = new ExtrinsicEra({ current: currentHeight, period: 4 });
const eraBirth = exERA.asMortalEra.birth(currentHeight.toNumber());
const eraDeath = exERA.asMortalEra.death(currentHeight.toNumber());
console.log('STARTED AT :' + eraBirth + ' EXPIRED AT :' + eraDeath);
const eraHash = await api.rpc.chain.getBlockHash(eraBirth);
const ex = api.tx.balances.transfer(keyring.eve.address, 12345);
it('makes a transfer with invalid time', async (done) => {
const nonce = await api.query.system.accountNonce(keyring.alice.address) as Index;
const signedBlock = await api.rpc.chain.getBlock() as SignedBlock;
const currentHeight = signedBlock.block.header.number;
const exERA = new ExtrinsicEra({ current: currentHeight, period: 4 });
const eraDeath = exERA.asMortalEra.death(currentHeight.toNumber());
const blockHash = signedBlock.block.header.hash;
const ex = api.tx.balances.transfer(keyring.eve.address, 12345);
return (
api.rpc.chain.subscribeNewHead(async (header: Header) => {
if (header.blockNumber.toNumber() === eraDeath - 1) {
try {
await ex.signAndSend(keyring.alice, { blockHash: eraHash, era: exERA, nonce } as any);
} catch (error) {
expect(error.message).toMatch(/1010: Invalid Transaction \(0\)/);
done();
return (
api.rpc.chain.subscribeNewHead(async (header: Header) => {
if (header.blockNumber.toNumber() === eraDeath - 1) {
try {
await ex.signAndSend(keyring.alice, { blockHash, era: exERA, nonce } as any);
} catch (error) {
expect(error.message).toMatch(/1010: Invalid Transaction \(0\)/);
done();
}
}
}
})
);
})
);
});
it('makes a transfer with custom numeric era', async (done) => {
const hash = await api.tx.balances
.transfer(keyring.eve.address, 12345)
.signAndSend(keyring.charlie, { era: 2 });
expect(hash.toHex()).toHaveLength(66);
done();
});
});
});
@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"block":{"extrinsics":["0x20010100033822245d","0x14010a002534","0x250281ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe2290b20be8c5929d7d4dfe3f8124bad55217fa0dc53210399448a7bddefade7a7e64489c1acbfe65eb42d235e6d54279cd88e4d6d6147862a4e5c6ba2ed44594006c000400ffe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4ee5c0"],"header":{"digest":{"logs":["0x0661757261208e08491700000000","0x05617572610101039584bca9a9dd88d4bede8787ce022ddea809cef8ebdbf0d7b17c918e49921c86e6d6aedebc991630f7634b3f3306b6c134e87325f34301de5745df0da0240e"]},"extrinsicsRoot":"0xe52df22c1a9deb8ed460e5d80a3dd8e1090bfd51631f53eca465915089703b92","number":"0xd0a","parentHash":"0xcf5fa8ef2fe76c0d6288535231d21989829933b986d32a3ba452173c5a2074f1","stateRoot":"0x3fe7723fdb1a60938e45b72f1d84f414434b9df4ece367dbb39912ebe1a85e9f"}},"justification":null},"id":7}
@@ -0,0 +1 @@
{"jsonrpc":"2.0","result":{"block":{"extrinsics":["0x20010100036022245d","0x14010a004d34","0x290281ff90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22f2040bbcf048e6bfb68c0722fe379c93afbc8f885ee21fc5b5679d4ade606a7d46cf151a2b8ad5bbce6ee198a41e3251302f5e9c64f457dbeacacf3e43b448087033000400ffe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4ee5c0"],"header":{"digest":{"logs":["0x0661757261209808491700000000","0x0561757261010195888c7f16b54cf99117933d503faea1a7a8bdab4b6a8144c078b6c3d9e55d3a1114af92b2360a0694a057df016e1ad2bd750d43411464b9e7414e921cfdc00b"]},"extrinsicsRoot":"0x751fc4dfad59fefd3c4f730596f1a742b56c9936b4e3d4801015472261a0ce48","number":"0xd14","parentHash":"0x497b3b929ae079f6f0d280ba85744b87559eaab8969619be5cc7596c5a974cb9","stateRoot":"0x011c352e0f97cad72578f9e1e8ec759096365b891928d6b094d0d8d329f9bbab"}},"justification":null},"id":8}
+24 -36
View File
@@ -4,63 +4,51 @@
import extrinsics from '@polkadot/api-metadata/extrinsics/static';
import Extrinsics from '../type/Extrinsics';
import Method from '../primitive/Method';
import rpc from '../json/SignedBlock.001.json';
import events from '../json/SignedBlock.002.json';
import immortalTxs from '../json/SignedBlock.004.immortal.json';
import mortalTxs from '../json/SignedBlock.004.mortal.json';
import SignedBlock from './SignedBlock';
describe.skip('SignedBlock', () => {
let block: SignedBlock;
describe('SignedBlock', () => {
beforeEach(() => {
Method.injectMethods(extrinsics);
block = new SignedBlock(rpc.result);
});
it('has the correct stateRoot', () => {
expect(
block.block.header.stateRoot.toString()
).toEqual('0xbfe02f7c26e5dad1226eccb1f4ef5306312602f7e1edbb9be50e8b87d9c7840b');
});
it('decodes a block with logs', () => {
it('decodes a full block', () => {
const s = new SignedBlock(events.result);
expect(
s.block.header.stateRoot.toString()
).toEqual('0x24bab7fe0fee187a57bd0029ba151e81a0bae0fea96ad9f85c579c1ba9d88948');
expect(s.block.header.digest.logs.length).toBe(1);
expect(s.block.header.digest.logs[0].type).toBe('AuthoritiesChange');
expect(s.block.header.digest.logs[0].value.toJSON()).toEqual([
'5EyrU8k6worwXWfW2LajRvS7XF6eta286tMpHeHUwMPpzQ9F',
'5Dpq7rU6KAZVdRtcegvpCoeJtZzFPzieRkRz4xajRAiMRkCf',
'5CCtQPAYtuDXt6H4WMrn5nFNk4gemkVRD3k1r1mA7YiZLPBq',
'5F2ABwFm3fUhWMZT7Zeu7UjCr1dndjJD7qBeHChJhiJC949V',
'5CA1n5DSpE46Q9Y7NaqD39T9X7S2dRtscsPvrv2dWuYhFtWr',
'5CrxmsTCLbPRzHnFPs8Q6cx8jzHweegMSQYEJBGXLjDZW4NR',
'5EQ4ZxYew2CXPkjGCBuPdTVg7Hp3bmBXiXBwn1iDmvj6CgaF'
'5EyrU8k6worwXWfW2LajRvS7XF6eta286tMpHeHUwMPpzUwT',
'5Dpq7rU6KAZVdRtcegvpCoeJtZzFPzieRkRz4xajRAiMRxCd',
'5CCtQPAYtuDXt6H4WMrn5nFNk4gemkVRD3k1r1mA7YiZLPP5',
'5F2ABwFm3fUhWMZT7Zeu7UjCr1dndjJD7qBeHChJhiJC99UH',
'5CA1n5DSpE46Q9Y7NaqD39T9X7S2dRtscsPvrv2dWuYhFg5U',
'5CrxmsTCLbPRzHnFPs8Q6cx8jzHweegMSQYEJBGXLjDZWDNv',
'5EQ4ZxYew2CXPkjGCBuPdTVg7Hp3bmBXiXBwn1iDmvj6CY3u'
]);
});
describe('extrinsics', () => {
let extrinsics: Extrinsics;
it('can decode immortals', () => {
const s = new SignedBlock(immortalTxs.result);
const immortalTx = s.block.extrinsics[2];
beforeEach(() => {
extrinsics = block.block.extrinsics;
expect(immortalTx.method.methodName).toEqual('transfer');
expect(immortalTx.method.sectionName).toEqual('balances');
});
it('has the correct data for the first', () => {
const x = extrinsics[0];
it('can decode mortals', () => {
const s = new SignedBlock(mortalTxs.result);
const mortalTx = s.block.extrinsics[2];
expect(x.callIndex).toEqual(new Uint8Array([2, 0]));
expect(x.method.args[0].toU8a()).toEqual(new Uint8Array([70, 41, 195, 91, 0, 0, 0, 0]));
});
it('has the correct data for the second', () => {
const x = extrinsics[1];
expect(x.callIndex).toEqual(new Uint8Array([1, 0]));
expect(x.method.args[0].toString()).toEqual('5CPaGq4KcmntaboAxg5nyqGXdyzaBV2hj6PvhNA3syigiRg8');
expect(x.method.args[1].toString()).toEqual('100000000000000');
expect(mortalTx.method.methodName).toEqual('transfer');
expect(mortalTx.method.sectionName).toEqual('balances');
});
});
});
+25 -10
View File
@@ -2,13 +2,15 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { assert, hexToU8a, isHex, isU8a, isObject, u8aToBn } from '@polkadot/util';
import { AnyU8a, IExtrinsicEra } from '../types';
import BN from 'bn.js';
import { assert, bnToBn, hexToU8a, isHex, isU8a, isObject, u8aToBn } from '@polkadot/util';
import Enum from '../codec/Enum';
import Tuple from '../codec/Tuple';
import U8a from '../codec/U8a';
import U64 from '../primitive/U64';
import { AnyU8a, IExtrinsicEra } from '../types';
type MortalEraValue = [U64, U64];
@@ -162,6 +164,13 @@ export class MortalEra extends Tuple {
throw new Error('Invalid data passed to Mortal era');
}
/**
* @description Encoded length for mortals occupy 2 bytes, different from the actual Tuple since it is encoded. This is a shortcut fro `toU8a().length`
*/
get encodedLength (): number {
return 2;
}
/**
* @description The period of this Mortal wraps as a [[U64]]
*/
@@ -180,11 +189,11 @@ export class MortalEra extends Tuple {
* @description Encodes the value as a Uint8Array as per the parity-codec specifications
* @param isBare true when the value has none of the type-specific prefixes (internal)
* Period and phase are encoded:
* The period of validity from the block hash found in the signing material.
* The phase in the period that this transaction's lifetime begins (and, importantly,
* implies which block hash is included in the signature material). If the `period` is
* greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
* `period` is.
* - The period of validity from the block hash found in the signing material.
* - The phase in the period that this transaction's lifetime begins (and, importantly,
* implies which block hash is included in the signature material). If the `period` is
* greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
* `period` is.
*/
toU8a (isBare?: boolean): Uint8Array {
const period = this.period.toNumber();
@@ -201,14 +210,20 @@ export class MortalEra extends Tuple {
/**
* @description Get the block number of the start of the era whose properties this object describes that `current` belongs to.
*/
birth (current: number) {
return Math.floor((Math.max(current,this.phase.toNumber()) - this.phase.toNumber()) / this.period.toNumber()) * this.period.toNumber() + this.phase.toNumber();
birth (current: BN | number) {
// FIXME No toNumber() here
return Math.floor(
(
Math.max(bnToBn(current).toNumber(), this.phase.toNumber()) - this.phase.toNumber()
) / this.period.toNumber()
) * this.period.toNumber() + this.phase.toNumber();
}
/**
* @description Get the block number of the first block at which the era has ended.
*/
death (current: number) {
death (current: BN | number) {
// FIXME No toNumber() here
return this.birth(current) + this.period.toNumber();
}
+2 -2
View File
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AnyNumber, AnyU8a, IKeyringPair } from '../types';
import { AnyNumber, AnyU8a, IExtrinsicEra, IKeyringPair } from '../types';
import { blake2AsU8a } from '@polkadot/util-crypto';
@@ -17,7 +17,7 @@ import Nonce from './NonceCompact';
type SignaturePayloadValue = {
nonce?: AnyNumber,
method?: Method,
era?: AnyU8a | ExtrinsicEra
era?: AnyU8a | IExtrinsicEra
blockHash?: AnyU8a
};
+1 -1
View File
@@ -113,7 +113,7 @@ export interface RuntimeVersionInterface {
export type SignatureOptions = {
blockHash: AnyU8a,
era?: Uint8Array,
era?: IExtrinsicEra,
nonce: AnyNumber,
version?: RuntimeVersionInterface
};