@synonymdev/react-native-ldk
Version:
React Native wrapper for LDK
1,022 lines (1,021 loc) • 76.8 kB
JavaScript
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,