feat: get payment codes and tx history

This commit is contained in:
abhishandy 2022-12-11 21:34:50 -05:00
parent bd389111ba
commit 563f09012d
14 changed files with 359 additions and 78 deletions

View file

@ -470,7 +470,7 @@ const PaymentCodeStack = createNativeStackNavigator();
const PaymentCodeStackRoot = () => {
return (
<PaymentCodeStack.Navigator name="PaymentCodeRoot" screenOptions={{ headerHideShadow: true }} initialRouteName="PaymentCode">
<PaymentCodeStack.Screen name="PaymentCode" component={PaymentCode} />
<PaymentCodeStack.Screen name="PaymentCode" component={PaymentCode} options={{ headerTitle: 'Payment Code (BIP47)' }} />
</PaymentCodeStack.Navigator>
);
};

View file

@ -96,3 +96,5 @@ export function getTransactionsFullByAddress(address: string): Promise<ElectrumT
export function txhexToElectrumTransaction(txhes: string): ElectrumTransaction;
export function isDisabled(): Promise<boolean>;
export function multiGetTransactionHexByTxid(txids: string[], batchsize?: number): Promise<Record<string, string>>;

View file

@ -1053,3 +1053,24 @@ function txhexToElectrumTransaction(txhex) {
// exported only to be used in unit tests
module.exports.txhexToElectrumTransaction = txhexToElectrumTransaction;
module.exports.multiGetTransactionHexByTxid = async function (txids, batchsize = 100) {
if (!mainClient) throw new Error('Electrum client is not connected');
const chunks = splitIntoChunks(txids, batchsize);
const result = {};
for (const chunk of chunks) {
if (!disableBatching) {
await Promise.all(
chunk.map(async txid => {
const hex = await mainClient.blockchainTransaction_get(txid);
result[txid] = hex;
}),
);
} else {
const res = await mainClient.blockchainTransaction_getBatch(chunk);
res.forEach(({ result: r, param }) => (result[param] = r));
}
}
return result;
};

View file

@ -120,6 +120,10 @@ export const BlueStorageProvider = ({ children }) => {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
}
await BlueElectrum.waitTillConnected();
const paymentCodesStart = Date.now();
await fetchSenderPaymentCodes(lastSnappedTo);
const paymentCodesEnd = Date.now();
console.log('fetch payment codes took', (paymentCodesEnd - paymentCodesStart) / 1000, 'sec');
const balanceStart = +new Date();
await fetchWalletBalances(lastSnappedTo);
const balanceEnd = +new Date();
@ -201,6 +205,7 @@ export const BlueStorageProvider = ({ children }) => {
const getTransactions = BlueApp.getTransactions;
const isAdancedModeEnabled = BlueApp.isAdancedModeEnabled;
const fetchSenderPaymentCodes = BlueApp.fetchSenderPaymentCodes;
const fetchWalletBalances = BlueApp.fetchWalletBalances;
const fetchWalletTransactions = BlueApp.fetchWalletTransactions;
const getBalance = BlueApp.getBalance;

View file

@ -725,6 +725,17 @@ export class AppStorage {
}
};
fetchSenderPaymentCodes = async index => {
console.log('fetchSenderPaymentCodes for wallet#', typeof index === 'undefined' ? '(all)' : index);
if (index || index === 0) {
this.wallets[index].fetchBIP47SenderPaymentCodes();
} else {
for (const wallet of this.wallets) {
wallet.fetchBIP47SenderPaymentCodes();
}
}
};
/**
*
* @returns {Array.<AbstractWallet>}

View file

@ -4,6 +4,7 @@ import BigNumber from 'bignumber.js';
import b58 from 'bs58check';
import BIP32Factory, { BIP32Interface } from 'bip32';
import * as ecc from 'tiny-secp256k1';
import BIP47Factory, { BIP47Interface } from 'bip47';
import { randomBytes } from '../rng';
import { AbstractHDWallet } from './abstract-hd-wallet';
@ -20,6 +21,7 @@ const bitcoin = require('bitcoinjs-lib');
const BlueElectrum: typeof BlueElectrumNs = require('../../blue_modules/BlueElectrum');
const reverse = require('buffer-reverse');
const bip32 = BIP32Factory(ecc);
const bip47 = BIP47Factory(ecc);
type BalanceByIndex = {
c: number;
@ -45,6 +47,16 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
_utxo: any[];
// BIP47
_enable_BIP47: boolean;
_payment_code: string;
_sender_payment_codes: string[];
addresses_by_payment_code: Record<string, string[]>;
next_free_payment_code_address_index: Record<string, number>;
_txs_by_payment_code_index: Record<string, Transaction[][]>;
_balances_by_payment_code_index: Record<string, BalanceByIndex>;
bip47_instance?: BIP47Interface;
constructor() {
super();
this._balances_by_external_index = {}; // 0 => { c: 0, u: 0 } // confirmed/unconfirmed
@ -54,6 +66,15 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
this._txs_by_internal_index = {};
this._utxo = [];
// BIP47
this._enable_BIP47 = false;
this._payment_code = '';
this._sender_payment_codes = [];
this.next_free_payment_code_address_index = {};
this._txs_by_payment_code_index = {};
this._balances_by_payment_code_index = {};
this.addresses_by_payment_code = {};
}
/**
@ -67,6 +88,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (const bal of Object.values(this._balances_by_internal_index)) {
ret += bal.c;
}
for (const pc of this._sender_payment_codes) {
ret += this._getBalancesByPaymentCodeIndex(pc).c;
}
return ret + (this.getUnconfirmedBalance() < 0 ? this.getUnconfirmedBalance() : 0);
}
@ -82,20 +106,23 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (const bal of Object.values(this._balances_by_internal_index)) {
ret += bal.u;
}
for (const pc of this._sender_payment_codes) {
ret += this._getBalancesByPaymentCodeIndex(pc).u;
}
return ret;
}
async generate() {
const buf = await randomBytes(16);
this.secret = bip39.entropyToMnemonic(buf.toString('hex'));
this.setPaymentCode();
this.setBIP47PaymentCode();
}
async generateFromEntropy(user: Buffer) {
const random = await randomBytes(user.length < 32 ? 32 - user.length : 0);
const buf = Buffer.concat([user, random], 32);
this.secret = bip39.entropyToMnemonic(buf.toString('hex'));
this.setPaymentCode();
this.setBIP47PaymentCode();
}
_getExternalWIFByIndex(index: number): string | false {
@ -268,6 +295,21 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
}
}
// next, bip47 addresses
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 2; c++) {
let hasUnconfirmed = false;
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
for (const tx of this._txs_by_payment_code_index[pc][c])
hasUnconfirmed = hasUnconfirmed || !tx.confirmations || tx.confirmations < 7;
if (hasUnconfirmed || this._txs_by_payment_code_index[pc][c].length === 0 || this._balances_by_payment_code_index[pc].u !== 0) {
addresses2fetch.push(this._getBIP47Address(pc, c));
}
}
}
// first: batch fetch for all addresses histories
const histories = await BlueElectrum.multiGetHistoryByAddress(addresses2fetch);
const txs: Record<string, ElectrumHistory> = {};
@ -314,6 +356,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
this._txs_by_internal_index[c] = this._txs_by_internal_index[c].filter(tx => !!tx.confirmations);
}
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 2; c++) {
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c].filter(tx => !!tx.confirmations);
}
}
// now, we need to put transactions in all relevant `cells` of internal hashmaps: this._txs_by_internal_index && this._txs_by_external_index
@ -399,6 +446,51 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
}
}
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 2; c++) {
for (const tx of Object.values(txdatas)) {
for (const vin of tx.vin) {
if (vin.addresses && vin.addresses.indexOf(this._getBIP47Address(pc, c)) !== -1) {
// this TX is related to our address
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = { ...txRest, inputs: txVin.slice(0), outputs: txVout.slice(0) };
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_payment_code_index[pc][c].length; cc++) {
if (this._txs_by_payment_code_index[pc][c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_payment_code_index[pc][c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_payment_code_index[pc][c].push(clonedTx);
}
}
for (const vout of tx.vout) {
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getBIP47Address(pc, c)) !== -1) {
// this TX is related to our address
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = { ...txRest, inputs: txVin.slice(0), outputs: txVout.slice(0) };
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) {
if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_internal_index[c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_internal_index[c].push(clonedTx);
}
}
}
}
}
this._lastTxFetch = +new Date();
}
@ -411,6 +503,14 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (const addressTxs of Object.values(this._txs_by_internal_index)) {
txs = txs.concat(addressTxs);
}
if (this._sender_payment_codes) {
for (const pc of this._sender_payment_codes) {
if (this._txs_by_payment_code_index[pc])
for (const addressTxs of Object.values(this._txs_by_payment_code_index[pc])) {
txs = txs.concat(addressTxs);
}
}
}
if (txs.length === 0) return []; // guard clause; so we wont spend time calculating addresses
@ -552,12 +652,60 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
return lastUsedIndex;
}
async _binarySearchIterationForBIP47Address(paymentCode: string, index: number) {
const generateChunkAddresses = (chunkNum: number) => {
const ret = [];
for (let c = this.gap_limit * chunkNum; c < this.gap_limit * (chunkNum + 1); c++) {
ret.push(this._getBIP47Address(paymentCode, c));
}
return ret;
};
let lastChunkWithUsedAddressesNum = null;
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(generateChunkAddresses(c));
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;
} else {
// empty chunk. no sense searching more chunks
break;
}
}
let lastUsedIndex = 0;
if (lastHistoriesWithUsedAddresses) {
// now searching for last used address in batch lastChunkWithUsedAddressesNum
for (
let c = Number(lastChunkWithUsedAddressesNum) * this.gap_limit;
c < Number(lastChunkWithUsedAddressesNum) * this.gap_limit + this.gap_limit;
c++
) {
const address = this._getBIP47Address(paymentCode, c);
if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) {
lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unsued
}
}
}
return lastUsedIndex;
}
async fetchBalance() {
try {
if (this.next_free_change_address_index === 0 && this.next_free_address_index === 0) {
// doing binary search for last used address:
this.next_free_change_address_index = await this._binarySearchIterationForInternalAddress(1000);
this.next_free_address_index = await this._binarySearchIterationForExternalAddress(1000);
if (this._sender_payment_codes) {
for (const pc of this._sender_payment_codes) {
this.next_free_payment_code_address_index[pc] = await this._binarySearchIterationForBIP47Address(pc, 10);
}
}
} // end rescanning fresh wallet
// finally fetching balance
@ -579,6 +727,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = this.next_free_change_address_index; c < this.next_free_change_address_index + this.gap_limit; c++) {
lagAddressesToFetch.push(this._getInternalAddressByIndex(c));
}
for (const pc in this._sender_payment_codes) {
for (let c = this.next_free_payment_code_address_index[pc]; c < this.next_free_payment_code_address_index[pc] + this.gap_limit; c++) {
lagAddressesToFetch.push(this._getBIP47Address(pc, c));
}
}
const txs = await BlueElectrum.multiGetHistoryByAddress(lagAddressesToFetch); // <------ electrum call
@ -598,6 +751,16 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
}
}
for (const pc in this._sender_payment_codes) {
for (let c = this.next_free_payment_code_address_index[pc]; c < this.next_free_payment_code_address_index[pc] + this.gap_limit; c++) {
const address = this._getBIP47Address(pc, c);
if (txs[address] && Array.isArray(txs[address]) && txs[address].length > 0) {
// whoa, someone uses our wallet outside! better catch up
this.next_free_payment_code_address_index[pc] = c + 1;
}
}
}
// next, business as usuall. fetch balances
const addresses2fetch = [];
@ -616,6 +779,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
addresses2fetch.push(this._getInternalAddressByIndex(c));
}
for (const pc in this._sender_payment_codes) {
for (let c = this.next_free_payment_code_address_index[pc]; c < this.next_free_payment_code_address_index[pc] + this.gap_limit; c++) {
addresses2fetch.push(this._getBIP47Address(pc, c));
}
}
const balances = await BlueElectrum.multiGetBalanceByAddress(addresses2fetch);
// converting to a more compact internal format
@ -660,6 +829,29 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
}
}
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 2; c++) {
const addr = this._getBIP47Address(pc, c);
if (balances.addresses[addr]) {
// first, if balances differ from what we store - we delete transactions for that
// address so next fetchTransactions() will refetch everything
if (this._getBalancesByPaymentCodeIndex(pc).c) {
if (
this._getBalancesByPaymentCodeIndex(pc).c !== balances.addresses[addr].confirmed ||
this._getBalancesByPaymentCodeIndex(pc).u !== balances.addresses[addr].unconfirmed
) {
delete this._txs_by_payment_code_index[pc];
}
}
// update local representation of balances on that address:
this._balances_by_payment_code_index[pc] = {
c: balances.addresses[addr].confirmed,
u: balances.addresses[addr].unconfirmed,
};
}
}
}
this._lastBalanceFetch = +new Date();
}
@ -758,6 +950,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + 1; c++) {
ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true;
}
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 1; c++) {
ownedAddressesHashmap[this._getBIP47Address(pc, c)] = true;
}
}
for (const tx of this.getTransactions()) {
for (const output of tx.outputs) {
@ -1194,4 +1391,101 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
const seed = this._getSeed();
return AbstractHDElectrumWallet.seedToFingerprint(seed);
}
// Same as that in AbstractHDWallet, but also sets the BIP47 payment code
setSecret(newSecret: string): this {
this.secret = newSecret.trim().toLowerCase();
this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' ');
// Try to match words to the default bip39 wordlist and complete partial words
const wordlist = bip39.wordlists[bip39.getDefaultWordlist()];
const lookupMap = wordlist.reduce((map, word) => {
const prefix3 = word.substr(0, 3);
const prefix4 = word.substr(0, 4);
map.set(prefix3, !map.has(prefix3) ? word : false);
map.set(prefix4, !map.has(prefix4) ? word : false);
return map;
}, new Map<string, string | false>());
this.secret = this.secret
.split(' ')
.map(word => lookupMap.get(word) || word)
.join(' ');
this.setBIP47PaymentCode();
return this;
}
/**
* Whether BIP47 is enabled
* @returns boolean
*/
isBIP47Enabled(): boolean {
return this._enable_BIP47;
}
switchBIP47(value: boolean): void {
this._enable_BIP47 = value;
}
getBIP47FromSeed(): BIP47Interface {
if (!this.bip47_instance) this.bip47_instance = bip47.fromBip39Seed(this.secret, undefined, this.passphrase);
return this.bip47_instance;
}
setBIP47PaymentCode(): void {
this._payment_code = this.getBIP47FromSeed().getSerializedPaymentCode();
}
getBIP47PaymentCode(): string {
return this._payment_code;
}
getBIP47NotificationAddress(): string {
return this.getBIP47FromSeed().getNotificationAddress();
}
async fetchBIP47SenderPaymentCodes() {
const bip47 = this.getBIP47FromSeed();
const address = bip47.getNotificationAddress();
const histories = await BlueElectrum.multiGetHistoryByAddress([address]);
const txHashes = histories[address].map(({ tx_hash }) => tx_hash);
const txHexs = await BlueElectrum.multiGetTransactionHexByTxid(txHashes);
this._sender_payment_codes = Object.values(txHexs).map(str => {
return bip47.getPaymentCodeFromRawNotificationTransaction(str);
});
}
getBIP47SenderPaymentCodes(): string[] {
return this._sender_payment_codes;
}
_getBIP47Address(paymentCode: string, index: number) {
if (!this.addresses_by_payment_code[paymentCode]) this.addresses_by_payment_code[paymentCode] = [];
if (this.addresses_by_payment_code[paymentCode][index]) {
return this.addresses_by_payment_code[paymentCode][index];
}
const bip47_instance = this.getBIP47FromSeed();
const senderBIP47_instance = bip47.fromPaymentCode(paymentCode);
const remotePaymentNode = senderBIP47_instance.getPaymentCodeNode();
const hdNode = bip47_instance.getPaymentWallet(remotePaymentNode, index);
const address = bip47_instance.getAddressFromNode(hdNode, bip47_instance.network);
this.addresses_by_payment_code[paymentCode][index] = address;
return address;
}
_getNextFreePaymentCodeAddress(paymentCode: string) {
return this.next_free_payment_code_address_index[paymentCode] || 0;
}
_getBalancesByPaymentCodeIndex(paymentCode: string): BalanceByIndex {
return this._balances_by_payment_code_index[paymentCode] || { c: 0, u: 0 };
}
}

View file

@ -2,12 +2,6 @@ import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import b58 from 'bs58check';
import createHash from 'create-hash';
import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types';
import BIP47Factory from 'bip47';
import * as ecc from 'tiny-secp256k1';
import ECPairFactory from 'ecpair';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const ECPair = ECPairFactory(ecc);
type WalletStatics = {
type: string;
@ -57,8 +51,6 @@ export class AbstractWallet {
_utxoMetadata: Record<string, UtxoMetadata>;
use_with_hardware_wallet: boolean; // eslint-disable-line camelcase
masterFingerprint: number | false;
_enableBIP47: boolean;
paymentCode: string;
constructor() {
const Constructor = this.constructor as unknown as WalletStatics;
@ -82,8 +74,6 @@ export class AbstractWallet {
this._utxoMetadata = {};
this.use_with_hardware_wallet = false;
this.masterFingerprint = false;
this._enableBIP47 = false;
this.paymentCode = '';
}
/**
@ -121,22 +111,6 @@ export class AbstractWallet {
this._hideTransactionsInWalletsList = value;
}
/**
* Whether BIP47 is enabled
* @returns true/false
*/
getBIP47(): boolean {
return this._enableBIP47;
}
setBIP47(value: boolean): void {
this._enableBIP47 = value;
}
setPaymentCode(): void {
this.paymentCode = BIP47Factory(ecc).fromBip39Seed(this.secret).getSerializedPaymentCode();
}
/**
*
* @returns {string}

View file

@ -244,13 +244,13 @@ export default class TransactionsNavigationHeader extends Component {
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</ToolTipMenu>
)}
{this.state.wallet.getBIP47() && (
{this.state.wallet.isBIP47Enabled() && (
<TouchableOpacity
accessibilityRole="button"
onPress={() => {
this.props.navigation.navigate('PaymentCodeRoot', {
screen: 'PaymentCode',
params: { paymentCode: this.state.wallet.paymentCode },
params: { paymentCode: this.state.wallet.getBIP47PaymentCode() },
});
}}
>

50
package-lock.json generated
View file

@ -31,7 +31,7 @@
"bip32": "3.0.1",
"bip38": "github:BlueWallet/bip38",
"bip39": "3.0.4",
"bip47": "github:abhiShandy/bip47#bluewallet",
"bip47": "github:abhiShandy/bip47#9a52740670a683850612406398c66ad2c2482a5f",
"bitcoinjs-lib": "6.0.2",
"bitcoinjs-message": "2.2.0",
"bolt11": "1.3.4",
@ -7839,8 +7839,9 @@
},
"node_modules/bip47": {
"name": "@spsina/bip47",
"version": "1.0.0",
"resolved": "git+ssh://git@github.com/abhiShandy/bip47.git#d75b4089310f2884a4610f0586ada8a6c223581f",
"version": "1.0.1",
"resolved": "git+ssh://git@github.com/abhiShandy/bip47.git#9a52740670a683850612406398c66ad2c2482a5f",
"integrity": "sha512-lsgEpiEMDgpiYOA2kizOwiSS3vjTeLe2VnkOTIGnJ7Eu7Mkgl9dLES7oSLAjY64aQXr0VolqCRciRDc2nAC++w==",
"license": "MIT",
"dependencies": {
"bip32": "^3.0.1",
@ -7849,23 +7850,12 @@
"bs58check": "^2.1.1",
"create-hmac": "^1.1.7",
"ecpair": "^2.0.1",
"tiny-secp256k1": "^2.2.1"
"tiny-secp256k1": "^1.1.6"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/bip47/node_modules/tiny-secp256k1": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz",
"integrity": "sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng==",
"dependencies": {
"uint8array-tools": "0.0.7"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/bip66": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
@ -26313,14 +26303,6 @@
"node": ">=0.10.0"
}
},
"node_modules/uint8array-tools": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz",
"integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ultron": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
@ -33019,8 +33001,9 @@
}
},
"bip47": {
"version": "git+ssh://git@github.com/abhiShandy/bip47.git#d75b4089310f2884a4610f0586ada8a6c223581f",
"from": "bip47@github:abhiShandy/bip47#bluewallet",
"version": "git+ssh://git@github.com/abhiShandy/bip47.git#9a52740670a683850612406398c66ad2c2482a5f",
"integrity": "sha512-lsgEpiEMDgpiYOA2kizOwiSS3vjTeLe2VnkOTIGnJ7Eu7Mkgl9dLES7oSLAjY64aQXr0VolqCRciRDc2nAC++w==",
"from": "bip47@github:abhiShandy/bip47#9a52740670a683850612406398c66ad2c2482a5f",
"requires": {
"bip32": "^3.0.1",
"bip39": "^3.0.4",
@ -33028,17 +33011,7 @@
"bs58check": "^2.1.1",
"create-hmac": "^1.1.7",
"ecpair": "^2.0.1",
"tiny-secp256k1": "^2.2.1"
},
"dependencies": {
"tiny-secp256k1": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz",
"integrity": "sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng==",
"requires": {
"uint8array-tools": "0.0.7"
}
}
"tiny-secp256k1": "^1.1.6"
}
},
"bip66": {
@ -47383,11 +47356,6 @@
}
}
},
"uint8array-tools": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz",
"integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ=="
},
"ultron": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",

View file

@ -117,7 +117,7 @@
"bip32": "3.0.1",
"bip38": "github:BlueWallet/bip38",
"bip39": "3.0.4",
"bip47": "github:abhiShandy/bip47#bluewallet",
"bip47": "github:abhiShandy/bip47#9a52740670a683850612406398c66ad2c2482a5f",
"bitcoinjs-lib": "6.0.2",
"bitcoinjs-message": "2.2.0",
"bolt11": "1.3.4",

View file

@ -128,7 +128,7 @@ const WalletDetails = () => {
const [useWithHardwareWallet, setUseWithHardwareWallet] = useState(wallet.useWithHardwareWalletEnabled());
const { isAdancedModeEnabled } = useContext(BlueStorageContext);
const [isAdvancedModeEnabledRender, setIsAdvancedModeEnabledRender] = useState(false);
const [isBIP47Enabled, setIsBIP47Enabled] = useState(wallet.getBIP47());
const [isBIP47Enabled, setIsBIP47Enabled] = useState(wallet.isBIP47Enabled());
const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList());
const { goBack, navigate, setOptions, popToTop } = useNavigation();
const { colors } = useTheme();
@ -188,7 +188,7 @@ const WalletDetails = () => {
wallet.setUseWithHardwareWalletEnabled(useWithHardwareWallet);
}
wallet.setHideTransactionsInWalletsList(!hideTransactionsInWalletsList);
wallet.setBIP47(isBIP47Enabled);
wallet.switchBIP47(isBIP47Enabled);
}
saveToDisk()
.then(() => {

View file

@ -12,8 +12,13 @@ export default function PaymentCode({ route }: NativeStackScreenProps<PaymentCod
return (
<View style={styles.container}>
<QRCodeComponent value={paymentCode} />
<Text style={styles.paymentCodeText}>{paymentCode}</Text>
{!paymentCode && <Text>Payment code not found</Text>}
{paymentCode && (
<>
<QRCodeComponent value={paymentCode} />
<Text style={styles.paymentCodeText}>{paymentCode}</Text>
</>
)}
</View>
);
}

View file

@ -179,6 +179,7 @@ const WalletTransactions = ({ navigation }) => {
// await BlueElectrum.ping();
await BlueElectrum.waitTillConnected();
/** @type {LegacyWallet} */
await wallet.fetchBIP47SenderPaymentCodes();
const balanceStart = +new Date();
const oldBalance = wallet.getBalance();
await wallet.fetchBalance();

View file

@ -4,7 +4,7 @@ import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock
const consoleWarnOrig = console.warn;
console.warn = (...args) => {
if (!args[0].startsWith('WARNING: Sending to a future segwit version address can lead to loss of funds')) {
if (typeof args[0] === 'string' && !args[0].startsWith('WARNING: Sending to a future segwit version address can lead to loss of funds')) {
consoleWarnOrig.apply(consoleWarnOrig, args);
}
};