From 1589dfa1956a527c22e9448b18840b86fd82f969 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Wed, 15 May 2024 13:35:42 -0400 Subject: [PATCH 1/5] DEL: Artificial Splash delay --- android/app/src/main/AndroidManifest.xml | 13 +++------- .../bluewallet/bluewallet/SplashActivity.java | 26 ------------------- .../app/src/main/res/layout/splash_screen.xml | 13 ---------- blue_modules/storage-context.tsx | 15 ++++++----- 4 files changed, 12 insertions(+), 55 deletions(-) delete mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/SplashActivity.java delete mode 100644 android/app/src/main/res/layout/splash_screen.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 385824b24..04a9554ff 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -64,16 +64,7 @@ - - - - - - - - + + + diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/SplashActivity.java b/android/app/src/main/java/io/bluewallet/bluewallet/SplashActivity.java deleted file mode 100644 index 161055463..000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/SplashActivity.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.bluewallet.bluewallet; // Replace with your package name - -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import androidx.appcompat.app.AppCompatActivity; - -public class SplashActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.splash_screen); // Replace with your layout name - - int SPLASH_DISPLAY_LENGTH = 1000; // Splash screen duration in milliseconds - - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - Intent mainIntent = new Intent(SplashActivity.this, MainActivity.class); - SplashActivity.this.startActivity(mainIntent); - SplashActivity.this.finish(); - } - }, SPLASH_DISPLAY_LENGTH); - } -} diff --git a/android/app/src/main/res/layout/splash_screen.xml b/android/app/src/main/res/layout/splash_screen.xml deleted file mode 100644 index c90f097b0..000000000 --- a/android/app/src/main/res/layout/splash_screen.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/blue_modules/storage-context.tsx b/blue_modules/storage-context.tsx index 6f79b3183..3bed3789b 100644 --- a/blue_modules/storage-context.tsx +++ b/blue_modules/storage-context.tsx @@ -73,12 +73,15 @@ export const BlueStorageProvider = ({ children }: { children: React.ReactNode }) const [reloadTransactionsMenuActionFunction, setReloadTransactionsMenuActionFunction] = useState<() => void>(() => {}); useEffect(() => { - setWallets(BlueApp.getWallets()); - - BlueElectrum.isDisabled().then(setIsElectrumDisabled); - if (walletsInitialized) { - BlueElectrum.connectMain(); - } + (async () => { + const isElectrumDisabledValue = await BlueElectrum.isDisabled(); + setIsElectrumDisabled(isElectrumDisabledValue); + if (walletsInitialized && wallets.length > 0 && !isElectrumDisabledValue) { + setWallets(BlueApp.getWallets()); + BlueElectrum.connectMain(); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [walletsInitialized]); const saveToDisk = async (force: boolean = false) => { From 667dba4836466907e13888d3a158ff3fbf74872d Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Wed, 15 May 2024 15:53:08 -0400 Subject: [PATCH 2/5] Update AndroidManifest.xml --- android/app/src/main/AndroidManifest.xml | 151 ++++++++++++----------- 1 file changed, 77 insertions(+), 74 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 04a9554ff..7376d7788 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,50 +7,52 @@ android:required="false" /> - - - + + + - + + android:name=".MainApplication" + android:label="@string/app_name" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:allowBackup="false" + android:largeHeap="true" + android:extractNativeLibs="true" + android:usesCleartextTraffic="true" + android:supportsRtl="true" + android:theme="@style/AppTheme" + android:networkSecurityConfig="@xml/network_security_config"> - - - - - - - - - + + + + + - @@ -58,64 +60,65 @@ + android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService" + android:exported="false"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + From 7e6418316ac2762de1daecd76ed89ad319899df1 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Wed, 15 May 2024 18:06:55 -0400 Subject: [PATCH 3/5] DEL: Splash iOS --- App.tsx | 12 +- MasterView.tsx | 11 +- class/wallets/lightning-ldk-wallet.ts | 316 ++++------------------- ios/BlueWallet.xcodeproj/project.pbxproj | 18 +- ios/BlueWallet/AppDelegate.mm | 18 -- ios/SplashScreen.m | 14 - ios/SplashScreen.swift | 39 --- screen/UnlockWith.tsx | 6 +- 8 files changed, 53 insertions(+), 381 deletions(-) delete mode 100644 ios/SplashScreen.m delete mode 100644 ios/SplashScreen.swift diff --git a/App.tsx b/App.tsx index 0772aed08..5f3b9bfb9 100644 --- a/App.tsx +++ b/App.tsx @@ -1,6 +1,6 @@ import 'react-native-gesture-handler'; // should be on top -import React, { useEffect } from 'react'; -import { NativeModules, Platform, useColorScheme } from 'react-native'; +import React from 'react'; +import { useColorScheme } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { navigationRef } from './NavigationService'; @@ -9,18 +9,10 @@ import { NavigationProvider } from './components/NavigationProvider'; import { BlueStorageProvider } from './blue_modules/storage-context'; import MasterView from './MasterView'; import { SettingsProvider } from './components/Context/SettingsContext'; -const { SplashScreen } = NativeModules; const App = () => { const colorScheme = useColorScheme(); - useEffect(() => { - if (Platform.OS === 'ios') { - // Call hide to setup the listener on the native side - SplashScreen?.addObserver(); - } - }, []); - return ( diff --git a/MasterView.tsx b/MasterView.tsx index f12468d37..7bef9f3c9 100644 --- a/MasterView.tsx +++ b/MasterView.tsx @@ -1,22 +1,13 @@ import 'react-native-gesture-handler'; // should be on top -import React, { Suspense, lazy, useEffect } from 'react'; -import { NativeModules, Platform } from 'react-native'; +import React, { Suspense, lazy } from 'react'; import MainRoot from './navigation'; import { useStorage } from './blue_modules/storage-context'; import Biometric from './class/biometrics'; const CompanionDelegates = lazy(() => import('./components/CompanionDelegates')); -const { SplashScreen } = NativeModules; const MasterView = () => { const { walletsInitialized } = useStorage(); - useEffect(() => { - if (Platform.OS === 'ios') { - // Call hide to setup the listener on the native side - SplashScreen?.addObserver(); - } - }, []); - return ( <> diff --git a/class/wallets/lightning-ldk-wallet.ts b/class/wallets/lightning-ldk-wallet.ts index aab484a55..82d223c2f 100644 --- a/class/wallets/lightning-ldk-wallet.ts +++ b/class/wallets/lightning-ldk-wallet.ts @@ -1,24 +1,16 @@ -import * as bip39 from 'bip39'; -import * as bitcoin from 'bitcoinjs-lib'; -import bolt11 from 'bolt11'; -import RNFS from 'react-native-fs'; -import RnLdk from 'rn-ldk/src/index'; -import presentAlert from '../../components/Alert'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; -import { randomBytes } from '../rng'; -import SyncedAsyncStorage from '../synced-async-storage'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; import { LightningCustodianWallet } from './lightning-custodian-wallet'; +import { randomBytes } from '../rng'; +import * as bip39 from 'bip39'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +import bolt11 from 'bolt11'; import { SegwitBech32Wallet } from './segwit-bech32-wallet'; +import presentAlert from '../../components/Alert'; +import * as bitcoin from 'bitcoinjs-lib'; export class LightningLdkWallet extends LightningCustodianWallet { - static readonly type = 'lightningLdk'; - static readonly typeReadable = 'Lightning LDK'; - // @ts-ignore: override - public readonly type = LightningLdkWallet.type; - // @ts-ignore: override - public readonly typeReadable = LightningLdkWallet.typeReadable; - + static type = 'lightningLdk'; + static typeReadable = 'Lightning LDK'; private _listChannels: any[] = []; private _listPayments: any[] = []; private _listInvoices: any[] = []; @@ -49,8 +41,8 @@ export class LightningLdkWallet extends LightningCustodianWallet { return pubkeyHex; } - constructor() { - super(); + constructor(props: any) { + super(props); this.preferredBalanceUnit = BitcoinUnit.SATS; this.chain = Chain.OFFCHAIN; this.user_invoices_raw = []; // compatibility with other lightning wallet class @@ -65,20 +57,16 @@ export class LightningLdkWallet extends LightningCustodianWallet { return false; } - async stop() { - return RnLdk.stop(); - } + async stop() {} async wipeLndDir() {} - async listPeers() { - return RnLdk.listPeers(); - } + async listPeers() {} async listChannels() { try { // exception might be in case of incompletely-started LDK. then just ignore and return cached version - this._listChannels = await RnLdk.listChannels(); + this._listChannels = []; } catch (_) {} return this._listChannels; @@ -89,7 +77,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async getInfo() { - const identityPubkey = await RnLdk.getNodeId(); + const identityPubkey = ''; return { identityPubkey, }; @@ -104,15 +92,15 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async fundingStateStepFinalize(txhex: string) { - return RnLdk.openChannelStep2(txhex); + return false; } async getMaturingBalance(): Promise { - return RnLdk.getMaturingBalance(); + return 0; } async getMaturingHeight(): Promise { - return RnLdk.getMaturingHeight(); + return 0; } /** @@ -121,16 +109,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { * @return {Promise} */ async isStarted() { - let rez; - try { - rez = await Promise.race([new Promise(resolve => setTimeout(() => resolve('timeout'), 1000)), RnLdk.getNodeId()]); - } catch (_) {} - - if (rez === 'timeout' || !rez) { - return false; - } - - return true; + return false; } /** @@ -149,36 +128,10 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async openChannel(pubkeyHex: string, host: string, amountSats: number, privateChannel: boolean) { - let triedToConnect = false; - let port = 9735; - - if (host.includes(':')) { - const splitted = host.split(':'); - host = splitted[0]; - port = +splitted[1]; - } - - for (let c = 0; c < 20; c++) { - const peers = await this.listPeers(); - if (peers.includes(pubkeyHex)) { - // all good, connected, lets open channel - return await RnLdk.openChannelStep1(pubkeyHex, +amountSats); - } - - if (!triedToConnect) { - triedToConnect = true; - await RnLdk.connectPeer(pubkeyHex, host, +port); - } - - await new Promise(resolve => setTimeout(resolve, 500)); // sleep - } - - throw new Error('timeout waiting for peer connection'); + return false; } - async connectPeer(pubkeyHex: string, host: string, port: number) { - return RnLdk.connectPeer(pubkeyHex, host, +port); - } + async connectPeer(pubkeyHex: string, host: string, port: number) {} async lookupNodeConnectionDetailsByPubkey(pubkey: string) { // first, trying cache: @@ -196,7 +149,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { const ret = { pubkey, host: address.addr.split(':')[0], - port: parseInt(address.addr.split(':')[1], 10), + port: parseInt(address.addr.split(':')[1]), }; this._nodeConnectionDetailsCache[pubkey] = Object.assign({}, ret, { ts: +new Date() }); @@ -207,8 +160,8 @@ export class LightningLdkWallet extends LightningCustodianWallet { } } - getAddress(): string | false { - return false; + getAddress() { + return undefined; } getSecret() { @@ -234,9 +187,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { return ret; } - getStorageNamespace() { - return RnLdk.getStorage().namespace; - } + getStorageNamespace() {} static async _decodeInvoice(invoice: string) { return bolt11.decode(invoice); @@ -246,41 +197,9 @@ export class LightningLdkWallet extends LightningCustodianWallet { return bitcoin.address.fromOutputScript(Buffer.from(scriptHex, 'hex')); } - async selftest() { - await RnLdk.getStorage().selftest(); - await RnLdk.selftest(); - } + async selftest() {} - async init() { - if (!this.getSecret()) return; - console.warn('starting ldk'); - - try { - // providing simple functions that RnLdk would otherwise rely on 3rd party APIs - RnLdk.provideDecodeInvoiceFunc(LightningLdkWallet._decodeInvoice); - RnLdk.provideScript2addressFunc(LightningLdkWallet._script2address); - const syncedStorage = new SyncedAsyncStorage(this.getEntropyHex()); - // await syncedStorage.selftest(); - // await RnLdk.selftest(); - // console.warn('selftest passed'); - await syncedStorage.synchronize(); - - RnLdk.setStorage(syncedStorage); - if (this._refundAddressScriptHex) { - await RnLdk.setRefundAddressScript(this._refundAddressScriptHex); - } else { - // fallback, unwrapping address from bip39 mnemonic we have - const address = this.unwrapFirstExternalAddressFromMnemonics(); - await this.setRefundAddress(address); - } - await RnLdk.start(this.getEntropyHex(), RNFS.DocumentDirectoryPath); - - this._execInBackground(this.reestablishChannels); - if (this.timeToCheckBlockchain()) this._execInBackground(this.checkBlockchain); - } catch (error: any) { - presentAlert({ message: 'LDK init error: ' + error.message }); - } - } + async init() {} unwrapFirstExternalAddressFromMnemonics() { if (this._unwrapFirstExternalAddressFromMnemonicsCache) return this._unwrapFirstExternalAddressFromMnemonicsCache; // cache hit @@ -297,52 +216,9 @@ export class LightningLdkWallet extends LightningCustodianWallet { return hd._getExternalWIFByIndex(0); } - async checkBlockchain() { - this._lastTimeBlockchainCheckedTs = +new Date(); - return RnLdk.checkBlockchain(); - } + async checkBlockchain() {} async payInvoice(invoice: string, freeAmount = 0) { - const decoded = this.decodeInvoice(invoice); - - // if its NOT zero amount invoice, we forcefully reset passed amount argument so underlying LDK code - // would extract amount from bolt11 - if (decoded.num_satoshis && parseInt(decoded.num_satoshis, 10) > 0) freeAmount = 0; - - if (await this.channelsNeedReestablish()) { - await this.reestablishChannels(); - await this.waitForAtLeastOneChannelBecomeActive(); - } - - const result = await RnLdk.payInvoice(invoice, freeAmount); - if (!result) throw new Error('Failed'); - - // ok, it was sent. now, waiting for an event that it was _actually_ paid: - for (let c = 0; c < 60; c++) { - await new Promise(resolve => setTimeout(resolve, 500)); // sleep - - for (const sentPayment of RnLdk.sentPayments || []) { - const paidHash = LightningLdkWallet.preimage2hash(sentPayment.payment_preimage); - if (paidHash === decoded.payment_hash) { - this._listPayments = this._listPayments || []; - this._listPayments.push( - Object.assign({}, sentPayment, { - memo: decoded.description || 'Lightning payment', - value: (freeAmount || decoded.num_satoshis) * -1, - received: +new Date(), - payment_preimage: sentPayment.payment_preimage, - payment_hash: decoded.payment_hash, - }), - ); - return; - } - } - - for (const failedPayment of RnLdk.failedPayments || []) { - if (failedPayment.payment_hash === decoded.payment_hash) throw new Error(JSON.stringify(failedPayment)); - } - } - // no? lets just throw timeout error throw new Error('Payment timeout'); } @@ -353,52 +229,12 @@ export class LightningLdkWallet extends LightningCustodianWallet { * but will never be acknowledged as 'established' by LDK until peer reconnects so that ldk & peer can negotiate and * agree that channel is now established */ - async reconnectPeersWithPendingChannels() { - const peers = await RnLdk.listPeers(); - const peers2reconnect: Record = {}; - if (this._listChannels) { - for (const channel of this._listChannels) { - if (!channel.is_funding_locked) { - // pending channel - if (!peers.includes(channel.remote_node_id)) peers2reconnect[channel.remote_node_id] = true; - } - } - } + async reconnectPeersWithPendingChannels() {} - for (const pubkey of Object.keys(peers2reconnect)) { - const { host, port } = await this.lookupNodeConnectionDetailsByPubkey(pubkey); - await this.connectPeer(pubkey, host, port); - } - } - - async getUserInvoices(limit: number | false = false) { + async getUserInvoices(limit = false) { const newInvoices: any[] = []; - let found = false; - // okay, so the idea is that `this._listInvoices` is a persistent storage of invoices, while - // `RnLdk.receivedPayments` is only a temp storage of emitted events - - // we iterate through all stored invoices - for (const invoice of this._listInvoices) { - const newInvoice = Object.assign({}, invoice); - - // iterate through events of received payments - for (const receivedPayment of RnLdk.receivedPayments || []) { - if (receivedPayment.payment_hash === invoice.payment_hash) { - // match! this particular payment was paid - newInvoice.ispaid = true; - newInvoice.value = Math.floor(parseInt(receivedPayment.amt, 10) / 1000); - found = true; - } - } - - newInvoices.push(newInvoice); - } - - // overwrite stored array if flag was set - if (found) this._listInvoices = newInvoices; - - return this._listInvoices; + return newInvoices; } isInvoiceGeneratedByWallet(paymentRequest: string) { @@ -409,36 +245,9 @@ export class LightningLdkWallet extends LightningCustodianWallet { return false; } - async addInvoice(amtSat: number, memo: string) { - if (await this.channelsNeedReestablish()) { - await this.reestablishChannels(); - await this.waitForAtLeastOneChannelBecomeActive(); - } + async addInvoice(amtSat: number, memo: string) {} - if (this.getReceivableBalance() < amtSat) throw new Error('You dont have enough inbound capacity'); - - const paymentRequest = await RnLdk.addInvoice(amtSat * 1000, memo); - if (!paymentRequest) return false; - - const decoded = this.decodeInvoice(paymentRequest); - - this._listInvoices = this._listInvoices || []; - const tx = { - payment_request: paymentRequest, - ispaid: false, - timestamp: +new Date(), - expire_time: 3600 * 1000, - amt: amtSat, - type: 'user_invoice', - payment_hash: decoded.payment_hash, - description: memo || '', - }; - this._listInvoices.push(tx); - - return paymentRequest; - } - - async getAddressAsync(): Promise { + async getAddressAsync() { throw new Error('getAddressAsync: Not implemented'); } @@ -485,35 +294,14 @@ export class LightningLdkWallet extends LightningCustodianWallet { return ret; } - async fetchTransactions() { - if (this.timeToCheckBlockchain()) { - try { - // exception might be in case of incompletely-started LDK - this._listChannels = await RnLdk.listChannels(); - await this.checkBlockchain(); - // ^^^ will be executed if above didnt throw exceptions, which means ldk fully started. - // we need this for a case when app returns from background if it was in bg for a really long time. - // ldk needs to update it's blockchain data, and this is practically the only place where it can - // do that (except on cold start) - } catch (_) {} - } - - try { - await this.reconnectPeersWithPendingChannels(); - } catch (error: any) { - console.log('fetchTransactions failed'); - console.log(error.message); - } - - await this.getUserInvoices(); // it internally updates paid user invoices - } + async fetchTransactions() {} getBalance() { let sum = 0; if (this._listChannels) { for (const channel of this._listChannels) { if (!channel.is_funding_locked) continue; // pending channel - sum += Math.floor(parseInt(channel.outbound_capacity_msat, 10) / 1000); + sum += Math.floor(parseInt(channel.outbound_capacity_msat) / 1000); } } @@ -525,7 +313,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { if (this._listChannels) { for (const channel of this._listChannels) { if (!channel.is_funding_locked) continue; // pending channel - sum += Math.floor(parseInt(channel.inbound_capacity_msat, 10) / 1000); + sum += Math.floor(parseInt(channel.inbound_capacity_msat) / 1000); } } return sum; @@ -547,7 +335,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { if (json && Array.isArray(json)) { for (const utxo of json) { if (utxo?.status?.confirmed) { - confirmedSat += parseInt(utxo.value, 10); + confirmedSat += parseInt(utxo.value); } } } @@ -569,7 +357,6 @@ export class LightningLdkWallet extends LightningCustodianWallet { await wallet.fetchUtxo(); console.log(wallet.getBalance(), wallet.getUtxo()); console.log('creating transaction...'); - // @ts-ignore wtf wallet.getUtxo() and first arg of createTransaction are not compatible const { tx } = wallet.createTransaction(wallet.getUtxo(), [{ address }], 2, address, 0, false, 0); if (!tx) throw new Error('claimCoins: could not create transaction'); console.log('broadcasting...'); @@ -585,7 +372,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async closeChannel(fundingTxidHex: string, force = false) { - return force ? await RnLdk.closeChannelForce(fundingTxidHex) : await RnLdk.closeChannelCooperatively(fundingTxidHex); + return false; } getLatestTransactionTime(): string | 0 { @@ -599,17 +386,9 @@ export class LightningLdkWallet extends LightningCustodianWallet { return new Date(max).toString(); } - async getLogs() { - return RnLdk.getLogs() - .map(log => log.line) - .join('\n'); - } + async getLogs() {} - async getLogsWithTs() { - return RnLdk.getLogs() - .map(log => log.ts + ' ' + log.line) - .join('\n'); - } + async getLogsWithTs() {} async fetchPendingTransactions() {} @@ -658,24 +437,17 @@ export class LightningLdkWallet extends LightningCustodianWallet { async setRefundAddress(address: string) { const script = bitcoin.address.toOutputScript(address); this._refundAddressScriptHex = script.toString('hex'); - await RnLdk.setRefundAddressScript(this._refundAddressScriptHex); } - static async getVersion() { - return RnLdk.getVersion(); - } + static async getVersion() {} - static getPackageVersion() { - return RnLdk.getPackageVersion(); - } + static getPackageVersion() {} getChannelsClosedEvents() { - return RnLdk.channelsClosed; + return [{ reason: '', text: '' }]; } - async purgeLocalStorage() { - return RnLdk.getStorage().purgeLocalStorage(); - } + async purgeLocalStorage() {} /** * executes async function in background, so calling code can return immediately, while catching all thrown exceptions @@ -690,7 +462,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { try { await func.call(that); } catch (error: any) { - presentAlert({ message: '_execInBackground error:' + error.message }); + presentAlert({ message: '_execInBackground error:' + error.message}); } })(); } diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 402662366..2ba3f0c8e 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -151,13 +151,11 @@ B4A29A3A2B55C990002A67DF /* BlueWalletWatch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = B40D4E30225841EC00428FCC /* BlueWalletWatch.app */; platformFilter = ios; }; B4A29A3C2B55C990002A67DF /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; B4A29A3D2B55C990002A67DF /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - B4AB21072B61D8CA0080440C /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB21062B61D8CA0080440C /* SplashScreen.swift */; }; - B4AB21092B61DC3F0080440C /* SplashScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = B4AB21082B61DC3F0080440C /* SplashScreen.m */; }; B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; }; B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; }; B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; C59F90CE0D04D3E4BB39BC5D /* libPods-BlueWalletUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F02C2F7CA3591E4E0B06EBA /* libPods-BlueWalletUITests.a */; }; - C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -445,8 +443,6 @@ B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITest.swift; sourceTree = ""; }; B4A29A452B55C990002A67DF /* BlueWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; B4A29A462B55C990002A67DF /* BlueWallet-NoLDK.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BlueWallet-NoLDK.plist"; sourceTree = ""; }; - B4AB21062B61D8CA0080440C /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = ""; }; - B4AB21082B61DC3F0080440C /* SplashScreen.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SplashScreen.m; sourceTree = ""; }; B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = ""; }; B4D3235A177F4580BA52F2F9 /* libRNCSlider.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCSlider.a; sourceTree = ""; }; B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = ""; }; @@ -474,7 +470,7 @@ files = ( 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */, 764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */, - C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */, + C978A716948AB7DEC5B6F677 /* (null) in Frameworks */, 773E382FE62E836172AAB98B /* libPods-BlueWallet.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -865,8 +861,6 @@ B4AB21052B61D8890080440C /* SplashScreen */ = { isa = PBXGroup; children = ( - B4AB21062B61D8CA0080440C /* SplashScreen.swift */, - B4AB21082B61DC3F0080440C /* SplashScreen.m */, ); name = SplashScreen; sourceTree = ""; @@ -1116,7 +1110,7 @@ ); mainGroup = 83CBB9F61A601CBA00E9B192; packageReferences = ( - 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */, + 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */, B41B76832B66B2FF002C48D5 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */, ); productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; @@ -1502,9 +1496,7 @@ files = ( B44033E92BCC371A00162242 /* MarketData.swift in Sources */, B44033CA2BCC350A00162242 /* Currency.swift in Sources */, - B4AB21092B61DC3F0080440C /* SplashScreen.m in Sources */, B44033EE2BCC374500162242 /* Numeric+abbreviated.swift in Sources */, - B4AB21072B61D8CA0080440C /* SplashScreen.swift in Sources */, B44033DD2BCC36C300162242 /* LatestTransaction.swift in Sources */, 6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */, B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */, @@ -2627,7 +2619,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */ = { + 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/EFPrefix/EFQRCode.git"; requirement = { @@ -2648,7 +2640,7 @@ /* Begin XCSwiftPackageProductDependency section */ 6DFC806F24EA0B6C007B8700 /* EFQRCode */ = { isa = XCSwiftPackageProductDependency; - package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode.git" */; + package = 6DFC806E24EA0B6C007B8700 /* XCRemoteSwiftPackageReference "EFQRCode" */; productName = EFQRCode; }; B41B76842B66B2FF002C48D5 /* Bugsnag */ = { diff --git a/ios/BlueWallet/AppDelegate.mm b/ios/BlueWallet/AppDelegate.mm index 0785bc770..c7c6eb307 100644 --- a/ios/BlueWallet/AppDelegate.mm +++ b/ios/BlueWallet/AppDelegate.mm @@ -41,8 +41,6 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu [NSUserDefaults.standardUserDefaults setValue:@"" forKey:@"deviceUIDCopy"]; } - [self addSplashScreenView]; - self.moduleName = @"BlueWallet"; // You can add your custom initial props in the dictionary below. // They will be passed down to the ViewController used by React Native. @@ -56,22 +54,6 @@ NSUserDefaults *group = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.blu return [super application:application didFinishLaunchingWithOptions:launchOptions]; } -- (void)addSplashScreenView -{ - // Get the rootView - RCTRootView *rootView = (RCTRootView *)self.window.rootViewController.view; - - // Capture the launch screen view - UIStoryboard *launchScreenStoryboard = [UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil]; - UIViewController *launchScreenVC = [launchScreenStoryboard instantiateInitialViewController]; - UIView *launchScreenView = launchScreenVC.view; - launchScreenView.frame = self.window.bounds; - [self.window addSubview:launchScreenView]; - - // Keep a reference to the launch screen view to remove it later - rootView.loadingView = launchScreenView; -} - - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG diff --git a/ios/SplashScreen.m b/ios/SplashScreen.m deleted file mode 100644 index 091048ffc..000000000 --- a/ios/SplashScreen.m +++ /dev/null @@ -1,14 +0,0 @@ -// -// SplashScreen.m -// BlueWallet -// -// Created by Marcos Rodriguez on 1/24/24. -// Copyright © 2024 BlueWallet. All rights reserved. -// - -#import - -@interface RCT_EXTERN_MODULE(SplashScreen, NSObject) -RCT_EXTERN_METHOD(addObserver) -RCT_EXTERN_METHOD(dismissSplashScreen) -@end diff --git a/ios/SplashScreen.swift b/ios/SplashScreen.swift deleted file mode 100644 index 6f70d3ca7..000000000 --- a/ios/SplashScreen.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// SplashScreen.swift -// BlueWallet -// -// Created by Marcos Rodriguez on 1/24/24. -// Copyright © 2024 BlueWallet. All rights reserved. -// - -import Foundation -import React - -@objc(SplashScreen) -class SplashScreen: NSObject, RCTBridgeModule { - static func moduleName() -> String! { - return "SplashScreen" - } - - static func requiresMainQueueSetup() -> Bool { - return true - } - - @objc - func addObserver() { - NotificationCenter.default.addObserver(self, selector: #selector(dismissSplashScreen), name: NSNotification.Name("HideSplashScreen"), object: nil) - } - - @objc - func dismissSplashScreen() { - DispatchQueue.main.async { - if let rootView = UIApplication.shared.delegate?.window??.rootViewController?.view as? RCTRootView { - rootView.loadingView?.removeFromSuperview() - rootView.loadingView = nil - } - NotificationCenter.default.removeObserver(self, name: NSNotification.Name("HideSplashScreen"), object: nil) - } - } - - -} diff --git a/screen/UnlockWith.tsx b/screen/UnlockWith.tsx index cb544dc80..b4f032562 100644 --- a/screen/UnlockWith.tsx +++ b/screen/UnlockWith.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useContext, useEffect, useReducer, useRef } from 'react'; -import { View, Image, ActivityIndicator, NativeModules, StyleSheet } from 'react-native'; +import { View, Image, ActivityIndicator, StyleSheet } from 'react-native'; import Biometric, { BiometricType } from '../class/biometrics'; import { BlueStorageContext } from '../blue_modules/storage-context'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; @@ -49,8 +49,6 @@ function reducer(state: State, action: Action): State { } } -const { SplashScreen } = NativeModules; - const UnlockWith: React.FC = () => { const [state, dispatch] = useReducer(reducer, initialState); const isUnlockingWallets = useRef(false); @@ -91,8 +89,6 @@ const UnlockWith: React.FC = () => { }, [state.isAuthenticating, startAndDecrypt, successfullyAuthenticated]); useEffect(() => { - SplashScreen?.dismissSplashScreen(); - const startUnlock = async () => { const storageIsEncrypted = await isStorageEncrypted(); const isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled(); From 13ab45a20d0f20e685fbab27060c221338e26496 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Wed, 15 May 2024 18:07:39 -0400 Subject: [PATCH 4/5] Update lightning-ldk-wallet.ts --- class/wallets/lightning-ldk-wallet.ts | 318 ++++++++++++++++++++++---- 1 file changed, 273 insertions(+), 45 deletions(-) diff --git a/class/wallets/lightning-ldk-wallet.ts b/class/wallets/lightning-ldk-wallet.ts index 82d223c2f..aab484a55 100644 --- a/class/wallets/lightning-ldk-wallet.ts +++ b/class/wallets/lightning-ldk-wallet.ts @@ -1,16 +1,24 @@ -import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; -import { LightningCustodianWallet } from './lightning-custodian-wallet'; -import { randomBytes } from '../rng'; import * as bip39 from 'bip39'; -import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; -import bolt11 from 'bolt11'; -import { SegwitBech32Wallet } from './segwit-bech32-wallet'; -import presentAlert from '../../components/Alert'; import * as bitcoin from 'bitcoinjs-lib'; +import bolt11 from 'bolt11'; +import RNFS from 'react-native-fs'; +import RnLdk from 'rn-ldk/src/index'; +import presentAlert from '../../components/Alert'; +import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; +import { randomBytes } from '../rng'; +import SyncedAsyncStorage from '../synced-async-storage'; +import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; +import { LightningCustodianWallet } from './lightning-custodian-wallet'; +import { SegwitBech32Wallet } from './segwit-bech32-wallet'; export class LightningLdkWallet extends LightningCustodianWallet { - static type = 'lightningLdk'; - static typeReadable = 'Lightning LDK'; + static readonly type = 'lightningLdk'; + static readonly typeReadable = 'Lightning LDK'; + // @ts-ignore: override + public readonly type = LightningLdkWallet.type; + // @ts-ignore: override + public readonly typeReadable = LightningLdkWallet.typeReadable; + private _listChannels: any[] = []; private _listPayments: any[] = []; private _listInvoices: any[] = []; @@ -41,8 +49,8 @@ export class LightningLdkWallet extends LightningCustodianWallet { return pubkeyHex; } - constructor(props: any) { - super(props); + constructor() { + super(); this.preferredBalanceUnit = BitcoinUnit.SATS; this.chain = Chain.OFFCHAIN; this.user_invoices_raw = []; // compatibility with other lightning wallet class @@ -57,16 +65,20 @@ export class LightningLdkWallet extends LightningCustodianWallet { return false; } - async stop() {} + async stop() { + return RnLdk.stop(); + } async wipeLndDir() {} - async listPeers() {} + async listPeers() { + return RnLdk.listPeers(); + } async listChannels() { try { // exception might be in case of incompletely-started LDK. then just ignore and return cached version - this._listChannels = []; + this._listChannels = await RnLdk.listChannels(); } catch (_) {} return this._listChannels; @@ -77,7 +89,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async getInfo() { - const identityPubkey = ''; + const identityPubkey = await RnLdk.getNodeId(); return { identityPubkey, }; @@ -92,15 +104,15 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async fundingStateStepFinalize(txhex: string) { - return false; + return RnLdk.openChannelStep2(txhex); } async getMaturingBalance(): Promise { - return 0; + return RnLdk.getMaturingBalance(); } async getMaturingHeight(): Promise { - return 0; + return RnLdk.getMaturingHeight(); } /** @@ -109,7 +121,16 @@ export class LightningLdkWallet extends LightningCustodianWallet { * @return {Promise} */ async isStarted() { - return false; + let rez; + try { + rez = await Promise.race([new Promise(resolve => setTimeout(() => resolve('timeout'), 1000)), RnLdk.getNodeId()]); + } catch (_) {} + + if (rez === 'timeout' || !rez) { + return false; + } + + return true; } /** @@ -128,10 +149,36 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async openChannel(pubkeyHex: string, host: string, amountSats: number, privateChannel: boolean) { - return false; + let triedToConnect = false; + let port = 9735; + + if (host.includes(':')) { + const splitted = host.split(':'); + host = splitted[0]; + port = +splitted[1]; + } + + for (let c = 0; c < 20; c++) { + const peers = await this.listPeers(); + if (peers.includes(pubkeyHex)) { + // all good, connected, lets open channel + return await RnLdk.openChannelStep1(pubkeyHex, +amountSats); + } + + if (!triedToConnect) { + triedToConnect = true; + await RnLdk.connectPeer(pubkeyHex, host, +port); + } + + await new Promise(resolve => setTimeout(resolve, 500)); // sleep + } + + throw new Error('timeout waiting for peer connection'); } - async connectPeer(pubkeyHex: string, host: string, port: number) {} + async connectPeer(pubkeyHex: string, host: string, port: number) { + return RnLdk.connectPeer(pubkeyHex, host, +port); + } async lookupNodeConnectionDetailsByPubkey(pubkey: string) { // first, trying cache: @@ -149,7 +196,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { const ret = { pubkey, host: address.addr.split(':')[0], - port: parseInt(address.addr.split(':')[1]), + port: parseInt(address.addr.split(':')[1], 10), }; this._nodeConnectionDetailsCache[pubkey] = Object.assign({}, ret, { ts: +new Date() }); @@ -160,8 +207,8 @@ export class LightningLdkWallet extends LightningCustodianWallet { } } - getAddress() { - return undefined; + getAddress(): string | false { + return false; } getSecret() { @@ -187,7 +234,9 @@ export class LightningLdkWallet extends LightningCustodianWallet { return ret; } - getStorageNamespace() {} + getStorageNamespace() { + return RnLdk.getStorage().namespace; + } static async _decodeInvoice(invoice: string) { return bolt11.decode(invoice); @@ -197,9 +246,41 @@ export class LightningLdkWallet extends LightningCustodianWallet { return bitcoin.address.fromOutputScript(Buffer.from(scriptHex, 'hex')); } - async selftest() {} + async selftest() { + await RnLdk.getStorage().selftest(); + await RnLdk.selftest(); + } - async init() {} + async init() { + if (!this.getSecret()) return; + console.warn('starting ldk'); + + try { + // providing simple functions that RnLdk would otherwise rely on 3rd party APIs + RnLdk.provideDecodeInvoiceFunc(LightningLdkWallet._decodeInvoice); + RnLdk.provideScript2addressFunc(LightningLdkWallet._script2address); + const syncedStorage = new SyncedAsyncStorage(this.getEntropyHex()); + // await syncedStorage.selftest(); + // await RnLdk.selftest(); + // console.warn('selftest passed'); + await syncedStorage.synchronize(); + + RnLdk.setStorage(syncedStorage); + if (this._refundAddressScriptHex) { + await RnLdk.setRefundAddressScript(this._refundAddressScriptHex); + } else { + // fallback, unwrapping address from bip39 mnemonic we have + const address = this.unwrapFirstExternalAddressFromMnemonics(); + await this.setRefundAddress(address); + } + await RnLdk.start(this.getEntropyHex(), RNFS.DocumentDirectoryPath); + + this._execInBackground(this.reestablishChannels); + if (this.timeToCheckBlockchain()) this._execInBackground(this.checkBlockchain); + } catch (error: any) { + presentAlert({ message: 'LDK init error: ' + error.message }); + } + } unwrapFirstExternalAddressFromMnemonics() { if (this._unwrapFirstExternalAddressFromMnemonicsCache) return this._unwrapFirstExternalAddressFromMnemonicsCache; // cache hit @@ -216,9 +297,52 @@ export class LightningLdkWallet extends LightningCustodianWallet { return hd._getExternalWIFByIndex(0); } - async checkBlockchain() {} + async checkBlockchain() { + this._lastTimeBlockchainCheckedTs = +new Date(); + return RnLdk.checkBlockchain(); + } async payInvoice(invoice: string, freeAmount = 0) { + const decoded = this.decodeInvoice(invoice); + + // if its NOT zero amount invoice, we forcefully reset passed amount argument so underlying LDK code + // would extract amount from bolt11 + if (decoded.num_satoshis && parseInt(decoded.num_satoshis, 10) > 0) freeAmount = 0; + + if (await this.channelsNeedReestablish()) { + await this.reestablishChannels(); + await this.waitForAtLeastOneChannelBecomeActive(); + } + + const result = await RnLdk.payInvoice(invoice, freeAmount); + if (!result) throw new Error('Failed'); + + // ok, it was sent. now, waiting for an event that it was _actually_ paid: + for (let c = 0; c < 60; c++) { + await new Promise(resolve => setTimeout(resolve, 500)); // sleep + + for (const sentPayment of RnLdk.sentPayments || []) { + const paidHash = LightningLdkWallet.preimage2hash(sentPayment.payment_preimage); + if (paidHash === decoded.payment_hash) { + this._listPayments = this._listPayments || []; + this._listPayments.push( + Object.assign({}, sentPayment, { + memo: decoded.description || 'Lightning payment', + value: (freeAmount || decoded.num_satoshis) * -1, + received: +new Date(), + payment_preimage: sentPayment.payment_preimage, + payment_hash: decoded.payment_hash, + }), + ); + return; + } + } + + for (const failedPayment of RnLdk.failedPayments || []) { + if (failedPayment.payment_hash === decoded.payment_hash) throw new Error(JSON.stringify(failedPayment)); + } + } + // no? lets just throw timeout error throw new Error('Payment timeout'); } @@ -229,12 +353,52 @@ export class LightningLdkWallet extends LightningCustodianWallet { * but will never be acknowledged as 'established' by LDK until peer reconnects so that ldk & peer can negotiate and * agree that channel is now established */ - async reconnectPeersWithPendingChannels() {} + async reconnectPeersWithPendingChannels() { + const peers = await RnLdk.listPeers(); + const peers2reconnect: Record = {}; + if (this._listChannels) { + for (const channel of this._listChannels) { + if (!channel.is_funding_locked) { + // pending channel + if (!peers.includes(channel.remote_node_id)) peers2reconnect[channel.remote_node_id] = true; + } + } + } - async getUserInvoices(limit = false) { + for (const pubkey of Object.keys(peers2reconnect)) { + const { host, port } = await this.lookupNodeConnectionDetailsByPubkey(pubkey); + await this.connectPeer(pubkey, host, port); + } + } + + async getUserInvoices(limit: number | false = false) { const newInvoices: any[] = []; + let found = false; - return newInvoices; + // okay, so the idea is that `this._listInvoices` is a persistent storage of invoices, while + // `RnLdk.receivedPayments` is only a temp storage of emitted events + + // we iterate through all stored invoices + for (const invoice of this._listInvoices) { + const newInvoice = Object.assign({}, invoice); + + // iterate through events of received payments + for (const receivedPayment of RnLdk.receivedPayments || []) { + if (receivedPayment.payment_hash === invoice.payment_hash) { + // match! this particular payment was paid + newInvoice.ispaid = true; + newInvoice.value = Math.floor(parseInt(receivedPayment.amt, 10) / 1000); + found = true; + } + } + + newInvoices.push(newInvoice); + } + + // overwrite stored array if flag was set + if (found) this._listInvoices = newInvoices; + + return this._listInvoices; } isInvoiceGeneratedByWallet(paymentRequest: string) { @@ -245,9 +409,36 @@ export class LightningLdkWallet extends LightningCustodianWallet { return false; } - async addInvoice(amtSat: number, memo: string) {} + async addInvoice(amtSat: number, memo: string) { + if (await this.channelsNeedReestablish()) { + await this.reestablishChannels(); + await this.waitForAtLeastOneChannelBecomeActive(); + } - async getAddressAsync() { + if (this.getReceivableBalance() < amtSat) throw new Error('You dont have enough inbound capacity'); + + const paymentRequest = await RnLdk.addInvoice(amtSat * 1000, memo); + if (!paymentRequest) return false; + + const decoded = this.decodeInvoice(paymentRequest); + + this._listInvoices = this._listInvoices || []; + const tx = { + payment_request: paymentRequest, + ispaid: false, + timestamp: +new Date(), + expire_time: 3600 * 1000, + amt: amtSat, + type: 'user_invoice', + payment_hash: decoded.payment_hash, + description: memo || '', + }; + this._listInvoices.push(tx); + + return paymentRequest; + } + + async getAddressAsync(): Promise { throw new Error('getAddressAsync: Not implemented'); } @@ -294,14 +485,35 @@ export class LightningLdkWallet extends LightningCustodianWallet { return ret; } - async fetchTransactions() {} + async fetchTransactions() { + if (this.timeToCheckBlockchain()) { + try { + // exception might be in case of incompletely-started LDK + this._listChannels = await RnLdk.listChannels(); + await this.checkBlockchain(); + // ^^^ will be executed if above didnt throw exceptions, which means ldk fully started. + // we need this for a case when app returns from background if it was in bg for a really long time. + // ldk needs to update it's blockchain data, and this is practically the only place where it can + // do that (except on cold start) + } catch (_) {} + } + + try { + await this.reconnectPeersWithPendingChannels(); + } catch (error: any) { + console.log('fetchTransactions failed'); + console.log(error.message); + } + + await this.getUserInvoices(); // it internally updates paid user invoices + } getBalance() { let sum = 0; if (this._listChannels) { for (const channel of this._listChannels) { if (!channel.is_funding_locked) continue; // pending channel - sum += Math.floor(parseInt(channel.outbound_capacity_msat) / 1000); + sum += Math.floor(parseInt(channel.outbound_capacity_msat, 10) / 1000); } } @@ -313,7 +525,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { if (this._listChannels) { for (const channel of this._listChannels) { if (!channel.is_funding_locked) continue; // pending channel - sum += Math.floor(parseInt(channel.inbound_capacity_msat) / 1000); + sum += Math.floor(parseInt(channel.inbound_capacity_msat, 10) / 1000); } } return sum; @@ -335,7 +547,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { if (json && Array.isArray(json)) { for (const utxo of json) { if (utxo?.status?.confirmed) { - confirmedSat += parseInt(utxo.value); + confirmedSat += parseInt(utxo.value, 10); } } } @@ -357,6 +569,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { await wallet.fetchUtxo(); console.log(wallet.getBalance(), wallet.getUtxo()); console.log('creating transaction...'); + // @ts-ignore wtf wallet.getUtxo() and first arg of createTransaction are not compatible const { tx } = wallet.createTransaction(wallet.getUtxo(), [{ address }], 2, address, 0, false, 0); if (!tx) throw new Error('claimCoins: could not create transaction'); console.log('broadcasting...'); @@ -372,7 +585,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { } async closeChannel(fundingTxidHex: string, force = false) { - return false; + return force ? await RnLdk.closeChannelForce(fundingTxidHex) : await RnLdk.closeChannelCooperatively(fundingTxidHex); } getLatestTransactionTime(): string | 0 { @@ -386,9 +599,17 @@ export class LightningLdkWallet extends LightningCustodianWallet { return new Date(max).toString(); } - async getLogs() {} + async getLogs() { + return RnLdk.getLogs() + .map(log => log.line) + .join('\n'); + } - async getLogsWithTs() {} + async getLogsWithTs() { + return RnLdk.getLogs() + .map(log => log.ts + ' ' + log.line) + .join('\n'); + } async fetchPendingTransactions() {} @@ -437,17 +658,24 @@ export class LightningLdkWallet extends LightningCustodianWallet { async setRefundAddress(address: string) { const script = bitcoin.address.toOutputScript(address); this._refundAddressScriptHex = script.toString('hex'); + await RnLdk.setRefundAddressScript(this._refundAddressScriptHex); } - static async getVersion() {} + static async getVersion() { + return RnLdk.getVersion(); + } - static getPackageVersion() {} + static getPackageVersion() { + return RnLdk.getPackageVersion(); + } getChannelsClosedEvents() { - return [{ reason: '', text: '' }]; + return RnLdk.channelsClosed; } - async purgeLocalStorage() {} + async purgeLocalStorage() { + return RnLdk.getStorage().purgeLocalStorage(); + } /** * executes async function in background, so calling code can return immediately, while catching all thrown exceptions @@ -462,7 +690,7 @@ export class LightningLdkWallet extends LightningCustodianWallet { try { await func.call(that); } catch (error: any) { - presentAlert({ message: '_execInBackground error:' + error.message}); + presentAlert({ message: '_execInBackground error:' + error.message }); } })(); } From ad7bdeed814c52eb9a25111a1fcf8b4c4957d1af Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Wed, 15 May 2024 18:09:40 -0400 Subject: [PATCH 5/5] Update storage-context.tsx --- blue_modules/storage-context.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/blue_modules/storage-context.tsx b/blue_modules/storage-context.tsx index 3bed3789b..835b3512a 100644 --- a/blue_modules/storage-context.tsx +++ b/blue_modules/storage-context.tsx @@ -73,15 +73,14 @@ export const BlueStorageProvider = ({ children }: { children: React.ReactNode }) const [reloadTransactionsMenuActionFunction, setReloadTransactionsMenuActionFunction] = useState<() => void>(() => {}); useEffect(() => { + setWallets(BlueApp.getWallets()); (async () => { const isElectrumDisabledValue = await BlueElectrum.isDisabled(); setIsElectrumDisabled(isElectrumDisabledValue); - if (walletsInitialized && wallets.length > 0 && !isElectrumDisabledValue) { - setWallets(BlueApp.getWallets()); + if (walletsInitialized && !isElectrumDisabledValue) { BlueElectrum.connectMain(); } })(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [walletsInitialized]); const saveToDisk = async (force: boolean = false) => {