"use strict";

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

var _assert = _interopRequireDefault(require("assert"));

var _crypto = _interopRequireDefault(require("crypto"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }

function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }

function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }

function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }

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(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }

function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }

function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

// Custom RPC commands added on top of c-lightning's built-in ones
// and exposed to the client side.
var commands = {
  // Connect to node & fund channel in one go
  _connectfund: function _connectfund(peeruri, satoshi, feerate) {
    var _this = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
      var peerid, res;
      return regeneratorRuntime.wrap(function _callee$(_context) {
        while (1) {
          switch (_context.prev = _context.next) {
            case 0:
              peerid = peeruri.split('@')[0];
              _context.next = 3;
              return _this.connect(peeruri);

            case 3:
              _context.next = 5;
              return _this.fundchannel(peerid, satoshi, feerate);

            case 5:
              res = _context.sent;
              (0, _assert["default"])(res && res.channel_id, 'cannot open channel');
              return _context.abrupt("return", getChannel(_this, peerid, res.channel_id));

            case 8:
            case "end":
              return _context.stop();
          }
        }
      }, _callee);
    }))();
  } // Close channel and return its details
  ,
  _close: function _close(peerid, chanid, force, timeout) {
    var _this2 = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
      var res, _yield$getChannel, peer, chan;

      return regeneratorRuntime.wrap(function _callee2$(_context2) {
        while (1) {
          switch (_context2.prev = _context2.next) {
            case 0:
              _context2.next = 2;
              return _this2.close(chanid, force, timeout);

            case 2:
              res = _context2.sent;
              (0, _assert["default"])(res && res.txid, 'cannot close channel');
              _context2.next = 6;
              return getChannel(_this2, peerid, chanid);

            case 6:
              _yield$getChannel = _context2.sent;
              peer = _yield$getChannel.peer;
              chan = _yield$getChannel.chan;
              return _context2.abrupt("return", {
                peer: peer,
                chan: chan,
                closing: res
              });

            case 10:
            case "end":
              return _context2.stop();
          }
        }
      }, _callee2);
    }))();
  } // `listpays` with the addition of metadata extracted from the BOLT11/BOLT12 invoice, the
  // paystatus of pending payments, and a fix for payment hashes and timestamps which were missing
  // in c-lightning v0.9.0 (and may still be missing in newer releases for old payments made with v0.9).
  ,
  _listpays: function _listpays() {
    var _arguments = arguments,
        _this3 = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {
      var _yield$_this3$listpay, pays;

      return regeneratorRuntime.wrap(function _callee5$(_context5) {
        while (1) {
          switch (_context5.prev = _context5.next) {
            case 0:
              _context5.next = 2;
              return _this3.listpays.apply(_this3, _toConsumableArray(_arguments));

            case 2:
              _yield$_this3$listpay = _context5.sent;
              pays = _yield$_this3$listpay.pays;

              if (pays.length) {
                _context5.next = 6;
                break;
              }

              return _context5.abrupt("return", {
                pays: pays
              });

            case 6:
              // Fix for payments made with c-lightning v0.9.0
              pays.filter(function (p) {
                return p.preimage && !p.payment_hash;
              }).forEach(function (p) {
                return p.payment_hash = hash(p.preimage);
              });
              _context5.next = 9;
              return Promise.all(pays.filter(function (p) {
                return p.payment_hash && !p.created_at;
              }).map( /*#__PURE__*/function () {
                var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(p) {
                  var listpays;
                  return regeneratorRuntime.wrap(function _callee3$(_context3) {
                    while (1) {
                      switch (_context3.prev = _context3.next) {
                        case 0:
                          _context3.next = 2;
                          return _this3.listsendpays(null, p.payment_hash);

                        case 2:
                          listpays = _context3.sent;
                          p.created_at = listpays.payments[0].created_at;

                        case 4:
                        case "end":
                          return _context3.stop();
                      }
                    }
                  }, _callee3);
                }));

                return function (_x) {
                  return _ref.apply(this, arguments);
                };
              }()));

            case 9:
              _context5.next = 11;
              return Promise.all(pays.map(function (pay) {
                return attachInvoiceMeta(_this3, pay);
              }));

            case 11:
              _context5.next = 13;
              return Promise.all(pays.filter(function (p) {
                return p.status == 'pending' && (p.bolt11 || p.bolt12);
              }).map( /*#__PURE__*/function () {
                var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(p) {
                  var paystatus;
                  return regeneratorRuntime.wrap(function _callee4$(_context4) {
                    while (1) {
                      switch (_context4.prev = _context4.next) {
                        case 0:
                          _context4.next = 2;
                          return _this3.paystatus(p.bolt11 || p.bolt12);

                        case 2:
                          paystatus = _context4.sent.pay[0];
                          if (paystatus) p.attempts = paystatus.attempts;

                        case 4:
                        case "end":
                          return _context4.stop();
                      }
                    }
                  }, _callee4);
                }));

                return function (_x2) {
                  return _ref2.apply(this, arguments);
                };
              }()));

            case 13:
              truncatePayerNotes(pays);
              return _context5.abrupt("return", {
                pays: pays
              });

            case 15:
            case "end":
              return _context5.stop();
          }
        }
      }, _callee5);
    }))();
  } // `listinvoices` with the addition of metadata extracted from the BOLT11/BOLT12 invoice,
  // Returns paid invoices only.
  ,
  _listinvoices: function _listinvoices() {
    var _this4 = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee6() {
      var res, invoices;
      return regeneratorRuntime.wrap(function _callee6$(_context6) {
        while (1) {
          switch (_context6.prev = _context6.next) {
            case 0:
              _context6.next = 2;
              return _this4.listinvoices();

            case 2:
              res = _context6.sent;
              invoices = res.invoices.filter(function (i) {
                return i.status == 'paid';
              }); // Extract additional metadata from the BOLT12 invoice
              // Not needed for BOLT11 invoices, since the only relevant field they have is the 'description',
              // which c-lightning already makes available directly in the `listinvoices` reply.

              _context6.next = 6;
              return Promise.all(invoices.filter(function (invoice) {
                return !!invoice.bolt12;
              }).map(function (invoice) {
                return attachInvoiceMeta(_this4, invoice);
              }));

            case 6:
              truncatePayerNotes(invoices);
              return _context6.abrupt("return", {
                invoices: invoices
              });

            case 8:
            case "end":
              return _context6.stop();
          }
        }
      }, _callee6);
    }))();
  } // Wrapper for the 'decode'/'decodepay' commands with some convenience enhancements
  ,
  _decode: function _decode(paystr) {
    var _this5 = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee7() {
      var decoded, error_msg, _decoded;

      return regeneratorRuntime.wrap(function _callee7$(_context7) {
        while (1) {
          switch (_context7.prev = _context7.next) {
            case 0:
              _context7.next = 2;
              return checkOffersEnabled(_this5);

            case 2:
              if (!_context7.sent) {
                _context7.next = 13;
                break;
              }

              _context7.next = 5;
              return _this5.decode(paystr);

            case 5:
              decoded = _context7.sent;

              if (decoded.valid) {
                _context7.next = 9;
                break;
              }

              error_msg = extractWarnings(decoded).join(' · ');
              throw new Error("Invalid payment string: ".concat(error_msg || 'unknown error'));

            case 9:
              // Make BOLT12 msat amounts available as an integer, as they are for BOLT11 invoices
              if (decoded.msatoshi == null && decoded.amount_msat) decoded.msatoshi = +decoded.amount_msat.slice(0, -4);
              return _context7.abrupt("return", decoded);

            case 13:
              _context7.next = 15;
              return _this5.decodepay(paystr);

            case 15:
              _decoded = _context7.sent;
              return _context7.abrupt("return", _objectSpread(_objectSpread({}, _decoded), {}, {
                type: 'bolt11 invoice',
                valid: true
              }));

            case 17:
            case "end":
              return _context7.stop();
          }
        }
      }, _callee7);
    }))();
  } // Pay the given invoice, emit an event that can be observed externally (by stream.js),
  // and return it with the invoice metadata.
  ,
  _pay: function _pay(paystr) {
    var _arguments2 = arguments,
        _this6 = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee8() {
      var _len, args, _key, pay_result;

      return regeneratorRuntime.wrap(function _callee8$(_context8) {
        while (1) {
          switch (_context8.prev = _context8.next) {
            case 0:
              _this6.emit('paying', paystr);

              for (_len = _arguments2.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
                args[_key - 1] = _arguments2[_key];
              }

              _context8.next = 4;
              return _this6.pay.apply(_this6, [paystr].concat(args));

            case 4:
              pay_result = _context8.sent;
              _context8.next = 7;
              return attachInvoiceMeta(_this6, pay_result);

            case 7:
              return _context8.abrupt("return", pay_result);

            case 8:
            case "end":
              return _context8.stop();
          }
        }
      }, _callee8);
    }))();
  } // Fetch an invoice for the given offer, decode it and return the original offer alongside it
  // Some parameters are unsupported (recurrence/timeout).
  ,
  _fetchinvoice: function _fetchinvoice(bolt12_offer, msatoshi, quantity, payer_note) {
    var _this7 = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee9() {
      var _yield$_this7$fetchin, bolt12_invoice, changes, invoice, offer;

      return regeneratorRuntime.wrap(function _callee9$(_context9) {
        while (1) {
          switch (_context9.prev = _context9.next) {
            case 0:
              _context9.next = 2;
              return _this7.fetchinvoice(bolt12_offer, msatoshi, quantity, null, null, null, null, payer_note);

            case 2:
              _yield$_this7$fetchin = _context9.sent;
              bolt12_invoice = _yield$_this7$fetchin.invoice;
              changes = _yield$_this7$fetchin.changes;
              _context9.next = 7;
              return _this7._decode(bolt12_invoice);

            case 7:
              invoice = _context9.sent;
              (0, _assert["default"])(invoice.type == 'bolt12 invoice', "Unexpected invoice type ".concat(invoice.type));
              _context9.next = 11;
              return _this7._decode(bolt12_offer);

            case 11:
              offer = _context9.sent;
              return _context9.abrupt("return", _objectSpread({
                paystr: bolt12_invoice,
                offer: offer,
                changes: changes
              }, invoice));

            case 13:
            case "end":
              return _context9.stop();
          }
        }
      }, _callee9);
    }))();
  } // Decode the payment string and verify that it is supported by Spark
  ,
  _decodecheck: function _decodecheck(paystr) {
    var _this8 = this;

    return _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee10() {
      var decoded;
      return regeneratorRuntime.wrap(function _callee10$(_context10) {
        while (1) {
          switch (_context10.prev = _context10.next) {
            case 0:
              _context10.next = 2;
              return _this8._decode(paystr);

            case 2:
              decoded = _context10.sent;
              _context10.t0 = decoded.type;
              _context10.next = _context10.t0 === 'bolt12 offer' ? 6 : _context10.t0 === 'bolt11 invoice' ? 11 : _context10.t0 === 'bolt12 invoice' ? 11 : 12;
              break;

            case 6:
              (0, _assert["default"])(!decoded.recurrence, 'Offers with recurrence are unsupported');
              (0, _assert["default"])(decoded.quantity_min == null || decoded.msatoshi || decoded.amount, 'Offers with quantity but no payment amount are unsupported');
              (0, _assert["default"])(!decoded.send_invoice || decoded.msatoshi, 'send_invoice offers with no amount are unsupported');
              (0, _assert["default"])(!decoded.send_invoice || decoded.min_quantity == null, 'send_invoice offers with quantity are unsupported');
              return _context10.abrupt("break", 13);

            case 11:
              return _context10.abrupt("break", 13);

            case 12:
              throw new Error("Unhandled payment string type: ".concat(decoded.type));

            case 13:
              return _context10.abrupt("return", decoded);

            case 14:
            case "end":
              return _context10.stop();
          }
        }
      }, _callee10);
    }))();
  } // `listconfigs` with caching
  ,
  _listconfigs: function _listconfigs() {
    var _this9 = this;

    return this._configs || (this._configs = this.listconfigs()["catch"](function (err) {
      delete _this9._configs;
      return Promise.reject(err);
    }));
  }
};
exports.commands = commands;

function getChannel(_x3, _x4, _x5) {
  return _getChannel.apply(this, arguments);
} // Check if experimental offers/bolt12 support is enabled
// Always considered off in c-lightning <=v0.10.0 because it used an incompatible spec.


function _getChannel() {
  _getChannel = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee11(ln, peerid, chanid) {
    var peer, chan;
    return regeneratorRuntime.wrap(function _callee11$(_context11) {
      while (1) {
        switch (_context11.prev = _context11.next) {
          case 0:
            _context11.next = 2;
            return ln.listpeers(peerid).then(function (r) {
              return r.peers[0];
            });

          case 2:
            peer = _context11.sent;
            (0, _assert["default"])(peer, 'cannot find peer');
            chan = peer.channels.find(function (chan) {
              return chan.channel_id == chanid;
            });
            (0, _assert["default"])(chan, 'cannot find channel');
            delete peer.channels;
            return _context11.abrupt("return", {
              peer: peer,
              chan: chan
            });

          case 8:
          case "end":
            return _context11.stop();
        }
      }
    }, _callee11);
  }));
  return _getChannel.apply(this, arguments);
}

function checkOffersEnabled(_x6) {
  return _checkOffersEnabled.apply(this, arguments);
} // Timestamp of the c-lightning v0.10.1 release. BOLT12 invoices created in v0.10.0 are
// incompatible and therefore ignored. Using the timestamp to detect them is prune to
// false positives/negatives, but its the best that can be done.


function _checkOffersEnabled() {
  _checkOffersEnabled = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee12(ln) {
    var conf;
    return regeneratorRuntime.wrap(function _callee12$(_context12) {
      while (1) {
        switch (_context12.prev = _context12.next) {
          case 0:
            _context12.next = 2;
            return ln._listconfigs();

          case 2:
            conf = _context12.sent;
            return _context12.abrupt("return", conf['experimental-offers'] && !/(^v?|-v)0\.(9\.|10\.0)/.test(conf['# version']));

          case 4:
          case "end":
            return _context12.stop();
        }
      }
    }, _callee12);
  }));
  return _checkOffersEnabled.apply(this, arguments);
}

var CLN_0_10_1_TS = 1628557020;

var isCompatibleBolt12 = function isCompatibleBolt12(obj) {
  return obj.bolt12 && (obj.created_at || obj.paid_at) >= CLN_0_10_1_TS;
}; // Get and attach metadata extracted from the bolt11/bolt12 of invoices/payments


function attachInvoiceMeta(_x7, _x8) {
  return _attachInvoiceMeta.apply(this, arguments);
}

function _attachInvoiceMeta() {
  _attachInvoiceMeta = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee13(ln, obj) {
    var invoice;
    return regeneratorRuntime.wrap(function _callee13$(_context13) {
      while (1) {
        switch (_context13.prev = _context13.next) {
          case 0:
            if (!(obj.bolt11 || isCompatibleBolt12(obj))) {
              _context13.next = 15;
              break;
            }

            _context13.prev = 1;
            _context13.next = 4;
            return ln._decode(obj.bolt11 || obj.bolt12);

          case 4:
            invoice = _context13.sent;
            extendInvoiceMeta(obj, invoice);
            _context13.next = 15;
            break;

          case 8:
            _context13.prev = 8;
            _context13.t0 = _context13["catch"](1);

            if (!obj.bolt12) {
              _context13.next = 14;
              break;
            }

            console.error('Failed decoding pay string, deprecated old-style bolt12?', obj);
            _context13.next = 15;
            break;

          case 14:
            throw _context13.t0;

          case 15:
          case "end":
            return _context13.stop();
        }
      }
    }, _callee13, null, [[1, 8]]);
  }));
  return _attachInvoiceMeta.apply(this, arguments);
}

var extendInvoiceMeta = function extendInvoiceMeta(pay, invoice) {
  return ['description', 'vendor', 'quantity', 'payer_note', 'offer_id', 'amount_msat'].filter(function (k) {
    return invoice[k] != null && (pay[k] == null || possiblyBuggyAmount(pay, k));
  }).forEach(function (k) {
    return pay[k] = invoice[k];
  });
}; // Pending multi-part payments sometimes report an incorrect amount,
// override it with the correct amount from the BOLT11/12 invoice.
// See https://github.com/ElementsProject/lightning/issues/4753


var possiblyBuggyAmount = function possiblyBuggyAmount(pay, k) {
  return k == 'amount_msat' && pay.status == 'pending' && pay.number_of_parts > 1 && pay.amount_msat == pay.amount_sent_msat;
};

var extractWarnings = function extractWarnings(obj) {
  return Object.entries(obj).filter(function (_ref3) {
    var _ref4 = _slicedToArray(_ref3, 2),
        k = _ref4[0],
        _ = _ref4[1];

    return k.startsWith('warning_');
  }).map(function (_ref5) {
    var _ref6 = _slicedToArray(_ref5, 2),
        _ = _ref6[0],
        v = _ref6[1];

    return v;
  });
}; // Truncate long `payer_note`s. They can get pretty big - up to 32kb


var truncatePayerNotes = function truncatePayerNotes(elements) {
  return elements.filter(function (el) {
    return el.payer_note && el.payer_note.length > 1024;
  }).forEach(function (el) {
    el.payer_note = el.payer_note.substr(0, 1024) + '…';
    el.payer_note_truncated = true;
  });
};

var hash = function hash(preimage) {
  return _crypto["default"].createHash('sha256').update(Buffer.from(preimage, 'hex')).digest('hex');
};