"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

var _rxjs = require("rxjs");

var _operators = require("rxjs/operators");

var _types = require("@polkadot/types");

var _util = require("@polkadot/util");

var _util2 = require("../util");

var _Result = _interopRequireDefault(require("./Result"));

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

// The default for 6s allowing for 5min eras. When translating this to faster blocks -
//   - 4s = (10 / 15) * 5 = 3.33m
//   - 2s = (10 / 30) * 5 = 1.66m
const BLOCKTIME = 6;
const ONE_MINUTE = 60 / BLOCKTIME;
const DEFAULT_MORTAL_LENGTH = 5 * ONE_MINUTE;

const _Extrinsic = (0, _types.ClassOf)('Extrinsic');

class Submittable extends _Extrinsic {
  constructor(extrinsic, _ref) {
    var _this;

    let {
      api,
      decorateMethod,
      type
    } = _ref;
    super(extrinsic, {
      version: api.extrinsicType
    });
    _this = this;
    this._api = void 0;
    this._decorateMethod = void 0;
    this._ignoreStatusCb = void 0;

    this._sendObservable = function () {
      let updateId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : -1;
      return _this._api.rpc.author.submitExtrinsic(_this).pipe((0, _operators.tap)(hash => {
        _this._updateSigner(updateId, hash);
      }));
    };

    this._subscribeObservable = function () {
      let updateId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : -1;
      return _this._api.rpc.author.submitAndWatchExtrinsic(_this).pipe((0, _operators.switchMap)(status => _this._statusObservable(status)), (0, _operators.tap)(status => {
        _this._updateSigner(updateId, status);
      }));
    };

    this._api = api;
    this._decorateMethod = decorateMethod;
    this._ignoreStatusCb = type === 'rxjs';
  } // sign a transaction, returning the this to allow cianing, i.e. .sign(...).send()


  sign(account, optionsOrNonce) {
    // NOTE 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 = (0, _util.isBn)(optionsOrNonce) || (0, _util.isNumber)(optionsOrNonce) ? {
      nonce: optionsOrNonce
    } : optionsOrNonce;
    super.sign(account, this._makeSignOptions(options, {}));
    return this;
  } // signAndSend with an immediate Hash result


  // signAndSend implementation for all 3 cases above
  signAndSend(account, optionsOrStatus, optionalStatusCb) {
    const [options, statusCb] = this._makeSignAndSendOptions(optionsOrStatus, optionalStatusCb);

    const isSubscription = this._api.hasSubscriptions && (this._ignoreStatusCb || !!statusCb);
    const address = (0, _util2.isKeyringPair)(account) ? account.address : account.toString();
    let updateId;
    return this._decorateMethod(() => this._getPrelimState(address, options).pipe((0, _operators.first)(), (0, _operators.mergeMap)(async (_ref2) => {
      let [nonce, header] = _ref2;

      const eraOptions = this._makeEraOptions(options, {
        header,
        nonce
      });

      if ((0, _util2.isKeyringPair)(account)) {
        this.sign(account, eraOptions);
      } else {
        updateId = await this._signViaSigner(address, eraOptions, header);
      }
    }), (0, _operators.switchMap)(() => {
      return isSubscription ? this._subscribeObservable(updateId) : this._sendObservable(updateId);
    })) // FIXME This is wrong, SubmittableResult is _not_ a codec
    )(statusCb);
  } // send with an immediate Hash result


  // send implementation for both immediate Hash and statusCb variants
  send(statusCb) {
    const isSubscription = this._api.hasSubscriptions && (this._ignoreStatusCb || !!statusCb);
    return this._decorateMethod(isSubscription ? this._subscribeObservable : this._sendObservable)(statusCb);
  }

  _makeSignAndSendOptions(optionsOrStatus, statusCb) {
    let options = {};

    if ((0, _util.isFunction)(optionsOrStatus)) {
      statusCb = optionsOrStatus;
    } else {
      options = _objectSpread({}, optionsOrStatus);
    }

    return [options, statusCb];
  }

  async _signViaSigner(address, optionsWithEra, header) {
    if (!this._api.signer) {
      throw new Error('no signer attached');
    }

    const payload = (0, _types.createType)('SignerPayload', _objectSpread({}, optionsWithEra, {
      address,
      method: this.method,
      blockNumber: header ? header.number : 0
    }));
    let result;

    if (this._api.signer.signPayload) {
      result = await this._api.signer.signPayload(payload.toPayload());
    } else if (this._api.signer.signRaw) {
      result = await this._api.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;
  }

  _makeSignOptions(options, extras) {
    return _objectSpread({
      blockHash: this._api.genesisHash
    }, options, {}, extras, {
      genesisHash: this._api.genesisHash,
      runtimeVersion: this._api.runtimeVersion,
      version: this._api.extrinsicType
    });
  }

  _makeEraOptions(options, _ref3) {
    let {
      header,
      nonce
    } = _ref3;

    if (!header) {
      if ((0, _util.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 this._makeSignOptions(options, {
        nonce
      });
    }

    return this._makeSignOptions(options, {
      blockHash: header.hash,
      era: (0, _types.createType)('ExtrinsicEra', {
        current: header.number,
        period: options.era || DEFAULT_MORTAL_LENGTH
      }),
      nonce
    });
  }

  _getPrelimState(address, options) {
    return (0, _rxjs.combineLatest)([// if we have a nonce already, don't retrieve the latest, use what is there
    (0, _util.isUndefined)(options.nonce) // FIXME This apparently is having issues on latest Kusama for nonce retrieval,
    // hence we are using the accountNonce only
    // ? this._api.rpc.account.nextIndex
    //   ? this._api.rpc.account.nextIndex(address)
    //   : this._api.query.system.accountNonce(address)
    ? this._api.query.system.accountNonce(address) : (0, _rxjs.of)((0, _types.createType)('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
    (0, _util.isUndefined)(options.era) || (0, _util.isNumber)(options.era) && options.era > 0 ? this._api.rpc.chain.getHeader() : (0, _rxjs.of)(null)]);
  }

  _updateSigner(updateId, status) {
    if (updateId !== -1 && this._api.signer && this._api.signer.update) {
      this._api.signer.update(updateId, status);
    }
  }

  _statusObservable(status) {
    if (!status.isFinalized) {
      return (0, _rxjs.of)(new _Result.default({
        status
      }));
    }

    const blockHash = status.asFinalized;
    return (0, _rxjs.combineLatest)([this._api.rpc.chain.getBlock(blockHash), this._api.query.system.events.at(blockHash)]).pipe((0, _operators.map)((_ref4) => {
      let [signedBlock, allEvents] = _ref4;
      return new _Result.default({
        events: (0, _util2.filterEvents)(this.hash, signedBlock, allEvents),
        status
      });
    }));
  }

}

exports.default = Submittable;