From 85475ca5fae1d5e70b6a8b4f7018c7c11a30d5d2 Mon Sep 17 00:00:00 2001 From: AJAY SEHWAL Date: Mon, 27 May 2024 14:03:31 +0530 Subject: [PATCH 1/9] refactor : api method --- backends/Eclair.ts | 66 +++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/backends/Eclair.ts b/backends/Eclair.ts index 01f26ae46..96e877204 100644 --- a/backends/Eclair.ts +++ b/backends/Eclair.ts @@ -13,13 +13,13 @@ const calls = new Map>(); export default class Eclair { clearCachedCalls = () => calls.clear(); - api = (method: string, params: any = {}) => { + api = (method: string, params: any = {}): Promise => { const { password, certVerification, enableTor } = stores.settingsStore; let { url } = stores.settingsStore; const id: string = method + JSON.stringify(params); if (calls.has(id)) { - return calls.get(id); + return calls.get(id)!; } url = url.slice(-1) === '/' ? url : url + '/'; @@ -28,42 +28,30 @@ export default class Eclair { 'Content-Type': 'application/x-www-form-urlencoded' }; const body = querystring.stringify(params); - - if (enableTor === true) { - calls.set( - id, - doTorRequest(url + method, RequestMethod.POST, body, headers) - ); - } else { - calls.set( - id, - ReactNativeBlobUtil.config({ - trusty: !certVerification - }) - .fetch('POST', url + method, headers, body) - .then((response: any) => { - calls.delete(id); - - const status = response.info().status; - if (status < 300) { - return response.json(); - } else { - let errorInfo; - try { - errorInfo = response.json(); - } catch (err) { - throw new Error( - 'response was (' + - status + - ')' + - response.text() - ); - } - throw new Error(errorInfo.error); - } - }) - ); - } + const requestPromise = enableTor + ? doTorRequest(url + method, RequestMethod.POST, body, headers) + : ReactNativeBlobUtil.config({ trusty: !certVerification }) + .fetch('POST', url + method, headers, body) + .then((response) => { + const status = response.info().status; + if (status < 300) { + return response.json(); + } else { + let errorInfo; + try { + errorInfo = response.json(); + } catch (err) { + throw new Error( + `response was (${status}) ${response.text()}` + ); + } + throw new Error(errorInfo.error); + } + }) + .finally(() => { + calls.delete(id); + }); + calls.set(id, requestPromise); setTimeout( (id: string) => { calls.delete(id); @@ -72,7 +60,7 @@ export default class Eclair { id ); - return calls.get(id); + return calls.get(id)!; }; getTransactions = () => From fc19e08e86851c1f2384709b8846399049ba68e0 Mon Sep 17 00:00:00 2001 From: AJAY SEHWAL Date: Wed, 29 May 2024 19:02:14 +0530 Subject: [PATCH 2/9] refactor : rpc method --- backends/Spark.ts | 64 ++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/backends/Spark.ts b/backends/Spark.ts index b9b02189c..fda410a4a 100644 --- a/backends/Spark.ts +++ b/backends/Spark.ts @@ -10,13 +10,13 @@ const calls = new Map>(); export default class Spark { clearCachedCalls = () => calls.clear(); - rpc = (rpcmethod: string, param = {}, range: any = null) => { + rpc = (rpcmethod: string, param = {}, range: any = null):Promise => { const { accessKey, certVerification, enableTor } = stores.settingsStore; let { url } = stores.settingsStore; const id = rpcmethod + JSON.stringify(param) + JSON.stringify(range); if (calls.has(id)) { - return calls.get(id); + return calls.get(id)!; } url = url.slice(-4) === '/rpc' ? url : url + '/rpc'; @@ -27,39 +27,35 @@ export default class Spark { } const body = JSON.stringify({ method: rpcmethod, params: param }); - if (enableTor === true) { - calls.set(id, doTorRequest(url, RequestMethod.POST, body, headers)); - } else { - calls.set( - id, - ReactNativeBlobUtil.config({ - trusty: !certVerification - }) - .fetch('POST', url, headers, body) - .then((response: any) => { - calls.delete(id); - const status = response.info().status; - if (status < 300) { - return response.json(); - } else { - let errorInfo; - try { - errorInfo = response.json(); - } catch (err) { - throw new Error( - 'response was (' + - status + - ')' + - response.text() - ); - } - throw new Error(errorInfo.message); - } - }) - ); - } + const request = enableTor + ? doTorRequest(url, RequestMethod.POST, body, headers) + : ReactNativeBlobUtil.config({ + trusty: !certVerification + }) + .fetch('POST', url, headers, body) + .then((response: any) => { + calls.delete(id); + const status = response.info().status; + if (status < 300) { + return response.json(); + } else { + let errorInfo; + try { + errorInfo = response.json(); + } catch (err) { + throw new Error( + 'response was (' + + status + + ')' + + response.text() + ); + } + throw new Error(errorInfo.message); + } + }); + calls.set(id, request); - return calls.get(id); + return calls.get(id)!; }; getTransactions = () => From 6a5a553d63f2052b3398dad579691d4041f53617 Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Wed, 28 Aug 2024 11:18:17 +0530 Subject: [PATCH 3/9] refactor : api method --- backends/Eclair.ts | 118 +++++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 48 deletions(-) diff --git a/backends/Eclair.ts b/backends/Eclair.ts index 96e877204..aed2a2090 100644 --- a/backends/Eclair.ts +++ b/backends/Eclair.ts @@ -6,61 +6,82 @@ import TransactionRequest from './../models/TransactionRequest'; import OpenChannelRequest from './../models/OpenChannelRequest'; import Base64Utils from './../utils/Base64Utils'; import { Hash as sha256Hash } from 'fast-sha256'; - -// keep track of all active calls so we can cancel when appropriate -const calls = new Map>(); +interface ApiResponse { + [key: string]: any; +} export default class Eclair { - clearCachedCalls = () => calls.clear(); + private calls: Map> = new Map(); + clearCachedCalls = () => this.calls.clear(); + private generateCallId(method: string, params: any): string { + return method + JSON.stringify(params); + } + private createHeaders(): Record { + return { + Authorization: + 'Basic ' + + Base64Utils.utf8ToBase64(':' + stores.settingsStore.password), + 'Content-Type': 'application/x-www-form-urlencoded' + }; + } + private normalizeUrl(url: string): string { + return url.endsWith('/') ? url : url + '/'; + } + private async makeTorRequest( + url: string, + method: string, + body: string, + headers: Record + ): Promise { + return doTorRequest(url + method, RequestMethod.POST, body, headers); + } + private async makeRegularRequest( + url: string, + method: string, + body: string, + headers: Record + ): Promise { + try { + const response = await ReactNativeBlobUtil.config({ + trusty: !stores.settingsStore.certVerification + }).fetch('POST', url + method, headers, body); - api = (method: string, params: any = {}): Promise => { - const { password, certVerification, enableTor } = stores.settingsStore; - let { url } = stores.settingsStore; + const status = response.info().status; + if (status < 300) { + return response.json(); + } else { + const errorInfo = await response.json(); + throw new Error(errorInfo.error); + } + } catch (error) { + if (error instanceof Error) { + throw error; + } + throw new Error('Unknown error occurred'); + } + } + private setCallCleanupTimeout(id: string): void { + setTimeout(() => { + this.calls.delete(id); + }, 9000); + } + api = (method: string, params: any = {}) => { + const id = this.generateCallId(method, params); - const id: string = method + JSON.stringify(params); - if (calls.has(id)) { - return calls.get(id)!; + if (this.calls.has(id)) { + return this.calls.get(id)!; } - url = url.slice(-1) === '/' ? url : url + '/'; - const headers = { - Authorization: 'Basic ' + Base64Utils.utf8ToBase64(':' + password), - 'Content-Type': 'application/x-www-form-urlencoded' - }; + const url = this.normalizeUrl(stores.settingsStore.url); + const headers = this.createHeaders(); const body = querystring.stringify(params); - const requestPromise = enableTor - ? doTorRequest(url + method, RequestMethod.POST, body, headers) - : ReactNativeBlobUtil.config({ trusty: !certVerification }) - .fetch('POST', url + method, headers, body) - .then((response) => { - const status = response.info().status; - if (status < 300) { - return response.json(); - } else { - let errorInfo; - try { - errorInfo = response.json(); - } catch (err) { - throw new Error( - `response was (${status}) ${response.text()}` - ); - } - throw new Error(errorInfo.error); - } - }) - .finally(() => { - calls.delete(id); - }); - calls.set(id, requestPromise); - setTimeout( - (id: string) => { - calls.delete(id); - }, - 9000, - id - ); + const apiCall = stores.settingsStore.enableTor + ? this.makeTorRequest(url, method, body, headers) + : this.makeRegularRequest(url, method, body, headers); + this.calls.set(id, apiCall); + this.setCallCleanupTimeout(id); - return calls.get(id)!; + return apiCall; }; getTransactions = () => @@ -494,9 +515,10 @@ export default class Eclair { supportsSweep = () => false; supportsOnchainBatching = () => false; supportsChannelBatching = () => false; - isLNDBased = () => false; supportsLSPS1customMessage = () => false; supportsLSPS1rest = () => true; + supportsOffers = () => false; + isLNDBased = () => false; } const mapInvoice = From 5fd848f3f1836ac9ede6cec72298b258baf3a5d9 Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Wed, 28 Aug 2024 11:20:10 +0530 Subject: [PATCH 4/9] refactor : api method --- backends/Eclair.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backends/Eclair.ts b/backends/Eclair.ts index aed2a2090..09f0e7f6d 100644 --- a/backends/Eclair.ts +++ b/backends/Eclair.ts @@ -11,6 +11,7 @@ interface ApiResponse { } export default class Eclair { + // keep track of all active calls so we can cancel when appropriate private calls: Map> = new Map(); clearCachedCalls = () => this.calls.clear(); private generateCallId(method: string, params: any): string { From c52ab81cbcd97715d227e3a2a7c7850379ff49dd Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Wed, 28 Aug 2024 11:48:11 +0530 Subject: [PATCH 5/9] refactor : rpc method --- backends/Spark.ts | 107 +++++++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/backends/Spark.ts b/backends/Spark.ts index fda410a4a..bf9c730a6 100644 --- a/backends/Spark.ts +++ b/backends/Spark.ts @@ -4,58 +4,75 @@ import { doTorRequest, RequestMethod } from '../utils/TorUtils'; import TransactionRequest from './../models/TransactionRequest'; import OpenChannelRequest from './../models/OpenChannelRequest'; -// keep track of all active calls so we can cancel when appropriate -const calls = new Map>(); - export default class Spark { - clearCachedCalls = () => calls.clear(); - - rpc = (rpcmethod: string, param = {}, range: any = null):Promise => { - const { accessKey, certVerification, enableTor } = stores.settingsStore; - let { url } = stores.settingsStore; - - const id = rpcmethod + JSON.stringify(param) + JSON.stringify(range); - if (calls.has(id)) { - return calls.get(id)!; + // keep track of all active calls so we can cancel when appropriate + private calls = new Map>(); + clearCachedCalls = () => this.calls.clear(); + private async makeRegularRequest( + url: string, + headers: Record, + body: string, + certVerification: boolean | undefined + ): Promise { + return ReactNativeBlobUtil.config({ trusty: !certVerification }) + .fetch('POST', url, headers, body) + .then(this.handleResponse); + } + private handleResponse(response: any): any { + const status = response.info().status; + if (status < 300) { + return response.json(); + } else { + return response + .json() + .catch(() => ({ message: response.text() })) + .then((error: Error) => { + throw new Error( + `HTTP ${status}: ${(error as Error).message}` + ); + }); } + } + rpc = ( + method: string, + params = {}, + range: { unit: string; slice: string } | null = null + ): Promise => { + const { accessKey, certVerification, enableTor, url } = + stores.settingsStore; + const rpcUrl = url.endsWith('/rpc') ? url : `${url}/rpc`; + const id = `${method}${JSON.stringify(params)}${JSON.stringify(range)}`; - url = url.slice(-4) === '/rpc' ? url : url + '/rpc'; + if (this.calls.has(id)) { + return this.calls.get(id)!; + } + const headers: Record = { 'X-Access': accessKey }; - const headers: any = { 'X-Access': accessKey }; if (range) { headers.Range = `${range.unit}=${range.slice}`; } - const body = JSON.stringify({ method: rpcmethod, params: param }); - - const request = enableTor - ? doTorRequest(url, RequestMethod.POST, body, headers) - : ReactNativeBlobUtil.config({ - trusty: !certVerification - }) - .fetch('POST', url, headers, body) - .then((response: any) => { - calls.delete(id); - const status = response.info().status; - if (status < 300) { - return response.json(); - } else { - let errorInfo; - try { - errorInfo = response.json(); - } catch (err) { - throw new Error( - 'response was (' + - status + - ')' + - response.text() - ); - } - throw new Error(errorInfo.message); - } - }); - calls.set(id, request); - - return calls.get(id)!; + const body = JSON.stringify({ method, params }); + const request = ( + enableTor + ? doTorRequest(rpcUrl, RequestMethod.POST, body, headers) + : this.makeRegularRequest( + rpcUrl, + headers, + body, + certVerification + ) + ).then( + (response) => { + this.calls.delete(id); + return response; + }, + (error) => { + this.calls.delete(id); + throw error; + } + ); + this.calls.set(id, request); + return request; }; getTransactions = () => From 07ee06a4adcca7adf20188c7d3c3fabe0cfac8fa Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Sun, 1 Sep 2024 13:54:41 +0530 Subject: [PATCH 6/9] fix : ts error in LND , LNC --- backends/CLightningREST.ts | 200 ++++--- backends/Eclair.ts | 4 +- backends/EmbeddedLND.ts | 28 +- backends/LND.ts | 173 +++--- backends/LightningNodeConnect.ts | 498 ++++++++++-------- models/OpenChannelRequest.ts | 47 +- utils/DataFormatUtils.ts | 14 +- .../lnc-rn/dist/typescript/types/lnc.d.ts | 1 + zeus_modules/@lightninglabs/lnc-rn/lib/lnc.ts | 2 +- .../@lightninglabs/lnc-rn/lib/types/lnc.ts | 2 + .../lnc-rn/lib/util/credentialStore.ts | 2 +- 11 files changed, 534 insertions(+), 437 deletions(-) diff --git a/backends/CLightningREST.ts b/backends/CLightningREST.ts index 13cef2a81..3d0075895 100644 --- a/backends/CLightningREST.ts +++ b/backends/CLightningREST.ts @@ -37,84 +37,81 @@ export default class CLightningREST extends LND { transactions: data.outputs })); getChannels = () => - this.getRequest('/v1/peer/listPeers').then((data: any) => { + this.getRequest('/v1/channel/listPeerChannels').then((data: any) => { const formattedChannels: any[] = []; - data.filter((peer: any) => peer.channels.length).map( - (peer: any) => { - peer.channels.forEach((channel: any) => { - if ( - channel.state === 'ONCHAIN' || - channel.state === 'CLOSED' || - channel.state === 'CHANNELD_AWAITING_LOCKIN' - ) - return; + const channels = data; + channels.forEach((channel: any) => { + if ( + channel.state === 'ONCHAIN' || + channel.state === 'CLOSED' || + channel.state === 'CHANNELD_AWAITING_LOCKIN' + ) + return; - // CLN v23.05 msat deprecations - const to_us_msat = - channel.to_us || - channel.to_us_msat || - channel.msatoshi_to_us || - 0; - const total_msat = - channel.total || - channel.total_msat || - channel.msatoshi_total || - 0; - const out_fulfilled_msat = - channel.out_fulfilled || - channel.out_fulfilled_msat || - channel.out_msatoshi_fulfilled || - 0; - const in_fulfilled_msat = - channel.in_fulfilled || - channel.in_fulfilled_msat || - channel.in_msatoshi_fulfilled || - 0; - const our_reserve_msat = - channel.our_reserve || - channel.our_reserve_msat || - channel.our_channel_reserve_satoshis || - 0; - const their_reserve_msat = - channel.their_reserve || - channel.their_reserve_msat || - channel.their_channel_reserve_satoshi || - 0; + // CLN v23.05 msat deprecations + const to_us_msat = + channel.to_us || + channel.to_us_msat || + channel.msatoshi_to_us || + 0; + const total_msat = + channel.total || + channel.total_msat || + channel.msatoshi_total || + 0; + const out_fulfilled_msat = + channel.out_fulfilled || + channel.out_fulfilled_msat || + channel.out_msatoshi_fulfilled || + 0; + const in_fulfilled_msat = + channel.in_fulfilled || + channel.in_fulfilled_msat || + channel.in_msatoshi_fulfilled || + 0; + const our_reserve_msat = + channel.our_reserve || + channel.our_reserve_msat || + channel.our_channel_reserve_satoshis || + 0; + const their_reserve_msat = + channel.their_reserve || + channel.their_reserve_msat || + channel.their_channel_reserve_satoshi || + 0; - formattedChannels.push({ - active: peer.connected, - remote_pubkey: peer.id, - channel_point: channel.funding_txid, - chan_id: channel.channel_id, - alias: peer.alias, - capacity: Number(total_msat / 1000).toString(), - local_balance: Number(to_us_msat / 1000).toString(), - remote_balance: Number( - (total_msat - to_us_msat) / 1000 - ).toString(), - total_satoshis_sent: Number( - out_fulfilled_msat / 1000 - ).toString(), - total_satoshis_received: Number( - in_fulfilled_msat / 1000 - ).toString(), - num_updates: ( - channel.in_payments_offered + - channel.out_payments_offered - ).toString(), - csv_delay: channel.our_to_self_delay, - private: channel.private, - local_chan_reserve_sat: Number( - our_reserve_msat / 1000 - ).toString(), - remote_chan_reserve_sat: Number( - their_reserve_msat / 1000 - ).toString(), - close_address: channel.close_to_addr - }); - }); - } - ); + formattedChannels.push({ + active: channel.peer_connected, + remote_pubkey: channel.peer_id, + channel_point: channel.funding_txid, + chan_id: channel.channel_id, + alias: channel.alias, + capacity: Number(total_msat / 1000).toString(), + local_balance: Number(to_us_msat / 1000).toString(), + remote_balance: Number( + (total_msat - to_us_msat) / 1000 + ).toString(), + total_satoshis_sent: Number( + out_fulfilled_msat / 1000 + ).toString(), + total_satoshis_received: Number( + in_fulfilled_msat / 1000 + ).toString(), + num_updates: ( + channel.in_payments_offered + + channel.out_payments_offered + ).toString(), + csv_delay: channel.our_to_self_delay, + private: channel.private, + local_chan_reserve_sat: Number( + our_reserve_msat / 1000 + ).toString(), + remote_chan_reserve_sat: Number( + their_reserve_msat / 1000 + ).toString(), + close_address: channel.close_to_addr + }); + }); return { channels: formattedChannels @@ -170,7 +167,8 @@ export default class CLightningREST extends LND { getNewAddress = () => this.getRequest('/v1/newaddr?addrType=bech32'); openChannelSync = (data: OpenChannelRequest) => { let request: any; - const feeRate = `${new BigNumber(data.sat_per_vbyte) + const satPerVbyte = data.satPerVbyte ?? '0'; // Default to '0' if undefined + const feeRate = `${new BigNumber(satPerVbyte) .times(1000) .toString()}perkb`; if (data.utxos && data.utxos.length > 0) { @@ -179,7 +177,7 @@ export default class CLightningREST extends LND { satoshis: data.satoshis, feeRate, announce: !data.privateChannel ? 'true' : 'false', - minfConf: data.min_confs, + minfConf: data.minConfs, utxos: data.utxos }; } else { @@ -188,7 +186,7 @@ export default class CLightningREST extends LND { satoshis: data.satoshis, feeRate, announce: !data.privateChannel ? 'true' : 'false', - minfConf: data.min_confs + minfConf: data.minConfs }; } @@ -199,7 +197,7 @@ export default class CLightningREST extends LND { id: `${data.addr.pubkey}@${data.addr.host}` }); decodePaymentRequest = (urlParams?: Array) => - this.getRequest(`/v1/pay/decodePay/${urlParams && urlParams[0]}`); + this.getRequest(`/v1/utility/decode/${urlParams && urlParams[0]}`); payLightningInvoice = (data: any) => this.postRequest('/v1/pay', { invoice: data.payment_request, @@ -212,10 +210,14 @@ export default class CLightningREST extends LND { amount: Number(data.amt && data.amt * 1000), maxfeepercent: data.max_fee_percent }); - closeChannel = (urlParams?: Array) => - this.deleteRequest( - `/v1/channel/closeChannel/${urlParams && urlParams[0]}/` + closeChannel = (urlParams?: Array) => { + const id = urlParams && urlParams[0]; + const unilateralTimeout = urlParams && urlParams[1] ? 2 : 0; + + return this.deleteRequest( + `/v1/channel/closeChannel/${id}?unilateralTimeout=${unilateralTimeout}` ); + }; getNodeInfo = () => this.getRequest('N/A'); getFees = () => this.getRequest('/v1/getFees/').then(({ feeCollected }: any) => ({ @@ -246,6 +248,33 @@ export default class CLightningREST extends LND { }; }; + // BOLT 12 / Offers + listOffers = () => this.getRequest('/v1/offers/listOffers'); + createOffer = ({ + description, + label, + singleUse + }: { + description?: string; + label?: string; + singleUse?: boolean; + }) => + this.postRequest('/v1/offers/offer', { + amount: 'any', + description, + label, + single_use: singleUse || false + }); + disableOffer = ({ offer_id }: { offer_id: string }) => + this.deleteRequest(`/v1/offers/disableOffer/${offer_id}`); + fetchInvoiceFromOffer = async (bolt12: string, amountSatoshis: string) => { + return await this.postRequest('/v1/offers/fetchInvoice', { + offer: bolt12, + msatoshi: Number(amountSatoshis) * 1000, + timeout: 60 + }); + }; + supportsMessageSigning = () => true; supportsLnurlAuth = () => true; supportsOnchainSends = () => true; @@ -274,7 +303,12 @@ export default class CLightningREST extends LND { supportsSweep = () => true; supportsOnchainBatching = () => false; supportsChannelBatching = () => false; - isLNDBased = () => false; supportsLSPS1customMessage = () => false; supportsLSPS1rest = () => true; + supportsOffers = async () => { + const res = await this.getRequest('/v1/utility/listConfigs'); + const supportsOffers: boolean = res['experimental-offers'] || false; + return supportsOffers; + }; + isLNDBased = () => false; } diff --git a/backends/Eclair.ts b/backends/Eclair.ts index 09f0e7f6d..5ea0e60fa 100644 --- a/backends/Eclair.ts +++ b/backends/Eclair.ts @@ -256,9 +256,9 @@ export default class Eclair { this.api('getnewaddress').then((address: any) => ({ address })); openChannelSync = (data: OpenChannelRequest) => this.api('open', { - nodeId: data.node_pubkey_string, + nodeId: data.nodePubkeyString, fundingSatoshis: data.satoshis, - fundingFeerateSatByte: data.sat_per_vbyte, + fundingFeerateSatByte: data.satPerVbyte, channelFlags: data.privateChannel ? 0 : 1 }).then(() => ({})); connectPeer = (data: any) => diff --git a/backends/EmbeddedLND.ts b/backends/EmbeddedLND.ts index 9ee0e7f55..9acfa7f17 100644 --- a/backends/EmbeddedLND.ts +++ b/backends/EmbeddedLND.ts @@ -92,13 +92,13 @@ export default class EmbeddedLND extends LND { await newAddress(data.type, data.account); openChannelSync = async (data: OpenChannelRequest) => await openChannelSync( - data.node_pubkey_string, - Number(data.local_funding_amount), + data.nodePubkeyString, + Number(data.localFundingAmount), data.privateChannel || false, - data.sat_per_vbyte ? Number(data.sat_per_vbyte) : undefined, + data.satPerVbyte ? Number(data.satPerVbyte) : undefined, data.scidAlias, - data.min_confs, - data.spend_unconfirmed, + data.minConfs, + data.spendUnconfirmed, data.simpleTaprootChannel, data.fundMax, data.utxos @@ -126,19 +126,19 @@ export default class EmbeddedLND extends LND { }); openChannel( - data.node_pubkey_string, - Number(data.local_funding_amount), + data.nodePubkeyString, + Number(data.localFundingAmount), data.privateChannel || false, - data.sat_per_vbyte && !data.funding_shim - ? Number(data.sat_per_vbyte) + data.satPerVbyte && !data.fundingShim + ? Number(data.satPerVbyte) : undefined, data.scidAlias, - data.min_confs, - data.spend_unconfirmed, + data.minConfs, + data.spendUnconfirmed, data.simpleTaprootChannel, data.fundMax, data.utxos, - data.funding_shim + data.fundingShim ); }); }; @@ -200,9 +200,9 @@ export default class EmbeddedLND extends LND { ); }; - getNodeInfo = async (urlParams?: Array) => + getNodeInfo = async (urlParams: string[]) => await getNodeInfo(urlParams[0]); - signMessage = async (msg: Uint8Array) => { + signMessage = async (msg: string) => { return await signMessageNodePubkey(Base64Utils.stringToUint8Array(msg)); }; verifyMessage = async (data: any) => { diff --git a/backends/LND.ts b/backends/LND.ts index 326f49fca..4909f5a24 100644 --- a/backends/LND.ts +++ b/backends/LND.ts @@ -21,11 +21,59 @@ export default class LND { torSocksPort?: number = undefined; clearCachedCalls = () => calls.clear(); - + private handleTorRequest( + url: string, + method: RequestMethod, + data: any, + headers: Headers | any + ) { + return doTorRequest(url, method, JSON.stringify(data), headers); + } + private async handleRegularRequest( + url: string, + method: RequestMethod, + data: any, + headers: Headers | any, + certVerification?: boolean + ) { + const response = await ReactNativeBlobUtil.config({ + trusty: !certVerification + }).fetch(method, url, headers, data ? JSON.stringify(data) : undefined); + if (response.info().status < 300) { + return this.handleSuccessResponse(response); + } else { + this.handleErrorResponse(response); + } + } + private handleSuccessResponse(response: any): any { + if (response.data.includes('\n')) { + const split = response.data.split('\n'); + return JSON.parse(split[split.length - 2]); + } + return response.json(); + } + private handleErrorResponse(response: any): never { + try { + const errorInfo = response.json(); + throw new Error( + (errorInfo.error && errorInfo.error.message) || + errorInfo.message || + errorInfo.error + ); + } catch (e) { + if (response.data && typeof response.data === 'string') { + throw new Error(response.data); + } else { + throw new Error( + localeString('backends.LND.restReq.connectionError') + ); + } + } + } restReq = async ( headers: Headers | any, url: string, - method: any, + method: RequestMethod, data?: any, certVerification?: boolean, useTor?: boolean @@ -38,71 +86,22 @@ export default class LND { } // API is a bit of a mess but // If tor enabled in setting, start up the daemon here - if (useTor === true) { - calls.set( - id, - doTorRequest( - url, - method as RequestMethod, - JSON.stringify(data), - headers - ).then((response: any) => { - calls.delete(id); - return response; - }) - ); - } else { - calls.set( - id, - ReactNativeBlobUtil.config({ - trusty: !certVerification - }) - .fetch( - method, - url, - headers, - data ? JSON.stringify(data) : data - ) - .then((response: any) => { - calls.delete(id); - if (response.info().status < 300) { - // handle ws responses - if (response.data.includes('\n')) { - const split = response.data.split('\n'); - const length = split.length; - // last instance is empty - return JSON.parse(split[length - 2]); - } - return response.json(); - } else { - try { - const errorInfo = response.json(); - throw new Error( - (errorInfo.error && - errorInfo.error.message) || - errorInfo.message || - errorInfo.error - ); - } catch (e) { - if ( - response.data && - typeof response.data === 'string' - ) { - throw new Error(response.data); - } else { - throw new Error( - localeString( - 'backends.LND.restReq.connectionError' - ) - ); - } - } - } - }) - ); + const request = useTor + ? this.handleTorRequest(url, method, data, headers) + : this.handleRegularRequest( + url, + method, + data, + headers, + certVerification + ); + calls.set(id, request); + try { + const response = await request; + return response; + } finally { + calls.delete(id); } - - return await calls.get(id); }; supports = (minVersion: string, eosVersion?: string) => { @@ -190,7 +189,12 @@ export default class LND { return `${baseUrl}${route}`; }; - request = (route: string, method: string, data?: any, params?: any) => { + request = ( + route: string, + method: RequestMethod, + data?: any, + params?: any + ) => { const { host, lndhubUrl, @@ -222,10 +226,11 @@ export default class LND { }; getRequest = (route: string, data?: any) => - this.request(route, 'get', null, data); + this.request(route, RequestMethod.GET, null, data); postRequest = (route: string, data?: any) => - this.request(route, 'post', data); - deleteRequest = (route: string) => this.request(route, 'delete', null); + this.request(route, RequestMethod.POST, data); + deleteRequest = (route: string) => + this.request(route, RequestMethod.DELETE, null); getTransactions = (data: any) => this.getRequest( @@ -327,11 +332,11 @@ export default class LND { let request: any = { private: data.privateChannel, scid_alias: data.scidAlias, - local_funding_amount: data.local_funding_amount, - min_confs: data.min_confs, - node_pubkey_string: data.node_pubkey_string, - sat_per_vbyte: data.sat_per_vbyte, - spend_unconfirmed: data.spend_unconfirmed + local_funding_amount: data.localFundingAmount, + min_confs: data.minConfs, + node_pubkey_string: data.nodePubkeyString, + sat_per_vbyte: data.satoshis, + spend_unconfirmed: data.spendUnconfirmed }; if (data.fundMax) { @@ -359,11 +364,11 @@ export default class LND { let request: any = { private: data.privateChannel, scid_alias: data.scidAlias, - local_funding_amount: data.local_funding_amount, - min_confs: data.min_confs, - node_pubkey_string: data.node_pubkey_string, - sat_per_vbyte: data.sat_per_vbyte, - spend_unconfirmed: data.spend_unconfirmed + local_funding_amount: data.localFundingAmount, + min_confs: data.minConfs, + node_pubkey_string: data.nodePubkeyString, + sat_per_vbyte: data.satPerByte, + spend_unconfirmed: data.spendUnconfirmed }; if (data.fundMax) { @@ -384,14 +389,14 @@ export default class LND { }); } - if (data.node_pubkey_string) { + if (data.nodePubkeyString) { request.node_pubkey = Base64Utils.hexToBase64( - data.node_pubkey_string + data.nodePubkeyString ); } - if (data.funding_shim) { - request.funding_shim = data.funding_shim; + if (data.fundingShim) { + request.funding_shim = data.fundingShim; delete request.sat_per_vbyte; } diff --git a/backends/LightningNodeConnect.ts b/backends/LightningNodeConnect.ts index a39724571..e8d1a5109 100644 --- a/backends/LightningNodeConnect.ts +++ b/backends/LightningNodeConnect.ts @@ -1,4 +1,8 @@ -import { NativeModules, NativeEventEmitter } from 'react-native'; +import { + NativeModules, + NativeEventEmitter, + EventSubscription +} from 'react-native'; import LNC, { lnrpc, walletrpc } from '../zeus_modules/@lightninglabs/lnc-rn'; @@ -13,18 +17,33 @@ import VersionUtils from '../utils/VersionUtils'; import { Hash as sha256Hash } from 'fast-sha256'; -const ADDRESS_TYPES = [ - 'WITNESS_PUBKEY_HASH', - 'NESTED_PUBKEY_HASH', - 'UNUSED_WITNESS_PUBKEY_HASH', - 'UNUSED_NESTED_PUBKEY_HASH', - 'TAPROOT_PUBKEY', - 'UNUSED_TAPROOT_PUBKEY' +const ADDRESS_TYPES: lnrpc.AddressType[] = [ + lnrpc.AddressType.WITNESS_PUBKEY_HASH, + lnrpc.AddressType.NESTED_PUBKEY_HASH, + lnrpc.AddressType.UNUSED_WITNESS_PUBKEY_HASH, + lnrpc.AddressType.UNUSED_NESTED_PUBKEY_HASH, + lnrpc.AddressType.TAPROOT_PUBKEY, + lnrpc.AddressType.UNUSED_TAPROOT_PUBKEY ]; +interface ChanPoint { + funding_txid_str: string; + output_index: number; +} + +interface Params { + base_fee_msat: string; + fee_rate: string; + global?: boolean; + chan_point?: ChanPoint; + time_lock_delta: number; + min_htlc_msat?: string | null; + max_htlc_msat?: string | null; + min_htlc_msat_specified?: boolean; +} export default class LightningNodeConnect { - lnc: any; - listener: any; + lnc: LNC; + listener: EventSubscription | null = null; permOpenChannel: boolean; permSendCoins: boolean; @@ -52,7 +71,7 @@ export default class LightningNodeConnect { }; connect = async () => await this.lnc.connect(); - checkPerms = async () => { + async checkPerms(): Promise { this.permOpenChannel = await this.lnc.hasPerms( 'lnrpc.Lightning.OpenChannel' ); @@ -74,55 +93,60 @@ export default class LightningNodeConnect { this.permSignMessage = await this.lnc.hasPerms( 'signrpc.Signer.SignMessage' ); - }; + } isConnected = async () => await this.lnc.isConnected(); disconnect = () => this.lnc.disconnect(); - - getTransactions = async () => - await this.lnc.lnd.lightning - .getTransactions({}) - .then((data: lnrpc.TransactionDetails) => { - const formatted = snakeize(data); - return { - transactions: formatted.transactions.reverse() - }; - }); - getChannels = async () => - await this.lnc.lnd.lightning - .listChannels({}) - .then((data: lnrpc.ListChannelsResponse) => snakeize(data)); - getPendingChannels = async () => - await this.lnc.lnd.lightning - .pendingChannels({}) - .then((data: lnrpc.PendingChannelsResponse) => snakeize(data)); - getClosedChannels = async () => - await this.lnc.lnd.lightning - .closedChannels({}) - .then((data: lnrpc.ClosedChannelsResponse) => snakeize(data)); - getChannelInfo = async (chanId: string) => { + async getTransactions(): Promise<{ transactions: lnrpc.Transaction[] }> { + const data = await this.lnc.lnd.lightning.getTransactions({}); + const formatted = snakeize(data); + return { + transactions: formatted.transactions.reverse() + }; + } + async getChannels(): Promise { + const data = await this.lnc.lnd.lightning.listChannels({}); + return snakeize(data); + } + + async getPendingChannels(): Promise { + const data = await this.lnc.lnd.lightning.pendingChannels({}); + return snakeize(data); + } + + async getClosedChannels(): Promise { + const data = await this.lnc.lnd.lightning.closedChannels({}); + return snakeize(data); + } + async getChannelInfo(chanId: string): Promise { const request: lnrpc.ChanInfoRequest = { chanId }; - return await this.lnc.lnd.lightning - .getChanInfo(request) - .then((data: lnrpc.ChannelEdge) => snakeize(data)); - }; - getBlockchainBalance = async (req: lnrpc.WalletBalanceRequest) => - await this.lnc.lnd.lightning - .walletBalance(req) - .then((data: lnrpc.WalletBalanceResponse) => snakeize(data)); - getLightningBalance = async (req: lnrpc.ChannelBalanceRequest) => - await this.lnc.lnd.lightning - .channelBalance(req) - .then((data: lnrpc.ChannelBalanceResponse) => snakeize(data)); - sendCoins = async (data: any) => - await this.lnc.lnd.lightning - .sendCoins({ - addr: data.addr, - sat_per_vbyte: data.sat_per_vbyte, - amount: data.amount, - spend_unconfirmed: data.spend_unconfirmed, - send_all: data.send_all - }) - .then((data: lnrpc.SendCoinsResponse) => snakeize(data)); + const data = await this.lnc.lnd.lightning.getChanInfo(request); + return snakeize(data); + } + + async getBlockchainBalance( + req: lnrpc.WalletBalanceRequest + ): Promise { + const data = await this.lnc.lnd.lightning.walletBalance(req); + return snakeize(data); + } + + async getLightningBalance( + req: lnrpc.ChannelBalanceRequest + ): Promise { + const data = await this.lnc.lnd.lightning.channelBalance(req); + return snakeize(data); + } + async sendCoins(data: { + addr: string; + sat_per_vbyte: number; + amount: string; + spend_unconfirmed: boolean; + send_all: boolean; + }): Promise { + const response = await this.lnc.lnd.lightning.sendCoins(data); + return snakeize(response); + } + sendCustomMessage = async (data: any) => await this.lnc.lnd.lightning .sendCustomMessage({ @@ -133,147 +157,159 @@ export default class LightningNodeConnect { .then((data: lnrpc.SendCustomMessageResponse) => snakeize(data)); subscribeCustomMessages = () => this.lnc.lnd.lightning.subscribeCustomMessages({}); - getMyNodeInfo = async () => - await this.lnc.lnd.lightning - .getInfo({}) - .then((data: lnrpc.GetInfoResponse) => snakeize(data)); - getInvoices = async () => - await this.lnc.lnd.lightning - .listInvoices({ reversed: true, num_max_invoices: 100 }) - .then((data: lnrpc.ListInvoiceResponse) => snakeize(data)); - createInvoice = async (data: any) => - await this.lnc.lnd.lightning - .addInvoice({ - memo: data.memo, - value_msat: data.value_msat || Number(data.value) * 1000, - expiry: data.expiry, - is_amp: data.is_amp, - private: data.private, - r_preimage: data.preimage - ? Base64Utils.hexToBase64(data.preimage) - : undefined, - route_hints: data.route_hints - }) - .then((data: lnrpc.AddInvoiceResponse) => snakeize(data)); - getPayments = async () => - await this.lnc.lnd.lightning - .listPayments({ - include_incomplete: true - }) - .then((data: lnrpc.ListPaymentsResponse) => snakeize(data)); - getNewAddress = async (data: any) => - await this.lnc.lnd.lightning - .newAddress({ - type: ADDRESS_TYPES[data.type], - account: data.account || 'default' - }) - .then((data: lnrpc.NewAddressResponse) => snakeize(data)); + async getMyNodeInfo(): Promise { + const data = await this.lnc.lnd.lightning.getInfo({}); + return snakeize(data); + } + + async getInvoices(): Promise { + const data = await this.lnc.lnd.lightning.listInvoices({ + reversed: true, + numMaxInvoices: '100' + }); + return snakeize(data); + } + + async createInvoice(data: { + memo?: string; + value_msat: any; + value?: number; + expiry: string; + is_amp?: boolean; + private?: boolean; + preimage?: string; + route_hints?: any[]; + }): Promise { + const response = await this.lnc.lnd.lightning.addInvoice({ + memo: data.memo, + valueMsat: data.value_msat || Number(data.value) * 1000, + expiry: data.expiry, + isAmp: data.is_amp, + private: data.private, + rPreimage: data.preimage + ? Base64Utils.hexToBase64(data.preimage) + : undefined, + routeHints: data.route_hints + }); + return snakeize(response); + } + async getPayments(): Promise { + const data = await this.lnc.lnd.lightning.listPayments({ + includeIncomplete: true + }); + return snakeize(data); + } + + async getNewAddress(data: { + type: number; + account?: string; + }): Promise { + const response = await this.lnc.lnd.lightning.newAddress({ + type: ADDRESS_TYPES[data.type], + account: data.account || 'default' + }); + return snakeize(response); + } openChannelSync = async (data: OpenChannelRequest) => { - let request: lnrpc.OpenChannelRequest = { - private: data.privateChannel, - scid_alias: data.scidAlias, - local_funding_amount: data.local_funding_amount, - min_confs: data.min_confs, - node_pubkey_string: data.node_pubkey_string, - sat_per_vbyte: data.sat_per_vbyte, - spend_unconfirmed: data.spend_unconfirmed - }; - - if (data.fundMax) { - request.fund_max = true; - } - - if (data.simpleTaprootChannel) { - request.commitment_type = lnrpc.CommitmentType['SIMPLE_TAPROOT']; - } - - if (data.utxos && data.utxos.length > 0) { - request.outpoints = data.utxos.map((utxo: string) => { - const [txid_str, output_index] = utxo.split(':'); - return { - txid_str, - output_index: Number(output_index) - }; - }); - } - return await this.lnc.lnd.lightning - .openChannelSync(request) - .then((data: lnrpc.ChannelPoint) => snakeize(data)); + const request: lnrpc.OpenChannelRequest = + this.createOpenChannelRequest(data); + const response = await this.lnc.lnd.lightning.openChannelSync(request); + return snakeize(response); }; openChannelStream = (data: OpenChannelRequest) => { - let request: lnrpc.OpenChannelRequest = { - private: data.privateChannel || false, - scid_alias: data.scidAlias, - local_funding_amount: data.local_funding_amount, - min_confs: data.min_confs, - node_pubkey: Base64Utils.hexToBase64(data.node_pubkey_string), - sat_per_vbyte: !data.funding_shim ? data.sat_per_vbyte : undefined, - spend_unconfirmed: data.spend_unconfirmed, - funding_shim: data.funding_shim - }; + const request: lnrpc.OpenChannelRequest = + this.createOpenChannelRequest(data); - if (data.fundMax) { - request.fund_max = true; - } - - if (data.simpleTaprootChannel) { - request.commitment_type = lnrpc.CommitmentType['SIMPLE_TAPROOT']; - } + this.lnc.lnd.lightning.openChannel(request); + return new Promise((resolve, reject) => { + const { LncModule } = NativeModules; + const eventEmitter = new NativeEventEmitter(LncModule); + this.listener = eventEmitter.addListener( + 'OpenChannelStream', + this.handleStreamEvent(resolve, reject) + ); + }); + }; + private handleStreamEvent( + resolve: (value: { result: any }) => void, + reject: (reason: any) => void + ) { + return (event: any) => { + if (event.result && event.result !== 'EOF') { + try { + const result = JSON.parse(event.result); + resolve({ result }); + } catch (e) { + const result = JSON.parse(event); + reject(result); + } finally { + this.listener?.remove(); + } + } + }; + } + + private createOpenChannelRequest( + data: OpenChannelRequest + ): lnrpc.OpenChannelRequest { + const request: lnrpc.OpenChannelRequest = { + private: data.privateChannel ?? false, + scidAlias: data.scidAlias ?? false, + localFundingAmount: data.localFundingAmount, + minConfs: data.minConfs, + nodePubkey: data.nodePubkey, + satPerVbyte: data.satPerVbyte, + spendUnconfirmed: data.spendUnconfirmed, + nodePubkeyString: data.nodePubkeyString, + pushSat: data.pushSat, + targetConf: data.targetConf, + satPerByte: data.satPerByte, + minHtlcMsat: data.minHtlcMsat, + remoteCsvDelay: data.remoteCsvDelay, + closeAddress: data.closeAddress, + fundingShim: data.fundingShim as lnrpc.FundingShim, + remoteMaxValueInFlightMsat: data.remoteMaxValueInFlightMsat, + remoteMaxHtlcs: data.remoteMaxHtlcs, + maxLocalCsv: data.maxLocalCsv, + commitmentType: data.simpleTaprootChannel + ? lnrpc.CommitmentType.SIMPLE_TAPROOT + : lnrpc.CommitmentType.UNKNOWN_COMMITMENT_TYPE, + zeroConf: data.zeroConf ?? false, + baseFee: data.baseFee, + feeRate: data.feeRate, + useBaseFee: false, + useFeeRate: false, + remoteChanReserveSat: '', + fundMax: data.fundMax ?? false, + memo: '', + outpoints: [] + }; if (data.utxos && data.utxos.length > 0) { - request.outpoints = data.utxos.map((utxo: string) => { - const [txid_str, output_index] = utxo.split(':'); - return { - txid_str, - output_index: Number(output_index) - }; - }); + request.outpoints = data.utxos.map(this.parseUtxo); } - if (data.funding_shim) { - request.funding_shim = data.funding_shim; - delete request.sat_per_vbyte; - } + return request; + } - const streamingCall = this.lnc.lnd.lightning.openChannel(request); + private parseUtxo(utxo: string): lnrpc.OutPoint { + const [txidBytes, txidStr, outputIndex] = utxo.split(':'); + return { + txidBytes, + txidStr, + outputIndex: Number(outputIndex) + }; + } - const { LncModule } = NativeModules; - const eventEmitter = new NativeEventEmitter(LncModule); - return new Promise((resolve, reject) => { - this.listener = eventEmitter.addListener( - streamingCall, - (event: any) => { - if (event.result && event.result !== 'EOF') { - let result; - try { - result = JSON.parse(event.result); - - resolve({ result }); - this.listener.remove(); - } catch (e) { - try { - result = JSON.parse(event); - } catch (e2) { - result = event.result || event; - } - - reject(result); - this.listener.remove(); - } - } - } - ); - }); - }; - connectPeer = async (data: any) => - await this.lnc.lnd.lightning - .connectPeer(data) - .then((data: lnrpc.ConnectPeerRequest) => snakeize(data)); + async connectPeer(data: any): Promise { + const result = this.lnc.lnd.lightning.connectPeer(data); + return snakeize(result); + } decodePaymentRequest = async (urlParams?: Array) => await this.lnc.lnd.lightning - .decodePayReq({ pay_req: urlParams && urlParams[0] }) + .decodePayReq({ payReq: urlParams && urlParams[0] }) .then((data: lnrpc.PayReq) => snakeize(data)); payLightningInvoice = (data: any) => { if (data.pubkey) delete data.pubkey; @@ -303,7 +339,7 @@ export default class LightningNodeConnect { }; getNodeInfo = async (urlParams?: Array) => await this.lnc.lnd.lightning - .getNodeInfo({ pub_key: urlParams && urlParams[0] }) + .getNodeInfo({ pubKey: urlParams && urlParams[0] }) .then((data: lnrpc.NodeInfo) => snakeize(data)); getFees = async () => await this.lnc.lnd.lightning @@ -314,7 +350,7 @@ export default class LightningNodeConnect { const base_fee_msat = data.base_fee_msat.replace(/,/g, '.'); const fee_rate = data.fee_rate.replace(/,/g, '.'); - let params; + let params: Params; if (data.global) { params = { @@ -356,8 +392,8 @@ export default class LightningNodeConnect { getRoutes = async (urlParams?: Array) => await this.lnc.lnd.lightning .queryRoutes({ - pub_key: urlParams && urlParams[0], - amt: urlParams && urlParams[1] && Number(urlParams[1]) + pubKey: urlParams && urlParams[0], + amt: urlParams && urlParams[1] }) .then((data: lnrpc.QueryRoutesResponse) => snakeize(data)); getForwardingHistory = async (hours = 24) => { @@ -367,13 +403,13 @@ export default class LightningNodeConnect { new Date(Date.now() - hours * 60 * 60 * 1000).getTime() / 1000 ).toString(), endTime: Math.round(new Date().getTime() / 1000).toString(), - indexOffset: 0 + indexOffset: 0, + peerAliasLookup: false }; return await this.lnc.lnd.lightning .forwardingHistory(req) .then((data: lnrpc.ForwardingHistoryResponse) => snakeize(data)); }; - // Coin Control fundPsbt = async (req: walletrpc.FundPsbtRequest) => await this.lnc.lnd.walletKit .fundPsbt(req) @@ -387,16 +423,16 @@ export default class LightningNodeConnect { .finalizePsbt(req) .then((data: walletrpc.FinalizePsbtResponse) => snakeize(data)); publishTransaction = async (req: walletrpc.Transaction) => { - if (req.tx_hex) req.tx_hex = Base64Utils.hexToBase64(req.tx_hex); + if (req.txHex) req.txHex = Base64Utils.hexToBase64(req.txHex as string); return await this.lnc.lnd.walletKit .publishTransaction(req) .then((data: walletrpc.PublishResponse) => snakeize(data)); }; fundingStateStep = async (req: lnrpc.FundingTransitionMsg) => { // Finalize - if (req.psbt_finalize?.final_raw_tx) - req.psbt_finalize.final_raw_tx = Base64Utils.hexToBase64( - req.psbt_finalize.final_raw_tx + if (req.psbtFinalize?.finalRawTx) + req.psbtFinalize.finalRawTx = Base64Utils.hexToBase64( + req.psbtFinalize.finalRawTx as string ); return await this.lnc.lnd.lightning @@ -443,50 +479,52 @@ export default class LightningNodeConnect { }; lookupInvoice = async (data: lnrpc.PaymentHash) => await this.lnc.lnd.lightning - .lookupInvoice({ r_hash: Base64Utils.hexToBase64(data.r_hash) }) + .lookupInvoice({ + rHash: Base64Utils.hexToBase64(data.rHash as string) + }) .then((data: lnrpc.Invoice) => snakeize(data)); - subscribeInvoice = (r_hash: string) => - this.lnc.lnd.invoices.subscribeSingleInvoice({ r_hash }); + subscribeInvoice = (rHash: string) => + this.lnc.lnd.invoices.subscribeSingleInvoice({ rHash }); subscribeInvoices = () => this.lnc.lnd.lightning.subscribeInvoices(); subscribeTransactions = () => this.lnc.lnd.lightning.subscribeTransactions(); - supports = (minVersion: string, eosVersion?: string) => { + supports(minVersion: string, eosVersion?: string): boolean { const { nodeInfo } = stores.nodeInfoStore; const { version } = nodeInfo; const { isSupportedVersion } = VersionUtils; return isSupportedVersion(version, minVersion, eosVersion); - }; - - supportsMessageSigning = () => this.permSignMessage; - supportsLnurlAuth = () => true; - supportsOnchainSends = () => this.permSendCoins; - supportsOnchainReceiving = () => this.permNewAddress; - supportsLightningSends = () => this.permSendLN; - supportsKeysend = () => true; - supportsChannelManagement = () => this.permOpenChannel; - supportsPendingChannels = () => true; - supportsMPP = () => this.supports('v0.10.0'); - supportsAMP = () => this.supports('v0.13.0'); - supportsCoinControl = () => this.permNewAddress; - supportsChannelCoinControl = () => + } + + supportsMessageSigning = (): boolean => this.permSignMessage; + supportsLnurlAuth = (): boolean => true; + supportsOnchainSends = (): boolean => this.permSendCoins; + supportsOnchainReceiving = (): boolean => this.permNewAddress; + supportsLightningSends = (): boolean => this.permSendLN; + supportsKeysend = (): boolean => true; + supportsChannelManagement = (): boolean => this.permOpenChannel; + supportsPendingChannels = (): boolean => true; + supportsMPP = (): boolean => this.supports('v0.10.0'); + supportsAMP = (): boolean => this.supports('v0.13.0'); + supportsCoinControl = (): boolean => this.permNewAddress; + supportsChannelCoinControl = (): boolean => this.permNewAddress && this.supports('v0.17.0'); - supportsHopPicking = () => this.permOpenChannel; - supportsAccounts = () => this.permImportAccount; - supportsRouting = () => this.permForwardingHistory; - supportsNodeInfo = () => true; - singleFeesEarnedTotal = () => false; - supportsAddressTypeSelection = () => true; - supportsTaproot = () => this.supports('v0.15.0'); - supportsBumpFee = () => true; - supportsLSPs = () => false; - supportsNetworkInfo = () => false; - supportsSimpleTaprootChannels = () => this.supports('v0.17.0'); - supportsCustomPreimages = () => true; - supportsSweep = () => true; - supportsOnchainBatching = () => true; - supportsChannelBatching = () => true; - isLNDBased = () => true; - supportsLSPS1customMessage = () => true; - supportsLSPS1rest = () => false; + supportsHopPicking = (): boolean => this.permOpenChannel; + supportsAccounts = (): boolean => this.permImportAccount; + supportsRouting = (): boolean => this.permForwardingHistory; + supportsNodeInfo = (): boolean => true; + singleFeesEarnedTotal = (): boolean => false; + supportsAddressTypeSelection = (): boolean => true; + supportsTaproot = (): boolean => this.supports('v0.15.0'); + supportsBumpFee = (): boolean => true; + supportsLSPs = (): boolean => false; + supportsNetworkInfo = (): boolean => false; + supportsSimpleTaprootChannels = (): boolean => this.supports('v0.17.0'); + supportsCustomPreimages = (): boolean => true; + supportsSweep = (): boolean => true; + supportsOnchainBatching = (): boolean => true; + supportsChannelBatching = (): boolean => true; + isLNDBased = (): boolean => true; + supportsLSPS1customMessage = (): boolean => true; + supportsLSPS1rest = (): boolean => false; } diff --git a/models/OpenChannelRequest.ts b/models/OpenChannelRequest.ts index e9a68c58d..ada5353a3 100644 --- a/models/OpenChannelRequest.ts +++ b/models/OpenChannelRequest.ts @@ -1,3 +1,7 @@ +import { + CommitmentType, + OutPoint +} from '@lightninglabs/lnc-core/dist/types/proto/lnrpc'; import BaseModel from './BaseModel'; export interface AdditionalChannel { @@ -8,19 +12,19 @@ export interface AdditionalChannel { } export default class OpenChannelRequest extends BaseModel { - public min_confs?: number; - public spend_unconfirmed?: boolean; - public remote_csv_delay?: number; - public node_pubkey_string: string; - public node_pubkey?: any; - public push_sat?: string; - public target_conf?: number; - public sat_per_byte?: string; // deprecated - public sat_per_vbyte?: string; + public minConfs: number; + public spendUnconfirmed: boolean; + public remoteCsvDelay: number; + public nodePubkeyString: string; + public nodePubkey?: any; + public pushSat: string; + public targetConf: number; + public satPerByte: string; // deprecated + public satPerVbyte: string; public private?: boolean; - public min_htlc_msat?: string; - public local_funding_amount: string; - public host: string; + public minHtlcMsat: string; + public localFundingAmount: string; + public host?: string; public id?: string; public satoshis?: string; public utxos?: string[]; @@ -28,13 +32,22 @@ export default class OpenChannelRequest extends BaseModel { public scidAlias?: boolean; public simpleTaprootChannel?: boolean; public fundMax?: boolean; + public outpoints?: OutPoint[]; + public commitmentType: CommitmentType; + public remoteMaxHtlcs:number + public closeAddress: string; + public remoteMaxValueInFlightMsat:string; + public maxLocalCsv:number; + public baseFee:string; + public feeRate:string; + public zeroConf:boolean; // external accoutn funding public account?: string; - public funding_shim?: { - psbt_shim: { - pending_chan_id: any; - base_psbt: string; - no_publish?: boolean; + public fundingShim: { + psbtShim: { + pendingChanId: Uint8Array | string; + basePsbt: Uint8Array | string; + noPublish: boolean; }; }; public additionalChannels?: Array; diff --git a/utils/DataFormatUtils.ts b/utils/DataFormatUtils.ts index 91b034b25..a06e8a56c 100644 --- a/utils/DataFormatUtils.ts +++ b/utils/DataFormatUtils.ts @@ -1,10 +1,14 @@ import { snakeCase, isArray, isObject, transform } from 'lodash'; -// change responses from camel-case to snake-case -const snakeize = (obj) => - transform(obj, (acc, value, key, target) => { +/** + * Recursively transforms an object's keys from camelCase to snake_case. + * + * @param obj - The object to transform + * @returns A new object with all keys converted to snake_case + */ +export function snakeize(obj: T): T { + return transform(obj, (acc: any, value: any, key: string, target: any) => { const snakeKey = isArray(target) ? key : snakeCase(key); acc[snakeKey] = isObject(value) ? snakeize(value) : value; }); - -export { snakeize }; +} diff --git a/zeus_modules/@lightninglabs/lnc-rn/dist/typescript/types/lnc.d.ts b/zeus_modules/@lightninglabs/lnc-rn/dist/typescript/types/lnc.d.ts index 1a853cc27..be8361c3a 100644 --- a/zeus_modules/@lightninglabs/lnc-rn/dist/typescript/types/lnc.d.ts +++ b/zeus_modules/@lightninglabs/lnc-rn/dist/typescript/types/lnc.d.ts @@ -27,6 +27,7 @@ export interface LncConfig { * authentication and connection process. */ export interface CredentialStore { + load(pairingPhrase: string): unknown; /** Stores the LNC pairing phrase used to initialize the connection to the LNC proxy */ pairingPhrase: string; /** Stores the host:port of the Lightning Node Connect proxy server to connect to */ diff --git a/zeus_modules/@lightninglabs/lnc-rn/lib/lnc.ts b/zeus_modules/@lightninglabs/lnc-rn/lib/lnc.ts index 5fd4d1ca8..63b161d8f 100644 --- a/zeus_modules/@lightninglabs/lnc-rn/lib/lnc.ts +++ b/zeus_modules/@lightninglabs/lnc-rn/lib/lnc.ts @@ -37,7 +37,7 @@ export default class LNC { if (config.credentialStore) { this.credentials = config.credentialStore; } else { - this.credentials = new LncCredentialStore(config.pairingPhrase); + this.credentials = new LncCredentialStore(config.credentialStore); // don't overwrite an existing serverHost if we're already paired if (!this.credentials.isPaired) this.credentials.serverHost = config.serverHost; diff --git a/zeus_modules/@lightninglabs/lnc-rn/lib/types/lnc.ts b/zeus_modules/@lightninglabs/lnc-rn/lib/types/lnc.ts index 52a5f393d..98d6c2dab 100644 --- a/zeus_modules/@lightninglabs/lnc-rn/lib/types/lnc.ts +++ b/zeus_modules/@lightninglabs/lnc-rn/lib/types/lnc.ts @@ -37,10 +37,12 @@ export interface CredentialStore { /** Stores the remote static key which LNC uses to reestablish a connection */ remoteKey: string; /** + * * Read-only field which should return `true` if the client app has prior * credentials persisted in the store */ isPaired: boolean; /** Clears any persisted data in the store */ clear(): void; + load?: (pairingPhrase?: string) => Promise; } diff --git a/zeus_modules/@lightninglabs/lnc-rn/lib/util/credentialStore.ts b/zeus_modules/@lightninglabs/lnc-rn/lib/util/credentialStore.ts index 94fd24f42..d5ea07eb1 100644 --- a/zeus_modules/@lightninglabs/lnc-rn/lib/util/credentialStore.ts +++ b/zeus_modules/@lightninglabs/lnc-rn/lib/util/credentialStore.ts @@ -64,7 +64,6 @@ export default class LncCredentialStore implements CredentialStore { get isPaired() { return !!this._remoteKey || !!this._pairingPhrase; } - /** Clears any persisted data in the store */ clear() { this._serverHost = ''; @@ -72,4 +71,5 @@ export default class LncCredentialStore implements CredentialStore { this._remoteKey = ''; this._pairingPhrase = ''; } + } From 8364edaf78e91caf2c5de16f177e61ecf6e77c47 Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Mon, 2 Sep 2024 11:09:37 +0530 Subject: [PATCH 7/9] fix : ts or prettier error in backend --- backends/EmbeddedLND.ts | 10 ++++++---- backends/LND.ts | 6 +++--- lndmobile/LndMobileInjection.ts | 6 +++--- lndmobile/wallet.ts | 2 +- models/OpenChannelRequest.ts | 13 +++++++------ 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/backends/EmbeddedLND.ts b/backends/EmbeddedLND.ts index dafc64afb..37dbdac35 100644 --- a/backends/EmbeddedLND.ts +++ b/backends/EmbeddedLND.ts @@ -8,6 +8,7 @@ import { checkLndStreamErrorResponse, LndMobileEventEmitter } from '../utils/LndMobileUtils'; +import Long from 'long'; const { addInvoice, @@ -146,7 +147,7 @@ export default class EmbeddedLND extends LND { }; connectPeer = async (data: any) => await connectPeer(data.addr.pubkey, data.addr.host, data.perm); - decodePaymentRequest = async (urlParams?: string[]) => + decodePaymentRequest = async (urlParams: Array) => await decodePayReq(urlParams && urlParams[0]); payLightningInvoice = async (data: any) => { const sendPaymentReq = { @@ -202,7 +203,7 @@ export default class EmbeddedLND extends LND { ); }; - getNodeInfo = async (urlParams: string[]) => + getNodeInfo = async (urlParams: Array) => await getNodeInfo(urlParams[0]); signMessage = async (msg: string) => { return await signMessageNodePubkey(Base64Utils.stringToUint8Array(msg)); @@ -217,8 +218,9 @@ export default class EmbeddedLND extends LND { // getFees = () => N/A; // setFees = () => N/A; - getRoutes = async (urlParams?: Array) => - urlParams && (await queryRoutes(urlParams[0], urlParams[1])); + getRoutes = async (urlParams: Array) => + urlParams && + (await queryRoutes(urlParams[0] as string, urlParams[1] as Long)); // getForwardingHistory = () => N/A // // Coin Control fundPsbt = async (data: any) => await fundPsbt(data); diff --git a/backends/LND.ts b/backends/LND.ts index 054211a72..6eeaa28ba 100644 --- a/backends/LND.ts +++ b/backends/LND.ts @@ -456,7 +456,7 @@ export default class LND { }); }; connectPeer = (data: any) => this.postRequest('/v1/peers', data); - decodePaymentRequest = (urlParams?: Array) => + decodePaymentRequest = (urlParams: Array) => this.getRequest(`/v1/payreq/${urlParams && urlParams[0]}`); payLightningInvoice = async (data: any) => { if (data.pubkey) delete data.pubkey; @@ -498,7 +498,7 @@ export default class LND { return this.deleteRequest(requestString); }; - getNodeInfo = (urlParams?: Array) => + getNodeInfo = (urlParams: Array) => this.getRequest(`/v1/graph/node/${urlParams && urlParams[0]}`); getFees = () => this.getRequest('/v1/fees'); setFees = (data: any) => { @@ -535,7 +535,7 @@ export default class LND { min_htlc_msat_specified: min_htlc ? true : false }); }; - getRoutes = (urlParams?: Array) => + getRoutes = (urlParams: Array) => this.getRequest( `/v1/graph/routes/${urlParams && urlParams[0]}/${ urlParams && urlParams[1] diff --git a/lndmobile/LndMobileInjection.ts b/lndmobile/LndMobileInjection.ts index 932eb4f7d..e8939be6c 100644 --- a/lndmobile/LndMobileInjection.ts +++ b/lndmobile/LndMobileInjection.ts @@ -181,7 +181,7 @@ export interface ILndMobileInjections { payment_request, amt, max_shard_size_msat, - max_parts = 1, + max_parts, fee_limit_sat, last_hop_pubkey, message, @@ -222,7 +222,7 @@ export interface ILndMobileInjections { sendKeysendPaymentV2: ({ amt, max_shard_size_msat, - max_parts = 1, + max_parts, fee_limit_sat, message, cltv_limit, @@ -543,4 +543,4 @@ export default { scheduledSync: { checkScheduledSyncWorkStatus } -} as ILndMobileInjections; +} as unknown as ILndMobileInjections; diff --git a/lndmobile/wallet.ts b/lndmobile/wallet.ts index e65fe5d3c..ce8adec25 100644 --- a/lndmobile/wallet.ts +++ b/lndmobile/wallet.ts @@ -439,7 +439,7 @@ export const subscribeInvoices = async (): Promise => { ); return response; } catch (e) { - throw e.message; + throw (e as Error).message; } }; diff --git a/models/OpenChannelRequest.ts b/models/OpenChannelRequest.ts index ada5353a3..6b35addb3 100644 --- a/models/OpenChannelRequest.ts +++ b/models/OpenChannelRequest.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-unresolved */ import { CommitmentType, OutPoint @@ -34,13 +35,13 @@ export default class OpenChannelRequest extends BaseModel { public fundMax?: boolean; public outpoints?: OutPoint[]; public commitmentType: CommitmentType; - public remoteMaxHtlcs:number + public remoteMaxHtlcs: number; public closeAddress: string; - public remoteMaxValueInFlightMsat:string; - public maxLocalCsv:number; - public baseFee:string; - public feeRate:string; - public zeroConf:boolean; + public remoteMaxValueInFlightMsat: string; + public maxLocalCsv: number; + public baseFee: string; + public feeRate: string; + public zeroConf: boolean; // external accoutn funding public account?: string; public fundingShim: { From 4aa5d4af4b1bf45201c30f2f6ec8954a66bc4081 Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Mon, 2 Sep 2024 11:24:16 +0530 Subject: [PATCH 8/9] fix : ts and prettier error in backend --- backends/EmbeddedLND.ts | 5 +---- backends/LND.ts | 14 +++----------- backends/Spark.ts | 4 ++-- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/backends/EmbeddedLND.ts b/backends/EmbeddedLND.ts index 37dbdac35..1877895d6 100644 --- a/backends/EmbeddedLND.ts +++ b/backends/EmbeddedLND.ts @@ -22,7 +22,6 @@ const { sendKeysendPaymentV2, listPayments, getNetworkInfo, - getRecoveryInfo, queryRoutes, lookupInvoice, fundingStateStep, @@ -77,7 +76,6 @@ export default class EmbeddedLND extends LND { subscribeCustomMessages = async () => await subscribeCustomMessages(); getMyNodeInfo = async () => await getInfo(); getNetworkInfo = async () => await getNetworkInfo(); - getRecoveryInfo = async () => await getRecoveryInfo(); getInvoices = async () => await listInvoices(); createInvoice = async (data: any) => await addInvoice({ @@ -298,8 +296,7 @@ export default class EmbeddedLND extends LND { supportsSweep = () => true; supportsOnchainBatching = () => true; supportsChannelBatching = () => true; + isLNDBased = () => true; supportsLSPS1customMessage = () => true; supportsLSPS1rest = () => false; - supportsOffers = () => false; - isLNDBased = () => true; } diff --git a/backends/LND.ts b/backends/LND.ts index 6eeaa28ba..9c9770021 100644 --- a/backends/LND.ts +++ b/backends/LND.ts @@ -307,7 +307,6 @@ export default class LND { console.log('subscribeCustomMessages ws close'); }); }; - getNetworkInfo = () => this.getRequest('/v1/graph/info'); getMyNodeInfo = () => this.getRequest('/v1/getinfo'); getInvoices = (data: any) => this.getRequest( @@ -337,17 +336,11 @@ export default class LND { min_confs: data.minConfs, node_pubkey_string: data.nodePubkeyString, sat_per_vbyte: data.satoshis, - spend_unconfirmed: data.spendUnconfirmed, - local_funding_amount: data.local_funding_amount || 0, - min_confs: data.min_confs, - node_pubkey_string: data.node_pubkey_string, - sat_per_vbyte: data.sat_per_vbyte, - spend_unconfirmed: data.spend_unconfirmed + spend_unconfirmed: data.spendUnconfirmed }; if (data.fundMax) { request.fund_max = true; - delete request.local_funding_amount; } if (data.simpleTaprootChannel) { @@ -673,14 +666,13 @@ export default class LND { supportsTaproot = () => this.supports('v0.15.0'); supportsBumpFee = () => true; supportsLSPs = () => true; - supportsNetworkInfo = () => true; + supportsNetworkInfo = () => false; supportsSimpleTaprootChannels = () => this.supports('v0.17.0'); supportsCustomPreimages = () => true; supportsSweep = () => true; supportsOnchainBatching = () => true; supportsChannelBatching = () => true; + isLNDBased = () => true; supportsLSPS1customMessage = () => true; supportsLSPS1rest = () => false; - supportsOffers = (): Promise | boolean => false; - isLNDBased = () => true; } diff --git a/backends/Spark.ts b/backends/Spark.ts index 5a8cff99e..fd62c0ede 100644 --- a/backends/Spark.ts +++ b/backends/Spark.ts @@ -231,9 +231,9 @@ export default class Spark { getNewAddress = () => this.rpc('newaddr'); openChannelSync = (data: OpenChannelRequest) => this.rpc('fundchannel', { - id: data.node_pubkey_string, + id: data.nodePubkeyString, amount: data.satoshis, - feerate: `${Number(data.sat_per_vbyte) * 1000}perkb`, + feerate: `${Number(data.satPerVbyte) * 1000}perkb`, announce: !data.privateChannel }).then(({ txid }: any) => ({ funding_txid_str: txid })); connectPeer = (data: any) => From 4cd7b4751f287c8ab58746e02a3a289512a0c3d8 Mon Sep 17 00:00:00 2001 From: ajaysehwal Date: Tue, 3 Sep 2024 08:48:13 +0530 Subject: [PATCH 9/9] fix : duplicate method error --- backends/LightningNodeConnect.ts | 93 ++------------------------------ 1 file changed, 5 insertions(+), 88 deletions(-) diff --git a/backends/LightningNodeConnect.ts b/backends/LightningNodeConnect.ts index f8438c223..b6775008b 100644 --- a/backends/LightningNodeConnect.ts +++ b/backends/LightningNodeConnect.ts @@ -193,45 +193,11 @@ export default class LightningNodeConnect { }); return snakeize(response); } - getMyNodeInfo = async () => - await this.lnc.lnd.lightning - .getInfo({}) - .then((data: lnrpc.GetInfoResponse) => snakeize(data)); - getNetworkInfo = async () => - await this.lnc.lnd.lightning - .getNetworkInfo({}) - .then((data: lnrpc.NetworkInfo) => snakeize(data)); - getInvoices = async () => - await this.lnc.lnd.lightning - .listInvoices({ reversed: true, num_max_invoices: 100 }) - .then((data: lnrpc.ListInvoiceResponse) => snakeize(data)); - createInvoice = async (data: any) => - await this.lnc.lnd.lightning - .addInvoice({ - memo: data.memo, - value_msat: data.value_msat || Number(data.value) * 1000, - expiry: data.expiry, - is_amp: data.is_amp, - private: data.private, - r_preimage: data.preimage - ? Base64Utils.hexToBase64(data.preimage) - : undefined, - route_hints: data.route_hints - }) - .then((data: lnrpc.AddInvoiceResponse) => snakeize(data)); - getPayments = async () => - await this.lnc.lnd.lightning - .listPayments({ - include_incomplete: true - }) - .then((data: lnrpc.ListPaymentsResponse) => snakeize(data)); - getNewAddress = async (data: any) => - await this.lnc.lnd.lightning - .newAddress({ - type: ADDRESS_TYPES[data.type], - account: data.account || 'default' - }) - .then((data: lnrpc.NewAddressResponse) => snakeize(data)); + + async getNetworkInfo(): Promise { + const info = await this.lnc.lnd.lightning.getNetworkInfo({}); + return snakeize(info); + } async getPayments(): Promise { const data = await this.lnc.lnd.lightning.listPayments({ @@ -255,37 +221,6 @@ export default class LightningNodeConnect { this.createOpenChannelRequest(data); const response = await this.lnc.lnd.lightning.openChannelSync(request); return snakeize(response); - let request: lnrpc.OpenChannelRequest = { - private: data.privateChannel, - scid_alias: data.scidAlias, - local_funding_amount: data.local_funding_amount || 0, - min_confs: data.min_confs, - node_pubkey_string: data.node_pubkey_string, - sat_per_vbyte: data.sat_per_vbyte, - spend_unconfirmed: data.spend_unconfirmed - }; - - if (data.fundMax) { - request.fund_max = true; - delete request.local_funding_amount; - } - - if (data.simpleTaprootChannel) { - request.commitment_type = lnrpc.CommitmentType['SIMPLE_TAPROOT']; - } - - if (data.utxos && data.utxos.length > 0) { - request.outpoints = data.utxos.map((utxo: string) => { - const [txid_str, output_index] = utxo.split(':'); - return { - txid_str, - output_index: Number(output_index) - }; - }); - } - return await this.lnc.lnd.lightning - .openChannelSync(request) - .then((data: lnrpc.ChannelPoint) => snakeize(data)); }; openChannelStream = (data: OpenChannelRequest) => { @@ -597,23 +532,5 @@ export default class LightningNodeConnect { isLNDBased = (): boolean => true; supportsLSPS1customMessage = (): boolean => true; supportsLSPS1rest = (): boolean => false; - supportsHopPicking = () => this.permOpenChannel; - supportsAccounts = () => this.permImportAccount; - supportsRouting = () => this.permForwardingHistory; - supportsNodeInfo = () => true; - singleFeesEarnedTotal = () => false; - supportsAddressTypeSelection = () => true; - supportsTaproot = () => this.supports('v0.15.0'); - supportsBumpFee = () => true; - supportsLSPs = () => false; - supportsNetworkInfo = () => true; - supportsSimpleTaprootChannels = () => this.supports('v0.17.0'); - supportsCustomPreimages = () => true; - supportsSweep = () => true; - supportsOnchainBatching = () => true; - supportsChannelBatching = () => true; - supportsLSPS1customMessage = () => true; - supportsLSPS1rest = () => false; supportsOffers = () => false; - isLNDBased = () => true; }