UNPKG

@sangaman/xud

Version:
294 lines 12.8 kB
"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