diff --git a/BlueComponents.js b/BlueComponents.js index 2418739e3..c63af974a 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -44,7 +44,6 @@ import Lnurl from './class/lnurl'; import { BlueStorageContext } from './blue_modules/storage-context'; import ToolTipMenu from './components/TooltipMenu'; -/** @type {AppStorage} */ const { height, width } = Dimensions.get('window'); const aspectRatio = height / width; let isIpad; diff --git a/UnlockWith.js b/UnlockWith.js index 2c6f820dd..adf93c014 100644 --- a/UnlockWith.js +++ b/UnlockWith.js @@ -7,7 +7,6 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { StackActions, useNavigation, useRoute } from '@react-navigation/native'; import { BlueStorageContext } from './blue_modules/storage-context'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; -/** @type {AppStorage} */ const styles = StyleSheet.create({ root: { diff --git a/appcenter-post-build.sh b/appcenter-post-build.sh index 8bd415181..5dcf1183c 100755 --- a/appcenter-post-build.sh +++ b/appcenter-post-build.sh @@ -13,7 +13,7 @@ if [ -f $FILENAME ]; then APTZ=`curl "https://$APPETIZE@api.appetize.io/v1/apps" -F "file=@$FILENAME" -F "platform=android"` echo Apptezize response: echo $APTZ - APPURL=`node -e "let e = JSON.parse('$APTZ'); console.log(e.publicURL);"` + APPURL=`node -e "let e = JSON.parse('$APTZ'); console.log(e.publicURL + '?device=pixel4');"` echo App url: $APPURL PR=`node scripts/appcenter-post-build-get-pr-number.js` echo PR: $PR diff --git a/blue_modules/BlueElectrum.js b/blue_modules/BlueElectrum.js index f3ed1c9d6..0644abb50 100644 --- a/blue_modules/BlueElectrum.js +++ b/blue_modules/BlueElectrum.js @@ -10,6 +10,34 @@ const ElectrumClient = require('electrum-client'); const reverse = require('buffer-reverse'); const BigNumber = require('bignumber.js'); const torrific = require('../blue_modules/torrific'); +const Realm = require('realm'); + +let _realm; +async function _getRealm() { + if (_realm) return _realm; + + const password = bitcoin.crypto.sha256(Buffer.from('fyegjitkyf[eqjnc.lf')).toString('hex'); + const buf = Buffer.from(password + password, 'hex'); + const encryptionKey = Int8Array.from(buf); + const path = 'electrumcache.realm'; + + const schema = [ + { + name: 'Cache', + primaryKey: 'cache_key', + properties: { + cache_key: { type: 'string', indexed: true }, + cache_value: 'string', // stringified json + }, + }, + ]; + _realm = await Realm.open({ + schema, + path, + encryptionKey, + }); + return _realm; +} const storageKey = 'ELECTRUM_PEERS'; const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: '443' }; @@ -470,15 +498,38 @@ module.exports.multiGetHistoryByAddress = async function (addresses, batchsize) return ret; }; -module.exports.multiGetTransactionByTxid = async function (txids, batchsize, verbose) { +module.exports.multiGetTransactionByTxid = async function (txids, batchsize, verbose = true) { 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 - verbose = verbose !== false; if (!mainClient) throw new Error('Electrum client is not connected'); const ret = {}; txids = [...new Set(txids)]; // deduplicate just for any case + // lets try cache first: + const realm = await _getRealm(); + const cacheKeySuffix = verbose ? '_verbose' : '_non_verbose'; + const keysCacheMiss = []; + for (const txid of txids) { + 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); + } catch (error) { + console.log(error, 'cache failed to parse', jsonString.cache_value); + } + } + + if (!ret[txid]) keysCacheMiss.push(txid); + } + + if (keysCacheMiss.length === 0) { + return ret; + } + + txids = keysCacheMiss; + // end cache + const chunks = splitIntoChunks(txids, batchsize); for (const chunk of chunks) { let results = []; @@ -522,6 +573,23 @@ 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; + // dont cache immature txs, but only for 'verbose', since its fully decoded tx jsons. non-verbose are just plain + // strings txhex + realm.create( + 'Cache', + { + cache_key: txid + cacheKeySuffix, + cache_value: JSON.stringify(ret[txid]), + }, + Realm.UpdateMode.Modified, + ); + } + }); + return ret; }; diff --git a/blue_modules/fs.js b/blue_modules/fs.js index 0e753b782..9376aa656 100644 --- a/blue_modules/fs.js +++ b/blue_modules/fs.js @@ -106,7 +106,7 @@ const showImagePickerAndReadImage = () => { }, response => { if (response.uri) { - const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString(); + const uri = response.uri.toString().replace('file://', ''); LocalQRCode.decode(uri, (error, result) => { if (!error) { resolve(result); @@ -130,7 +130,7 @@ const takePhotoWithImagePickerAndReadPhoto = () => { }, response => { if (response.uri) { - const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString(); + const uri = response.uri.toString().replace('file://', ''); LocalQRCode.decode(uri, (error, result) => { if (!error) { resolve(result); @@ -174,7 +174,7 @@ const showFilePickerAndReadFile = async function () { if (res?.type === DocumentPicker.types.images || res?.type?.startsWith('image/')) { return new Promise(resolve => { - const uri = Platform.OS === 'ios' ? res.uri.toString().replace('file://', '') : res.uri; + const uri = res.uri.toString().replace('file://', ''); LocalQRCode.decode(decodeURI(uri), (error, result) => { if (!error) { resolve({ data: result, uri: decodeURI(res.uri) }); diff --git a/class/wallets/abstract-hd-electrum-wallet.js b/class/wallets/abstract-hd-electrum-wallet.js index aead29417..9c0fc09a5 100644 --- a/class/wallets/abstract-hd-electrum-wallet.js +++ b/class/wallets/abstract-hd-electrum-wallet.js @@ -43,16 +43,6 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { return ret + (this.getUnconfirmedBalance() < 0 ? this.getUnconfirmedBalance() : 0); } - /** - * @inheritDoc - */ - timeToRefreshTransaction() { - for (const tx of this.getTransactions()) { - if (tx.confirmations < 7) return true; - } - return false; - } - /** * * @inheritDoc diff --git a/class/wallets/legacy-wallet.js b/class/wallets/legacy-wallet.js index 444bfff1c..cb51e8b7c 100644 --- a/class/wallets/legacy-wallet.js +++ b/class/wallets/legacy-wallet.js @@ -37,7 +37,7 @@ export class LegacyWallet extends AbstractWallet { */ timeToRefreshTransaction() { for (const tx of this.getTransactions()) { - if (tx.confirmations < 7) { + if (tx.confirmations < 7 && this._lastTxFetch < +new Date() - 5 * 60 * 1000) { return true; } } diff --git a/class/wallets/watch-only-wallet.js b/class/wallets/watch-only-wallet.js index efb9765aa..21925c68a 100644 --- a/class/wallets/watch-only-wallet.js +++ b/class/wallets/watch-only-wallet.js @@ -24,6 +24,16 @@ export class WatchOnlyWallet extends LegacyWallet { return super.getLastTxFetch(); } + timeToRefreshTransaction() { + if (this._hdWalletInstance) return this._hdWalletInstance.timeToRefreshTransaction(); + return super.timeToRefreshTransaction(); + } + + timeToRefreshBalance() { + if (this._hdWalletInstance) return this._hdWalletInstance.timeToRefreshBalance(); + return super.timeToRefreshBalance(); + } + allowSend() { return this.useWithHardwareWalletEnabled() && this.isHd() && this._hdWalletInstance.allowSend(); } diff --git a/loc/en.json b/loc/en.json index 1d81bc7b7..85f98b732 100644 --- a/loc/en.json +++ b/loc/en.json @@ -276,7 +276,7 @@ "electrum_history": "Server history", "electrum_reset_to_default": "Are you sure to want to reset your Electrum settings to default?", "electrum_clear": "Clear", - "tor_supported": "TOR supported", + "tor_supported": "Tor supported", "encrypt_decrypt": "Decrypt Storage", "encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.", "encrypt_del_uninstall": "Delete if BlueWallet is uninstalled", @@ -297,7 +297,7 @@ "lightning_error_lndhub_uri": "Not a valid LNDHub URI", "lightning_saved": "Your changes have been saved successfully.", "lightning_settings": "Lightning Settings", - "tor_settings": "TOR Settings", + "tor_settings": "Tor Settings", "lightning_settings_explain": "To connect to your own LND node, please install LNDHub and put its URL here in settings. Leave blank to use BlueWallet’s LNDHub (lndhub.io). Wallets created after saving changes will connect to the specified LNDHub.", "network": "Network", "network_broadcast": "Broadcast Transaction", diff --git a/loc/fa.json b/loc/fa.json index 715e498fe..f255d3ca6 100644 --- a/loc/fa.json +++ b/loc/fa.json @@ -276,6 +276,7 @@ "electrum_history": "تاریخچهٔ سرورها", "electrum_reset_to_default": "آیا مطمئن هستید که می‌خواهید تنظیمات الکترام را به حالت پیش‌فرض بازنشانی کنید؟", "electrum_clear": "پاک‌کردن", + "tor_supported": "پشتیبانی از تور", "encrypt_decrypt": "رمزگشایی فضای ذخیره‌سازی", "encrypt_decrypt_q": "آیا مطمئن هستید که می‌خواهید فضای ذخیره‌سازی خود را رمزگشایی کنید؟ این کار اجازه می‌دهد تا کیف پول‌های شما بدون گذرواژه قابل‌دسترسی باشند.", "encrypt_del_uninstall": "درصورت لغو نصب BlueWallet، حذف شود", @@ -296,6 +297,7 @@ "lightning_error_lndhub_uri": "یوآرآی LNDHub معتبر نیست", "lightning_saved": "تغییرات شما با موفقیت ذخیره شدند.", "lightning_settings": "تنظیمات لایتنینگ", + "tor_settings": "تنظیمات تور", "lightning_settings_explain": "برای اتصال به گره LND خود، لطفاً LNDHub را نصب کرده و آدرس آن را اینجا در تنظیمات قرار دهید. برای استفاده از LNDHub برنامهٔ BlueWallet (به آدرس lndhub.io)، خالی بگذارید. کیف پول‌های ایجادشده بعد از ذخیرهٔ تغییرات به LNDHub مشخص‌شده متصل خواهند شد.", "network": "شبکه", "network_broadcast": "انتشار تراکنش", diff --git a/loc/sl_SI.json b/loc/sl_SI.json index 1342e2fac..6d4aff493 100644 --- a/loc/sl_SI.json +++ b/loc/sl_SI.json @@ -14,6 +14,7 @@ "no": "Ne", "save": "Shrani", "seed": "Seme", + "success": "Uspešno", "wallet_key": "Ključ denarnice", "invalid_animated_qr_code_fragment" : "Neveljaven del animirane QR kode. Prosimo poskusite ponovno.", "file_saved": "Datoteka ({filePath}) je bila shranjena v mapo Prenosi.", @@ -135,9 +136,9 @@ "ask": "Ali ste shranili varnostno kopijo (seznam besed) vaše denarnice? Varnostna kopija je potrebna za dostop do vaših sredstev v primeru izgube naprave. Brez varnostne kopije bodo vaša sredstva trajno izgubljena.", "ask_no": "Ne, nisem", "ask_yes": "Da, sem", - "ok": "V redu, sem si zapisal!", - "ok_lnd": "V redu, sem shranil.", - "text": "Prosimo zapišite si seznam besed (mnemonično seme) na list papirja. To je varnostna kopija, ki jo lahko uporabite za obnovitev denarnice na drugi napravi.", + "ok": "V redu, sem si zapisal", + "ok_lnd": "V redu, sem shranil", + "text": "Prosimo zapišite si seznam besed (mnemonično seme) na list papirja.\nTo je varnostna kopija, ki jo lahko uporabite za obnovitev denarnice.", "text_lnd": "Shranite varnostno kopijo te denarnice. Omogoča vam obnovitev denarnice v primeru izgube te naprave.", "text_lnd2": "Ta denarnica uporablja gostovanje BlueWallet.", "title": "Vaša denarnica je ustvarjena" @@ -174,7 +175,6 @@ "details_address_field_is_not_valid": "Naslov ni veljaven", "details_adv_fee_bump": "Omogoči Povečanje Omrežnine", "details_adv_full": "Uporabi Celotno Stanje", - "details_adv_full_remove": "Drugi prejemniki bodo odstranjeni iz te transakcije.", "details_adv_full_sure": "Ali ste prepričani, da želite za to transakcijo uporabiti celotno stanje denarnice?", "details_adv_import": "Uvozi transakcijo", "details_amount_field_is_not_valid": "Znesek ni veljaven", @@ -183,15 +183,12 @@ "details_error_decode": "Ni mogoče dekodirati Bitcoin naslova", "details_fee_field_is_not_valid": "Omrežnina ni veljavna", "details_next": "Naprej", - "details_no_maximum": "Izbrana denarnica ne podpira samodejnega izračuna največjega stanja. Ali ste prepričani, da želite izbrati to denarnico?", - "details_no_multiple": "Izbrana denarnica ne podpira pošiljanja več prejemnikom. Ali ste prepričani, da želite izbrati to denarnico?", "details_no_signed_tx": "Izbrana datoteka ne vsebuje transakcije, ki jo je mogoče uvoziti.", "details_note_placeholder": "lastna opomba", "details_scan": "Skeniraj", "details_total_exceeds_balance": "Znesek presega razpoložljivo stanje.", "details_unrecognized_file_format": "Neprepoznana oblika datoteke", "details_wallet_before_tx": "Pred ustvarjanjem transakcije, morate dodati Bitcoin denarnico.", - "details_wallet_selection": "Izbira Denarnice", "dynamic_init": "Inicializacija", "dynamic_next": "Naprej", "dynamic_prev": "Nazaj", @@ -279,6 +276,7 @@ "electrum_history": "Zgodovina strežnikov", "electrum_reset_to_default": "Ali ste prepričani, da želite ponastaviti nastavitve Electrum strežnika na privzeto?", "electrum_clear": "Počisti", + "tor_supported": "TOR podprt", "encrypt_decrypt": "Dešifriraj Shrambo", "encrypt_decrypt_q": "Ali ste prepričani, da želite dešifrirati shrambo? To bo omogočilo dostop do vaših denarnic brez gesla.", "encrypt_del_uninstall": "Izbriši, če je BlueWallet odstranjen", @@ -295,10 +293,11 @@ "groundcontrol_explanation": "GroundControl je brezplačen odprtokoden strežnik potisnih obvestil za bitcoin denarnice. Da se ne zanašate na BlueWallet infrastrukturo, lahko namestite svoj strežnik GroundControl in tukaj dodate njegov URL. Pustite prazno, za uporabo privzetega.", "header": "Nastavitve", "language": "Jezik", - "language_restart": "Pri izbiri novega jezika bo morda potrebno ponovno zagnati BlueWallet, da bo sprememba začela veljati.", + "language_isRTL": "Za spremembo orientacije pisave je potreben ponovni zagon aplikacije.", "lightning_error_lndhub_uri": "Neveljaven LndHub URI", "lightning_saved": "Spremembe so bile uspešno shranjene", "lightning_settings": "Lightning Nastavitve", + "tor_settings": "TOR Nastavitve", "lightning_settings_explain": "Za povezavo z lastnim LND vozliščem, prosimo namestite LndHub in tukaj vnesite URL vozlišča. Pustite prazno za uporabo BlueWallet LNDHub (lndhub.io). Denarnice ustvarjene po potrditvi sprememb bodo povezane z novim LNDHub-om.", "network": "Omrežje", "network_broadcast": "Objavi transakcijo", @@ -325,7 +324,8 @@ "success_transaction_broadcasted" : "Vaša transakcija je bila objavljena!", "total_balance": "Skupno stanje", "total_balance_explanation": "Prikaži skupno stanje vseh denarnic na pripomočkih na domačem zaslonu.", - "widgets": "Pripomočki" + "widgets": "Pripomočki", + "tools": "Orodja" }, "notifications": { "would_you_like_to_receive_notifications": "Želite prikaz obvestil ob prejemu plačila?", @@ -409,6 +409,7 @@ "details_no_cancel": "Ne, prekliči", "details_save": "Shrani", "details_show_xpub": "Prikaži XPUB denarnice", + "details_show_addresses": "Prikaži naslove", "details_title": "Denarnica", "details_type": "Tip", "details_use_with_hardware_wallet": "Uporaba s strojno denarnico", @@ -561,5 +562,28 @@ "MAX": "MAX", "sat_byte": "sat/bajt", "sats": "sats" + }, + "addresses": { + "sign_title": "Podpiši / Preveri sporočilo", + "sign_help": "Ustvarite ali preverite kriptografski podpis na podlagi Bitcoin naslova", + "sign_sign": "Podpiši", + "sign_sign_submit": "Podpiši in Oddaj", + "sign_verify": "Preveri", + "sign_signature_correct": "Preverjanje uspešno!", + "sign_signature_incorrect": "Preverjanje neuspešno!", + "sign_placeholder_address": "Naslov", + "sign_placeholder_message": "Sporočilo", + "sign_placeholder_signature": "Podpis", + "sign_aopp_title": "AOPP", + "sign_aopp_confirm": "Ali želite podpisano sporočilo poslati na {hostname}?", + "address_balance": "Stanje: {balance} sats", + "addresses_title": "Naslovi", + "type_change": "Vračilo", + "type_receive": "Prejemni" + }, + "aopp": { + "title": "Izberite Naslov", + "send_success": "Podpis uspešno poslan", + "send_error": "Napaka pri pošiljanju podpisa" } } diff --git a/screen/send/ScanQRCode.js b/screen/send/ScanQRCode.js index c8b427099..8f0eccf58 100644 --- a/screen/send/ScanQRCode.js +++ b/screen/send/ScanQRCode.js @@ -215,7 +215,7 @@ const ScanQRCode = () => { setIsLoading(false); } else { if (response.uri) { - const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.uri; + const uri = response.uri.toString().replace('file://', ''); LocalQRCode.decode(uri, (error, result) => { if (!error) { onBarCodeRead({ data: result }); diff --git a/screen/send/details.js b/screen/send/details.js index 7f52c86d4..d0815c2ae 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -443,7 +443,7 @@ const SendDetails = () => { const changeAddress = await getChangeAddressAsync(); const requestedSatPerByte = Number(feeRate); const lutxo = utxo || wallet.getUtxo(); - console.log({ requestedSatPerByte, utxo }); + console.log({ requestedSatPerByte, lutxo: lutxo.length }); const targets = []; for (const transaction of addresses) { diff --git a/screen/send/psbtWithHardwareWallet.js b/screen/send/psbtWithHardwareWallet.js index ce1375a3e..218b6008d 100644 --- a/screen/send/psbtWithHardwareWallet.js +++ b/screen/send/psbtWithHardwareWallet.js @@ -37,7 +37,6 @@ import loc from '../../loc'; import { BlueStorageContext } from '../../blue_modules/storage-context'; import Notifications from '../../blue_modules/notifications'; const BlueElectrum = require('../../blue_modules/BlueElectrum'); -/** @type {AppStorage} */ const bitcoin = require('bitcoinjs-lib'); const fs = require('../../blue_modules/fs'); diff --git a/screen/settings/torSettings.js b/screen/settings/torSettings.js index fbd43fbca..714cd1446 100644 --- a/screen/settings/torSettings.js +++ b/screen/settings/torSettings.js @@ -76,14 +76,15 @@ const TorSettings = () => { Daemon Status: {daemonStatus} - - - - - - - - + + + + + + + + + ); }; diff --git a/screen/wallets/addMultisigHelp.js b/screen/wallets/addMultisigHelp.js index 2899cd905..85bf3b406 100644 --- a/screen/wallets/addMultisigHelp.js +++ b/screen/wallets/addMultisigHelp.js @@ -3,7 +3,6 @@ import { Image, View, Text, ScrollView, StyleSheet } from 'react-native'; import { useTheme } from '@react-navigation/native'; import { SafeBlueArea, BlueLoading } from '../../BlueComponents'; import navigationStyle from '../../components/navigationStyle'; -/** @type {AppStorage} */ import loc from '../../loc'; const WalletsAddMultisigHelp = () => { diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index f38b86b29..c408dd137 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -463,7 +463,7 @@ const WalletTransactions = () => { }, response => { if (response.uri) { - const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.uri; + const uri = response.uri.toString().replace('file://', ''); LocalQRCode.decode(uri, (error, result) => { if (!error) { onBarCodeRead({ data: result }); diff --git a/tests/setup.js b/tests/setup.js index 67424e479..c65d96335 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -95,6 +95,8 @@ jest.mock('react-native-haptic-feedback', () => ({})); const realmInstanceMock = { close: function () {}, + write: function () {}, + objectForPrimaryKey: function () { return {}; }, objects: function () { const wallets = { filtered: function () {