@polkadot/api
Version:
Promise and RxJS wrappers around the Polkadot JS RPC
269 lines (221 loc) • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createClass = createClass;
var _rxjs = require("rxjs");
var _util = require("@polkadot/util");
var _util2 = require("../util");
var _Result = require("./Result");
// Copyright 2017-2022 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0
/* eslint-disable no-dupe-class-members */
const identity = input => input;
function makeEraOptions(api, registry, partialOptions, _ref) {
let {
header,
mortalLength,
nonce
} = _ref;
if (!header) {
if ((0, _util.isNumber)(partialOptions.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 partialOptions.era;
delete partialOptions.blockHash;
}
return makeSignOptions(api, partialOptions, {
nonce
});
}
return makeSignOptions(api, partialOptions, {
blockHash: header.hash,
era: registry.createTypeUnsafe('ExtrinsicEra', [{
current: header.number,
period: partialOptions.era || mortalLength
}]),
nonce
});
}
function makeSignAndSendOptions(partialOptions, statusCb) {
let options = {};
if ((0, _util.isFunction)(partialOptions)) {
statusCb = partialOptions;
} else {
options = (0, _util.objectSpread)({}, partialOptions);
}
return [options, statusCb];
}
function makeSignOptions(api, partialOptions, extras) {
return (0, _util.objectSpread)({
blockHash: api.genesisHash,
genesisHash: api.genesisHash
}, partialOptions, extras, {
runtimeVersion: api.runtimeVersion,
signedExtensions: api.registry.signedExtensions,
version: api.extrinsicType
});
}
function optionsOrNonce() {
let partialOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return (0, _util.isBn)(partialOptions) || (0, _util.isNumber)(partialOptions) ? {
nonce: partialOptions
} : partialOptions;
}
function createClass(_ref2) {
let {
api,
apiType,
blockHash,
decorateMethod
} = _ref2;
// an instance of the base extrinsic for us to extend
const ExtrinsicBase = api.registry.createClass('Extrinsic');
class Submittable extends ExtrinsicBase {
#ignoreStatusCb;
#transformResult = identity;
constructor(registry, extrinsic) {
super(registry, extrinsic, {
version: api.extrinsicType
});
this.#ignoreStatusCb = apiType === 'rxjs';
} // dry run an extrinsic
dryRun(account, optionsOrHash) {
if (blockHash || (0, _util.isString)(optionsOrHash) || (0, _util.isU8a)(optionsOrHash)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return decorateMethod(() => api.rpc.system.dryRun(this.toHex(), blockHash || optionsOrHash));
} // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call
return decorateMethod(() => this.#observeSign(account, optionsOrHash).pipe((0, _rxjs.switchMap)(() => api.rpc.system.dryRun(this.toHex()))))();
} // calculate the payment info for this transaction (if signed and submitted)
paymentInfo(account, optionsOrHash) {
if (blockHash || (0, _util.isString)(optionsOrHash) || (0, _util.isU8a)(optionsOrHash)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return decorateMethod(() => api.rpc.payment.queryInfo(this.toHex(), blockHash || optionsOrHash));
}
const [allOptions] = makeSignAndSendOptions(optionsOrHash);
const address = (0, _util2.isKeyringPair)(account) ? account.address : account.toString(); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call
return decorateMethod(() => api.derive.tx.signingInfo(address, allOptions.nonce, allOptions.era).pipe((0, _rxjs.first)(), (0, _rxjs.switchMap)(signingInfo => {
// setup our options (same way as in signAndSend)
const eraOptions = makeEraOptions(api, this.registry, allOptions, signingInfo);
const signOptions = makeSignOptions(api, eraOptions, {});
return api.rpc.payment.queryInfo(this.isSigned ? api.tx(this).signFake(address, signOptions).toHex() : this.signFake(address, signOptions).toHex());
})))();
} // send with an immediate Hash result
// send implementation for both immediate Hash and statusCb variants
send(statusCb) {
const isSubscription = api.hasSubscriptions && (this.#ignoreStatusCb || !!statusCb); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call
return decorateMethod(isSubscription ? this.#observeSubscribe : this.#observeSend)(statusCb);
}
/**
* @description Sign a transaction, returning the this to allow chaining, i.e. .sign(...).send(). When options, e.g. nonce/blockHash are not specified, it will be inferred. To retrieve eg. nonce use `signAsync` (the preferred interface, this is provided for backwards compatibility)
* @deprecated
*/
sign(account, partialOptions) {
super.sign(account, makeSignOptions(api, optionsOrNonce(partialOptions), {}));
return this;
}
/**
* @description Signs a transaction, returning `this` to allow chaining. E.g.: `sign(...).send()`. Like `.signAndSend` this will retrieve the nonce and blockHash to send the tx with.
*/
signAsync(account, partialOptions) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call
return decorateMethod(() => this.#observeSign(account, partialOptions).pipe((0, _rxjs.mapTo)(this)))();
} // signAndSend with an immediate Hash result
// signAndSend implementation for all 3 cases above
signAndSend(account, partialOptions, optionalStatusCb) {
const [options, statusCb] = makeSignAndSendOptions(partialOptions, optionalStatusCb);
const isSubscription = api.hasSubscriptions && (this.#ignoreStatusCb || !!statusCb); // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call
return decorateMethod(() => this.#observeSign(account, options).pipe((0, _rxjs.switchMap)(info => isSubscription ? this.#observeSubscribe(info) : this.#observeSend(info))) // FIXME This is wrong, SubmittableResult is _not_ a codec
)(statusCb);
} // adds a transform to the result, applied before result is returned
withResultTransform(transform) {
this.#transformResult = transform;
return this;
}
#observeSign = (account, partialOptions) => {
const address = (0, _util2.isKeyringPair)(account) ? account.address : account.toString();
const options = optionsOrNonce(partialOptions);
return api.derive.tx.signingInfo(address, options.nonce, options.era).pipe((0, _rxjs.first)(), (0, _rxjs.mergeMap)(async signingInfo => {
const eraOptions = makeEraOptions(api, this.registry, options, signingInfo);
let updateId = -1;
if ((0, _util2.isKeyringPair)(account)) {
this.sign(account, eraOptions);
} else {
updateId = await this.#signViaSigner(address, eraOptions, signingInfo.header);
}
return {
options: eraOptions,
updateId
};
}));
};
#observeStatus = (txHash, status) => {
if (!status.isFinalized && !status.isInBlock) {
return (0, _rxjs.of)(this.#transformResult(new _Result.SubmittableResult({
status,
txHash
})));
}
const blockHash = status.isInBlock ? status.asInBlock : status.asFinalized;
return api.derive.tx.events(blockHash).pipe((0, _rxjs.map)(_ref3 => {
let {
block,
events
} = _ref3;
return this.#transformResult(new _Result.SubmittableResult({ ...(0, _util2.filterEvents)(txHash, block, events, status),
status,
txHash
}));
}), (0, _rxjs.catchError)(internalError => (0, _rxjs.of)(this.#transformResult(new _Result.SubmittableResult({
internalError,
status,
txHash
})))));
};
#observeSend = info => {
return api.rpc.author.submitExtrinsic(this).pipe((0, _rxjs.tap)(hash => {
this.#updateSigner(hash, info);
}));
};
#observeSubscribe = info => {
const txHash = this.hash;
return api.rpc.author.submitAndWatchExtrinsic(this).pipe((0, _rxjs.switchMap)(status => this.#observeStatus(txHash, status)), (0, _rxjs.tap)(status => {
this.#updateSigner(status, info);
}));
};
#signViaSigner = async (address, options, header) => {
const signer = options.signer || api.signer;
(0, _util.assert)(signer, 'No signer specified, either via api.setSigner or via sign options. You possibly need to pass through an explicit keypair for the origin so it can be used for signing.');
const payload = this.registry.createTypeUnsafe('SignerPayload', [(0, _util.objectSpread)({}, options, {
address,
blockNumber: header ? header.number : 0,
method: this.method
})]);
let result;
if ((0, _util.isFunction)(signer.signPayload)) {
result = await signer.signPayload(payload.toPayload());
} else if ((0, _util.isFunction)(signer.signRaw)) {
result = await signer.signRaw(payload.toRaw());
} else {
throw new Error('Invalid signer interface, it should implement either signPayload or signRaw (or both)');
} // Here we explicitly call `toPayload()` again instead of working with an object
// (reference) as passed to the signer. This means that we are sure that the
// payload data is not modified from our inputs, but the signer
super.addSignature(address, result.signature, payload.toPayload());
return result.id;
};
#updateSigner = (status, info) => {
if (info && info.updateId !== -1) {
const {
options,
updateId
} = info;
const signer = options.signer || api.signer;
if (signer && (0, _util.isFunction)(signer.update)) {
signer.update(updateId, status);
}
}
};
}
return Submittable;
}