UNPKG

@synonymdev/react-native-ldk

Version:
1,022 lines (1,021 loc) 76.8 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 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) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import ldk from './ldk'; import { err, ok } from './utils/result'; import { DefaultTransactionDataShape, EEventTypes, ELdkData, ELdkFiles, ELdkLogLevels, ENetworks, defaultUserConfig, } from './utils/types'; import { appendPath, findOutputsFromRawTxs, getTxIdFromRawTx, isValidBech32mEncodedString, parseData, promiseTimeout, sleep, startParamCheck, } from './utils/helpers'; import * as bitcoin from 'bitcoinjs-lib'; import networks from './utils/networks'; import decodeRawTx from '@synonymdev/raw-transaction-decoder'; const MAX_PENDING_PAY_AGE = 1000 * 60 * 60; class LightningManager { constructor() { this.currentBlock = { hex: '', hash: '', height: 0, }; this.unconfirmedTxs = []; this.watchTxs = []; this.watchOutputs = []; this.confirmedWatchOutputs = []; this.getBestBlock = () => __awaiter(this, void 0, void 0, function* () { return ({ hex: '', hash: '', height: 0, }); }); this.account = { name: '', seed: '', }; this.getTransactionData = () => __awaiter(this, void 0, void 0, function* () { return DefaultTransactionDataShape; }); this.getTransactionPosition = () => __awaiter(this, void 0, void 0, function* () { return -1; }); this.network = ENetworks.regtest; this.baseStoragePath = ''; this.logFilePath = ''; this.getAddress = () => __awaiter(this, void 0, void 0, function* () { return ({ address: '', publicKey: '', }); }); this.addresses = []; this.getScriptPubKeyHistory = () => __awaiter(this, void 0, void 0, function* () { return []; }); this.getFees = () => __awaiter(this, void 0, void 0, function* () { return ({ nonAnchorChannelFee: 5, anchorChannelFee: 5, channelCloseMinimum: 5, minAllowedAnchorChannelRemoteFee: 5, minAllowedNonAnchorChannelRemoteFee: 5, outputSpendingFee: 5, maximumFeeEstimate: 5, urgentOnChainSweep: 5, }); }); this.broadcastTransaction = () => __awaiter(this, void 0, void 0, function* () { }); this.isSyncing = false; this.pendingSyncPromises = []; this.isStarting = false; this.pendingStartPromises = []; this.previousAccountName = ''; this.handleStartError = (e) => { this.isStarting = false; this.resolveAllPendingStartPromises(e); return e; }; this.retrySyncOrReturnError = ({ timeout = 20000, retryAttempts, e, }) => __awaiter(this, void 0, void 0, function* () { this.isSyncing = false; if ((e === null || e === void 0 ? void 0 : e.code) === 'timed-out') { return this.handleSyncError(e); } if (retryAttempts > 0) { yield sleep(); return this.syncLdk({ timeout, retryAttempts: retryAttempts - 1, }); } else { return this.handleSyncError(e); } }); this.handleSyncError = (e) => { this.isSyncing = false; this.resolveAllPendingSyncPromises(e); return e; }; this.checkWatchTxs = (watchTxs, channels, bestBlock) => __awaiter(this, void 0, void 0, function* () { const height = bestBlock === null || bestBlock === void 0 ? void 0 : bestBlock.height; if (!height) { return err('No height provided'); } for (const watchTxData of watchTxs) { const { txid, script_pubkey } = watchTxData; let requiredConfirmations = 1; const channel = channels.find((c) => c.funding_txid === txid); if (channel && (channel === null || channel === void 0 ? void 0 : channel.confirmations_required) !== undefined) { requiredConfirmations = channel.confirmations_required; } const txData = yield this.getTransactionData(txid); if (!txData) { continue; } if (!(txData === null || txData === void 0 ? void 0 : txData.transaction)) { console.log('Unable to retrieve transaction data from the getTransactionData method.'); continue; } const txConfirmations = txData.height === 0 ? 0 : height - txData.height + 1; if (txConfirmations >= requiredConfirmations) { const pos = yield this.getTransactionPosition({ tx_hash: txid, height: txData.height, }); if (pos >= 0) { const setTxConfirmedRes = yield ldk.setTxConfirmed({ header: txData.header, height: txData.height, txData: [{ transaction: txData.transaction, pos }], }); if (setTxConfirmedRes.isOk()) { yield this.saveUnconfirmedTx(Object.assign(Object.assign({}, txData), { txid, script_pubkey })); this.watchTxs = this.watchTxs.filter((tx) => tx.txid !== txid); } } } } return ok('Watch transactions checked'); }); this.resetAddressFileIfUnused = () => __awaiter(this, void 0, void 0, function* () { const channelFilesRes = yield ldk.listChannelFiles(); if (channelFilesRes.isErr()) { return; } if (channelFilesRes.value.length > 0) { return; } const accountPath = appendPath(this.baseStoragePath, this.account.name); const writeRes = yield ldk.writeToFile({ fileName: ELdkFiles.addresses, path: accountPath, content: JSON.stringify([]), remotePersist: false, }); this.addresses = []; if (writeRes.isErr()) { console.error(writeRes.error); } }); this.saveAddressToFile = (address) => __awaiter(this, void 0, void 0, function* () { if (!address) { return err('No address provided'); } if (this.addresses.includes(address)) { return ok(true); } this.addresses.push(address); const accountPath = appendPath(this.baseStoragePath, this.account.name); const writeRes = yield ldk.writeToFile({ fileName: ELdkFiles.addresses, path: accountPath, content: JSON.stringify(this.addresses), remotePersist: true, }); if (writeRes.isErr()) { return err(writeRes.error.message); } return ok(true); }); this.readAddressesFromFile = () => __awaiter(this, void 0, void 0, function* () { const accountPath = appendPath(this.baseStoragePath, this.account.name); const writeRes = yield ldk.readFromFile({ fileName: ELdkFiles.addresses, path: accountPath, }); if (writeRes.isOk()) { return parseData(writeRes.value.content, []); } return []; }); this.saveConfirmedWatchOutput = (confirmedWatchOutput) => __awaiter(this, void 0, void 0, function* () { if (!confirmedWatchOutput) { return err('No confirmedWatchOutput provided to saveConfirmedWatchOutput.'); } if (this.confirmedWatchOutputs.includes(confirmedWatchOutput)) { return ok(true); } this.confirmedWatchOutputs.push(confirmedWatchOutput); const accountPath = appendPath(this.baseStoragePath, this.account.name); const writeRes = yield ldk.writeToFile({ fileName: ELdkFiles.confirmed_watch_outputs, path: accountPath, content: JSON.stringify(this.confirmedWatchOutputs), remotePersist: true, }); if (writeRes.isErr()) { return err(writeRes.error.message); } return ok(true); }); this.getConfirmedWatchOutputs = () => __awaiter(this, void 0, void 0, function* () { const accountPath = appendPath(this.baseStoragePath, this.account.name); const writeRes = yield ldk.readFromFile({ fileName: ELdkFiles.confirmed_watch_outputs, path: accountPath, }); if (writeRes.isOk()) { return parseData(writeRes.value.content, []); } return []; }); this.checkWatchOutputs = (watchOutputs) => __awaiter(this, void 0, void 0, function* () { var _a, _b; for (const { index, script_pubkey } of watchOutputs) { if (this.confirmedWatchOutputs.includes(script_pubkey)) { this.watchOutputs = this.watchOutputs.filter((o) => o.script_pubkey !== script_pubkey); continue; } const transactions = yield this.getScriptPubKeyHistory(script_pubkey); for (const { txid } of transactions) { const transactionData = yield this.getTransactionData(txid); if (!transactionData) { continue; } if (!(transactionData === null || transactionData === void 0 ? void 0 : transactionData.height) && ((_a = transactionData === null || transactionData === void 0 ? void 0 : transactionData.vout) === null || _a === void 0 ? void 0 : _a.length) < index + 1) { continue; } if (!(transactionData === null || transactionData === void 0 ? void 0 : transactionData.vout[index])) { continue; } const txs = yield this.getScriptPubKeyHistory(transactionData === null || transactionData === void 0 ? void 0 : transactionData.vout[index].hex); if (txs.length <= 1) { continue; } const fallbackIndex = 1; const fallbackTx = txs[fallbackIndex]; let useFallback = true; const txsLength = txs.length - 1; let transactionId; let txData; let fallbackTxData; for (let i = txsLength; i >= 0; i--) { yield sleep(100); const tx = txs[i]; if (!(tx === null || tx === void 0 ? void 0 : tx.txid)) { continue; } const txDataRes = yield this.getTransactionData(tx.txid); if (!txDataRes || !(txDataRes === null || txDataRes === void 0 ? void 0 : txDataRes.height) || !(txDataRes === null || txDataRes === void 0 ? void 0 : txDataRes.transaction)) { if (!txDataRes) { console.error('No txDataRes'); } continue; } if (i === fallbackIndex) { fallbackTxData = txDataRes; } const decodedTxRes = decodeRawTx(txDataRes === null || txDataRes === void 0 ? void 0 : txDataRes.transaction); if (decodedTxRes.isErr()) { console.error('Error decoding transaction', decodedTxRes.error.message); continue; } const decodedTx = decodedTxRes.value; const decodedOutputs = (_b = decodedTx === null || decodedTx === void 0 ? void 0 : decodedTx.outputs) !== null && _b !== void 0 ? _b : []; let decodedAddresses = []; decodedOutputs.map((o) => { var _a; if ((_a = o === null || o === void 0 ? void 0 : o.scriptPubKey) === null || _a === void 0 ? void 0 : _a.addresses) { decodedAddresses = decodedAddresses.concat(o.scriptPubKey.addresses); } }); for (const address of decodedAddresses) { if (this.addresses.includes(address)) { transactionId = tx.txid; txData = txDataRes; useFallback = false; break; } } if (transactionId && txData) { break; } } if (useFallback) { transactionId = fallbackTx === null || fallbackTx === void 0 ? void 0 : fallbackTx.txid; if (!transactionId) { continue; } txData = fallbackTxData !== null && fallbackTxData !== void 0 ? fallbackTxData : (yield this.getTransactionData(transactionId)); } if (!transactionId || !(txData === null || txData === void 0 ? void 0 : txData.height)) { continue; } const pos = yield this.getTransactionPosition({ tx_hash: transactionId, height: txData.height, }); if (pos >= 0) { const setTxConfirmedRes = yield ldk.setTxConfirmed({ header: txData.header, height: txData.height, txData: [{ transaction: txData.transaction, pos }], }); if (setTxConfirmedRes.isOk()) { yield this.saveUnconfirmedTx(Object.assign(Object.assign({}, txData), { txid: transactionId, script_pubkey })); yield this.saveConfirmedWatchOutput(script_pubkey); this.watchOutputs = this.watchOutputs.filter((o) => o.script_pubkey !== script_pubkey); } } } } return ok('Watch outputs checked'); }); this.addPeer = ({ pubKey, address, port, timeout, }) => __awaiter(this, void 0, void 0, function* () { const peer = { pubKey, address, port }; const addPeerResponse = yield ldk.addPeer(Object.assign(Object.assign({}, peer), { timeout })); if (addPeerResponse.isErr()) { return err(addPeerResponse.error.message); } this.saveLdkPeerData(peer).then().catch(console.error); return ok(addPeerResponse.value); }); this.removePeer = ({ pubKey, address, port, }) => __awaiter(this, void 0, void 0, function* () { const peers = yield this.getPeers(); const newPeers = peers.filter((p) => p.pubKey !== pubKey && p.address !== address && p.port !== port); const writeRes = yield ldk.writeToFile({ fileName: ELdkFiles.peers, content: JSON.stringify(newPeers), remotePersist: true, }); if (writeRes.isErr()) { return err(writeRes.error); } return ok(newPeers); }); this.getPeers = () => __awaiter(this, void 0, void 0, function* () { const res = yield ldk.readFromFile({ fileName: ELdkFiles.peers }); if (res.isOk()) { return parseData(res.value.content, []); } return []; }); this.restoreFromRemoteServer = ({ account, serverDetails, network, overwrite = false, }) => __awaiter(this, void 0, void 0, function* () { const nodeIdRes = yield ldk.nodeId(); if (nodeIdRes.isOk()) { return err('Cannot restore while LDK is running.'); } const storageSetRes = yield this.setStorage(account); if (storageSetRes.isErr()) { return err(storageSetRes.error); } const backupSetupRes = yield ldk.backupSetup({ seed: account.seed, network, details: serverDetails, }); if (backupSetupRes.isErr()) { return err(backupSetupRes.error); } const restoreRes = yield ldk.restoreFromRemoteBackup({ overwrite, }); if (restoreRes.isErr()) { return err(restoreRes.error); } return ok('Restored from remote server.'); }); this.importAccount = ({ backup, overwrite = false, }) => __awaiter(this, void 0, void 0, function* () { var _c, _d; console.warn('importAccount() is deprecated and should only be used to import accounts that were backed up with a previous async method.'); if (!this.baseStoragePath) { return err('baseStoragePath required for wallet persistence. Call setBaseStoragePath(path) first.'); } try { if (!backup) { return err('No backup was provided for import.'); } let accountBackup; if (typeof backup === 'string') { try { accountBackup = JSON.parse(backup); } catch (_e) { return err('Invalid backup string.'); } } else if (typeof backup === 'object') { accountBackup = backup; } else { return err('Invalid backup. Unable to import.'); } if (!(accountBackup === null || accountBackup === void 0 ? void 0 : accountBackup.account.name)) { return err('No account name was provided in the accountBackup object.'); } if (!(accountBackup === null || accountBackup === void 0 ? void 0 : accountBackup.account.seed)) { return err('No seed was provided in the accountBackup object.'); } if (!(accountBackup === null || accountBackup === void 0 ? void 0 : accountBackup.data)) { return err('No data was provided in the accountBackup object.'); } if (!(ELdkData.channel_manager in accountBackup.data) || !(ELdkData.channel_monitors in accountBackup.data) || !(ELdkData.peers in accountBackup.data) || !(ELdkData.unconfirmed_transactions in accountBackup.data) || !(ELdkData.broadcasted_transactions in accountBackup.data) || !(ELdkData.payment_ids in accountBackup.data) || !(ELdkData.timestamp in accountBackup.data)) { return err(`Invalid account backup data. Please ensure the following keys exist in the accountBackup object: ${ELdkData.channel_manager}, ${ELdkData.channel_monitors}, ${ELdkData.peers}, ${ELdkData.unconfirmed_transactions}`); } const accountPath = appendPath(this.baseStoragePath, accountBackup.account.name); let timestamp = 0; const channelManagerRes = yield ldk.readFromFile({ fileName: ELdkFiles.channel_manager, path: accountPath, format: 'hex', }); if (channelManagerRes.isErr() && channelManagerRes.code !== 'file_does_not_exist') { return err(channelManagerRes.error); } if (!overwrite && ((_c = accountBackup.data) === null || _c === void 0 ? void 0 : _c.timestamp) <= timestamp) { const msg = ((_d = accountBackup.data) === null || _d === void 0 ? void 0 : _d.timestamp) < timestamp ? 'This appears to be an old backup. The stored backup is more recent than the backup trying to be imported.' : 'No need to import. The backup timestamps match.'; return err(msg); } const saveChannelManagerRes = yield ldk.writeToFile({ fileName: ELdkFiles.channel_manager, path: accountPath, content: accountBackup.data.channel_manager, format: 'hex', remotePersist: false, }); if (saveChannelManagerRes.isErr()) { return err(saveChannelManagerRes.error); } let channelIds = Object.keys(accountBackup.data.channel_monitors); for (let index = 0; index < channelIds.length; index++) { const channelId = channelIds[index]; const saveChannelRes = yield ldk.writeToFile({ fileName: `${channelId}.bin`, path: appendPath(accountPath, ELdkFiles.channels), content: accountBackup.data.channel_monitors[channelId], format: 'hex', remotePersist: false, }); if (saveChannelRes.isErr()) { return err(saveChannelRes.error); } } const savePeersRes = yield ldk.writeToFile({ fileName: ELdkFiles.peers, path: accountPath, content: JSON.stringify(accountBackup.data.peers), remotePersist: false, }); if (savePeersRes.isErr()) { return err(savePeersRes.error); } const savePaymentIdsRes = yield ldk.writeToFile({ fileName: ELdkFiles.payment_ids, path: accountPath, content: JSON.stringify(accountBackup.data.payment_ids), remotePersist: false, }); if (savePaymentIdsRes.isErr()) { return err(savePaymentIdsRes.error); } const confirmedTxRes = yield ldk.writeToFile({ fileName: ELdkFiles.unconfirmed_transactions, path: accountPath, content: JSON.stringify(accountBackup.data.unconfirmed_transactions), remotePersist: false, }); if (confirmedTxRes.isErr()) { return err(confirmedTxRes.error); } const broadcastedTxRes = yield ldk.writeToFile({ fileName: ELdkFiles.broadcasted_transactions, path: accountPath, content: JSON.stringify(accountBackup.data.broadcasted_transactions), remotePersist: false, }); if (broadcastedTxRes.isErr()) { return err(broadcastedTxRes.error); } return ok(accountBackup.account); } catch (e) { return err(e); } }); this.payWithTimeout = ({ paymentRequest, amountSats, timeout = 20000, }) => __awaiter(this, void 0, void 0, function* () { yield ldk.setLogLevel(ELdkLogLevels.trace, true); yield ldk.writeToLogFile('debug', `Trace logging enabled`); return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { try { yield ldk.writeToLogFile('debug', `ldk.pay() called with hard timeout of ${timeout}ms`); if (timeout < 1000) { return resolve(err('Timeout must be at least 1000ms.')); } this.subscribeToPaymentResponses(resolve); let payResponse = yield ldk.pay({ paymentRequest, amountSats, timeout, }); yield ldk.writeToLogFile('debug', payResponse.isOk() ? `ldk.pay() success (pending callbacks) Payment ID: ${payResponse.value}` : `ldk.pay() error ${payResponse.error.message}.`); if (!payResponse) { this.unsubscribeFromPaymentSubscriptions(); return resolve(err('Unable to pay the provided lightning invoice.')); } if (payResponse.isErr()) { this.unsubscribeFromPaymentSubscriptions(); return resolve(err(payResponse.error.message)); } yield this.appendLdkPaymentId(payResponse.value); } finally { setTimeout(() => __awaiter(this, void 0, void 0, function* () { yield ldk.setLogLevel(ELdkLogLevels.trace, false); yield ldk.writeToLogFile('debug', `Trace logging disabled`); }), 10000); } })); }); this.subscribeToPaymentResponses = (resolve) => { this.pathFailedSubscription = ldk.onEvent(EEventTypes.channel_manager_payment_path_failed, (res) => __awaiter(this, void 0, void 0, function* () { if (res.payment_failed_permanently) { this.unsubscribeFromPaymentSubscriptions(); this.removeLdkPaymentId(res.payment_id).then(); return resolve(err('Payment path failed.')); } })); this.paymentFailedSubscription = ldk.onEvent(EEventTypes.channel_manager_payment_failed, (res) => __awaiter(this, void 0, void 0, function* () { this.unsubscribeFromPaymentSubscriptions(); this.removeLdkPaymentId(res.payment_id).then(); return resolve(err('Payment failed')); })); this.paymentSentSubscription = ldk.onEvent(EEventTypes.channel_manager_payment_sent, (res) => { this.unsubscribeFromPaymentSubscriptions(); this.removeLdkPaymentId(res.payment_id).then(); return resolve(ok(res)); }); }; this.unsubscribeFromPaymentSubscriptions = () => { this.pathFailedSubscription && this.pathFailedSubscription.remove(); this.paymentFailedSubscription && this.paymentFailedSubscription.remove(); this.paymentSentSubscription && this.paymentSentSubscription.remove(); }; this.getChangeDestinationScript = (address = '') => { try { const network = networks[this.network]; const script = bitcoin.address.toOutputScript(address, network); return script.toString('hex'); } catch (e) { console.log(e); return ''; } }; this.checkUnconfirmedTransactions = () => __awaiter(this, void 0, void 0, function* () { let needsToSync = false; let newUnconfirmedTxs = []; for (const unconfirmedTx of this.unconfirmedTxs) { const { txid, height } = unconfirmedTx; const newTxData = yield this.getTransactionData(txid); if (!newTxData) { yield ldk.setTxUnconfirmed(txid); needsToSync = true; continue; } if (!(newTxData === null || newTxData === void 0 ? void 0 : newTxData.header) && (newTxData === null || newTxData === void 0 ? void 0 : newTxData.height) === 0) { newUnconfirmedTxs.push(unconfirmedTx); continue; } if (this.currentBlock.height - newTxData.height >= 6) { continue; } if (newTxData.height < height && newTxData.height === 0) { yield ldk.setTxUnconfirmed(txid); needsToSync = true; continue; } newUnconfirmedTxs.push(Object.assign(Object.assign({}, newTxData), { txid, script_pubkey: unconfirmedTx.script_pubkey })); } yield this.updateUnconfirmedTxs(newUnconfirmedTxs); if (needsToSync) { yield this.syncLdk({ force: true }); } return ok('Unconfirmed transactions checked'); }); this.getLdkUnconfirmedTxs = () => __awaiter(this, void 0, void 0, function* () { const res = yield ldk.readFromFile({ fileName: ELdkFiles.unconfirmed_transactions, }); if (res.isOk()) { return parseData(res.value.content, []); } return []; }); this.getLdkPaymentIds = () => __awaiter(this, void 0, void 0, function* () { const res = yield ldk.readFromFile({ fileName: ELdkFiles.payment_ids, }); if (res.isOk()) { let parsed = parseData(res.value.content, []); if (parsed.length === 64 && res.value.content.length === 66) { parsed = [parsed]; } return parsed; } return []; }); this.removeLdkPaymentId = (paymentId) => __awaiter(this, void 0, void 0, function* () { const paymentIds = yield this.getLdkPaymentIds(); if (!paymentIds.length || !paymentId.includes(paymentId)) { return; } const newPaymentIds = paymentIds.filter((id) => id !== paymentId); yield ldk.writeToFile({ fileName: ELdkFiles.payment_ids, content: JSON.stringify(newPaymentIds), remotePersist: false, }); }); this.appendLdkPaymentId = (paymentId) => __awaiter(this, void 0, void 0, function* () { let paymentIds = yield this.getLdkPaymentIds(); if (paymentId.includes(paymentId)) { return; } paymentIds.push(paymentId); yield ldk.writeToFile({ fileName: ELdkFiles.payment_ids, content: JSON.stringify(paymentIds), remotePersist: false, }); }); this.getLdkPaymentsClaimed = () => __awaiter(this, void 0, void 0, function* () { const res = yield ldk.readFromFile({ fileName: ELdkFiles.payments_claimed, }); if (res.isOk()) { return parseData(res.value.content, []); } return []; }); this.getLdkPaymentsSent = () => __awaiter(this, void 0, void 0, function* () { const res = yield ldk.readFromFile({ fileName: ELdkFiles.payments_sent, }); if (res.isOk()) { return parseData(res.value.content, []); } return []; }); this.getBolt11Invoices = () => __awaiter(this, void 0, void 0, function* () { const res = yield ldk.readFromFile({ fileName: ELdkFiles.bolt11_invoices, }); if (res.isOk()) { let parsed = parseData(res.value.content, []); return parsed; } return []; }); this.appendBolt11Invoice = (bolt11) => __awaiter(this, void 0, void 0, function* () { let invoices = yield this.getBolt11Invoices(); if (invoices.includes(bolt11)) { return; } invoices.push(bolt11); yield ldk.writeToFile({ fileName: ELdkFiles.bolt11_invoices, content: JSON.stringify(invoices), remotePersist: false, }); }); this.getInvoiceFromPaymentHash = (paymentHash) => __awaiter(this, void 0, void 0, function* () { const invoices = yield this.getBolt11Invoices(); for (let index = 0; index < invoices.length; index++) { const paymentRequest = invoices[index]; const invoiceRes = yield ldk.decode({ paymentRequest }); if (invoiceRes.isOk()) { if (invoiceRes.value.payment_hash === paymentHash) { return invoiceRes.value; } } } }); this.getLdkSpendableOutputs = () => __awaiter(this, void 0, void 0, function* () { const res = yield ldk.readFromFile({ fileName: ELdkFiles.spendable_outputs, }); if (res.isOk()) { return parseData(res.value.content, []); } return []; }); this.saveUnconfirmedTx = (data) => __awaiter(this, void 0, void 0, function* () { if (!data) { return err('No data provided to saveUnconfirmedTx'); } const alreadyExists = this.unconfirmedTxs.find((unconfirmedTx) => unconfirmedTx.txid === data.txid); if (!alreadyExists) { this.unconfirmedTxs.push(data); return yield ldk.writeToFile({ fileName: ELdkFiles.unconfirmed_transactions, content: JSON.stringify(this.unconfirmedTxs), remotePersist: true, }); } return ok(true); }); this.updateUnconfirmedTxs = (unconfirmedTxs) => __awaiter(this, void 0, void 0, function* () { this.unconfirmedTxs = unconfirmedTxs; return yield ldk.writeToFile({ fileName: ELdkFiles.unconfirmed_transactions, content: JSON.stringify(this.unconfirmedTxs), remotePersist: true, }); }); this.saveBroadcastedTxs = (rawTx) => __awaiter(this, void 0, void 0, function* () { if (!rawTx) { return ok(true); } const broadcastedTransactions = yield this.getLdkBroadcastedTxs(); if (!broadcastedTransactions.includes(rawTx)) { broadcastedTransactions.push(rawTx); return yield ldk.writeToFile({ fileName: ELdkFiles.broadcasted_transactions, content: JSON.stringify(broadcastedTransactions), remotePersist: true, }); } return ok(true); }); this.saveLdkPeerData = (peer) => __awaiter(this, void 0, void 0, function* () { const peers = yield this.getPeers(); const duplicatePeerArr = peers.filter((p) => p.pubKey === peer.pubKey && p.address === peer.address); if (duplicatePeerArr.length > 0) { return ok(true); } peers.push(peer); return yield ldk.writeToFile({ fileName: ELdkFiles.peers, content: JSON.stringify(peers), remotePersist: true, }); }); ldk.onEvent(EEventTypes.native_log, (line) => { if (line.indexOf('Could not locate file at') > -1) { return; } console.log(`react-native-ldk: ${line}`); }); ldk.onEvent(EEventTypes.ldk_log, (line) => console.log(`LDK: ${line}`)); ldk.onEvent(EEventTypes.register_tx, this.onRegisterTx.bind(this)); ldk.onEvent(EEventTypes.register_output, this.onRegisterOutput.bind(this)); ldk.onEvent(EEventTypes.broadcast_transaction, this.onBroadcastTransaction.bind(this)); ldk.onEvent(EEventTypes.channel_manager_payment_claimable, this.onChannelManagerPaymentClaimable.bind(this)); ldk.onEvent(EEventTypes.channel_manager_payment_sent, this.onChannelManagerPaymentSent.bind(this)); ldk.onEvent(EEventTypes.channel_manager_open_channel_request, this.onChannelManagerOpenChannelRequest.bind(this)); ldk.onEvent(EEventTypes.channel_manager_payment_path_successful, this.onChannelManagerPaymentPathSuccessful.bind(this)); ldk.onEvent(EEventTypes.channel_manager_payment_path_failed, this.onChannelManagerPaymentPathFailed.bind(this)); ldk.onEvent(EEventTypes.channel_manager_payment_failed, this.onChannelManagerPaymentFailed.bind(this)); ldk.onEvent(EEventTypes.channel_manager_pending_htlcs_forwardable, this.onChannelManagerPendingHtlcsForwardable.bind(this)); ldk.onEvent(EEventTypes.channel_manager_spendable_outputs, this.onChannelManagerSpendableOutputs.bind(this)); ldk.onEvent(EEventTypes.channel_manager_channel_closed, this.onChannelManagerChannelClosed.bind(this)); ldk.onEvent(EEventTypes.channel_manager_discard_funding, this.onChannelManagerDiscardFunding.bind(this)); ldk.onEvent(EEventTypes.channel_manager_payment_claimed, this.onChannelManagerPaymentClaimed.bind(this)); ldk.onEvent(EEventTypes.emergency_force_close_channel, this.onEmergencyForceCloseChannel.bind(this)); ldk.onEvent(EEventTypes.network_graph_updated, this.onNetworkGraphUpdated.bind(this)); ldk.onEvent(EEventTypes.channel_manager_restarted, this.onChannelManagerRestarted.bind(this)); ldk.onEvent(EEventTypes.lsp_log, this.onLspLogEvent.bind(this)); ldk.onEvent(EEventTypes.used_close_address, (address) => { this.saveAddressToFile(address).catch(console.error); }); } setBaseStoragePath(path) { return __awaiter(this, void 0, void 0, function* () { const storagePath = appendPath(path, ''); this.baseStoragePath = storagePath; return ok('Storage set'); }); } resolveAllPendingStartPromises(result) { while (this.pendingStartPromises.length > 0) { const resolve = this.pendingStartPromises.shift(); if (resolve) { resolve(result); } } } start({ account, getBestBlock, getTransactionData, getTransactionPosition, getAddress, getScriptPubKeyHistory, getFees, broadcastTransaction, network, rapidGossipSyncUrl = 'https://rapidsync.lightningdevkit.org/snapshot/', scorerDownloadUrl = 'https://rapidsync.lightningdevkit.org/scoring/scorer.bin', forceCloseOnStartup, userConfig = defaultUserConfig, skipParamCheck = false, skipRemoteBackups = false, lspLogEvent, }) { return __awaiter(this, void 0, void 0, function* () { if (!account) { return err('No account provided. Please pass an account object containing the name & seed to the start method and try again.'); } if (!(account === null || account === void 0 ? void 0 : account.name) || !(account === null || account === void 0 ? void 0 : account.seed)) { return err('No account name or seed provided. Please pass an account object containing the name & seed to the start method and try again.'); } if (!getBestBlock) { return err('getBestBlock method not specified in start method.'); } if (!getTransactionData) { return err('getTransactionData is not set in start method.'); } if (!getTransactionPosition) { return err('getTransactionPosition is not set in start method.'); } if (this.previousAccountName !== account.name) { this.isStarting = false; this.previousAccountName = account.name; } if (this.isStarting) { return new Promise((resolve) => { this.pendingStartPromises.push(resolve); }); } this.isStarting = true; if (!skipParamCheck) { const paramCheckResponse = yield startParamCheck({ account, getBestBlock, getTransactionData, getTransactionPosition, broadcastTransaction, getAddress, getScriptPubKeyHistory, getFees, network, }); if (paramCheckResponse.isErr()) { return this.handleStartError(paramCheckResponse); } } else { ldk .writeToLogFile('info', 'Skipping start param check. Set skipParamCheck=false for debugging.') .catch(console.error); } this.getBestBlock = getBestBlock; this.account = account; this.network = network; this.addresses = yield this.readAddressesFromFile(); this.getAddress = getAddress; this.getScriptPubKeyHistory = getScriptPubKeyHistory; this.getFees = getFees; this.broadcastTransaction = broadcastTransaction; this.getTransactionData = getTransactionData; this.getTransactionPosition = getTransactionPosition; const bestBlock = yield this.getBestBlock(); this.unconfirmedTxs = yield this.getLdkUnconfirmedTxs(); this.watchTxs = []; this.watchOutputs = []; this.confirmedWatchOutputs = yield this.getConfirmedWatchOutputs(); this.lspLogEvent = lspLogEvent; if (!this.baseStoragePath) { return this.handleStartError(err('baseStoragePath required for wallet persistence. Call setBaseStoragePath(path) first.')); } const storageSetRes = yield this.setStorage(account); if (storageSetRes.isErr()) { return this.handleStartError(storageSetRes); } const readSeed = yield ldk.readFromFile({ fileName: ELdkFiles.seed, format: 'hex', }); if (readSeed.isErr()) { if (readSeed.code === 'file_does_not_exist') { const writeRes = yield ldk.writeToFile({ fileName: ELdkFiles.seed, content: account.seed, format: 'hex', remotePersist: false, }); if (writeRes.isErr()) { return this.handleStartError(err(writeRes.error)); } } else { return this.handleStartError(err(readSeed.error)); } } else { if (readSeed.value.content !== account.seed) { return this.handleStartError(err('Seed for current node cannot be changed.')); } } if (network !== ENetworks.mainnet) { rapidGossipSyncUrl = ''; scorerDownloadUrl = ''; } const closeAddress = yield this.getAddress(); const witnessProgram = bitcoin.crypto .hash160(Buffer.from(closeAddress.publicKey, 'hex')) .toString('hex'); const witnessProgramVersion = isValidBech32mEncodedString(closeAddress.address).isValid ? 1 : 0; let promises = [ ldk.setLogLevel(ELdkLogLevels.info, true), ldk.setLogLevel(ELdkLogLevels.warn, true), ldk.setLogLevel(ELdkLogLevels.error, true), ldk.setLogLevel(ELdkLogLevels.debug, true), ldk.initKeysManager({ seed: this.account.seed, address: closeAddress.address, channelCloseDestinationScriptPublicKey: closeAddress.publicKey, channelCloseWitnessProgram: witnessProgram, channelCloseWitnessProgramVersion: witnessProgramVersion, }), ldk.initNetworkGraph({ network, rapidGossipSyncUrl, skipHoursThreshold: 3, }), this.setFees(), ldk.initUserConfig(userConfig), ]; if (scorerDownloadUrl) { promises.push(ldk.downloadScorer({ scorerDownloadUrl, skipHoursThreshold: 3, })); } if (!skipRemoteBackups) { promises.push(ldk.backupSelfCheck()); } const results = yield Promise.all(promises); for (const result of results) { if (result.isErr()) { return this.handleStartError(result); } } const channelManagerRes = yield ldk.initChannelManager({ network: this.network, bestBlock, }); if (channelManagerRes.isErr()) { return this.handleStartError(channelManagerRes); } if (forceCloseOnStartup && forceCloseOnStartup.forceClose) { yield ldk.forceCloseAllChannels(forceCloseOnStartup.broadcastLatestTx); } if (!forceCloseOnStartup || forceCloseOnStartup.broadcastLatestTx) { this.addPeers().catch(console.error); } this.syncLdk().catch(console.error); this.removeExpiredAndUnclaimedInvoices().catch(console.error); this.removeStalePaymentClaims().catch(console.error); ldk.nodeStateDump().catch(console.error); this.cleanupBroadcastedTxs().catch(console.error); this.resetAddressFileIfUnused().catch(console.error); this.isStarting = false; const result = ok('Node started'); this.resolveAllPendingStartPromises(result); return result; }); } setStorage(account) { return __awaiter(this, void 0, void 0, function* () { let accountStoragePath = appendPath(this.baseStoragePath, account.name); this.logFilePath = `${accountStoragePath}/logs/${Date.now()}.log`; const logFilePathRes = yield ldk.setLogFilePath(this.logFilePath); if (logFilePathRes.isErr()) { return logFilePathRes; } const storagePathRes = yield ldk.setAccountStoragePath(accountStoragePath); if (storagePathRes.isErr()) { return storagePathRes; } return ok('Storage set'); }); } addPeers() { return __awaiter(this, void 0, void 0, function* () { const peers = yield this.getPeers(); yield Promise.all(peers.map((peer) => { this.addPeer(Object.assign(Object.assign({}, peer), { timeout: 4000 })); })); }); } setFees() { return __awaiter(this, void 0, void 0, function* () { const fees = yield this.getFees(); return yield ldk.updateFees(fees); }); } setTrustedZeroConfPeerNodeIds(nodeIds, merge = true) { return __awaiter(this, void 0, void 0, function* () { let trustedNodeIds = nodeIds; const accountPath = appendPath(this.baseStoragePath, this.account.name); if (merge) { const readRes = yield ldk.readFromFile({ fileName: ELdkFiles.trusted_peer_node_ids, path: accountPath, }); if (readRes.isOk()) { let currentIds = parseData(readRes.value.content, []); trustedNodeIds = Array.from(new Set([...currentIds, ...nodeIds])); } } return yield ldk.writeToFile({ fileName: ELdkFiles.trusted_peer_node_ids, path: accountPath,