From 17477f91f5820d33f6c54415422ce02674dc53f8 Mon Sep 17 00:00:00 2001 From: Ivan Vershigora Date: Mon, 25 Mar 2024 00:07:43 +0300 Subject: [PATCH] feat: refactor BlueElectrum to Typescript --- BlueApp.ts | 2 +- blue_modules/BlueElectrum.d.ts | 98 --- .../{BlueElectrum.js => BlueElectrum.ts} | 634 ++++++++++-------- blue_modules/WidgetCommunication.js | 8 - blue_modules/WidgetCommunication.ts | 9 + blue_modules/storage-context.tsx | 3 +- class/hd-segwit-bech32-transaction.js | 8 +- class/wallets/abstract-hd-electrum-wallet.ts | 9 +- class/wallets/abstract-hd-wallet.ts | 6 +- class/wallets/hd-legacy-breadwallet-wallet.ts | 3 +- class/wallets/hd-legacy-p2pkh-wallet.ts | 4 +- class/wallets/legacy-wallet.ts | 5 +- class/wallets/multisig-hd-wallet.ts | 5 +- models/networkTransactionFees.ts | 2 +- screen/receive/details.js | 2 +- screen/selftest.js | 3 +- screen/send/broadcast.js | 2 +- screen/send/confirm.js | 2 +- screen/send/psbtWithHardwareWallet.js | 4 +- screen/settings/electrumSettings.js | 3 +- screen/transactions/CPFP.js | 2 +- screen/transactions/transactionStatus.js | 2 +- screen/wallets/transactions.js | 7 +- tests/integration/BlueElectrum.test.js | 10 +- 24 files changed, 398 insertions(+), 435 deletions(-) delete mode 100644 blue_modules/BlueElectrum.d.ts rename blue_modules/{BlueElectrum.js => BlueElectrum.ts} (77%) delete mode 100644 blue_modules/WidgetCommunication.js create mode 100644 blue_modules/WidgetCommunication.ts diff --git a/BlueApp.ts b/BlueApp.ts index f12fcd71b..afaf2a2f3 100644 --- a/BlueApp.ts +++ b/BlueApp.ts @@ -6,7 +6,7 @@ import * as Keychain from 'react-native-keychain'; import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store'; import Realm from 'realm'; -import BlueElectrum from './blue_modules/BlueElectrum'; +import * as BlueElectrum from './blue_modules/BlueElectrum'; import { initCurrencyDaemon } from './blue_modules/currency'; import * as encryption from './blue_modules/encryption'; import { diff --git a/blue_modules/BlueElectrum.d.ts b/blue_modules/BlueElectrum.d.ts deleted file mode 100644 index 4c0642765..000000000 --- a/blue_modules/BlueElectrum.d.ts +++ /dev/null @@ -1,98 +0,0 @@ -type Utxo = { - height: number; - value: number; - address: string; - txid: string; - vout: number; - wif?: string; -}; - -export type ElectrumTransaction = { - txid: string; - hash: string; - version: number; - size: number; - vsize: number; - weight: number; - locktime: number; - vin: { - txid: string; - vout: number; - scriptSig: { asm: string; hex: string }; - txinwitness: string[]; - sequence: number; - addresses?: string[]; - value?: number; - }[]; - vout: { - value: number; - n: number; - scriptPubKey: { - asm: string; - hex: string; - reqSigs: number; - type: string; - addresses: string[]; - }; - }[]; - blockhash: string; - confirmations?: number; - time: number; - blocktime: number; -}; - -type MempoolTransaction = { - height: 0; - tx_hash: string; - fee: number; -}; - -export async function connectMain(): Promise; - -export async function waitTillConnected(): Promise; - -export function forceDisconnect(): void; - -export function getBalanceByAddress(address: string): Promise<{ confirmed: number; unconfirmed: number }>; - -export function multiGetUtxoByAddress(addresses: string[]): Promise>; - -// TODO: this function returns different results based on the value of `verbose`, consider splitting it into two -export function multiGetTransactionByTxid( - txids: string[], - batchsize: number = 45, - verbose: true = true, -): Promise>; -export function multiGetTransactionByTxid(txids: string[], batchsize: number, verbose: false): Promise>; - -export type MultiGetBalanceResponse = { - balance: number; - unconfirmed_balance: number; - addresses: Record; -}; - -export function multiGetBalanceByAddress(addresses: string[], batchsize?: number): Promise; - -export function getTransactionsByAddress(address: string): ElectrumTransaction[]; - -export function getMempoolTransactionsByAddress(address: string): Promise; - -export function estimateCurrentBlockheight(): number; - -export type ElectrumHistory = { - tx_hash: string; - height: number; - address: string; -}; - -export function multiGetHistoryByAddress(addresses: string[]): Promise>; - -export function estimateFees(): Promise<{ fast: number; medium: number; slow: number }>; - -export function broadcastV2(txhex: string): Promise; - -export function getTransactionsFullByAddress(address: string): Promise; - -export function txhexToElectrumTransaction(txhes: string): ElectrumTransaction; - -export function isDisabled(): Promise; diff --git a/blue_modules/BlueElectrum.js b/blue_modules/BlueElectrum.ts similarity index 77% rename from blue_modules/BlueElectrum.js rename to blue_modules/BlueElectrum.ts index f3646f495..ea5766dfe 100644 --- a/blue_modules/BlueElectrum.js +++ b/blue_modules/BlueElectrum.ts @@ -1,26 +1,111 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; +import BigNumber from 'bignumber.js'; +import * as bitcoin from 'bitcoinjs-lib'; import { Alert } from 'react-native'; -import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class'; import DefaultPreference from 'react-native-default-preference'; +import Realm from 'realm'; + +import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class'; +import presentAlert from '../components/Alert'; import loc from '../loc'; import WidgetCommunication from './WidgetCommunication'; -import presentAlert from '../components/Alert'; -const bitcoin = require('bitcoinjs-lib'); -const ElectrumClient = require('electrum-client'); -const BigNumber = require('bignumber.js'); +const ElectrumClient = require('electrum-client'); const net = require('net'); const tls = require('tls'); -const Realm = require('realm'); +type Utxo = { + height: number; + value: number; + address: string; + txid: string; + vout: number; + wif?: string; +}; -const ELECTRUM_HOST = 'electrum_host'; -const ELECTRUM_TCP_PORT = 'electrum_tcp_port'; -const ELECTRUM_SSL_PORT = 'electrum_ssl_port'; -const ELECTRUM_SERVER_HISTORY = 'electrum_server_history'; +type ElectrumTransaction = { + txid: string; + hash: string; + version: number; + size: number; + vsize: number; + weight: number; + locktime: number; + vin: { + txid: string; + vout: number; + scriptSig: { asm: string; hex: string }; + txinwitness: string[]; + sequence: number; + addresses?: string[]; + value?: number; + }[]; + vout: { + value: number; + n: number; + scriptPubKey: { + asm: string; + hex: string; + reqSigs: number; + type: string; + addresses: string[]; + }; + }[]; + blockhash: string; + confirmations?: number; + time: number; + blocktime: number; +}; + +type ElectrumTransactionWithHex = ElectrumTransaction & { + hex: string; +}; + +type MempoolTransaction = { + height: 0; + tx_hash: string; + fee: number; +}; + +type Peer = + | { + host: string; + ssl: string; + tcp?: undefined; + } + | { + host: string; + tcp: string; + ssl?: undefined; + }; + +export const ELECTRUM_HOST = 'electrum_host'; +export const ELECTRUM_TCP_PORT = 'electrum_tcp_port'; +export const ELECTRUM_SSL_PORT = 'electrum_ssl_port'; +export const ELECTRUM_SERVER_HISTORY = 'electrum_server_history'; const ELECTRUM_CONNECTION_DISABLED = 'electrum_disabled'; +const storageKey = 'ELECTRUM_PEERS'; +const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: '443' }; +export const hardcodedPeers: Peer[] = [ + { host: 'mainnet.foundationdevices.com', ssl: '50002' }, + { host: 'bitcoin.lukechilds.co', ssl: '50002' }, + { host: 'electrum.jochen-hoenicke.de', ssl: '50006' }, + { host: 'electrum1.bluewallet.io', ssl: '443' }, + { host: 'electrum.acinq.co', ssl: '50002' }, + { host: 'electrum.bitaroo.net', ssl: '50002' }, +]; + +let mainClient: typeof ElectrumClient | undefined; +let mainConnected: boolean = false; +let wasConnectedAtLeastOnce: boolean = false; +let serverName: string | false = false; +let disableBatching: boolean = false; +let connectionAttempt: number = 0; +let currentPeerIndex = Math.floor(Math.random() * hardcodedPeers.length); +let latestBlock: { height: number; time: number } | { height: undefined; time: undefined } = { height: undefined, time: undefined }; +const txhashHeightCache: Record = {}; +let _realm: Realm | undefined; -let _realm; async function _getRealm() { if (_realm) return _realm; @@ -39,6 +124,8 @@ async function _getRealm() { }, }, ]; + + // @ts-ignore schema doesn't match Realm's schema type _realm = await Realm.open({ schema, path, @@ -47,32 +134,7 @@ async function _getRealm() { return _realm; } -const storageKey = 'ELECTRUM_PEERS'; -const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: '443' }; -const hardcodedPeers = [ - { host: 'mainnet.foundationdevices.com', ssl: '50002' }, - { host: 'bitcoin.lukechilds.co', ssl: '50002' }, - { host: 'electrum.jochen-hoenicke.de', ssl: '50006' }, - { host: 'electrum1.bluewallet.io', ssl: '443' }, - { host: 'electrum.acinq.co', ssl: '50002' }, - { host: 'electrum.bitaroo.net', ssl: '50002' }, -]; - -/** @type {ElectrumClient} */ -let mainClient; -let mainConnected = false; -let wasConnectedAtLeastOnce = false; -let serverName = false; -let disableBatching = false; -let connectionAttempt = 0; -let currentPeerIndex = Math.floor(Math.random() * hardcodedPeers.length); - -let latestBlockheight = false; -let latestBlockheightTimestamp = false; - -const txhashHeightCache = {}; - -async function isDisabled() { +export async function isDisabled(): Promise { let result; try { const savedValue = await AsyncStorage.getItem(ELECTRUM_CONNECTION_DISABLED); @@ -87,16 +149,50 @@ async function isDisabled() { return !!result; } -async function setDisabled(disabled = true) { +export async function setDisabled(disabled = true) { return AsyncStorage.setItem(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : ''); } -async function connectMain() { +function getCurrentPeer() { + return hardcodedPeers[currentPeerIndex]; +} + +/** + * Returns NEXT hardcoded electrum server (increments index after use) + */ +function getNextPeer() { + const peer = getCurrentPeer(); + currentPeerIndex++; + if (currentPeerIndex + 1 >= hardcodedPeers.length) currentPeerIndex = 0; + return peer; +} + +async function getSavedPeer(): Promise { + const host = await AsyncStorage.getItem(ELECTRUM_HOST); + const tcpPort = await AsyncStorage.getItem(ELECTRUM_TCP_PORT); + const sslPort = await AsyncStorage.getItem(ELECTRUM_SSL_PORT); + + if (!host) { + return null; + } + + if (sslPort) { + return { host, ssl: sslPort }; + } + + if (tcpPort) { + return { host, tcp: tcpPort }; + } + + return null; +} + +export async function connectMain(): Promise { if (await isDisabled()) { console.log('Electrum connection disabled by user. Skipping connectMain call'); return; } - let usingPeer = await getNextPeer(); + let usingPeer = getNextPeer(); const savedPeer = await getSavedPeer(); if (savedPeer && savedPeer.host && (savedPeer.tcp || savedPeer.ssl)) { usingPeer = savedPeer; @@ -105,14 +201,14 @@ async function connectMain() { await DefaultPreference.setName('group.io.bluewallet.bluewallet'); try { if (usingPeer.host.endsWith('onion')) { - const randomPeer = await getCurrentPeer(); + const randomPeer = getCurrentPeer(); await DefaultPreference.set(ELECTRUM_HOST, randomPeer.host); - await DefaultPreference.set(ELECTRUM_TCP_PORT, randomPeer.tcp); - await DefaultPreference.set(ELECTRUM_SSL_PORT, randomPeer.ssl); + await DefaultPreference.set(ELECTRUM_TCP_PORT, randomPeer.tcp ?? ''); + await DefaultPreference.set(ELECTRUM_SSL_PORT, randomPeer.ssl ?? ''); } else { await DefaultPreference.set(ELECTRUM_HOST, usingPeer.host); - await DefaultPreference.set(ELECTRUM_TCP_PORT, usingPeer.tcp); - await DefaultPreference.set(ELECTRUM_SSL_PORT, usingPeer.ssl); + await DefaultPreference.set(ELECTRUM_TCP_PORT, usingPeer.tcp ?? ''); + await DefaultPreference.set(ELECTRUM_SSL_PORT, usingPeer.ssl ?? ''); } WidgetCommunication.reloadAllTimelines(); @@ -125,7 +221,7 @@ async function connectMain() { console.log('begin connection:', JSON.stringify(usingPeer)); mainClient = new ElectrumClient(net, tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp'); - mainClient.onError = function (e) { + mainClient.onError = function (e: { message: string }) { console.log('electrum mainClient.onError():', e.message); if (mainConnected) { // most likely got a timeout from electrum ping. lets reconnect @@ -169,8 +265,10 @@ async function connectMain() { } const header = await mainClient.blockchainHeaders_subscribe(); if (header && header.height) { - latestBlockheight = header.height; - latestBlockheightTimestamp = Math.floor(+new Date() / 1000); + latestBlock = { + height: header.height, + time: Math.floor(+new Date() / 1000), + }; } // AsyncStorage.setItem(storageKey, JSON.stringify(peers)); TODO: refactor } @@ -193,7 +291,7 @@ async function connectMain() { } } -async function presentNetworkErrorAlert(usingPeer) { +const presentNetworkErrorAlert = async (usingPeer?: Peer) => { if (await isDisabled()) { console.log( 'Electrum connection disabled by user. Perhaps we are attempting to show this network error alert after the user disabled connections.', @@ -268,47 +366,24 @@ async function presentNetworkErrorAlert(usingPeer) { ], { cancelable: false }, ); -} - -async function getCurrentPeer() { - return hardcodedPeers[currentPeerIndex]; -} - -/** - * Returns NEXT hardcoded electrum server (increments index after use) - * - * @returns {Promise<{tcp, host, ssl?}|*>} - */ -async function getNextPeer() { - const peer = getCurrentPeer(); - currentPeerIndex++; - if (currentPeerIndex + 1 >= hardcodedPeers.length) currentPeerIndex = 0; - return peer; -} - -async function getSavedPeer() { - const host = await AsyncStorage.getItem(ELECTRUM_HOST); - const port = await AsyncStorage.getItem(ELECTRUM_TCP_PORT); - const sslPort = await AsyncStorage.getItem(ELECTRUM_SSL_PORT); - return { host, tcp: port, ssl: sslPort }; -} +}; /** * Returns random electrum server out of list of servers * previous electrum server told us. Nearly half of them is * usually offline. * Not used for now. - * - * @returns {Promise<{tcp: number, host: string}>} */ // eslint-disable-next-line @typescript-eslint/no-unused-vars -async function getRandomDynamicPeer() { +async function getRandomDynamicPeer(): Promise { try { - let peers = JSON.parse(await AsyncStorage.getItem(storageKey)); + let peers = JSON.parse((await AsyncStorage.getItem(storageKey)) as string); peers = peers.sort(() => Math.random() - 0.5); // shuffle for (const peer of peers) { - const ret = {}; - ret.host = peer[1]; + const ret = { + host: peer[1] as string, + tcp: '', + }; for (const item of peer[2]) { if (item.startsWith('t')) { ret.tcp = item.replace('t', ''); @@ -323,12 +398,7 @@ async function getRandomDynamicPeer() { } } -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getBalanceByAddress = async function (address) { +export const getBalanceByAddress = async function (address: string): Promise<{ confirmed: number; unconfirmed: number }> { if (!mainClient) throw new Error('Electrum client is not connected'); const script = bitcoin.address.toOutputScript(address); const hash = bitcoin.crypto.sha256(script); @@ -338,7 +408,7 @@ module.exports.getBalanceByAddress = async function (address) { return balance; }; -module.exports.getConfig = async function () { +export const getConfig = async function () { if (!mainClient) throw new Error('Electrum client is not connected'); return { host: mainClient.host, @@ -348,16 +418,11 @@ module.exports.getConfig = async function () { }; }; -module.exports.getSecondsSinceLastRequest = function () { +export const getSecondsSinceLastRequest = function () { return mainClient && mainClient.timeLastCall ? (+new Date() - mainClient.timeLastCall) / 1000 : -1; }; -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getTransactionsByAddress = async function (address) { +export const getTransactionsByAddress = async function (address: string): Promise { if (!mainClient) throw new Error('Electrum client is not connected'); const script = bitcoin.address.toOutputScript(address); const hash = bitcoin.crypto.sha256(script); @@ -370,12 +435,7 @@ module.exports.getTransactionsByAddress = async function (address) { return history; }; -/** - * - * @param address {String} - * @returns {Promise} - */ -module.exports.getMempoolTransactionsByAddress = async function (address) { +export const getMempoolTransactionsByAddress = async function (address: string): Promise { if (!mainClient) throw new Error('Electrum client is not connected'); const script = bitcoin.address.toOutputScript(address); const hash = bitcoin.crypto.sha256(script); @@ -383,7 +443,7 @@ module.exports.getMempoolTransactionsByAddress = async function (address) { return mainClient.blockchainScripthash_getMempool(reversedHash.toString('hex')); }; -module.exports.ping = async function () { +export const ping = async function () { try { await mainClient.server_ping(); } catch (_) { @@ -393,14 +453,100 @@ module.exports.ping = async function () { return true; }; -module.exports.getTransactionsFullByAddress = async function (address) { - const txs = await this.getTransactionsByAddress(address); - const ret = []; +// exported only to be used in unit tests +export function txhexToElectrumTransaction(txhex: string): ElectrumTransactionWithHex { + const tx = bitcoin.Transaction.fromHex(txhex); + + const ret: ElectrumTransactionWithHex = { + txid: tx.getId(), + hash: tx.getId(), + version: tx.version, + size: Math.ceil(txhex.length / 2), + vsize: tx.virtualSize(), + weight: tx.weight(), + locktime: tx.locktime, + vin: [], + vout: [], + hex: txhex, + blockhash: '', + confirmations: 0, + time: 0, + blocktime: 0, + }; + + if (txhashHeightCache[ret.txid]) { + // got blockheight where this tx was confirmed + ret.confirmations = estimateCurrentBlockheight() - txhashHeightCache[ret.txid]; + if (ret.confirmations < 0) { + // ugly fix for when estimator lags behind + ret.confirmations = 1; + } + ret.time = calculateBlockTime(txhashHeightCache[ret.txid]); + ret.blocktime = calculateBlockTime(txhashHeightCache[ret.txid]); + } + + for (const inn of tx.ins) { + const txinwitness = []; + if (inn.witness[0]) txinwitness.push(inn.witness[0].toString('hex')); + if (inn.witness[1]) txinwitness.push(inn.witness[1].toString('hex')); + + ret.vin.push({ + txid: Buffer.from(inn.hash).reverse().toString('hex'), + vout: inn.index, + scriptSig: { hex: inn.script.toString('hex'), asm: '' }, + txinwitness, + sequence: inn.sequence, + }); + } + + let n = 0; + for (const out of tx.outs) { + const value = new BigNumber(out.value).dividedBy(100000000).toNumber(); + let address: false | string = false; + let type: false | string = false; + + if (SegwitBech32Wallet.scriptPubKeyToAddress(out.script.toString('hex'))) { + address = SegwitBech32Wallet.scriptPubKeyToAddress(out.script.toString('hex')); + type = 'witness_v0_keyhash'; + } else if (SegwitP2SHWallet.scriptPubKeyToAddress(out.script.toString('hex'))) { + address = SegwitP2SHWallet.scriptPubKeyToAddress(out.script.toString('hex')); + type = '???'; // TODO + } else if (LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex'))) { + address = LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex')); + type = '???'; // TODO + } else { + address = TaprootWallet.scriptPubKeyToAddress(out.script.toString('hex')); + type = 'witness_v1_taproot'; + } + + if (!address) { + throw new Error('Internal error: unable to decode address from output script'); + } + + ret.vout.push({ + value, + n, + scriptPubKey: { + asm: '', + hex: out.script.toString('hex'), + reqSigs: 1, // todo + type, + addresses: [address], + }, + }); + n++; + } + return ret; +} + +export const getTransactionsFullByAddress = async (address: string): Promise => { + const txs = await getTransactionsByAddress(address); + const ret: ElectrumTransaction[] = []; for (const tx of txs) { let full; try { full = await mainClient.blockchainTransaction_get(tx.tx_hash, true); - } catch (error) { + } catch (error: any) { if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { // apparently, stupid esplora instead of returning txhex when it cant return verbose tx started // throwing a proper exception. lets fetch txhex manually and decode on our end @@ -417,7 +563,7 @@ module.exports.getTransactionsFullByAddress = async function (address) { let prevTxForVin; try { prevTxForVin = await mainClient.blockchainTransaction_get(input.txid, true); - } catch (error) { + } catch (error: any) { if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { // apparently, stupid esplora instead of returning txhex when it cant return verbose tx started // throwing a proper exception. lets fetch txhex manually and decode on our end @@ -442,7 +588,7 @@ module.exports.getTransactionsFullByAddress = async function (address) { } for (const output of full.vout) { - if (output.scriptPubKey && output.scriptPubKey.addresses) output.addresses = output.scriptPubKey.addresses; + if (output?.scriptPubKey && output.scriptPubKey.addresses) output.addresses = output.scriptPubKey.addresses; // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: if (output?.scriptPubKey?.address) output.addresses = [output.scriptPubKey.address]; } @@ -458,26 +604,28 @@ module.exports.getTransactionsFullByAddress = async function (address) { return ret; }; -/** - * - * @param addresses {Array} - * @param batchsize {Number} - * @returns {Promise<{balance: number, unconfirmed_balance: number, addresses: object}>} - */ -module.exports.multiGetBalanceByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 200; +type MultiGetBalanceResponse = { + balance: number; + unconfirmed_balance: number; + addresses: Record; +}; + +export const multiGetBalanceByAddress = async (addresses: string[], batchsize: number = 200): Promise => { if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = { balance: 0, unconfirmed_balance: 0, addresses: {} }; + const ret = { + balance: 0, + unconfirmed_balance: 0, + addresses: {} as Record, + }; const chunks = splitIntoChunks(addresses, batchsize); for (const chunk of chunks) { const scripthashes = []; - const scripthash2addr = {}; + const scripthash2addr: Record = {}; for (const addr of chunk) { const script = bitcoin.address.toOutputScript(addr); const hash = bitcoin.crypto.sha256(script); - let reversedHash = Buffer.from(hash).reverse(); - reversedHash = reversedHash.toString('hex'); + const reversedHash = Buffer.from(hash).reverse().toString('hex'); scripthashes.push(reversedHash); scripthash2addr[reversedHash] = addr; } @@ -486,7 +634,7 @@ module.exports.multiGetBalanceByAddress = async function (addresses, batchsize) if (disableBatching) { const promises = []; - const index2scripthash = {}; + const index2scripthash: Record = {}; for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { promises.push(mainClient.blockchainScripthash_getBalance(scripthashes[promiseIndex])); index2scripthash[promiseIndex] = scripthashes[promiseIndex]; @@ -510,20 +658,18 @@ module.exports.multiGetBalanceByAddress = async function (addresses, batchsize) return ret; }; -module.exports.multiGetUtxoByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 100; +export const multiGetUtxoByAddress = async function (addresses: string[], batchsize: number = 100): Promise> { if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = {}; + const ret: Record = {}; const chunks = splitIntoChunks(addresses, batchsize); for (const chunk of chunks) { const scripthashes = []; - const scripthash2addr = {}; + const scripthash2addr: Record = {}; for (const addr of chunk) { const script = bitcoin.address.toOutputScript(addr); const hash = bitcoin.crypto.sha256(script); - let reversedHash = Buffer.from(hash).reverse(); - reversedHash = reversedHash.toString('hex'); + const reversedHash = Buffer.from(hash).reverse().toString('hex'); scripthashes.push(reversedHash); scripthash2addr[reversedHash] = addr; } @@ -553,20 +699,27 @@ module.exports.multiGetUtxoByAddress = async function (addresses, batchsize) { return ret; }; -module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) { - batchsize = batchsize || 100; +export type ElectrumHistory = { + tx_hash: string; + height: number; + address: string; +}; + +export const multiGetHistoryByAddress = async function ( + addresses: string[], + batchsize: number = 100, +): Promise> { if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = {}; + const ret: Record = {}; const chunks = splitIntoChunks(addresses, batchsize); for (const chunk of chunks) { const scripthashes = []; - const scripthash2addr = {}; + const scripthash2addr: Record = {}; for (const addr of chunk) { const script = bitcoin.address.toOutputScript(addr); const hash = bitcoin.crypto.sha256(script); - let reversedHash = Buffer.from(hash).reverse(); - reversedHash = reversedHash.toString('hex'); + const reversedHash = Buffer.from(hash).reverse().toString('hex'); scripthashes.push(reversedHash); scripthash2addr[reversedHash] = addr; } @@ -575,7 +728,7 @@ module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) if (disableBatching) { const promises = []; - const index2scripthash = {}; + const index2scripthash: Record = {}; for (let promiseIndex = 0; promiseIndex < scripthashes.length; promiseIndex++) { index2scripthash[promiseIndex] = scripthashes[promiseIndex]; promises.push(mainClient.blockchainScripthash_getHistory(scripthashes[promiseIndex])); @@ -604,13 +757,20 @@ module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) return ret; }; -module.exports.multiGetTransactionByTxid = async function (txids, batchsize, verbose = true) { +// if verbose === true ? Record : Record +type MultiGetTransactionByTxidResult = T extends true ? Record : Record; + +// TODO: this function returns different results based on the value of `verboseParam`, consider splitting it into two +export async function multiGetTransactionByTxid( + txids: string[], + verbose: T, + batchsize: number = 45, +): Promise> { txids = txids.filter(txid => !!txid); // failsafe: removing 'undefined' or other falsy stuff from txids array - batchsize = batchsize || 45; // this value is fine-tuned so althrough wallets in test suite will occasionally // throw 'response too large (over 1,000,000 bytes', test suite will pass if (!mainClient) throw new Error('Electrum client is not connected'); - const ret = {}; + const ret: MultiGetTransactionByTxidResult = {}; txids = [...new Set(txids)]; // deduplicate just for any case // lets try cache first: @@ -621,7 +781,7 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver const jsonString = realm.objectForPrimaryKey('Cache', txid + cacheKeySuffix); // search for a realm object with a primary key if (jsonString && jsonString.cache_value) { try { - ret[txid] = JSON.parse(jsonString.cache_value); + ret[txid] = JSON.parse(jsonString.cache_value as string); } catch (error) { console.log(error, 'cache failed to parse', jsonString.cache_value); } @@ -646,7 +806,7 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver // in case of ElectrumPersonalServer it might not track some transactions (like source transactions for our transactions) // so we wrap it in try-catch. note, when `Promise.all` fails we will get _zero_ results, but we have a fallback for that const promises = []; - const index2txid = {}; + const index2txid: Record = {}; for (let promiseIndex = 0; promiseIndex < chunk.length; promiseIndex++) { const txid = chunk[promiseIndex]; index2txid[promiseIndex] = txid; @@ -664,16 +824,16 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver const txid = index2txid[resultIndex]; results.push({ result: tx, param: txid }); } - } catch (_) { - if (String(_?.message ?? _).startsWith('verbose transactions are currently unsupported')) { + } catch (error: any) { + if (String(error?.message ?? error).startsWith('verbose transactions are currently unsupported')) { // electrs-esplora. cant use verbose, so fetching txs one by one and decoding locally for (const txid of chunk) { try { let tx = await mainClient.blockchainTransaction_get(txid, false); tx = txhexToElectrumTransaction(tx); results.push({ result: tx, param: txid }); - } catch (error) { - console.log(error); + } catch (err) { + console.log(err); } } } else { @@ -688,8 +848,8 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver tx = txhexToElectrumTransaction(tx); } results.push({ result: tx, param: txid }); - } catch (error) { - console.log(error); + } catch (err) { + console.log(err); } } } @@ -707,13 +867,17 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver txdata.result = txhexToElectrumTransaction(txdata.result); } ret[txdata.param] = txdata.result; + // @ts-ignore: hex property if (ret[txdata.param]) delete ret[txdata.param].hex; // compact } } // in bitcoin core 22.0.0+ they removed `.addresses` and replaced it with plain `.address`: - for (const txid of Object.keys(ret) ?? []) { - for (const vout of ret[txid]?.vout ?? []) { + for (const txid of Object.keys(ret)) { + const tx = ret[txid]; + if (typeof tx === 'string') continue; + for (const vout of tx?.vout ?? []) { + // @ts-ignore: address is not in type definition if (vout?.scriptPubKey?.address) vout.scriptPubKey.addresses = [vout.scriptPubKey.address]; } } @@ -721,9 +885,13 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver // saving cache: realm.write(() => { for (const txid of Object.keys(ret)) { - if (verbose && (!ret[txid].confirmations || ret[txid].confirmations < 7)) continue; + const tx = ret[txid]; // dont cache immature txs, but only for 'verbose', since its fully decoded tx jsons. non-verbose are just plain // strings txhex + if (verbose && typeof tx !== 'string' && (!tx.confirmations || tx.confirmations < 7)) { + continue; + } + realm.create( 'Cache', { @@ -736,21 +904,18 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver }); return ret; -}; +} /** * Simple waiter till `mainConnected` becomes true (which means * it Electrum was connected in other function), or timeout 30 sec. - * - * - * @returns {Promise | Promise<*>>} */ -module.exports.waitTillConnected = async function () { - let waitTillConnectedInterval = false; +export const waitTillConnected = async function (): Promise { + let waitTillConnectedInterval: NodeJS.Timeout | undefined; let retriesCounter = 0; if (await isDisabled()) { console.warn('Electrum connections disabled by user. waitTillConnected skipping...'); - return; + return false; } return new Promise(function (resolve, reject) { waitTillConnectedInterval = setInterval(() => { @@ -778,7 +943,7 @@ module.exports.waitTillConnected = async function () { // Returns the value at a given percentile in a sorted numeric array. // "Linear interpolation between closest ranks" method -function percentile(arr, p) { +function percentile(arr: number[], p: number) { if (arr.length === 0) return 0; if (typeof p !== 'number') throw new TypeError('p must be a number'); if (p <= 0) return arr[0]; @@ -796,12 +961,8 @@ function percentile(arr, p) { /** * The histogram is an array of [fee, vsize] pairs, where vsizen is the cumulative virtual size of mempool transactions * with a fee rate in the interval [feen-1, feen], and feen-1 > feen. - * - * @param numberOfBlocks {Number} - * @param feeHistorgram {Array} - * @returns {number} */ -module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHistorgram) { +export const calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks: number, feeHistorgram: number[][]) { // first, transforming histogram: let totalVsize = 0; const histogramToUse = []; @@ -821,7 +982,7 @@ module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHi // now we have histogram of precisely size for numberOfBlocks. // lets spread it into flat array so its easier to calculate percentile: - let histogramFlat = []; + let histogramFlat: number[] = []; for (const hh of histogramToUse) { histogramFlat = histogramFlat.concat(Array(Math.round(hh.vsize / 25000)).fill(hh.fee)); // division is needed so resulting flat array is not too huge @@ -834,7 +995,7 @@ module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHi return Math.round(percentile(histogramFlat, 0.5) || 1); }; -module.exports.estimateFees = async function () { +export const estimateFees = async function (): Promise<{ fast: number; medium: number; slow: number }> { let histogram; let timeoutId; try { @@ -847,9 +1008,9 @@ module.exports.estimateFees = async function () { } // fetching what electrum (which uses bitcoin core) thinks about fees: - const _fast = await module.exports.estimateFee(1); - const _medium = await module.exports.estimateFee(18); - const _slow = await module.exports.estimateFee(144); + const _fast = await estimateFee(1); + const _medium = await estimateFee(18); + const _slow = await estimateFee(144); /** * sanity check, see @@ -859,7 +1020,7 @@ module.exports.estimateFees = async function () { if (!histogram || histogram?.[0]?.[0] > 1000) return { fast: _fast, medium: _medium, slow: _slow }; // calculating fast fees from mempool: - const fast = Math.max(2, module.exports.calcEstimateFeeFromFeeHistorgam(1, histogram)); + const fast = Math.max(2, calcEstimateFeeFromFeeHistorgam(1, histogram)); // recalculating medium and slow fees using bitcoincore estimations only like relative weights: // (minimum 1 sat, just for any case) const medium = Math.max(1, Math.round((fast * _medium) / _fast)); @@ -873,7 +1034,7 @@ module.exports.estimateFees = async function () { * @param numberOfBlocks {number} The number of blocks to target for confirmation * @returns {Promise} Satoshis per byte */ -module.exports.estimateFee = async function (numberOfBlocks) { +export const estimateFee = async function (numberOfBlocks: number): Promise { if (!mainClient) throw new Error('Electrum client is not connected'); numberOfBlocks = numberOfBlocks || 1; const coinUnitsPerKilobyte = await mainClient.blockchainEstimatefee(numberOfBlocks); @@ -881,31 +1042,31 @@ module.exports.estimateFee = async function (numberOfBlocks) { return Math.round(new BigNumber(coinUnitsPerKilobyte).dividedBy(1024).multipliedBy(100000000).toNumber()); }; -module.exports.serverFeatures = async function () { +export const serverFeatures = async function () { if (!mainClient) throw new Error('Electrum client is not connected'); return mainClient.server_features(); }; -module.exports.broadcast = async function (hex) { +export const broadcast = async function (hex: string) { if (!mainClient) throw new Error('Electrum client is not connected'); try { - const broadcast = await mainClient.blockchainTransaction_broadcast(hex); - return broadcast; + const res = await mainClient.blockchainTransaction_broadcast(hex); + return res; } catch (error) { return error; } }; -module.exports.broadcastV2 = async function (hex) { +export const broadcastV2 = async function (hex: string): Promise { if (!mainClient) throw new Error('Electrum client is not connected'); return mainClient.blockchainTransaction_broadcast(hex); }; -module.exports.estimateCurrentBlockheight = function () { - if (latestBlockheight) { - const timeDiff = Math.floor(+new Date() / 1000) - latestBlockheightTimestamp; +export const estimateCurrentBlockheight = function (): number { + if (latestBlock.height) { + const timeDiff = Math.floor(+new Date() / 1000) - latestBlock.time; const extraBlocks = Math.floor(timeDiff / (9.93 * 60)); - return latestBlockheight + extraBlocks; + return latestBlock.height + extraBlocks; } const baseTs = 1587570465609; // uS @@ -913,14 +1074,9 @@ module.exports.estimateCurrentBlockheight = function () { return Math.floor(baseHeight + (+new Date() - baseTs) / 1000 / 60 / 9.93); }; -/** - * - * @param height - * @returns {number} Timestamp in seconds - */ -module.exports.calculateBlockTime = function (height) { - if (latestBlockheight) { - return Math.floor(latestBlockheightTimestamp + (height - latestBlockheight) * 9.93 * 60); +export const calculateBlockTime = function (height: number): number { + if (latestBlock.height) { + return Math.floor(latestBlock.time + (height - latestBlock.height) * 9.93 * 60); } const baseTs = 1585837504; // sec @@ -929,17 +1085,13 @@ module.exports.calculateBlockTime = function (height) { }; /** - * - * @param host - * @param tcpPort - * @param sslPort * @returns {Promise} Whether provided host:port is a valid electrum server */ -module.exports.testConnection = async function (host, tcpPort, sslPort) { +export const testConnection = async function (host: string, tcpPort?: number, sslPort?: number): Promise { const client = new ElectrumClient(net, tls, sslPort || tcpPort, host, sslPort ? 'tls' : 'tcp'); client.onError = () => {}; // mute - let timeoutId = false; + let timeoutId: NodeJS.Timeout | undefined; try { const rez = await Promise.race([ new Promise(resolve => { @@ -961,27 +1113,19 @@ module.exports.testConnection = async function (host, tcpPort, sslPort) { return false; }; -module.exports.forceDisconnect = () => { +export const forceDisconnect = (): void => { mainClient.close(); }; -module.exports.setBatchingDisabled = () => { +export const setBatchingDisabled = () => { disableBatching = true; }; -module.exports.setBatchingEnabled = () => { +export const setBatchingEnabled = () => { disableBatching = false; }; -module.exports.connectMain = connectMain; -module.exports.isDisabled = isDisabled; -module.exports.setDisabled = setDisabled; -module.exports.hardcodedPeers = hardcodedPeers; -module.exports.ELECTRUM_HOST = ELECTRUM_HOST; -module.exports.ELECTRUM_TCP_PORT = ELECTRUM_TCP_PORT; -module.exports.ELECTRUM_SSL_PORT = ELECTRUM_SSL_PORT; -module.exports.ELECTRUM_SERVER_HISTORY = ELECTRUM_SERVER_HISTORY; -const splitIntoChunks = function (arr, chunkSize) { +const splitIntoChunks = function (arr: any[], chunkSize: number) { const groups = []; let i; for (i = 0; i < arr.length; i += chunkSize) { @@ -990,97 +1134,13 @@ const splitIntoChunks = function (arr, chunkSize) { return groups; }; -const semVerToInt = function (semver) { +const semVerToInt = function (semver: string): number { if (!semver) return 0; if (semver.split('.').length !== 3) return 0; - const ret = semver.split('.')[0] * 1000000 + semver.split('.')[1] * 1000 + semver.split('.')[2] * 1; + const ret = Number(semver.split('.')[0]) * 1000000 + Number(semver.split('.')[1]) * 1000 + Number(semver.split('.')[2]) * 1; if (isNaN(ret)) return 0; return ret; }; - -function txhexToElectrumTransaction(txhex) { - const tx = bitcoin.Transaction.fromHex(txhex); - - const ret = { - txid: tx.getId(), - hash: tx.getId(), - version: tx.version, - size: Math.ceil(txhex.length / 2), - vsize: tx.virtualSize(), - weight: tx.weight(), - locktime: tx.locktime, - vin: [], - vout: [], - hex: txhex, - blockhash: '', - confirmations: 0, - time: 0, - blocktime: 0, - }; - - if (txhashHeightCache[ret.txid]) { - // got blockheight where this tx was confirmed - ret.confirmations = module.exports.estimateCurrentBlockheight() - txhashHeightCache[ret.txid]; - if (ret.confirmations < 0) { - // ugly fix for when estimator lags behind - ret.confirmations = 1; - } - ret.time = module.exports.calculateBlockTime(txhashHeightCache[ret.txid]); - ret.blocktime = module.exports.calculateBlockTime(txhashHeightCache[ret.txid]); - } - - for (const inn of tx.ins) { - const txinwitness = []; - if (inn.witness[0]) txinwitness.push(inn.witness[0].toString('hex')); - if (inn.witness[1]) txinwitness.push(inn.witness[1].toString('hex')); - - ret.vin.push({ - txid: Buffer.from(inn.hash).reverse().toString('hex'), - vout: inn.index, - scriptSig: { hex: inn.script.toString('hex'), asm: '' }, - txinwitness, - sequence: inn.sequence, - }); - } - - let n = 0; - for (const out of tx.outs) { - const value = new BigNumber(out.value).dividedBy(100000000).toNumber(); - let address = false; - let type = false; - - if (SegwitBech32Wallet.scriptPubKeyToAddress(out.script.toString('hex'))) { - address = SegwitBech32Wallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = 'witness_v0_keyhash'; - } else if (SegwitP2SHWallet.scriptPubKeyToAddress(out.script.toString('hex'))) { - address = SegwitP2SHWallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = '???'; // TODO - } else if (LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex'))) { - address = LegacyWallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = '???'; // TODO - } else { - address = TaprootWallet.scriptPubKeyToAddress(out.script.toString('hex')); - type = 'witness_v1_taproot'; - } - - ret.vout.push({ - value, - n, - scriptPubKey: { - asm: '', - hex: out.script.toString('hex'), - reqSigs: 1, // todo - type, - addresses: [address], - }, - }); - n++; - } - return ret; -} - -// exported only to be used in unit tests -module.exports.txhexToElectrumTransaction = txhexToElectrumTransaction; diff --git a/blue_modules/WidgetCommunication.js b/blue_modules/WidgetCommunication.js deleted file mode 100644 index 05cba00d8..000000000 --- a/blue_modules/WidgetCommunication.js +++ /dev/null @@ -1,8 +0,0 @@ -function WidgetCommunication(props) { - WidgetCommunication.isBalanceDisplayAllowed = () => {}; - WidgetCommunication.setBalanceDisplayAllowed = () => {}; - WidgetCommunication.reloadAllTimelines = () => {}; - return null; -} - -export default WidgetCommunication; diff --git a/blue_modules/WidgetCommunication.ts b/blue_modules/WidgetCommunication.ts new file mode 100644 index 000000000..f93d9c129 --- /dev/null +++ b/blue_modules/WidgetCommunication.ts @@ -0,0 +1,9 @@ +function WidgetCommunication() { + return null; +} + +WidgetCommunication.isBalanceDisplayAllowed = () => {}; +WidgetCommunication.setBalanceDisplayAllowed = () => {}; +WidgetCommunication.reloadAllTimelines = () => {}; + +export default WidgetCommunication; diff --git a/blue_modules/storage-context.tsx b/blue_modules/storage-context.tsx index ef2be1d9a..cbc73012d 100644 --- a/blue_modules/storage-context.tsx +++ b/blue_modules/storage-context.tsx @@ -8,12 +8,11 @@ import type { TWallet } from '../class/wallets/types'; import presentAlert from '../components/Alert'; import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc'; import { FiatUnit, TFiatUnit } from '../models/fiatUnit'; +import * as BlueElectrum from './BlueElectrum'; import { PREFERRED_CURRENCY_STORAGE_KEY } from './currency'; import triggerHapticFeedback, { HapticFeedbackTypes } from './hapticFeedback'; import A from '../blue_modules/analytics'; -const BlueElectrum = require('./BlueElectrum'); - // hashmap of timestamps we _started_ refetching some wallet const _lastTimeTriedToRefetchWallet: { [walletID: string]: number } = {}; diff --git a/class/hd-segwit-bech32-transaction.js b/class/hd-segwit-bech32-transaction.js index 21936fe53..0788b7488 100644 --- a/class/hd-segwit-bech32-transaction.js +++ b/class/hd-segwit-bech32-transaction.js @@ -1,7 +1,7 @@ import { HDSegwitBech32Wallet } from './wallets/hd-segwit-bech32-wallet'; import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; +import * as BlueElectrum from '../blue_modules/BlueElectrum'; const bitcoin = require('bitcoinjs-lib'); -const BlueElectrum = require('../blue_modules/BlueElectrum'); const BigNumber = require('bignumber.js'); /** @@ -39,7 +39,7 @@ export class HDSegwitBech32Transaction { * @private */ async _fetchTxhexAndDecode() { - const hexes = await BlueElectrum.multiGetTransactionByTxid([this._txid], 10, false); + const hexes = await BlueElectrum.multiGetTransactionByTxid([this._txid], false, 10); this._txhex = hexes[this._txid]; if (!this._txhex) throw new Error("Transaction can't be found in mempool"); this._txDecoded = bitcoin.Transaction.fromHex(this._txhex); @@ -80,7 +80,7 @@ export class HDSegwitBech32Transaction { * @private */ async _fetchRemoteTx() { - const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded.getId()]); + const result = await BlueElectrum.multiGetTransactionByTxid([this._txid || this._txDecoded.getId()], true); this._remoteTx = Object.values(result)[0]; } @@ -154,7 +154,7 @@ export class HDSegwitBech32Transaction { prevInputs.push(reversedHash); } - const prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs); + const prevTransactions = await BlueElectrum.multiGetTransactionByTxid(prevInputs, true); // fetched, now lets count how much satoshis went in let wentIn = 0; diff --git a/class/wallets/abstract-hd-electrum-wallet.ts b/class/wallets/abstract-hd-electrum-wallet.ts index b71e90e86..a957caa13 100644 --- a/class/wallets/abstract-hd-electrum-wallet.ts +++ b/class/wallets/abstract-hd-electrum-wallet.ts @@ -10,7 +10,7 @@ import { CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; import { ECPairFactory } from 'ecpair'; import { ECPairInterface } from 'ecpair/src/ecpair'; -import type BlueElectrumNs from '../../blue_modules/BlueElectrum'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import { ElectrumHistory } from '../../blue_modules/BlueElectrum'; import ecc from '../../blue_modules/noble_ecc'; import { randomBytes } from '../rng'; @@ -18,7 +18,6 @@ import { AbstractHDWallet } from './abstract-hd-wallet'; import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types'; const ECPair = ECPairFactory(ecc); -const BlueElectrum: typeof BlueElectrumNs = require('../../blue_modules/BlueElectrum'); const bip32 = BIP32Factory(ecc); const bip47 = BIP47Factory(ecc); @@ -319,7 +318,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } // next, batch fetching each txid we got - const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs)); + const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs), true); // now, tricky part. we collect all transactions from inputs (vin), and batch fetch them too. // then we combine all this data (we need inputs to see source addresses and amounts) @@ -330,7 +329,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { // ^^^^ not all inputs have txid, some of them are Coinbase (newly-created coins) } } - const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids); + const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids, true); // fetched all transactions from our inputs. now we need to combine it. // iterating all _our_ transactions: @@ -1505,7 +1504,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { const histories = await BlueElectrum.multiGetHistoryByAddress([address]); const txHashes = histories[address].map(({ tx_hash }) => tx_hash); - const txHexs = await BlueElectrum.multiGetTransactionByTxid(txHashes, 50, false); + const txHexs = await BlueElectrum.multiGetTransactionByTxid(txHashes, false); for (const txHex of Object.values(txHexs)) { try { const paymentCode = bip47_instance.getPaymentCodeFromRawNotificationTransaction(txHex); diff --git a/class/wallets/abstract-hd-wallet.ts b/class/wallets/abstract-hd-wallet.ts index 353f45aed..e94512199 100644 --- a/class/wallets/abstract-hd-wallet.ts +++ b/class/wallets/abstract-hd-wallet.ts @@ -1,8 +1,8 @@ -import { LegacyWallet } from './legacy-wallet'; -import * as bip39 from 'bip39'; import { BIP32Interface } from 'bip32'; +import * as bip39 from 'bip39'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import * as bip39custom from '../../blue_modules/bip39'; -import BlueElectrum from '../../blue_modules/BlueElectrum'; +import { LegacyWallet } from './legacy-wallet'; import { Transaction } from './types'; type AbstractHDWalletStatics = { diff --git a/class/wallets/hd-legacy-breadwallet-wallet.ts b/class/wallets/hd-legacy-breadwallet-wallet.ts index db685503c..e22572a8f 100644 --- a/class/wallets/hd-legacy-breadwallet-wallet.ts +++ b/class/wallets/hd-legacy-breadwallet-wallet.ts @@ -2,7 +2,8 @@ import BIP32Factory, { BIP32Interface } from 'bip32'; import * as bitcoinjs from 'bitcoinjs-lib'; import { Psbt } from 'bitcoinjs-lib'; import { CoinSelectReturnInput } from 'coinselect'; -import BlueElectrum, { ElectrumHistory } from '../../blue_modules/BlueElectrum'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; +import { ElectrumHistory } from '../../blue_modules/BlueElectrum'; import ecc from '../../blue_modules/noble_ecc'; import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet'; diff --git a/class/wallets/hd-legacy-p2pkh-wallet.ts b/class/wallets/hd-legacy-p2pkh-wallet.ts index 4d6ff8e37..db49c3403 100644 --- a/class/wallets/hd-legacy-p2pkh-wallet.ts +++ b/class/wallets/hd-legacy-p2pkh-wallet.ts @@ -1,10 +1,11 @@ import BIP32Factory, { BIP32Interface } from 'bip32'; import { Psbt } from 'bitcoinjs-lib'; import { CoinSelectReturnInput } from 'coinselect'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import ecc from '../../blue_modules/noble_ecc'; import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; + const bip32 = BIP32Factory(ecc); -const BlueElectrum = require('../../blue_modules/BlueElectrum'); /** * HD Wallet (BIP39). @@ -70,7 +71,6 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { // now we need to fetch txhash for each input as required by PSBT const txhexes = await BlueElectrum.multiGetTransactionByTxid( this.getUtxo().map(x => x.txid), - 50, false, ); diff --git a/class/wallets/legacy-wallet.ts b/class/wallets/legacy-wallet.ts index 940256538..debb30b0d 100644 --- a/class/wallets/legacy-wallet.ts +++ b/class/wallets/legacy-wallet.ts @@ -140,7 +140,6 @@ export class LegacyWallet extends AbstractWallet { if (LegacyWallet.type !== this.type) return; // but only for LEGACY single-address wallets const txhexes = await BlueElectrum.multiGetTransactionByTxid( this._utxo.map(u => u.txid), - 50, false, ); @@ -275,7 +274,7 @@ export class LegacyWallet extends AbstractWallet { // is safe because in that case our cache is filled // next, batch fetching each txid we got - const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs)); + const txdatas = await BlueElectrum.multiGetTransactionByTxid(Object.keys(txs), true); const transactions = Object.values(txdatas); // now, tricky part. we collect all transactions from inputs (vin), and batch fetch them too. @@ -287,7 +286,7 @@ export class LegacyWallet extends AbstractWallet { // ^^^^ not all inputs have txid, some of them are Coinbase (newly-created coins) } } - const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids); + const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids, true); // fetched all transactions from our inputs. now we need to combine it. // iterating all _our_ transactions: diff --git a/class/wallets/multisig-hd-wallet.ts b/class/wallets/multisig-hd-wallet.ts index 1a6c2ba2b..e040242c5 100644 --- a/class/wallets/multisig-hd-wallet.ts +++ b/class/wallets/multisig-hd-wallet.ts @@ -3,17 +3,17 @@ import * as bip39 from 'bip39'; import * as bitcoin from 'bitcoinjs-lib'; import { Psbt, Transaction } from 'bitcoinjs-lib'; import b58 from 'bs58check'; +import { CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; import createHash from 'create-hash'; import { ECPairFactory } from 'ecpair'; import * as mn from 'electrum-mnemonic'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import ecc from '../../blue_modules/noble_ecc'; import { decodeUR } from '../../blue_modules/ur'; import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import { CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; import { CreateTransactionResult, CreateTransactionUtxo } from './types'; const ECPair = ECPairFactory(ecc); -const BlueElectrum = require('../../blue_modules/BlueElectrum'); const bip32 = BIP32Factory(ecc); type SeedOpts = { @@ -1072,7 +1072,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { // now we need to fetch txhash for each input as required by PSBT const txhexes = await BlueElectrum.multiGetTransactionByTxid( this.getUtxo(true).map(x => x.txid), - 50, false, ); diff --git a/models/networkTransactionFees.ts b/models/networkTransactionFees.ts index 18a5bef4f..c7325ec2d 100644 --- a/models/networkTransactionFees.ts +++ b/models/networkTransactionFees.ts @@ -1,4 +1,4 @@ -const BlueElectrum = require('../blue_modules/BlueElectrum'); +import * as BlueElectrum from '../blue_modules/BlueElectrum'; export const NetworkTransactionFeeType = Object.freeze({ FAST: 'Fast', diff --git a/screen/receive/details.js b/screen/receive/details.js index 560bf41b9..14cfb61a5 100644 --- a/screen/receive/details.js +++ b/screen/receive/details.js @@ -117,7 +117,7 @@ const ReceiveDetails = () => { const txs = await BlueElectrum.getMempoolTransactionsByAddress(addressToUse); const tx = txs.pop(); if (tx) { - const rez = await BlueElectrum.multiGetTransactionByTxid([tx.tx_hash], 10, true); + const rez = await BlueElectrum.multiGetTransactionByTxid([tx.tx_hash], true, 10); if (rez && rez[tx.tx_hash] && rez[tx.tx_hash].vsize) { const satPerVbyte = Math.round(tx.fee / rez[tx.tx_hash].vsize); const fees = await BlueElectrum.estimateFees(); diff --git a/screen/selftest.js b/screen/selftest.js index 7c21d3632..f38486647 100644 --- a/screen/selftest.js +++ b/screen/selftest.js @@ -24,9 +24,10 @@ import presentAlert from '../components/Alert'; import * as encryption from '../blue_modules/encryption'; import * as fs from '../blue_modules/fs'; import SaveFileButton from '../components/SaveFileButton'; +import * as BlueElectrum from '../blue_modules/BlueElectrum'; const BlueCrypto = require('react-native-blue-crypto'); -const BlueElectrum = require('../blue_modules/BlueElectrum'); + const bip32 = BIP32Factory(ecc); const styles = StyleSheet.create({ diff --git a/screen/send/broadcast.js b/screen/send/broadcast.js index c641d17dd..781ef3b96 100644 --- a/screen/send/broadcast.js +++ b/screen/send/broadcast.js @@ -16,7 +16,7 @@ import { BlueSpacing20, BlueTextCentered, } from '../../BlueComponents'; -import BlueElectrum from '../../blue_modules/BlueElectrum'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import Notifications from '../../blue_modules/notifications'; import { useTheme } from '../../components/themes'; import Button from '../../components/Button'; diff --git a/screen/send/confirm.js b/screen/send/confirm.js index a4641496f..d217a272a 100644 --- a/screen/send/confirm.js +++ b/screen/send/confirm.js @@ -18,7 +18,7 @@ import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import SafeArea from '../../components/SafeArea'; import { satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency'; -const BlueElectrum = require('../../blue_modules/BlueElectrum'); +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; const Bignumber = require('bignumber.js'); const bitcoin = require('bitcoinjs-lib'); diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index 96aee2506..159b76679 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -5,6 +5,7 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Linking, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; import DocumentPicker from 'react-native-document-picker'; import RNFS from 'react-native-fs'; + import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import Notifications from '../../blue_modules/notifications'; @@ -20,8 +21,7 @@ import { useTheme } from '../../components/themes'; import { requestCameraAuthorization } from '../../helpers/scan-qr'; import loc from '../../loc'; import SaveFileButton from '../../components/SaveFileButton'; - -const BlueElectrum = require('../../blue_modules/BlueElectrum'); +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; const PsbtWithHardwareWallet = () => { const { txMetadata, fetchAndSaveWalletTransactions, isElectrumDisabled } = useContext(BlueStorageContext); diff --git a/screen/settings/electrumSettings.js b/screen/settings/electrumSettings.js index a2e7728e7..e7441e7b7 100644 --- a/screen/settings/electrumSettings.js +++ b/screen/settings/electrumSettings.js @@ -36,8 +36,7 @@ import { requestCameraAuthorization } from '../../helpers/scan-qr'; import Button from '../../components/Button'; import ListItem from '../../components/ListItem'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; - -const BlueElectrum = require('../../blue_modules/BlueElectrum'); +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; export default class ElectrumSettings extends Component { static contextType = BlueStorageContext; diff --git a/screen/transactions/CPFP.js b/screen/transactions/CPFP.js index 02f492114..e55ab2d64 100644 --- a/screen/transactions/CPFP.js +++ b/screen/transactions/CPFP.js @@ -25,7 +25,7 @@ import presentAlert from '../../components/Alert'; import Button from '../../components/Button'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import SafeArea from '../../components/SafeArea'; -const BlueElectrum = require('../../blue_modules/BlueElectrum'); +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; const styles = StyleSheet.create({ root: { diff --git a/screen/transactions/transactionStatus.js b/screen/transactions/transactionStatus.js index 817f8e533..41c61d30a 100644 --- a/screen/transactions/transactionStatus.js +++ b/screen/transactions/transactionStatus.js @@ -114,7 +114,7 @@ const TransactionsStatus = () => { setIntervalMs(31000); // upon first execution we increase poll interval; console.log('checking tx', hash, 'for confirmations...'); - const transactions = await BlueElectrum.multiGetTransactionByTxid([hash], 10, true); + const transactions = await BlueElectrum.multiGetTransactionByTxid([hash], true, 10); const txFromElectrum = transactions[hash]; console.log('got txFromElectrum=', txFromElectrum); diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index fc50b110b..b419b89b5 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -8,6 +8,7 @@ import { FlatList, I18nManager, InteractionManager, + LayoutAnimation, PixelRatio, ScrollView, StyleSheet, @@ -15,16 +16,17 @@ import { TouchableOpacity, View, findNodeHandle, - LayoutAnimation, } from 'react-native'; import { Icon } from 'react-native-elements'; +import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import BlueClipboard from '../../blue_modules/clipboard'; import { isDesktop } from '../../blue_modules/environment'; import * as fs from '../../blue_modules/fs'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { BlueStorageContext, WalletTransactionsStatus } from '../../blue_modules/storage-context'; import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class'; +import Biometric from '../../class/biometrics'; import WalletGradient from '../../class/wallet-gradient'; import presentAlert from '../../components/Alert'; import { FButton, FContainer } from '../../components/FloatButtons'; @@ -39,9 +41,6 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import loc from '../../loc'; import { Chain } from '../../models/bitcoinUnits'; import ActionSheet from '../ActionSheet'; -import Biometric from '../../class/biometrics'; - -const BlueElectrum = require('../../blue_modules/BlueElectrum'); const buttonFontSize = PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22 diff --git a/tests/integration/BlueElectrum.test.js b/tests/integration/BlueElectrum.test.js index 43c489e97..d6417e2bb 100644 --- a/tests/integration/BlueElectrum.test.js +++ b/tests/integration/BlueElectrum.test.js @@ -257,6 +257,7 @@ describe('BlueElectrum', () => { '5e2fa84148a7389537434b3ad12fcae71ed43ce5fb0f016a7f154a9b99a973df', '5e2fa84148a7389537434b3ad12fcae71ed43ce5fb0f016a7f154a9b99a973df', // duplicate intended ], + true, 3, ); @@ -284,7 +285,7 @@ describe('BlueElectrum', () => { it('multiGetTransactionByTxid() can work with big batches', async () => { // eslint-disable-next-line prettier/prettier const vinTxids = ["fb083ca5fb98451314836bbf31d08bf658deddcc8947d76eefe44b5aa49f3a48","5f0f4ac816af5717f6e049454ef698c5d46b2303b1622a17b42eed0d914ea412","8f58af45a9375ed2516237e6910186f4c9c5655a873396e71721057bdd68b3f2","e3cca3df55a880adbe639955c68772d2c491b36e28368380241ac096d9967095","5aef653da43618151ea24ed13b26e67415a397212a60dbb3609a67ba1c01373b","b67c9ce40020584b82085f4093d066c2759542d9aa612bd1cc2e561548e2f9bb","28f29df4660afa58dd6e0f9c31d3e3c204b575029c4b429dbab7da834748f097","147aa266b25fb24bfd7f2e66a1d8eeaa5086c35922c59a36a7a7c4e6a7805d5d","40f00b01d2173c46fc1b4f462132f183beb368bd43df7b88869a8bb7a8bb3b5f","b67c9ce40020584b82085f4093d066c2759542d9aa612bd1cc2e561548e2f9bb","147aa266b25fb24bfd7f2e66a1d8eeaa5086c35922c59a36a7a7c4e6a7805d5d","ba4a6c71c3b3d738609526693bdf87ed5b441f3546d70a2a83128cf6645c10dd","b1b7410fb84def2a3fa0ba54e587e3ce373c4f6e1972b5a7044c804e53101131","a4a0790f7d00ad7da97e349062cff61da12bc2b1ee06223a41d4960621ad6781","b1b7410fb84def2a3fa0ba54e587e3ce373c4f6e1972b5a7044c804e53101131","15e499177b871ac205bf509e1abd81c29a72475661957ea82db69f4afde6cc3f","6702bf9dc679c9086d0cb3f43c10d48bb200e1b29d73b4b4a399bd3eae31f4d0","51c07c5c882b49931d18adf2f62afed25db53ef4264b1f3144152b6c4dfeda1c","38eba464479c79a230e0256b7e9cc9b9cc8539b9a7297bc9961046df29d63b4a","5d29eef47330797e9b37cf0d57e48a871a6e249a0102e4744ec9d30613b054b8","3af7c1fa064a2c74e0d34972895daba4b78f32eae596774bb4bb35d44cf069c1","cc5485827920ea5de1375c9806a1b6f7db1f87f2e4074481778d1e44183b3cf6","38eba464479c79a230e0256b7e9cc9b9cc8539b9a7297bc9961046df29d63b4a","217b99f47c1a05076b68e98cf65b08df8aa485c28c74e43a0ab5a4aa98a89fa0","fb2b8dcb2997c331bd8d9f4d611293874bde116184ef38dfd5a0d5745f9d475d","2ef35d16748d56d4ec19fb458bc0147e85dd1d78e25f71f0ddd71ba22f0f49ec","05b4009d2ec7c6472a10d652efdbd7e4fa72a2e2448287e386c4d68864ba75af","cd801bc012c361fd6be0bc5b440c71d656d2fa14af90af1f469fc91ca3be0967","24807d3563342275bee7cc4b98dc2fadb9e39d074534e79c7921b46596988344","2b29aaa43d344393769f38e1d3dc0ba1d89b2f9868311ff9ec9eb44183d21250","b07747a598d4f1f0beae08332568292104f4360929057396c5637c1900e4f3ff","f13a237529bfd21edad34678f1da8ebd4606422a85b5f92ee270f3808c286e44","20264987b20aa1d6975cf9bad62855c3f8c1492e38ea7a73963eb355bd18b77f","65e0a29033eb3cea6b4c9eaa7448754e468585cf1877e0b228281138421f30cb","133874b546cdc715439d1d1dba865419a9640ee740f8a72191d1221ac29d066b","b056e86b160b08d3e944f1d61368d3523e9c0820ebc8f755b98ac38eb3e307d1","4bb01e9b0388441442a04bb36fe932c3e3da525c4c09413c90f0a6a74c57d280","2fc80afde86a45f5f113067f44433d44da28d6e17dd8dbe19f511bc371863a1c","e7650f7fa769c2b1aee41477278633a342017c81009feff8b6566b3f3664b6dc","01315e63a7b852f1dfd83bc7a405351492c076d88f2ae4adc3c32d3431fd5a35","7cb1e59903fb72e4f618ba7ab078765e9117d8aca2421268fe24b41986df37ba","7fd27c38816b461eba58b547a524747b28eec2b58e2c8fd29fa9858de96265f5","38b95ee03adf62feb5188c1a8c5ac6334e95e0e21bba6b30e667b54f6ad7b728","8927bc71680f4a9f25af6f2c0c4e1956e0e703124ac725d842c64bba5e92a6e5","48d0b1d45f8e228d41f01f71e56618d0f2ce8263bbd9b59352760640eb2df66c","8f41920019edb0924b861daa6feaf2922d0ce0a9c45b777ba88f6e026cb9a6ea","7a4dad8678a23b608ce8acacd9a2f4c24929bc68037fb2991a43cc022e977b60","684daad794ebc87661f8250eb42621db03f6b3f31185e0f760ee3db73286e6c9","69080677f9b643e100e62a2edf12e2dbd529b047d584ff922e5458381761a05d","ccc4c5111cc02d243b67fc20fd93026d88add65c6fd2055602152227903c703c","242fc7936d4480e5fba4b9515aca0b97de121a9effcc1be26d4e455c7adef9f1","833db41cd8b9c20470bc3b5e70249d243aacfcf6b69593ac460a0029467689b9","5fd098cace35e4c0d08f10059d520bae1b2b074ac1059d2980a1be82a8f77f31","e445ff4d77e50cd5d04d2dd3c482677c5531c2198494768dacb624d4f7f7bfc5","c4e521e8f00cc3979bdd0e0c99970374fa1f92ff742a3e9fdf52c1e4bbcfd78d","a1d6296a9801ee538e0d27feb10f8be41b00b0fccc6c83fcb49e98f23c42dcd3","aa368285e0e05d9bc3f957b89b95260349684cc872b167f36a61982bf63c2201","f4fa04c284d1f3eb907f9e7f8adf6e1e832a21ed763d6d23857e45926f9ce10a","fb9e869cf8465c71c8f4a1756a6dbd8dcd1eca4246d8645b037ad0cc65e800f0","e059ad921e8a07dacd01727774bd17821768e61d3f7062f77131f517a9197e50","8736be2b2a646ce5a471e16baf9d11aadd818a71dfe790395b0b2809e115c1e6","4fe6b558e435fdad3843b2db5d14915501c147148dbeb4985e3e596feda17d99","e8318e5e612c6d48b4711e5414abc396ab3d7633a2529eaabe3a82d7c851ccb4","804b049e776174f3eded63d18185d769841f7abc5b0751109a5942862c939fdf","3d263a914bb8e82f1566490d2e8d20ab66542f223089cc4cce9b034930e775f1","1daf03c9dabfd10483a637b59f18d44b8e5fc8a4b2d44e72ffa610824f71879b","3bd025508217aeba5c4a616ea6c30969f252f58231087ce11714db7110b2005c","1a66088cb7d460aeb56c00dd069156adc5cef549648e5481ee5c5e589879b731","4ff1cc8b15a09062bed7a53124c3af351ceda82d439aa3606fe3ca277a20bc2a","ca2095f1eced85cb8e07f785b4e1b8c3f7d9e8ee60f87458c40bdfe731d09c30","d7f31a681b35a7012ced38f106f3b478e9a31f8d58fbf244c721ca1ba89cb394","4bd47e0c626359950fc09888e1a505dfe7286800d0ec0a21b49c66d8de32c901","1a66088cb7d460aeb56c00dd069156adc5cef549648e5481ee5c5e589879b731","3bd025508217aeba5c4a616ea6c30969f252f58231087ce11714db7110b2005c","19ebfbba4c51e79099c031e91a36621312a50c46d0f87d83183a71a0834afe3f","e8ebe53a94ee73fa803f0f144c8ac31d5075de45e2f798b383a6d88164d6113a","944569d8a887d238cf2d7c104ea3fe558bdea38e961b73a0378bfd7969e13157","a08897fe730d7de1f2d5376e1d6e9e500c6e20dce4bd9225c5694667464bf8eb","472a92ca21fe52d676dca31266943a87745bec8b04450b83e5ea29559253e902","3c86ca512b36c13218249dede37b9ed2813b368115926931c08df7dfb049822b","b49c0fce3665545d887872e927e76d7df0927e4baec27c7106181651611424ab","8881027cfa2be033b7a9724b5547b710f5ba8aef1becb3ed53d34d614a54e3bc","bb5ee6ed47f61fc2cc33c29540f1e778a9c9583083e2441c9b23439c5e46820f","cbd9c2e1782d72fae42254f615caba2cc28de5f222ba142ff2020c5499990cc4","52b39e96c6672ecc70554cea0a10c57ad4636526a8f51477d2108bdda78d30e8","c2c4cb3164f6576cf559ab0e7180ad3d1f5ba27156727085ddf3ec36b4fccf33","af940582b1416ab699abc542994f2f8da43c0af0c5518e57c2087eb33dc0503d","d7eace81b9bb79318113d2f30f9d18d90d78f0dffaee7f3bacbcc343ee0101cd","aaf156826bb506f93fc4aaee3bed00172819f02a06ecd5a446afce5321f93ba3","ca2e3b7dde57407212bc4017621f6433908dc2f8b725d365fc7109c7b8067097","d6fdbf77be6e2979543798f41b6cf36fca557c17d0a9d367cc52701770eb2d46","1af6c454434e19d802f83f600982e083de6b4f925c90938783c55236174640b2","66a5abcb3210724cc93b9e4eb537ef9f28e987f2915f00620eb887a419521125","91df6caa3a703fbaebaf35a3ed61d7b80eae59e0daf3f4443c41e2ed69d8cadf","cf62992c60c36cd327652e93bfb0fb7fa14d1f80195616907128ca7fd2b8b24c","1cf9269f1c03276f21bd0f939ee83f9319df7516f9efb06cdbc8ad4f2e985d15","39e051909a54e187a21500c3ab48966058414320a881dbb90517ef770e18b553","696d6bab49f36a42c10486bb4db41f6c7ab89e1f615c221a3c8115b9813246d6","ce01648dfa0c14f86836404e745a9d071c992675d8899b837233a1ccf6e8d9d1","6e60a751d87946c28cbbd6d19115a036edc083b61d981efe513327bbe4e035cf","c73a7a623ffe18351b51942f10371950a5afc5b967ad8e831fc1b4339078b004","2ababd951785b88b19c609338df9bf5f3b25f33d85196410533dc02f6e3d2e60","4edfa2ade6895b198d53dfe5070148c920cc55e810ab4b7c109192d045289669","be8e83fa02962e432fea91f9d49fade6a781c8e85d277ac4f90ab73d7ec1c75e","297256d3db749c28489984d33e3a44118068c71dee801f6c8f7e61dec30e7103","7df23870105909c9c390e97527ea9a37f5fc4dab7a2d11b5bce48ac97e81fcb5","5fcef9d6d418f7a47bbbff082d97b4cf789403df2a0eb75f1ae18b873362f8fb","b3ca2274cc0a5bd844df396ee075344a210fa95b50738d6f8938a76b865fe874","fd2606fc511fa58038c9e9511f3db2d0163ee4615b1c58af53ad1c419b85a7b7","75386c3230b6eab0dd67207ade52679d6fb3c8f9003bca870e0bde8980971146","35c7d91dfcecac4ea844a26f4650e0c7455722359d2299f265ab609da6a7e885","7d67395f69c72c319b991b8c0a8c550cdc7124aa73b86edb5dc78125c8cb2b06","45c7b3879a07324548d034d5698166f8e0fcfcfdf596c18640fe3f7636913bdb","a069791dbb92acabe3ade5dcc6f88f4310abcd6e77927764c71db4c850190542","60c8f1368add0de87f0849d0e93a92d707fecd6f476a850f242dd2c283e05fcc","8d32423d43a5a22332c312975ed03e1e8131dc35c846a16a7cfc3abc255b8eb5","9717ce2d17c080a6bbc635ec15295d0b6567442f8f6e247e56d61a35fed8f574","618d565513599b0cfcee5ab01f6ef86aabaf11615eafd56f66eaba29df6073ca","e53cde1fccab2a904f542a9cad704843a71964be0223113d8c229a706b1d3120","ced6089c414f9deceb70228e42ad0281ec8b13ef4f3a8b3952b6417f9f69346e","b1cb1f5a8b04e3cd5d4e736ed3c703dac8db6376bfa74f4ca5ed0264ffcb6992","326b5964ab27e31c3952520bae0e06c66daf576e3a70faca2a891228831cc3ee","40a0b058213a2e3540c9fc3a5dcbd1eb5bd876c9748f81bb12dc89a944e45a29","12bc66fa8c86611d2785bec80faad4b880cd48bc4ff30f337022d9e8675e30c4","3a7ef749c28ab552daa4fb7e54c3980e86aefa92506caded4feedb4bb70a03c9","4ed789a4d9adc6777c585653267b1d22292d992adfb5224faa1115a1c2b3734c","dc0de14bc6290725ef57d2a54a4044dbb83b425af4a94d609ed7ef516d4d03b1","bc83071647ec91f147f83635bcf99faf8f3431b0af41094ad9a574f117618adc","adce7a0971cd0a515e31e761f4f76113347548d792e0d14cedd683c9221a006a","8fbc8775ab4a90aabc860679fa9ec02be810d1076b607f0f0573baa37ca759fe","ad24ecdfbacb2e8f49425aec400f028e3cc26fb2f0bb7e1dda3ca9ccd98b2c65","01864c0c6901c8e0214987703d75b6f7248bde19964d1c3460dcb8b447296c52","91bcfb9559bb4ea9890c403cc8d0420b4312b4f1f233838636b1f1cf0d03cca1","74ea7961e345714f014bea53111b23952f5309f318af4c373388308971c481ab","1a0df705f0bdf61d9474d949395fbed08f9ffa73668b1201ff6eb4be6ecc94a8","d65d0447197ac25105c8b6fed1675be1940e595eed94aa552140d0027c3a14ae","52d1fcdb4899521daf0b36fd46e0daf8a0e3d1fcd80246f8ac6bd9b92e1bf814","fc26e159b9ba609a0c3abb098f800ddfdc124f0150f6bcd6ef28a73edcfc46b1","fa04269ada73e905ae9b70e0b348c04e980ce0c9a5f92813642691c90fff9b4c","6f67910e966aed715c97a3f34123895e5f12cc3eac821b35519f14410ed84217","2e3f3493cacf15681f4587b2ddccf16bf24c93025cb62b5c9c7c725303c18e42","ff5aaeb07738cecc1ec9c5959254005fa20ca2fd331dd2f15dc80ad6dd8810d2","de1e6715cd6c6ab48a4fed996c168d40cbaa8a47356deb9729ae3df1430e6ccd","b005828433393021f9db66d6e8ad6928f0a9d5c3dc2cf4dbfa0e264b6d4f9e3f","8e67b23999d09515a11fcb142bd1c843580eba1e07df1a89e97bfc61db430739","9a69f53c94388a60f1b16f7d2861bd554fcd7f6aceef6b1c1e3f48ca9030fbff","65ead8101d45973f10664e840193ae01ff8179a81e0a143e6a4cb90a4f0a78f8","ffa35cc1ab3169651692b80f3c91a16ff5136d7c15fba57f43101fc31b9c45f7","c3f038db69e3d5f661f109b711ad04e7f596b68b2d555a081ea422a18109016b","2a07e0970916588dc3d1cf8274c0e54672232a55cc01437deea383fbc9fd8903","6ef42127f51e88ba7505c920e77897d5b983bc86955439492094f1966bb17dd0","d22a5dfe87c858a69e1470dcc8d2fb14dc7b1c30002ca6c2ab85ee8d11f26c01","088131dc57db44873dca6ecc79ac8fbef7d37dccb1618ce9373526512956dde6","4ef88767cc4a4234fd487165ca4d733eac96f5d0f69d0da3668509ad8322a6e7","0b9171dd1de9b00df7c5ca401acf9d715cc5a1c4a77082996beb166e661197a8","dab64e620f07903bd218338db299f48cd21a578e18b54d21d75d9f887d4cdee5","0f357a93a11c2921ac7d078ba2fc01ed6142141ad27170a70d6ca1e7bcd216da","935d138f548875f2700aad732286be9f9b7a27950e49b8a9d9a26d4a04984849","97049b7cc03e75ca4d88e5fc499d028e0efd36426f983e7cc62e79ac25bacd74","29ce849f696e358d3d191165dd0b5a0fe877551d8076a13545bb42e4c1f2628a","d612e39e947aaa4ead5ba2b9f117f9f0f66eaa7b6f8e9ce3fad27840d048c4bb","2f9e1b6669b51121d81ee2f8e7ff070d55501e478b27d9bcd5b517037dbb907a","09fb6c5cf76713fc1aa2cba8cff23a15706ebc69a0ace264ab97b1d27f87b25b","dfe8efc67c47bc2a09806ef1a7b795223b7d200fce0a41173382c8d5d15f4e1d","25c9af80002c3cafcf9a71a115d5cf1fb52ee5e2bbb92e7fe5af2ac498bdade1","dfe8efc67c47bc2a09806ef1a7b795223b7d200fce0a41173382c8d5d15f4e1d","d3f431413d390d24af4f8bfa5f72768f4e1dfd2d84ae1235a27961405c6b8660","d3f431413d390d24af4f8bfa5f72768f4e1dfd2d84ae1235a27961405c6b8660","4bbbbc9591fe8270da47b29df25e14322e6560ac547382ae2a42ee35ecaa44bf","d56c5203a816443e1823372533b711434c45e25a09e5cedad076c9d8038065e9","44c9d492e1374aafb3f6d71f8f408eb74394550ad8e4665c0169bb7dd930ffea","11610aae481891ec2a0fc552d7f3c569d6ebcd8150c322c1d58b94f3a0f1c3df","3dac2a5afb3d345b6276c42ed4a69aee7b9600e0562ad6d3a9828fcbdba78267","67c759d4395f8b91e82a43a13be121e6924def3f3ce88c6a62bd45ec04e2a0a8","d6a7a8d9fe420579dd5351ac769cbd5c1d2454ef8f1b142a71cef10ecc70ec56","7f34eecdda77971fc96007d90eebd2f7addc85c214e0442f40990a28b840a032","153b939a629f12f0802d21bc0b335bb49daef00c6b9c4535c6144883a02a05a3","2f61964ca74a3b0f3c845426395a2d4cefa51ff5b0716bf1d9933274c40cefe9","83cb85c0863bc46fbac3879b4c67a60629008c02eaa88ddd1d51cf8d6013eadc","1e7c81072f87b7d817c8583a78a4d4056178b21f9ecf3d237b19bcc8cee1d391","736af4be184ea560f3010dd6bd796846e773e223f14ba13c1b9cec272788126a","0addef1e4c530c74fbaa56d65b00e9a0929bf64375fa6bf848d8afc8e1da3c3d","77bfab96a52e4fbf3b9afac9447ca6be2c64564f2c58fc560f7104611e646128","af32a73e481f2825f2dd83e5aa06fea0ee2d064a997a203af95d5e33ccf91ad5","37974afcd645a15237810a6785d78cfb51d791d6cd074b96770781248675a838","e0374bccc25125fb6b18555d7ec9761df443bd5801185359534ecdbae8c34a05","e26c3a19188fc8e9fee4962df6d30cf57d238463e79e92c7c65e77dcbd317d8f","17a4fafbf186721a4088d5f3d4373c74bd00af9a86e09bf5f056ba72c36748a4","41a0d9e099ee0c58affff9765cb0536174b98eda38bf403eaeb4d7f014180a48","b032980d1dfdf8f6692c1eac85f7d24134c075e6071e1cc1453a340ce04c94c2","a2fa19621c26c64773920e21c5ddd2758bdd992759a5124e1b4ae761d31d81b6","2a97222ee8383b85a17cdf9e868d1a04eedc5a7bd2b9813f1333fda730fd8149","7e278244c39cea3270f540d7add70595cb73204951030e9a6062ab9f3528a20a","f7be68d1757c477d837c754a5aebb96d44e7668d74204a6e471101acc2e3c331","0de6045abf24a55ffbe03db5517e1b619239e76d9308681899e6c115f10936bb","dfb894c450cef578aa27499be753b8945b759a6006f498e8a808da9362e60420","99727700bf127721388f7ebbe23188da7e87beaee64f775b0d6dff3e6ee9f499","09bbfdb57c117eb7b11b386d388923f8e6caf2a3c6b29fff81041c5ce427f6f3","3d3a1c0602c0e00c01efd86425ae6cdf9abc81fff15679bdcdaccea56cf992ac","4bf67246834d6fa98ec500cb519f39afd2159b4f932f7438c3f8afca62781215","8d2c3da3e0514f5f4dc88c4157bd320f89101c392be1a323b6eb42e94a47c415","fcf5d4d250a4967064b3c571f7e63decf50802e2ca6ac841cefe66aee6b6f1a9","fc44a49a0431b10c27c130da37ee6dcd1b0dc695d0b65ca6f57b89ca32030d69","ac58809688940109655913fea3cd3f4c37dc3192124d4b3bd6597cff4a9a6fc4","034243891e1c94fdf12a7008ed3db1e162c39a6b722e2226615e9d28ab233a38","acc6c6cd91d8cc9b37bcccd7385d98a95708b79ebd3a048b84499e33a988329c","90fb75c59748fd82c8052bb7ead9623d5a8cd1f8d0d75b743678b750a61d5647","071c3b31cca18e2c879dff4a8202cd2fdcb25045b64b6f1a035c5a712ba6f4e7","327e9f1a1b1d630314b5fe11e3cd5b2b2288e1044d3b98cc553523cdb765c5c8","6fe963af6a8576cc6190979a7fa46f71100d7468f01ae6fd3a1f60d293d95fec","d6adfc9ada50609dbfd6f598a9402a235f270aeb895cbd3433bfa1611301313a","0dd9f649e2f2afa45cd17181f55a01c76cb3f631770ea75952c4ad48fbd57d72","89622477e7d7cf0ff4ad038876b2231878a4f21976f6949af7b6876e0a0ccaf0","ddee132dd2a5b78962d0e6195a4990646e8d4567df5df058eb8280c2854b1b8d","d030c924c80a977415ba752a59eb8c359fc255608a72d137e5a3995e4f3dd09a","55e93a772bb8fd4851dfb79014d40f11d9dd714eff39acef6045776d6ee7abb7","79f0fe9adab2a9c45054ca0665649f702284d435fcecf6963f426494c4685177","9350f517362ea8d2e2a14ad23e77820b7f811a57b740ac145aa9607aebfbbcc1","f20bd6e5f63e3751b65cc0f69f800d1386b71f7bcf831df63a3e0a1980940f3a","e58d90563ede6de8f27a04736ae935f918c840b8c4d3ba2e9fd37197bd6dd776","a26368de811b9f9b4982bf58d3dcf926929b1768c9140cf52e6628f75699c92c","70b58e0eedf00c7424eee4693ed777f70cce8d9db6a3d41e7e7f911e1f3b7f94","eb5768d60f8b0323f07f7e89c0879617fedc960c37c85cf7c25c50e0165f0597","a90e6b3648ef7e2cd8951ee712a1b186c1092a11a149a1ddd96b97e01ce7d4f0","d2641c4410d3add40bf7a4dbdbc80c3f056c2a5b15fff09282eb06022c951ed7","8732005fd6e34080693fa81e4227187c05e70cefa43726fe459ae1b80a041437","124e5d8278708bd1a32a51c452508e15dfd06e7191c3994933cadb8f072fec4f","27f82563dc1dfcf0172267da16a4adae713cd2980e02577450530f36fd9aceba","e5dd6efb0064688f3857440b97e231045c4a1b3f708a00cd18ee721b4ac71db0","1aa08a948bc8b6bd3285973f97a14e4ad4a04b8c1cd696dbc4654f2689b3b4a4","20acd54cc349c30313c76e850d2d9a381540514d6330722fbb4d17c1d94fffcc","d934c22a6d997a8a1ecbc6c085587a52ec21a29d56803eb6e40d0c5f7bf79761","02ab03784d559a467cb4b720a8ce8f203f6968c2ecff93a79114fa13f7f8a3b6","01fc012c78c3f28beaa494e3b3cc45aaeea52923e24c1ea0bf5aaf6715fbddb8","e737cc7650ebe1801bbd87523a015431a9a2b44d090af8d84ff780e2fc0a7570","d8ee63e1d52dc44c682966ff8fc53b0d00646400a92df4adf02cfa0418c2823e","b6dfc2022e2a716dcf0e5c8057126c03fa75844e893bcd9461d2c24172ca0e0f","231cc8f40e3456165c8667715a40b9538bfb73d48668aca51db481b0b485bccd","f289532af57cb7e80c0f670205b64d27d2b9c6cdb6f78df475b585cbbc2d1a69","0a766fd0048fd37119a4bdad28dc2c952369a3e8fcc04892dd39adbd3700f818","d1cd8572518e8bbd33be0c841ced2e9bbe9d67471f27e4b9374c26b8e62473d9","5fcef9d6d418f7a47bbbff082d97b4cf789403df2a0eb75f1ae18b873362f8fb","42362a794b85db5592eaab0e21c9b6894a932653d59a97f3e081a7f9760ece61","70631829bbf1208cb277a988224efeedff692a6a44bb0d33b99a5750652cc2e3","269c8261cc14dd76365a9ed49f71332c0ce95198a51746c4343e6a8c99842bc7","7219f03e489efd3648c5182bc96a3f7bd9d39c1d656d25ae57314ab281eadb79","09fb6c5cf76713fc1aa2cba8cff23a15706ebc69a0ace264ab97b1d27f87b25b","2f9e1b6669b51121d81ee2f8e7ff070d55501e478b27d9bcd5b517037dbb907a","3879367de390469ac48196f22e461e58e03545f79e4ec5f6c4f935db5f26c7a8","4e65324ae5d788ba5e8982eb9af30ef9db63d454576aee979b183bdbf822bd10","18f46b4609876d1d108434c8207317f0f11a602aba8f899e3371f91887442bd9","23fd40eaa32f83a635fdba9ee1dbcb2308a3f6f651428c339dbe083bc10b1379","4785d461d01c6c9ad6fb294478df3ff76f677bb239b1434eea096f18e82508e1","f402bac0eae54e6d72b832962e2224eeddd330ae8df85bc1e02c4e47c831fd9a","d2da86368f860bd524a9bc9a6a3fc68fd822e18d921e20a68843e41946aaea20","07377d1ffb0e5f4582c311e11e5d5598ffd9d4b40d6b431a783d89d34e9597ff","a06e59909846a40b8ff6936ccb25279ae7152f0d1213cbcc52bc702601b1af1b","3632eb0ec85c5d95efeafca8a6237cb594d83f98779bdd763edf2e79b4e9e19a","387eb7e0dad26484d9f00d39e40658e4271d1c82b9f0c0365a211a12a536353a","090ce294c0ba760c0338591af254b03ed4310defe2fa6c4ff8096de17d3ee856","e0b84355712e3f0c4cc076a40e96c5991667ee17b746db73f52e67f5b1d59b1c","5444a9606d8aacf4b8f4779a00df1fb9f28b6d275f519011b6c511ff2419b745","f59ea6fd64694fdb9d7e94577a33a95725967b72bc01069a285e52caf78704ab"]; - const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids); + const vintxdatas = await BlueElectrum.multiGetTransactionByTxid(vinTxids, true); assert.ok(vintxdatas['8881027cfa2be033b7a9724b5547b710f5ba8aef1becb3ed53d34d614a54e3bc']); }); @@ -298,7 +299,10 @@ describe('BlueElectrum', () => { // possible solution: fetch it without verbose and decode locally. unfortunatelly it omits such info as confirmations, time etc // so whoever uses it should be prepared for this. // tbh consumer wallets dont usually work with such big txs, so probably we dont need it - const txdatas = await BlueElectrum.multiGetTransactionByTxid(['484a11c5e086a281413b9192b4f60c06abf745f08c2c28c4b4daefe6df3b9e5c']); + const txdatas = await BlueElectrum.multiGetTransactionByTxid( + ['484a11c5e086a281413b9192b4f60c06abf745f08c2c28c4b4daefe6df3b9e5c'], + true, + ); assert.ok(txdatas['484a11c5e086a281413b9192b4f60c06abf745f08c2c28c4b4daefe6df3b9e5c']); }); @@ -306,8 +310,8 @@ describe('BlueElectrum', () => { if (disableBatching) BlueElectrum.setBatchingDisabled(); const txdatas = await BlueElectrum.multiGetTransactionByTxid( ['881c54edd95cbdd1583d6b9148eb35128a47b64a2e67a5368a649d6be960f08e'], - 3, false, + 3, ); assert.strictEqual(