@sangaman/xud
Version:
Exchange Union Daemon
294 lines • 12.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const grpc_1 = __importDefault(require("grpc"));
const fs_1 = __importDefault(require("fs"));
const BaseClient_1 = __importStar(require("../BaseClient"));
const errors_1 = __importDefault(require("./errors"));
const lndrpc_grpc_pb_1 = require("../proto/lndrpc_grpc_pb");
const lndrpc = __importStar(require("../proto/lndrpc_pb"));
/** A class representing a client to interact with lnd. */
class LndClient extends BaseClient_1.default {
/**
* Create an lnd client.
* @param config the lnd configuration
*/
constructor(config, logger) {
super(logger);
this.unaryCall = (methodName, params) => {
return new Promise((resolve, reject) => {
if (this.isDisabled()) {
reject(errors_1.default.LND_IS_DISABLED);
return;
}
this.lightning[methodName](params, this.meta, (err, response) => {
if (err) {
reject(err);
}
else {
resolve(response.toObject());
}
});
});
};
this.getLndInfo = () => __awaiter(this, void 0, void 0, function* () {
let channels;
let chains;
let blockheight;
let uris;
let version;
let error;
if (this.isDisabled()) {
error = errors_1.default.LND_IS_DISABLED.message;
}
else if (!this.isConnected()) {
error = errors_1.default.LND_IS_DISCONNECTED.message;
}
else {
try {
const lnd = yield this.getInfo();
channels = {
active: lnd.numActiveChannels,
pending: lnd.numPendingChannels,
};
chains = lnd.chainsList,
blockheight = lnd.blockHeight,
uris = lnd.urisList,
version = lnd.version;
}
catch (err) {
this.logger.error(`LND error: ${err}`);
error = err.message;
}
}
return {
channels,
chains,
blockheight,
uris,
version,
};
});
/**
* Verify that the lnd gRPC service can be reached by attempting a `getInfo` call.
* If successful, subscribe to invoice events and store the lnd identity pubkey.
* If not, set a timer to attempt to reach the service again in 5 seconds.
*/
this.verifyConnection = () => __awaiter(this, void 0, void 0, function* () {
if (this.isDisabled()) {
throw (errors_1.default.LND_IS_DISABLED);
}
if (this.isConnected()) {
this.logger.warn(`not verifying connection to lnd, lnd is already connected`);
return;
}
if (this.isDisconnected()) {
this.logger.info(`trying to verify connection to lnd with uri: ${this.uri}`);
this.lightning = new lndrpc_grpc_pb_1.LightningClient(this.uri, this.credentials);
try {
const getInfoResponse = yield this.getInfo();
if (getInfoResponse) {
// mark connection as active
this.setStatus(BaseClient_1.ClientStatus.CONNECTION_VERIFIED);
this.identityPubKey = getInfoResponse.identityPubkey;
this.subscribeInvoices();
if (this.reconnectionTimer) {
clearTimeout(this.reconnectionTimer);
this.reconnectionTimer = undefined;
}
}
}
catch (err) {
this.setStatus(BaseClient_1.ClientStatus.DISCONNECTED);
this.logger.error(`could not verify connection to lnd at ${this.uri}, error: ${JSON.stringify(err)}, retrying in 5000 ms`);
this.reconnectionTimer = setTimeout(this.verifyConnection, 5000);
}
}
});
/**
* Return general information concerning the lightning node including it’s identity pubkey, alias, the chains it
* is connected to, and information concerning the number of open+pending channels.
*/
this.getInfo = () => {
return this.unaryCall('getInfo', new lndrpc.GetInfoRequest());
};
/**
* Attempt to add a new invoice to the lnd invoice database.
* @param value the value of this invoice in satoshis
*/
this.addInvoice = (value) => {
const request = new lndrpc.Invoice();
request.setValue(value);
return this.unaryCall('addInvoice', request);
};
/**
* Pay an invoice through the Lightning Network.
* @param payment_request an invoice for a payment within the Lightning Network
*/
this.payInvoice = (paymentRequest) => {
const request = new lndrpc.SendRequest();
request.setPaymentRequest(paymentRequest);
return this.unaryCall('sendPaymentSync', request);
};
/**
* Get a new address for the internal lnd wallet.
*/
this.newAddress = (addressType) => {
const request = new lndrpc.NewAddressRequest();
request.setType(addressType);
return this.unaryCall('newAddress', request);
};
/**
* Return the total of unspent outputs for the internal lnd wallet.
*/
this.walletBalance = () => {
return this.unaryCall('walletBalance', new lndrpc.WalletBalanceRequest());
};
/**
* Return the total funds available across all channels.
*/
this.channelBalance = () => {
return this.unaryCall('channelBalance', new lndrpc.ChannelBalanceRequest());
};
/**
* Connect to another lnd node.
*/
this.connectPeer = (pubkey, host, port) => {
const request = new lndrpc.ConnectPeerRequest();
const address = new lndrpc.LightningAddress();
address.setHost(`${host}:${port}`);
address.setPubkey(pubkey);
request.setAddr(address);
return this.unaryCall('connectPeer', request);
};
/**
* Open a channel with a connected lnd node.
*/
this.openChannel = (node_pubkey_string, local_funding_amount) => {
const request = new lndrpc.OpenChannelRequest;
request.setNodePubkeyString(node_pubkey_string);
request.setLocalFundingAmount(local_funding_amount);
return this.unaryCall('openChannelSync', request);
};
/**
* List all open channels for this node.
*/
this.listChannels = () => {
return this.unaryCall('listChannels', new lndrpc.ListChannelsRequest());
};
/**
* Attempt to close an open channel.
*/
this.closeChannel = (fundingTxId, outputIndex, force) => {
if (this.isDisabled()) {
throw (errors_1.default.LND_IS_DISABLED);
}
if (this.isDisconnected()) {
throw (errors_1.default.LND_IS_DISCONNECTED);
}
const request = new lndrpc.CloseChannelRequest();
const channelPoint = new lndrpc.ChannelPoint();
channelPoint.setFundingTxidStr(fundingTxId);
channelPoint.setOutputIndex(outputIndex);
request.setChannelPoint(channelPoint);
request.setForce(force);
this.lightning.closeChannel(request, this.meta)
// TODO: handle close channel events
.on('data', (message) => {
this.logger.info(`closeChannel update: ${message}`);
})
.on('end', () => {
this.logger.info('closeChannel ended');
})
.on('status', (status) => {
this.logger.debug(`closeChannel status: ${JSON.stringify(status)}`);
})
.on('error', (error) => {
this.logger.error(`closeChannel error: ${error}`);
});
};
/**
* Subscribe to events for when invoices are settled.
*/
this.subscribeInvoices = () => {
if (this.isDisabled()) {
throw (errors_1.default.LND_IS_DISABLED);
}
if (this.isDisconnected()) {
throw (errors_1.default.LND_IS_DISCONNECTED);
}
this.invoiceSubscription = this.lightning.subscribeInvoices(new lndrpc.InvoiceSubscription(), this.meta)
// TODO: handle invoice events
.on('data', (message) => {
this.logger.info(`invoice update: ${message}`);
})
.on('end', () => __awaiter(this, void 0, void 0, function* () {
this.logger.info('invoice ended');
this.setStatus(BaseClient_1.ClientStatus.DISCONNECTED);
yield this.verifyConnection();
}))
.on('status', (status) => {
this.logger.debug(`invoice status: ${JSON.stringify(status)}`);
})
.on('error', (error) => __awaiter(this, void 0, void 0, function* () {
this.logger.error(`invoice error: ${error}`);
this.setStatus(BaseClient_1.ClientStatus.DISCONNECTED);
yield this.verifyConnection();
}));
};
/** End all subscriptions and reconnection attempts. */
this.close = () => {
if (this.reconnectionTimer) {
clearTimeout(this.reconnectionTimer);
}
if (this.invoiceSubscription) {
this.invoiceSubscription.cancel();
}
};
let shouldEnable = true;
const { disable, certpath, macaroonpath } = config;
if (disable) {
shouldEnable = false;
}
if (!fs_1.default.existsSync(certpath)) {
this.logger.error('could not find lnd certificate, is lnd installed?');
shouldEnable = false;
}
if (!fs_1.default.existsSync(macaroonpath)) {
this.logger.error('could not find lnd macaroon, is lnd installed?');
shouldEnable = false;
}
if (shouldEnable) {
this.uri = `${config.host}:${config.port}`;
const lndCert = fs_1.default.readFileSync(certpath);
this.credentials = grpc_1.default.credentials.createSsl(lndCert);
const adminMacaroon = fs_1.default.readFileSync(macaroonpath);
this.meta = new grpc_1.default.Metadata();
this.meta.add('macaroon', adminMacaroon.toString('hex'));
// mark connection as disconnected
this.setStatus(BaseClient_1.ClientStatus.DISCONNECTED);
}
}
get pubKey() {
return this.identityPubKey;
}
}
exports.default = LndClient;
//# sourceMappingURL=LndClient.js.map