lndhub/class/Paym.js
2021-09-22 13:46:51 +01:00

156 lines
4.7 KiB
JavaScript

var crypto = require('crypto');
var lightningPayReq = require('bolt11');
import { BigNumber } from 'bignumber.js';
export class Paym {
constructor(redis, bitcoindrpc, lightning) {
this._redis = redis;
this._bitcoindrpc = bitcoindrpc;
this._lightning = lightning;
this._decoded = false;
this._bolt11 = false;
this._isPaid = null;
}
setInvoice(bolt11) {
this._bolt11 = bolt11;
}
async decodePayReqViaRpc(invoice) {
let that = this;
return new Promise(function (resolve, reject) {
that._lightning.decodePayReq({ pay_req: invoice }, function (err, info) {
if (err) return reject(err);
that._decoded = info;
return resolve(info);
});
});
}
async queryRoutes() {
if (!this._bolt11) throw new Error('bolt11 is not provided');
if (!this._decoded) await this.decodePayReqViaRpc(this._bolt11);
var request = {
pub_key: this._decoded.destination,
amt: this._decoded.num_satoshis,
final_cltv_delta: 144,
fee_limit: { fixed: Math.floor(this._decoded.num_satoshis * forwardFee) + 1 },
};
let that = this;
return new Promise(function (resolve, reject) {
that._lightning.queryRoutes(request, function (err, response) {
if (err) return reject(err);
resolve(response);
});
});
}
async sendToRouteSync(routes) {
if (!this._bolt11) throw new Error('bolt11 is not provided');
if (!this._decoded) await this.decodePayReqViaRpc(this._bolt11);
let request = {
payment_hash_string: this._decoded.payment_hash,
route: routes[0],
};
console.log('sendToRouteSync:', { request });
let that = this;
return new Promise(function (resolve, reject) {
that._lightning.sendToRouteSync(request, function (err, response) {
if (err) reject(err);
resolve(that.processSendPaymentResponse(response));
});
});
}
processSendPaymentResponse(payment) {
if (payment && payment.payment_route && payment.payment_route.total_amt_msat) {
// paid just now
this._isPaid = true;
payment.payment_route.total_fees = +payment.payment_route.total_fees + Math.floor(+payment.payment_route.total_amt * internalFee);
if (this._bolt11) payment.pay_req = this._bolt11;
if (this._decoded) payment.decoded = this._decoded;
}
if (payment.payment_error && payment.payment_error.indexOf('already paid') !== -1) {
// already paid
this._isPaid = true;
if (this._decoded) {
payment.decoded = this._decoded;
if (this._bolt11) payment.pay_req = this._bolt11;
// trying to guess the fee
payment.payment_route = payment.payment_route || {};
payment.payment_route.total_fees = Math.floor(this._decoded.num_satoshis * forwardFee); // we dont know the exact fee, so we use max (same as fee_limit)
payment.payment_route.total_amt = this._decoded.num_satoshis;
}
}
if (payment.payment_error && payment.payment_error.indexOf('unable to') !== -1) {
// failed to pay
this._isPaid = false;
}
if (payment.payment_error && payment.payment_error.indexOf('FinalExpiryTooSoon') !== -1) {
this._isPaid = false;
}
if (payment.payment_error && payment.payment_error.indexOf('UnknownPaymentHash') !== -1) {
this._isPaid = false;
}
if (payment.payment_error && payment.payment_error.indexOf('IncorrectOrUnknownPaymentDetails') !== -1) {
this._isPaid = false;
}
if (payment.payment_error && payment.payment_error.indexOf('payment is in transition') !== -1) {
this._isPaid = null; // null is default, but lets set it anyway
}
return payment;
}
/**
* Returns NULL if unknown, true if its paid, false if its unpaid
* (judging by error in sendPayment response)
*
* @returns {boolean|null}
*/
getIsPaid() {
return this._isPaid;
}
async attemptPayToRoute() {
let routes = await this.queryRoutes();
return await this.sendToRouteSync(routes.routes);
}
async listPayments() {
return new Promise((resolve, reject) => {
this._lightning.listPayments({}, function (err, response) {
if (err) return reject(err);
resolve(response);
});
});
}
async isExpired() {
if (!this._bolt11) throw new Error('bolt11 is not provided');
const decoded = await this.decodePayReqViaRpc(this._bolt11);
return +decoded.timestamp + +decoded.expiry < +new Date() / 1000;
}
decodePayReqLocally(payReq) {
this._decoded_locally = lightningPayReq.decode(payReq);
}
async getPaymentHash() {
if (!this._bolt11) throw new Error('bolt11 is not provided');
if (!this._decoded) await this.decodePayReqViaRpc(this._bolt11);
return this._decoded['payment_hash'];
}
}