REF: resolved conflict

This commit is contained in:
overtorment 2023-04-10 10:52:10 +01:00
commit 7288682b56
203 changed files with 83085 additions and 24127 deletions

View file

@ -1,3 +0,0 @@
{
"presets": ["module:metro-react-native-babel-preset"]
}

View file

@ -1,9 +0,0 @@
[android]
target = Google Inc.:Google APIs:23
[download]
max_number_of_retries = 3
[maven_repositories]
central = https://repo1.maven.org/maven2

View file

@ -3,7 +3,7 @@ jobs:
lint: lint:
docker: docker:
- image: cimg/node:16.17.1 - image: cimg/node:16.20.0
working_directory: ~/repo working_directory: ~/repo
@ -26,7 +26,7 @@ jobs:
unit: unit:
docker: docker:
- image: cimg/node:16.17.1 - image: cimg/node:16.20.0
working_directory: ~/repo working_directory: ~/repo
@ -50,10 +50,12 @@ jobs:
integration: integration:
docker: docker:
- image: cimg/node:16.17.1 - image: cimg/node:16.20.0
working_directory: ~/repo working_directory: ~/repo
resource_class: large
steps: steps:
- checkout - checkout

View file

@ -1,7 +1,11 @@
{ {
"testRunner": "jest", "testRunner": {
"runnerConfig": "tests/e2e/config.json", "$0": "jest",
"skipLegacyWorkersInjection": true, "args": {
"config": "tests/e2e/jest.config.js",
"_": ["e2e"]
}
},
"apps": { "apps": {
"ios": { "ios": {
"type": "ios.app", "type": "ios.app",

View file

@ -68,5 +68,10 @@
"env": { "env": {
"es6": true "es6": true
}, },
"globals": { "fetch": false } "globals": { "fetch": false },
"settings": {
"react": { // this is for eslint-plugin-react
"version": "detect" // React version. "detect" automatically picks the version you have installed.
}
}
} }

View file

@ -30,7 +30,7 @@ jobs:
cache: 'gradle' cache: 'gradle'
- name: Install node_modules - name: Install node_modules
run: npm install run: npm install --production
- name: Build - name: Build
env: env:

View file

@ -39,6 +39,7 @@ jobs:
- name: Run tests - name: Run tests
run: npm test || npm test || npm test run: npm test || npm test || npm test
env: env:
BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}}
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}
HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }} HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }}
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }} HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
@ -94,7 +95,7 @@ jobs:
avd-name: Pixel_API_29_AOSP avd-name: Pixel_API_29_AOSP
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none -partition-size 2047 emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none -partition-size 2047
arch: x86_64 arch: x86_64
script: npm run e2e:release-test || npm run e2e:release-test || npm run e2e:release-test script: npm run e2e:release-test || npm run e2e:release-test || npm run e2e:release-test || npm run e2e:release-test
env: env:
TRAVIS: 1 TRAVIS: 1
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }} HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}

13
.gitignore vendored
View file

@ -20,7 +20,11 @@ DerivedData
*.hmap *.hmap
*.ipa *.ipa
*.xcuserstate *.xcuserstate
ios/.xcode.env.local
*.hprof *.hprof
.cxx/
*.keystore
!debug.keystore
# Android/IntelliJ # Android/IntelliJ
# #
@ -52,6 +56,7 @@ buck-out/
*/fastlane/report.xml */fastlane/report.xml
*/fastlane/Preview.html */fastlane/Preview.html
*/fastlane/screenshots */fastlane/screenshots
**/fastlane/test_output
# Bundle artifact # Bundle artifact
*.jsbundle *.jsbundle
@ -61,8 +66,12 @@ release-notes.json
release-notes.txt release-notes.txt
current-branch.json current-branch.json
ios/Pods/ # Ruby / CocoaPods
/ios/Pods/
/vendor/bundle/
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
artifacts/ artifacts/
# Editors # Editors

View file

@ -1 +1 @@
2.7.4 2.7.6

View file

@ -1,42 +1,43 @@
[main] [main]
host = https://www.transifex.com host = https://www.transifex.com
[bluewallet.loc-en-json--master] [o:bluewallet:p:bluewallet-fastlane:r:ios-fastlane-metadata-en-us-description-txt--master]
file_filter = loc/<lang>.json file_filter = ios/fastlane/metadata/<lang>/description.txt
source_file = ios/fastlane/metadata/en-US/description.txt
source_lang = en_US
type = TXT
minimum_perc = 10
lang_map = fr_FR: fr-FR, nl_NL: nl-NL, pt_BR: pt-BR, zh_CN: zh-Hans, zh_HK: zh-Hant, ar_SA: ar-SA, es_MX: es-MX, fr_CA: fr-CA, pt_PT: pt-PT, de_DE: de-DE, es_ES: es-ES
[o:bluewallet:p:bluewallet-fastlane:r:ios-fastlane-metadata-en-us-keywords-txt--master]
file_filter = ios/fastlane/metadata/<lang>/keywords.txt
source_file = ios/fastlane/metadata/en-US/keywords.txt
source_lang = en_US
type = TXT
minimum_perc = 10
lang_map = es_ES: es-ES, es_MX: es-MX, fr_CA: fr-CA, fr_FR: fr-FR, nl_NL: nl-NL, pt_BR: pt-BR, zh_CN: zh-Hans, ar_SA: ar-SA, de_DE: de-DE, pt_PT: pt-PT, zh_HK: zh-Hant
[o:bluewallet:p:bluewallet-fastlane:r:ios-fastlane-metadata-en-us-name-txt--master]
file_filter = ios/fastlane/metadata/<lang>/name.txt
source_file = ios/fastlane/metadata/en-US/name.txt
source_lang = en_US
type = TXT
minimum_perc = 10
lang_map = zh_HK: zh-Hant, ar_SA: ar-SA, es_MX: es-MX, fr_FR: fr-FR, nl_NL: nl-NL, pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-Hans, de_DE: de-DE, es_ES: es-ES, fr_CA: fr-CA
[o:bluewallet:p:bluewallet-fastlane:r:ios-fastlane-metadata-en-us-promotional-text-txt--master]
file_filter = ios/fastlane/metadata/<lang>/promotional_text.txt
source_file = ios/fastlane/metadata/en-US/promotional_text.txt
source_lang = en_US
type = TXT
minimum_perc = 10
lang_map = zh_CN: zh-Hans, zh_HK: zh-Hant, ar_SA: ar-SA, es_MX: es-MX, fr_CA: fr-CA, fr_FR: fr-FR, pt_PT: pt-PT, de_DE: de-DE, es_ES: es-ES, nl_NL: nl-NL, pt_BR: pt-BR
[o:bluewallet:p:bluewallet:r:loc-en-json--master]
file_filter = loc/<lang>.json
source_file = loc/en.json
source_lang = en
type = KEYVALUEJSON
minimum_perc = 30 minimum_perc = 30
source_file = loc/en.json lang_map = vi_VN: vi_vn, zh_TW: zh_tw, af_ZA: zar_afr, id_ID: id_id, sk_SK: sk_sk, ja_JP: jp_jp, uk_UA: ua, zh_CN: zh_cn, hr_HR: hr_hr, tr_TR: tr_tr, sv_SE: sv_se, th_TH: th_th, pt_BR: pt_br, es_ES: es, fr_FR: fr_fr, hu_HU: hu_hu, nl_NL: nl_nl, ro: ro, ca: ca, cy: cy, de_DE: de_de, nb_NO: nb_no, pt_PT: pt_pt, xh: zar_xho, bg_BG: bg_bg, cs_CZ: cs_cz, da_DK: da_dk, el: el, fa_IR: fa, fi_FI: fi_fi
source_lang = en
type = KEYVALUEJSON
lang_map = af_ZA: zar_afr, bg_BG: bg_bg, ca: ca, cs_CZ: cs_cz, cy: cy, da_DK: da_dk, de_DE: de_de, el: el, es_ES: es, fa_IR: fa, fi_FI: fi_fi, fr_FR: fr_fr, hr_HR: hr_hr, hu_HU: hu_hu, id_ID: id_id, ja_JP: jp_jp, nb_NO: nb_no, nl_NL: nl_nl, pt_BR: pt_br, pt_PT: pt_pt, ro: ro, sk_SK: sk_sk, sv_SE: sv_se, th_TH: th_th, tr_TR: tr_tr, uk_UA: ua, vi_VN: vi_vn, xh: zar_xho, zh_CN: zh_cn, zh_TW: zh_tw
[bluewallet-fastlane.ios-fastlane-metadata-en-us-description-txt--master]
file_filter = ios/fastlane/metadata/<lang>/description.txt
minimum_perc = 10
source_file = ios/fastlane/metadata/en-US/description.txt
source_lang = en_US
type = TXT
lang_map = ar_SA: ar-SA, de_DE: de-DE, es_ES: es-ES, es_MX: es-MX, fr_CA: fr-CA, fr_FR: fr-FR, nl_NL: nl-NL, pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-Hans, zh_HK: zh-Hant
[bluewallet-fastlane.ios-fastlane-metadata-en-us-keywords-txt--master]
file_filter = ios/fastlane/metadata/<lang>/keywords.txt
minimum_perc = 10
source_file = ios/fastlane/metadata/en-US/keywords.txt
source_lang = en_US
type = TXT
lang_map = ar_SA: ar-SA, de_DE: de-DE, es_ES: es-ES, es_MX: es-MX, fr_CA: fr-CA, fr_FR: fr-FR, nl_NL: nl-NL, pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-Hans, zh_HK: zh-Hant
[bluewallet-fastlane.ios-fastlane-metadata-en-us-name-txt--master]
file_filter = ios/fastlane/metadata/<lang>/name.txt
minimum_perc = 10
source_file = ios/fastlane/metadata/en-US/name.txt
source_lang = en_US
type = TXT
lang_map = ar_SA: ar-SA, de_DE: de-DE, es_ES: es-ES, es_MX: es-MX, fr_CA: fr-CA, fr_FR: fr-FR, nl_NL: nl-NL, pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-Hans, zh_HK: zh-Hant
[bluewallet-fastlane.ios-fastlane-metadata-en-us-promotional-text-txt--master]
file_filter = ios/fastlane/metadata/<lang>/promotional_text.txt
minimum_perc = 10
source_file = ios/fastlane/metadata/en-US/promotional_text.txt
source_lang = en_US
type = TXT
lang_map = ar_SA: ar-SA, de_DE: de-DE, es_ES: es-ES, es_MX: es-MX, fr_CA: fr-CA, fr_FR: fr-FR, nl_NL: nl-NL, pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-Hans, zh_HK: zh-Hant

13
.xcode-env Normal file
View file

@ -0,0 +1,13 @@
# This `.xcode.env` file is versioned and is used to source the
environment
# used when running script phases inside Xcode.
# To customize your local environment, you can create an
`.xcode.env.local`
# file that is not versioned.
# NODE_BINARY variable contains the PATH to the node executable.
#
# Customize the NODE_BINARY variable here.
# For example, to use nvm with brew, add the following line
# . "$(brew --prefix nvm)/nvm.sh" --no-use
export NODE_BINARY=$(command -v node)

64
App.js
View file

@ -291,7 +291,7 @@ const App = () => {
currency.updateExchangeRate(); currency.updateExchangeRate();
const processed = await processPushNotifications(); const processed = await processPushNotifications();
if (processed) return; if (processed) return;
const clipboard = await BlueClipboard.getClipboardContent(); const clipboard = await BlueClipboard().getClipboardContent();
const isAddressFromStoredWallet = wallets.some(wallet => { const isAddressFromStoredWallet = wallets.some(wallet => {
if (wallet.chain === Chain.ONCHAIN) { if (wallet.chain === Chain.ONCHAIN) {
// checking address validity is faster than unwrapping hierarchy only to compare it to garbage // checking address validity is faster than unwrapping hierarchy only to compare it to garbage
@ -332,38 +332,40 @@ const App = () => {
const showClipboardAlert = ({ contentType }) => { const showClipboardAlert = ({ contentType }) => {
ReactNativeHapticFeedback.trigger('impactLight', { ignoreAndroidSystemSettings: false }); ReactNativeHapticFeedback.trigger('impactLight', { ignoreAndroidSystemSettings: false });
BlueClipboard.getClipboardContent().then(clipboard => { BlueClipboard()
if (Platform.OS === 'ios' || Platform.OS === 'macos') { .getClipboardContent()
ActionSheet.showActionSheetWithOptions( .then(clipboard => {
{ if (Platform.OS === 'ios' || Platform.OS === 'macos') {
options: [loc._.cancel, loc._.continue], ActionSheet.showActionSheetWithOptions(
{
options: [loc._.cancel, loc._.continue],
title: loc._.clipboard,
message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning,
cancelButtonIndex: 0,
},
buttonIndex => {
if (buttonIndex === 1) {
handleOpenURL({ url: clipboard });
}
},
);
} else {
ActionSheet.showActionSheetWithOptions({
buttons: [
{ text: loc._.cancel, style: 'cancel', onPress: () => {} },
{
text: loc._.continue,
style: 'default',
onPress: () => {
handleOpenURL({ url: clipboard });
},
},
],
title: loc._.clipboard, title: loc._.clipboard,
message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning, message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning,
cancelButtonIndex: 0, });
}, }
buttonIndex => { });
if (buttonIndex === 1) {
handleOpenURL({ url: clipboard });
}
},
);
} else {
ActionSheet.showActionSheetWithOptions({
buttons: [
{ text: loc._.cancel, style: 'cancel', onPress: () => {} },
{
text: loc._.continue,
style: 'default',
onPress: () => {
handleOpenURL({ url: clipboard });
},
},
],
title: loc._.clipboard,
message: contentType === ClipboardContentType.BITCOIN ? loc.wallets.clipboard_bitcoin : loc.wallets.clipboard_lightning,
});
}
});
}; };
return ( return (

View file

@ -53,7 +53,7 @@ export const BlueButton = props => {
style={{ style={{
borderWidth: 0.7, borderWidth: 0.7,
borderColor: 'transparent', borderColor: 'transparent',
backgroundColor: backgroundColor, backgroundColor,
minHeight: 45, minHeight: 45,
height: 45, height: 45,
maxHeight: 45, maxHeight: 45,
@ -89,7 +89,7 @@ export const SecondButton = forwardRef((props, ref) => {
style={{ style={{
borderWidth: 0.7, borderWidth: 0.7,
borderColor: 'transparent', borderColor: 'transparent',
backgroundColor: backgroundColor, backgroundColor,
minHeight: 45, minHeight: 45,
height: 45, height: 45,
maxHeight: 45, maxHeight: 45,
@ -559,7 +559,12 @@ export const BlueHeaderDefaultMain = props => {
> >
{props.leftText} {props.leftText}
</Text> </Text>
<PlusIcon onPress={props.onNewWalletPress} Component={TouchableOpacity} /> <PlusIcon
accessibilityRole="button"
accessibilityLabel={loc.wallets.add_title}
onPress={props.onNewWalletPress}
Component={TouchableOpacity}
/>
</View> </View>
); );
}; };

View file

@ -1,6 +1,6 @@
source 'https://rubygems.org' source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby '2.7.4' ruby '>= 2.6.10'
gem 'cocoapods', '~> 1.11', '>= 1.11.2' gem 'cocoapods', '~> 1.11', '>= 1.11.3'

View file

@ -83,6 +83,9 @@ import { isDesktop, isTablet, isHandset } from './blue_modules/environment';
import SettingsPrivacy from './screen/settings/SettingsPrivacy'; import SettingsPrivacy from './screen/settings/SettingsPrivacy';
import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage'; import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage';
import LdkViewLogs from './screen/wallets/ldkViewLogs'; import LdkViewLogs from './screen/wallets/ldkViewLogs';
import PaymentCode from './screen/wallets/paymentCode';
import PaymentCodesList from './screen/wallets/paymentCodesList';
import loc from './loc';
const WalletsStack = createNativeStackNavigator(); const WalletsStack = createNativeStackNavigator();
@ -465,6 +468,20 @@ const ExportMultisigCoordinationSetupRoot = () => {
); );
}; };
const PaymentCodeStack = createNativeStackNavigator();
const PaymentCodeStackRoot = () => {
return (
<PaymentCodeStack.Navigator name="PaymentCodeRoot" screenOptions={{ headerHideShadow: true }} initialRouteName="PaymentCode">
<PaymentCodeStack.Screen name="PaymentCode" component={PaymentCode} options={{ headerTitle: loc.bip47.payment_code }} />
<PaymentCodeStack.Screen
name="PaymentCodesList"
component={PaymentCodesList}
options={{ headerTitle: loc.bip47.payment_codes_list }}
/>
</PaymentCodeStack.Navigator>
);
};
const RootStack = createNativeStackNavigator(); const RootStack = createNativeStackNavigator();
const NavigationDefaultOptions = { headerShown: false, stackPresentation: isDesktop ? 'containedModal' : 'modal' }; const NavigationDefaultOptions = { headerShown: false, stackPresentation: isDesktop ? 'containedModal' : 'modal' };
const Navigation = () => { const Navigation = () => {
@ -500,6 +517,8 @@ const Navigation = () => {
stackPresentation: isDesktop ? 'containedModal' : 'fullScreenModal', stackPresentation: isDesktop ? 'containedModal' : 'fullScreenModal',
}} }}
/> />
<RootStack.Screen name="PaymentCodeRoot" component={PaymentCodeStackRoot} options={NavigationDefaultOptions} />
</RootStack.Navigator> </RootStack.Navigator>
); );
}; };

View file

@ -83,8 +83,7 @@ npx react-native run-ios
npm run maccatalystpatches npm run maccatalystpatches
``` ```
Once the patches are applied, open Xcode and select "My Mac" as destination. If you are running macOS Catalina, you may need to remove all iOS 14 Widget targets. Once the patches are applied, open Xcode and select "My Mac" as destination.
## TESTS ## TESTS
@ -103,7 +102,7 @@ Grab an issue from [the backlog](https://github.com/BlueWallet/BlueWallet/projec
## Translations ## Translations
We accepts translations via [Transifex](https://www.transifex.com/bluewallet/bluewallet/) We accept translations via [Transifex](https://www.transifex.com/bluewallet/bluewallet/)
To participate you need to: To participate you need to:
1. Sign up to Transifex 1. Sign up to Transifex

View file

@ -4,6 +4,7 @@ import {
watchEvents, watchEvents,
useReachability, useReachability,
useInstalled, useInstalled,
usePaired,
transferCurrentComplicationUserInfo, transferCurrentComplicationUserInfo,
} from 'react-native-watch-connectivity'; } from 'react-native-watch-connectivity';
import { Chain } from './models/bitcoinUnits'; import { Chain } from './models/bitcoinUnits';
@ -17,13 +18,14 @@ function WatchConnectivity() {
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata, preferredFiatCurrency } = const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata, preferredFiatCurrency } =
useContext(BlueStorageContext); useContext(BlueStorageContext);
const isReachable = useReachability(); const isReachable = useReachability();
const isPaired = usePaired();
const isInstalled = useInstalled(); // true | false const isInstalled = useInstalled(); // true | false
const messagesListenerActive = useRef(false); const messagesListenerActive = useRef(false);
const lastPreferredCurrency = useRef(FiatUnit.USD.endPointKey); const lastPreferredCurrency = useRef(FiatUnit.USD.endPointKey);
useEffect(() => { useEffect(() => {
let messagesListener = () => {}; let messagesListener = () => {};
if (isInstalled && isReachable && walletsInitialized && messagesListenerActive.current === false) { if (isPaired && isInstalled && isReachable && walletsInitialized && messagesListenerActive.current === false) {
messagesListener = watchEvents.addListener('message', handleMessages); messagesListener = watchEvents.addListener('message', handleMessages);
messagesListenerActive.current = true; messagesListenerActive.current = true;
} else { } else {
@ -35,14 +37,14 @@ function WatchConnectivity() {
messagesListenerActive.current = false; messagesListenerActive.current = false;
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletsInitialized, isReachable, isInstalled]); }, [walletsInitialized, isPaired, isReachable, isInstalled]);
useEffect(() => { useEffect(() => {
if (isInstalled && isReachable && walletsInitialized) { if (isPaired && isInstalled && isReachable && walletsInitialized) {
sendWalletsToWatch(); sendWalletsToWatch();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletsInitialized, wallets, isReachable, isInstalled]); }, [walletsInitialized, wallets, isPaired, isReachable, isInstalled]);
useEffect(() => { useEffect(() => {
updateApplicationContext({ isWalletsInitialized: walletsInitialized, randomID: Math.floor(Math.random() * 11) }); updateApplicationContext({ isWalletsInitialized: walletsInitialized, randomID: Math.floor(Math.random() * 11) });
@ -208,7 +210,7 @@ function WatchConnectivity() {
balance: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true), balance: formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
type: wallet.type, type: wallet.type,
preferredBalanceUnit: wallet.getPreferredBalanceUnit(), preferredBalanceUnit: wallet.getPreferredBalanceUnit(),
receiveAddress: receiveAddress, receiveAddress,
transactions: watchTransactions, transactions: watchTransactions,
hideBalance: wallet.hideBalance, hideBalance: wallet.hideBalance,
}; };

View file

@ -1,173 +1,124 @@
apply plugin: "com.android.application" apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: "com.facebook.react"
import com.android.build.OutputFile import com.android.build.OutputFile
/** /**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets * This is the configuration block to customize your React Native Android app.
* and bundleReleaseJsAndAssets). * By default you don't need to apply any configuration, just uncomment the lines you need.
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation. If none specified and
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
* // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js",
*
* // https://reactnative.dev/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/ */
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen
// codegenDir = file("../node_modules/react-native-codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
project.ext.react = [ /* Variants */
enableHermes: true, // clean and rebuild if changing // The list of variants to that are debuggable. For those we're going to
] // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
apply from: "../../node_modules/react-native/react.gradle" /* Bundling */
apply plugin: "com.bugsnag.android.gradle" // A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
}
/** /**
* Set this to true to create two separate APKs instead of one: * Set this to true to create four separate APKs instead of one,
* - An APK that only works on ARM devices * one for each native architecture. This is useful if you don't
* - An APK that only works on x86 devices * use App Bundles (https://developer.android.com/guide/app-bundle/)
* The advantage is the size of the APK is reduced by about 4MB. * and want to have separate APKs to upload to the Play Store.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/ */
def enableSeparateBuildPerCPUArchitecture = false def enableSeparateBuildPerCPUArchitecture = false
/** /**
* Run Proguard to shrink the Java bytecode in release builds. * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/ */
def enableProguardInReleaseBuilds = false def enableProguardInReleaseBuilds = false
/** /**
* The preferred build flavor of JavaScriptCore. * The preferred build flavor of JavaScriptCore (JSC)
* *
* For example, to use the international variant, you can use: * For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
* *
* The international variant includes ICU i18n library and necessary data * The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that * give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default. * this variant is about 6MiB larger per architecture than default.
*/ */
def jscFlavor = 'org.webkit:android-jsc-intl:+' def jscFlavor = 'org.webkit:android-jsc-intl:+'
/** /**
* Whether to enable the Hermes VM. * Private function to get the list of Native Architectures you want to build.
* * This reads the value from reactNativeArchitectures in your gradle.properties
* This should be set on project.ext.react and mirrored here. If it is not set * file and works together with the --active-arch-only flag of react-native run-android.
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/ */
def enableHermes = project.ext.react.get("enableHermes", false); def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
android { android {
ndkVersion rootProject.ext.ndkVersion ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
defaultConfig { defaultConfig {
applicationId "io.bluewallet.bluewallet" applicationId "io.bluewallet.bluewallet"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "6.3.2" versionName "6.4.0"
multiDexEnabled true testBuildType System.getProperty('testBuildType', 'debug')
missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
ndk {
abiFilters 'arm64-v8a', 'x86_64', 'x86', 'armeabi-v7a'
}
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
missingDimensionStrategy 'detox', 'full'
} }
splits { splits {
abi { abi {
reset() reset()
enable enableSeparateBuildPerCPUArchitecture enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" include (*reactNativeArchitectures())
} }
} }
buildTypes { buildTypes {
release { release {
// Caution! In production, you need to generate your own keystore file. // Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android. // see https://reactnative.dev/docs/signed-apk-android.
minifyEnabled enableProguardInReleaseBuilds minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
} }
} }
@ -180,7 +131,8 @@ android {
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI) def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
} }
} }
@ -188,47 +140,20 @@ android {
} }
dependencies { dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation files("../../node_modules/rn-ldk/android/libs/LDK-release.aar") implementation files("../../node_modules/rn-ldk/android/libs/LDK-release.aar")
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
//noinspection GradleDynamicVersion
implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar") implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar")
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else { } else {
implementation jscFlavor implementation jscFlavor
} }
androidTestImplementation('com.wix:detox:+')
androidTestImplementation(project(path: ":detox")) implementation 'androidx.appcompat:appcompat:1.1.0'
implementation fileTree(dir: "libs", include: ["*.jar"])
} }
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.implementation
into 'libs'
}
apply plugin: 'com.google.gms.google-services' // Google Services plugin apply plugin: 'com.google.gms.google-services' // Google Services plugin
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
bugsnag {
uploadReactNativeMappings = true
}

View file

@ -1,19 +0,0 @@
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)

View file

@ -1,72 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package io.bluewallet.bluewallet;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new ReactFlipperPlugin());
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

View file

@ -14,6 +14,7 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false" android:allowBackup="false"
android:largeHeap="true"
android:extractNativeLibs="true" android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
@ -38,7 +39,8 @@
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" /> <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" /> <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver"> <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
@ -57,7 +59,8 @@
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@ -71,8 +74,6 @@
<data android:scheme="bluewallet" /> <data android:scheme="bluewallet" />
<data android:scheme="lapp" /> <data android:scheme="lapp" />
<data android:scheme="blue" /> <data android:scheme="blue" />
<data android:scheme="bankid" />
<data android:scheme="swish" />
<data android:scheme="http" /> <data android:scheme="http" />
<data android:scheme="https" /> <data android:scheme="https" />
</intent-filter> </intent-filter>

View file

@ -8,6 +8,10 @@ import androidx.annotation.Nullable;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
public class MainActivity extends ReactActivity { public class MainActivity extends ReactActivity {
/** /**
@ -20,10 +24,27 @@ public class MainActivity extends ReactActivity {
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null); super.onCreate(null);
if (getResources().getBoolean(R.bool.portrait_only)) { if (getResources().getBoolean(R.bool.portrait_only)) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
} }
}
/**
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
* (aka React 18) with two boolean flags.
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new DefaultReactActivityDelegate(
this,
getMainComponentName(),
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
// If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
);
}
} }

View file

@ -7,18 +7,18 @@ import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader; import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.modules.i18nmanager.I18nUtil;
import java.util.List; import java.util.List;
import com.bugsnag.android.Bugsnag; import com.bugsnag.android.Bugsnag;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
public class MainApplication extends Application implements ReactApplication { public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) { new DefaultReactNativeHost(this) {
@Override @Override
public boolean getUseDeveloperSupport() { public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG; return BuildConfig.DEBUG;
@ -38,10 +38,15 @@ public class MainApplication extends Application implements ReactApplication {
return "index"; return "index";
} }
@Override @Override
protected JSIModulePackage getJSIModulePackage() { protected boolean isNewArchEnabled() {
return new ReanimatedJSIModulePackage(); return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
} }
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
}; };
@Override @Override
@ -56,37 +61,9 @@ public class MainApplication extends Application implements ReactApplication {
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
sharedI18nUtilInstance.allowRTL(getApplicationContext(), true); sharedI18nUtilInstance.allowRTL(getApplicationContext(), true);
SoLoader.init(this, /* native exopackage */ false); SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
} // If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
/** }
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.rndiffapp.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -4,33 +4,39 @@ buildscript {
ext { ext {
minSdkVersion = 28 minSdkVersion = 28
supportLibVersion = "28.0.0" supportLibVersion = "28.0.0"
buildToolsVersion = "30.0.3" buildToolsVersion = "33.0.0"
compileSdkVersion = 30 compileSdkVersion = 33
targetSdkVersion = 30 targetSdkVersion = 33
googlePlayServicesVersion = "16.+" googlePlayServicesVersion = "16.+"
googlePlayServicesIidVersion = "16.0.1" googlePlayServicesIidVersion = "16.0.1"
firebaseVersion = "17.3.4" firebaseVersion = "17.3.4"
firebaseMessagingVersion = "20.2.1" firebaseMessagingVersion = "21.1.0"
ndkVersion = "23.0.7599858"
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
kotlin_version = '1.8.0'
kotlinVersion = '1.8.0'
} }
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath('com.android.tools.build:gradle:4.2.2') classpath("com.android.tools.build:gradle:7.4.2")
classpath("com.bugsnag:bugsnag-android-gradle-plugin:5.+") classpath("com.bugsnag:bugsnag-android-gradle-plugin:5.+")
classpath 'com.google.gms:google-services:4.3.14' // Google Services plugin classpath 'com.google.gms:google-services:4.3.14' // Google Services plugin
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
} }
} }
allprojects { allprojects {
repositories { repositories {
maven {
url("$rootDir/../node_modules/detox/Detox-android")
}
jcenter() { jcenter() {
content { content {
includeModule("com.facebook.yoga", "proguard-annotations") includeModule("com.facebook.yoga", "proguard-annotations")
@ -73,9 +79,8 @@ subprojects {
afterEvaluate {project -> afterEvaluate {project ->
if (project.hasProperty("android")) { if (project.hasProperty("android")) {
android { android {
compileSdkVersion 30
buildToolsVersion '30.0.3' buildToolsVersion '30.0.3'
compileSdkVersion 29 compileSdkVersion 31
defaultConfig { defaultConfig {
minSdkVersion 28 minSdkVersion 28
} }
@ -83,3 +88,9 @@ subprojects {
} }
} }
} }
subprojects { subproject ->
if(project['name'] == 'react-native-widget-center') {
project.configurations { compile { } }
}
}

View file

@ -9,8 +9,8 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
@ -24,11 +24,18 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX # Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true android.enableJetifier=true
# added when build failed because of memory: # Use this property to specify which architecture you want to build.
# https://stackoverflow.com/questions/56075455/expiring-daemon-because-jvm-heap-space-is-exhausted # You can also override it from the CLI using
org.gradle.daemon=true # ./gradlew <task> -PreactNativeArchitectures=x86_64
org.gradle.configureondemand=true reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# Version of flipper SDK to use with React Native # Use this property to enable support to the new architecture.
FLIPPER_VERSION=0.127.0 # This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip

271
android/gradlew vendored
View file

@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,78 +17,113 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@ -105,79 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
exec "$JAVACMD" "$@" JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -1,6 +1,6 @@
rootProject.name = 'BlueWallet' rootProject.name = 'BlueWallet'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app' include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')
include ':detox' include ':detox'
project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox') project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')

View file

@ -820,9 +820,15 @@ module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHi
module.exports.estimateFees = async function () { module.exports.estimateFees = async function () {
let histogram; let histogram;
let timeoutId;
try { try {
histogram = await Promise.race([mainClient.mempool_getFeeHistogram(), new Promise(resolve => setTimeout(resolve, 29000))]); histogram = await Promise.race([
} catch (_) {} mainClient.mempool_getFeeHistogram(),
new Promise(resolve => (timeoutId = setTimeout(resolve, 15000))),
]);
} finally {
clearTimeout(timeoutId);
}
if (!histogram) throw new Error('timeout while getting mempool_getFeeHistogram'); if (!histogram) throw new Error('timeout while getting mempool_getFeeHistogram');
@ -832,7 +838,7 @@ module.exports.estimateFees = async function () {
const _slow = await module.exports.estimateFee(144); const _slow = await module.exports.estimateFee(144);
// calculating fast fees from mempool: // calculating fast fees from mempool:
const fast = module.exports.calcEstimateFeeFromFeeHistorgam(1, histogram); const fast = Math.max(2, module.exports.calcEstimateFeeFromFeeHistorgam(1, histogram));
// recalculating medium and slow fees using bitcoincore estimations only like relative weights: // recalculating medium and slow fees using bitcoincore estimations only like relative weights:
// (minimum 1 sat, just for any case) // (minimum 1 sat, just for any case)
const medium = Math.max(1, Math.round((fast * _medium) / _fast)); const medium = Math.max(1, Math.round((fast * _medium) / _fast));

View file

@ -1,21 +0,0 @@
import { useContext, useEffect } from 'react';
import Obscure from 'react-native-obscure';
import { BlueStorageContext } from './storage-context';
const Privacy = () => {
const { isPrivacyBlurEnabled } = useContext(BlueStorageContext);
useEffect(() => {
Privacy.disableBlur();
}, [isPrivacyBlurEnabled]);
Privacy.enableBlur = () => {
if (!isPrivacyBlurEnabled) return;
Obscure.activateObscure();
};
Privacy.disableBlur = () => {
Obscure.deactivateObscure();
};
return null;
};
export default Privacy;

View file

@ -0,0 +1,29 @@
import { useContext, useEffect } from 'react';
// @ts-ignore: react-native-obscure is not in the type definition
import Obscure from 'react-native-obscure';
import { BlueStorageContext } from './storage-context';
interface PrivacyComponent extends React.FC {
enableBlur: (isPrivacyBlurEnabled: boolean) => void;
disableBlur: () => void;
}
const Privacy: PrivacyComponent = () => {
const { isPrivacyBlurEnabled } = useContext(BlueStorageContext);
useEffect(() => {
Obscure.deactivateObscure();
}, [isPrivacyBlurEnabled]);
return null;
};
Privacy.enableBlur = (isPrivacyBlurEnabled: boolean) => {
if (!isPrivacyBlurEnabled) return;
Obscure.activateObscure();
};
Privacy.disableBlur = () => {
Obscure.deactivateObscure();
};
export default Privacy;

View file

@ -1,22 +0,0 @@
import { useContext, useEffect } from 'react';
import { enabled } from 'react-native-privacy-snapshot';
import { BlueStorageContext } from './storage-context';
const Privacy = () => {
const { isPrivacyBlurEnabled } = useContext(BlueStorageContext);
useEffect(() => {
Privacy.disableBlur();
}, [isPrivacyBlurEnabled]);
Privacy.enableBlur = () => {
if (!isPrivacyBlurEnabled) return;
enabled(true);
};
Privacy.disableBlur = () => {
enabled(false);
};
return null;
};
export default Privacy;

View file

@ -0,0 +1,30 @@
import { useContext, useEffect } from 'react';
// @ts-ignore: react-native-obscure is not in the type definition
import { enabled } from 'react-native-privacy-snapshot';
import { BlueStorageContext } from './storage-context';
interface PrivacyComponent extends React.FC {
enableBlur: (isPrivacyBlurEnabled: boolean) => void;
disableBlur: () => void;
}
const Privacy: PrivacyComponent = () => {
const { isPrivacyBlurEnabled } = useContext(BlueStorageContext);
useEffect(() => {
Privacy.disableBlur();
}, [isPrivacyBlurEnabled]);
return null;
};
Privacy.enableBlur = (isPrivacyBlurEnabled: boolean) => {
if (!isPrivacyBlurEnabled) return;
enabled(true);
};
Privacy.disableBlur = () => {
enabled(false);
};
export default Privacy;

View file

@ -1,5 +0,0 @@
export default class Privacy {
static enableBlur() {}
static disableBlur() {}
}

21
blue_modules/Privacy.tsx Normal file
View file

@ -0,0 +1,21 @@
import React from 'react';
interface PrivacyComponent extends React.FC {
enableBlur: () => void;
disableBlur: () => void;
}
const Privacy: PrivacyComponent = () => {
// Define Privacy's behavior
return null;
};
Privacy.enableBlur = () => {
// Define the enableBlur behavior
};
Privacy.disableBlur = () => {
// Define the disableBlur behavior
};
export default Privacy;

View file

@ -42,7 +42,6 @@
"version": "1.0.2", "version": "1.0.2",
"react-native": { "react-native": {
"path": "path-browserify", "path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform", "_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable", "_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable", "_stream_writable": "readable-stream/writable",
@ -52,7 +51,6 @@
}, },
"browser": { "browser": {
"path": "path-browserify", "path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform", "_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable", "_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable", "_stream_writable": "readable-stream/writable",

View file

@ -59,7 +59,6 @@
"version": "0.1.6", "version": "0.1.6",
"react-native": { "react-native": {
"path": "path-browserify", "path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform", "_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable", "_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable", "_stream_writable": "readable-stream/writable",
@ -69,7 +68,6 @@
}, },
"browser": { "browser": {
"path": "path-browserify", "path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform", "_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable", "_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable", "_stream_writable": "readable-stream/writable",

View file

@ -1,38 +0,0 @@
import { useAsyncStorage } from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-clipboard/clipboard';
function BlueClipboard() {
BlueClipboard.STORAGE_KEY = 'ClipboardReadAllowed';
const isClipboardAccessAllowed = useAsyncStorage(BlueClipboard.STORAGE_KEY).getItem;
const setIsClipboardAccessAllowed = useAsyncStorage(BlueClipboard.STORAGE_KEY).setItem;
BlueClipboard.isReadClipboardAllowed = async () => {
try {
const clipboardAccessAllowed = await isClipboardAccessAllowed();
if (clipboardAccessAllowed === null) {
await setIsClipboardAccessAllowed(JSON.stringify(true));
return true;
}
return !!JSON.parse(clipboardAccessAllowed);
} catch {
await setIsClipboardAccessAllowed(JSON.stringify(true));
return true;
}
};
BlueClipboard.setReadClipboardAllowed = value => {
setIsClipboardAccessAllowed(JSON.stringify(!!value));
};
BlueClipboard.getClipboardContent = async () => {
const isAllowed = await BlueClipboard.isReadClipboardAllowed();
if (isAllowed) {
return Clipboard.getString();
} else {
return '';
}
};
}
BlueClipboard.default = new BlueClipboard();
export default BlueClipboard;

42
blue_modules/clipboard.ts Normal file
View file

@ -0,0 +1,42 @@
import { useAsyncStorage } from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-clipboard/clipboard';
const BlueClipboard = () => {
const STORAGE_KEY = 'ClipboardReadAllowed';
const { getItem, setItem } = useAsyncStorage(STORAGE_KEY);
const isReadClipboardAllowed = async () => {
try {
const clipboardAccessAllowed = await getItem();
if (clipboardAccessAllowed === null) {
await setItem(JSON.stringify(true));
return true;
}
return !!JSON.parse(clipboardAccessAllowed);
} catch {
await setItem(JSON.stringify(true));
return true;
}
};
const setReadClipboardAllowed = (value: boolean) => {
setItem(JSON.stringify(!!value));
};
const getClipboardContent = async () => {
const isAllowed = await isReadClipboardAllowed();
if (isAllowed) {
return Clipboard.getString();
} else {
return '';
}
};
return {
isReadClipboardAllowed,
setReadClipboardAllowed,
getClipboardContent,
};
};
export default BlueClipboard;

View file

@ -53,6 +53,9 @@ async function _restoreSavedPreferredFiatCurrencyFromStorage() {
if (preferredFiatCurrency === null) { if (preferredFiatCurrency === null) {
throw Error('No Preferred Fiat selected'); throw Error('No Preferred Fiat selected');
} }
preferredFiatCurrency = FiatUnit[preferredFiatCurrency.endPointKey] || preferredFiatCurrency;
// ^^^ in case configuration in json file changed (and is different from what we stored) we reload it
} catch (_) { } catch (_) {
const deviceCurrencies = RNLocalize.getCurrencies(); const deviceCurrencies = RNLocalize.getCurrencies();
if (Object.keys(FiatUnit).some(unit => unit === deviceCurrencies[0])) { if (Object.keys(FiatUnit).some(unit => unit === deviceCurrencies[0])) {

View file

@ -1,40 +0,0 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
import { getSystemName, isTablet, getDeviceType } from 'react-native-device-info';
const isMacCatalina = getSystemName() === 'Mac OS X';
const isDesktop = getDeviceType() === 'Desktop';
const getIsTorCapable = () => {
let capable = true;
if (Platform.OS === 'android' && Platform.Version < 26) {
capable = false;
} else if (isDesktop) {
capable = false;
}
return capable;
};
const IS_TOR_DAEMON_DISABLED = 'is_tor_daemon_disabled';
export async function setIsTorDaemonDisabled(disabled = true) {
return AsyncStorage.setItem(IS_TOR_DAEMON_DISABLED, disabled ? '1' : '');
}
export async function isTorDaemonDisabled() {
let isTorDaemonDisabled;
try {
const savedValue = await AsyncStorage.getItem(IS_TOR_DAEMON_DISABLED);
if (savedValue === null) {
isTorDaemonDisabled = false;
} else {
isTorDaemonDisabled = savedValue;
}
} catch {
isTorDaemonDisabled = true;
}
return !!isTorDaemonDisabled;
}
export const isHandset = getDeviceType() === 'Handset';
export const isTorCapable = getIsTorCapable();
export { isMacCatalina, isDesktop, isTablet };

View file

@ -0,0 +1,41 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
import { isTablet, getDeviceType } from 'react-native-device-info';
const isDesktop: boolean = getDeviceType() === 'Desktop';
const getIsTorCapable = (): boolean => {
let capable = true;
if (Platform.OS === 'android' && Platform.Version < 26) {
capable = false;
} else if (isDesktop) {
capable = false;
}
return capable;
};
const IS_TOR_DAEMON_DISABLED: string = 'is_tor_daemon_disabled';
export async function setIsTorDaemonDisabled(disabled: boolean = true): Promise<void> {
return AsyncStorage.setItem(IS_TOR_DAEMON_DISABLED, disabled ? '1' : '');
}
export async function isTorDaemonDisabled(): Promise<boolean> {
let isTorDaemonDisabled: boolean;
try {
const savedValue = await AsyncStorage.getItem(IS_TOR_DAEMON_DISABLED);
if (savedValue === null) {
isTorDaemonDisabled = false;
} else {
isTorDaemonDisabled = savedValue === '1';
}
} catch {
isTorDaemonDisabled = true;
}
return isTorDaemonDisabled;
}
export const isHandset: boolean = getDeviceType() === 'Handset';
export const isTorCapable: boolean = getIsTorCapable();
export { isDesktop, isTablet };

View file

@ -6,8 +6,6 @@ import DocumentPicker from 'react-native-document-picker';
import { launchCamera, launchImageLibrary } from 'react-native-image-picker'; import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
import { presentCameraNotAuthorizedAlert } from '../class/camera'; import { presentCameraNotAuthorizedAlert } from '../class/camera';
import { isDesktop } from '../blue_modules/environment'; import { isDesktop } from '../blue_modules/environment';
import ActionSheet from '../screen/ActionSheet';
import BlueClipboard from './clipboard';
import alert from '../components/Alert'; import alert from '../components/Alert';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image'); const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
@ -205,43 +203,8 @@ const showFilePickerAndReadFile = async function () {
} }
}; };
// Intended for macOS Catalina. Not for long press shortcut
const showActionSheet = async props => {
const isClipboardEmpty = (await BlueClipboard.getClipboardContent()).trim().length === 0;
let copyFromClipboardIndex;
const options = [loc._.cancel, loc.wallets.take_photo, loc.wallets.list_long_choose];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
copyFromClipboardIndex = options.length - 1;
}
options.push(loc.wallets.import_file);
const importFileButtonIndex = options.length - 1;
return new Promise(resolve =>
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0, anchor: props.anchor }, async buttonIndex => {
if (buttonIndex === 1) {
takePhotoWithImagePickerAndReadPhoto().then(resolve);
} else if (buttonIndex === 2) {
showImagePickerAndReadImage()
.then(resolve)
.catch(error => alert(error.message));
} else if (buttonIndex === copyFromClipboardIndex) {
const clipboard = await BlueClipboard.getClipboardContent();
resolve(clipboard);
} else if (importFileButtonIndex) {
const { data } = await showFilePickerAndReadFile();
if (data) {
resolve(data);
}
}
}),
);
};
module.exports.writeFileAndExport = writeFileAndExport; module.exports.writeFileAndExport = writeFileAndExport;
module.exports.openSignedTransaction = openSignedTransaction; module.exports.openSignedTransaction = openSignedTransaction;
module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile; module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile;
module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage; module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage;
module.exports.takePhotoWithImagePickerAndReadPhoto = takePhotoWithImagePickerAndReadPhoto; module.exports.takePhotoWithImagePickerAndReadPhoto = takePhotoWithImagePickerAndReadPhoto;
module.exports.showActionSheet = showActionSheet;

View file

@ -69,7 +69,7 @@ function Notifications(props) {
// if user is staring at the app when he receives the notification we process it instantly // if user is staring at the app when he receives the notification we process it instantly
// so app refetches related wallet // so app refetches related wallet
if (payload.foreground) props.onProcessNotifications(); if (payload.foreground) props.onProcessNotifications(); // eslint-disable-line react/prop-types
}, },
// (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android) // (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)

View file

@ -6,7 +6,7 @@ import { useAsyncStorage } from '@react-native-async-storage/async-storage';
import { FiatUnit } from '../models/fiatUnit'; import { FiatUnit } from '../models/fiatUnit';
import Notifications from '../blue_modules/notifications'; import Notifications from '../blue_modules/notifications';
import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc'; import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc';
import { LegacyWallet } from '../class'; import { LegacyWallet, WatchOnlyWallet } from '../class';
import { isTorDaemonDisabled, setIsTorDaemonDisabled } from './environment'; import { isTorDaemonDisabled, setIsTorDaemonDisabled } from './environment';
import alert from '../components/Alert'; import alert from '../components/Alert';
const BlueApp = require('../BlueApp'); const BlueApp = require('../BlueApp');
@ -120,6 +120,10 @@ export const BlueStorageProvider = ({ children }) => {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL); setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
} }
await BlueElectrum.waitTillConnected(); await BlueElectrum.waitTillConnected();
const paymentCodesStart = Date.now();
await fetchSenderPaymentCodes(lastSnappedTo);
const paymentCodesEnd = Date.now();
console.log('fetch payment codes took', (paymentCodesEnd - paymentCodesStart) / 1000, 'sec');
const balanceStart = +new Date(); const balanceStart = +new Date();
await fetchWalletBalances(lastSnappedTo); await fetchWalletBalances(lastSnappedTo);
const balanceEnd = +new Date(); const balanceEnd = +new Date();
@ -190,7 +194,7 @@ export const BlueStorageProvider = ({ children }) => {
addWallet(w); addWallet(w);
await saveToDisk(); await saveToDisk();
A(A.ENUM.CREATED_WALLET); A(A.ENUM.CREATED_WALLET);
Alert.alert('', loc.wallets.import_success); Alert.alert('', w.type === WatchOnlyWallet.type ? loc.wallets.import_success_watchonly : loc.wallets.import_success);
Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []); Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []);
// start balance fetching at the background // start balance fetching at the background
await w.fetchBalance(); await w.fetchBalance();
@ -199,8 +203,9 @@ export const BlueStorageProvider = ({ children }) => {
let txMetadata = BlueApp.tx_metadata || {}; let txMetadata = BlueApp.tx_metadata || {};
const getTransactions = BlueApp.getTransactions; const getTransactions = BlueApp.getTransactions;
const isAdancedModeEnabled = BlueApp.isAdancedModeEnabled; const isAdvancedModeEnabled = BlueApp.isAdvancedModeEnabled;
const fetchSenderPaymentCodes = BlueApp.fetchSenderPaymentCodes;
const fetchWalletBalances = BlueApp.fetchWalletBalances; const fetchWalletBalances = BlueApp.fetchWalletBalances;
const fetchWalletTransactions = BlueApp.fetchWalletTransactions; const fetchWalletTransactions = BlueApp.fetchWalletTransactions;
const getBalance = BlueApp.getBalance; const getBalance = BlueApp.getBalance;
@ -214,7 +219,7 @@ export const BlueStorageProvider = ({ children }) => {
const decryptStorage = BlueApp.decryptStorage; const decryptStorage = BlueApp.decryptStorage;
const isPasswordInUse = BlueApp.isPasswordInUse; const isPasswordInUse = BlueApp.isPasswordInUse;
const cachedPassword = BlueApp.cachedPassword; const cachedPassword = BlueApp.cachedPassword;
const setIsAdancedModeEnabled = BlueApp.setIsAdancedModeEnabled; const setIsAdvancedModeEnabled = BlueApp.setIsAdvancedModeEnabled;
const getHodlHodlSignatureKey = BlueApp.getHodlHodlSignatureKey; const getHodlHodlSignatureKey = BlueApp.getHodlHodlSignatureKey;
const addHodlHodlContract = BlueApp.addHodlHodlContract; const addHodlHodlContract = BlueApp.addHodlHodlContract;
const getHodlHodlContracts = BlueApp.getHodlHodlContracts; const getHodlHodlContracts = BlueApp.getHodlHodlContracts;
@ -239,7 +244,7 @@ export const BlueStorageProvider = ({ children }) => {
setItem, setItem,
getItem, getItem,
getHodlHodlContracts, getHodlHodlContracts,
isAdancedModeEnabled, isAdvancedModeEnabled,
fetchWalletBalances, fetchWalletBalances,
fetchWalletTransactions, fetchWalletTransactions,
fetchAndSaveWalletTransactions, fetchAndSaveWalletTransactions,
@ -260,7 +265,7 @@ export const BlueStorageProvider = ({ children }) => {
getHodlHodlApiKey, getHodlHodlApiKey,
decryptStorage, decryptStorage,
isPasswordInUse, isPasswordInUse,
setIsAdancedModeEnabled, setIsAdvancedModeEnabled,
setPreferredFiatCurrency, setPreferredFiatCurrency,
preferredFiatCurrency, preferredFiatCurrency,
setLanguage, setLanguage,

View file

@ -67,7 +67,7 @@ export class AppStorage {
*/ */
setItem = (key, value) => { setItem = (key, value) => {
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
return RNSecureKeyStore.set(key, value, { accessible: ACCESSIBLE.WHEN_UNLOCKED }); return RNSecureKeyStore.set(key, value, { accessible: ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY });
} else { } else {
return AsyncStorage.setItem(key, value); return AsyncStorage.setItem(key, value);
} }
@ -289,8 +289,8 @@ export class AppStorage {
realmkeyValue.create( realmkeyValue.create(
'KeyValue', 'KeyValue',
{ {
key: key, key,
value: value, value,
}, },
Realm.UpdateMode.Modified, Realm.UpdateMode.Modified,
); );
@ -602,6 +602,11 @@ export class AppStorage {
keyCloned._hdWalletInstance._txs_by_external_index = {}; keyCloned._hdWalletInstance._txs_by_external_index = {};
keyCloned._hdWalletInstance._txs_by_internal_index = {}; keyCloned._hdWalletInstance._txs_by_internal_index = {};
} }
if (keyCloned._bip47_instance) {
delete keyCloned._bip47_instance; // since it wont be restored into a proper class instance
}
walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type })); walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type }));
} }
if (realm) realm.close(); if (realm) realm.close();
@ -682,6 +687,7 @@ export class AppStorage {
} }
} else { } else {
for (const wallet of this.wallets) { for (const wallet of this.wallets) {
console.log('fetching balance for', wallet.getLabel());
await wallet.fetchBalance(); await wallet.fetchBalance();
} }
} }
@ -725,6 +731,27 @@ export class AppStorage {
} }
}; };
fetchSenderPaymentCodes = async index => {
console.log('fetchSenderPaymentCodes for wallet#', typeof index === 'undefined' ? '(all)' : index);
if (index || index === 0) {
try {
if (!(this.wallets[index].allowBIP47() && this.wallets[index].isBIP47Enabled())) return;
await this.wallets[index].fetchBIP47SenderPaymentCodes();
} catch (error) {
console.error('Failed to fetch sender payment codes for wallet', index, error);
}
} else {
for (const wallet of this.wallets) {
try {
if (!(wallet.allowBIP47() && wallet.isBIP47Enabled())) continue;
await wallet.fetchBIP47SenderPaymentCodes();
} catch (error) {
console.error('Failed to fetch sender payment codes for wallet', wallet.label, error);
}
}
}
};
/** /**
* *
* @returns {Array.<AbstractWallet>} * @returns {Array.<AbstractWallet>}
@ -789,14 +816,14 @@ export class AppStorage {
return finalBalance; return finalBalance;
}; };
isAdancedModeEnabled = async () => { isAdvancedModeEnabled = async () => {
try { try {
return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)); return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED));
} catch (_) {} } catch (_) {}
return false; return false;
}; };
setIsAdancedModeEnabled = async value => { setIsAdvancedModeEnabled = async value => {
await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : ''); await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : '');
}; };

View file

@ -29,7 +29,7 @@ export default class Azteco {
} }
static getParamsFromUrl(u) { static getParamsFromUrl(u) {
const urlObject = url.parse(u, true); // eslint-disable-line node/no-deprecated-api const urlObject = url.parse(u, true); // eslint-disable-line n/no-deprecated-api
return { return {
uri: u, uri: u,
c1: urlObject.query.c1, c1: urlObject.query.c1,

View file

@ -184,7 +184,7 @@ class DeeplinkSchemaMatch {
}, },
]); ]);
} else { } else {
const urlObject = url.parse(event.url, true); // eslint-disable-line node/no-deprecated-api const urlObject = url.parse(event.url, true); // eslint-disable-line n/no-deprecated-api
(async () => { (async () => {
if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') { if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') {
switch (urlObject.host) { switch (urlObject.host) {
@ -390,7 +390,8 @@ class DeeplinkSchemaMatch {
break; break;
} }
} else if (value.startsWith('lightning')) { } else if (value.startsWith('lightning')) {
lndInvoice = `lightning:${txInfo[index + 1]}`; const lnpart = txInfo[index + 1].split('&').find(el => el.toLowerCase().startsWith('ln'));
lndInvoice = `lightning:${lnpart}`;
if (!this.isLightningInvoice(lndInvoice)) { if (!this.isLightningInvoice(lndInvoice)) {
lndInvoice = false; lndInvoice = false;
break; break;

View file

@ -168,7 +168,7 @@ export class HDSegwitBech32Transaction {
value = new BigNumber(value).multipliedBy(100000000).toNumber(); value = new BigNumber(value).multipliedBy(100000000).toNumber();
wentIn += value; wentIn += value;
const address = SegwitBech32Wallet.witnessToAddress(inp.witness[inp.witness.length - 1]); const address = SegwitBech32Wallet.witnessToAddress(inp.witness[inp.witness.length - 1]);
utxos.push({ vout: inp.index, value: value, txId: reversedHash, address: address }); utxos.push({ vout: inp.index, value, txId: reversedHash, address });
} }
} }
@ -193,7 +193,7 @@ export class HDSegwitBech32Transaction {
changeAmount += value; changeAmount += value;
} else { } else {
// this is target // this is target
targets.push({ value: value, address: address }); targets.push({ value, address });
} }
} }
@ -205,9 +205,9 @@ export class HDSegwitBech32Transaction {
if (this._wallet.weOwnAddress(address)) { if (this._wallet.weOwnAddress(address)) {
unconfirmedUtxos.push({ unconfirmedUtxos.push({
vout: outp.n, vout: outp.n,
value: value, value,
txId: this._txid || this._txDecoded.getId(), txId: this._txid || this._txDecoded.getId(),
address: address, address,
}); });
} }
} }

View file

@ -1,7 +1,7 @@
import { bech32 } from 'bech32'; import { bech32 } from 'bech32';
import bolt11 from 'bolt11'; import bolt11 from 'bolt11';
import { isTorDaemonDisabled } from '../blue_modules/environment'; import { isTorDaemonDisabled } from '../blue_modules/environment';
import { parse } from 'url'; // eslint-disable-line node/no-deprecated-api import { parse } from 'url'; // eslint-disable-line n/no-deprecated-api
import { createHmac } from 'crypto'; import { createHmac } from 'crypto';
import secp256k1 from 'secp256k1'; import secp256k1 from 'secp256k1';
const CryptoJS = require('crypto-js'); const CryptoJS = require('crypto-js');
@ -289,11 +289,23 @@ export default class Lnurl {
return this?._lnurlPayServicePayload?.commentAllowed ? parseInt(this._lnurlPayServicePayload.commentAllowed) : false; return this?._lnurlPayServicePayload?.commentAllowed ? parseInt(this._lnurlPayServicePayload.commentAllowed) : false;
} }
getMin() {
return this?._lnurlPayServicePayload?.min ? parseInt(this._lnurlPayServicePayload.min) : false;
}
getMax() {
return this?._lnurlPayServicePayload?.max ? parseInt(this._lnurlPayServicePayload.max) : false;
}
getAmount() {
return this.getMin();
}
authenticate(secret) { authenticate(secret) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this._lnurl) throw new Error('this._lnurl is not set'); if (!this._lnurl) throw new Error('this._lnurl is not set');
const url = parse(Lnurl.getUrlFromLnurl(this._lnurl), true); // eslint-disable-line node/no-deprecated-api const url = parse(Lnurl.getUrlFromLnurl(this._lnurl), true);
const hmac = createHmac('sha256', secret); const hmac = createHmac('sha256', secret);
hmac.on('readable', async () => { hmac.on('readable', async () => {

View file

@ -140,9 +140,9 @@ export class MultisigCosigner {
static exportToJson(xfp, xpub, path) { static exportToJson(xfp, xpub, path) {
return JSON.stringify({ return JSON.stringify({
xfp: xfp, xfp,
xpub: xpub, xpub,
path: path, path,
}); });
} }

View file

@ -217,7 +217,7 @@ const startImport = (importTextOrig, askPassphrase = false, searchAccounts = fal
wallet.setDerivationPath(path); wallet.setDerivationPath(path);
yield { progress: `bip39 ${i.script_type} ${path}` }; yield { progress: `bip39 ${i.script_type} ${path}` };
if (await wallet.wasEverUsed()) { if (await wallet.wasEverUsed()) {
yield { wallet: wallet }; yield { wallet };
walletFound = true; walletFound = true;
} else { } else {
break; // don't check second account if first one is empty break; // don't check second account if first one is empty

View file

@ -4,22 +4,26 @@ import BigNumber from 'bignumber.js';
import b58 from 'bs58check'; import b58 from 'bs58check';
import BIP32Factory, { BIP32Interface } from 'bip32'; import BIP32Factory, { BIP32Interface } from 'bip32';
import { randomBytes } from '../rng';
import { AbstractHDWallet } from './abstract-hd-wallet';
import { ECPairFactory } from 'ecpair';
import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types';
import { ElectrumHistory } from '../../blue_modules/BlueElectrum';
import type BlueElectrumNs from '../../blue_modules/BlueElectrum';
import { ECPairInterface } from 'ecpair/src/ecpair'; import { ECPairInterface } from 'ecpair/src/ecpair';
import { Psbt, Transaction as BTransaction } from 'bitcoinjs-lib'; import { Psbt, Transaction as BTransaction } from 'bitcoinjs-lib';
import { CoinSelectReturnInput, CoinSelectTarget } from 'coinselect'; import { CoinSelectReturnInput, CoinSelectTarget } from 'coinselect';
import ecc from '../../blue_modules/noble_ecc'; import ecc from '../../blue_modules/noble_ecc';
import BIP47Factory, { BIP47Interface } from '@spsina/bip47';
import { ECPairFactory } from 'ecpair';
import { randomBytes } from '../rng';
import { AbstractHDWallet } from './abstract-hd-wallet';
import { CreateTransactionResult, CreateTransactionUtxo, Transaction, Utxo } from './types';
import { ElectrumHistory } from '../../blue_modules/BlueElectrum';
import type BlueElectrumNs from '../../blue_modules/BlueElectrum';
const ECPair = ECPairFactory(ecc); const ECPair = ECPairFactory(ecc);
const bitcoin = require('bitcoinjs-lib'); const bitcoin = require('bitcoinjs-lib');
const BlueElectrum: typeof BlueElectrumNs = require('../../blue_modules/BlueElectrum'); const BlueElectrum: typeof BlueElectrumNs = require('../../blue_modules/BlueElectrum');
const reverse = require('buffer-reverse'); const reverse = require('buffer-reverse');
const bip32 = BIP32Factory(ecc); const bip32 = BIP32Factory(ecc);
const bip47 = BIP47Factory(ecc);
type BalanceByIndex = { type BalanceByIndex = {
c: number; c: number;
@ -45,6 +49,16 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
_utxo: any[]; _utxo: any[];
// BIP47
_enable_BIP47: boolean;
_payment_code: string;
_sender_payment_codes: string[];
_addresses_by_payment_code: Record<string, string[]>;
_next_free_payment_code_address_index: Record<string, number>;
_txs_by_payment_code_index: Record<string, Transaction[][]>;
_balances_by_payment_code_index: Record<string, BalanceByIndex>;
_bip47_instance?: BIP47Interface;
constructor() { constructor() {
super(); super();
this._balances_by_external_index = {}; // 0 => { c: 0, u: 0 } // confirmed/unconfirmed this._balances_by_external_index = {}; // 0 => { c: 0, u: 0 } // confirmed/unconfirmed
@ -54,6 +68,15 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
this._txs_by_internal_index = {}; this._txs_by_internal_index = {};
this._utxo = []; this._utxo = [];
// BIP47
this._enable_BIP47 = false;
this._payment_code = '';
this._sender_payment_codes = [];
this._next_free_payment_code_address_index = {};
this._txs_by_payment_code_index = {};
this._balances_by_payment_code_index = {};
this._addresses_by_payment_code = {};
} }
/** /**
@ -67,6 +90,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (const bal of Object.values(this._balances_by_internal_index)) { for (const bal of Object.values(this._balances_by_internal_index)) {
ret += bal.c; ret += bal.c;
} }
for (const pc of this._sender_payment_codes) {
ret += this._getBalancesByPaymentCodeIndex(pc).c;
}
return ret + (this.getUnconfirmedBalance() < 0 ? this.getUnconfirmedBalance() : 0); return ret + (this.getUnconfirmedBalance() < 0 ? this.getUnconfirmedBalance() : 0);
} }
@ -82,6 +108,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (const bal of Object.values(this._balances_by_internal_index)) { for (const bal of Object.values(this._balances_by_internal_index)) {
ret += bal.u; ret += bal.u;
} }
for (const pc of this._sender_payment_codes) {
ret += this._getBalancesByPaymentCodeIndex(pc).u;
}
return ret; return ret;
} }
@ -121,7 +150,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
return child.toWIF(); return child.toWIF();
} }
_getNodeAddressByIndex(node: number, index: number) { _getNodeAddressByIndex(node: number, index: number): string {
index = index * 1; // cast to int index = index * 1; // cast to int
if (node === 0) { if (node === 0) {
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
@ -143,22 +172,20 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
this._node1 = hdNode.derive(node); this._node1 = hdNode.derive(node);
} }
let address; let address: string;
if (node === 0) { if (node === 0) {
// @ts-ignore // @ts-ignore
address = this.constructor._nodeToBech32SegwitAddress(this._node0.derive(index)); address = this._hdNodeToAddress(this._node0.derive(index));
} } else {
// tbh the only possible else is node === 1
if (node === 1) {
// @ts-ignore // @ts-ignore
address = this.constructor._nodeToBech32SegwitAddress(this._node1.derive(index)); address = this._hdNodeToAddress(this._node1.derive(index));
} }
if (node === 0) { if (node === 0) {
return (this.external_addresses_cache[index] = address); return (this.external_addresses_cache[index] = address);
} } else {
// tbh the only possible else option is node === 1
if (node === 1) {
return (this.internal_addresses_cache[index] = address); return (this.internal_addresses_cache[index] = address);
} }
} }
@ -189,7 +216,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
throw new Error('Internal error: this._node0 or this._node1 is undefined'); throw new Error('Internal error: this._node0 or this._node1 is undefined');
} }
_getExternalAddressByIndex(index: number) { _getExternalAddressByIndex(index: number): string {
return this._getNodeAddressByIndex(0, index); return this._getNodeAddressByIndex(0, index);
} }
@ -266,6 +293,21 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
} }
} }
// next, bip47 addresses
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
let hasUnconfirmed = false;
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
for (const tx of this._txs_by_payment_code_index[pc][c])
hasUnconfirmed = hasUnconfirmed || !tx.confirmations || tx.confirmations < 7;
if (hasUnconfirmed || this._txs_by_payment_code_index[pc][c].length === 0 || this._balances_by_payment_code_index[pc].u !== 0) {
addresses2fetch.push(this._getBIP47Address(pc, c));
}
}
}
// first: batch fetch for all addresses histories // first: batch fetch for all addresses histories
const histories = await BlueElectrum.multiGetHistoryByAddress(addresses2fetch); const histories = await BlueElectrum.multiGetHistoryByAddress(addresses2fetch);
const txs: Record<string, ElectrumHistory> = {}; const txs: Record<string, ElectrumHistory> = {};
@ -296,7 +338,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
const inpTxid = txdatas[txid].vin[inpNum].txid; const inpTxid = txdatas[txid].vin[inpNum].txid;
const inpVout = txdatas[txid].vin[inpNum].vout; const inpVout = txdatas[txid].vin[inpNum].vout;
// got txid and output number of _previous_ transaction we shoud look into // got txid and output number of _previous_ transaction we shoud look into
if (vintxdatas[inpTxid] && vintxdatas[inpTxid].vout[inpVout]) { if (vintxdatas[inpTxid]?.vout[inpVout]) {
// extracting amount & addresses from previous output and adding it to _our_ input: // extracting amount & addresses from previous output and adding it to _our_ input:
txdatas[txid].vin[inpNum].addresses = vintxdatas[inpTxid].vout[inpVout].scriptPubKey.addresses; txdatas[txid].vin[inpNum].addresses = vintxdatas[inpTxid].vout[inpVout].scriptPubKey.addresses;
txdatas[txid].vin[inpNum].value = vintxdatas[inpTxid].vout[inpVout].value; txdatas[txid].vin[inpNum].value = vintxdatas[inpTxid].vout[inpVout].value;
@ -312,6 +354,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
this._txs_by_internal_index[c] = this._txs_by_internal_index[c].filter(tx => !!tx.confirmations); this._txs_by_internal_index[c] = this._txs_by_internal_index[c].filter(tx => !!tx.confirmations);
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c].filter(tx => !!tx.confirmations);
}
}
// now, we need to put transactions in all relevant `cells` of internal hashmaps: this._txs_by_internal_index && this._txs_by_external_index // now, we need to put transactions in all relevant `cells` of internal hashmaps: this._txs_by_internal_index && this._txs_by_external_index
@ -397,6 +444,51 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
} }
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
for (const tx of Object.values(txdatas)) {
for (const vin of tx.vin) {
if (vin.addresses && vin.addresses.indexOf(this._getBIP47Address(pc, c)) !== -1) {
// this TX is related to our address
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = { ...txRest, inputs: txVin.slice(0), outputs: txVout.slice(0) };
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_payment_code_index[pc][c].length; cc++) {
if (this._txs_by_payment_code_index[pc][c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_payment_code_index[pc][c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_payment_code_index[pc][c].push(clonedTx);
}
}
for (const vout of tx.vout) {
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses.indexOf(this._getBIP47Address(pc, c)) !== -1) {
// this TX is related to our address
this._txs_by_payment_code_index[pc] = this._txs_by_payment_code_index[pc] || {};
this._txs_by_payment_code_index[pc][c] = this._txs_by_payment_code_index[pc][c] || [];
const { vin: txVin, vout: txVout, ...txRest } = tx;
const clonedTx = { ...txRest, inputs: txVin.slice(0), outputs: txVout.slice(0) };
// trying to replace tx if it exists already (because it has lower confirmations, for example)
let replaced = false;
for (let cc = 0; cc < this._txs_by_internal_index[c].length; cc++) {
if (this._txs_by_internal_index[c][cc].txid === clonedTx.txid) {
replaced = true;
this._txs_by_internal_index[c][cc] = clonedTx;
}
}
if (!replaced) this._txs_by_internal_index[c].push(clonedTx);
}
}
}
}
}
this._lastTxFetch = +new Date(); this._lastTxFetch = +new Date();
} }
@ -409,6 +501,14 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (const addressTxs of Object.values(this._txs_by_internal_index)) { for (const addressTxs of Object.values(this._txs_by_internal_index)) {
txs = txs.concat(addressTxs); txs = txs.concat(addressTxs);
} }
if (this._sender_payment_codes) {
for (const pc of this._sender_payment_codes) {
if (this._txs_by_payment_code_index[pc])
for (const addressTxs of Object.values(this._txs_by_payment_code_index[pc])) {
txs = txs.concat(addressTxs);
}
}
}
if (txs.length === 0) return []; // guard clause; so we wont spend time calculating addresses if (txs.length === 0) return []; // guard clause; so we wont spend time calculating addresses
@ -421,6 +521,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + 1; c++) { for (let c = 0; c < this.next_free_change_address_index + 1; c++) {
ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true; ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true;
} }
if (this._sender_payment_codes)
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 1; c++) {
ownedAddressesHashmap[this._getBIP47Address(pc, c)] = true;
}
}
// hack: in case this code is called from LegacyWallet: // hack: in case this code is called from LegacyWallet:
if (this.getAddress()) ownedAddressesHashmap[String(this.getAddress())] = true; if (this.getAddress()) ownedAddressesHashmap[String(this.getAddress())] = true;
@ -434,17 +540,14 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (const vin of tx.inputs) { for (const vin of tx.inputs) {
// if input (spending) goes from our address - we are loosing! // if input (spending) goes from our address - we are loosing!
if ( if ((vin.address && ownedAddressesHashmap[vin.address]) || (vin.addresses?.[0] && ownedAddressesHashmap[vin.addresses[0]])) {
(vin.address && ownedAddressesHashmap[vin.address]) ||
(vin.addresses && vin.addresses[0] && ownedAddressesHashmap[vin.addresses[0]])
) {
tx.value -= new BigNumber(vin.value ?? 0).multipliedBy(100000000).toNumber(); tx.value -= new BigNumber(vin.value ?? 0).multipliedBy(100000000).toNumber();
} }
} }
for (const vout of tx.outputs) { for (const vout of tx.outputs) {
// when output goes to our address - this means we are gaining! // when output goes to our address - this means we are gaining!
if (vout.scriptPubKey.addresses && vout.scriptPubKey.addresses[0] && ownedAddressesHashmap[vout.scriptPubKey.addresses[0]]) { if (vout.scriptPubKey.addresses?.[0] && ownedAddressesHashmap[vout.scriptPubKey.addresses[0]]) {
tx.value += new BigNumber(vout.value).multipliedBy(100000000).toNumber(); tx.value += new BigNumber(vout.value).multipliedBy(100000000).toNumber();
} }
} }
@ -499,7 +602,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
) { ) {
const address = this._getInternalAddressByIndex(c); const address = this._getInternalAddressByIndex(c);
if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) { if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) {
lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unsued lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unused
} }
} }
} }
@ -542,7 +645,50 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
) { ) {
const address = this._getExternalAddressByIndex(c); const address = this._getExternalAddressByIndex(c);
if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) { if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) {
lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unsued lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unused
}
}
}
return lastUsedIndex;
}
async _binarySearchIterationForBIP47Address(paymentCode: string, index: number) {
const generateChunkAddresses = (chunkNum: number) => {
const ret = [];
for (let c = this.gap_limit * chunkNum; c < this.gap_limit * (chunkNum + 1); c++) {
ret.push(this._getBIP47Address(paymentCode, c));
}
return ret;
};
let lastChunkWithUsedAddressesNum = null;
let lastHistoriesWithUsedAddresses = null;
for (let c = 0; c < Math.round(index / this.gap_limit); c++) {
const histories = await BlueElectrum.multiGetHistoryByAddress(generateChunkAddresses(c));
// @ts-ignore
if (this.constructor._getTransactionsFromHistories(histories).length > 0) {
// in this particular chunk we have used addresses
lastChunkWithUsedAddressesNum = c;
lastHistoriesWithUsedAddresses = histories;
} else {
// empty chunk. no sense searching more chunks
break;
}
}
let lastUsedIndex = 0;
if (lastHistoriesWithUsedAddresses) {
// now searching for last used address in batch lastChunkWithUsedAddressesNum
for (
let c = Number(lastChunkWithUsedAddressesNum) * this.gap_limit;
c < Number(lastChunkWithUsedAddressesNum) * this.gap_limit + this.gap_limit;
c++
) {
const address = this._getBIP47Address(paymentCode, c);
if (lastHistoriesWithUsedAddresses[address] && lastHistoriesWithUsedAddresses[address].length > 0) {
lastUsedIndex = Math.max(c, lastUsedIndex) + 1; // point to next, which is supposed to be unused
} }
} }
} }
@ -556,6 +702,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
// doing binary search for last used address: // doing binary search for last used address:
this.next_free_change_address_index = await this._binarySearchIterationForInternalAddress(1000); this.next_free_change_address_index = await this._binarySearchIterationForInternalAddress(1000);
this.next_free_address_index = await this._binarySearchIterationForExternalAddress(1000); this.next_free_address_index = await this._binarySearchIterationForExternalAddress(1000);
if (this._sender_payment_codes) {
for (const pc of this._sender_payment_codes) {
this._next_free_payment_code_address_index[pc] = await this._binarySearchIterationForBIP47Address(pc, 1000);
}
}
} // end rescanning fresh wallet } // end rescanning fresh wallet
// finally fetching balance // finally fetching balance
@ -577,6 +728,15 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = this.next_free_change_address_index; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = this.next_free_change_address_index; c < this.next_free_change_address_index + this.gap_limit; c++) {
lagAddressesToFetch.push(this._getInternalAddressByIndex(c)); lagAddressesToFetch.push(this._getInternalAddressByIndex(c));
} }
for (const pc of this._sender_payment_codes) {
for (
let c = this._next_free_payment_code_address_index[pc];
c < this._next_free_payment_code_address_index[pc] + this.gap_limit;
c++
) {
lagAddressesToFetch.push(this._getBIP47Address(pc, c));
}
}
const txs = await BlueElectrum.multiGetHistoryByAddress(lagAddressesToFetch); // <------ electrum call const txs = await BlueElectrum.multiGetHistoryByAddress(lagAddressesToFetch); // <------ electrum call
@ -596,6 +756,20 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
} }
} }
for (const pc of this._sender_payment_codes) {
for (
let c = this._next_free_payment_code_address_index[pc];
c < this._next_free_payment_code_address_index[pc] + this.gap_limit;
c++
) {
const address = this._getBIP47Address(pc, c);
if (txs[address] && Array.isArray(txs[address]) && txs[address].length > 0) {
// whoa, someone uses our wallet outside! better catch up
this._next_free_payment_code_address_index[pc] = c + 1;
}
}
}
// next, business as usuall. fetch balances // next, business as usuall. fetch balances
const addresses2fetch = []; const addresses2fetch = [];
@ -614,6 +788,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
addresses2fetch.push(this._getInternalAddressByIndex(c)); addresses2fetch.push(this._getInternalAddressByIndex(c));
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._next_free_payment_code_address_index[pc] + this.gap_limit; c++) {
addresses2fetch.push(this._getBIP47Address(pc, c));
}
}
const balances = await BlueElectrum.multiGetBalanceByAddress(addresses2fetch); const balances = await BlueElectrum.multiGetBalanceByAddress(addresses2fetch);
// converting to a more compact internal format // converting to a more compact internal format
@ -658,6 +838,22 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
} }
} }
for (const pc of this._sender_payment_codes) {
let confirmed = 0;
let unconfirmed = 0;
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
const addr = this._getBIP47Address(pc, c);
if (balances.addresses[addr].confirmed || balances.addresses[addr].unconfirmed) {
confirmed = confirmed + balances.addresses[addr].confirmed;
unconfirmed = unconfirmed + balances.addresses[addr].unconfirmed;
}
}
this._balances_by_payment_code_index[pc] = {
c: confirmed,
u: unconfirmed,
};
}
this._lastBalanceFetch = +new Date(); this._lastBalanceFetch = +new Date();
} }
@ -667,28 +863,44 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
// considering confirmed balance: // considering confirmed balance:
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._balances_by_external_index[c] && this._balances_by_external_index[c].c && this._balances_by_external_index[c].c > 0) { if (this._balances_by_external_index?.[c]?.c > 0) {
addressess.push(this._getExternalAddressByIndex(c)); addressess.push(this._getExternalAddressByIndex(c));
} }
} }
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._balances_by_internal_index[c] && this._balances_by_internal_index[c].c && this._balances_by_internal_index[c].c > 0) { if (this._balances_by_internal_index?.[c]?.c > 0) {
addressess.push(this._getInternalAddressByIndex(c)); addressess.push(this._getInternalAddressByIndex(c));
} }
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._next_free_payment_code_address_index[pc] + this.gap_limit; c++) {
if (this._balances_by_payment_code_index?.[pc]?.c > 0) {
addressess.push(this._getBIP47Address(pc, c));
}
}
}
// considering UNconfirmed balance: // considering UNconfirmed balance:
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._balances_by_external_index[c] && this._balances_by_external_index[c].u && this._balances_by_external_index[c].u > 0) { if (this._balances_by_external_index?.[c]?.u > 0) {
addressess.push(this._getExternalAddressByIndex(c)); addressess.push(this._getExternalAddressByIndex(c));
} }
} }
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._balances_by_internal_index[c] && this._balances_by_internal_index[c].u && this._balances_by_internal_index[c].u > 0) { if (this._balances_by_internal_index?.[c]?.u > 0) {
addressess.push(this._getInternalAddressByIndex(c)); addressess.push(this._getInternalAddressByIndex(c));
} }
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._next_free_payment_code_address_index[pc] + this.gap_limit; c++) {
if (this._balances_by_payment_code_index?.[pc]?.u > 0) {
addressess.push(this._getBIP47Address(pc, c));
}
}
}
// note: we could remove checks `.c` and `.u` to simplify code, but the resulting `addressess` array would be bigger, thus bigger batch // note: we could remove checks `.c` and `.u` to simplify code, but the resulting `addressess` array would be bigger, thus bigger batch
// to fetch (or maybe even several fetches), which is not critical but undesirable. // to fetch (or maybe even several fetches), which is not critical but undesirable.
// anyway, result has `.confirmations` property for each utxo, so outside caller can easily filter out unconfirmed if he wants to // anyway, result has `.confirmations` property for each utxo, so outside caller can easily filter out unconfirmed if he wants to
@ -756,6 +968,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + 1; c++) { for (let c = 0; c < this.next_free_change_address_index + 1; c++) {
ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true; ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true;
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + 1; c++) {
ownedAddressesHashmap[this._getBIP47Address(pc, c)] = true;
}
}
for (const tx of this.getTransactions()) { for (const tx of this.getTransactions()) {
for (const output of tx.outputs) { for (const output of tx.outputs) {
@ -811,6 +1028,12 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._getInternalAddressByIndex(c) === address) return path + '/1/' + c; if (this._getInternalAddressByIndex(c) === address) return path + '/1/' + c;
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
// not technically correct but well, to have at least somethign in PSBT...
if (this._getBIP47Address(pc, c) === address) return "m/47'/0'/0'/" + c;
}
}
return false; return false;
} }
@ -827,6 +1050,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._getInternalAddressByIndex(c) === address) return this._getNodePubkeyByIndex(1, c); if (this._getInternalAddressByIndex(c) === address) return this._getNodePubkeyByIndex(1, c);
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
if (this._getBIP47Address(pc, c) === address) return this._getBIP47PubkeyByIndex(pc, c);
}
}
return false; return false;
} }
@ -844,6 +1072,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._getInternalAddressByIndex(c) === address) return this._getWIFByIndex(true, c); if (this._getInternalAddressByIndex(c) === address) return this._getWIFByIndex(true, c);
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
if (this._getBIP47Address(pc, c) === address) return this._getBIP47WIF(pc, c);
}
}
return false; return false;
} }
@ -861,6 +1094,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._getInternalAddressByIndex(c) === cleanAddress) return true; if (this._getInternalAddressByIndex(c) === cleanAddress) return true;
} }
for (const pc of this._sender_payment_codes) {
for (let c = 0; c < this._getNextFreePaymentCodeAddress(pc) + this.gap_limit; c++) {
if (this._getBIP47Address(pc, c) === address) return true;
}
}
return false; return false;
} }
@ -1052,22 +1290,29 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
/** /**
* Creates Segwit Bech32 Bitcoin address * Creates Segwit Bech32 Bitcoin address
*
* @param hdNode
* @returns {String}
*/ */
static _nodeToBech32SegwitAddress(hdNode: BIP32Interface) { _nodeToBech32SegwitAddress(hdNode: BIP32Interface): string {
return bitcoin.payments.p2wpkh({ return bitcoin.payments.p2wpkh({
pubkey: hdNode.publicKey, pubkey: hdNode.publicKey,
}).address; }).address;
} }
static _nodeToLegacyAddress(hdNode: BIP32Interface) { _nodeToLegacyAddress(hdNode: BIP32Interface): string {
return bitcoin.payments.p2pkh({ return bitcoin.payments.p2pkh({
pubkey: hdNode.publicKey, pubkey: hdNode.publicKey,
}).address; }).address;
} }
/**
* Creates Segwit P2SH Bitcoin address
*/
_nodeToP2shSegwitAddress(hdNode: BIP32Interface): string {
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey }),
});
return address;
}
static _getTransactionsFromHistories(histories: Record<string, ElectrumHistory[]>) { static _getTransactionsFromHistories(histories: Record<string, ElectrumHistory[]>) {
const txs = []; const txs = [];
for (const history of Object.values(histories)) { for (const history of Object.values(histories)) {
@ -1080,14 +1325,29 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
/** /**
* Probes zero address in external hierarchy for transactions, if there are any returns TRUE. * Probes zero address in external hierarchy for transactions, if there are any returns TRUE.
* Zero address is a pretty good indicator, since its a first one to fund the wallet. How can you use the wallet and * Zero address is a pretty good indicator, since its a first one to fund the wallet.
* not fund it first? * Q: How can you use the wallet and not fund it first?
* A: You can if it is a BIP47 wallet!
* *
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async wasEverUsed() { async wasEverUsed(): Promise<boolean> {
const txs = await BlueElectrum.getTransactionsByAddress(this._getExternalAddressByIndex(0)); const txs1 = await BlueElectrum.getTransactionsByAddress(this._getExternalAddressByIndex(0));
return txs.length > 0; if (txs1.length > 0) {
return true;
}
if (!this.allowBIP47()) {
return false;
}
// only check BIP47 if derivation path is regular, otherwise too many wallets will be found
if (!["m/84'/0'/0'", "m/44'/0'/0'", "m/49'/0'/0'"].includes(this.getDerivationPath() as string)) {
return false;
}
const bip47_instance = this.getBIP47FromSeed();
const address = bip47_instance.getNotificationAddress();
const txs2 = await BlueElectrum.getTransactionsByAddress(address);
return txs2.length > 0;
} }
/** /**
@ -1192,4 +1452,114 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
const seed = this._getSeed(); const seed = this._getSeed();
return AbstractHDElectrumWallet.seedToFingerprint(seed); return AbstractHDElectrumWallet.seedToFingerprint(seed);
} }
/**
* Whether BIP47 is enabled. This is per-wallet setting that can be changed, NOT a feature-flag
* @returns boolean
*/
isBIP47Enabled(): boolean {
return this._enable_BIP47;
}
switchBIP47(value: boolean): void {
this._enable_BIP47 = value;
}
getBIP47FromSeed(): BIP47Interface {
if (!this._bip47_instance || !this._bip47_instance.getNotificationAddress) {
this._bip47_instance = bip47.fromBip39Seed(this.secret, undefined, this.passphrase);
}
return this._bip47_instance;
}
getBIP47PaymentCode(): string {
if (!this._payment_code) {
this._payment_code = this.getBIP47FromSeed().getSerializedPaymentCode();
}
return this._payment_code;
}
getBIP47NotificationAddress(): string {
const bip47 = this.getBIP47FromSeed();
return bip47.getNotificationAddress();
}
async fetchBIP47SenderPaymentCodes(): Promise<void> {
const bip47_instance = this.getBIP47FromSeed();
const address = bip47_instance.getNotificationAddress();
const histories = await BlueElectrum.multiGetHistoryByAddress([address]);
const txHashes = histories[address].map(({ tx_hash }) => tx_hash);
const txHexs = await BlueElectrum.multiGetTransactionByTxid(txHashes, 50, false);
for (const txHex of Object.values(txHexs)) {
try {
const paymentCode = bip47_instance.getPaymentCodeFromRawNotificationTransaction(txHex);
if (this._sender_payment_codes.includes(paymentCode)) continue; // already have it
// final check if PC is even valid (could've been constructed by a buggy code, and our code would crash with that):
try {
BIP47Factory(ecc).fromPaymentCode(paymentCode);
} catch (_) {
continue;
}
this._sender_payment_codes.push(paymentCode);
this._next_free_payment_code_address_index[paymentCode] = 0; // initialize
this._balances_by_payment_code_index[paymentCode] = { c: 0, u: 0 };
} catch (e) {
// do nothing
}
}
}
getBIP47SenderPaymentCodes(): string[] {
return this._sender_payment_codes;
}
_hdNodeToAddress(hdNode: BIP32Interface): string {
return this._nodeToBech32SegwitAddress(hdNode);
}
_getBIP47Address(paymentCode: string, index: number): string {
if (!this._addresses_by_payment_code[paymentCode]) this._addresses_by_payment_code[paymentCode] = [];
if (this._addresses_by_payment_code[paymentCode][index]) {
return this._addresses_by_payment_code[paymentCode][index];
}
const bip47_instance = this.getBIP47FromSeed();
const senderBIP47_instance = bip47.fromPaymentCode(paymentCode);
const remotePaymentNode = senderBIP47_instance.getPaymentCodeNode();
const hdNode = bip47_instance.getPaymentWallet(remotePaymentNode, index);
const address = this._hdNodeToAddress(hdNode);
this._address_to_wif_cache[address] = hdNode.toWIF();
this._addresses_by_payment_code[paymentCode][index] = address;
return address;
}
_getNextFreePaymentCodeAddress(paymentCode: string) {
return this._next_free_payment_code_address_index[paymentCode] || 0;
}
_getBalancesByPaymentCodeIndex(paymentCode: string): BalanceByIndex {
return this._balances_by_payment_code_index[paymentCode] || { c: 0, u: 0 };
}
_getBIP47WIF(paymentCode: string, index: number): string {
const bip47_instance = this.getBIP47FromSeed();
const senderBIP47_instance = bip47.fromPaymentCode(paymentCode);
const remotePaymentNode = senderBIP47_instance.getPaymentCodeNode();
const hdNode = bip47_instance.getPaymentWallet(remotePaymentNode, index);
return hdNode.toWIF();
}
_getBIP47PubkeyByIndex(paymentCode: string, index: number): Buffer {
const bip47_instance = this.getBIP47FromSeed();
const senderBIP47_instance = bip47.fromPaymentCode(paymentCode);
const remotePaymentNode = senderBIP47_instance.getPaymentCodeNode();
const hdNode = bip47_instance.getPaymentWallet(remotePaymentNode, index);
return hdNode.publicKey;
}
} }

View file

@ -143,6 +143,18 @@ export class AbstractWallet {
return BitcoinUnit.BTC; return BitcoinUnit.BTC;
} }
async allowOnchainAddress(): Promise<boolean> {
throw new Error('allowOnchainAddress: Not implemented');
}
allowBIP47(): boolean {
return false;
}
switchBIP47(value: boolean): void {
throw new Error('switchBIP47: not implemented');
}
allowReceive(): boolean { allowReceive(): boolean {
return true; return true;
} }
@ -223,6 +235,16 @@ export class AbstractWallet {
this._derivationPath = derivationPath; this._derivationPath = derivationPath;
} }
this.secret = m[2]; this.secret = m[2];
if (derivationPath.startsWith("m/84'/0'/") && this.secret.toLowerCase().startsWith('xpub')) {
// need to convert xpub to zpub
this.secret = this._xpubToZpub(this.secret);
}
if (derivationPath.startsWith("m/49'/0'/") && this.secret.toLowerCase().startsWith('xpub')) {
// need to convert xpub to ypub
this.secret = this._xpubToYpub(this.secret);
}
} }
try { try {
@ -348,6 +370,10 @@ export class AbstractWallet {
return false; return false;
} }
isBIP47Enabled(): boolean {
return false;
}
async wasEverUsed(): Promise<boolean> { async wasEverUsed(): Promise<boolean> {
throw new Error('Not implemented'); throw new Error('Not implemented');
} }
@ -390,6 +416,22 @@ export class AbstractWallet {
return b58.encode(data); return b58.encode(data);
} }
_xpubToZpub(xpub: string): string {
let data = b58.decode(xpub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]);
return b58.encode(data);
}
_xpubToYpub(xpub: string): string {
let data = b58.decode(xpub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('049d7cb2', 'hex'), data]);
return b58.encode(data);
}
prepareForSerialization(): void {} prepareForSerialization(): void {}
/* /*

View file

@ -23,6 +23,10 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
return mn.validateMnemonic(this.secret, PREFIX); return mn.validateMnemonic(this.secret, PREFIX);
} }
allowBIP47() {
return false;
}
async generate() { async generate() {
throw new Error('Not implemented'); throw new Error('Not implemented');
} }

View file

@ -34,6 +34,10 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
return true; return true;
} }
allowBIP47() {
return true;
}
getXpub() { getXpub() {
if (this._xpub) { if (this._xpub) {
return this._xpub; // cache hit return this._xpub; // cache hit
@ -48,44 +52,8 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
return this._xpub; return this._xpub;
} }
_getNodeAddressByIndex(node, index) { _hdNodeToAddress(hdNode) {
index = index * 1; // cast to int return this._nodeToLegacyAddress(hdNode);
if (node === 0) {
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
}
if (node === 1) {
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
}
if (node === 0 && !this._node0) {
const xpub = this.getXpub();
const hdNode = bip32.fromBase58(xpub);
this._node0 = hdNode.derive(node);
}
if (node === 1 && !this._node1) {
const xpub = this.getXpub();
const hdNode = bip32.fromBase58(xpub);
this._node1 = hdNode.derive(node);
}
let address;
if (node === 0) {
address = this.constructor._nodeToLegacyAddress(this._node0.derive(index));
}
if (node === 1) {
address = this.constructor._nodeToLegacyAddress(this._node1.derive(index));
}
if (node === 0) {
return (this.external_addresses_cache[index] = address);
}
if (node === 1) {
return (this.internal_addresses_cache[index] = address);
}
} }
async fetchUtxo() { async fetchUtxo() {

View file

@ -46,4 +46,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
allowXpub() { allowXpub() {
return true; return true;
} }
allowBIP47() {
return true;
}
} }

View file

@ -24,6 +24,10 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
return mn.validateMnemonic(this.secret, PREFIX); return mn.validateMnemonic(this.secret, PREFIX);
} }
allowBIP47() {
return false;
}
async generate() { async generate() {
throw new Error('Not implemented'); throw new Error('Not implemented');
} }

View file

@ -40,44 +40,8 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
return true; return true;
} }
_getNodeAddressByIndex(node, index) { _hdNodeToAddress(hdNode) {
index = index * 1; // cast to int return this._nodeToP2shSegwitAddress(hdNode);
if (node === 0) {
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
}
if (node === 1) {
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
}
if (node === 0 && !this._node0) {
const xpub = this.constructor._ypubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node0 = hdNode.derive(0);
}
if (node === 1 && !this._node1) {
const xpub = this.constructor._ypubToXpub(this.getXpub());
const hdNode = bip32.fromBase58(xpub);
this._node1 = hdNode.derive(1);
}
let address;
if (node === 0) {
address = this.constructor._nodeToP2shSegwitAddress(this._node0.derive(index));
}
if (node === 1) {
address = this.constructor._nodeToP2shSegwitAddress(this._node1.derive(index));
}
if (node === 0) {
return (this.external_addresses_cache[index] = address);
}
if (node === 1) {
return (this.internal_addresses_cache[index] = address);
}
} }
/** /**
@ -134,18 +98,6 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
return psbt; return psbt;
} }
/**
* Creates Segwit P2SH Bitcoin address
* @param hdNode
* @returns {String}
*/
static _nodeToP2shSegwitAddress(hdNode) {
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey }),
});
return address;
}
isSegwit() { isSegwit() {
return true; return true;
} }

View file

@ -115,7 +115,7 @@ export class LightningCustodianWallet extends LegacyWallet {
async payInvoice(invoice, freeAmount = 0) { async payInvoice(invoice, freeAmount = 0) {
const response = await this._api.post('/payinvoice', { const response = await this._api.post('/payinvoice', {
body: { invoice: invoice, amount: freeAmount }, body: { invoice, amount: freeAmount },
headers: { headers: {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -215,7 +215,7 @@ export class LightningCustodianWallet extends LegacyWallet {
async addInvoice(amt, memo) { async addInvoice(amt, memo) {
const response = await this._api.post('/addinvoice', { const response = await this._api.post('/addinvoice', {
body: { amt: amt + '', memo: memo }, body: { amt: amt + '', memo },
headers: { headers: {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -254,7 +254,7 @@ export class LightningCustodianWallet extends LegacyWallet {
password = this.secret.replace('lndhub://', '').split(':')[1]; password = this.secret.replace('lndhub://', '').split(':')[1];
} }
const response = await this._api.post('/auth?type=auth', { const response = await this._api.post('/auth?type=auth', {
body: { login: login, password: password }, body: { login, password },
headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' }, headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json' },
}); });

View file

@ -567,11 +567,11 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
} }
if (secret.indexOf('sortedmulti(') !== -1 && json.descriptor) { if (secret.indexOf('sortedmulti(') !== -1 && json.descriptor) {
if (json.label) this.setLabel(json.label); if (json.label) this.setLabel(json.label);
if (json.descriptor.startsWith('wsh(')) { if (json.descriptor.includes('sh(wsh(')) {
this.setNativeSegwit();
} else if (json.descriptor.startsWith('sh(wsh(')) {
this.setWrappedSegwit(); this.setWrappedSegwit();
} else if (json.descriptor.startsWith('sh(')) { } else if (json.descriptor.includes('wsh(')) {
this.setNativeSegwit();
} else if (json.descriptor.includes('sh(')) {
this.setLegacy(); this.setLegacy();
} }
@ -624,8 +624,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
for (const pk of json.extendedPublicKeys) { for (const pk of json.extendedPublicKeys) {
const path = this.constructor.isPathValid(json.bip32Path) ? json.bip32Path : "m/1'"; const path = this.constructor.isPathValid(json.bip32Path) ? json.bip32Path : "m/1'";
// wtf, where caravan stores fingerprints..? this.addCosigner(pk.xpub, pk.xfp ?? '00000000', path);
this.addCosigner(pk.xpub, '00000000', path);
} }
} }

View file

@ -67,6 +67,10 @@ export class SLIP39LegacyP2PKHWallet extends HDLegacyP2PKHWallet {
static type = 'SLIP39legacyP2PKH'; static type = 'SLIP39legacyP2PKH';
static typeReadable = 'SLIP39 Legacy (P2PKH)'; static typeReadable = 'SLIP39 Legacy (P2PKH)';
allowBIP47() {
return false;
}
_getSeed = SLIP39Mixin._getSeed; _getSeed = SLIP39Mixin._getSeed;
validateMnemonic = SLIP39Mixin.validateMnemonic; validateMnemonic = SLIP39Mixin.validateMnemonic;
setSecret = SLIP39Mixin.setSecret; setSecret = SLIP39Mixin.setSecret;
@ -87,6 +91,10 @@ export class SLIP39SegwitBech32Wallet extends HDSegwitBech32Wallet {
static type = 'SLIP39segwitBech32'; static type = 'SLIP39segwitBech32';
static typeReadable = 'SLIP39 SegWit (Bech32)'; static typeReadable = 'SLIP39 SegWit (Bech32)';
allowBIP47() {
return false;
}
_getSeed = SLIP39Mixin._getSeed; _getSeed = SLIP39Mixin._getSeed;
validateMnemonic = SLIP39Mixin.validateMnemonic; validateMnemonic = SLIP39Mixin.validateMnemonic;
setSecret = SLIP39Mixin.setSecret; setSecret = SLIP39Mixin.setSecret;

View file

@ -100,6 +100,7 @@ export class WatchOnlyWallet extends LegacyWallet {
if (this._hdWalletInstance) { if (this._hdWalletInstance) {
delete this._hdWalletInstance._node0; delete this._hdWalletInstance._node0;
delete this._hdWalletInstance._node1; delete this._hdWalletInstance._node1;
delete this._hdWalletInstance._bip47_instance;
} }
} }

View file

@ -238,7 +238,12 @@ class AmountInput extends Component {
}); });
return ( return (
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}> <TouchableWithoutFeedback
accessibilityRole="button"
accessibilityLabel={loc._.enter_amount}
disabled={this.props.pointerEvents === 'none'}
onPress={() => this.textInput.focus()}
>
<> <>
<View style={styles.root}> <View style={styles.root}>
{!disabled && <View style={[styles.center, stylesHook.center]} />} {!disabled && <View style={[styles.center, stylesHook.center]} />}
@ -289,6 +294,7 @@ class AmountInput extends Component {
{!disabled && amount !== BitcoinUnit.MAX && ( {!disabled && amount !== BitcoinUnit.MAX && (
<TouchableOpacity <TouchableOpacity
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={loc._.change_input_currency}
testID="changeAmountUnitButton" testID="changeAmountUnitButton"
style={styles.changeAmountUnit} style={styles.changeAmountUnit}
onPress={this.changeAmountUnit} onPress={this.changeAmountUnit}
@ -306,6 +312,8 @@ class AmountInput extends Component {
</BlueText> </BlueText>
<View style={styles.spacing8} /> <View style={styles.spacing8} />
<TouchableOpacity <TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.refresh}
onPress={this.updateRate} onPress={this.updateRate}
disabled={this.state.isRateBeingUpdated} disabled={this.state.isRateBeingUpdated}
style={this.state.isRateBeingUpdated ? styles.disabledButton : styles.enabledButon} style={this.state.isRateBeingUpdated ? styles.disabledButton : styles.enabledButon}

View file

@ -30,6 +30,8 @@ export const ArrowPicker = (props: ArrowPickerProps) => {
return ( return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Pressable <Pressable
accessibilityRole="button"
accessibilityLabel={loc.send.dynamic_prev}
onPress={() => { onPress={() => {
Keyboard.dismiss(); Keyboard.dismiss();
let newIndex = keyIndex; let newIndex = keyIndex;
@ -48,12 +50,16 @@ export const ArrowPicker = (props: ArrowPickerProps) => {
styles.wrapperCustom, styles.wrapperCustom,
]} ]}
> >
{/*
// @ts-ignore: Ignore */}
<Icon size={24} name="chevron-left" type="ionicons" tvParallaxProperties={undefined} /> <Icon size={24} name="chevron-left" type="ionicons" tvParallaxProperties={undefined} />
</Pressable> </Pressable>
<View style={{ width: 200 }}> <View style={{ width: 200 }}>
<Text style={[styles.text, stylesHook.text]}>{props.isItemUnknown ? loc.send.fee_custom : keys[keyIndex]}</Text> <Text style={[styles.text, stylesHook.text]}>{props.isItemUnknown ? loc.send.fee_custom : keys[keyIndex]}</Text>
</View> </View>
<Pressable <Pressable
accessibilityRole="button"
accessibilityLabel={loc.send.dynamic_next}
onPress={() => { onPress={() => {
Keyboard.dismiss(); Keyboard.dismiss();
let newIndex = keyIndex; let newIndex = keyIndex;
@ -72,6 +78,8 @@ export const ArrowPicker = (props: ArrowPickerProps) => {
styles.wrapperCustom, styles.wrapperCustom,
]} ]}
> >
{/*
// @ts-ignore: Ignore */}
<Icon size={24} name="chevron-right" type="ionicons" tvParallaxProperties={undefined} /> <Icon size={24} name="chevron-right" type="ionicons" tvParallaxProperties={undefined} />
</Pressable> </Pressable>
</View> </View>

View file

@ -38,7 +38,7 @@ const Button = props => {
const opacity = { opacity: disabled ? 0.5 : 1.0 }; const opacity = { opacity: disabled ? 0.5 : 1.0 };
return ( return (
<TouchableOpacity onPress={onPress} disabled={disabled} style={opacity}> <TouchableOpacity accessibilityRole="button" onPress={onPress} disabled={disabled} style={opacity}>
<View style={[styles.buttonContainer, buttonStyles()]}> <View style={[styles.buttonContainer, buttonStyles()]}>
<Text style={[styles.text, textStyles()]}>{text}</Text> <Text style={[styles.text, textStyles()]}>{text}</Text>
</View> </View>

View file

@ -7,7 +7,7 @@ import React from 'react';
export const LdkButton = props => { export const LdkButton = props => {
const { colors } = useTheme(); const { colors } = useTheme();
return ( return (
<TouchableOpacity onPress={props.onPress}> <TouchableOpacity accessibilityRole="button" onPress={props.onPress}>
<View <View
style={{ style={{
borderColor: (props.active && colors.lnborderColor) || colors.buttonDisabledBackgroundColor, borderColor: (props.active && colors.lnborderColor) || colors.buttonDisabledBackgroundColor,

View file

@ -133,7 +133,7 @@ const MultipleStepsListItem = props => {
</> </>
)} )}
{!showActivityIndicator && props.rightButton && checked && ( {!showActivityIndicator && props.rightButton && checked && (
<View style={styles.rightButtonContainer} accessibilityComponentType> <View style={styles.rightButtonContainer}>
<TouchableOpacity <TouchableOpacity
accessibilityRole="button" accessibilityRole="button"
disabled={props.rightButton.disabled} disabled={props.rightButton.disabled}

View file

@ -4,12 +4,58 @@ import QRCode from 'react-native-qrcode-svg';
import ToolTipMenu from './TooltipMenu'; import ToolTipMenu from './TooltipMenu';
import Share from 'react-native-share'; import Share from 'react-native-share';
import loc from '../loc'; import loc from '../loc';
import PropTypes from 'prop-types';
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import { useTheme } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native';
const QRCodeComponent = ({ interface QRCodeComponentProps {
value, value: string;
isLogoRendered?: boolean;
isMenuAvailable?: boolean;
logoSize?: number;
size?: number;
ecl?: 'H' | 'Q' | 'M' | 'L';
onError?: () => void;
}
interface ActionIcons {
iconType: 'SYSTEM';
iconValue: string;
}
interface ActionType {
Share: 'share';
Copy: 'copy';
}
interface Action {
id: string;
text: string;
icon: ActionIcons;
}
const actionKeys: ActionType = {
Share: 'share',
Copy: 'copy',
};
interface ActionIcons {
iconType: 'SYSTEM';
iconValue: string;
}
const actionIcons: { [key: string]: ActionIcons } = {
Share: {
iconType: 'SYSTEM',
iconValue: 'square.and.arrow.up',
},
Copy: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
};
const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
value = '',
isLogoRendered = true, isLogoRendered = true,
isMenuAvailable = true, isMenuAvailable = true,
logoSize = 90, logoSize = 90,
@ -17,39 +63,39 @@ const QRCodeComponent = ({
ecl = 'H', ecl = 'H',
onError = () => {}, onError = () => {},
}) => { }) => {
const qrCode = useRef(); const qrCode = useRef<any>();
const { colors } = useTheme(); const { colors } = useTheme();
const handleShareQRCode = () => { const handleShareQRCode = () => {
qrCode.current.toDataURL(data => { qrCode.current.toDataURL((data: string) => {
const shareImageBase64 = { const shareImageBase64 = {
url: `data:image/png;base64,${data}`, url: `data:image/png;base64,${data}`,
}; };
Share.open(shareImageBase64).catch(error => console.log(error)); Share.open(shareImageBase64).catch((error: any) => console.log(error));
}); });
}; };
const onPressMenuItem = id => { const onPressMenuItem = (id: string) => {
if (id === QRCodeComponent.actionKeys.Share) { if (id === actionKeys.Share) {
handleShareQRCode(); handleShareQRCode();
} else if (id === QRCodeComponent.actionKeys.Copy) { } else if (id === actionKeys.Copy) {
qrCode.current.toDataURL(Clipboard.setImage); qrCode.current.toDataURL(Clipboard.setImage);
} }
}; };
const menuActions = () => { const menuActions = (): Action[] => {
const actions = []; const actions: Action[] = [];
if (Platform.OS === 'ios' || Platform.OS === 'macos') { if (Platform.OS === 'ios' || Platform.OS === 'macos') {
actions.push({ actions.push({
id: QRCodeComponent.actionKeys.Copy, id: actionKeys.Copy,
text: loc.transactions.details_copy, text: loc.transactions.details_copy,
icon: QRCodeComponent.actionIcons.Copy, icon: actionIcons.Copy,
}); });
} }
actions.push({ actions.push({
id: QRCodeComponent.actionKeys.Share, id: actionKeys.Share,
text: loc.receive.details_share, text: loc.receive.details_share,
icon: QRCodeComponent.actionIcons.Share, icon: actionIcons.Share,
}); });
return actions; return actions;
}; };
@ -61,10 +107,11 @@ const QRCodeComponent = ({
size={size} size={size}
logoSize={logoSize} logoSize={logoSize}
color="#000000" color="#000000"
// @ts-ignore: logoBackgroundColor is not in the type definition
logoBackgroundColor={colors.brandingColor} logoBackgroundColor={colors.brandingColor}
backgroundColor="#FFFFFF" backgroundColor="#FFFFFF"
ecl={ecl} ecl={ecl}
getRef={qrCode} getRef={(c: any) => (qrCode.current = c)}
onError={onError} onError={onError}
/> />
); );
@ -87,29 +134,3 @@ export default QRCodeComponent;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
qrCodeContainer: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' }, qrCodeContainer: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' },
}); });
QRCodeComponent.actionKeys = {
Share: 'share',
Copy: 'copy',
};
QRCodeComponent.actionIcons = {
Share: {
iconType: 'SYSTEM',
iconValue: 'square.and.arrow.up',
},
Copy: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
};
QRCodeComponent.propTypes = {
value: PropTypes.string.isRequired,
isMenuAvailable: PropTypes.bool,
size: PropTypes.number,
ecl: PropTypes.string,
isLogoRendered: PropTypes.bool,
onError: PropTypes.func,
logoSize: PropTypes.number,
};

View file

@ -19,7 +19,7 @@ export const SquareButton = forwardRef((props, ref) => {
flex: 1, flex: 1,
borderWidth: 0.7, borderWidth: 0.7,
borderColor: 'transparent', borderColor: 'transparent',
backgroundColor: backgroundColor, backgroundColor,
minHeight: 50, minHeight: 50,
height: 50, height: 50,
maxHeight: 50, maxHeight: 50,

View file

@ -59,11 +59,18 @@ const ToolTipMenu = (props, ref) => {
}} }}
style={buttonStyle} style={buttonStyle}
> >
{props.onPress ? <TouchableOpacity onPress={props.onPress}>{props.children}</TouchableOpacity> : props.children} {props.onPress ? (
<TouchableOpacity accessibilityRole="button" onPress={props.onPress}>
{props.children}
</TouchableOpacity>
) : (
props.children
)}
</ContextMenuButton> </ContextMenuButton>
) : ( ) : (
<ContextMenuView <ContextMenuView
ref={ref} ref={ref}
internalCleanupMode="viewController"
onPressMenuItem={({ nativeEvent }) => { onPressMenuItem={({ nativeEvent }) => {
props.onPressMenuItem(nativeEvent.actionKey); props.onPressMenuItem(nativeEvent.actionKey);
}} }}
@ -81,7 +88,13 @@ const ToolTipMenu = (props, ref) => {
} }
: {})} : {})}
> >
{props.onPress ? <TouchableOpacity onPress={props.onPress}>{props.children}</TouchableOpacity> : props.children} {props.onPress ? (
<TouchableOpacity accessibilityRole="button" onPress={props.onPress}>
{props.children}
</TouchableOpacity>
) : (
props.children
)}
</ContextMenuView> </ContextMenuView>
); );
}; };

View file

@ -1,305 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Image, Text, TouchableOpacity, View, InteractionManager, I18nManager, StyleSheet } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import { LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class';
import { BitcoinUnit } from '../models/bitcoinUnits';
import WalletGradient from '../class/wallet-gradient';
import Biometric from '../class/biometrics';
import loc, { formatBalance } from '../loc';
import { BlueStorageContext } from '../blue_modules/storage-context';
import ToolTipMenu from './TooltipMenu';
import { BluePrivateBalance } from '../BlueComponents';
export default class TransactionsNavigationHeader extends Component {
static propTypes = {
wallet: PropTypes.shape().isRequired,
onWalletUnitChange: PropTypes.func,
navigation: PropTypes.shape(),
onManageFundsPressed: PropTypes.func,
};
static actionKeys = {
CopyToClipboard: 'copyToClipboard',
WalletBalanceVisibility: 'walletBalanceVisibility',
Refill: 'refill',
RefillWithExternalWallet: 'qrcode',
};
static actionIcons = {
Eye: {
iconType: 'SYSTEM',
iconValue: 'eye',
},
EyeSlash: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
Clipboard: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
Refill: {
iconType: 'SYSTEM',
iconValue: 'goforward.plus',
},
RefillWithExternalWallet: {
iconType: 'SYSTEM',
iconValue: 'qrcode',
},
};
static getDerivedStateFromProps(props) {
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange };
}
static contextType = BlueStorageContext;
constructor(props) {
super(props);
this.state = {
wallet: props.wallet,
walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit(),
allowOnchainAddress: false,
};
}
menuRef = React.createRef();
handleCopyPress = _item => {
Clipboard.setString(formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString());
};
componentDidUpdate(prevState) {
InteractionManager.runAfterInteractions(() => {
if (prevState.wallet.getID() !== this.state.wallet.getID() && this.state.wallet.type === LightningCustodianWallet.type) {
this.verifyIfWalletAllowsOnchainAddress();
}
});
}
verifyIfWalletAllowsOnchainAddress = () => {
if (this.state.wallet.type === LightningCustodianWallet.type) {
this.state.wallet
.allowOnchainAddress()
.then(value => this.setState({ allowOnchainAddress: value }))
.catch(e => {
console.log('This Lndhub wallet does not have an onchain address API.');
this.setState({ allowOnchainAddress: false });
});
}
};
componentDidMount() {
this.verifyIfWalletAllowsOnchainAddress();
}
handleBalanceVisibility = async _item => {
const wallet = this.state.wallet;
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled && wallet.hideBalance) {
if (!(await Biometric.unlockWithBiometrics())) {
return this.props.navigation.goBack();
}
}
wallet.hideBalance = !wallet.hideBalance;
this.setState({ wallet });
await this.context.saveToDisk();
};
changeWalletBalanceUnit = () => {
this.menuRef.current?.dismissMenu();
let walletPreviousPreferredUnit = this.state.wallet.getPreferredBalanceUnit();
const wallet = this.state.wallet;
if (walletPreviousPreferredUnit === BitcoinUnit.BTC) {
wallet.preferredBalanceUnit = BitcoinUnit.SATS;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
} else if (walletPreviousPreferredUnit === BitcoinUnit.SATS) {
wallet.preferredBalanceUnit = BitcoinUnit.LOCAL_CURRENCY;
walletPreviousPreferredUnit = BitcoinUnit.SATS;
} else if (walletPreviousPreferredUnit === BitcoinUnit.LOCAL_CURRENCY) {
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
} else {
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
}
this.setState({ wallet, walletPreviousPreferredUnit: walletPreviousPreferredUnit }, () => {
this.props.onWalletUnitChange(wallet);
});
};
manageFundsPressed = id => {
this.props.onManageFundsPressed(id);
};
onPressMenuItem = id => {
if (id === TransactionsNavigationHeader.actionKeys.WalletBalanceVisibility) {
this.handleBalanceVisibility();
} else if (id === TransactionsNavigationHeader.actionKeys.CopyToClipboard) {
this.handleCopyPress();
}
};
toolTipMenuActions = [
{
id: TransactionsNavigationHeader.actionKeys.Refill,
text: loc.lnd.refill,
icon: TransactionsNavigationHeader.actionIcons.Refill,
},
{
id: TransactionsNavigationHeader.actionKeys.RefillWithExternalWallet,
text: loc.lnd.refill_external,
icon: TransactionsNavigationHeader.actionIcons.RefillWithExternalWallet,
},
];
render() {
const balance =
!this.state.wallet.hideBalance &&
formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString();
return (
<LinearGradient
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
style={styles.lineaderGradient}
{...WalletGradient.linearGradientProps(this.state.wallet.type)}
>
<Image
source={(() => {
switch (this.state.wallet.type) {
case LightningLdkWallet.type:
case LightningCustodianWallet.type:
return I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png');
case MultisigHDWallet.type:
return I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png');
default:
return I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png');
}
})()}
style={styles.chainIcon}
/>
<Text testID="WalletLabel" numberOfLines={1} style={styles.walletLabel}>
{this.state.wallet.getLabel()}
</Text>
<ToolTipMenu
onPress={this.changeWalletBalanceUnit}
ref={this.menuRef}
title={loc.wallets.balance}
onPressMenuItem={this.onPressMenuItem}
actions={
this.state.wallet.hideBalance
? [
{
id: TransactionsNavigationHeader.actionKeys.WalletBalanceVisibility,
text: loc.transactions.details_balance_show,
icon: TransactionsNavigationHeader.actionIcons.Eye,
},
]
: [
{
id: TransactionsNavigationHeader.actionKeys.WalletBalanceVisibility,
text: loc.transactions.details_balance_hide,
icon: TransactionsNavigationHeader.actionIcons.EyeSlash,
},
{
id: TransactionsNavigationHeader.actionKeys.CopyToClipboard,
text: loc.transactions.details_copy,
icon: TransactionsNavigationHeader.actionIcons.Clipboard,
},
]
}
>
<View style={styles.balance}>
{this.state.wallet.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
testID="WalletBalance"
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
numberOfLines={1}
adjustsFontSizeToFit
style={styles.walletBalance}
>
{balance}
</Text>
)}
</View>
</ToolTipMenu>
{this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && (
<ToolTipMenu
isMenuPrimaryAction
isButton
onPressMenuItem={this.manageFundsPressed}
actions={this.toolTipMenuActions}
buttonStyle={styles.manageFundsButton}
>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</ToolTipMenu>
)}
{this.state.wallet.type === LightningLdkWallet.type && (
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
<View style={styles.manageFundsButton}>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</View>
</TouchableOpacity>
)}
{this.state.wallet.type === MultisigHDWallet.type && (
<TouchableOpacity accessibilityRole="button" onPress={this.manageFundsPressed}>
<View style={styles.manageFundsButton}>
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
</View>
</TouchableOpacity>
)}
</LinearGradient>
);
}
}
const styles = StyleSheet.create({
lineaderGradient: {
padding: 15,
minHeight: 140,
justifyContent: 'center',
},
chainIcon: {
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
},
walletLabel: {
backgroundColor: 'transparent',
fontSize: 19,
color: '#fff',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
},
walletBalance: {
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: '#fff',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
},
manageFundsButton: {
marginTop: 14,
marginBottom: 10,
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 9,
minHeight: 39,
alignSelf: 'flex-start',
justifyContent: 'center',
alignItems: 'center',
},
manageFundsButtonText: {
fontWeight: '500',
fontSize: 14,
color: '#FFFFFF',
padding: 12,
},
});

View file

@ -0,0 +1,315 @@
import React, { useState, useEffect, useRef, useContext, useCallback, useMemo } from 'react';
import { Image, Text, TouchableOpacity, View, I18nManager, StyleSheet } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import { AbstractWallet, LightningCustodianWallet, LightningLdkWallet, MultisigHDWallet } from '../class';
import { BitcoinUnit } from '../models/bitcoinUnits';
import WalletGradient from '../class/wallet-gradient';
import Biometric from '../class/biometrics';
import loc, { formatBalance } from '../loc';
import { BlueStorageContext } from '../blue_modules/storage-context';
import ToolTipMenu from './TooltipMenu';
import { BluePrivateBalance } from '../BlueComponents';
interface TransactionsNavigationHeaderProps {
wallet: AbstractWallet;
onWalletUnitChange?: (wallet: any) => void;
navigation: {
navigate: (route: string, params?: any) => void;
goBack: () => void;
};
onManageFundsPressed?: (id: string) => void; // Add a type definition for this prop
actionKeys: {
CopyToClipboard: 'copyToClipboard';
WalletBalanceVisibility: 'walletBalanceVisibility';
Refill: 'refill';
RefillWithExternalWallet: 'qrcode';
};
}
const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps> = ({
// @ts-ignore: Ugh
wallet: initialWallet,
// @ts-ignore: Ugh
onWalletUnitChange,
// @ts-ignore: Ugh
navigation,
// @ts-ignore: Ugh
onManageFundsPressed,
}) => {
const [wallet, setWallet] = useState(initialWallet);
const [allowOnchainAddress, setAllowOnchainAddress] = useState(false);
const context = useContext(BlueStorageContext);
const menuRef = useRef(null);
const verifyIfWalletAllowsOnchainAddress = useCallback(() => {
if (wallet.type === LightningCustodianWallet.type) {
wallet
.allowOnchainAddress()
.then((value: boolean) => setAllowOnchainAddress(value))
.catch((e: any) => {
console.log('This Lndhub wallet does not have an onchain address API.');
setAllowOnchainAddress(false);
});
}
}, [wallet]);
useEffect(() => {
verifyIfWalletAllowsOnchainAddress();
}, [wallet, verifyIfWalletAllowsOnchainAddress]);
const handleCopyPress = () => {
Clipboard.setString(formatBalance(wallet.getBalance(), wallet.getPreferredBalanceUnit()).toString());
};
const updateWalletVisibility = (wallet: AbstractWallet, newHideBalance: boolean) => {
wallet.hideBalance = newHideBalance;
return wallet;
};
const handleBalanceVisibility = async () => {
// @ts-ignore: Gotta update this class
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled && wallet.hideBalance) {
// @ts-ignore: Ugh
if (!(await Biometric.unlockWithBiometrics())) {
return navigation.goBack();
}
}
const updatedWallet = updateWalletVisibility(wallet, !wallet.hideBalance);
setWallet(updatedWallet);
context.saveToDisk();
};
const updateWalletWithNewUnit = (wallet: AbstractWallet, newPreferredUnit: BitcoinUnit) => {
wallet.preferredBalanceUnit = newPreferredUnit;
return wallet;
};
const changeWalletBalanceUnit = () => {
// @ts-ignore: Ugh
menuRef.current?.dismissMenu();
let newWalletPreferredUnit = wallet.getPreferredBalanceUnit();
if (newWalletPreferredUnit === BitcoinUnit.BTC) {
newWalletPreferredUnit = BitcoinUnit.SATS;
} else if (newWalletPreferredUnit === BitcoinUnit.SATS) {
newWalletPreferredUnit = BitcoinUnit.LOCAL_CURRENCY;
} else {
newWalletPreferredUnit = BitcoinUnit.BTC;
}
const updatedWallet = updateWalletWithNewUnit(wallet, newWalletPreferredUnit);
setWallet(updatedWallet);
onWalletUnitChange?.(updatedWallet);
};
const handleManageFundsPressed = () => {
onManageFundsPressed?.(actionKeys.Refill);
};
const onPressMenuItem = (id: string) => {
if (id === 'walletBalanceVisibility') {
handleBalanceVisibility();
} else if (id === 'copyToClipboard') {
handleCopyPress();
}
};
const balance = useMemo(() => {
const hideBalance = wallet.hideBalance;
const balanceUnit = wallet.getPreferredBalanceUnit();
const balanceFormatted = formatBalance(wallet.getBalance(), balanceUnit, true).toString();
return !hideBalance && balanceFormatted;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.hideBalance, wallet.getPreferredBalanceUnit()]);
return (
<LinearGradient
colors={WalletGradient.gradientsFor(wallet.type)}
style={styles.lineaderGradient}
// @ts-ignore: Ugh
{...WalletGradient.linearGradientProps(wallet.type)}
>
<Image
source={(() => {
switch (wallet.type) {
case LightningLdkWallet.type:
case LightningCustodianWallet.type:
return I18nManager.isRTL ? require('../img/lnd-shape-rtl.png') : require('../img/lnd-shape.png');
case MultisigHDWallet.type:
return I18nManager.isRTL ? require('../img/vault-shape-rtl.png') : require('../img/vault-shape.png');
default:
return I18nManager.isRTL ? require('../img/btc-shape-rtl.png') : require('../img/btc-shape.png');
}
})()}
style={styles.chainIcon}
/>
<Text testID="WalletLabel" numberOfLines={1} style={styles.walletLabel}>
{wallet.getLabel()}
</Text>
<ToolTipMenu
onPress={changeWalletBalanceUnit}
ref={menuRef}
title={loc.wallets.balance}
onPressMenuItem={onPressMenuItem}
actions={
wallet.hideBalance
? [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_show,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye',
},
},
]
: [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_hide,
icon: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
},
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
icon: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
},
]
}
>
<View style={styles.walletBalance}>
{wallet.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
testID="WalletBalance"
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
numberOfLines={1}
adjustsFontSizeToFit
style={styles.walletBalance}
>
{balance}
</Text>
)}
</View>
</ToolTipMenu>
{wallet.type === LightningCustodianWallet.type && allowOnchainAddress && (
<ToolTipMenu
isMenuPrimaryAction
isButton
onPressMenuItem={handleManageFundsPressed}
actions={[
{
id: actionKeys.Refill,
text: loc.lnd.refill,
icon: actionIcons.Refill,
},
{
id: actionKeys.RefillWithExternalWallet,
text: loc.lnd.refill_external,
icon: actionIcons.RefillWithExternalWallet,
},
]}
buttonStyle={styles.manageFundsButton}
>
<Text style={styles.manageFundsButtonText}>{loc.lnd.title}</Text>
</ToolTipMenu>
)}
{wallet.type === MultisigHDWallet.type && (
<TouchableOpacity accessibilityRole="button" onPress={handleManageFundsPressed}>
<View style={styles.manageFundsButton}>
<Text style={styles.manageFundsButtonText}>{loc.multisig.manage_keys}</Text>
</View>
</TouchableOpacity>
)}
</LinearGradient>
);
};
const styles = StyleSheet.create({
lineaderGradient: {
padding: 15,
minHeight: 140,
justifyContent: 'center',
},
chainIcon: {
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
},
walletLabel: {
backgroundColor: 'transparent',
fontSize: 19,
color: '#fff',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
},
walletBalance: {
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: '#fff',
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
},
manageFundsButton: {
marginTop: 14,
marginBottom: 10,
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 9,
minHeight: 39,
alignSelf: 'flex-start',
justifyContent: 'center',
alignItems: 'center',
},
manageFundsButtonText: {
fontWeight: '500',
fontSize: 14,
color: '#FFFFFF',
padding: 12,
},
});
export const actionKeys = {
CopyToClipboard: 'copyToClipboard',
WalletBalanceVisibility: 'walletBalanceVisibility',
Refill: 'refill',
RefillWithExternalWallet: 'qrcode',
};
export const actionIcons = {
Eye: {
iconType: 'SYSTEM',
iconValue: 'eye',
},
EyeSlash: {
iconType: 'SYSTEM',
iconValue: 'eye.slash',
},
Clipboard: {
iconType: 'SYSTEM',
iconValue: 'doc.on.doc',
},
Refill: {
iconType: 'SYSTEM',
iconValue: 'goforward.plus',
},
RefillWithExternalWallet: {
iconType: 'SYSTEM',
iconValue: 'qrcode',
},
};
export default TransactionsNavigationHeader;

View file

@ -203,6 +203,7 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
shadowRadius={8} shadowRadius={8}
> >
<TouchableWithoutFeedback <TouchableWithoutFeedback
accessibilityRole="button"
testID={item.getLabel()} testID={item.getLabel()}
onPressIn={onPressedIn} onPressIn={onPressedIn}
onPressOut={onPressedOut} onPressOut={onPressedOut}
@ -311,7 +312,7 @@ const WalletsCarousel = forwardRef((props, ref) => {
const { width } = useWindowDimensions(); const { width } = useWindowDimensions();
const sliderHeight = 195; const sliderHeight = 195;
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
return ( return isHandset ? (
<FlatList <FlatList
ref={flatListRef} ref={flatListRef}
renderItem={renderItem} renderItem={renderItem}
@ -331,6 +332,19 @@ const WalletsCarousel = forwardRef((props, ref) => {
onScrollToIndexFailed={onScrollToIndexFailed} onScrollToIndexFailed={onScrollToIndexFailed}
{...props} {...props}
/> />
) : (
<View style={cStyles.contentLargeScreen}>
{props.data.map((item, index) => (
<WalletCarouselItem
isSelectedWallet={!props.horizontal && props.selectedWallet && item ? props.selectedWallet === item.getID() : undefined}
item={item}
index={index}
handleLongPress={props.handleLongPress}
onPress={props.onPress}
key={index}
/>
))}
</View>
); );
}); });

View file

@ -1,21 +0,0 @@
import React, { useContext } from 'react';
import Handoff from 'react-native-handoff';
import { BlueStorageContext } from '../blue_modules/storage-context';
import PropTypes from 'prop-types';
const HandoffComponent = props => {
const { isHandOffUseEnabled } = useContext(BlueStorageContext);
return isHandOffUseEnabled ? <Handoff {...props} /> : null;
};
export default HandoffComponent;
HandoffComponent.propTypes = {
url: PropTypes.string,
};
HandoffComponent.activityTypes = {
ReceiveOnchain: 'io.bluewallet.bluewallet.receiveonchain',
Xpub: 'io.bluewallet.bluewallet.xpub',
ViewInBlockExplorer: 'io.bluewallet.bluewallet.blockexplorer',
};

35
components/handoff.tsx Normal file
View file

@ -0,0 +1,35 @@
import React, { useContext } from 'react';
// @ts-ignore: react-native-handoff is not in the type definition
import Handoff from 'react-native-handoff';
import { BlueStorageContext } from '../blue_modules/storage-context';
interface HandoffComponentProps {
url?: string;
}
interface HandoffComponentWithActivityTypes extends React.FC<HandoffComponentProps> {
activityTypes: {
ReceiveOnchain: string;
Xpub: string;
ViewInBlockExplorer: string;
};
}
const HandoffComponent: HandoffComponentWithActivityTypes = props => {
const { isHandOffUseEnabled } = useContext(BlueStorageContext);
if (isHandOffUseEnabled) {
return <Handoff {...props} />;
}
return null;
};
const activityTypes = {
ReceiveOnchain: 'io.bluewallet.bluewallet.receiveonchain',
Xpub: 'io.bluewallet.bluewallet.xpub',
ViewInBlockExplorer: 'io.bluewallet.bluewallet.blockexplorer',
};
HandoffComponent.activityTypes = activityTypes;
export default HandoffComponent;

View file

@ -2,6 +2,7 @@
import React from 'react'; import React from 'react';
import { Image, Keyboard, TouchableOpacity, StyleSheet } from 'react-native'; import { Image, Keyboard, TouchableOpacity, StyleSheet } from 'react-native';
import { Theme } from './themes'; import { Theme } from './themes';
import loc from '../loc';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
button: { button: {
@ -41,6 +42,7 @@ const navigationStyle = (
...opts ...opts
}: NavigationOptions & { }: NavigationOptions & {
closeButton?: boolean; closeButton?: boolean;
closeButtonFunc?: (deps: { navigation: any; route: any }) => React.ReactElement; closeButtonFunc?: (deps: { navigation: any; route: any }) => React.ReactElement;
}, },
formatter: OptionsFormatter, formatter: OptionsFormatter,
@ -56,7 +58,13 @@ const navigationStyle = (
navigation.goBack(null); navigation.goBack(null);
}; };
headerRight = () => ( headerRight = () => (
<TouchableOpacity accessibilityRole="button" style={styles.button} onPress={handleClose} testID="NavigationCloseButton"> <TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.close}
style={styles.button}
onPress={handleClose}
testID="NavigationCloseButton"
>
<Image source={theme.closeImage} /> <Image source={theme.closeImage} />
</TouchableOpacity> </TouchableOpacity>
); );
@ -73,7 +81,7 @@ const navigationStyle = (
fontWeight: '600', fontWeight: '600',
color: theme.colors.foregroundColor, color: theme.colors.foregroundColor,
}, },
headerRight: headerRight, headerRight,
headerBackTitleVisible: false, headerBackTitleVisible: false,
headerTintColor: theme.colors.foregroundColor, headerTintColor: theme.colors.foregroundColor,
...opts, ...opts,
@ -108,6 +116,7 @@ export const navigationStyleTx = (opts: NavigationOptions, formatter: OptionsFor
headerLeft: () => ( headerLeft: () => (
<TouchableOpacity <TouchableOpacity
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={loc._.close}
style={styles.button} style={styles.button}
onPress={() => { onPress={() => {
Keyboard.dismiss(); Keyboard.dismiss();

View file

@ -47,7 +47,7 @@ module.exports = (
]; ];
prompt(title, text, buttons, { prompt(title, text, buttons, {
type: type, type,
cancelable: isCancelable, cancelable: isCancelable,
// @ts-ignore suppressed because its supported only on ios and is absent from type definitions // @ts-ignore suppressed because its supported only on ios and is absent from type definitions
keyboardType, keyboardType,

1
ios/.xcode.env Normal file
View file

@ -0,0 +1 @@
export NODE_BINARY=$(command -v node)

View file

@ -7,7 +7,6 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
32002D9D236FAA9F00B93396 /* TodayDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32002D9C236FAA9F00B93396 /* TodayDataStore.swift */; }; 32002D9D236FAA9F00B93396 /* TodayDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32002D9C236FAA9F00B93396 /* TodayDataStore.swift */; };
@ -56,7 +55,6 @@
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B9D9B3A7B2CB4255876B67AF /* libz.tbd */; }; 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B9D9B3A7B2CB4255876B67AF /* libz.tbd */; };
849047CA2702A32A008EE567 /* Handoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849047C92702A32A008EE567 /* Handoff.swift */; }; 849047CA2702A32A008EE567 /* Handoff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849047C92702A32A008EE567 /* Handoff.swift */; };
84E05A842721191B001A0D3A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 84E05A832721191B001A0D3A /* Settings.bundle */; }; 84E05A842721191B001A0D3A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 84E05A832721191B001A0D3A /* Settings.bundle */; };
8CBB9CC9ACE4B44B45C28DF8 /* libPods-WidgetsExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E45F9A25C15BEA362225C9B /* libPods-WidgetsExtension.a */; };
906451CAD44154C2950030EC /* libPods-BlueWallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */; }; 906451CAD44154C2950030EC /* libPods-BlueWallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */; };
B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E32225841EC00428FCC /* Interface.storyboard */; }; B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E32225841EC00428FCC /* Interface.storyboard */; };
B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
@ -70,12 +68,14 @@
B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E5B2258425500428FCC /* ReceiveInterfaceController.swift */; }; B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E5B2258425500428FCC /* ReceiveInterfaceController.swift */; };
B40D4E642258425500428FCC /* WalletDetailsInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E5C2258425500428FCC /* WalletDetailsInterfaceController.swift */; }; B40D4E642258425500428FCC /* WalletDetailsInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E5C2258425500428FCC /* WalletDetailsInterfaceController.swift */; };
B40D4E682258426B00428FCC /* KeychainSwiftDistrib.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */; }; B40D4E682258426B00428FCC /* KeychainSwiftDistrib.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */; };
B40FC3FA29CCD1D00007EBAC /* SwiftTCPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */; };
B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0372225847C500FBAA95 /* WalletGradient.swift */; }; B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0372225847C500FBAA95 /* WalletGradient.swift */; };
B43D0379225847C500FBAA95 /* WatchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0373225847C500FBAA95 /* WatchDataSource.swift */; }; B43D0379225847C500FBAA95 /* WatchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0373225847C500FBAA95 /* WatchDataSource.swift */; };
B43D037A225847C500FBAA95 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0374225847C500FBAA95 /* Transaction.swift */; }; B43D037A225847C500FBAA95 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0374225847C500FBAA95 /* Transaction.swift */; };
B43D037B225847C500FBAA95 /* TransactionTableRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0375225847C500FBAA95 /* TransactionTableRow.swift */; }; B43D037B225847C500FBAA95 /* TransactionTableRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0375225847C500FBAA95 /* TransactionTableRow.swift */; };
B43D037C225847C500FBAA95 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0376225847C500FBAA95 /* Wallet.swift */; }; B43D037C225847C500FBAA95 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0376225847C500FBAA95 /* Wallet.swift */; };
B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0377225847C500FBAA95 /* WalletInformation.swift */; }; B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0377225847C500FBAA95 /* WalletInformation.swift */; };
B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; };
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; };
E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; };
@ -170,8 +170,6 @@
00E356F21AD99517003FC87E /* BlueWalletTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BlueWalletTests.m; sourceTree = "<group>"; }; 00E356F21AD99517003FC87E /* BlueWalletTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BlueWalletTests.m; sourceTree = "<group>"; };
04466491BA2D4876A71222FC /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; }; 04466491BA2D4876A71222FC /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* BlueWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07F961A680F5B00A75B9A /* BlueWallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlueWallet.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = BlueWallet/AppDelegate.m; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = BlueWallet/Images.xcassets; sourceTree = "<group>"; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = BlueWallet/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = BlueWallet/Info.plist; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = BlueWallet/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = BlueWallet/main.m; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = BlueWallet/main.m; sourceTree = "<group>"; };
@ -290,12 +288,10 @@
78A87E7251D94144A71A2F67 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = "<group>"; }; 78A87E7251D94144A71A2F67 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = "<group>"; };
7B468CC34D5B41F3950078EF /* libsqlite3.0.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; }; 7B468CC34D5B41F3950078EF /* libsqlite3.0.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; };
7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WalletInformationAndMarketWidgetExtension/Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; sourceTree = "<group>"; }; 7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WalletInformationAndMarketWidgetExtension/Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
7E45F9A25C15BEA362225C9B /* libPods-WidgetsExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WidgetsExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; };
8448882949434D41A054C0B2 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = "<group>"; }; 8448882949434D41A054C0B2 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = "<group>"; };
849047C92702A32A008EE567 /* Handoff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Handoff.swift; sourceTree = "<group>"; }; 849047C92702A32A008EE567 /* Handoff.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Handoff.swift; sourceTree = "<group>"; };
84E05A832721191B001A0D3A /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; }; 84E05A832721191B001A0D3A /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
8637D4B5E14D443A9031DA95 /* libRNFS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFS.a; sourceTree = "<group>"; }; 8637D4B5E14D443A9031DA95 /* libRNFS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFS.a; sourceTree = "<group>"; };
8C30FBCA42C6BF0B45757763 /* Pods-WidgetsExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WidgetsExtension/Pods-WidgetsExtension.debug.xcconfig"; sourceTree = "<group>"; };
90F86BC5194548CA87D729A9 /* libToolTipMenu.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libToolTipMenu.a; sourceTree = "<group>"; }; 90F86BC5194548CA87D729A9 /* libToolTipMenu.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libToolTipMenu.a; sourceTree = "<group>"; };
94565BFC6A0C4235B3EC7B01 /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = "<group>"; }; 94565BFC6A0C4235B3EC7B01 /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = "<group>"; };
95208B2A05884A76B5BB99C0 /* libRCTGoogleAnalyticsBridge.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTGoogleAnalyticsBridge.a; sourceTree = "<group>"; }; 95208B2A05884A76B5BB99C0 /* libRCTGoogleAnalyticsBridge.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTGoogleAnalyticsBridge.a; sourceTree = "<group>"; };
@ -322,6 +318,7 @@
B40D4E5B2258425500428FCC /* ReceiveInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceController.swift; sourceTree = "<group>"; }; B40D4E5B2258425500428FCC /* ReceiveInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceController.swift; sourceTree = "<group>"; };
B40D4E5C2258425500428FCC /* WalletDetailsInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsInterfaceController.swift; sourceTree = "<group>"; }; B40D4E5C2258425500428FCC /* WalletDetailsInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsInterfaceController.swift; sourceTree = "<group>"; };
B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainSwiftDistrib.swift; sourceTree = SOURCE_ROOT; }; B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainSwiftDistrib.swift; sourceTree = SOURCE_ROOT; };
B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTCPClient.swift; sourceTree = "<group>"; };
B43B69B8225C462E00925B1E /* libPods-RCTLinking.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libPods-RCTLinking.a"; sourceTree = BUILT_PRODUCTS_DIR; }; B43B69B8225C462E00925B1E /* libPods-RCTLinking.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libPods-RCTLinking.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B43B69BA225C46D800925B1E /* libRCTLinking.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRCTLinking.a; sourceTree = BUILT_PRODUCTS_DIR; }; B43B69BA225C46D800925B1E /* libRCTLinking.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRCTLinking.a; sourceTree = BUILT_PRODUCTS_DIR; };
B43D0372225847C500FBAA95 /* WalletGradient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletGradient.swift; sourceTree = "<group>"; }; B43D0372225847C500FBAA95 /* WalletGradient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletGradient.swift; sourceTree = "<group>"; };
@ -332,6 +329,8 @@
B43D0377225847C500FBAA95 /* WalletInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletInformation.swift; sourceTree = "<group>"; }; B43D0377225847C500FBAA95 /* WalletInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletInformation.swift; sourceTree = "<group>"; };
B43D046E22584C1B00FBAA95 /* libRNWatch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRNWatch.a; sourceTree = BUILT_PRODUCTS_DIR; }; B43D046E22584C1B00FBAA95 /* libRNWatch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRNWatch.a; sourceTree = BUILT_PRODUCTS_DIR; };
B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.release.xcconfig"; path = "Pods/Target Support Files/Pods-BlueWallet/Pods-BlueWallet.release.xcconfig"; sourceTree = "<group>"; }; B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.release.xcconfig"; path = "Pods/Target Support Files/Pods-BlueWallet/Pods-BlueWallet.release.xcconfig"; sourceTree = "<group>"; };
B461B850299599F800E431AA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = "<group>"; };
B461B851299599F800E431AA /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = BlueWallet/AppDelegate.mm; sourceTree = "<group>"; };
B4D3235A177F4580BA52F2F9 /* libRNCSlider.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCSlider.a; sourceTree = "<group>"; }; B4D3235A177F4580BA52F2F9 /* libRNCSlider.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCSlider.a; sourceTree = "<group>"; };
B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = "<group>"; }; B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = "<group>"; };
B9D9B3A7B2CB4255876B67AF /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; B9D9B3A7B2CB4255876B67AF /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
@ -340,7 +339,6 @@
CA741BA794714D3F80251AC9 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; }; CA741BA794714D3F80251AC9 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; };
CD746B955C55410793BB72C0 /* libRNGestureHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNGestureHandler.a; sourceTree = "<group>"; }; CD746B955C55410793BB72C0 /* libRNGestureHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNGestureHandler.a; sourceTree = "<group>"; };
CF4A4D7AAD974D67A2D62B3E /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; }; CF4A4D7AAD974D67A2D62B3E /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; };
E0A230940404CA7C51FBED37 /* Pods-WidgetsExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WidgetsExtension/Pods-WidgetsExtension.release.xcconfig"; sourceTree = "<group>"; };
E6B44173A8854B6D85D7F933 /* libRNVectorIcons-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNVectorIcons-tvOS.a"; sourceTree = "<group>"; }; E6B44173A8854B6D85D7F933 /* libRNVectorIcons-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNVectorIcons-tvOS.a"; sourceTree = "<group>"; };
E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; }; E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@ -384,7 +382,6 @@
files = ( files = (
6DD4109E266CADF10087DE03 /* SwiftUI.framework in Frameworks */, 6DD4109E266CADF10087DE03 /* SwiftUI.framework in Frameworks */,
6DD4109D266CADF10087DE03 /* WidgetKit.framework in Frameworks */, 6DD4109D266CADF10087DE03 /* WidgetKit.framework in Frameworks */,
8CBB9CC9ACE4B44B45C28DF8 /* libPods-WidgetsExtension.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -426,11 +423,11 @@
13B07FAE1A68108700A75B9A /* BlueWallet */ = { 13B07FAE1A68108700A75B9A /* BlueWallet */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B461B850299599F800E431AA /* AppDelegate.h */,
B461B851299599F800E431AA /* AppDelegate.mm */,
32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */, 32C7944323B8879D00BE2AFA /* BlueWalletRelease.entitlements */,
32F0A2502310B0910095C559 /* BlueWallet.entitlements */, 32F0A2502310B0910095C559 /* BlueWallet.entitlements */,
008F07F21AC5B25A0029DE68 /* main.jsbundle */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */, 13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */, 13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB71A68108700A75B9A /* main.m */, 13B07FB71A68108700A75B9A /* main.m */,
@ -463,7 +460,6 @@
6D333B3C252FE1A3004D72DF /* SwiftUI.framework */, 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */,
98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */, 98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */,
41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */, 41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */,
7E45F9A25C15BEA362225C9B /* libPods-WidgetsExtension.a */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -583,6 +579,7 @@
6DEB4C3A254FBF4800E9F9AA /* Colors.swift */, 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */,
6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */, 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */,
6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */, 6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */,
B40FC3F829CCD1AC0007EBAC /* SwiftTCPClient.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -641,8 +638,6 @@
FF45EB303C9601ED114589A4 /* Pods-MarketWidgetExtension.release.xcconfig */, FF45EB303C9601ED114589A4 /* Pods-MarketWidgetExtension.release.xcconfig */,
AA7DCFB2C7887DF26EDB5710 /* Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig */, AA7DCFB2C7887DF26EDB5710 /* Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig */,
7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */, 7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */,
8C30FBCA42C6BF0B45757763 /* Pods-WidgetsExtension.debug.xcconfig */,
E0A230940404CA7C51FBED37 /* Pods-WidgetsExtension.release.xcconfig */,
); );
name = Pods; name = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
@ -794,7 +789,6 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 6DD410A9266CADF40087DE03 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */; buildConfigurationList = 6DD410A9266CADF40087DE03 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */;
buildPhases = ( buildPhases = (
0CD668CB2F65F36C4CADD5AB /* [CP] Check Pods Manifest.lock */,
6DD41098266CADF10087DE03 /* Sources */, 6DD41098266CADF10087DE03 /* Sources */,
6DD41099266CADF10087DE03 /* Frameworks */, 6DD41099266CADF10087DE03 /* Frameworks */,
6DD4109A266CADF10087DE03 /* Resources */, 6DD4109A266CADF10087DE03 /* Resources */,
@ -1013,28 +1007,6 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; shellScript = "export EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
}; };
0CD668CB2F65F36C4CADD5AB /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-WidgetsExtension-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
4B36CFF6FE55027DCA5CB6E1 /* Upload Bugsnag dSYM */ = { 4B36CFF6FE55027DCA5CB6E1 /* Upload Bugsnag dSYM */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1063,12 +1035,12 @@
); );
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL", "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/LDKFramework/LDKFramework.framework/LDKFramework", "${PODS_XCFRAMEWORKS_BUILD_DIR}/rn-ldk/LDKFramework.framework/LDKFramework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LDKFramework.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LDKFramework.framework",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -1190,9 +1162,9 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */, 6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */,
B461B852299599F800E431AA /* AppDelegate.mm in Sources */,
32B5A32A2334450100F8D608 /* Bridge.swift in Sources */, 32B5A32A2334450100F8D608 /* Bridge.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -1214,6 +1186,7 @@
files = ( files = (
6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */, 6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */,
6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */, 6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */,
B40FC3FA29CCD1D00007EBAC /* SwiftTCPClient.swift in Sources */,
6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */, 6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */,
6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */, 6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */,
6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */, 6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */,
@ -1390,10 +1363,11 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@ -1433,10 +1407,11 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@ -1477,7 +1452,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
@ -1516,7 +1491,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price"; PRODUCT_NAME = "BlueWallet - Bitcoin Price";
@ -1548,7 +1523,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
@ -1580,7 +1555,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist; INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0; IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -1592,7 +1567,6 @@
}; };
6DD410AA266CADF40087DE03 /* Debug */ = { 6DD410AA266CADF40087DE03 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 8C30FBCA42C6BF0B45757763 /* Pods-WidgetsExtension.debug.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
@ -1618,7 +1592,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
@ -1636,7 +1610,6 @@
}; };
6DD410AB266CADF40087DE03 /* Release */ = { 6DD410AB266CADF40087DE03 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = E0A230940404CA7C51FBED37 /* Pods-WidgetsExtension.release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
@ -1663,7 +1636,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -1682,7 +1655,7 @@
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
@ -1709,7 +1682,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -1726,9 +1699,10 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.2; IPHONEOS_DEPLOYMENT_TARGET = 11.2;
LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_VERSION = 4.2; SWIFT_VERSION = 4.2;
}; };
@ -1739,7 +1713,7 @@
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
@ -1766,7 +1740,7 @@
COPY_PHASE_STRIP = YES; COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -1776,8 +1750,9 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.2; IPHONEOS_DEPLOYMENT_TARGET = 11.2;
LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 4.2; SWIFT_VERSION = 4.2;
@ -1808,7 +1783,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
@ -1848,7 +1823,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
PRODUCT_NAME = "${TARGET_NAME}"; PRODUCT_NAME = "${TARGET_NAME}";
@ -1883,7 +1858,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension; IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist; INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
@ -1921,7 +1896,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension; IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist; INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 6.3.2; MARKETING_VERSION = 6.4.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -2011,7 +1986,7 @@
repositoryURL = "https://github.com/EFPrefix/EFQRCode.git"; repositoryURL = "https://github.com/EFPrefix/EFQRCode.git";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 5.1.8; version = 6.2.2;
}; };
}; };
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */

View file

@ -39,6 +39,21 @@
<key>orderHint</key> <key>orderHint</key>
<integer>2</integer> <integer>2</integer>
</dict> </dict>
<key>Stickers.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>TodayExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>5</integer>
</dict>
<key>WidgetsExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>88</integer>
</dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>
<dict> <dict>

View file

@ -5,8 +5,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/EFPrefix/EFQRCode.git", "location" : "https://github.com/EFPrefix/EFQRCode.git",
"state" : { "state" : {
"revision" : "62eff300b439d0088007b4e2e2e8c600f4b2e515", "revision" : "2991c2f318ad9529d93b2a73a382a3f9c72c64ce",
"version" : "5.1.8" "version" : "6.2.2"
} }
}, },
{ {
@ -14,8 +14,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/ApolloZhu/swift_qrcodejs.git", "location" : "https://github.com/ApolloZhu/swift_qrcodejs.git",
"state" : { "state" : {
"revision" : "f3fe9fdd39fa48e45b501000194b43a3883f732f", "revision" : "374dc7f7b9e76c6aeb393f6a84590c6d387e1ecb",
"version" : "1.1.4" "version" : "2.2.2"
} }
} }
], ],

View file

@ -1,15 +1,6 @@
/** #import <RCTAppDelegate.h>
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UserNotifications/UNUserNotificationCenter.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate> @interface AppDelegate : RCTAppDelegate
@property (nonatomic, strong) UIWindow *window;
@end @end

View file

@ -1,41 +1,20 @@
#import <Bugsnag/Bugsnag.h> #import <Bugsnag/Bugsnag.h>
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "AppDelegate.h" #import "AppDelegate.h"
#import <React/RCTLinkingManager.h> #import <React/RCTLinkingManager.h>
#import <React/RCTBundleURLProvider.h> #import <React/RCTBundleURLProvider.h>
#import <React/RCTI18nUtil.h> #import <React/RCTI18nUtil.h>
#import <React/RCTRootView.h> #import <React/RCTBundleURLProvider.h>
#import "RNQuickActionManager.h" #import "RNQuickActionManager.h"
#import <UserNotifications/UserNotifications.h> #import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h> #import <RNCPushNotificationIOS.h>
#import "EventEmitter.h" #import "EventEmitter.h"
@import WatchConnectivity; #import <React/RCTRootView.h>
#if !TARGET_OS_MACCATALYST #import <WatchConnectivity/WatchConnectivity.h>
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
static void InitializeFlipper(UIApplication *application) { @interface AppDelegate() <UNUserNotificationCenterDelegate>
FlipperClient *client = [FlipperClient sharedClient];
SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; @end
[client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
[client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
[client addPlugin:[FlipperKitReactPlugin new]];
[client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
[client start];
}
#endif
#endif
@implementation AppDelegate @implementation AppDelegate
@ -51,43 +30,39 @@ static void InitializeFlipper(UIApplication *application) {
forKeyPath:@"deviceUIDCopy" forKeyPath:@"deviceUIDCopy"
options:NSKeyValueObservingOptionNew options:NSKeyValueObservingOptionNew
context:NULL]; context:NULL];
self.moduleName = @"BlueWallet";
#if !TARGET_OS_MACCATALYST // You can add your custom initial props in the dictionary below.
#ifdef FB_SONARKIT_ENABLED // They will be passed down to the ViewController used by React Native.
InitializeFlipper(application); self.initialProps = @{};
#endif
#endif
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"BlueWallet"
initialProperties:nil];
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor clearColor];
}
[[RCTI18nUtil sharedInstance] allowRTL:YES]; [[RCTI18nUtil sharedInstance] allowRTL:YES];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
UIView* launchScreenView = [[UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil] instantiateInitialViewController].view;
launchScreenView.frame = self.window.bounds;
rootView.loadingView = launchScreenView;
// Define UNUserNotificationCenter
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self; center.delegate = self;
return [super application:application didFinishLaunchingWithOptions:launchOptions];
/* For debugging purposes since iOS Simulator does not support handoff
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
[defaults setValue:@{@"activityType": @"io.bluewallet.bluewallet.receiveonchain", @"userInfo": @{@"address": @""}} forKey:@"onUserActivityOpen"];
*/
return YES;
} }
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
return true;
}
- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context - (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{ {
if([keyPath isEqual:@"deviceUID"] || [keyPath isEqual:@"deviceUIDCopy"]) if([keyPath isEqual:@"deviceUID"] || [keyPath isEqual:@"deviceUIDCopy"])
@ -103,15 +78,6 @@ static void InitializeFlipper(UIApplication *application) {
} }
} }
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{ {
@ -154,46 +120,6 @@ static void InitializeFlipper(UIApplication *application) {
completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge); completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);
} }
// Required to register for notifications
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[RNCPushNotificationIOS didRegisterUserNotificationSettings:notificationSettings];
}
// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
[RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// IOS 10+ Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler
{
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
completionHandler();
}
// IOS 4-10 Required for the localNotification event.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[RNCPushNotificationIOS didReceiveLocalNotification:notification];
}
- (void)openPreferences { - (void)openPreferences {
[EventEmitter.sharedInstance openSettings]; [EventEmitter.sharedInstance openSettings];
} }
@ -225,4 +151,28 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
} }
} }
// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
[RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler
{
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
}
@end @end

View file

@ -101,8 +101,6 @@
<array> <array>
<string>https</string> <string>https</string>
<string>http</string> <string>http</string>
<string>bankid</string>
<string>swish</string>
</array> </array>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
@ -133,6 +131,13 @@
<key>NSIncludesSubdomains</key> <key>NSIncludesSubdomains</key>
<true/> <true/>
</dict> </dict>
<key>ts.net</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict> </dict>
</dict> </dict>
<key>NSAppleMusicUsageDescription</key> <key>NSAppleMusicUsageDescription</key>

View file

@ -15,7 +15,7 @@ static EventEmitter *sharedInstance;
RCT_EXPORT_MODULE(); RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup { + (BOOL)requiresMainQueueSetup {
return YES; return NO;
} }
+ (EventEmitter *)sharedInstance { + (EventEmitter *)sharedInstance {

View file

@ -1,26 +1,59 @@
platform :ios, '13.0'
workspace 'BlueWallet'
require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
workspace 'BlueWallet'
platform :ios, '13.0'
prepare_react_native_project!
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
# dependencies: {
# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
# flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
flipper_config = FlipperConfiguration.disabled
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
use_frameworks! :linkage => linkage.to_sym
end
target 'BlueWallet' do target 'BlueWallet' do
config = use_native_modules! config = use_native_modules!
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!( use_react_native!(
:path => config[:reactNativePath], :path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and then install pods # Hermes is now enabled by default. Disable by setting this flag to false.
:hermes_enabled => false # Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => true,
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
#:flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
) )
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines.
use_flipper!({ "Flipper-DoubleConversion" => "1.1.7" })
post_install do |installer| post_install do |installer|
react_native_post_install(installer) react_native_post_install(
__apply_Xcode_12_5_M1_post_install_workaround(installer) installer,
pod 'Bugsnag' # Set `mac_catalyst_enabled` to `true` in order to apply patches
# necessary for Mac Catalyst builds
:mac_catalyst_enabled => false
)
pod 'Bugsnag'
plugin 'cocoapods-bugsnag' plugin 'cocoapods-bugsnag'
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
target.build_configurations.each do |config| target.build_configurations.each do |config|
@ -31,12 +64,9 @@ target 'BlueWallet' do
config.build_settings['DEVELOPMENT_TEAM'] = "A7W54YZ4WU" config.build_settings['DEVELOPMENT_TEAM'] = "A7W54YZ4WU"
end end
end end
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end end
end end
end end
target 'WidgetsExtension' do
pod 'SwiftSocket', :git => 'https://github.com/swiftsocket/SwiftSocket.git', :branch => 'master'
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,101 @@
//
// File.swift
// BlueWallet
//
// Created by Marcos Rodriguez on 3/23/23.
// Copyright © 2023 BlueWallet. All rights reserved.
//
import Foundation
class SwiftTCPClient: NSObject {
private var inputStream: InputStream?
private var outputStream: OutputStream?
private let bufferSize = 1024
// Define the completion block type
typealias ReceiveCompletion = (Result<Data, Error>) -> Void
// Add a completion block property
var receiveCompletion: ReceiveCompletion?
func connect(to host: String, port: UInt32) -> Bool {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host as CFString, port, &readStream, &writeStream)
guard let read = readStream?.takeRetainedValue(), let write = writeStream?.takeRetainedValue() else {
return false
}
inputStream = read as InputStream
outputStream = write as OutputStream
inputStream?.delegate = self
outputStream?.delegate = self
inputStream?.schedule(in: .current, forMode: .default)
outputStream?.schedule(in: .current, forMode: .default)
inputStream?.open()
outputStream?.open()
return true
}
func send(data: Data) -> Bool {
guard let outputStream = outputStream else {
return false
}
let bytesWritten = data.withUnsafeBytes { bufferPointer -> Int in
guard let baseAddress = bufferPointer.baseAddress else {
return 0
}
return outputStream.write(baseAddress.assumingMemoryBound(to: UInt8.self), maxLength: data.count)
}
return bytesWritten == data.count
}
func receive() -> Data? {
let data = NSMutableData()
return data as Data
}
func close() {
inputStream?.close()
outputStream?.close()
inputStream?.remove(from: .current, forMode: .default)
outputStream?.remove(from: .current, forMode: .default)
inputStream = nil
outputStream = nil
}
}
extension SwiftTCPClient: StreamDelegate {
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case .hasBytesAvailable:
if let inputStream = aStream as? InputStream {
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer {
buffer.deallocate()
}
while inputStream.hasBytesAvailable {
let bytesRead = inputStream.read(buffer, maxLength: bufferSize)
if bytesRead > 0 {
let data = Data(bytes: buffer, count: bytesRead)
receiveCompletion?(.success(data))
}
}
}
case .hasSpaceAvailable, .openCompleted, .endEncountered, .errorOccurred:
break
default:
break
}
}
}

View file

@ -6,8 +6,7 @@
// Copyright © 2020 BlueWallet. All rights reserved. // Copyright © 2020 BlueWallet. All rights reserved.
// //
import Foundation
import SwiftSocket
struct APIError: LocalizedError { struct APIError: LocalizedError {
var errorDescription: String = "Failed to fetch Electrum data..." var errorDescription: String = "Failed to fetch Electrum data..."
@ -21,13 +20,12 @@ extension WidgetAPI {
return return
} }
DispatchQueue.global(qos: .background).async { DispatchQueue.global(qos: .background).async {
let client = TCPClient(address: host, port: port) let client = SwiftTCPClient()
let send = "{\"id\": 1, \"method\": \"blockchain.estimatefee\", \"params\": [1]}\n" client.receiveCompletion = { result in
switch client.connect(timeout: 1) { switch result {
case .success: case .success(let data):
switch client.send(string: send) { print("Received: \(data)")
case .success: guard let response = String(bytes: data, encoding: .utf8)?.data(using: .utf8) else {
guard let data = client.read(1024*10, timeout: 1), let response = String(bytes: data, encoding: .utf8)?.data(using: .utf8) else {
client.close() client.close()
completion(nil, APIError()) completion(nil, APIError())
return return
@ -45,12 +43,21 @@ extension WidgetAPI {
completion(nil, APIError()) completion(nil, APIError())
} }
case .failure(let error): case .failure(let error):
print(error) print("Error: \(error.localizedDescription)")
client.close() client.close()
completion(nil, APIError()) completion(nil, APIError())
} }
case .failure(let error): }
print(error)
if client.connect(to: host, port: UInt32(exactly: port)!) {
let message = "{\"id\": 1, \"method\": \"blockchain.estimatefee\", \"params\": [1]}\n"
if let data = message.data(using: .utf8), client.send(data: data) {
print("Message sent!")
RunLoop.current.run(until: Date(timeIntervalSinceNow: 5))
}
client.close()
} else {
print("Connection failed")
client.close() client.close()
if userElectrumSettings.host == DefaultElectrumPeers.last?.host { if userElectrumSettings.host == DefaultElectrumPeers.last?.host {
completion(nil, APIError()) completion(nil, APIError())

View file

@ -35,6 +35,10 @@ class WidgetAPI {
urlString = "https://api.exir.io/v1/ticker?symbol=btc-irt" urlString = "https://api.exir.io/v1/ticker?symbol=btc-irt"
case "wazirx": case "wazirx":
urlString = "https://api.wazirx.com/api/v2/tickers/btcinr" urlString = "https://api.wazirx.com/api/v2/tickers/btcinr"
case "Bitstamp":
urlString = "https://www.bitstamp.net/api/v2/ticker/btc\(endPointKey.lowercased())"
case "CoinGecko":
urlString = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=\(endPointKey.lowercased())"
default: default:
urlString = "https://api.coindesk.com/v1/bpi/currentprice/\(endPointKey).json" urlString = "https://api.coindesk.com/v1/bpi/currentprice/\(endPointKey).json"
} }
@ -61,6 +65,12 @@ class WidgetAPI {
let unix = Double(lastUpdated / 1_000) let unix = Double(lastUpdated / 1_000)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix)) let lastUpdatedString = ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: unix))
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble) latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "CoinGecko":
guard let rateDict = json["bitcoin"] as? [String: Any],
let rateDouble = rateDict[endPointKey.lowercased()] as? Double
else { break }
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: String(rateDouble), lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "YadioConvert": case "YadioConvert":
guard let rateDict = json as? [String: Any], guard let rateDict = json as? [String: Any],
let rateDouble = rateDict["rate"] as? Double, let rateDouble = rateDict["rate"] as? Double,
@ -74,6 +84,10 @@ class WidgetAPI {
let rateString = String(rateDouble) let rateString = String(rateDouble)
let lastUpdatedString = ISO8601DateFormatter().string(from: Date()) let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble) latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "Bitstamp":
guard let rateString = json["last"] as? String, let rateDouble = Double(rateString) else { break }
let lastUpdatedString = ISO8601DateFormatter().string(from: Date())
latestRateDataStore = WidgetDataStore(rate: rateString, lastUpdate: lastUpdatedString, rateDouble: rateDouble)
case "wazirx": case "wazirx":
guard let tickerDict = json["ticker"] as? [String: Any], guard let tickerDict = json["ticker"] as? [String: Any],
let rateString = tickerDict["buy"] as? String, let rateString = tickerDict["buy"] as? String,

View file

@ -1,14 +1,14 @@
محفظة Bitcoin تتيح لك تخزين عملات Bitcoin وإرسالها واستلامها وشراءها، مع التركيز على الأمان والبساطة. محفظة بتكوين تتيح لك تخزين البتكوين، ارساله واستقباله مع اهتمام خاص بالأمان العالي والبساطة المتناهية.
في BlueWallet ستمتلك المفاتيح الخاصة لعملات Bitcoin. محفظة الـ Bitcoin هذه أنشأها مستخدمو Bitcoin من أجل المجتمع. في BlueWallet ستمتلك المفاتيح الخاصة للبتكوين الذي تقرر الاحتفاظ به فيها. هذه المحفظة أنشأها مستخدمو البتكوين من أجل المجتمع.
يمكنك إجراء المعاملات مع أي شخص في العالم على الفور، وتغيير النظام المالي من جيبك مباشرةً. يمكنك إجراء المعاملات مع أي شخص في العالم على الفور والمشاركة في تطوير النظام المالي العالمي من جيبك مباشرةً.
يمكنك إنشاء عدد غير محدود من محافظ Bitcoin مجانًا أو استيراد محفظتك الحالية. الأمر بسيط وسريع. يمكنك إنشاء عدد غير محدود من محافظ البتكوين مجانًا أو استيراد محفظتك الحالية. الأمر سريع بسيط.
_____ _____
إليك ما تحصل عليه: إليك ما ستحصل عليه:
1 - الأمان من خلال التصميم 1 - الأمان من خلال التصميم
@ -22,32 +22,25 @@ _____
التشفير الكامل التشفير الكامل
علاوةً على التشفير متعدد الطبقات لنظام iOS، فإننا نقوم بتشفير كل شيء بكلمات مرور مضافة علاوةً على التشفير متعدد الطبقات لنظام iOS، فإننا نقوم بتشفير كل شيء بكلمات مرور مضافة
العقدة الكاملة النود التام
اتصل بمحفظة Bitcoin باستخدام العقدة الكاملة عن طريق برنامج Electrum اربط محفظة البتكوين بنود تام خاص بك عن طريق برنامج Electrum
التخزين البارد التخزين البارد
اتصل بمحفظة جهاز واحتفظ بعملاتك في وحدة "التخزين البارد" اتصل بمحفظة خارجية لتخزين البتكوين في وحدة تخزين بارد
2 - التركيز على تجربتك 2 - التركيز على تجربتك
كن متحكمًا كن متحكمًا
لا تغادر المفاتيح الخاصة جهازك أبدًا. Private keys never leave your device.You control your private keys
أنت من يتحكم في مفاتيحك الخاصة
مرونة الرسوم رسوم مرنة
بدايةً من ساتوشي واحد. تحددها أنت؛ أي المستخدم بنفسك بدايةً من ساتوشي واحد. تحددها أنت أيها المستخدم بنفسك
الاستبدال بالرسوم (RBF) الاستبدال بالرسوم (RBF)
(RBF) عزز سرعة معاملاتك بزيادة الرسوم (BIP125) (RBF) عزز سرعة معاملاتك بزيادة الرسوم (BIP125)
محافظ للتحقق من الرصيد فقط محافظ لمراقبة الرصيد فقط
تتيح لك محافظ التحقق من الرصيد متابعة وحدة التخزين البارد دون لمس الجهاز. تتيح لك محافظ مراقبة الرصيد من متابعة وحدة التخزين البارد دون الحاجة للمس المحفظة الخارجية.
شبكة Lightning شبكة البرق
محفظة Lightning دون الحاجة إلى إعدادها. معاملات رخيصة جدًا وسريعة مع توفير أفضل تجربة لمستخدمي Bitcoin. محفظة البرق دون الحاجة إلى إعدادها. معاملات رخيصة جدًا وسريعة مع توفير أفضل تجربة لمستخدمي البتكوين.
شراء Bitcoin
ادخل في أغوار الثورة المالية المفتوحة مع القدرة على شراء Bitcoin مباشرة من محفظتك.
متداول محلي
منصة تداول Bitcoin من نظير إلى نظير، والتي تتيح لك شراء عملات Bitcoin وبيعها مباشرةً إلى المستخدمين الآخرين دون الاستعانة بأطراف خارجية.

View file

@ -0,0 +1,46 @@
ビットコインを送る・受け取る・保存することができる、セキュリティとシンプルさを重視したウォレットです。
BlueWalletは、秘密鍵をあなたが所有するビットコインウォレット。ビットコインユーザーが作った、コミュニティのためのビットコインウォレット。
世界中の誰とでもすぐに取引ができ、あなたのポケットからファイナンスの仕組みを変革します。
無料で数の制限なくビットコインウォレットを作ることも、お持ちのウォレットをインポートすることもできます。シンプルでスピーディです。
_____
特長:
1 - デザインによるセキュリティ
オープンソース
MITライセンスにより、あなた自身でビルド・実行可能 ReactNative製。
もっともらしい否認
アクセスの開示を強要された時でも、フェイクのビットコインウォレットを復号できるパスワード
完全な暗号化
iOSのマルチレイヤー暗号化に加え、追加のパスワードですべてを暗号化
フルノード
Electrumを通じてビットコインフルードに接続
コールドストレージ
ハードウェアウォレットに接続し、コインをコールドストレージに保存
2 - ユーザーエクスペリエンス重視
思いのままに
秘密鍵はずっとあなたのデバイスの中に。あなたが秘密鍵をコントロールします
柔軟な手数料
1 Satoshiから。ユーザーのあなた自身が決定します
Replace-By-Fee
(RBF) 手数料を増やして取引をスピードアップ (BIP125)
閲覧専用ウォレット
閲覧専用ウォレットにより、ハードウェアに触れることなくコールドストレージを監視できます。
ライトニングネットワーク
設定の要らないライトニングウォレット。有り得ないほど安く、速い取引で最高のビットコイン体験を。

View file

@ -0,0 +1 @@
ビットコイン,ウォレット,ビットコインウォレット,ブロックチェーン,btc,仮想通貨,暗号通貨,electrum,イーサリアム

View file

@ -0,0 +1 @@
BlueWallet - ビットコインウォレット

View file

@ -0,0 +1,10 @@
特長
* オープンソース
* 完全な暗号化
* もっともらしい否認
* 柔軟な手数料
* Replace-By-Fee (RBF)
* SegWit
* 閲覧専用(Sentinel)ウォレット
* ライトニングネットワーク

View file

@ -2,7 +2,7 @@ Uma carteira Bitcoin que permite armazenar, enviar e receber Bitcoins. Focada em
BlueWallet, uma carteira de bitcoin onde você detém a posse das chaves privadas. Uma carteira Bitcoin criada para a comunidade por usuários do Bitcoin. BlueWallet, uma carteira de bitcoin onde você detém a posse das chaves privadas. Uma carteira Bitcoin criada para a comunidade por usuários do Bitcoin.
Você pode fazer transações instantaneamente com qualquer pessoa no mundo e transformar o sistema financeiro direto do seu bolso. Você pode fazer transações instantaneamente com qualquer pessoa no mundo e transformar o sistema financeiro direto do seu bolso.
Crie um número ilimitado de carteiras de Bitcoin gratuitamente ou importe sua carteira existente. É simples e rápido. Crie um número ilimitado de carteiras de Bitcoin gratuitamente ou importe sua carteira existente. É simples e rápido.
@ -13,7 +13,7 @@ Entenda o que oferecemos:
1 - Designada para ser segura 1 - Designada para ser segura
De Código Aberto Código Aberto
Licenciado pelo MIT, você pode compilar e rodar a sua própria versão! Feita com ReactNative Licenciado pelo MIT, você pode compilar e rodar a sua própria versão! Feita com ReactNative
Negação plausível Negação plausível
@ -26,21 +26,21 @@ Nó completo
Conecte-se ao seu nó completo Bitcoin através da Electrum Conecte-se ao seu nó completo Bitcoin através da Electrum
Armazenamento Frio Armazenamento Frio
Conecte com sua carteira hardware e mantenha suas moedas em armazenamento frio Conecte-se a sua carteira hardware e mantenha suas moedas em armazenamento frio
2 - Foco na sua experiência 2 - Focada na sua experiência
Tenha o controle Tenha o controle
Chaves privadas nunca saem do seu dispositivo. Você detém o controle delas. Chaves privadas nunca saem do seu dispositivo. Você detém o controle delas.
Taxas flexíveis Taxas flexíveis
A partir de 1 satoshi. Você decide A partir de 1 Satoshi. Definidas por você, o usuário
Replace-By-Fee Replace-By-Fee
(RBF) Acelere suas transações aumentando a taxa depois de enviar (BIP125) (RBF) Acelere suas transações aumentando a taxa depois de enviar (BIP125)
Carteiras somente leitura Carteiras somente leitura
Carteiras somente leitura permitem a você ficar de olho no seu armazenamento frio sem tocar na hardwallet. Carteiras somente leitura permitem a você ficar de olho no seu armazenamento frio sem tocar na sua carteira hardware.
Lightning Network Rede Lightning
Carteira Lightning sem precisar configurar nada. Transações muito baratas e rápidas para ter a melhor experiência com o Bitcoin. Carteira Lightning sem precisar configurar nada. Transações muito baratas e rápidas para ter a melhor experiência com o Bitcoin.

View file

@ -9,30 +9,31 @@
"disabled": "معطّل", "disabled": "معطّل",
"of": "{number} من {total}", "of": "{number} من {total}",
"ok": "موافق", "ok": "موافق",
"storage_is_encrypted": "وحدة التخزين الخاصة بك مشفرة. أنت بحاجة إلى كلمة المرور لفك تشفيرها", "storage_is_encrypted": "وحدة التخزين مشفرة. أنت بحاجة إلى كلمة المرور لفك تشفيرها",
"allow": "السماح",
"dont_allow": "عدم السماح",
"yes": "نعم", "yes": "نعم",
"no": "لا", "no": "لا",
"save": "حفظ", "save": "حفظ",
"seed": "عبارة الاسترداد", "seed": "عبارة الاسترداد",
"success": "نجاح", "success": "نجاح",
"wallet_key": "مفتاح المحفظة", "wallet_key": "مفتاح المحفظة",
"invalid_animated_qr_code_fragment" : "قطعة من رمز الـ QRcode المتحرك غير صحيحة، حاول مرة اخرى.", "invalid_animated_qr_code_fragment": "قطعة من رمز الـ QRcode المتحرك غير صحيحة، حاول مرة اخرى.",
"file_saved": "تم حفظ الملف {filePath} في {destination}.", "file_saved": "تم حفظ الملف {filePath} في {destination}.",
"file_save_title": "احفظ الملف",
"file_save_location": "حدد مكان حفظ {filePath}",
"downloads_folder": "مجلد التنزيلات", "downloads_folder": "مجلد التنزيلات",
"external_storage": "التخزين خارجيًا", "close": "اغلاق",
"discard_changes": "تجاهل التغييرات؟", "change_input_currency": "تغيير عملة الادخال",
"discard_changes_detail": "يوجد تغييرات غير محفوظة. هل أنت متأكد من الخروج من الشاشة وتجاهلها؟" "refresh": "تحديث",
"more": "المزيد",
"pick_image": "اختر صورة من الصور",
"pick_file": "اختر ملف",
"enter_amount": "أدخل القيمة",
"qr_custom_input_button": "أنقر ١٠ مرات لإدخال قيمة مخصصة"
}, },
"alert": { "alert": {
"default": "تنبيه" "default": "تنبيه"
}, },
"azteco": { "azteco": {
"codeIs": "رمز القسيمة الخاص بك هو", "codeIs": "رمز القسيمة الخاص بك هو",
"errorBeforeRefeem": "يجب عليك إضافة محفظة Bitcoin أولًا قبل الاسترداد.", "errorBeforeRefeem": "يجب عليك إضافة محفظة بتكوين أولًا قبل الاسترداد.",
"errorSomething": "حدث خطأ. هل ما زالت هذه القسيمة صالحة؟", "errorSomething": "حدث خطأ. هل ما زالت هذه القسيمة صالحة؟",
"redeem": "الاسترداد إلى المحفظة", "redeem": "الاسترداد إلى المحفظة",
"redeemButton": "الاسترداد", "redeemButton": "الاسترداد",
@ -50,39 +51,32 @@
"network": "خطأ في الشبكة" "network": "خطأ في الشبكة"
}, },
"lnd": { "lnd": {
"active":"نشط", "active": "نشط",
"inactive":"غير نشط", "inactive": "غير نشط",
"channels": "القنوات", "channels": "القنوات",
"no_channels": "لا يوجد قنوات", "no_channels": "لا يوجد قنوات",
"claim_balance": "المطالبة برصيد {balance}", "claim_balance": "المطالبة برصيد {balance}",
"close_channel": "اغلق القناة", "close_channel": "اغلق القناة",
"new_channel" : "قناة جديدة", "new_channel": "قناة جديدة",
"errorInvoiceExpired": "انتهت صلاحية الفاتورة", "errorInvoiceExpired": "انتهت صلاحية الفاتورة",
"force_close_channel": "هل تود فرض اغلاق القناة؟", "force_close_channel": "هل تود فرض اغلاق القناة؟",
"expired": "منتهية الصلاحية", "expired": "منتهية الصلاحية",
"node_alias": "الاسم المستعار للعقدة", "node_alias": "الاسم المستعار للعقدة",
"expiredLow": "انتهت صلاحيتها",
"expiresIn": "تنتهي بعد {time} دقيقة", "expiresIn": "تنتهي بعد {time} دقيقة",
"payButton": "دفع", "payButton": "دفع",
"placeholder": "فاتورة", "placeholder": "برقية",
"open_channel": "فتح قناة", "open_channel": "فتح قناة",
"funding_amount_placeholder": "مبلغ التمويل، على سبيل المثال 0.001", "funding_amount_placeholder": "مبلغ التمويل، على سبيل المثال 0.001",
"opening_channnel_for_from":"جارِ فتح قناة للمحفظة {forWalletLabel} بتمويل من {fromWalletLabel}", "opening_channnel_for_from": "جارِ فتح قناة للمحفظة {forWalletLabel} بتمويل من {fromWalletLabel}",
"are_you_sure_open_channel": "هل أنت متأكد أنك تريد فتح هذه القناة؟", "are_you_sure_open_channel": "هل أنت متأكد أنك تريد فتح هذه القناة؟",
"are_you_sure_exit_without_new_channel": "هل أنت متأكد أنك تريد الخروج دون فتح قناة؟",
"public": "علني",
"public_description": "قابلة للرؤية في الشبكة: من الممكن أن تكون عقدة توجيه وتكسب رسوم.",
"private": "خاص",
"private_description": "غير قابلة للرؤية في الشبكة: ستحافظ على خصوصية مدفوعاتك.",
"local_reserve": "الاحتياطي المحلي",
"potentialFee": "الرسوم المحتملة: {fee}", "potentialFee": "الرسوم المحتملة: {fee}",
"remote_host": "المضيف البعيد", "remote_host": "المضيف البعيد",
"refill": "إعادة التعبئة", "refill": "إعادة التعبئة",
"reconnect_peer": "إعادة الاتصال بالأقران", "reconnect_peer": "إعادة الاتصال بالأقران",
"refill_create": "للمتابعة، يُرجى إنشاء محفظة Bitcoin لإعادة التعبئة باستخدامها.", "refill_create": "للمتابعة، يُرجى إنشاء محفظة بتكوين لإعادة التعبئة باستخدامها.",
"refill_external": "إعادة التعبئة باستخدام محفظة خارجية", "refill_external": "إعادة التعبئة باستخدام محفظة خارجية",
"refill_lnd_balance": "إعادة تعبئة رصيد محفظة البرق (Lightning)", "refill_lnd_balance": "إعادة تعبئة رصيد محفظة البرق (Lightning)",
"sameWalletAsInvoiceError": "لا يمكنك دفع فاتورة بنفس المحفظة المستخدمة لإنشائها.", "sameWalletAsInvoiceError": "لايمكنك دفع البرقية من نفس المحفظة",
"title": "إدارة الأموال", "title": "إدارة الأموال",
"can_send": "يمكن أن ترسل", "can_send": "يمكن أن ترسل",
"can_receive": "يمكن ان تستقبل", "can_receive": "يمكن ان تستقبل",
@ -92,13 +86,12 @@
"additional_info": "معلومة إضافية", "additional_info": "معلومة إضافية",
"for": "إلى:", "for": "إلى:",
"lightning_invoice": "فاتورة برق (Lightning)", "lightning_invoice": "فاتورة برق (Lightning)",
"has_been_paid": "تم دفع هذه الفاتورة إلى", "open_direct_channel": "فتح قناة مباشرة مع هذه النود:",
"open_direct_channel": "فتح قناة مباشرة مع هذه العقدة:",
"please_pay_between_and": "يرجى دفع ما بين {min} و{max}", "please_pay_between_and": "يرجى دفع ما بين {min} و{max}",
"please_pay": "يُرجى الدفع", "please_pay": "يُرجى الدفع",
"preimage": "الصورة الأصلية", "preimage": "الصورة الأصلية",
"sats": "بالساتوشي", "sats": "بالساتوشي",
"wasnt_paid_and_expired": "لم يتم دفع هذه الفاتورة وانتهت صلاحيتها" "wasnt_paid_and_expired": "لم يتم دفع هذه البرقية وانتهت صلاحيتها"
}, },
"plausibledeniability": { "plausibledeniability": {
"create_fake_storage": "إنشاء وحدة تخزين مشفرة", "create_fake_storage": "إنشاء وحدة تخزين مشفرة",
@ -108,7 +101,7 @@
"help2": "ستعمل وحدة التخزين الجديدة بكامل طاقتها، ويمكنك تخزين بعض المبالغ البسيطة هناك بحيث تبدو أكثر مصداقية ومنطقية.", "help2": "ستعمل وحدة التخزين الجديدة بكامل طاقتها، ويمكنك تخزين بعض المبالغ البسيطة هناك بحيث تبدو أكثر مصداقية ومنطقية.",
"password_should_not_match": "كلمة المرور قيد الاستخدام حاليًا. يُرجى تجربة كلمة مرور مختلفة.", "password_should_not_match": "كلمة المرور قيد الاستخدام حاليًا. يُرجى تجربة كلمة مرور مختلفة.",
"passwords_do_not_match": "كلمات المرور غير متطابقة، حاول مرة أخرى.", "passwords_do_not_match": "كلمات المرور غير متطابقة، حاول مرة أخرى.",
"retype_password": "إعادة إدخال كلمة المرور", "retype_password": "أعد إدخال كلمة المرور",
"success": "نجحت العملية", "success": "نجحت العملية",
"title": "الإنكار المقبول" "title": "الإنكار المقبول"
}, },
@ -120,13 +113,12 @@
"ok_lnd": "حسنًا، لقد حفظتها.", "ok_lnd": "حسنًا، لقد حفظتها.",
"text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة. إنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.", "text": "يُرجى أخذ لحظة من وقتك لكتابة هذه العبارة التذكيرية على ورقة. إنها وسيلتك الاحتياطية لاستعادة المحفظة على جهاز آخر.",
"text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتتمكن من استعادة المحفظة في حالة فقدها.", "text_lnd": "يُرجى حفظ هذه النسخة الاحتياطية للمحفظة. لكي تتتمكن من استعادة المحفظة في حالة فقدها.",
"text_lnd2": "هذه المحفظة يتم استضافتها بواسطة BlueWallet.",
"title": "تم إنشاء محفظتك" "title": "تم إنشاء محفظتك"
}, },
"receive": { "receive": {
"details_create": "إنشاء", "details_create": "إنشاء",
"details_label": "الوصف", "details_label": "الوصف",
"details_setAmount": "استلام مبلغ", "details_setAmount": "استلام مبلغ محدد",
"details_share": "مشاركة", "details_share": "مشاركة",
"header": "استلام", "header": "استلام",
"maxSats": "الحد الأقصى للمبلغ هو {min} ساتوشي", "maxSats": "الحد الأقصى للمبلغ هو {min} ساتوشي",
@ -150,23 +142,23 @@
"create_fee": "الرسوم", "create_fee": "الرسوم",
"create_memo": "مذكرة", "create_memo": "مذكرة",
"create_satoshi_per_vbyte": "ساتوشي لكل ف بايت", "create_satoshi_per_vbyte": "ساتوشي لكل ف بايت",
"create_this_is_hex": "هذا هو التنسيق السداسي لمعاملتك، موقَّع وجاهز للبث على الشبكة.", "create_this_is_hex": "هذا هو رقم العملية بصيغة ست عشرية (Hex) ، موقَّع وجاهز للبث على الشبكة.",
"create_to": "إلى", "create_to": "إلى",
"create_tx_size": "حجم المعاملة", "create_tx_size": "حجم المعاملة",
"create_verify": "التحقق على coinb.in", "create_verify": "التحقق على coinb.in",
"details_add_rec_add": "إضافة المستلم", "details_add_rec_add": "إضافة المستلم",
"details_add_rec_rem": "إزالة المستلم", "details_add_rec_rem": "إزالة المستلم",
"details_address": "العنوان", "details_address": "العنوان",
"details_address_field_is_not_valid": "حقل العنوان غير صالح", "details_address_field_is_not_valid": "العنوان غير صالح",
"details_adv_fee_bump": "السماح بزيادة الرسوم", "details_adv_fee_bump": "السماح بزيادة الرسوم",
"details_adv_full": "استخدام الرصيد الكامل", "details_adv_full": "استخدام الرصيد الكامل",
"details_adv_full_sure": "هل أنت متأكد أنك تريد استخدام الرصيد الكامل لمحفظتك لهذه المعاملة؟", "details_adv_full_sure": "هل أنت متأكد أنك تريد استخدام الرصيد الكامل لمحفظتك لهذه المعاملة؟",
"details_adv_full_sure_frozen": "هل أنت متأكد انك ترغب بإستخدام الرصيد الكامل لمحفظتك لهذة المعاملة؟ يرجى ملاحظة انه تم استبعاد العملات المجمدة.", "details_adv_full_sure_frozen": "هل أنت متأكد انك ترغب بإستخدام الرصيد الكامل لمحفظتك لهذة المعاملة؟ يرجى ملاحظة انه تم استبعاد العملات المجمدة.",
"details_adv_import": "استيراد المعاملة", "details_adv_import": "استيراد العملية",
"details_adv_import_qr": "استيراد معاملة (QR)", "details_adv_import_qr": "استيراد معاملة (QR)",
"details_amount_field_is_not_valid": "حقل المبلغ غير صالح", "details_amount_field_is_not_valid": "المبلغ غير صالح",
"details_amount_field_is_less_than_minimum_amount_sat": "المبلغ المحدد صغير جدًا. الرجاء إدخال مبلغ أكبر من 500 ساتوشي.", "details_amount_field_is_less_than_minimum_amount_sat": "المبلغ المحدد صغير جدًا. الرجاء إدخال مبلغ أكبر من 500 ساتوشي.",
"details_create": "إنشاء فاتورة", "details_create": "إنشاء برقية",
"details_error_decode": "يتعذَّر فك تشفير عنوان Bitcoin", "details_error_decode": "يتعذَّر فك تشفير عنوان Bitcoin",
"details_fee_field_is_not_valid": "حقل الرسوم غير صالح", "details_fee_field_is_not_valid": "حقل الرسوم غير صالح",
"details_frozen": "{amount} بتكوين تم تجميدها", "details_frozen": "{amount} بتكوين تم تجميدها",
@ -178,7 +170,7 @@
"details_total_exceeds_balance": "مبلغ الإرسال يتجاوز الرصيد المتاح.", "details_total_exceeds_balance": "مبلغ الإرسال يتجاوز الرصيد المتاح.",
"details_total_exceeds_balance_frozen": "مبلغ الإرسال يتجاوز الرصيد المتاح، يرجى ملاحظة انه تم استبعاد العملات المجمدة.", "details_total_exceeds_balance_frozen": "مبلغ الإرسال يتجاوز الرصيد المتاح، يرجى ملاحظة انه تم استبعاد العملات المجمدة.",
"details_unrecognized_file_format": "تنسيق ملف غير معروف", "details_unrecognized_file_format": "تنسيق ملف غير معروف",
"details_wallet_before_tx": "يجب عليك إضافة محفظة Bitcoin أولًا قبل إنشاء معاملة.", "details_wallet_before_tx": "يجب عليك إضافة محفظة بتكوين أولًا قبل إنشاء معاملة.",
"dynamic_init": "جارٍ التهيئة...", "dynamic_init": "جارٍ التهيئة...",
"dynamic_next": "التالي", "dynamic_next": "التالي",
"dynamic_prev": "السابق", "dynamic_prev": "السابق",
@ -199,7 +191,6 @@
"input_paste": "اللصق", "input_paste": "اللصق",
"input_total": "الإجمالي:", "input_total": "الإجمالي:",
"permission_camera_message": "نحتاج إلى إذنك لاستخدام الكاميرا الخاصة بك", "permission_camera_message": "نحتاج إلى إذنك لاستخدام الكاميرا الخاصة بك",
"permission_camera_title": "إذن باستخدام الكاميرا",
"psbt_sign": "وقّع معاملة", "psbt_sign": "وقّع معاملة",
"open_settings": "فتح الإعدادات", "open_settings": "فتح الإعدادات",
"permission_storage_later": "اسألني لاحقًا", "permission_storage_later": "اسألني لاحقًا",
@ -207,14 +198,13 @@
"permission_storage_denied_message": "BlueWallet غير قادر على حفظ هذا الملف. يرجى فتح إعدادات جهازك وتمكين إذن التخزين.", "permission_storage_denied_message": "BlueWallet غير قادر على حفظ هذا الملف. يرجى فتح إعدادات جهازك وتمكين إذن التخزين.",
"permission_storage_title": "إذن وصول BlueWallet إلى وحدة التخزين", "permission_storage_title": "إذن وصول BlueWallet إلى وحدة التخزين",
"psbt_clipboard": "النسخ إلى الحافظة", "psbt_clipboard": "النسخ إلى الحافظة",
"psbt_this_is_psbt": "هذه معاملة Bitcoin موقَّعة جزئيًا (PSBT). يُرجى الانتهاء من توقيعها باستخدام محفظة الجهاز الخاصة بك.", "psbt_this_is_psbt": "هذه معاملة بتكوين موقَّعة جزئيًا (PSBT). يُرجى توقيعها باستخدام المحفظة الخارجية.",
"psbt_tx_export": "التصدير إلى ملف", "psbt_tx_export": "التصدير إلى ملف",
"no_tx_signing_in_progress": "لا يوجد معاملة توقيع قيد التقدم.", "no_tx_signing_in_progress": "لا يوجد معاملة توقيع قيد التقدم.",
"outdated_rate": "تم تحديث السعر آخر مرة في {date}", "outdated_rate": "تم تحديث السعر آخر مرة في {date}",
"psbt_tx_open": "فتح معاملة موقَّعة", "psbt_tx_open": "فتح معاملة موقَّعة",
"psbt_tx_scan": "المسح الضوئي معاملة موقَّعة", "psbt_tx_scan": "المسح الضوئي لمعاملة موقَّعة",
"qr_error_no_qrcode": "لم نتمكن من العثور على كود QR في الصورة المحددة. تأكد من أن الصورة تحتوي فقط على كود QR ولا تحتوي على محتوى إضافي مثل النص أو الأزرار.", "qr_error_no_qrcode": "لم نتمكن من قراءة رمز الاستجابة السريع (QR) في الصورة المحددة. تأكد أن الصورة تحتوي فقط على رمز (QR) دون أي محتوى إضافي آخر مثل النص أو الأزرار.",
"qr_error_no_wallet": "لا يحتوي الملف المحدَّد على محفظة يمكن استيرادها.",
"reset_amount": "إعادة تعيين المبلغ", "reset_amount": "إعادة تعيين المبلغ",
"reset_amount_confirm": "هل تريد إعادة تعيين المبلغ؟", "reset_amount_confirm": "هل تريد إعادة تعيين المبلغ؟",
"success_done": "تم", "success_done": "تم",
@ -227,14 +217,16 @@
"about_backup": "احتفظ دائمًا بنسخة احتياطية من مفاتيحك!", "about_backup": "احتفظ دائمًا بنسخة احتياطية من مفاتيحك!",
"about_free": "محفظة BlueWallet هي مشروع مجاني ومفتوح المصدر، أنشأه مستخدمو Bitcoin.", "about_free": "محفظة BlueWallet هي مشروع مجاني ومفتوح المصدر، أنشأه مستخدمو Bitcoin.",
"about_license": "ترخيص MIT", "about_license": "ترخيص MIT",
"about_release_notes": "ملاحظات الإصدار", "about_release_notes": "معلومات الإصدار",
"about_review": "اترك لنا مراجعتك", "about_review": "اترك لنا مراجعة",
"performance_score": "معدل الأداء: {num}",
"run_performance_test": "اختبار الأداء",
"about_selftest": "تشغيل اختبار ذاتي", "about_selftest": "تشغيل اختبار ذاتي",
"about_selftest_electrum_disabled": "لا يتوفر الاختبار الذاتي مع وضع Electrum الغير متصل بالانترنت. يرجى تعطيل وضع عدم اتصال الشبكة والمحاولة مرة أخرى.", "about_selftest_electrum_disabled": "لا يتوفر الاختبار الذاتي مع وضع Electrum الغير متصل بالانترنت. يرجى تعطيل وضع عدم اتصال الشبكة والمحاولة مرة أخرى.",
"about_selftest_ok": "تم اجتياز جميع الاختبارات الداخلية بنجاح. المحفظة تعمل بشكل جيد.", "about_selftest_ok": "تم اجتياز جميع الاختبارات الداخلية بنجاح. المحفظة تعمل بشكل جيد.",
"about_sm_github": "GitHub", "about_sm_github": "GitHub",
"about_sm_discord": "سيرفر Discord", "about_sm_discord": "سيرفر Discord",
"about_sm_telegram": "دردشة Telegram", "about_sm_telegram": "قناة تيليجرام",
"about_sm_twitter": "تابعنا على تويتر", "about_sm_twitter": "تابعنا على تويتر",
"advanced_options": "الخيارات المتقدمة", "advanced_options": "الخيارات المتقدمة",
"biometrics": "القياسات الحيوية", "biometrics": "القياسات الحيوية",
@ -243,7 +235,7 @@
"biom_no_passcode": "جهازك ليس لديه رمز مرور. للمتابعة ، يرجى اضافة رمز مرور من إعدادات الجهاز.", "biom_no_passcode": "جهازك ليس لديه رمز مرور. للمتابعة ، يرجى اضافة رمز مرور من إعدادات الجهاز.",
"biom_remove_decrypt": "ستتم إزالة جميع محافظك وفك تشفير التخزين الخاص بك. هل انت متأكد انك تريد المتابعة؟", "biom_remove_decrypt": "ستتم إزالة جميع محافظك وفك تشفير التخزين الخاص بك. هل انت متأكد انك تريد المتابعة؟",
"currency": "العملة", "currency": "العملة",
"currency_source": "يتم الحصول على الأسعار من", "currency_source": "تم الاستعلام عن السعر عن طريق",
"currency_fetch_error": "حدث خطأ أثناء الحصول على سعر العملة المحددة.", "currency_fetch_error": "حدث خطأ أثناء الحصول على سعر العملة المحددة.",
"default_desc": "عند تعطيل هذا الإعداد، ستفتح BlueWallet المحفظة المحدَّدة فور التشغيل.", "default_desc": "عند تعطيل هذا الإعداد، ستفتح BlueWallet المحفظة المحدَّدة فور التشغيل.",
"default_info": "المعلومات الافتراضية", "default_info": "المعلومات الافتراضية",
@ -251,7 +243,6 @@
"default_wallets": "عرض جميع المحافظ", "default_wallets": "عرض جميع المحافظ",
"electrum_connected": "متصل", "electrum_connected": "متصل",
"electrum_connected_not": "غير متصل", "electrum_connected_not": "غير متصل",
"electrum_connnected_not_description": "مطلوب اتصال Electrum نشط لاستيراد المحفظة. يمكنك التحقق إذا كان قد تم إنشاء اتصال بالانتقال إلى قسم الشبكة في الإعدادات.",
"electrum_error_connect": "يتعذَّر الاتصال بخادم Electrum المقدَّم", "electrum_error_connect": "يتعذَّر الاتصال بخادم Electrum المقدَّم",
"lndhub_uri": "على سبيل المثال، {example}", "lndhub_uri": "على سبيل المثال، {example}",
"electrum_host": "على سبيل المثال، {example}", "electrum_host": "على سبيل المثال، {example}",
@ -279,7 +270,6 @@
"tor_unsupported": "اتصالات Tor غير مدعومة.", "tor_unsupported": "اتصالات Tor غير مدعومة.",
"encrypt_decrypt": "فك تشفير وحدة التخزين", "encrypt_decrypt": "فك تشفير وحدة التخزين",
"encrypt_decrypt_q": "هل أنت متأكد أنك تريد فك تشفير وحدة التخزين الخاصة بك؟ سيسمح إجراء ذلك بالوصول إلى محافظك دون كلمة مرور.", "encrypt_decrypt_q": "هل أنت متأكد أنك تريد فك تشفير وحدة التخزين الخاصة بك؟ سيسمح إجراء ذلك بالوصول إلى محافظك دون كلمة مرور.",
"encrypt_del_uninstall": "الحذف في حال إلغاء تثبيت BlueWallet",
"encrypt_enc_and_pass": "مشفرة ومحمية بكلمة مرور", "encrypt_enc_and_pass": "مشفرة ومحمية بكلمة مرور",
"encrypt_title": "الأمان", "encrypt_title": "الأمان",
"encrypt_tstorage": "وحدة التخزين", "encrypt_tstorage": "وحدة التخزين",
@ -287,10 +277,10 @@
"encrypt_use_expl": "سيتم استخدام {type} لتأكيد هويتك قبل إجراء معاملة أو فتح محفظة أو تصديرها أو حذفها. ولن يتم استخدام {type} لفتح وحدة تخزين مشفرة.", "encrypt_use_expl": "سيتم استخدام {type} لتأكيد هويتك قبل إجراء معاملة أو فتح محفظة أو تصديرها أو حذفها. ولن يتم استخدام {type} لفتح وحدة تخزين مشفرة.",
"general": "عام", "general": "عام",
"general_adv_mode": "الوضع المتقدم", "general_adv_mode": "الوضع المتقدم",
"general_adv_mode_e": "عند تمكين هذا الإعداد، سترى خيارات متقدمة في أثناء إنشاء المحفظة، مثل أنواع المحافظ المختلفة، والقدرة على تحديد مثيل LNDHub الذي ترغب في الاتصال به، وإنتروبيا (عشوائية) مخصصة.", "general_adv_mode_e": "عند تمكين هذا الإعداد، سترى خيارات متقدمة أثناء إنشاء المحفظة، مثل أنواع محافظ مختلفة، القدرة على الاتصال ب LNDHub محدد، وإنتروبيا (عشوائية) مخصصة.",
"general_continuity": "الارتباط", "general_continuity": "الاتساق",
"general_continuity_e": "عند تمكين هذا الإعداد، ستتمكن من عرض المحافظ والمعاملات المحدَّدة باستخدام أجهزتك الأخرى المتصلة بحساب Apple iCloud.", "general_continuity_e": "عند تمكين هذا الإعداد، ستتمكن من عرض المحافظ والعمليات المحدَّدة باستخدام أجهزتك الأخرى المتصلة بنفس حساب Apple iCloud.",
"groundcontrol_explanation": "GroundControl هو خادم إشعارات فورية مجاني مفتوح المصدر لمحافظ Bitcoin. يمكنك تثبيت خادم GroundControl الخاص بك ووضع عنوان URL له هنا لعدم الاعتماد على البنية التحتية لمحفظة BlueWallet. اترك الحقل فارغًا لاستخدام الإعدادات الافتراضية", "groundcontrol_explanation": "GroundControl هو خادم إشعارات فورية مجاني مفتوح المصدر لمحافظ البتكوين. يمكنك تثبيت خادم GroundControl الخاص بك ووضع عنوان (URL) له هنا لعدم الاعتماد على البنية التحتية لمحفظة BlueWallet. اترك الحقل فارغًا لاستخدام الإعدادات الافتراضية",
"header": "الإعدادات", "header": "الإعدادات",
"language": "اللغة", "language": "اللغة",
"last_updated": "آخر تحديث", "last_updated": "آخر تحديث",
@ -299,20 +289,19 @@
"lightning_saved": "تم حفظ تغييراتك بنجاح", "lightning_saved": "تم حفظ تغييراتك بنجاح",
"lightning_settings": "إعدادات البرق (Lightning)", "lightning_settings": "إعدادات البرق (Lightning)",
"tor_settings": "اعدادات tor", "tor_settings": "اعدادات tor",
"lightning_settings_explain": "للاتصال بعقدة LND الخاصة بك، يُرجى تثبيت LndHub ثم وضع رابطه هنا في الإعدادات. اترك الحقل فارغًا لاستخدام LNDHub (lndhub.io) لمحفظة BlueWallet. ستتصل المحافظ التي يتم إنشاؤها بعد حفظ التغييرات بنفس عقدة LNDHub التي قمت بتحديدها.", "lightning_settings_explain": "للاتصال بنود LND الخاص بك، يُرجى تثبيت LNDHub ثم وضع رابطه هنا في الإعدادات. تذكر: فقط المحافظ التي يتم إنشاؤها بعد حفظ التغييرات ستتصل بنود LNDHub التي قمت بإضافتها.",
"network": "الشبكة", "network": "الشبكة",
"network_broadcast": "بث المعاملة", "network_broadcast": "بث العملية",
"network_electrum": "خادم Electrum", "network_electrum": "خادم Electrum",
"not_a_valid_uri": "معرِّف URI غير صالح", "not_a_valid_uri": "معرِّف URI غير صالح",
"notifications": "الإشعارات", "notifications": "الإشعارات",
"open_link_in_explorer" : "فتح الرابط في المتصفح", "open_link_in_explorer": "فتح الرابط في المتصفح",
"password": "كلمه المرور", "password": "كلمه المرور",
"password_explain": "أنشئ كلمة المرور التي ستستخدمها لفك تشفير وحدة التخزين", "password_explain": "أنشئ كلمة المرور التي ستستخدمها لفك تشفير وحدة التخزين",
"passwords_do_not_match": "كلمتا المرور لا تتطابقان", "passwords_do_not_match": "كلمتا المرور لا تتطابقان",
"plausible_deniability": "الإنكار المقبول", "plausible_deniability": "الإنكار المقبول",
"privacy": "الخصوصية", "privacy": "الخصوصية",
"privacy_read_clipboard": "قراءة الحافظة", "privacy_read_clipboard": "قراءة الحافظة",
"privacy_read_clipboard_alert": "سيعرض BlueWallet اختصاراً اذا كانت هناك فاتورة أو عنوان موجود في الحافظة للتعامل معه.",
"privacy_system_settings": "اعدادات الجهاز", "privacy_system_settings": "اعدادات الجهاز",
"privacy_quickactions": "اختصارات المحفظة", "privacy_quickactions": "اختصارات المحفظة",
"privacy_quickactions_explanation": "المس مع الاستمرار ايقونة تطبيق BlueWallet على شاشتك الرئيسية للمشاهدة عرض سريع لرصيد محفظتك.", "privacy_quickactions_explanation": "المس مع الاستمرار ايقونة تطبيق BlueWallet على شاشتك الرئيسية للمشاهدة عرض سريع لرصيد محفظتك.",
@ -325,7 +314,7 @@
"selfTest": "اختبار ذاتي", "selfTest": "اختبار ذاتي",
"save": "حفظ", "save": "حفظ",
"saved": "تم الحفظ", "saved": "تم الحفظ",
"success_transaction_broadcasted" : "تمت بنجاح! لقد تم نشر معاملتك!", "success_transaction_broadcasted": "تمت بنجاح! لقد تم نشر معاملتك!",
"total_balance": "الرصيد الاجمالي", "total_balance": "الرصيد الاجمالي",
"total_balance_explanation": "اعرض الرصيد الإجمالي لجميع محافظك على ويدجيت الشاشة الرئيسية الخاصة بك.", "total_balance_explanation": "اعرض الرصيد الإجمالي لجميع محافظك على ويدجيت الشاشة الرئيسية الخاصة بك.",
"widgets": "ويدجيت", "widgets": "ويدجيت",
@ -344,7 +333,7 @@
"copy_link": "نسخ الرابط", "copy_link": "نسخ الرابط",
"expand_note": "توسيع الملاحظة", "expand_note": "توسيع الملاحظة",
"cpfp_create": "إنشاء", "cpfp_create": "إنشاء",
"cpfp_exp": "سننشئ معاملة أخرى تستبدل معاملتك غير المؤكدة. وسيكون إجمالي الرسوم أعلى من رسوم المعاملة الأصلية؛ حتى يجري تعدينها بشكلٍ أسرع. وهذا ما يُسمَّى CPFP؛ أي دعم المعاملة الرئيسية بمعاملة فرعية.", "cpfp_exp": "سننشئ عملية أخرى تستبدل عمليتك غير المؤكدة. وسيكون إجمالي الرسوم أعلى من رسوم العملية الأصلية؛ حتى يجري تعدينها بشكلٍ أسرع. وهذا ما يُسمَّى CPFP؛ أي التحكم بالعملية الرئيسية بمعاملة فرعية.",
"cpfp_no_bump": "هذه المعاملة غير قابلة للتسريع", "cpfp_no_bump": "هذه المعاملة غير قابلة للتسريع",
"cpfp_title": "تسريع المعاملة (CPFP)", "cpfp_title": "تسريع المعاملة (CPFP)",
"details_balance_hide": "إخفاء الرصيد", "details_balance_hide": "إخفاء الرصيد",
@ -358,12 +347,12 @@
"details_from": "من", "details_from": "من",
"details_inputs": "المدخلات", "details_inputs": "المدخلات",
"details_outputs": "المخرجات", "details_outputs": "المخرجات",
"date": "التاريخ",
"details_received": "التاريخ", "details_received": "التاريخ",
"transaction_note_saved": "تم حفظ مذكرة المعاملة بنجاح.", "transaction_note_saved": "تم حفظ مذكرة المعاملة بنجاح.",
"details_show_in_block_explorer": "العرض في مستكشف الكتل", "details_show_in_block_explorer": "العرض في مستكشف الكتل",
"details_title": "المعاملة", "details_title": "العملية",
"details_to": "إلى", "details_to": "إلى",
"details_transaction_details": "تفاصيل المعاملة",
"enable_offline_signing": "هذه المحفظة لا يتم استعمالها مع التوقيع دون اتصال. هل ترغب في تمكينه الآن؟", "enable_offline_signing": "هذه المحفظة لا يتم استعمالها مع التوقيع دون اتصال. هل ترغب في تمكينه الآن؟",
"list_conf": "تأكيد: {number}", "list_conf": "تأكيد: {number}",
"pending": "قيد الانتظار", "pending": "قيد الانتظار",
@ -373,13 +362,13 @@
"eta_3h": "الوقت المقدر للتأكيد: في حوالي 3 ساعات", "eta_3h": "الوقت المقدر للتأكيد: في حوالي 3 ساعات",
"eta_1d": "الوقت المقدر للتأكيد: في حوالي يوم واحد", "eta_1d": "الوقت المقدر للتأكيد: في حوالي يوم واحد",
"view_wallet": "لمشاهدة {walletLabel}", "view_wallet": "لمشاهدة {walletLabel}",
"list_title": "المعاملات", "list_title": "العمليات",
"open_url_error": "تعذر فتح الرابط باستخدام المتصفح الافتراضي. يرجى تغيير المتصفح الافتراضي الخاص بك وحاول مرة أخرى.", "open_url_error": "تعذر فتح الرابط باستخدام المتصفح الافتراضي. يرجى تغيير المتصفح الافتراضي الخاص بك وحاول مرة أخرى.",
"rbf_explain": "سنستبدل هذه المعاملة بمعاملة جديدة تدفع رسوم اعلى؛ حتى يجري تعدينها بشكلٍ أسرع. وهذا ما يُسمَّى RBF؛ أي الاستبدال بالرسوم.", "rbf_explain": "سنستبدل هذه المعاملة بمعاملة جديدة تدفع رسوم اعلى؛ حتى يجري تعدينها بشكلٍ أسرع. وهذا ما يُسمَّى RBF؛ أي الاستبدال بالرسوم.",
"rbf_title": "تسريع المعاملة (RBF)", "rbf_title": "تسريع المعاملة (RBF)",
"status_bump": "تسريع المعاملة", "status_bump": "تسريع المعاملة",
"status_cancel": "إلغاء المعاملة", "status_cancel": "إلغاء العملية",
"transactions_count": "عدد المعاملات", "transactions_count": "عدد العمليات",
"txid": "معرّف المعاملة", "txid": "معرّف المعاملة",
"updating": "جارٍ التحديث ..." "updating": "جارٍ التحديث ..."
}, },
@ -393,10 +382,9 @@
"add_import_wallet": "استيراد محفظة", "add_import_wallet": "استيراد محفظة",
"add_lightning": "البرق", "add_lightning": "البرق",
"add_lightning_explain": "لإرسال المعاملات بشكل فوري عبر شبكة البرق Lightning ", "add_lightning_explain": "لإرسال المعاملات بشكل فوري عبر شبكة البرق Lightning ",
"add_lndhub": "اتصل ببرنامج تضمين LNDHub الخاص بك", "add_lndhub": "اتصل ب LNDHub الخاص بك",
"add_lndhub_error": "عنوان العقدة المقدَّم هو عقدة LNDHub صالحة.", "add_lndhub_error": "عنوان النود المقدَّم غير صالح.",
"add_lndhub_placeholder": "عنوان العقدة الخاص بك", "add_lndhub_placeholder": "عنوان النود الخاص بك",
"add_or": "أو",
"add_placeholder": "محفظتي الأولى", "add_placeholder": "محفظتي الأولى",
"add_title": "إضافة محفظة", "add_title": "إضافة محفظة",
"add_wallet_name": "الاسم", "add_wallet_name": "الاسم",
@ -415,10 +403,8 @@
"details_derivation_path": "مسار الاشتقاق (derivation path)", "details_derivation_path": "مسار الاشتقاق (derivation path)",
"details_display": "العرض في قائمة المحافظ", "details_display": "العرض في قائمة المحافظ",
"details_export_backup": "التصدير/النسخ الاحتياطي", "details_export_backup": "التصدير/النسخ الاحتياطي",
"details_master_fingerprint": "بصمة الإصبع الرئيسية", "details_export_history": "تصدير السجل ل ملف CSV",
"details_ms_l": "{m} of {n} legacy (p2sh)", "details_master_fingerprint": "البصمة الرئيسية",
"details_ms_ns": "{m} of {n} native segwit (p2wsh)",
"details_ms_ws": "{m} of {n} wrapped segwit (p2sh-p2wsh)",
"details_multisig_type": "متعدد التواقيع", "details_multisig_type": "متعدد التواقيع",
"details_no_cancel": "لا، إلغاء", "details_no_cancel": "لا، إلغاء",
"details_save": "حفظ", "details_save": "حفظ",
@ -437,10 +423,10 @@
"import_passphrase_message": "أدخل عبارة المرور إذا كنت قد استخدمت أيًا منها", "import_passphrase_message": "أدخل عبارة المرور إذا كنت قد استخدمت أيًا منها",
"import_error": "فشل الاستيراد. يُرجى التأكد من أن البيانات المقدَّمة صالحة.", "import_error": "فشل الاستيراد. يُرجى التأكد من أن البيانات المقدَّمة صالحة.",
"import_explanation": "اكتب هنا عبارتك التذكيرية أو مفتاحك الخاص أو WIF أو أي شيء لديك. ستبذل BlueWallet قصارى جهدها لتخمين التنسيق الصحيح واستيراد محفظتك", "import_explanation": "اكتب هنا عبارتك التذكيرية أو مفتاحك الخاص أو WIF أو أي شيء لديك. ستبذل BlueWallet قصارى جهدها لتخمين التنسيق الصحيح واستيراد محفظتك",
"import_file": "استيراد ملف",
"import_imported": "تم الاستيراد", "import_imported": "تم الاستيراد",
"import_scan_qr": "المسح الضوئي أو استيراد ملف", "import_scan_qr": "المسح الضوئي أو استيراد ملف",
"import_success": "تم استيراد محفظتك بنجاح.", "import_success": "تم استيراد محفظتك بنجاح.",
"import_success_watchonly": "تم استيراد محفظتك بنجاح. تنويه: هذه محفظة مراقبة فقط. لا يمكنك تنفيذ العمليات.",
"import_search_accounts": "البحث عن حسابات", "import_search_accounts": "البحث عن حسابات",
"import_title": "الاستيراد", "import_title": "الاستيراد",
"import_discovery_title": "اكتشاف", "import_discovery_title": "اكتشاف",
@ -461,13 +447,11 @@
"list_empty_txs1_lightning": "يجب استخدام محفظة البرق (Lightning) في معاملاتك اليومية. الرسوم رخيصة جدًا والسرعة كبيرة حقًا.", "list_empty_txs1_lightning": "يجب استخدام محفظة البرق (Lightning) في معاملاتك اليومية. الرسوم رخيصة جدًا والسرعة كبيرة حقًا.",
"list_empty_txs2": "ابدأ بمحفظتك", "list_empty_txs2": "ابدأ بمحفظتك",
"list_empty_txs2_lightning": "\nللبدء في استخدامها، اضغط على \"إدارة الأموال\" واشحن رصيدك.", "list_empty_txs2_lightning": "\nللبدء في استخدامها، اضغط على \"إدارة الأموال\" واشحن رصيدك.",
"list_header": "تمثِّل المحفظة زوجًا من المفاتيح السرية (المفتاح الخاص) وعنوان يمكنك مشاركته لاستلام العملات المعدنية.", "list_latest_transaction": "آخر عملية",
"list_import_problem": "حدثت مشكلة في استيراد هذه المحفظة",
"list_latest_transaction": "آخر معاملة",
"list_ln_browser": "متصفح LApp", "list_ln_browser": "متصفح LApp",
"list_long_choose": "اختيار صورة", "list_long_choose": "اختيار صورة",
"list_long_clipboard": "النسخ من الحافظة", "list_long_clipboard": "النسخ من الحافظة",
"list_long_scan": "مسح رمز الاستجابة السرعة ضوئيًا", "list_long_scan": "مسح رمز الاستجابة (QR) ضوئيًا",
"list_title": "المحافظ", "list_title": "المحافظ",
"list_tryagain": "إعادة المحاولة", "list_tryagain": "إعادة المحاولة",
"no_ln_wallet_error": "قبل دفع فاتورة برق (Lightning) ، يجب عليك أولاً إضافة محفظة برق (Lightning).", "no_ln_wallet_error": "قبل دفع فاتورة برق (Lightning) ، يجب عليك أولاً إضافة محفظة برق (Lightning).",
@ -475,11 +459,9 @@
"reorder_title": "إعادة ترتيب المحافظ", "reorder_title": "إعادة ترتيب المحافظ",
"reorder_instructions": "اضغط باستمرار على اي محفظة لتحريكها عبر القائمة", "reorder_instructions": "اضغط باستمرار على اي محفظة لتحريكها عبر القائمة",
"please_continue_scanning": "الرجاء متابعة الفحص.", "please_continue_scanning": "الرجاء متابعة الفحص.",
"scan_error": "خطأ في الفحص", "select_no_bitcoin": "لا توجد محافظ بتكوين متاحة حاليًا.",
"select_no_bitcoin": "لا توجد محافظ Bitcoin متاحة حاليًا.",
"select_no_bitcoin_exp": "تحتاج إلى محفظة بيتكوين لإعادة تعبئة محافظ البرق (Lightning). يُرجى إنشاء محفظة أو استيراد واحدة.", "select_no_bitcoin_exp": "تحتاج إلى محفظة بيتكوين لإعادة تعبئة محافظ البرق (Lightning). يُرجى إنشاء محفظة أو استيراد واحدة.",
"select_wallet": "اختيار محفظة", "select_wallet": "اختيار محفظة",
"take_photo": "التقاط صورة",
"xpub_copiedToClipboard": "تم النسخ إلى الحافظة.", "xpub_copiedToClipboard": "تم النسخ إلى الحافظة.",
"pull_to_refresh": "اسحب للتحديث", "pull_to_refresh": "اسحب للتحديث",
"warning_do_not_disclose": "تحذير! لا تنشر هذا.", "warning_do_not_disclose": "تحذير! لا تنشر هذا.",
@ -509,7 +491,6 @@
"cosign_this_transaction": "المشاركة في التوقيع على هذه المعاملة؟", "cosign_this_transaction": "المشاركة في التوقيع على هذه المعاملة؟",
"lets_start": "لنبدأ", "lets_start": "لنبدأ",
"create": "إنشاء", "create": "إنشاء",
"provide_key": "قدم مفتاحًا",
"native_segwit_title": "أفضل ممارسة", "native_segwit_title": "أفضل ممارسة",
"wrapped_segwit_title": "أفضل توافق", "wrapped_segwit_title": "أفضل توافق",
"legacy_title": "تنسيق قديم", "legacy_title": "تنسيق قديم",
@ -526,7 +507,6 @@
"quorum_header": "العدد", "quorum_header": "العدد",
"of": "من", "of": "من",
"wallet_type": "نوع المحفظة", "wallet_type": "نوع المحفظة",
"view_key": "عرض",
"invalid_mnemonics": "لا يبدو أن هذه العبارة التذكرية صالحة.", "invalid_mnemonics": "لا يبدو أن هذه العبارة التذكرية صالحة.",
"invalid_cosigner": "بيانات شريك توقيع ليست صحيحة", "invalid_cosigner": "بيانات شريك توقيع ليست صحيحة",
"not_a_multisignature_xpub": "هذه ليست XPUB من محفظة متعددة التوقيعات!", "not_a_multisignature_xpub": "هذه ليست XPUB من محفظة متعددة التوقيعات!",
@ -534,14 +514,11 @@
"create_new_key": "إنشاء جديد", "create_new_key": "إنشاء جديد",
"scan_or_open_file": "المسح الضوئي أو استيراد ملف", "scan_or_open_file": "المسح الضوئي أو استيراد ملف",
"i_have_mnemonics": "لدي عبارة تذكيرية لهذا المفتاح.", "i_have_mnemonics": "لدي عبارة تذكيرية لهذا المفتاح.",
"please_write_down_mnemonics": "يُرجى كتابة هذه العبارة التذكيرية على ورقة. لا تقلق، يمكنك كتابتها لاحقًا.",
"i_wrote_it_down": "حسنًا، لقد دوَّنتها!",
"type_your_mnemonics": "أدخل العبارة التذكيرية لاستيراد مفتاح خزنتك الحالية.", "type_your_mnemonics": "أدخل العبارة التذكيرية لاستيراد مفتاح خزنتك الحالية.",
"this_is_cosigners_xpub": "هذا هو XPUB الخاص بشريك التوقيع—وهو جاهز للاستيراد إلى محفظة أخرى. من الآمن مشاركته.", "this_is_cosigners_xpub": "هذا هو XPUB الخاص بشريك التوقيع—وهو جاهز للاستيراد إلى محفظة أخرى. من الآمن مشاركته.",
"wallet_key_created": "تم انشاء خزنتك. يُرجى أخذ لحظة من وقتك لعمل نسخ احتياطي لعبارتك التذكيرية.", "wallet_key_created": "تم انشاء خزنتك. يُرجى أخذ لحظة من وقتك لعمل نسخ احتياطي لعبارتك التذكيرية.",
"are_you_sure_seed_will_be_lost": "هل أنت متأكد؟ ستفقد عبارتك التذكيرية إذا لم يكن لديك نسخة احتياطية.", "are_you_sure_seed_will_be_lost": "هل أنت متأكد؟ ستفقد عبارتك التذكيرية إذا لم يكن لديك نسخة احتياطية.",
"forget_this_seed": "انسى هذه العبارة التذكيرية واستخدام XPUB بدلا من ذلك.", "forget_this_seed": "انسى هذه العبارة التذكيرية واستخدام XPUB بدلا من ذلك.",
"invalid_fingerprint": "بصمة العبارة التذكيرية هذه لا تتطابق مع بصمة شريك التوقيع.",
"view_edit_cosigners": "عرض/تحرير شركاء التوقيع", "view_edit_cosigners": "عرض/تحرير شركاء التوقيع",
"this_cosigner_is_already_imported": "تم استيراد شريك التوقيع هذا بالفعل.", "this_cosigner_is_already_imported": "تم استيراد شريك التوقيع هذا بالفعل.",
"export_signed_psbt": "تصدير توقيع PSBT", "export_signed_psbt": "تصدير توقيع PSBT",
@ -594,7 +571,6 @@
"sign_title": "التوقيع/التحقق من الرسالة", "sign_title": "التوقيع/التحقق من الرسالة",
"sign_help": "هنا يمكنك إنشاء أو التحقق من توقيع مشفر بناءً على عنوان Bitcoin.", "sign_help": "هنا يمكنك إنشاء أو التحقق من توقيع مشفر بناءً على عنوان Bitcoin.",
"sign_sign": "توقيع", "sign_sign": "توقيع",
"sign_sign_submit": "توقيع وارسال",
"sign_verify": "تحقق", "sign_verify": "تحقق",
"sign_signature_correct": "نجح التحقق!", "sign_signature_correct": "نجح التحقق!",
"sign_signature_incorrect": "فشل التحقق!", "sign_signature_incorrect": "فشل التحقق!",
@ -622,5 +598,11 @@
"auth_answer": "لقد قمت بالتوثيق مع {hostname} بنجاح!", "auth_answer": "لقد قمت بالتوثيق مع {hostname} بنجاح!",
"could_not_auth": "لم نتمكن من توثيقك على {hostname}.", "could_not_auth": "لم نتمكن من توثيقك على {hostname}.",
"authenticate": "التوثيق" "authenticate": "التوثيق"
},
"bip47": {
"payment_code": "كود الدفع",
"payment_codes_list": "قائمة أكواد الدفع",
"who_can_pay_me": "من يستطيع الدفع لي:",
"purpose": "أكواد المشاركة التي يمكن أعادة استخدامها (BIP47)"
} }
} }

View file

@ -8,8 +8,6 @@
"of": "{number} от {total}", "of": "{number} от {total}",
"ok": "OK", "ok": "OK",
"storage_is_encrypted": "Вашият портфейл е криптиран. Необходима е парола за декриптиране", "storage_is_encrypted": "Вашият портфейл е криптиран. Необходима е парола за декриптиране",
"allow": "Разреши",
"dont_allow": "Не разрешавай",
"yes": "Да", "yes": "Да",
"no": "Не", "no": "Не",
"save": "Запази", "save": "Запази",
@ -18,12 +16,7 @@
"wallet_key": "Парола на портфейла", "wallet_key": "Парола на портфейла",
"invalid_animated_qr_code_fragment": "Невалиден анимиран QRCode фрагмент. Моля, опитай отново.", "invalid_animated_qr_code_fragment": "Невалиден анимиран QRCode фрагмент. Моля, опитай отново.",
"file_saved": "Файлът {filePath} беше запазен в {destination}.", "file_saved": "Файлът {filePath} беше запазен в {destination}.",
"file_save_title": "Запази", "downloads_folder": "Папка с изтегляния"
"file_save_location": "Избери къде да запазиш {filePath}",
"downloads_folder": "Папка с изтегляния",
"external_storage": "Външно хранилище",
"discard_changes": "Отказваш промените?",
"discard_changes_detail": "Имате не запазени промени. Искаш ли да излезеш?"
}, },
"azteco": { "azteco": {
"codeIs": "Цода на вашият ваучър е", "codeIs": "Цода на вашият ваучър е",
@ -47,7 +40,6 @@
"lnd": { "lnd": {
"errorInvoiceExpired": "Изтекла фактура", "errorInvoiceExpired": "Изтекла фактура",
"expired": "Изтекла", "expired": "Изтекла",
"expiredLow": "изтекла",
"payButton": "Плати", "payButton": "Плати",
"placeholder": "Фактура", "placeholder": "Фактура",
"potentialFee": "Възможна такса: {fee}", "potentialFee": "Възможна такса: {fee}",
@ -62,7 +54,6 @@
"additional_info": "Допълнителна информация", "additional_info": "Допълнителна информация",
"for": "За:", "for": "За:",
"lightning_invoice": "Лайтнинг фактура", "lightning_invoice": "Лайтнинг фактура",
"has_been_paid": "Фактурата е платена",
"open_direct_channel": "Директно свързване с нода:", "open_direct_channel": "Директно свързване с нода:",
"please_pay": "Моля, плати", "please_pay": "Моля, плати",
"sats": "сатоши", "sats": "сатоши",
@ -84,8 +75,7 @@
"ask": "Запазихте ли паролата - 12/24 думи за портфейла? В случай, че изгубите достъп до устройството, паролата е необходима за да въстановите средствата. В случай, че загубите паролата - 12/24 думи, перманентно ще изгубите достъп до средствата.", "ask": "Запазихте ли паролата - 12/24 думи за портфейла? В случай, че изгубите достъп до устройството, паролата е необходима за да въстановите средствата. В случай, че загубите паролата - 12/24 думи, перманентно ще изгубите достъп до средствата.",
"ask_no": "Не, не съм", "ask_no": "Не, не съм",
"ask_yes": "Да", "ask_yes": "Да",
"text_lnd": "Моля, запазете паролата/ думите. Те ви позволяват да възтановите портфейла и средствата си на друго устройство.", "text_lnd": "Моля, запазете паролата/ думите. Те ви позволяват да възтановите портфейла и средствата си на друго устройство."
"text_lnd2": "Този портфейл се подържа от Блу Уолет."
}, },
"receive": { "receive": {
"details_create": "Създай", "details_create": "Създай",
@ -162,7 +152,6 @@
"no_tx_signing_in_progress": "Няма транзакция в прогрес.", "no_tx_signing_in_progress": "Няма транзакция в прогрес.",
"psbt_tx_open": "Отвори подписана транзакция", "psbt_tx_open": "Отвори подписана транзакция",
"psbt_tx_scan": "Подпиши транзакция", "psbt_tx_scan": "Подпиши транзакция",
"qr_error_no_wallet": "Избраният файл не съдържа портфейл който може да бъде импортиран.",
"success_done": "Готово", "success_done": "Готово",
"txSaved": "Файл с трансакцията ({filePath}) беше запазен в папката Свалени.", "txSaved": "Файл с трансакцията ({filePath}) беше запазен в папката Свалени.",
"problem_with_psbt": "Проблем с ЧПБТ / PSBT" "problem_with_psbt": "Проблем с ЧПБТ / PSBT"
@ -188,7 +177,6 @@
"biom_no_passcode": "Вашето устройство няма създадена парола. За да продължите, конфигурирайте парола в 'Настройки'на устройството.", "biom_no_passcode": "Вашето устройство няма създадена парола. За да продължите, конфигурирайте парола в 'Настройки'на устройството.",
"biom_remove_decrypt": "Всички портфейли ще бъдат изтрити и хранилището ще бъде декриптирано. Сигурни ли сте, че искате да продължите?", "biom_remove_decrypt": "Всички портфейли ще бъдат изтрити и хранилището ще бъде декриптирано. Сигурни ли сте, че искате да продължите?",
"currency": "Валута", "currency": "Валута",
"currency_source": "Котировките са предотставени от",
"default_info": "Информация", "default_info": "Информация",
"default_title": "При Стартиране", "default_title": "При Стартиране",
"default_wallets": "Виж всички портфейли", "default_wallets": "Виж всички портфейли",
@ -211,7 +199,6 @@
"electrum_clear": "Изчисти", "electrum_clear": "Изчисти",
"encrypt_decrypt": "Декриптирай хранилището", "encrypt_decrypt": "Декриптирай хранилището",
"encrypt_decrypt_q": "Сигурни ли сте, че искате да декриптирате хранилището? Това ще направи портфейлите ви достъпни без парола.", "encrypt_decrypt_q": "Сигурни ли сте, че искате да декриптирате хранилището? Това ще направи портфейлите ви достъпни без парола.",
"encrypt_del_uninstall": "Изтрий в случай, че Блу Уолет е деинсталиран",
"encrypt_enc_and_pass": "Криптиран и защитен с парола", "encrypt_enc_and_pass": "Криптиран и защитен с парола",
"encrypt_title": "Сигурност", "encrypt_title": "Сигурност",
"encrypt_tstorage": "Хранилище", "encrypt_tstorage": "Хранилище",

Some files were not shown because too many files have changed in this diff Show more