mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-13 11:09:20 +01:00
feat: get payment codes and tx history
This commit is contained in:
parent
bd389111ba
commit
563f09012d
14 changed files with 359 additions and 78 deletions
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
2
blue_modules/BlueElectrum.d.ts
vendored
2
blue_modules/BlueElectrum.d.ts
vendored
|
@ -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>>;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
50
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue