diff --git a/Navigation.js b/Navigation.js index 92b9ee3f6..5bceb67dc 100644 --- a/Navigation.js +++ b/Navigation.js @@ -470,7 +470,7 @@ const PaymentCodeStack = createNativeStackNavigator(); const PaymentCodeStackRoot = () => { return ( - + ); }; diff --git a/blue_modules/BlueElectrum.d.ts b/blue_modules/BlueElectrum.d.ts index 7d4e066d5..89689fa83 100644 --- a/blue_modules/BlueElectrum.d.ts +++ b/blue_modules/BlueElectrum.d.ts @@ -96,3 +96,5 @@ export function getTransactionsFullByAddress(address: string): Promise; + +export function multiGetTransactionHexByTxid(txids: string[], batchsize?: number): Promise>; diff --git a/blue_modules/BlueElectrum.js b/blue_modules/BlueElectrum.js index 7717c0a3f..3ee0947f0 100644 --- a/blue_modules/BlueElectrum.js +++ b/blue_modules/BlueElectrum.js @@ -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; +}; diff --git a/blue_modules/storage-context.js b/blue_modules/storage-context.js index f27860622..f7f413c46 100644 --- a/blue_modules/storage-context.js +++ b/blue_modules/storage-context.js @@ -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; diff --git a/class/app-storage.js b/class/app-storage.js index 43e720075..039e0a778 100644 --- a/class/app-storage.js +++ b/class/app-storage.js @@ -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.} diff --git a/class/wallets/abstract-hd-electrum-wallet.ts b/class/wallets/abstract-hd-electrum-wallet.ts index 817efbb92..493d6352f 100644 --- a/class/wallets/abstract-hd-electrum-wallet.ts +++ b/class/wallets/abstract-hd-electrum-wallet.ts @@ -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; + next_free_payment_code_address_index: Record; + _txs_by_payment_code_index: Record; + _balances_by_payment_code_index: Record; + 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 = {}; @@ -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()); + + 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 }; + } } diff --git a/class/wallets/abstract-wallet.ts b/class/wallets/abstract-wallet.ts index 02d6e8f93..d86381bb6 100644 --- a/class/wallets/abstract-wallet.ts +++ b/class/wallets/abstract-wallet.ts @@ -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; 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} diff --git a/components/TransactionsNavigationHeader.js b/components/TransactionsNavigationHeader.js index 26dd3c511..89d38214c 100644 --- a/components/TransactionsNavigationHeader.js +++ b/components/TransactionsNavigationHeader.js @@ -244,13 +244,13 @@ export default class TransactionsNavigationHeader extends Component { {loc.lnd.title} )} - {this.state.wallet.getBIP47() && ( + {this.state.wallet.isBIP47Enabled() && ( { this.props.navigation.navigate('PaymentCodeRoot', { screen: 'PaymentCode', - params: { paymentCode: this.state.wallet.paymentCode }, + params: { paymentCode: this.state.wallet.getBIP47PaymentCode() }, }); }} > diff --git a/package-lock.json b/package-lock.json index 077159688..72b8eab15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index af299e15f..8406d3513 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/screen/wallets/details.js b/screen/wallets/details.js index ae9f505fa..f31a93114 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -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(() => { diff --git a/screen/wallets/paymentCode.tsx b/screen/wallets/paymentCode.tsx index 019246db7..99c918557 100644 --- a/screen/wallets/paymentCode.tsx +++ b/screen/wallets/paymentCode.tsx @@ -12,8 +12,13 @@ export default function PaymentCode({ route }: NativeStackScreenProps - - {paymentCode} + {!paymentCode && Payment code not found} + {paymentCode && ( + <> + + {paymentCode} + + )} ); } diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index 71b8a9d09..e0c5234d9 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -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(); diff --git a/tests/setup.js b/tests/setup.js index 6a6014985..bd18f3a92 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -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); } };