DEL: Remove ldk

This commit is contained in:
Marcos Rodriguez Velez 2024-03-06 12:25:54 -04:00
parent 702289ffb2
commit 1ef78c82d7
No known key found for this signature in database
GPG key ID: 6030B2F48CCE86D7
30 changed files with 24 additions and 3010 deletions

View file

@ -18,7 +18,6 @@ import {
HDSegwitElectrumSeedP2WPKHWallet,
HDAezeedWallet,
MultisigHDWallet,
LightningLdkWallet,
SLIP39SegwitP2SHWallet,
SLIP39LegacyP2PKHWallet,
SLIP39SegwitBech32Wallet,
@ -381,9 +380,6 @@ class AppStorage {
unserializedWallet.passphrase = passphrase;
}
break;
case LightningLdkWallet.type:
unserializedWallet = LightningLdkWallet.fromJson(key);
break;
case SLIP39SegwitP2SHWallet.type:
unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key);
@ -452,12 +448,6 @@ class AppStorage {
const ID = wallet.getID();
const tempWallets = [];
if (wallet.type === LightningLdkWallet.type) {
/** @type {LightningLdkWallet} */
const ldkwallet = wallet;
ldkwallet.stop().then(ldkwallet.purgeLocalStorage).catch(alert);
}
for (const value of this.wallets) {
if (value.getID() === ID) {
// the one we should delete

View file

@ -36,7 +36,6 @@ import ImportSpeed from './screen/wallets/importSpeed';
import WalletsList from './screen/wallets/list';
import PleaseBackup from './screen/wallets/pleaseBackup';
import PleaseBackupLNDHub from './screen/wallets/pleaseBackupLNDHub';
import PleaseBackupLdk from './screen/wallets/pleaseBackupLdk';
import ProvideEntropy from './screen/wallets/provideEntropy';
import ReorderWallets from './screen/wallets/reorderWallets';
import SelectWallet from './screen/wallets/selectWallet';
@ -72,8 +71,6 @@ import navigationStyle from './components/navigationStyle';
import { useTheme } from './components/themes';
import loc from './loc';
import LappBrowser from './screen/lnd/browser';
import LdkInfo from './screen/lnd/ldkInfo';
import LdkOpenChannel from './screen/lnd/ldkOpenChannel';
import LNDCreateInvoice from './screen/lnd/lndCreateInvoice';
import LNDViewAdditionalInvoiceInformation from './screen/lnd/lndViewAdditionalInvoiceInformation';
import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage';
@ -84,7 +81,6 @@ import LnurlPaySuccess from './screen/lnd/lnurlPaySuccess';
import ScanLndInvoice from './screen/lnd/scanLndInvoice';
import SettingsPrivacy from './screen/settings/SettingsPrivacy';
import DrawerList from './screen/wallets/drawerList';
import LdkViewLogs from './screen/wallets/ldkViewLogs';
import PaymentCode from './screen/wallets/paymentCode';
import PaymentCodesList from './screen/wallets/paymentCodesList';
@ -97,10 +93,7 @@ const WalletsRoot = () => {
<WalletsStack.Navigator screenOptions={{ headerShadowVisible: false }}>
<WalletsStack.Screen name="WalletsList" component={WalletsList} options={WalletsList.navigationOptions(theme)} />
<WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions(theme)} />
<WalletsStack.Screen name="LdkOpenChannel" component={LdkOpenChannel} options={LdkOpenChannel.navigationOptions(theme)} />
<WalletsStack.Screen name="LdkInfo" component={LdkInfo} options={LdkInfo.navigationOptions(theme)} />
<WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions(theme)} />
<WalletsStack.Screen name="LdkViewLogs" component={LdkViewLogs} options={LdkViewLogs.navigationOptions(theme)} />
<WalletsStack.Screen name="TransactionDetails" component={TransactionDetails} options={TransactionDetails.navigationOptions(theme)} />
<WalletsStack.Screen name="TransactionStatus" component={TransactionStatus} options={TransactionStatus.navigationOptions(theme)} />
<WalletsStack.Screen name="CPFP" component={CPFP} options={CPFP.navigationOptions(theme)} />
@ -207,7 +200,6 @@ const AddWalletRoot = () => {
component={PleaseBackupLNDHub}
options={PleaseBackupLNDHub.navigationOptions(theme)}
/>
<AddWalletStack.Screen name="PleaseBackupLdk" component={PleaseBackupLdk} options={PleaseBackupLdk.navigationOptions(theme)} />
<AddWalletStack.Screen name="ProvideEntropy" component={ProvideEntropy} options={ProvideEntropy.navigationOptions(theme)} />
<AddWalletStack.Screen
name="WalletsAddMultisig"
@ -317,23 +309,6 @@ const ScanLndInvoiceRoot = () => {
);
};
const LDKOpenChannelStack = createNativeStackNavigator();
const LDKOpenChannelRoot = () => {
const theme = useTheme();
return (
<LDKOpenChannelStack.Navigator id="LDKOpenChannelRoot" screenOptions={{ headerShadowVisible: false }} initialRouteName="SelectWallet">
<LDKOpenChannelStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<LDKOpenChannelStack.Screen
name="LDKOpenChannelSetAmount"
component={LdkOpenChannel}
options={LdkOpenChannel.navigationOptions(theme)}
/>
<LDKOpenChannelStack.Screen name="Success" component={Success} options={{ headerShown: false, gestureEnabled: false }} />
</LDKOpenChannelStack.Navigator>
);
};
const AztecoRedeemStack = createNativeStackNavigator();
const AztecoRedeemRoot = () => {
const theme = useTheme();
@ -609,7 +584,6 @@ const Navigation = () => {
<RootStack.Screen name="SelectWallet" component={SelectWallet} />
<RootStack.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={NavigationDefaultOptions} />
<RootStack.Screen name="LappBrowserRoot" component={LappBrowserStackRoot} options={NavigationDefaultOptions} />
<RootStack.Screen name="LDKOpenChannelRoot" component={LDKOpenChannelRoot} options={NavigationDefaultOptions} />
<RootStack.Screen
name="ScanQRCodeRoot"

View file

@ -98,7 +98,6 @@ android {
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation files("../../node_modules/rn-ldk/android/libs/LDK-release.aar")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")

View file

@ -10,7 +10,6 @@ import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-se
import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
import { MultisigHDWallet } from './wallets/multisig-hd-wallet';
import { HDAezeedWallet } from './wallets/hd-aezeed-wallet';
import { LightningLdkWallet } from './wallets/lightning-ldk-wallet';
import { SLIP39LegacyP2PKHWallet, SLIP39SegwitP2SHWallet, SLIP39SegwitBech32Wallet } from './wallets/slip39-wallets';
import { useTheme } from '../components/themes';
@ -68,9 +67,6 @@ export default class WalletGradient {
case HDAezeedWallet.type:
gradient = WalletGradient.aezeedWallet;
break;
case LightningLdkWallet.type:
gradient = WalletGradient.ldkWallet;
break;
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;
@ -131,9 +127,6 @@ export default class WalletGradient {
case HDAezeedWallet.type:
gradient = WalletGradient.aezeedWallet;
break;
case LightningLdkWallet.type:
gradient = WalletGradient.ldkWallet;
break;
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;

View file

@ -11,7 +11,6 @@ import {
HDSegwitP2SHWallet,
LegacyWallet,
LightningCustodianWallet,
LightningLdkWallet,
MultisigHDWallet,
SLIP39LegacyP2PKHWallet,
SLIP39SegwitBech32Wallet,
@ -183,17 +182,6 @@ const startImport = (
yield { wallet: lnd };
}
// is it LDK?
yield { progress: 'lightning' };
if (text.startsWith('ldk://')) {
const ldk = new LightningLdkWallet();
ldk.setSecret(text);
if (ldk.valid()) {
await ldk.init();
yield { wallet: ldk };
}
}
// check bip39 wallets
yield { progress: 'bip39' };
const hd2 = new HDSegwitBech32Wallet();

View file

@ -1,692 +0,0 @@
import RNFS from 'react-native-fs';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import RnLdk from 'rn-ldk/src/index';
import { LightningCustodianWallet } from './lightning-custodian-wallet';
import SyncedAsyncStorage from '../synced-async-storage';
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';
const bitcoin = require('bitcoinjs-lib');
export class LightningLdkWallet extends LightningCustodianWallet {
static type = 'lightningLdk';
static typeReadable = 'Lightning LDK';
private _listChannels: any[] = [];
private _listPayments: any[] = [];
private _listInvoices: any[] = [];
private _nodeConnectionDetailsCache: any = {}; // pubkey -> {pubkey, host, port, ts}
private _refundAddressScriptHex: string = '';
private _lastTimeBlockchainCheckedTs: number = 0;
private _unwrapFirstExternalAddressFromMnemonicsCache: string = '';
private static _predefinedNodes: Record<string, string> = {
Bitrefill: '03d607f3e69fd032524a867b288216bfab263b6eaee4e07783799a6fe69bb84fac@3.237.23.179:9735',
'OpenNode.com': '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e@3.132.230.42:9735',
Fold: '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774@35.238.153.25:9735',
'Moon (paywithmoon.com)': '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5@52.86.210.65:9735',
'coingate.com': '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44:9735',
'Blockstream Store': '02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f@35.232.170.67:9735',
ACINQ: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f@3.33.236.230:9735',
};
static getPredefinedNodes() {
return LightningLdkWallet._predefinedNodes;
}
static pubkeyToAlias(pubkeyHex: string) {
for (const key of Object.keys(LightningLdkWallet._predefinedNodes)) {
const val = LightningLdkWallet._predefinedNodes[key];
if (val.startsWith(pubkeyHex)) return key;
}
return pubkeyHex;
}
constructor(props?: any) {
super(props);
this.preferredBalanceUnit = BitcoinUnit.SATS;
this.chain = Chain.OFFCHAIN;
this.user_invoices_raw = []; // compatibility with other lightning wallet class
}
valid() {
try {
const entropy = bip39.mnemonicToEntropy(this.secret.replace('ldk://', ''));
return entropy.length === 64 || entropy.length === 32;
} catch (_) {}
return false;
}
async stop() {
return RnLdk.stop();
}
async wipeLndDir() {}
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 = await RnLdk.listChannels();
} catch (_) {}
return this._listChannels;
}
async getLndTransactions() {
return [];
}
async getInfo() {
const identityPubkey = await RnLdk.getNodeId();
return {
identityPubkey,
};
}
allowSend() {
return true;
}
timeToCheckBlockchain() {
return +new Date() - this._lastTimeBlockchainCheckedTs > 5 * 60 * 1000; // 5 min, half of block time
}
async fundingStateStepFinalize(txhex: string) {
return RnLdk.openChannelStep2(txhex);
}
async getMaturingBalance(): Promise<number> {
return RnLdk.getMaturingBalance();
}
async getMaturingHeight(): Promise<number> {
return RnLdk.getMaturingHeight();
}
/**
* Probes getNodeId() call. if its available - LDK has started
*
* @return {Promise<boolean>}
*/
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;
}
/**
* Waiter till getNodeId() starts to respond. Returns true if it eventually does,
* false in case of timeout.
*
* @return {Promise<boolean>}
*/
async waitTillStarted() {
for (let c = 0; c < 30; c++) {
if (await this.isStarted()) return true;
await new Promise(resolve => setTimeout(resolve, 500)); // sleep
}
return false;
}
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');
}
async connectPeer(pubkeyHex: string, host: string, port: number) {
return RnLdk.connectPeer(pubkeyHex, host, +port);
}
async lookupNodeConnectionDetailsByPubkey(pubkey: string) {
// first, trying cache:
if (this._nodeConnectionDetailsCache[pubkey] && +new Date() - this._nodeConnectionDetailsCache[pubkey].ts < 4 * 7 * 24 * 3600 * 1000) {
// cache hit
return this._nodeConnectionDetailsCache[pubkey];
}
// doing actual fetch and filling cache:
const response = await fetch(`https://1ml.com/node/${pubkey}/json`);
const json = await response.json();
if (json && json.addresses && Array.isArray(json.addresses)) {
for (const address of json.addresses) {
if (address.network === 'tcp') {
const ret = {
pubkey,
host: address.addr.split(':')[0],
port: parseInt(address.addr.split(':')[1], 10),
};
this._nodeConnectionDetailsCache[pubkey] = Object.assign({}, ret, { ts: +new Date() });
return ret;
}
}
}
}
getAddress() {
return undefined;
}
getSecret() {
return this.secret;
}
timeToRefreshBalance() {
return (+new Date() - this._lastBalanceFetch) / 1000 > 300; // 5 min
}
timeToRefreshTransaction() {
return (+new Date() - this._lastTxFetch) / 1000 > 300; // 5 min
}
async generate() {
const buf = await randomBytes(16);
this.secret = 'ldk://' + bip39.entropyToMnemonic(buf.toString('hex'));
}
getEntropyHex() {
let ret = bip39.mnemonicToEntropy(this.secret.replace('ldk://', ''));
while (ret.length < 64) ret = '0' + ret;
return ret;
}
getStorageNamespace() {
return RnLdk.getStorage().namespace;
}
static async _decodeInvoice(invoice: string) {
return bolt11.decode(invoice);
}
static async _script2address(scriptHex: string) {
return bitcoin.address.fromOutputScript(Buffer.from(scriptHex, 'hex'));
}
async selftest() {
await RnLdk.getStorage().selftest();
await RnLdk.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 });
}
}
unwrapFirstExternalAddressFromMnemonics() {
if (this._unwrapFirstExternalAddressFromMnemonicsCache) return this._unwrapFirstExternalAddressFromMnemonicsCache; // cache hit
const hd = new HDSegwitBech32Wallet();
hd.setSecret(this.getSecret().replace('ldk://', ''));
const address = hd._getExternalAddressByIndex(0);
this._unwrapFirstExternalAddressFromMnemonicsCache = address;
return address;
}
unwrapFirstExternalWIFFromMnemonics() {
const hd = new HDSegwitBech32Wallet();
hd.setSecret(this.getSecret().replace('ldk://', ''));
return hd._getExternalWIFByIndex(0);
}
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');
}
/**
* In case user initiated channel opening, and then lost peer connection (i.e. app went in background for an
* extended period of time), when user gets back to the app the channel might already have enough confirmations,
* 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<string, boolean> = {};
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;
}
}
}
for (const pubkey of Object.keys(peers2reconnect)) {
const { host, port } = await this.lookupNodeConnectionDetailsByPubkey(pubkey);
await this.connectPeer(pubkey, host, port);
}
}
async getUserInvoices(limit = false) {
const newInvoices: any[] = [];
let found = false;
// okay, so the idea is that `this._listInvoices` is a persistant storage of invoices, while
// `RnLdk.receivedPayments` is only a temp storage of emited 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) {
return Boolean(this?._listInvoices?.some(invoice => invoice.payment_request === paymentRequest));
}
weOwnAddress(address: string) {
return false;
}
async addInvoice(amtSat: number, memo: string) {
if (await this.channelsNeedReestablish()) {
await this.reestablishChannels();
await this.waitForAtLeastOneChannelBecomeActive();
}
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() {
throw new Error('getAddressAsync: Not implemented');
}
async allowOnchainAddress(): Promise<boolean> {
throw new Error('allowOnchainAddress: Not implemented');
}
getTransactions() {
const ret = [];
for (const payment of this?._listPayments || []) {
const newTx = Object.assign({}, payment, {
type: 'paid_invoice',
walletID: this.getID(),
});
ret.push(newTx);
}
// ############################################
for (const invoice of this?._listInvoices || []) {
const tx = {
payment_request: invoice.payment_request,
ispaid: invoice.ispaid,
received: invoice.timestamp,
type: invoice.type,
value: invoice.value || invoice.amt,
memo: invoice.description,
timestamp: invoice.timestamp, // important
expire_time: invoice.expire_time, // important
walletID: this.getID(),
};
if (tx.ispaid || invoice.timestamp + invoice.expire_time > +new Date()) {
// expired non-paid invoices are not shown
ret.push(tx);
}
}
ret.sort(function (a, b) {
return b.received - a.received;
});
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
}
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);
}
}
return sum;
}
getReceivableBalance() {
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.inbound_capacity_msat, 10) / 1000);
}
}
return sum;
}
/**
* This method checks if there is balance on first unwapped address we have.
* This address is a fallback in case user has _no_ other wallets to withdraw onchain coins to, so closed-channel
* funds land on this address. Ofcourse, if user provided us a withdraw address, it should be stored in
* `this._refundAddressScriptHex` and its balance frankly is not our concern.
*
* @return {Promise<{confirmedBalance: number}>}
*/
async walletBalance() {
let confirmedSat = 0;
if (this._unwrapFirstExternalAddressFromMnemonicsCache) {
const response = await fetch('https://blockstream.info/api/address/' + this._unwrapFirstExternalAddressFromMnemonicsCache + '/utxo');
const json = await response.json();
if (json && Array.isArray(json)) {
for (const utxo of json) {
if (utxo?.status?.confirmed) {
confirmedSat += parseInt(utxo.value, 10);
}
}
}
}
return { confirmedBalance: confirmedSat };
}
async fetchBalance() {
await this.listChannels(); // updates channels
}
async claimCoins(address: string) {
console.log('unwrapping wif...');
const wif = this.unwrapFirstExternalWIFFromMnemonics();
const wallet = new SegwitBech32Wallet();
wallet.setSecret(String(wif));
console.log('fetching balance...');
await wallet.fetchUtxo();
console.log(wallet.getBalance(), wallet.getUtxo());
console.log('creating transation...');
// @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...');
return await wallet.broadcastTx(tx.toHex());
}
async fetchInfo() {
throw new Error('fetchInfo: Not implemented');
}
allowReceive() {
return true;
}
async closeChannel(fundingTxidHex: string, force = false) {
return force ? await RnLdk.closeChannelForce(fundingTxidHex) : await RnLdk.closeChannelCooperatively(fundingTxidHex);
}
getLatestTransactionTime(): string | 0 {
if (this.getTransactions().length === 0) {
return 0;
}
let max = -1;
for (const tx of this.getTransactions()) {
if (tx.received) max = Math.max(tx.received, max);
}
return new Date(max).toString();
}
async getLogs() {
return RnLdk.getLogs()
.map(log => log.line)
.join('\n');
}
async getLogsWithTs() {
return RnLdk.getLogs()
.map(log => log.ts + ' ' + log.line)
.join('\n');
}
async fetchPendingTransactions() {}
async fetchUserInvoices() {
await this.getUserInvoices();
}
static preimage2hash(preimageHex: string): string {
const hash = bitcoin.crypto.sha256(Buffer.from(preimageHex, 'hex'));
return hash.toString('hex');
}
async reestablishChannels() {
const connectedInThisRun: any = {};
for (const channel of await this.listChannels()) {
if (channel.is_usable) continue; // already connected..?
if (connectedInThisRun[channel.remote_node_id]) continue; // already tried to reconnect (in case there are several channels with the same node)
const { pubkey, host, port } = await this.lookupNodeConnectionDetailsByPubkey(channel.remote_node_id);
await this.connectPeer(pubkey, host, port);
connectedInThisRun[pubkey] = true;
}
}
async channelsNeedReestablish() {
const freshListChannels = await this.listChannels();
const active = freshListChannels.filter(chan => !!chan.is_usable && chan.is_funding_locked).length;
return freshListChannels.length !== +active;
}
async waitForAtLeastOneChannelBecomeActive() {
const active = (await this.listChannels()).filter(chan => !!chan.is_usable).length;
for (let c = 0; c < 10; c++) {
await new Promise(resolve => setTimeout(resolve, 500)); // sleep
const freshListChannels = await this.listChannels();
const active2 = freshListChannels.filter(chan => !!chan.is_usable).length;
if (freshListChannels.length === +active2) return true; // all active kek
if (freshListChannels.length === 0) return true; // no channels at all
if (+active2 > +active) return true; // something became active, lets ret
}
return false;
}
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 getPackageVersion() {
return RnLdk.getPackageVersion();
}
getChannelsClosedEvents() {
return RnLdk.channelsClosed;
}
async purgeLocalStorage() {
return RnLdk.getStorage().purgeLocalStorage();
}
/**
* executes async function in background, so calling code can return immediately, while catching all thrown exceptions
* and showing them in alert() instead of propagating them up
*
* @param func {function} Async functino to execute
* @private
*/
_execInBackground(func: () => void) {
const that = this;
(async () => {
try {
await func.call(that);
} catch (error: any) {
presentAlert({ message: '_execInBackground error:' + error.message });
}
})();
}
}

View file

@ -9,7 +9,6 @@ import { HDSegwitElectrumSeedP2WPKHWallet } from './hd-segwit-electrum-seed-p2wp
import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet';
import { LegacyWallet } from './legacy-wallet';
import { LightningCustodianWallet } from './lightning-custodian-wallet';
import { LightningLdkWallet } from './lightning-ldk-wallet';
import { MultisigHDWallet } from './multisig-hd-wallet';
import { SegwitBech32Wallet } from './segwit-bech32-wallet';
import { SegwitP2SHWallet } from './segwit-p2sh-wallet';
@ -106,7 +105,6 @@ export type TWallet =
| HDSegwitP2SHWallet
| LegacyWallet
| LightningCustodianWallet
| LightningLdkWallet
| MultisigHDWallet
| SLIP39LegacyP2PKHWallet
| SLIP39SegwitBech32Wallet

View file

@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useContext, useCallback, useMemo }
import { Image, Text, TouchableOpacity, View, I18nManager, StyleSheet } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import { AbstractWallet, HDSegwitBech32Wallet, LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class';
import { AbstractWallet, HDSegwitBech32Wallet, LightningCustodianWallet, MultisigHDWallet } from '../class';
import { BitcoinUnit } from '../models/bitcoinUnits';
import WalletGradient from '../class/wallet-gradient';
import Biometric from '../class/biometrics';
@ -150,7 +150,6 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
<Image
source={(() => {
switch (wallet.type) {
case LightningLdkWallet.type:
case LightningCustodianWallet.type:
return I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png');
case MultisigHDWallet.type:
@ -253,13 +252,6 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
</View>
</TouchableOpacity>
)}
{wallet.type === LightningLdkWallet.type && (
<TouchableOpacity accessibilityRole="button" accessibilityLabel={loc.lnd.title} onPress={handleManageFundsPressed}>
<View style={styles.manageFundsButton}>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</View>
</TouchableOpacity>
)}
{wallet.type === MultisigHDWallet.type && (
<TouchableOpacity accessibilityRole="button" onPress={handleManageFundsPressed}>
<View style={styles.manageFundsButton}>

View file

@ -17,7 +17,7 @@ import {
import LinearGradient from 'react-native-linear-gradient';
import loc, { formatBalance, transactionTimeToReadable } from '../loc';
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class';
import { LightningCustodianWallet, MultisigHDWallet } from '../class';
import WalletGradient from '../class/wallet-gradient';
import { BluePrivateBalance } from '../BlueComponents';
import { BlueStorageContext } from '../blue_modules/storage-context';
@ -155,7 +155,6 @@ export const WalletCarouselItem = ({ item, _, onPress, handleLongPress, isSelect
const opacity = isSelectedWallet === false ? 0.5 : 1.0;
let image;
switch (item.type) {
case LightningLdkWallet.type:
case LightningCustodianWallet.type:
image = I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png');
break;

View file

@ -1,393 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSMinimumSystemVersion</key>
<string>11</string>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.bluewallet.bluewallet.fetchTxsForWallet</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>BlueWallet</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>PSBT</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>io.bluewallet.psbt</string>
</array>
</dict>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>TXN</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>io.bluewallet.psbt.txn</string>
</array>
</dict>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>ELECTRUMBACKUP</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>io.bluewallet.backup</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>JSON File</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>public.json</string>
</array>
</dict>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>BW COSIGNER</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>io.bluewallet.bwcosigner</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
<string>lightning</string>
<string>bluewallet</string>
<string>lapp</string>
<string>blue</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.finance</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
<string>http</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>onion</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>tailscale.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>ts.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>In order to quickly scan the recipient&apos;s address, we need your permission to use the camera to scan their QR Code.</string>
<key>NSFaceIDUsageDescription</key>
<string>In order to use FaceID please confirm your permission.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Your authorization is required to save this image.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>In order to import an image for scanning, we need your permission to access your photo library.</string>
<key>NSUserActivityTypes</key>
<array>
<string>io.bluewallet.bluewallet.receiveonchain</string>
<string>io.bluewallet.bluewallet.xpub</string>
</array>
<key>UIAppFonts</key>
<array>
<string>AntDesign.ttf</string>
<string>Entypo.ttf</string>
<string>EvilIcons.ttf</string>
<string>Feather.ttf</string>
<string>FontAwesome.ttf</string>
<string>FontAwesome5_Brands.ttf</string>
<string>FontAwesome5_Regular.ttf</string>
<string>FontAwesome5_Solid.ttf</string>
<string>Foundation.ttf</string>
<string>Ionicons.ttf</string>
<string>MaterialCommunityIcons.ttf</string>
<string>MaterialIcons.ttf</string>
<string>Octicons.ttf</string>
<string>SimpleLineIcons.ttf</string>
<string>Zocial.ttf</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Partially Signed Bitcoin Transaction</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.bluewallet.psbt</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>psbt</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.json</string>
</array>
<key>UTTypeDescription</key>
<string>BW COSIGNER</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.bluewallet.bwcosigner</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>bwcosigner</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Bitcoin Transaction</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.bluewallet.psbt.txn</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>txn</string>
</array>
</dict>
</dict>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.text</string>
</array>
<key>UTTypeDescription</key>
<string>JSON File</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>public.json</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>json</string>
</array>
<key>public.mime-type</key>
<array>
<string>application/json</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Partially Signed Bitcoin Transaction</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.bluewallet.psbt</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>psbt</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Bitcoin Transaction</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.bluewallet.psbt.txn</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>txn</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Electrum Backup</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.bluewallet.backup</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>backup</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.json</string>
</array>
<key>UTTypeDescription</key>
<string>BW COSIGNER</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>io.bluewallet.bwcosigner</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>bwcosigner</string>
</array>
</dict>
</dict>
</array>
<key>bugsnag</key>
<dict>
<key>apiKey</key>
<string>17ba9059f676f1cc4f45d98182388b01</string>
</dict>
</dict>
</plist>

View file

@ -377,7 +377,6 @@
B47B21EB2B2128B8001F6690 /* BlueWalletUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITests.swift; sourceTree = "<group>"; };
B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITest.swift; sourceTree = "<group>"; };
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 = "<absolute>"; };
B4AB21062B61D8CA0080440C /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = "<group>"; };
B4AB21082B61DC3F0080440C /* SplashScreen.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SplashScreen.m; sourceTree = "<group>"; };
B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = "<group>"; };
@ -494,7 +493,6 @@
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
B4A29A462B55C990002A67DF /* BlueWallet-NoLDK.plist */,
13B07FB71A68108700A75B9A /* main.m */,
32B5A3292334450100F8D608 /* Bridge.swift */,
32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */,

View file

@ -1,95 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1520"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "cd ${SRCROOT}&#10;cd ..&#10;cp scripts/maccatalystpatches/lightning-ldk-wallet.ts class/wallets/lightning-ldk-wallet.ts&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B4A29A212B55C990002A67DF"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet-NoLDK"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B4A29A212B55C990002A67DF"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet-NoLDK"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B4A29A212B55C990002A67DF"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet-NoLDK"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B4A29A212B55C990002A67DF"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet-NoLDK"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -19,11 +19,6 @@
<key>orderHint</key>
<integer>71</integer>
</dict>
<key>BlueWallet-NoLDK.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>136</integer>
</dict>
<key>BlueWallet.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
@ -32,12 +27,12 @@
<key>BlueWalletUITests.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>97</integer>
<integer>8</integer>
</dict>
<key>BlueWalletWatch (Complication).xcscheme</key>
<dict>
<key>orderHint</key>
<integer>123</integer>
<integer>9</integer>
</dict>
<key>BlueWalletWatch (Glance).xcscheme_^#shared#^_</key>
<dict>
@ -107,6 +102,11 @@
<key>primary</key>
<true/>
</dict>
<key>B4A29A212B55C990002A67DF</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View file

@ -1,32 +0,0 @@
{
"pins" : [
{
"identity" : "bugsnag-cocoa",
"kind" : "remoteSourceControl",
"location" : "https://github.com/bugsnag/bugsnag-cocoa",
"state" : {
"revision" : "49f60b8dc2e94e7ede1114e2c39ba6ac0b576f42",
"version" : "6.28.0"
}
},
{
"identity" : "efqrcode",
"kind" : "remoteSourceControl",
"location" : "https://github.com/EFPrefix/EFQRCode.git",
"state" : {
"revision" : "2991c2f318ad9529d93b2a73a382a3f9c72c64ce",
"version" : "6.2.2"
}
},
{
"identity" : "swift_qrcodejs",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ApolloZhu/swift_qrcodejs.git",
"state" : {
"revision" : "374dc7f7b9e76c6aeb393f6a84590c6d387e1ecb",
"version" : "2.2.2"
}
}
],
"version" : 2
}

View file

@ -60,8 +60,6 @@ end
target 'BlueWallet' do
configure_target()
# Manually add rn-ldk pod for this target
pod 'rn-ldk', :path => '../node_modules/rn-ldk'
end
target 'BlueWallet-NoLDK' do

View file

@ -471,8 +471,6 @@ PODS:
- React-Core
- RealmJS (12.6.0):
- React
- rn-ldk (0.8.4):
- React-Core
- RNCAsyncStorage (1.22.3):
- React-Core
- RNCClipboard (1.13.2):
@ -585,7 +583,6 @@ DEPENDENCIES:
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- ReactNativeCameraKit (from `../node_modules/react-native-camera-kit`)
- RealmJS (from `../node_modules/realm`)
- rn-ldk (from `../node_modules/rn-ldk`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
@ -734,8 +731,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-camera-kit"
RealmJS:
:path: "../node_modules/realm"
rn-ldk:
:path: "../node_modules/rn-ldk"
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
@ -844,7 +839,6 @@ SPEC CHECKSUMS:
ReactCommon: cadee954951b13f7550766c0074dd38af2da3575
ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb
RealmJS: a62dc7a1f94b888fe9e8712cd650167ad97dc636
rn-ldk: 0d8749d98cc5ce67302a32831818c116b67f7643
RNCAsyncStorage: 10591b9e0a91eaffee14e69b3721009759235125
RNCClipboard: 60fed4b71560d7bfe40e9d35dea9762b024da86d
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
@ -869,6 +863,6 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Yoga: 76b2d5677fc9694bae53c80d0cccfc55719064a3
PODFILE CHECKSUM: e0fda52578c203cfa975067c3570c97082471d84
PODFILE CHECKSUM: 724cdfc1953f7e223f24ab1d579cb8e01c0f1624
COCOAPODS: 1.14.3

28
package-lock.json generated
View file

@ -12,7 +12,7 @@
"dependencies": {
"@babel/preset-env": "^7.20.0",
"@bugsnag/react-native": "7.22.5",
"@bugsnag/source-maps": "2.3.1",
"@bugsnag/source-maps": "2.3.2",
"@keystonehq/bc-ur-registry": "0.6.4",
"@ngraveio/bc-ur": "1.1.6",
"@noble/secp256k1": "1.6.3",
@ -101,7 +101,6 @@
"react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38",
"readable-stream": "3.6.2",
"realm": "12.6.0",
"rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4",
"rn-nodeify": "10.3.0",
"scryptsy": "2.1.0",
"slip39": "https://github.com/BlueWallet/slip39-js",
@ -2284,9 +2283,9 @@
"integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA=="
},
"node_modules/@bugsnag/source-maps": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@bugsnag/source-maps/-/source-maps-2.3.1.tgz",
"integrity": "sha512-9xJTcf5+W7+y1fQBftSOste/3ORi+d5EeCCMdvaHSX69MKQP0lrDiSYpLwX/ErcXrTbvu7nimGGKJP2vBdF7zQ==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@bugsnag/source-maps/-/source-maps-2.3.2.tgz",
"integrity": "sha512-FGkGHWzX+W8T5FebBqGTBEPyfVJn4yr0o605mzt0gHVr1OO9i/JeceYZKSCHt116BmuBnwkckUKMH9ABUZ3kaw==",
"dependencies": {
"command-line-args": "^5.1.1",
"command-line-usage": "^6.1.0",
@ -20598,15 +20597,6 @@
"inherits": "^2.0.1"
}
},
"node_modules/rn-ldk": {
"version": "0.8.4",
"resolved": "git+ssh://git@github.com/BlueWallet/rn-ldk.git#ab0ce31e4ec8c208fe16954ecbcaba5df60f56c6",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/rn-nodeify": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/rn-nodeify/-/rn-nodeify-10.3.0.tgz",
@ -24244,9 +24234,9 @@
"integrity": "sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA=="
},
"@bugsnag/source-maps": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@bugsnag/source-maps/-/source-maps-2.3.1.tgz",
"integrity": "sha512-9xJTcf5+W7+y1fQBftSOste/3ORi+d5EeCCMdvaHSX69MKQP0lrDiSYpLwX/ErcXrTbvu7nimGGKJP2vBdF7zQ==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@bugsnag/source-maps/-/source-maps-2.3.2.tgz",
"integrity": "sha512-FGkGHWzX+W8T5FebBqGTBEPyfVJn4yr0o605mzt0gHVr1OO9i/JeceYZKSCHt116BmuBnwkckUKMH9ABUZ3kaw==",
"requires": {
"command-line-args": "^5.1.1",
"command-line-usage": "^6.1.0",
@ -38033,10 +38023,6 @@
"inherits": "^2.0.1"
}
},
"rn-ldk": {
"version": "git+ssh://git@github.com/BlueWallet/rn-ldk.git#ab0ce31e4ec8c208fe16954ecbcaba5df60f56c6",
"from": "rn-ldk@github:BlueWallet/rn-ldk#v0.8.4"
},
"rn-nodeify": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/rn-nodeify/-/rn-nodeify-10.3.0.tgz",

View file

@ -55,7 +55,7 @@
"android:clean": "cd android; ./gradlew clean ; cd .. ; npm run android",
"ios": "react-native run-ios",
"postinstall": "rn-nodeify --install buffer,events,process,stream,inherits,path,assert,crypto --hack; npm run releasenotes2json; npm run branch2json; npm run patches",
"patches": "patch -p1 < scripts/rn-ldk.patch; patch -p1 < scripts/react-native-camera-kit.patch; patch -p1 < scripts/react-native-widget-center.patch",
"patches": "patch -p1 < scripts/react-native-camera-kit.patch; patch -p1 < scripts/react-native-widget-center.patch",
"test": "npm run tslint && npm run lint && npm run unit && npm run jest",
"jest": "jest -b tests/integration/*",
"windowspatches": "./scripts/windows-patches.sh",
@ -187,7 +187,6 @@
"react-native-widget-center": "https://github.com/BlueWallet/react-native-widget-center#a128c38",
"readable-stream": "3.6.2",
"realm": "12.6.0",
"rn-ldk": "github:BlueWallet/rn-ldk#v0.8.4",
"rn-nodeify": "10.3.0",
"scryptsy": "2.1.0",
"slip39": "https://github.com/BlueWallet/slip39-js",

View file

@ -1,11 +0,0 @@
// react-native.config.js
module.exports = {
dependencies: {
'rn-ldk': {
platforms: {
ios: null, // Disable autolinking for ios
// android: null, // Uncomment if you also want to disable autolinking for Android
},
},
},
};

View file

@ -1,471 +0,0 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { View, StyleSheet, Text, Keyboard, TouchableOpacity, SectionList } from 'react-native';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { BlueSpacing20, BlueSpacing10, BlueLoading, BlueTextCentered } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { Chain } from '../../models/bitcoinUnits';
import loc, { formatBalance } from '../../loc';
import LNNodeBar from '../../components/LNNodeBar';
import BottomModal from '../../components/BottomModal';
import Button from '../../components/Button';
import { Psbt } from 'bitcoinjs-lib';
import { AbstractWallet, LightningLdkWallet } from '../../class';
import presentAlert from '../../components/Alert';
import { useTheme } from '../../components/themes';
import StyledButton, { StyledButtonType } from '../../components/StyledButton';
import SafeArea from '../../components/SafeArea';
const selectWallet = require('../../helpers/select-wallet');
const confirm = require('../../helpers/confirm');
const LdkNodeInfoChannelStatus = { ACTIVE: 'Active', INACTIVE: 'Inactive', PENDING: 'PENDING', STATUS: 'status' };
type LdkInfoRouteProps = RouteProp<
{
params: {
walletID: string;
psbt: Psbt;
};
},
'params'
>;
const LdkInfo = () => {
const { walletID } = useRoute<LdkInfoRouteProps>().params;
const { wallets } = useContext(BlueStorageContext);
const refreshDataInterval = useRef<NodeJS.Timer>();
const sectionList = useRef<SectionList | null>();
const wallet: LightningLdkWallet = wallets.find((w: AbstractWallet) => w.getID() === walletID);
const { colors } = useTheme();
const { setOptions, navigate } = useNavigation();
const name = useRoute().name;
const [isLoading, setIsLoading] = useState(true);
const [channels, setChannels] = useState<any[]>([]);
const [inactiveChannels, setInactiveChannels] = useState<any[]>([]);
const [pendingChannels, setPendingChannels] = useState<any[]>([]);
const [wBalance, setWalletBalance] = useState<{ confirmedBalance?: number }>({});
const [maturingBalance, setMaturingBalance] = useState(0);
const [maturingEta, setMaturingEta] = useState('');
const centerContent = channels.length === 0 && pendingChannels.length === 0 && inactiveChannels.length === 0;
const allChannelsAmount = useRef(0);
// Modals
const [selectedChannelIndex, setSelectedChannelIndex] = useState<any>();
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.background,
},
listHeaderText: {
color: colors.foregroundColor,
backgroundColor: colors.background,
},
listHeaderBack: {
backgroundColor: colors.background,
},
detailsText: {
color: colors.alternativeTextColor,
},
modalContent: {
backgroundColor: colors.elevated,
},
separator: {
backgroundColor: colors.inputBorderColor,
},
});
const refetchData = async (withLoadingIndicator = true) => {
setIsLoading(withLoadingIndicator);
try {
const listChannels = await wallet.listChannels();
if (listChannels && Array.isArray(listChannels)) {
const activeChannels = listChannels.filter(channel => channel.is_usable === true);
setChannels(activeChannels);
} else {
setChannels([]);
}
if (listChannels && Array.isArray(listChannels)) {
const inactive = listChannels.filter(channel => !channel.is_usable && channel.is_funding_locked);
setInactiveChannels(inactive);
} else {
setInactiveChannels([]);
}
if (listChannels && Array.isArray(listChannels)) {
const listPendingChannels = listChannels.filter(channel => !channel.is_funding_locked);
setPendingChannels(listPendingChannels);
} else {
setPendingChannels([]);
}
const walletBalance: { confirmedBalance?: number } = await wallet.walletBalance();
setWalletBalance(walletBalance);
setMaturingBalance(await wallet.getMaturingBalance());
const maturingHeight = await wallet.getMaturingHeight();
if (maturingHeight > 0) {
const result = await fetch('https://blockstream.info/api/blocks/tip/height');
const tip = await result.text();
const hrs = Math.ceil((maturingHeight - +tip) / 6); // convert blocks to hours
setMaturingEta(`${hrs} hours`);
} else {
setMaturingEta('');
}
} catch (e) {
console.log(e);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
const channelsAvailable = channels.length + pendingChannels.length + inactiveChannels.length;
if (allChannelsAmount.current === 0 && channelsAvailable >= 1) {
sectionList?.current?.scrollToLocation({ animated: false, sectionIndex: 0, itemIndex: 0 });
}
allChannelsAmount.current = channelsAvailable;
}, [channels, pendingChannels, inactiveChannels]);
// do we even need periodic sync when user stares at this screen..?
useEffect(() => {
refetchData().then(() => {
refreshDataInterval.current = setInterval(() => {
refetchData(false);
if (wallet.timeToCheckBlockchain()) {
wallet.checkBlockchain();
wallet.reconnectPeersWithPendingChannels();
}
}, 2000);
});
return () => {
clearInterval(refreshDataInterval?.current as NodeJS.Timeout);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setOptions({
headerStyle: {
backgroundColor: colors.customHeader,
},
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors]);
const showModal = (index: any) => {
setSelectedChannelIndex(index);
};
const closeChannel = async (channel: any) => {
if (!(await confirm())) return;
setSelectedChannelIndex(undefined);
const wallets2use = wallets.filter((w: AbstractWallet) => w.chain === Chain.ONCHAIN);
const toWallet: AbstractWallet = await selectWallet(
navigate,
name,
null,
wallets2use,
'Onchain wallet is required to withdraw funds to',
);
// using wallets2use instead of simple Chain.ONCHAIN argument because by default this argument only selects wallets
// that can send, which is not possible if user wants to withdraw to watch-only wallet
if (!toWallet) return;
console.warn('want to close to wallet ', toWallet.getLabel());
const address = await toWallet.getAddressAsync();
if (!address) return presentAlert({ message: 'Error: could not get address for channel withdrawal' });
await wallet.setRefundAddress(address);
let forceClose = false;
if (!channel.is_usable) {
if (!(await confirm(loc.lnd.force_close_channel))) return;
forceClose = true;
}
const rez = await wallet.closeChannel(channel.channel_id, forceClose);
if (rez) {
presentAlert({ message: loc._.success });
return refetchData();
}
};
const claimBalance = async () => {
const wallets2use = wallets.filter((w: AbstractWallet) => w.chain === Chain.ONCHAIN);
const selectedWallet: AbstractWallet = await selectWallet(
navigate,
name,
null,
wallets2use,
'Onchain wallet is required to withdraw funds to',
);
// using wallets2use instead of simple Chain.ONCHAIN argument because by default this argument only selects wallets
// that can send, which is not possible if user wants to withdraw to watch-only wallet
if (!selectedWallet) return;
const address = await selectedWallet.getAddressAsync();
if (address && (await confirm())) {
console.warn('selected ', selectedWallet.getLabel(), address);
setIsLoading(true);
try {
const rez = await wallet.claimCoins(address);
if (rez) {
presentAlert({ message: loc._.success });
await refetchData();
}
} finally {
setIsLoading(false);
}
}
};
const closeModal = () => {
Keyboard.dismiss();
setSelectedChannelIndex(undefined);
};
const handleOnConnectPeerTapped = async (channelData: any) => {
closeModal();
const { pubkey, host, port } = await wallet.lookupNodeConnectionDetailsByPubkey(channelData.remote_node_id);
return wallet.connectPeer(pubkey, host, port);
};
const renderModal = () => {
const status = selectedChannelIndex?.status;
const channelData = selectedChannelIndex?.channel.item;
return (
<BottomModal isVisible={selectedChannelIndex !== undefined} onClose={closeModal} avoidKeyboard>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<Text style={stylesHook.detailsText}>{loc.lnd.node_alias}</Text>
<BlueSpacing10 />
{channelData && (
<Text style={stylesHook.detailsText}>
{LightningLdkWallet.pubkeyToAlias(channelData.remote_node_id) +
' (' +
channelData.remote_node_id.substr(0, 10) +
'...' +
channelData.remote_node_id.substr(-6) +
')'}
</Text>
)}
<BlueSpacing20 />
<LNNodeBar
disabled={
status === LdkNodeInfoChannelStatus.ACTIVE || status === LdkNodeInfoChannelStatus.INACTIVE ? !channelData?.is_usable : true
}
canSend={Number(channelData?.outbound_capacity_msat / 1000)}
canReceive={Number(channelData?.inbound_capacity_msat / 1000)}
itemPriceUnit={wallet.getPreferredBalanceUnit()}
/>
<Text style={stylesHook.detailsText}>
{status === LdkNodeInfoChannelStatus.PENDING
? loc.transactions.pending
: channelData?.is_usable
? loc.lnd.active
: loc.lnd.inactive}
</Text>
{status === LdkNodeInfoChannelStatus.INACTIVE && (
<>
<StyledButton
onPress={() => handleOnConnectPeerTapped(channelData)}
text={loc.lnd.reconnect_peer}
buttonStyle={StyledButtonType.grey}
/>
<BlueSpacing20 />
</>
)}
<StyledButton onPress={() => closeChannel(channelData)} text={loc.lnd.close_channel} buttonStyle={StyledButtonType.destroy} />
<BlueSpacing20 />
</View>
</BottomModal>
);
};
const renderSectionItem = (item: any) => {
switch (item.section.key) {
case LdkNodeInfoChannelStatus.ACTIVE:
return renderItemChannel({ status: LdkNodeInfoChannelStatus.ACTIVE, channel: item });
case LdkNodeInfoChannelStatus.PENDING:
return renderItemChannel({ status: LdkNodeInfoChannelStatus.PENDING, channel: item });
case LdkNodeInfoChannelStatus.INACTIVE:
return renderItemChannel({ status: LdkNodeInfoChannelStatus.INACTIVE, channel: item });
default:
return null;
}
};
const renderItemChannel = (channel: any) => {
const channelData = channel.channel.item;
return (
<TouchableOpacity accessibilityRole="button" onPress={() => showModal(channel)}>
<LNNodeBar
disabled={!channelData.is_usable}
canSend={Number(channelData.outbound_capacity_msat / 1000)}
canReceive={Number(channelData.inbound_capacity_msat / 1000)}
itemPriceUnit={wallet.getPreferredBalanceUnit()}
nodeAlias={LightningLdkWallet.pubkeyToAlias(channelData.remote_node_id)}
/>
</TouchableOpacity>
);
};
const navigateToOpenPrivateChannel = async () => {
navigateToOpenChannel({ isPrivateChannel: true });
};
const navigateToOpenChannel = async ({ isPrivateChannel }: { isPrivateChannel: boolean }) => {
const availableWallets = [...wallets.filter((item: AbstractWallet) => item.isSegwit() && item.allowSend())];
if (availableWallets.length === 0) {
return presentAlert({ message: loc.lnd.refill_create });
}
// @ts-ignore: Address types later
navigate('LDKOpenChannelRoot', {
screen: 'SelectWallet',
params: {
availableWallets,
chainType: Chain.ONCHAIN,
onWalletSelect: (selectedWallet: AbstractWallet) => {
const selectedWalletID = selectedWallet.getID();
selectedWallet.getAddressAsync().then(selectWallet.setRefundAddress);
// @ts-ignore: Address types later
navigate('LDKOpenChannelRoot', {
screen: 'LDKOpenChannelSetAmount',
params: {
isPrivateChannel,
fundingWalletID: selectedWalletID,
ldkWalletID: walletID,
},
});
},
},
});
};
const itemSeparatorComponent = () => {
return <View style={[styles.separator, stylesHook.separator]} />;
};
const renderSectionHeader = (section: any) => {
switch (section.section.key) {
case LdkNodeInfoChannelStatus.PENDING:
return <Text style={[styles.listHeaderText, stylesHook.listHeaderText]}>{loc.transactions.pending}</Text>;
case LdkNodeInfoChannelStatus.ACTIVE:
return <Text style={[styles.listHeaderText, stylesHook.listHeaderText]}>{loc.lnd.active}</Text>;
case LdkNodeInfoChannelStatus.INACTIVE:
return <Text style={[styles.listHeaderText, stylesHook.listHeaderText]}>{loc.lnd.inactive}</Text>;
default:
return null;
}
};
const sections = () => {
const sectionForList = [];
if (channels.length > 0) {
sectionForList.push({ key: LdkNodeInfoChannelStatus.ACTIVE, data: channels });
}
if (inactiveChannels.length > 0) {
sectionForList.push({ key: LdkNodeInfoChannelStatus.INACTIVE, data: inactiveChannels });
}
if (pendingChannels.length > 0) {
sectionForList.push({ key: LdkNodeInfoChannelStatus.PENDING, data: pendingChannels });
}
return sectionForList;
};
return (
<SafeArea style={[styles.root, stylesHook.root]}>
<SectionList
ref={(ref: SectionList) => {
sectionList.current = ref;
}}
renderItem={renderSectionItem}
keyExtractor={channel => channel.channel_id}
initialNumToRender={7}
ItemSeparatorComponent={itemSeparatorComponent}
renderSectionHeader={section => (
<View style={[styles.listHeaderBack, stylesHook.listHeaderBack]}>{renderSectionHeader(section)}</View>
)}
contentContainerStyle={[centerContent ? {} : styles.contentContainerStyle, stylesHook.root]}
contentInset={{ top: 0, left: 0, bottom: 8, right: 0 }}
centerContent={centerContent}
sections={sections()}
ListEmptyComponent={isLoading ? <BlueLoading /> : <BlueTextCentered>{loc.lnd.no_channels}</BlueTextCentered>}
/>
{renderModal()}
<View style={styles.marginHorizontal16}>
{wBalance && wBalance.confirmedBalance ? (
<>
<Button
onPress={claimBalance}
title={loc.formatString(loc.lnd.claim_balance, {
balance: formatBalance(wBalance.confirmedBalance, wallet.getPreferredBalanceUnit()),
})}
/>
<BlueSpacing20 />
</>
) : null}
{maturingBalance ? (
<Text style={stylesHook.detailsText}>
Balance awaiting confirmations: {formatBalance(Number(maturingBalance), wallet.getPreferredBalanceUnit(), true)}
</Text>
) : null}
{maturingEta ? <Text style={stylesHook.detailsText}>ETA: {maturingEta}</Text> : null}
<Button title={loc.lnd.new_channel} onPress={navigateToOpenPrivateChannel} disabled={isLoading} />
<BlueSpacing20 />
</View>
</SafeArea>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
justifyContent: 'space-between',
},
marginHorizontal16: {
marginHorizontal: 16,
},
contentContainerStyle: {
marginHorizontal: 16,
},
listHeaderText: {
marginTop: 8,
marginBottom: 8,
fontWeight: 'bold',
fontSize: 24,
},
listHeaderBack: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
modalContent: {
minHeight: 418,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
borderColor: 'rgba(0, 0, 0, 0.1)',
padding: 24,
},
separator: {
height: 1,
marginTop: 16,
},
});
LdkInfo.navigationOptions = navigationStyle(
{
title: loc.lnd.channels,
},
(options, { theme, navigation, route }) => {
return {
...options,
statusBarStyle: 'auto',
};
},
);
export default LdkInfo;

View file

@ -1,320 +0,0 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { BlueLoading, BlueDismissKeyboardInputAccessory, BlueSpacing20, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import BigNumber from 'bignumber.js';
import AddressInput from '../../components/AddressInput';
import AmountInput from '../../components/AmountInput';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import loc from '../../loc';
import { AbstractWallet, HDSegwitBech32Wallet, LightningLdkWallet } from '../../class';
import { ArrowPicker } from '../../components/ArrowPicker';
import { Psbt } from 'bitcoinjs-lib';
import Biometric from '../../class/biometrics';
import presentAlert from '../../components/Alert';
import { useTheme } from '../../components/themes';
import Button from '../../components/Button';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import SafeArea from '../../components/SafeArea';
import { btcToSatoshi, fiatToBTC } from '../../blue_modules/currency';
type LdkOpenChannelProps = RouteProp<
{
params: {
isPrivateChannel: boolean;
psbt: Psbt;
fundingWalletID: string;
ldkWalletID: string;
remoteHostWithPubkey: string;
};
},
'params'
>;
const LdkOpenChannel = (props: any) => {
const { wallets, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
const [isBiometricUseCapableAndEnabled, setIsBiometricUseCapableAndEnabled] = useState(false);
const { colors }: { colors: any } = useTheme();
const { navigate, setParams } = useNavigation();
const {
fundingWalletID,
isPrivateChannel,
ldkWalletID,
psbt,
remoteHostWithPubkey = '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f@52.50.244.44:9735' /* Bitrefill */,
} = useRoute<LdkOpenChannelProps>().params;
const fundingWallet: HDSegwitBech32Wallet = wallets.find((w: AbstractWallet) => w.getID() === fundingWalletID);
const ldkWallet: LightningLdkWallet = wallets.find((w: AbstractWallet) => w.getID() === ldkWalletID);
const [unit, setUnit] = useState<BitcoinUnit | string>(ldkWallet.getPreferredBalanceUnit());
const [isLoading, setIsLoading] = useState(false);
const psbtOpenChannelStartedTs = useRef<number>();
const name = useRoute().name;
const [fundingAmount, setFundingAmount] = useState<any>({ amount: null, amountSats: null });
const [verified, setVerified] = useState(false);
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
},
});
/**
* Handles when user navigates back from transaction creation flow and has PSBT object
*/
useEffect(() => {
if (!psbt) return;
(async () => {
if (psbtOpenChannelStartedTs.current ? +new Date() - psbtOpenChannelStartedTs.current >= 5 * 60 * 1000 : false) {
// its 10 min actually, but lets check 5 min just for any case
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
return presentAlert({ message: 'Channel opening expired. Please try again' });
}
setVerified(true);
})();
}, [psbt]);
useEffect(() => {
Biometric.isBiometricUseCapableAndEnabled().then(setIsBiometricUseCapableAndEnabled);
}, []);
const finalizeOpenChannel = async () => {
setIsLoading(true);
if (isBiometricUseCapableAndEnabled) {
if (!(await Biometric.unlockWithBiometrics())) {
setIsLoading(false);
return;
}
}
if (psbtOpenChannelStartedTs.current ? +new Date() - psbtOpenChannelStartedTs.current >= 5 * 60 * 1000 : false) {
// its 10 min actually, but lets check 5 min just for any case
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
setIsLoading(false);
return presentAlert({ message: 'Channel opening expired. Please try again' });
}
const tx = psbt.extractTransaction();
const res = await ldkWallet.fundingStateStepFinalize(tx.toHex()); // comment this out to debug
// const res = true; // debug
if (!res) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
setIsLoading(false);
return presentAlert({ message: 'Something wend wrong during opening channel tx broadcast' });
}
fetchAndSaveWalletTransactions(ldkWallet.getID());
await new Promise(resolve => setTimeout(resolve, 3000)); // sleep to make sure network propagates
fetchAndSaveWalletTransactions(fundingWalletID);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
// @ts-ignore: Address types later
navigate('Success', { amount: undefined });
setIsLoading(false);
};
const openChannel = async () => {
setIsLoading(true);
try {
const amountSatsNumber = new BigNumber(fundingAmount.amountSats).toNumber();
if (!amountSatsNumber) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
return presentAlert({ message: 'Amount is not valid' });
}
const pubkey = remoteHostWithPubkey.split('@')[0];
const host = remoteHostWithPubkey.split('@')[1];
if (!pubkey || !host) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
return presentAlert({ message: 'Remote node address is not valid' });
}
const fundingAddressTemp = await ldkWallet.openChannel(pubkey, host, fundingAmount.amountSats, isPrivateChannel);
console.warn('initiated channel opening');
if (!fundingAddressTemp) {
let reason = '';
const channelsClosed = ldkWallet.getChannelsClosedEvents();
const event = channelsClosed.pop();
if (event) {
reason += event.reason + ' ' + event.text;
}
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
return presentAlert({ message: 'Initiating channel open failed: ' + reason });
}
psbtOpenChannelStartedTs.current = +new Date();
// @ts-ignore: Address types later
navigate('SendDetailsRoot', {
screen: 'SendDetails',
params: {
memo: 'open channel',
address: fundingAddressTemp,
walletID: fundingWalletID,
amount: fundingAmount.amount,
amountSats: fundingAmount.amountSats,
unit,
noRbf: true,
launchedBy: name,
isEditable: false,
},
});
} catch (error: any) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: error.message });
} finally {
setIsLoading(false);
}
};
const onBarScanned = (ret: { data?: any }) => {
if (!ret.data) ret = { data: ret };
// @ts-ignore: Address types later
setParams({ remoteHostWithPubkey: ret.data });
};
const render = () => {
if (isLoading || !ldkWallet || !fundingWallet) {
return (
<View style={[styles.root, styles.justifyContentCenter, stylesHook.root]}>
<BlueLoading style={{}} />
</View>
);
}
if (verified) {
return (
<View style={[styles.activeRoot, stylesHook.root]}>
<BlueText>
{loc.formatString(loc.lnd.opening_channnel_for_from, {
forWalletLabel: ldkWallet.getLabel(),
fromWalletLabel: fundingWallet.getLabel(),
})}
</BlueText>
<BlueText>{loc.lnd.are_you_sure_open_channel}</BlueText>
<BlueSpacing20 />
<View style={styles.horizontalButtons}>
<Button onPress={finalizeOpenChannel} title={loc._.continue} />
</View>
</View>
);
}
return (
<View style={[styles.activeRoot, stylesHook.root]}>
<BlueText>
{loc.formatString(loc.lnd.opening_channnel_for_from, {
forWalletLabel: ldkWallet.getLabel(),
fromWalletLabel: fundingWallet.getLabel(),
})}
</BlueText>
<AmountInput
placeholder={loc.lnd.funding_amount_placeholder}
isLoading={isLoading}
amount={fundingAmount.amount}
onAmountUnitChange={(newUnit: string) => {
let amountSats = fundingAmount.amountSats;
switch (newUnit) {
case BitcoinUnit.SATS:
amountSats = parseInt(fundingAmount.amount, 10);
break;
case BitcoinUnit.BTC:
amountSats = btcToSatoshi(fundingAmount.amount);
break;
case BitcoinUnit.LOCAL_CURRENCY:
// also accounting for cached fiat->sat conversion to avoid rounding error
amountSats = btcToSatoshi(fiatToBTC(fundingAmount.amount));
break;
}
setFundingAmount({ amount: fundingAmount.amount, amountSats });
setUnit(newUnit);
}}
onChangeText={(text: string) => {
let amountSats = fundingAmount.amountSats;
switch (unit) {
case BitcoinUnit.BTC:
amountSats = btcToSatoshi(text);
break;
case BitcoinUnit.LOCAL_CURRENCY:
amountSats = btcToSatoshi(fiatToBTC(Number(text)));
break;
case BitcoinUnit.SATS:
amountSats = parseInt(text, 10);
break;
}
setFundingAmount({ amount: text, amountSats });
}}
unit={unit}
inputAccessoryViewID={(BlueDismissKeyboardInputAccessory as any).InputAccessoryViewID}
/>
<AddressInput
placeholder={loc.lnd.remote_host}
address={remoteHostWithPubkey}
isLoading={isLoading}
inputAccessoryViewID={(BlueDismissKeyboardInputAccessory as any).InputAccessoryViewID}
onChangeText={text =>
// @ts-ignore: Address types later
setParams({ remoteHostWithPubkey: text })
}
onBarScanned={onBarScanned}
launchedBy={name}
/>
<BlueDismissKeyboardInputAccessory />
<ArrowPicker
onChange={newKey => {
const nodes = LightningLdkWallet.getPredefinedNodes();
if (nodes[newKey])
// @ts-ignore: Address types later
setParams({ remoteHostWithPubkey: nodes[newKey] });
}}
items={LightningLdkWallet.getPredefinedNodes()}
isItemUnknown={!Object.values(LightningLdkWallet.getPredefinedNodes()).some(node => node === remoteHostWithPubkey)}
/>
<BlueSpacing20 />
<View style={styles.horizontalButtons}>
<Button onPress={openChannel} disabled={remoteHostWithPubkey.length === 0} title={loc.lnd.open_channel} />
</View>
</View>
);
};
return <SafeArea style={[styles.root, stylesHook.root]}>{render()}</SafeArea>;
};
const styles = StyleSheet.create({
root: {
flex: 1,
},
justifyContentCenter: {
justifyContent: 'center',
},
horizontalButtons: {
flexDirection: 'row',
justifyContent: 'center',
},
activeRoot: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
},
});
LdkOpenChannel.navigationOptions = navigationStyle(
{
closeButton: true,
closeButtonFunc: ({ navigation }) => navigation.getParent().pop(),
},
(options, { theme, navigation, route }) => {
return {
...options,
headerTitle: loc.lnd.new_channel,
headerLargeTitle: true,
statusBarStyle: 'auto',
};
},
);
export default LdkOpenChannel;

View file

@ -25,17 +25,9 @@ import {
} from '../../BlueComponents';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import {
AbstractWallet,
HDSegwitBech32Wallet,
HDSegwitP2SHWallet,
LightningCustodianWallet,
LightningLdkWallet,
SegwitP2SHWallet,
} from '../../class';
import { HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, SegwitP2SHWallet } from '../../class';
import presentAlert from '../../components/Alert';
import Button from '../../components/Button';
import { LdkButton } from '../../components/LdkButton';
import ListItem from '../../components/ListItem';
import { useTheme } from '../../components/themes';
import useAsyncPromise from '../../hooks/useAsyncPromise';
@ -272,35 +264,9 @@ const WalletsAdd: React.FC = () => {
setIsLoading(false);
// @ts-ignore: Return later to update
navigate('WalletsAddMultisig', { walletLabel: label.trim().length > 0 ? label : loc.multisig.default_label });
} else if (selectedWalletType === ButtonSelected.LDK) {
setIsLoading(false);
createLightningLdkWallet();
}
};
const createLightningLdkWallet = async () => {
const foundLdk = wallets.find((w: AbstractWallet) => w.type === LightningLdkWallet.type);
if (foundLdk) {
return presentAlert({ message: 'LDK wallet already exists' });
}
setIsLoading(true);
const wallet = new LightningLdkWallet();
wallet.setLabel(label || loc.wallets.details_title);
await wallet.generate();
await wallet.init();
setIsLoading(false);
addWallet(wallet);
await saveToDisk();
A(A.ENUM.CREATED_WALLET);
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
// @ts-ignore: Return later to update
navigate('PleaseBackupLdk', {
walletID: wallet.getID(),
});
};
const createLightningWallet = async () => {
const wallet = new LightningCustodianWallet();
wallet.setLabel(label || loc.wallets.details_title);
@ -405,15 +371,6 @@ const WalletsAdd: React.FC = () => {
onPress={handleOnLightningButtonPressed}
style={styles.button}
/>
{backdoorPressed > 10 ? (
<LdkButton
active={selectedWalletType === ButtonSelected.LDK}
onPress={handleOnLdkButtonPressed}
style={styles.button}
subtext={LightningLdkWallet.getPackageVersion()}
text="LDK"
/>
) : null}
<VaultButton
testID="ActivateVaultButton"
active={selectedWalletType === ButtonSelected.VAULT}

View file

@ -29,7 +29,6 @@ import {
WatchOnlyWallet,
MultisigHDWallet,
HDAezeedWallet,
LightningLdkWallet,
} from '../../class';
import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { useRoute, useNavigation } from '@react-navigation/native';
@ -176,11 +175,7 @@ const WalletDetails = () => {
color: colors.buttonTextColor,
},
});
useEffect(() => {
if (wallet.type === LightningLdkWallet.type) {
wallet.getInfo().then(setLightningWalletInfo);
}
}, [wallet]);
const save = () => {
setIsLoading(true);
@ -545,18 +540,6 @@ const WalletDetails = () => {
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_type.toLowerCase()}</Text>
<Text style={[styles.textValue, stylesHook.textValue]}>{wallet.typeReadable}</Text>
{wallet.type === LightningLdkWallet.type && (
<>
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.identity_pubkey}</Text>
{lightningWalletInfo?.identityPubkey ? (
<>
<BlueText>{lightningWalletInfo.identityPubkey}</BlueText>
</>
) : (
<ActivityIndicator />
)}
</>
)}
{wallet.type === MultisigHDWallet.type && (
<>
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_multisig_type}</Text>
@ -694,12 +677,7 @@ const WalletDetails = () => {
<SecondButton onPress={navigateToSignVerify} testID="SignVerify" title={loc.addresses.sign_title} />
</>
)}
{wallet.type === LightningLdkWallet.type && (
<>
<BlueSpacing20 />
<SecondButton onPress={navigateToLdkViewLogs} testID="LdkLogs" title={loc.lnd.view_logs} />
</>
)}
<BlueSpacing20 />
<BlueSpacing20 />
<TouchableOpacity accessibilityRole="button" onPress={handleDeleteButtonTapped} testID="DeleteButton">

View file

@ -1,160 +0,0 @@
import React, { useEffect, useState, useContext, useRef } from 'react';
import { StyleSheet, View, ScrollView, TouchableOpacity } from 'react-native';
import { useNavigation, useRoute } from '@react-navigation/native';
import { Icon } from 'react-native-elements';
import { BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import loc from '../../loc';
import { LightningLdkWallet } from '../../class';
import presentAlert from '../../components/Alert';
import { useTheme } from '../../components/themes';
import SafeArea from '../../components/SafeArea';
const fs = require('../../blue_modules/fs');
const LdkViewLogs = () => {
const { colors } = useTheme();
const { wallets } = useContext(BlueStorageContext);
const { walletID } = useRoute().params;
/** @type {LightningLdkWallet} */
const wallet = wallets.find(w => w.getID() === walletID);
const [isLoading, setIsLoading] = useState(false);
const [logs, setLogs] = useState('');
const [info, setInfo] = useState('');
const [getInfo, setGetInfo] = useState({});
const { setOptions } = useNavigation();
const refreshDataInterval = useRef();
const stylesHooks = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
},
});
useEffect(() => {
setIsLoading(true);
refetchData()
.then(() => {
refreshDataInterval.current = setInterval(() => {
refetchData();
}, 5000);
})
.finally(() => {
setOptions({
// eslint-disable-next-line react/no-unstable-nested-components
headerRight: () => (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc.wallets.list_tryagain}
style={styles.reloadLogs}
onPress={getLogs}
>
<Icon name="redo" type="font-awesome-5" size={22} color={colors.foregroundColor} />
</TouchableOpacity>
),
});
});
return () => {
clearInterval(refreshDataInterval.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const getLogs = () => {
wallet.getLogs().then(setLogs);
};
const syncBlockchain = () => {
wallet.checkBlockchain();
};
const exportLogs = async () => {
return fs.writeFileAndExport('rn-ldk.log', info + '\n' + (await wallet.getLogsWithTs()));
};
const selfTest = async () => {
try {
await wallet.selftest();
presentAlert({ message: 'ok' });
} catch (error) {
presentAlert({ message: error.message });
}
};
const refetchData = async () => {
getLogs();
await wallet
.getInfo()
.then(async newInfo => {
setGetInfo(newInfo);
const peers = await wallet.listPeers();
const listChannels = await wallet.listChannels();
const version = await LightningLdkWallet.getVersion();
let nfo = 'num peers: ' + peers.length;
nfo += '\nnum channels: ' + listChannels.length;
nfo += '\nldk binary version: ' + version;
nfo += '\nstorage namespace: ' + wallet.getStorageNamespace();
setInfo(nfo);
})
.finally(() => {
setIsLoading(false);
});
};
if (isLoading) {
return (
<View style={[styles.root, stylesHooks.root]}>
<BlueLoading />
</View>
);
}
return (
<SafeArea>
<ScrollView style={styles.root}>
<TouchableOpacity accessibilityRole="button" onPress={selfTest} style={styles.button}>
<BlueText>self test</BlueText>
</TouchableOpacity>
<TouchableOpacity accessibilityRole="button" onPress={exportLogs} style={styles.button}>
<BlueText>export logs to a file</BlueText>
</TouchableOpacity>
<TouchableOpacity accessibilityRole="button" onPress={syncBlockchain} style={styles.button}>
<BlueText>sync blockchain</BlueText>
</TouchableOpacity>
<BlueText>Identity pubkey: {getInfo.identityPubkey}</BlueText>
<BlueText>{info}</BlueText>
<BlueSpacing20 />
<BlueText>{logs}</BlueText>
</ScrollView>
</SafeArea>
);
};
LdkViewLogs.navigationOptions = navigationStyle({}, opts => ({
...opts,
title: loc.lnd.view_logs,
}));
export default LdkViewLogs;
const styles = StyleSheet.create({
root: {
flex: 1,
},
button: {
alignItems: 'center',
padding: 10,
borderRadius: 15,
margin: 5,
borderWidth: 1,
},
reloadLogs: {
marginHorizontal: 16,
minWidth: 150,
justifyContent: 'center',
alignItems: 'flex-end',
},
});

View file

@ -1,89 +0,0 @@
import React, { useCallback, useContext, useEffect } from 'react';
import { useNavigation, useRoute } from '@react-navigation/native';
import { View, useWindowDimensions, StyleSheet, BackHandler, ScrollView } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { BlueCopyTextToClipboard, BlueSpacing20, BlueTextCentered } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useTheme } from '../../components/themes';
import Button from '../../components/Button';
import SafeArea from '../../components/SafeArea';
import usePrivacy from '../../hooks/usePrivacy';
const PleaseBackupLdk = () => {
const { wallets } = useContext(BlueStorageContext);
const { walletID } = useRoute().params;
/** @type {LightningLdkWallet} */
const wallet = wallets.find(w => w.getID() === walletID);
const navigation = useNavigation();
const { colors } = useTheme();
const { height, width } = useWindowDimensions();
const { enableBlur, disableBlur } = usePrivacy();
const handleBackButton = useCallback(() => {
navigation.getParent().pop();
return true;
}, [navigation]);
const styles = StyleSheet.create({
root: {
flex: 1,
backgroundColor: colors.elevated,
},
scrollViewContent: {
flexGrow: 1,
backgroundColor: colors.elevated,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
qrCodeContainer: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' },
});
useEffect(() => {
enableBlur();
BackHandler.addEventListener('hardwareBackPress', handleBackButton);
return () => {
disableBlur();
BackHandler.removeEventListener('hardwareBackPress', handleBackButton);
};
}, [disableBlur, enableBlur, handleBackButton]);
const pop = () => navigation.getParent().pop();
return (
<SafeArea style={styles.root}>
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent}>
<View>
<BlueTextCentered>Please save this wallet backup. It allows you to restore all your channels on other device.</BlueTextCentered>
<BlueSpacing20 />
</View>
<BlueSpacing20 />
<View style={styles.qrCodeContainer}>
<QRCode
value={wallet.secret}
logo={require('../../img/qr-code.png')}
logoSize={90}
size={height > width ? width - 40 : width / 2}
color="#000000"
logoBackgroundColor={colors.brandingColor}
backgroundColor="#FFFFFF"
ecl="H"
/>
</View>
<BlueCopyTextToClipboard text={wallet.getSecret()} />
<BlueSpacing20 />
<Button onPress={pop} title={loc.pleasebackup.ok_lnd} />
</ScrollView>
</SafeArea>
);
};
PleaseBackupLdk.navigationOptions = navigationStyle({
title: loc.pleasebackup.title,
gestureEnabled: false,
swipeEnabled: false,
headerBackVisible: false,
});
export default PleaseBackupLdk;

View file

@ -7,7 +7,7 @@ import { BlueText, BlueSpacing20, BluePrivateBalance } from '../../BlueComponent
import navigationStyle from '../../components/navigationStyle';
import WalletGradient from '../../class/wallet-gradient';
import loc, { formatBalance, transactionTimeToReadable } from '../../loc';
import { LightningLdkWallet, MultisigHDWallet, LightningCustodianWallet } from '../../class';
import { MultisigHDWallet, LightningCustodianWallet } from '../../class';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useTheme } from '../../components/themes';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
@ -147,7 +147,6 @@ const SelectWallet = () => {
<Image
source={(() => {
switch (item.type) {
case LightningLdkWallet.type:
case LightningCustodianWallet.type:
return I18nManager.isRTL ? require('../../img/lnd-shape-rtl.png') : require('../../img/lnd-shape.png');
case MultisigHDWallet.type:

View file

@ -21,7 +21,7 @@ import { Chain } from '../../models/bitcoinUnits';
import { BlueAlertWalletExportReminder } from '../../BlueComponents';
import WalletGradient from '../../class/wallet-gradient';
import navigationStyle from '../../components/navigationStyle';
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
import { LightningCustodianWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
import ActionSheet from '../ActionSheet';
import loc from '../../loc';
import { FContainer, FButton } from '../../components/FloatButtons';
@ -183,12 +183,6 @@ const WalletTransactions = ({ navigation }) => {
return false;
};
const refreshLnNodeInfo = () => {
if (wallet.type === LightningLdkWallet.type) {
setLnNodeInfo({ canReceive: wallet.getReceivableBalance(), canSend: wallet.getBalance() });
}
};
/**
* Forcefully fetches TXs and balance for wallet
*/
@ -199,7 +193,6 @@ const WalletTransactions = ({ navigation }) => {
let noErr = true;
let smthChanged = false;
try {
refreshLnNodeInfo();
// await BlueElectrum.ping();
await BlueElectrum.waitTillConnected();
if (wallet.allowBIP47() && wallet.isBIP47Enabled()) {
@ -275,11 +268,7 @@ const WalletTransactions = ({ navigation }) => {
return (
<View style={styles.flex}>
<View style={styles.listHeader}>{wallet.chain === Chain.OFFCHAIN && renderLappBrowserButton()}</View>
{wallet.type === LightningLdkWallet.type && (lnNodeInfo.canSend > 0 || lnNodeInfo.canReceive > 0) && (
<View style={[styles.marginHorizontal18, styles.marginBottom18]}>
<LNNodeBar canSend={lnNodeInfo.canSend} canReceive={lnNodeInfo.canReceive} itemPriceUnit={itemPriceUnit} />
</View>
)}
<View style={styles.listHeaderTextRow}>
<Text style={[styles.listHeaderText, stylesHook.listHeaderText]}>{loc.transactions.list_title}</Text>
</View>
@ -538,8 +527,6 @@ const WalletTransactions = ({ navigation }) => {
onManageFundsPressed={id => {
if (wallet.type === MultisigHDWallet.type) {
navigateToViewEditCosigners();
} else if (wallet.type === LightningLdkWallet.type) {
navigate('LdkInfo', { walletID: wallet.getID() });
} else if (wallet.type === LightningCustodianWallet.type) {
if (wallet.getUserHasSavedExport()) {
onManageFundsPressed({ id });

View file

@ -1,469 +0,0 @@
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';
const bitcoin = require('bitcoinjs-lib');
export class LightningLdkWallet extends LightningCustodianWallet {
static type = 'lightningLdk';
static typeReadable = 'Lightning LDK';
private _listChannels: any[] = [];
private _listPayments: any[] = [];
private _listInvoices: any[] = [];
private _nodeConnectionDetailsCache: any = {}; // pubkey -> {pubkey, host, port, ts}
private _refundAddressScriptHex: string = '';
private _lastTimeBlockchainCheckedTs: number = 0;
private _unwrapFirstExternalAddressFromMnemonicsCache: string = '';
private static _predefinedNodes: Record<string, string> = {
Bitrefill: '03d607f3e69fd032524a867b288216bfab263b6eaee4e07783799a6fe69bb84fac@3.237.23.179:9735',
'OpenNode.com': '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e@3.132.230.42:9735',
Fold: '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774@35.238.153.25:9735',
'Moon (paywithmoon.com)': '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5@52.86.210.65:9735',
'coingate.com': '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44:9735',
'Blockstream Store': '02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f@35.232.170.67:9735',
ACINQ: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f@3.33.236.230:9735',
};
static getPredefinedNodes() {
return LightningLdkWallet._predefinedNodes;
}
static pubkeyToAlias(pubkeyHex: string) {
for (const key of Object.keys(LightningLdkWallet._predefinedNodes)) {
const val = LightningLdkWallet._predefinedNodes[key];
if (val.startsWith(pubkeyHex)) return key;
}
return pubkeyHex;
}
constructor(props: any) {
super(props);
this.preferredBalanceUnit = BitcoinUnit.SATS;
this.chain = Chain.OFFCHAIN;
this.user_invoices_raw = []; // compatibility with other lightning wallet class
}
valid() {
try {
const entropy = bip39.mnemonicToEntropy(this.secret.replace('ldk://', ''));
return entropy.length === 64 || entropy.length === 32;
} catch (_) {}
return false;
}
async stop() {}
async wipeLndDir() {}
async listPeers() {}
async listChannels() {
try {
// exception might be in case of incompletely-started LDK. then just ignore and return cached version
this._listChannels = [];
} catch (_) {}
return this._listChannels;
}
async getLndTransactions() {
return [];
}
async getInfo() {
const identityPubkey = '';
return {
identityPubkey,
};
}
allowSend() {
return true;
}
timeToCheckBlockchain() {
return +new Date() - this._lastTimeBlockchainCheckedTs > 5 * 60 * 1000; // 5 min, half of block time
}
async fundingStateStepFinalize(txhex: string) {
return false;
}
async getMaturingBalance(): Promise<number> {
return 0;
}
async getMaturingHeight(): Promise<number> {
return 0;
}
/**
* Probes getNodeId() call. if its available - LDK has started
*
* @return {Promise<boolean>}
*/
async isStarted() {
return false;
}
/**
* Waiter till getNodeId() starts to respond. Returns true if it eventually does,
* false in case of timeout.
*
* @return {Promise<boolean>}
*/
async waitTillStarted() {
for (let c = 0; c < 30; c++) {
if (await this.isStarted()) return true;
await new Promise(resolve => setTimeout(resolve, 500)); // sleep
}
return false;
}
async openChannel(pubkeyHex: string, host: string, amountSats: number, privateChannel: boolean) {
return false;
}
async connectPeer(pubkeyHex: string, host: string, port: number) {}
async lookupNodeConnectionDetailsByPubkey(pubkey: string) {
// first, trying cache:
if (this._nodeConnectionDetailsCache[pubkey] && +new Date() - this._nodeConnectionDetailsCache[pubkey].ts < 4 * 7 * 24 * 3600 * 1000) {
// cache hit
return this._nodeConnectionDetailsCache[pubkey];
}
// doing actual fetch and filling cache:
const response = await fetch(`https://1ml.com/node/${pubkey}/json`);
const json = await response.json();
if (json && json.addresses && Array.isArray(json.addresses)) {
for (const address of json.addresses) {
if (address.network === 'tcp') {
const ret = {
pubkey,
host: address.addr.split(':')[0],
port: parseInt(address.addr.split(':')[1]),
};
this._nodeConnectionDetailsCache[pubkey] = Object.assign({}, ret, { ts: +new Date() });
return ret;
}
}
}
}
getAddress() {
return undefined;
}
getSecret() {
return this.secret;
}
timeToRefreshBalance() {
return (+new Date() - this._lastBalanceFetch) / 1000 > 300; // 5 min
}
timeToRefreshTransaction() {
return (+new Date() - this._lastTxFetch) / 1000 > 300; // 5 min
}
async generate() {
const buf = await randomBytes(16);
this.secret = 'ldk://' + bip39.entropyToMnemonic(buf.toString('hex'));
}
getEntropyHex() {
let ret = bip39.mnemonicToEntropy(this.secret.replace('ldk://', ''));
while (ret.length < 64) ret = '0' + ret;
return ret;
}
getStorageNamespace() {}
static async _decodeInvoice(invoice: string) {
return bolt11.decode(invoice);
}
static async _script2address(scriptHex: string) {
return bitcoin.address.fromOutputScript(Buffer.from(scriptHex, 'hex'));
}
async selftest() {}
async init() {}
unwrapFirstExternalAddressFromMnemonics() {
if (this._unwrapFirstExternalAddressFromMnemonicsCache) return this._unwrapFirstExternalAddressFromMnemonicsCache; // cache hit
const hd = new HDSegwitBech32Wallet();
hd.setSecret(this.getSecret().replace('ldk://', ''));
const address = hd._getExternalAddressByIndex(0);
this._unwrapFirstExternalAddressFromMnemonicsCache = address;
return address;
}
unwrapFirstExternalWIFFromMnemonics() {
const hd = new HDSegwitBech32Wallet();
hd.setSecret(this.getSecret().replace('ldk://', ''));
return hd._getExternalWIFByIndex(0);
}
async checkBlockchain() {}
async payInvoice(invoice: string, freeAmount = 0) {
// no? lets just throw timeout error
throw new Error('Payment timeout');
}
/**
* In case user initiated channel opening, and then lost peer connection (i.e. app went in background for an
* extended period of time), when user gets back to the app the channel might already have enough confirmations,
* 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 getUserInvoices(limit = false) {
const newInvoices: any[] = [];
return newInvoices;
}
isInvoiceGeneratedByWallet(paymentRequest: string) {
return Boolean(this?._listInvoices?.some(invoice => invoice.payment_request === paymentRequest));
}
weOwnAddress(address: string) {
return false;
}
async addInvoice(amtSat: number, memo: string) {}
async getAddressAsync() {
throw new Error('getAddressAsync: Not implemented');
}
async allowOnchainAddress(): Promise<boolean> {
throw new Error('allowOnchainAddress: Not implemented');
}
getTransactions() {
const ret = [];
for (const payment of this?._listPayments || []) {
const newTx = Object.assign({}, payment, {
type: 'paid_invoice',
walletID: this.getID(),
});
ret.push(newTx);
}
// ############################################
for (const invoice of this?._listInvoices || []) {
const tx = {
payment_request: invoice.payment_request,
ispaid: invoice.ispaid,
received: invoice.timestamp,
type: invoice.type,
value: invoice.value || invoice.amt,
memo: invoice.description,
timestamp: invoice.timestamp, // important
expire_time: invoice.expire_time, // important
walletID: this.getID(),
};
if (tx.ispaid || invoice.timestamp + invoice.expire_time > +new Date()) {
// expired non-paid invoices are not shown
ret.push(tx);
}
}
ret.sort(function (a, b) {
return b.received - a.received;
});
return ret;
}
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) / 1000);
}
}
return sum;
}
getReceivableBalance() {
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.inbound_capacity_msat) / 1000);
}
}
return sum;
}
/**
* This method checks if there is balance on first unwapped address we have.
* This address is a fallback in case user has _no_ other wallets to withdraw onchain coins to, so closed-channel
* funds land on this address. Ofcourse, if user provided us a withdraw address, it should be stored in
* `this._refundAddressScriptHex` and its balance frankly is not our concern.
*
* @return {Promise<{confirmedBalance: number}>}
*/
async walletBalance() {
let confirmedSat = 0;
if (this._unwrapFirstExternalAddressFromMnemonicsCache) {
const response = await fetch('https://blockstream.info/api/address/' + this._unwrapFirstExternalAddressFromMnemonicsCache + '/utxo');
const json = await response.json();
if (json && Array.isArray(json)) {
for (const utxo of json) {
if (utxo?.status?.confirmed) {
confirmedSat += parseInt(utxo.value);
}
}
}
}
return { confirmedBalance: confirmedSat };
}
async fetchBalance() {
await this.listChannels(); // updates channels
}
async claimCoins(address: string) {
console.log('unwrapping wif...');
const wif = this.unwrapFirstExternalWIFFromMnemonics();
const wallet = new SegwitBech32Wallet();
wallet.setSecret(String(wif));
console.log('fetching balance...');
await wallet.fetchUtxo();
console.log(wallet.getBalance(), wallet.getUtxo());
console.log('creating transation...');
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...');
return await wallet.broadcastTx(tx.toHex());
}
async fetchInfo() {
throw new Error('fetchInfo: Not implemented');
}
allowReceive() {
return true;
}
async closeChannel(fundingTxidHex: string, force = false) {
return false;
}
getLatestTransactionTime(): string | 0 {
if (this.getTransactions().length === 0) {
return 0;
}
let max = -1;
for (const tx of this.getTransactions()) {
if (tx.received) max = Math.max(tx.received, max);
}
return new Date(max).toString();
}
async getLogs() {}
async getLogsWithTs() {}
async fetchPendingTransactions() {}
async fetchUserInvoices() {
await this.getUserInvoices();
}
static preimage2hash(preimageHex: string): string {
const hash = bitcoin.crypto.sha256(Buffer.from(preimageHex, 'hex'));
return hash.toString('hex');
}
async reestablishChannels() {
const connectedInThisRun: any = {};
for (const channel of await this.listChannels()) {
if (channel.is_usable) continue; // already connected..?
if (connectedInThisRun[channel.remote_node_id]) continue; // already tried to reconnect (in case there are several channels with the same node)
const { pubkey, host, port } = await this.lookupNodeConnectionDetailsByPubkey(channel.remote_node_id);
await this.connectPeer(pubkey, host, port);
connectedInThisRun[pubkey] = true;
}
}
async channelsNeedReestablish() {
const freshListChannels = await this.listChannels();
const active = freshListChannels.filter(chan => !!chan.is_usable && chan.is_funding_locked).length;
return freshListChannels.length !== +active;
}
async waitForAtLeastOneChannelBecomeActive() {
const active = (await this.listChannels()).filter(chan => !!chan.is_usable).length;
for (let c = 0; c < 10; c++) {
await new Promise(resolve => setTimeout(resolve, 500)); // sleep
const freshListChannels = await this.listChannels();
const active2 = freshListChannels.filter(chan => !!chan.is_usable).length;
if (freshListChannels.length === +active2) return true; // all active kek
if (freshListChannels.length === 0) return true; // no channels at all
if (+active2 > +active) return true; // something became active, lets ret
}
return false;
}
async setRefundAddress(address: string) {
const script = bitcoin.address.toOutputScript(address);
this._refundAddressScriptHex = script.toString('hex');
}
static async getVersion() {}
static getPackageVersion() {}
getChannelsClosedEvents() {
return [{ reason: '', text: '' }];
}
async purgeLocalStorage() {}
/**
* executes async function in background, so calling code can return immediately, while catching all thrown exceptions
* and showing them in alert() instead of propagating them up
*
* @param func {function} Async functino to execute
* @private
*/
_execInBackground(func: () => void) {
const that = this;
(async () => {
try {
await func.call(that);
} catch (error: any) {
presentAlert({ message: '_execInBackground error:' + error.message});
}
})();
}
}

View file

@ -1,20 +0,0 @@
import { LightningLdkWallet } from '../../class';
import SyncedAsyncStorage from '../../class/synced-async-storage';
const assert = require('assert');
describe('', () => {
// eslint-disable-next-line jest/no-disabled-tests
it.skip('can import & dump LDK bytes from network storage ', async () => {
const ldk = new LightningLdkWallet();
ldk.setSecret('');
assert.ok(ldk.valid());
const syncedStorage = new SyncedAsyncStorage(ldk.getEntropyHex());
const keys = await syncedStorage.getAllKeysRemote();
for (const k of keys) {
let val = await syncedStorage.getItemRemote(k);
val = syncedStorage.decrypt(val);
console.log(`${k}\n---------------------------------------\n${val}`);
}
});
});

View file

@ -1,63 +0,0 @@
import { HDSegwitBech32Wallet, LightningLdkWallet } from '../../class';
const assert = require('assert');
describe('', () => {
function isHex(h) {
const re = /[0-9A-Fa-f]{6}/g;
return re.test(h);
}
it('can generate', async () => {
const ldk = new LightningLdkWallet();
await ldk.generate();
const secret = ldk.getSecret();
assert.ok(secret.startsWith('ldk://'), 'got ' + secret);
assert.ok(ldk.valid());
assert.ok(isHex(ldk.getEntropyHex()));
assert.ok(!isHex(ldk.getSecret()));
assert.strictEqual(ldk.getEntropyHex().length, 64);
assert.strictEqual(ldk.getEntropyHex(), ldk.getEntropyHex().toLowerCase());
//
const hd2 = new HDSegwitBech32Wallet();
hd2.setSecret(ldk.getSecret().replace('ldk://', ''));
assert.ok(hd2.validateMnemonic());
//
ldk.setSecret(secret);
assert.ok(ldk.valid());
ldk.setSecret('bla');
assert.ok(!ldk.valid());
//
ldk.setSecret('ldk://zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote');
assert.strictEqual(ldk.getEntropyHex(), 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
});
it('can convert preimage to hash', () => {
assert.strictEqual(
LightningLdkWallet.preimage2hash('7ec674e1edfc6f5cf32b445e12aa1a1fe0c91b97dd61f98bb41214f31f3642d0'),
'294c32ef715c92ac72af888b735950e8a8ea51c00bd4a01572a8da772956dde5',
);
});
it('can work with 12 words mnemonics instead of 24', () => {
const ldk = new LightningLdkWallet();
ldk.setSecret('ldk://abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about');
assert.strictEqual(ldk.getEntropyHex(), '0000000000000000000000000000000000000000000000000000000000000000');
assert.ok(ldk.valid());
assert.ok(isHex(ldk.getEntropyHex()));
assert.ok(!isHex(ldk.getSecret()));
assert.strictEqual(ldk.getEntropyHex().length, 64);
assert.strictEqual(ldk.getEntropyHex(), ldk.getEntropyHex().toLowerCase());
});
it('can unwrap address', () => {
const ldk = new LightningLdkWallet();
ldk.setSecret('ldk://abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about');
assert.strictEqual(ldk.unwrapFirstExternalAddressFromMnemonics(), 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu');
});
});