Merge branch 'master' into opsrn

This commit is contained in:
marcosrdz 2021-03-30 08:46:03 -04:00
commit 2bd4d27f94
321 changed files with 14511 additions and 6522 deletions

View file

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

View file

@ -25,6 +25,9 @@ env:
matrix:
- API=28
before_script:
- echo "MAVEN_OPTS='-Dmaven.wagon.httpconnectionManager.ttlSeconds=25 -Dmaven.wagon.http.retryHandler.count=3'" > ~/.mavenrc
before_install:
# Set up JDK 8 for Android SDK
- curl "${GRAVIS}.install-jdk-travis.sh" --output ~/.install-jdk-travis.sh

View file

@ -7,7 +7,7 @@ minimum_perc = 30
source_file = loc/en.json
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, 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
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

11
App.js
View file

@ -9,8 +9,10 @@ import {
Linking,
Platform,
StyleSheet,
UIManager,
useColorScheme,
View,
StatusBar,
} from 'react-native';
import { NavigationContainer, CommonActions } from '@react-navigation/native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
@ -51,6 +53,12 @@ const ClipboardContentType = Object.freeze({
LIGHTNING: 'LIGHTNING',
});
if (Platform.OS === 'android') {
if (UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
const App = () => {
const { walletsInitialized, wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions } = useContext(
BlueStorageContext,
@ -302,7 +310,7 @@ const App = () => {
isVisible={isClipboardContentModalVisible}
onClose={hideClipboardContentModal}
>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
<KeyboardAvoidingView enabled={!Platform.isPad} behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<BlueTextCentered>
{clipboardContentType === ClipboardContentType.BITCOIN && loc.wallets.clipboard_bitcoin}
@ -329,6 +337,7 @@ const App = () => {
return (
<SafeAreaProvider>
<View style={styles.root}>
<StatusBar barStyle={colorScheme === 'dark' ? 'light-content' : 'dark-content'} backgroundColor="transparent" translucent />
<NavigationContainer ref={navigationRef} theme={colorScheme === 'dark' ? BlueDarkTheme : BlueDefaultTheme}>
<InitRoot />
<Notifications onProcessNotifications={processPushNotifications} />

View file

@ -21,42 +21,32 @@ const startAndDecrypt = async retry => {
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false);
} while (!password);
}
const success = await BlueApp.loadFromDisk(password);
let success = false;
let wasException = false;
try {
success = await BlueApp.loadFromDisk(password);
} catch (error) {
// in case of exception reading from keystore, lets retry instead of assuming there is no storage and
// proceeding with no wallets
console.warn(error);
wasException = true;
}
if (wasException) {
// retrying, but only once
try {
await new Promise(resolve => setTimeout(resolve, 3000)); // sleep
success = await BlueApp.loadFromDisk(password);
} catch (_) {}
}
if (success) {
console.log('loaded from disk');
// now, lets try to fetch balance and txs for first wallet if it is time for it
/* let hadToRefresh = false;
let noErr = true;
try {
let wallets = BlueApp.getWallets();
if (wallets && wallets[0] && wallets[0].timeToRefreshBalance()) {
console.log('time to refresh wallet #0');
let oldBalance = wallets[0].getBalance();
await wallets[0].fetchBalance();
if (oldBalance !== wallets[0].getBalance() || wallets[0].getUnconfirmedBalance() !== 0 || wallets[0].timeToRefreshTransaction()) {
// balance changed, thus txs too
// or wallet thinks its time to reload TX list
await wallets[0].fetchTransactions();
hadToRefresh = true;
EV(EV.enum.WALLETS_COUNT_CHANGED);
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
} else {
console.log('balance not changed');
}
} // end of timeToRefresh
} catch (Err) {
noErr = false;
console.warn(Err);
}
if (hadToRefresh && noErr) {
await BlueApp.saveToDisk(); // caching
} */
// We want to return true to let the UnlockWith screen that its ok to proceed.
return true;
}
if (!success && password) {
if (password) {
// we had password and yet could not load/decrypt
unlockAttempt++;
if (unlockAttempt < 10 || Platform.OS !== 'ios') {

View file

@ -1,5 +1,5 @@
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
import React, { Component, useState, useMemo, useCallback, useContext } from 'react';
import React, { Component, useState, useMemo, useCallback, useContext, useRef, useEffect, forwardRef } from 'react';
import PropTypes from 'prop-types';
import { Icon, Input, Text, Header, ListItem, Avatar } from 'react-native-elements';
import {
@ -11,6 +11,7 @@ import {
InputAccessoryView,
Keyboard,
KeyboardAvoidingView,
Linking,
PixelRatio,
Platform,
PlatformColor,
@ -20,35 +21,31 @@ import {
TextInput,
TouchableOpacity,
TouchableWithoutFeedback,
UIManager,
View,
InteractionManager,
} from 'react-native';
import Clipboard from '@react-native-community/clipboard';
import Clipboard from '@react-native-clipboard/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import { LightningCustodianWallet, MultisigHDWallet } from './class';
import { BitcoinUnit } from './models/bitcoinUnits';
import * as NavigationService from './NavigationService';
import WalletGradient from './class/wallet-gradient';
import ToolTip from 'react-native-tooltip';
import { BlurView } from '@react-native-community/blur';
import showPopupMenu from 'react-native-popup-menu-android';
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees';
import Biometric from './class/biometrics';
import { getSystemName } from 'react-native-device-info';
import { encodeUR } from 'bc-ur/dist';
import QRCode from 'react-native-qrcode-svg';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useNavigation, useTheme } from '@react-navigation/native';
import { BlueCurrentTheme } from './components/themes';
import loc, { formatBalance, formatBalanceWithoutSuffix, formatBalancePlain, removeTrailingZeros, transactionTimeToReadable } from './loc';
import loc, { formatBalance, formatBalanceWithoutSuffix, transactionTimeToReadable } from './loc';
import Lnurl from './class/lnurl';
import { BlueStorageContext } from './blue_modules/storage-context';
import ToolTipMenu from './components/TooltipMenu';
/** @type {AppStorage} */
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
const BigNumber = require('bignumber.js');
const currency = require('./blue_modules/currency');
const fs = require('./blue_modules/fs');
let isIpad;
if (aspectRatio > 1.6) {
isIpad = false;
@ -93,7 +90,7 @@ export const BlueButton = props => {
);
};
export const SecondButton = props => {
export const SecondButton = forwardRef((props, ref) => {
const { colors } = useTheme();
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor;
let fontColor = colors.buttonTextColor;
@ -117,6 +114,7 @@ export const SecondButton = props => {
alignItems: 'center',
}}
{...props}
ref={ref}
>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
@ -124,7 +122,7 @@ export const SecondButton = props => {
</View>
</TouchableOpacity>
);
};
});
export const BitcoinButton = props => {
const { colors } = useTheme();
@ -226,19 +224,19 @@ export class BlueWalletNavigationHeader extends Component {
onWalletUnitChange: PropTypes.func,
};
static getDerivedStateFromProps(props, state) {
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange, allowOnchainAddress: state.allowOnchainAddress };
static getDerivedStateFromProps(props) {
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange };
}
static contextType = BlueStorageContext;
walletBalanceText = React.createRef();
tooltip = React.createRef();
constructor(props) {
super(props);
this.state = {
wallet: props.wallet,
walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit(),
showManageFundsButton: false,
allowOnchainAddress: false,
};
}
@ -246,13 +244,28 @@ export class BlueWalletNavigationHeader extends Component {
Clipboard.setString(formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString());
};
componentDidMount() {
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.'));
.catch(e => {
console.log('This Lndhub wallet does not have an onchain address API.');
this.setState({ allowOnchainAddress: false });
});
}
};
componentDidMount() {
this.verifyIfWalletAllowsOnchainAddress();
}
handleBalanceVisibility = async _item => {
@ -271,45 +284,6 @@ export class BlueWalletNavigationHeader extends Component {
await this.context.saveToDisk();
};
showAndroidTooltip = () => {
showPopupMenu(this.toolTipMenuOptions(), this.handleToolTipSelection, this.walletBalanceText.current);
};
handleToolTipSelection = item => {
if (item === 'balanceCopy' || item.id === 'balanceCopy') {
this.handleCopyPress();
} else if (item === 'balancePrivacy' || item.id === 'balancePrivacy') {
this.handleBalanceVisibility();
}
};
toolTipMenuOptions() {
return Platform.select({
// NOT WORKING ATM.
// ios: [
// { text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide, onPress: this.handleBalanceVisibility },
// { text: loc.transactions.details_copy, onPress: this.handleCopyPress },
// ],
android: this.state.wallet.hideBalance
? [
{
id: 'balancePrivacy',
label: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
},
]
: [
{
id: 'balancePrivacy',
label: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
},
{
id: 'balanceCopy',
label: loc.transactions.details_copy,
},
],
});
}
changeWalletBalanceUnit = () => {
let walletPreviousPreferredUnit = this.state.wallet.getPreferredBalanceUnit();
const wallet = this.state.wallet;
@ -336,7 +310,15 @@ export class BlueWalletNavigationHeader extends Component {
this.props.onManageFundsPressed();
};
showToolTipMenu = () => {
this.tooltip.current.showMenu();
};
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)}
@ -362,8 +344,8 @@ export class BlueWalletNavigationHeader extends Component {
right: 0,
}}
/>
<Text
testID="WalletLabel"
numberOfLines={1}
style={{
backgroundColor: 'transparent',
@ -373,41 +355,44 @@ export class BlueWalletNavigationHeader extends Component {
>
{this.state.wallet.getLabel()}
</Text>
{Platform.OS === 'ios' && (
<ToolTip
ref={tooltip => (this.tooltip = tooltip)}
actions={
this.state.wallet.hideBalance
? [
{
text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
onPress: this.handleBalanceVisibility,
},
]
: [
{
text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
onPress: this.handleBalanceVisibility,
},
{
text: loc.transactions.details_copy,
onPress: this.handleCopyPress,
},
]
}
/>
)}
<ToolTipMenu
ref={this.tooltip}
anchorRef={this.walletBalanceText}
actions={
this.state.wallet.hideBalance
? [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_show,
onPress: this.handleBalanceVisibility,
},
]
: [
{
id: 'walletBalanceVisibility',
text: loc.transactions.details_balance_hide,
onPress: this.handleBalanceVisibility,
},
{
id: 'copyToClipboard',
text: loc.transactions.details_copy,
onPress: this.handleCopyPress,
},
]
}
/>
<TouchableOpacity
style={styles.balance}
onPress={this.changeWalletBalanceUnit}
ref={this.walletBalanceText}
onLongPress={() => (Platform.OS === 'ios' ? this.tooltip.showMenu() : this.showAndroidTooltip())}
onLongPress={this.showToolTipMenu}
>
{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={{
@ -417,7 +402,7 @@ export class BlueWalletNavigationHeader extends Component {
color: '#fff',
}}
>
{formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString()}
{balance}
</Text>
)}
</TouchableOpacity>
@ -482,7 +467,7 @@ export class BlueWalletNavigationHeader extends Component {
}
}
export const BlueButtonLink = props => {
export const BlueButtonLink = forwardRef((props, ref) => {
const { colors } = useTheme();
return (
<TouchableOpacity
@ -492,11 +477,12 @@ export const BlueButtonLink = props => {
justifyContent: 'center',
}}
{...props}
ref={ref}
>
<Text style={{ color: colors.foregroundColor, textAlign: 'center', fontSize: 16 }}>{props.title}</Text>
</TouchableOpacity>
);
};
});
export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => {
Alert.alert(
@ -546,9 +532,6 @@ export class BlueCopyTextToClipboard extends Component {
constructor(props) {
super(props);
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
}
this.state = { hasTappedText: false, address: props.text };
}
@ -574,7 +557,7 @@ export class BlueCopyTextToClipboard extends Component {
render() {
return (
<View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<TouchableOpacity onPress={this.copyToClipboard} disabled={this.state.hasTappedText}>
<TouchableOpacity onPress={this.copyToClipboard} disabled={this.state.hasTappedText} testID="BlueCopyTextToClipboard">
<Animated.Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
{this.state.address}
</Animated.Text>
@ -594,8 +577,10 @@ const styleCopyTextToClipboard = StyleSheet.create({
});
export const SafeBlueArea = props => {
const { style, ...nonStyleProps } = props;
const { colors } = useTheme();
return <SafeAreaView forceInset={{ horizontal: 'always' }} style={{ flex: 1, backgroundColor: colors.background }} {...props} />;
const baseStyle = { flex: 1, backgroundColor: colors.background };
return <SafeAreaView forceInset={{ horizontal: 'always' }} style={[baseStyle, style]} {...nonStyleProps} />;
};
export const BlueCard = props => {
@ -622,6 +607,7 @@ export const BlueTextCentered = props => {
export const BlueListItem = React.memo(props => {
const { colors } = useTheme();
return (
<ListItem
containerStyle={props.containerStyle ?? { backgroundColor: 'transparent' }}
@ -630,7 +616,9 @@ export const BlueListItem = React.memo(props => {
topDivider={props.topDivider !== undefined ? props.topDivider : false}
testID={props.testID}
onPress={props.onPress}
onLongPress={props.onLongPress}
disabled={props.disabled}
accessible={props.switch === undefined}
>
{props.leftAvatar && <Avatar>{props.leftAvatar}</Avatar>}
{props.leftIcon && <Avatar icon={props.leftIcon} />}
@ -642,12 +630,14 @@ export const BlueListItem = React.memo(props => {
fontWeight: '500',
}}
numberOfLines={0}
accessible={props.switch === undefined}
>
{props.title}
</ListItem.Title>
{props.subtitle && (
<ListItem.Subtitle
numberOfLines={1}
numberOfLines={props.subtitleNumberOfLines ?? 1}
accessible={props.switch === undefined}
style={{ flexWrap: 'wrap', color: colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }}
>
{props.subtitle}
@ -667,7 +657,7 @@ export const BlueListItem = React.memo(props => {
<>
{props.chevron && <ListItem.Chevron />}
{props.rightIcon && <Avatar icon={props.rightIcon} />}
{props.switch && <Switch {...props.switch} />}
{props.switch && <Switch {...props.switch} accessibilityLabel={props.title} accessible accessibilityRole="switch" />}
{props.checkmark && <ListItem.CheckBox iconType="octaicon" checkedColor="#0070FF" checkedIcon="check" checked />}
</>
)}
@ -1280,6 +1270,9 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
}),
[colors.lightBorder],
);
const toolTip = useRef();
const copyToolTip = useRef();
const listItemRef = useRef();
const title = useMemo(() => {
if (item.confirmations === 0) {
@ -1420,6 +1413,10 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
}
}, [item]);
useEffect(() => {
setSubtitleNumberOfLines(1);
}, [subtitle]);
const onPress = useCallback(async () => {
if (item.hash) {
navigate('TransactionStatus', { hash: item.hash });
@ -1466,117 +1463,118 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
}, [item, wallets]);
const onLongPress = useCallback(() => {
if (subtitleNumberOfLines === 1) {
setSubtitleNumberOfLines(0);
}
}, [subtitleNumberOfLines]);
toolTip.current.showMenu();
}, []);
const handleOnExpandNote = useCallback(() => {
setSubtitleNumberOfLines(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [subtitle]);
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
const handleOnCopyTap = useCallback(() => {
toolTip.current.hideMenu();
setTimeout(copyToolTip.current.showMenu, 205);
}, []);
const handleOnCopyAmountTap = useCallback(() => Clipboard.setString(rowTitle.replace(/[\s\\-]/g, '')), [rowTitle]);
const handleOnCopyTransactionID = useCallback(() => Clipboard.setString(item.hash), [item.hash]);
const handleOnCopyNote = useCallback(() => Clipboard.setString(subtitle), [subtitle]);
const handleOnViewOnBlockExplorer = useCallback(() => {
const url = `https://blockstream.info/tx/${item.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
}, [item.hash]);
const handleCopyOpenInBlockExplorerPress = useCallback(() => {
Clipboard.setString(`https://blockstream.info/tx/${item.hash}`);
}, [item.hash]);
const toolTipActions = useMemo(() => {
const actions = [
{
id: 'copy',
text: loc.transactions.details_copy,
onPress: handleOnCopyTap,
},
];
if (item.hash) {
actions.push({
id: 'open_in_blockExplorer',
text: loc.transactions.details_show_in_block_explorer,
onPress: handleOnViewOnBlockExplorer,
});
}
if (subtitle && subtitleNumberOfLines === 1) {
actions.push({
id: 'expandNote',
text: loc.transactions.expand_note,
onPress: handleOnExpandNote,
});
}
return actions;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item.hash, subtitle, rowTitle, subtitleNumberOfLines, txMetadata]);
const copyToolTipActions = useMemo(() => {
const actions = [];
if (rowTitle !== loc.lnd.expired) {
actions.push({
id: 'copyAmount',
text: loc.send.create_amount,
onPress: handleOnCopyAmountTap,
});
}
if (item.hash) {
actions.push(
{
id: 'copyTX_ID',
text: loc.transactions.txid,
onPress: handleOnCopyTransactionID,
},
{
id: 'copy_blockExplorer',
text: loc.transactions.block_explorer_link,
onPress: handleCopyOpenInBlockExplorerPress,
},
);
}
if (subtitle) {
actions.push({
id: 'copyNote',
text: loc.transactions.note,
onPress: handleOnCopyNote,
});
}
return actions;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [toolTipActions]);
return (
<View style={{ marginHorizontal: 4 }}>
<BlueListItem
leftAvatar={avatar}
title={title}
titleNumberOfLines={subtitleNumberOfLines}
subtitle={subtitle}
subtitleProps={subtitleProps}
onPress={onPress}
onLongPress={onLongPress}
chevron={false}
Component={TouchableOpacity}
rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle}
containerStyle={containerStyle}
/>
</View>
<TouchableWithoutFeedback ref={listItemRef}>
<View style={{ marginHorizontal: 4 }}>
<ToolTipMenu ref={toolTip} anchorRef={listItemRef} actions={toolTipActions} />
<ToolTipMenu ref={copyToolTip} anchorRef={listItemRef} actions={copyToolTipActions} />
<BlueListItem
leftAvatar={avatar}
title={title}
subtitleNumberOfLines={subtitleNumberOfLines}
subtitle={subtitle}
subtitleProps={subtitleProps}
onPress={onPress}
chevron={false}
Component={TouchableOpacity}
rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle}
containerStyle={containerStyle}
onLongPress={onLongPress}
/>
</View>
</TouchableWithoutFeedback>
);
});
const isDesktop = getSystemName() === 'Mac OS X';
export const BlueAddressInput = ({
isLoading = false,
address = '',
placeholder = loc.send.details_address,
onChangeText,
onBarScanned,
launchedBy,
}) => {
const { colors } = useTheme();
return (
<View
style={{
flexDirection: 'row',
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: colors.inputBackgroundColor,
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
}}
>
<TextInput
testID="AddressInput"
onChangeText={onChangeText}
placeholder={placeholder}
numberOfLines={1}
placeholderTextColor="#81868e"
value={address}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, color: '#81868e' }}
editable={!isLoading}
onSubmitEditing={Keyboard.dismiss}
/>
<TouchableOpacity
testID="BlueAddressInputScanQrButton"
disabled={isLoading}
onPress={() => {
Keyboard.dismiss();
if (isDesktop) {
fs.showActionSheet().then(onBarScanned);
} else {
NavigationService.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy,
onBarScanned,
},
});
}
}}
style={{
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: colors.scanLabel,
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
}}
>
<Image style={{}} source={require('./img/scan-white.png')} />
<Text style={{ marginLeft: 4, color: colors.inverseForegroundColor }}>{loc.send.details_scan}</Text>
</TouchableOpacity>
</View>
);
};
BlueAddressInput.propTypes = {
isLoading: PropTypes.bool,
onChangeText: PropTypes.func,
onBarScanned: PropTypes.func.isRequired,
launchedBy: PropTypes.string.isRequired,
address: PropTypes.string,
placeholder: PropTypes.string,
};
export class BlueReplaceFeeSuggestions extends Component {
static propTypes = {
onFeeSelected: PropTypes.func.isRequired,
@ -1732,272 +1730,6 @@ export class BlueReplaceFeeSuggestions extends Component {
}
}
export class BlueBitcoinAmount extends Component {
static propTypes = {
isLoading: PropTypes.bool,
/**
* amount is a sting thats always in current unit denomination, e.g. '0.001' or '9.43' or '10000'
*/
amount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* callback that returns currently typed amount, in current denomination, e.g. 0.001 or 10000 or $9.34
* (btc, sat, fiat)
*/
onChangeText: PropTypes.func,
/**
* callback thats fired to notify of currently selected denomination, returns <BitcoinUnit.*>
*/
onAmountUnitChange: PropTypes.func,
disabled: PropTypes.bool,
};
/**
* cache of conversions fiat amount => satoshi
* @type {{}}
*/
static conversionCache = {};
static getCachedSatoshis(amount) {
return BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] || false;
}
static setCachedSatoshis(amount, sats) {
BlueBitcoinAmount.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] = sats;
}
constructor(props) {
super(props);
this.state = { unit: props.unit || BitcoinUnit.BTC, previousUnit: BitcoinUnit.SATS };
}
/**
* here we must recalculate old amont value (which was denominated in `previousUnit`) to new denomination `newUnit`
* and fill this value in input box, so user can switch between, for example, 0.001 BTC <=> 100000 sats
*
* @param previousUnit {string} one of {BitcoinUnit.*}
* @param newUnit {string} one of {BitcoinUnit.*}
*/
onAmountUnitChange(previousUnit, newUnit) {
const amount = this.props.amount || 0;
console.log('was:', amount, previousUnit, '; converting to', newUnit);
let sats = 0;
switch (previousUnit) {
case BitcoinUnit.BTC:
sats = new BigNumber(amount).multipliedBy(100000000).toString();
break;
case BitcoinUnit.SATS:
sats = amount;
break;
case BitcoinUnit.LOCAL_CURRENCY:
sats = new BigNumber(currency.fiatToBTC(amount)).multipliedBy(100000000).toString();
break;
}
if (previousUnit === BitcoinUnit.LOCAL_CURRENCY && BlueBitcoinAmount.conversionCache[amount + previousUnit]) {
// cache hit! we reuse old value that supposedly doesnt have rounding errors
sats = BlueBitcoinAmount.conversionCache[amount + previousUnit];
}
console.log('so, in sats its', sats);
const newInputValue = formatBalancePlain(sats, newUnit, false);
console.log('and in', newUnit, 'its', newInputValue);
if (newUnit === BitcoinUnit.LOCAL_CURRENCY && previousUnit === BitcoinUnit.SATS) {
// we cache conversion, so when we will need reverse conversion there wont be a rounding error
BlueBitcoinAmount.conversionCache[newInputValue + newUnit] = amount;
}
this.props.onChangeText(newInputValue);
if (this.props.onAmountUnitChange) this.props.onAmountUnitChange(newUnit);
}
/**
* responsible for cycling currently selected denomination, BTC->SAT->LOCAL_CURRENCY->BTC
*/
changeAmountUnit = () => {
let previousUnit = this.state.unit;
let newUnit;
if (previousUnit === BitcoinUnit.BTC) {
newUnit = BitcoinUnit.SATS;
} else if (previousUnit === BitcoinUnit.SATS) {
newUnit = BitcoinUnit.LOCAL_CURRENCY;
} else if (previousUnit === BitcoinUnit.LOCAL_CURRENCY) {
newUnit = BitcoinUnit.BTC;
} else {
newUnit = BitcoinUnit.BTC;
previousUnit = BitcoinUnit.SATS;
}
this.setState({ unit: newUnit, previousUnit }, () => this.onAmountUnitChange(previousUnit, newUnit));
};
maxLength = () => {
switch (this.state.unit) {
case BitcoinUnit.BTC:
return 10;
case BitcoinUnit.SATS:
return 15;
default:
return 15;
}
};
textInput = React.createRef();
handleTextInputOnPress = () => {
this.textInput.current.focus();
};
render() {
const amount = this.props.amount || 0;
let secondaryDisplayCurrency = formatBalanceWithoutSuffix(amount, BitcoinUnit.LOCAL_CURRENCY, false);
// if main display is sat or btc - secondary display is fiat
// if main display is fiat - secondary dislay is btc
let sat;
switch (this.state.unit) {
case BitcoinUnit.BTC:
sat = new BigNumber(amount).multipliedBy(100000000).toString();
secondaryDisplayCurrency = formatBalanceWithoutSuffix(sat, BitcoinUnit.LOCAL_CURRENCY, false);
break;
case BitcoinUnit.SATS:
secondaryDisplayCurrency = formatBalanceWithoutSuffix((isNaN(amount) ? 0 : amount).toString(), BitcoinUnit.LOCAL_CURRENCY, false);
break;
case BitcoinUnit.LOCAL_CURRENCY:
secondaryDisplayCurrency = currency.fiatToBTC(parseFloat(isNaN(amount) ? 0 : amount));
if (BlueBitcoinAmount.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY]) {
// cache hit! we reuse old value that supposedly doesn't have rounding errors
const sats = BlueBitcoinAmount.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY];
secondaryDisplayCurrency = currency.satoshiToBTC(sats);
}
break;
}
if (amount === BitcoinUnit.MAX) secondaryDisplayCurrency = ''; // we don't want to display NaN
return (
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
{!this.props.disabled && <View style={{ alignSelf: 'center', padding: amount === BitcoinUnit.MAX ? 0 : 15 }} />}
<View style={{ flex: 1 }}>
<View
style={{ flexDirection: 'row', alignContent: 'space-between', justifyContent: 'center', paddingTop: 16, paddingBottom: 2 }}
>
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
<Text
style={{
color: this.props.disabled
? BlueCurrentTheme.colors.buttonDisabledTextColor
: BlueCurrentTheme.colors.alternativeTextColor2,
fontSize: 18,
marginHorizontal: 4,
fontWeight: 'bold',
alignSelf: 'center',
justifyContent: 'center',
}}
>
{currency.getCurrencySymbol() + ' '}
</Text>
)}
<TextInput
{...this.props}
testID="BitcoinAmountInput"
keyboardType="numeric"
adjustsFontSizeToFit
onChangeText={text => {
text = text.trim();
if (this.state.unit !== BitcoinUnit.LOCAL_CURRENCY) {
text = text.replace(',', '.');
const split = text.split('.');
if (split.length >= 2) {
text = `${parseInt(split[0], 10)}.${split[1]}`;
} else {
text = `${parseInt(split[0], 10)}`;
}
text = this.state.unit === BitcoinUnit.BTC ? text.replace(/[^0-9.]/g, '') : text.replace(/[^0-9]/g, '');
if (text.startsWith('.')) {
text = '0.';
}
} else if (this.state.unit === BitcoinUnit.LOCAL_CURRENCY) {
text = text.replace(/,/gi, '');
if (text.split('.').length > 2) {
// too many dots. stupid code to remove all but first dot:
let rez = '';
let first = true;
for (const part of text.split('.')) {
rez += part;
if (first) {
rez += '.';
first = false;
}
}
text = rez;
}
text = text.replace(/[^\d.,-]/g, ''); // remove all but numbers, dots & commas
text = text.replace(/(\..*)\./g, '$1');
}
this.props.onChangeText(text);
}}
onBlur={() => {
if (this.props.onBlur) this.props.onBlur();
}}
onFocus={() => {
if (this.props.onFocus) this.props.onFocus();
}}
placeholder="0"
maxLength={this.maxLength()}
ref={textInput => (this.textInput = textInput)}
editable={!this.props.isLoading && !this.props.disabled}
value={amount === BitcoinUnit.MAX ? loc.units.MAX : parseFloat(amount) >= 0 ? amount : undefined}
placeholderTextColor={
this.props.disabled ? BlueCurrentTheme.colors.buttonDisabledTextColor : BlueCurrentTheme.colors.alternativeTextColor2
}
style={{
color: this.props.disabled
? BlueCurrentTheme.colors.buttonDisabledTextColor
: BlueCurrentTheme.colors.alternativeTextColor2,
fontWeight: 'bold',
fontSize: amount.length > 10 ? 20 : 36,
}}
/>
{this.state.unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
<Text
style={{
color: this.props.disabled
? BlueCurrentTheme.colors.buttonDisabledTextColor
: BlueCurrentTheme.colors.alternativeTextColor2,
fontSize: 15,
marginHorizontal: 4,
fontWeight: '600',
alignSelf: 'center',
justifyContent: 'center',
}}
>
{' ' + loc.units[this.state.unit]}
</Text>
)}
</View>
<View style={{ alignItems: 'center', marginBottom: 22 }}>
<Text style={{ fontSize: 16, color: '#9BA0A9', fontWeight: '600' }}>
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX
? removeTrailingZeros(secondaryDisplayCurrency)
: secondaryDisplayCurrency}
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${loc.units[BitcoinUnit.BTC]}` : null}
</Text>
</View>
</View>
{!this.props.disabled && amount !== BitcoinUnit.MAX && (
<TouchableOpacity
testID="changeAmountUnitButton"
style={{ alignSelf: 'center', marginRight: 16, paddingLeft: 16, paddingVertical: 16 }}
onPress={this.changeAmountUnit}
>
<Image source={require('./img/round-compare-arrows-24-px.png')} />
</TouchableOpacity>
)}
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = StyleSheet.create({
balanceBlur: {
height: 30,
@ -2077,7 +1809,7 @@ export class DynamicQRCode extends Component {
fragments = [];
componentDidMount() {
const { value, capacity = 800 } = this.props;
const { value, capacity = 200 } = this.props;
this.fragments = encodeUR(value, capacity);
this.setState(
{

24
FAQ.md Normal file
View file

@ -0,0 +1,24 @@
# FAQ
## Too much nodejs dependencies! Who audits all of that?
We do. Really, when we bump deps we glance over the diff, and all versions are
pinned. Also we use paid audit solution https://snyk.io which is specifically
designed to keep an eye on deps.
And yes we have too many of them, and PRs cutting deps are welcome
(see https://github.com/BlueWallet/BlueWallet/blob/master/CONTRIBUTING.md)
Also really risky dependencies (like, from not-reputable/anonymous maintainers)
we fork and use under our organization, and when we update them from upstream (rarely)
we do review the code
## Does BlueWallet downloads the Bitcoin Headers? I see no place you call blockchain.block.headers so I'm wondering how do you guys deal with the headers, how can you make sure you follow the correct chain in order to make sure you're spending a confirmed UTXO?
The idea is that by default BW doesnt use public electrum servers, only
ones hosted by bluewallet, so they are kinda trusted. And end-user has an
option to change electrum server to something he provides, so he knows what
is he doing and trusts his own electrum server.
We would definitely need proper SPV verification if we used random
electrum server every time from a pool of public servers.

View file

@ -1,42 +1,17 @@
import React, { useEffect, useState, useRef } from 'react';
import React from 'react';
import LottieView from 'lottie-react-native';
import WalletMigrate from './screen/wallets/walletMigrate';
import * as NavigationService from './NavigationService';
import { StackActions } from '@react-navigation/native';
const LoadingScreen = () => {
const [isMigratingData, setIsMigratinData] = useState(true);
const loadingAnimation = useRef();
const handleMigrationComplete = async () => {
setIsMigratinData(false);
};
const walletMigrate = useRef(new WalletMigrate(handleMigrationComplete));
const replaceStackNavigation = () => {
NavigationService.dispatch(StackActions.replace('UnlockWithScreenRoot'));
};
const onAnimationFinish = () => {
if (isMigratingData) {
loadingAnimation.current.play(0);
} else {
replaceStackNavigation();
}
replaceStackNavigation();
};
useEffect(() => {
walletMigrate.current.start();
}, [walletMigrate]);
return (
<LottieView
ref={loadingAnimation}
source={require('./img/bluewalletsplash.json')}
autoPlay
loop={false}
onAnimationFinish={onAnimationFinish}
/>
);
return <LottieView source={require('./img/bluewalletsplash.json')} autoPlay loop={false} onAnimationFinish={onAnimationFinish} />;
};
export default LoadingScreen;

View file

@ -34,6 +34,7 @@ import WalletExport from './screen/wallets/export';
import ExportMultisigCoordinationSetup from './screen/wallets/exportMultisigCoordinationSetup';
import ViewEditMultisigCosigners from './screen/wallets/viewEditMultisigCosigners';
import WalletXpub from './screen/wallets/xpub';
import SignVerify from './screen/wallets/signVerify';
import BuyBitcoin from './screen/wallets/buyBitcoin';
import HodlHodl from './screen/wallets/hodlHodl';
import HodlHodlViewOffer from './screen/wallets/hodlHodlViewOffer';
@ -60,6 +61,7 @@ import SendCreate from './screen/send/create';
import Confirm from './screen/send/confirm';
import PsbtWithHardwareWallet from './screen/send/psbtWithHardwareWallet';
import PsbtMultisig from './screen/send/psbtMultisig';
import PsbtMultisigQRCode from './screen/send/psbtMultisigQRCode';
import Success from './screen/send/success';
import Broadcast from './screen/send/broadcast';
import IsItMyAddress from './screen/send/isItMyAddress';
@ -75,10 +77,9 @@ import LnurlPaySuccess from './screen/lnd/lnurlPaySuccess';
import LoadingScreen from './LoadingScreen';
import UnlockWith from './UnlockWith';
import DrawerList from './screen/wallets/drawerList';
import { isTablet } from 'react-native-device-info';
import { isCatalyst, isTablet } from './blue_modules/environment';
import SettingsPrivacy from './screen/settings/SettingsPrivacy';
import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage';
import PsbtMultisigQRCode from './screen/send/psbtMultisigQRCode';
const defaultScreenOptions =
Platform.OS === 'ios'
@ -116,7 +117,7 @@ const WalletsRoot = () => {
return (
<WalletsStack.Navigator {...(Platform.OS === 'android' ? { screenOptions: defaultScreenOptions } : null)}>
<WalletsStack.Screen name="WalletsList" component={WalletsList} />
<WalletsStack.Screen name="WalletsList" component={WalletsList} options={WalletsList.navigationOptions(theme)} />
<WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions(theme)} />
<WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions(theme)} />
<WalletsStack.Screen name="TransactionDetails" component={TransactionDetails} options={TransactionDetails.navigationOptions(theme)} />
@ -348,7 +349,8 @@ const ReorderWalletsStackRoot = () => {
const Drawer = createDrawerNavigator();
function DrawerRoot() {
const dimensions = useWindowDimensions();
const isLargeScreen = Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 3 && isTablet();
const isLargeScreen =
Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 2 && (isTablet() || isCatalyst);
const drawerStyle = { width: '0%' };
return (
@ -384,6 +386,17 @@ const WalletXpubStackRoot = () => {
);
};
const SignVerifyStack = createStackNavigator();
const SignVerifyStackRoot = () => {
const theme = useTheme();
return (
<SignVerifyStack.Navigator name="SignVerifyRoot" screenOptions={defaultStackScreenOptions} initialRouteName="SignVerify">
<SignVerifyStack.Screen name="SignVerify" component={SignVerify} options={SignVerify.navigationOptions(theme)} />
</SignVerifyStack.Navigator>
);
};
const WalletExportStack = createStackNavigator();
const WalletExportStackRoot = () => {
const theme = useTheme();
@ -484,6 +497,7 @@ const Navigation = () => {
/>
<RootStack.Screen name="ViewEditMultisigCosignersRoot" component={ViewEditMultisigCosignersRoot} options={{ headerShown: false }} />
<RootStack.Screen name="WalletXpubRoot" component={WalletXpubStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="SignVerifyRoot" component={SignVerifyStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="BuyBitcoin" component={BuyBitcoin} options={BuyBitcoin.navigationOptions(theme)} />
<RootStack.Screen name="Marketplace" component={Marketplace} options={Marketplace.navigationOptions(theme)} />
<RootStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />

View file

@ -1,12 +0,0 @@
import Obscure from 'react-native-obscure';
import { Platform } from 'react-native';
import { enabled } from 'react-native-privacy-snapshot';
export default class Privacy {
static enableBlur() {
Platform.OS === 'android' ? Obscure.activateObscure() : enabled(true);
}
static disableBlur() {
Platform.OS === 'android' ? Obscure.deactivateObscure() : enabled(false);
}
}

View file

@ -9,8 +9,8 @@
Thin Bitcoin Wallet.
Built with React Native and Electrum.
[![Appstore](https://bluewallet.io/img/app-store-badge.svg)](https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8)
[![Playstore](https://bluewallet.io/img/play-store-badge.svg)](https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet)
[![Appstore](https://bluewallet.io/uploads/app-store-badge-blue.svg)](https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8)
[![Playstore](https://bluewallet.io/uploads/play-store-badge-blue.svg)](https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet)
Website: [bluewallet.io](http://bluewallet.io)
@ -123,6 +123,12 @@ Please note the values in curly braces should not be translated. These are the n
Transifex automatically creates Pull Request when language reaches 100% translation. We also trigger this by hand before each release, so don't worry if you can't translate everything, every word counts.
## Q&A
Builds automated and tested with BrowserStack
<a href="https://www.browserstack.com/"><img src="https://i.imgur.com/syscHCN.png" width="160px"></a>
## RESPONSIBLE DISCLOSURE
Found critical bugs/vulnerabilities? Please email them bluewallet@bluewallet.io

View file

@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react';
import { View, Image, TouchableOpacity, StyleSheet, StatusBar, ActivityIndicator, useColorScheme } from 'react-native';
import { Icon } from 'react-native-elements';
import Biometric from './class/biometrics';
import LottieView from 'lottie-react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StackActions, useNavigation, useRoute } from '@react-navigation/native';
import { BlueStorageContext } from './blue_modules/storage-context';
@ -13,21 +14,12 @@ const styles = StyleSheet.create({
flex: 1,
},
container: {
flex: 2,
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
},
qrCode: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
qrCodeImage: {
width: 120,
height: 120,
},
biometric: {
flex: 0.2,
flex: 1,
justifyContent: 'flex-end',
marginBottom: 58,
},
@ -141,9 +133,7 @@ const UnlockWith = () => {
<SafeAreaView style={styles.root}>
<StatusBar barStyle="default" />
<View style={styles.container}>
<View style={styles.qrCode}>
<Image source={require('./img/qr-code.png')} style={styles.qrCodeImage} />
</View>
<LottieView source={require('./img/bluewalletsplash.json')} progress={1} loop={false} />
<View style={styles.biometric}>
<View style={styles.biometricRow}>{renderUnlockOptions()}</View>
</View>

View file

@ -1,5 +1,11 @@
import { useContext, useEffect } from 'react';
import { updateApplicationContext, watchEvents, useReachability, useInstalled } from 'react-native-watch-connectivity';
import {
updateApplicationContext,
watchEvents,
useReachability,
useInstalled,
transferCurrentComplicationUserInfo,
} from 'react-native-watch-connectivity';
import { InteractionManager } from 'react-native';
import { Chain } from './models/bitcoinUnits';
import loc, { formatBalance, transactionTimeToReadable } from './loc';
@ -7,18 +13,41 @@ import { BlueStorageContext } from './blue_modules/storage-context';
import Notifications from './blue_modules/notifications';
function WatchConnectivity() {
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata } = useContext(BlueStorageContext);
const { walletsInitialized, wallets, fetchWalletTransactions, saveToDisk, txMetadata, preferredFiatCurrency } = useContext(
BlueStorageContext,
);
const isReachable = useReachability();
const isInstalled = useInstalled(); // true | false
useEffect(() => {
if (isInstalled && isReachable && walletsInitialized) {
sendWalletsToWatch();
watchEvents.on('message', handleMessages);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletsInitialized, isReachable, isInstalled]);
useEffect(() => {
if (isInstalled && isReachable && walletsInitialized) {
sendWalletsToWatch();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletsInitialized, wallets, isReachable, isInstalled]);
useEffect(() => {
if (isInstalled && isReachable && walletsInitialized && preferredFiatCurrency) {
try {
transferCurrentComplicationUserInfo({
preferredFiatCurrency: JSON.parse(preferredFiatCurrency).endPointKey,
});
sendWalletsToWatch();
} catch (e) {
console.log('WatchConnectivity useEffect preferredFiatCurrency error');
console.log(e);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [preferredFiatCurrency, walletsInitialized, isReachable, isInstalled]);
const handleMessages = (message, reply) => {
if (message.request === 'createInvoice') {
handleLightningInvoiceCreateRequest(message.walletIndex, message.amount, message.description)
@ -28,6 +57,11 @@ function WatchConnectivity() {
sendWalletsToWatch();
} else if (message.message === 'fetchTransactions') {
fetchWalletTransactions().then(() => saveToDisk());
} else if (message.message === 'hideBalance') {
const walletIndex = message.walletIndex;
const wallet = wallets[walletIndex];
wallet.hideBalance = message.hideBalance;
saveToDisk().finally(() => reply({}));
}
};
@ -154,6 +188,7 @@ function WatchConnectivity() {
receiveAddress: receiveAddress,
transactions: watchTransactions,
xpub: wallet.getXpub() ? wallet.getXpub() : wallet.getSecret(),
hideBalance: wallet.hideBalance,
});
}
updateApplicationContext({ wallets: walletsToProcess, randomID: Math.floor(Math.random() * 11) });

View file

@ -136,7 +136,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "6.0.3"
versionName "6.0.8"
multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type

View file

@ -1,11 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto"
package="io.bluewallet.bluewallet">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application

View file

@ -2,7 +2,8 @@
echo Uploading to Appetize and publishing link to Github...
echo -n "Branch "
git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3
BRANCH=`git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3`
echo $BRANCH
echo -n "Branch 2 "
git log -n 1 --pretty=%d HEAD | awk '{print $2}' | sed 's/origin\///' | sed 's/)//'
@ -19,11 +20,11 @@ if [ -f $FILENAME ]; then
# uploading file
HASH=`date +%s`
FILENAME_UNIQ="$APPCENTER_OUTPUT_DIRECTORY/$HASH.apk"
FILENAME_UNIQ="$APPCENTER_OUTPUT_DIRECTORY/$BRANCH-$HASH.apk"
cp "$FILENAME" "$FILENAME_UNIQ"
curl "http://filestorage.bluewallet.io:1488/upload.php" -F "fileToUpload=@$FILENAME_UNIQ"
rm "$FILENAME_UNIQ"
DLOAD_APK="http://filestorage.bluewallet.io:1488/$HASH.apk"
DLOAD_APK="http://filestorage.bluewallet.io:1488/$BRANCH-$HASH.apk"
curl -X POST --data "{\"body\":\"♫ This was a triumph. I'm making a note here: HUGE SUCCESS ♫\n\n [android in browser] $APPURL\n\n[download apk]($DLOAD_APK) \"}" -u "$GITHUB" "https://api.github.com/repos/BlueWallet/BlueWallet/issues/$PR/comments"
fi

View file

@ -1,8 +1,10 @@
/* global alert */
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
import { Platform, Alert } from 'react-native';
import { AppStorage, LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../class';
import DefaultPreference from 'react-native-default-preference';
import RNWidgetCenter from 'react-native-widget-center';
import loc from '../loc';
const bitcoin = require('bitcoinjs-lib');
const ElectrumClient = require('electrum-client');
const reverse = require('buffer-reverse');
@ -26,11 +28,13 @@ const hardcodedPeers = [
{ host: 'electrum3.bluewallet.io', ssl: '443' }, // 2x weight
];
let mainClient: ElectrumClient = false;
/** @type {ElectrumClient} */
let mainClient;
let mainConnected = false;
let wasConnectedAtLeastOnce = false;
let serverName = false;
let disableBatching = false;
let connectionAttempt = 0;
let latestBlockheight = false;
let latestBlockheightTimestamp = false;
@ -77,7 +81,7 @@ async function connectMain() {
serverName = ver[0];
mainConnected = true;
wasConnectedAtLeastOnce = true;
if (ver[0].startsWith('ElectrumPersonalServer') || ver[0].startsWith('electrs')) {
if (ver[0].startsWith('ElectrumPersonalServer') || ver[0].startsWith('electrs') || ver[0].startsWith('Fulcrum')) {
// TODO: once they release support for batching - disable batching only for lower versions
disableBatching = true;
}
@ -95,13 +99,89 @@ async function connectMain() {
if (!mainConnected) {
console.log('retry');
connectionAttempt = connectionAttempt + 1;
mainClient.close && mainClient.close();
setTimeout(connectMain, 500);
if (connectionAttempt >= 5) {
presentNetworkErrorAlert(usingPeer);
} else {
setTimeout(connectMain, 500);
}
}
}
connectMain();
async function presentNetworkErrorAlert(usingPeer) {
Alert.alert(
loc.errors.network,
loc.formatString(
usingPeer ? loc.settings.electrum_unable_to_connect : loc.settings.electrum_error_connect,
usingPeer ? { server: `${usingPeer.host}:${usingPeer.ssl ?? usingPeer.tcp}` } : {},
),
[
{
text: loc.wallets.list_tryagain,
onPress: () => {
connectionAttempt = 0;
mainClient.close() && mainClient.close();
setTimeout(connectMain, 500);
},
style: 'default',
},
{
text: loc.settings.electrum_reset,
onPress: () => {
Alert.alert(
loc.settings.electrum_reset,
loc.settings.electrum_reset_to_default,
[
{
text: loc._.cancel,
style: 'cancel',
onPress: () => {},
},
{
text: loc._.ok,
style: 'destructive',
onPress: async () => {
await AsyncStorage.setItem(AppStorage.ELECTRUM_HOST, '');
await AsyncStorage.setItem(AppStorage.ELECTRUM_TCP_PORT, '');
await AsyncStorage.setItem(AppStorage.ELECTRUM_SSL_PORT, '');
try {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.clear(AppStorage.ELECTRUM_HOST);
await DefaultPreference.clear(AppStorage.ELECTRUM_SSL_PORT);
await DefaultPreference.clear(AppStorage.ELECTRUM_TCP_PORT);
RNWidgetCenter.reloadAllTimelines();
} catch (e) {
// Must be running on Android
console.log(e);
}
alert(loc.settings.electrum_saved);
setTimeout(connectMain, 500);
},
},
],
{ cancelable: true },
);
connectionAttempt = 0;
mainClient.close() && mainClient.close();
},
style: 'destructive',
},
{
text: loc._.cancel,
onPress: () => {
connectionAttempt = 0;
mainClient.close() && mainClient.close();
},
style: 'cancel',
},
],
{ cancelable: false },
);
}
/**
* Returns random hardcoded electrum server guaranteed to work
* at the time of writing.
@ -421,7 +501,7 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver
txdata.result = await mainClient.blockchainTransaction_get(txdata.param, verbose);
}
ret[txdata.param] = txdata.result;
delete ret[txdata.param].hex; // compact
if (ret[txdata.param]) delete ret[txdata.param].hex; // compact
}
}
@ -453,16 +533,86 @@ module.exports.waitTillConnected = async function () {
if (retriesCounter++ >= 30) {
clearInterval(waitTillConnectedInterval);
connectionAttempt = 0;
presentNetworkErrorAlert();
reject(new Error('Waiting for Electrum connection timeout'));
}
}, 500);
});
};
// Returns the value at a given percentile in a sorted numeric array.
// "Linear interpolation between closest ranks" method
function percentile(arr, p) {
if (arr.length === 0) return 0;
if (typeof p !== 'number') throw new TypeError('p must be a number');
if (p <= 0) return arr[0];
if (p >= 1) return arr[arr.length - 1];
const index = (arr.length - 1) * p;
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= arr.length) return arr[lower];
return arr[lower] * (1 - weight) + arr[upper] * weight;
}
/**
* The histogram is an array of [fee, vsize] pairs, where vsizen is the cumulative virtual size of mempool transactions
* with a fee rate in the interval [feen-1, feen], and feen-1 > feen.
*
* @param numberOfBlocks {Number}
* @param feeHistorgram {Array}
* @returns {number}
*/
module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHistorgram) {
// first, transforming histogram:
let totalVsize = 0;
const histogramToUse = [];
for (const h of feeHistorgram) {
let [fee, vsize] = h;
let timeToStop = false;
if (totalVsize + vsize >= 1000000 * numberOfBlocks) {
vsize = 1000000 * numberOfBlocks - totalVsize; // only the difference between current summarized sige to tip of the block
timeToStop = true;
}
histogramToUse.push({ fee, vsize });
totalVsize += vsize;
if (timeToStop) break;
}
// now we have histogram of precisely size for numberOfBlocks.
// lets spread it into flat array so its easier to calculate percentile:
let histogramFlat = [];
for (const hh of histogramToUse) {
histogramFlat = histogramFlat.concat(Array(Math.round(hh.vsize / 25000)).fill(hh.fee));
// division is needed so resulting flat array is not too huge
}
histogramFlat = histogramFlat.sort(function (a, b) {
return a - b;
});
return Math.round(percentile(histogramFlat, 0.5) || 1);
};
module.exports.estimateFees = async function () {
const fast = await module.exports.estimateFee(1);
const medium = await module.exports.estimateFee(18);
const slow = await module.exports.estimateFee(144);
const histogram = await mainClient.mempool_getFeeHistogram();
// fetching what electrum (which uses bitcoin core) thinks about fees:
const _fast = await module.exports.estimateFee(1);
const _medium = await module.exports.estimateFee(18);
const _slow = await module.exports.estimateFee(144);
// calculating fast fees from mempool:
const fast = module.exports.calcEstimateFeeFromFeeHistorgam(1, histogram);
// recalculating medium and slow fees using bitcoincore estimations only like relative weights:
// (minimum 1 sat, just for any case)
const medium = Math.max(1, Math.round((fast * _medium) / _fast));
const slow = Math.max(1, Math.round((fast * _slow) / _fast));
return { fast, medium, slow };
};

View file

@ -0,0 +1,10 @@
import Obscure from 'react-native-obscure';
export default class Privacy {
static enableBlur() {
Obscure.activateObscure();
}
static disableBlur() {
Obscure.deactivateObscure();
}
}

View file

@ -0,0 +1,10 @@
import { enabled } from 'react-native-privacy-snapshot';
export default class Privacy {
static enableBlur() {
enabled(true);
}
static disableBlur() {
enabled(false);
}
}

5
blue_modules/Privacy.js Normal file
View file

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

View file

@ -8,6 +8,7 @@ function WidgetCommunication() {
WidgetCommunication.WidgetCommunicationAllWalletsSatoshiBalance = 'WidgetCommunicationAllWalletsSatoshiBalance';
WidgetCommunication.WidgetCommunicationAllWalletsLatestTransactionTime = 'WidgetCommunicationAllWalletsLatestTransactionTime';
WidgetCommunication.WidgetCommunicationDisplayBalanceAllowed = 'WidgetCommunicationDisplayBalanceAllowed';
WidgetCommunication.LatestTransactionIsUnconfirmed = 'WidgetCommunicationLatestTransactionIsUnconfirmed';
const { wallets, walletsInitialized, isStorageEncrypted } = useContext(BlueStorageContext);
WidgetCommunication.isBalanceDisplayAllowed = async () => {
@ -40,7 +41,11 @@ function WidgetCommunication() {
}
balance += wallet.getBalance();
if (wallet.getLatestTransactionTimeEpoch() > latestTransactionTime) {
latestTransactionTime = wallet.getLatestTransactionTimeEpoch();
if (wallet.getTransactions()[0].confirmations === 0) {
latestTransactionTime = WidgetCommunication.LatestTransactionIsUnconfirmed;
} else {
latestTransactionTime = wallet.getLatestTransactionTimeEpoch();
}
}
}
return { allWalletsBalance: balance, latestTransactionTime };

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 bitcoinjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,2 @@
# aezeed
A package for encoding, decoding, and generating mnemonics of the aezeed specification. (WIP)

View file

@ -0,0 +1,85 @@
{
"_from": "aezeed",
"_id": "aezeed@0.0.4",
"_inBundle": false,
"_integrity": "sha512-KAv2y2AtbqpdtsabCLE+C0G0h4BZLeMHsLCRga3VicYLxD17RflUBJ++c5qdpN6B6fkvK90r6bWg52Z/gMC7gQ==",
"_location": "/aezeed",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "aezeed",
"name": "aezeed",
"escapedName": "aezeed",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/aezeed/-/aezeed-0.0.4.tgz",
"_shasum": "8fce8778d34f5566328f61df7706351cb15873a9",
"_spec": "aezeed",
"_where": "/home/overtorment/Documents/BlueWallet",
"author": {
"name": "Jonathan Underwood"
},
"bugs": {
"url": "https://github.com/bitcoinjs/aezeed/issues"
},
"bundleDependencies": false,
"dependencies": {
"aez": "^1.0.1",
"crc-32": "npm:junderw-crc32c@^1.2.0",
"randombytes": "^2.1.0",
"scryptsy": "^2.1.0"
},
"deprecated": false,
"description": "A package for encoding, decoding, and generating mnemonics of the aezeed specification.",
"devDependencies": {
"@types/jest": "^26.0.10",
"@types/node": "^14.6.0",
"@types/randombytes": "^2.0.0",
"@types/scryptsy": "^2.0.0",
"jest": "^26.4.2",
"prettier": "^2.1.0",
"ts-jest": "^26.2.0",
"tslint": "^6.1.3",
"typescript": "^4.0.2"
},
"files": [
"src"
],
"homepage": "https://github.com/bitcoinjs/aezeed#readme",
"keywords": [
"aezeed",
"bitcoin",
"lightning",
"lnd"
],
"license": "MIT",
"main": "src/cipherseed.js",
"name": "aezeed",
"repository": {
"type": "git",
"url": "git+https://github.com/bitcoinjs/aezeed.git"
},
"scripts": {
"build": "npm run clean && tsc -p tsconfig.json",
"clean": "rm -rf src",
"coverage": "npm run unit -- --coverage",
"format": "npm run prettier -- --write",
"format:ci": "npm run prettier -- --check",
"gitdiff": "git diff --exit-code",
"gitdiff:ci": "npm run build && npm run gitdiff",
"lint": "tslint -p tsconfig.json -c tslint.json",
"prepublishOnly": "npm run test && npm run gitdiff",
"prettier": "prettier 'ts_src/**/*.ts' --single-quote --trailing-comma=all --ignore-path ./.prettierignore",
"test": "npm run build && npm run format:ci && npm run lint && npm run unit",
"unit": "jest --config=jest.json --runInBand"
},
"types": "src/cipherseed.d.ts",
"version": "0.0.4"
}

15
blue_modules/aezeed/src/cipherseed.d.ts vendored Normal file
View file

@ -0,0 +1,15 @@
/// <reference types="node" />
export declare class CipherSeed {
entropy: Buffer;
salt: Buffer;
internalVersion: number;
birthday: number;
private static decipher;
static fromMnemonic(mnemonic: string, password?: string): CipherSeed;
static random(): CipherSeed;
static changePassword(mnemonic: string, oldPassword: string | null, newPassword: string): string;
constructor(entropy: Buffer, salt: Buffer, internalVersion?: number, birthday?: number);
get birthDate(): Date;
toMnemonic(password?: string, cipherSeedVersion?: number): string;
private encipher;
}

View file

@ -0,0 +1,105 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CipherSeed = void 0;
const BlueCrypto = require('react-native-blue-crypto');
const scrypt = require("scryptsy");
const rng = require("randombytes");
const mn = require("./mnemonic");
const params_1 = require("./params");
const aez = require('aez');
const crc = require('junderw-crc32c');
const BITCOIN_GENESIS = new Date('2009-01-03T18:15:05.000Z').getTime();
const daysSinceGenesis = (time) => Math.floor((time.getTime() - BITCOIN_GENESIS) / params_1.ONE_DAY);
async function scryptWrapper(secret, salt, N, r, p, dkLen, progressCallback) {
if (BlueCrypto.isAvailable()) {
secret = Buffer.from(secret).toString('hex');
salt = Buffer.from(salt).toString('hex');
const hex = await BlueCrypto.scrypt(secret, salt, N, r, p, dkLen);
return Buffer.from(hex, 'hex');
} else {
// fallback to js implementation
return scrypt(secret, salt, N, r, p, dkLen, progressCallback);
}
}
class CipherSeed {
constructor(entropy, salt, internalVersion = 0, birthday = daysSinceGenesis(new Date())) {
this.entropy = entropy;
this.salt = salt;
this.internalVersion = internalVersion;
this.birthday = birthday;
if (entropy && entropy.length !== 16)
throw new Error('incorrect entropy length');
if (salt && salt.length !== 5)
throw new Error('incorrect salt length');
}
static async decipher(cipherBuf, password) {
if (cipherBuf[0] >= params_1.PARAMS.length) {
throw new Error('Invalid cipherSeedVersion');
}
const cipherSeedVersion = cipherBuf[0];
const params = params_1.PARAMS[cipherSeedVersion];
const checksum = Buffer.allocUnsafe(4);
const checksumNum = crc.buf(cipherBuf.slice(0, 29));
checksum.writeInt32BE(checksumNum);
if (!checksum.equals(cipherBuf.slice(29))) {
throw new Error('CRC checksum mismatch');
}
const salt = cipherBuf.slice(24, 29);
const key = await scryptWrapper(Buffer.from(password, 'utf8'), salt, params.n, params.r, params.p, 32);
const adBytes = Buffer.allocUnsafe(6);
adBytes.writeUInt8(cipherSeedVersion, 0);
salt.copy(adBytes, 1);
const plainText = aez.decrypt(key, null, [adBytes], 4, cipherBuf.slice(1, 24));
if (plainText === null)
throw new Error('Invalid Password');
return new CipherSeed(plainText.slice(3, 19), salt, plainText[0], plainText.readUInt16BE(1));
}
static async fromMnemonic(mnemonic, password = params_1.DEFAULT_PASSWORD) {
const bytes = mn.mnemonicToBytes(mnemonic);
return await CipherSeed.decipher(bytes, password);
}
static random() {
return new CipherSeed(rng(16), rng(5));
}
static async changePassword(mnemonic, oldPassword, newPassword) {
const pwd = oldPassword === null ? params_1.DEFAULT_PASSWORD : oldPassword;
const cs = await CipherSeed.fromMnemonic(mnemonic, pwd);
return await cs.toMnemonic(newPassword);
}
get birthDate() {
return new Date(BITCOIN_GENESIS + this.birthday * params_1.ONE_DAY);
}
async toMnemonic(password = params_1.DEFAULT_PASSWORD, cipherSeedVersion = params_1.CIPHER_SEED_VERSION) {
return mn.mnemonicFromBytes(await this.encipher(password, cipherSeedVersion));
}
async encipher(password, cipherSeedVersion) {
const pwBuf = Buffer.from(password, 'utf8');
const params = params_1.PARAMS[cipherSeedVersion];
const key = await scryptWrapper(pwBuf, this.salt, params.n, params.r, params.p, 32);
const seedBytes = Buffer.allocUnsafe(19);
seedBytes.writeUInt8(this.internalVersion, 0);
seedBytes.writeUInt16BE(this.birthday, 1);
this.entropy.copy(seedBytes, 3);
const adBytes = Buffer.allocUnsafe(6);
adBytes.writeUInt8(cipherSeedVersion, 0);
this.salt.copy(adBytes, 1);
const cipherText = aez.encrypt(key, null, [adBytes], 4, seedBytes);
const cipherSeedBytes = Buffer.allocUnsafe(33);
cipherSeedBytes.writeUInt8(cipherSeedVersion, 0);
cipherText.copy(cipherSeedBytes, 1);
this.salt.copy(cipherSeedBytes, 24);
const checksumNum = crc.buf(cipherSeedBytes.slice(0, 29));
cipherSeedBytes.writeInt32BE(checksumNum, 29);
return cipherSeedBytes;
}
}
exports.CipherSeed = CipherSeed;

3
blue_modules/aezeed/src/mnemonic.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
/// <reference types="node" />
export declare function mnemonicFromBytes(bytes: Buffer): string;
export declare function mnemonicToBytes(mnemonic: string): Buffer;

File diff suppressed because it is too large Load diff

8
blue_modules/aezeed/src/params.d.ts vendored Normal file
View file

@ -0,0 +1,8 @@
export declare const PARAMS: {
n: number;
r: number;
p: number;
}[];
export declare const DEFAULT_PASSWORD = "aezeed";
export declare const CIPHER_SEED_VERSION = 0;
export declare const ONE_DAY: number;

View file

@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ONE_DAY = exports.CIPHER_SEED_VERSION = exports.DEFAULT_PASSWORD = exports.PARAMS = void 0;
exports.PARAMS = [
{
// version 0
n: 32768,
r: 8,
p: 1,
},
];
exports.DEFAULT_PASSWORD = 'aezeed';
exports.CIPHER_SEED_VERSION = 0;
exports.ONE_DAY = 24 * 60 * 60 * 1000;

View file

@ -4,7 +4,7 @@ var assert = require('assert')
var Buffer = require('safe-buffer').Buffer
var bs58check = require('bs58check')
var createHash = require('create-hash')
var scrypt = require('./scryptsy')
var scrypt = require('scryptsy')
var xor = require('buffer-xor/inplace')
var ecurve = require('ecurve')

View file

@ -1,3 +0,0 @@
test/
.gitignore
.min-wd

View file

@ -1,70 +0,0 @@
scryptsy
========
[![build status](https://secure.travis-ci.org/cryptocoinjs/scryptsy.svg)](http://travis-ci.org/cryptocoinjs/scryptsy)
[![Coverage Status](https://img.shields.io/coveralls/cryptocoinjs/scryptsy.svg)](https://coveralls.io/r/cryptocoinjs/scryptsy)
[![Version](http://img.shields.io/npm/v/scryptsy.svg)](https://www.npmjs.org/package/scryptsy)
`scryptsy` is a pure Javascript implementation of the [scrypt][wiki] key derivation function that is fully compatible with Node.js and the browser (via Browserify).
Why?
----
`Scrypt` is an integral part of many crypto currencies. It's a part of the [BIP38](https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki) standard for encrypting private Bitcoin keys. It also serves as the [proof-of-work system](http://en.wikipedia.org/wiki/Proof-of-work_system) for many crypto currencies, most notably: Litecoin and Dogecoin.
Installation
------------
npm install --save scryptsy
Example
-------
```js
var scrypt = require('scryptsy')
var key = "pleaseletmein"
var salt = "SodiumChloride"
var data = scrypt(key, salt, 16384, 8, 1, 64)
console.log(data.toString('hex'))
// => 7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887
```
API
---
### scrypt(key, salt, N, r, p, keyLenBytes, [progressCallback])
- **key**: The key. Either `Buffer` or `string`.
- **salt**: The salt. Either `Buffer` or `string`.
- **N**: The number of iterations. `number` (integer)
- **r**: Memory factor. `number` (integer)
- **p**: Parallelization factor. `number` (integer)
- **keyLenBytes**: The number of bytes to return. `number` (integer)
- **progressCallback**: Call callback on every `1000` ops. Passes in `{current, total, percent}` as first parameter to `progressCallback()`.
Returns `Buffer`.
Resources
---------
- [Tarsnap Blurb on Scrypt][tarsnap]
- [Scrypt Whitepaper](http://www.tarsnap.com/scrypt/scrypt.pdf)
- [IETF Scrypt](https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-00) (Test vector params are [incorrect](https://twitter.com/dchest/status/247734446881640448).)
License
-------
MIT
[wiki]: http://en.wikipedia.org/wiki/Scrypt
[tarsnap]: http://www.tarsnap.com/scrypt.html

View file

@ -1,195 +0,0 @@
/* eslint-disable camelcase */
let pbkdf2 = require('pbkdf2')
var MAX_VALUE = 0x7fffffff
// N = Cpu cost, r = Memory cost, p = parallelization cost
async function scrypt (key, salt, N, r, p, dkLen, progressCallback) {
if (N === 0 || (N & (N - 1)) !== 0) throw Error('N must be > 0 and a power of 2')
if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large')
if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large')
var XY = Buffer.alloc(256 * r)
var V = Buffer.alloc(128 * r * N)
// pseudo global
var B32 = new Int32Array(16) // salsa20_8
var x = new Int32Array(16) // salsa20_8
var _X = Buffer.alloc(64) // blockmix_salsa8
// pseudo global
var B = pbkdf2.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256')
var tickCallback
if (progressCallback) {
var totalOps = p * N * 2
var currentOp = 0
tickCallback = function () {
return new Promise(function(resolve, reject) {
++currentOp
// send progress notifications once every 1,000 ops
if (currentOp % 1000 === 0) {
progressCallback({
current: currentOp,
total: totalOps,
percent: (currentOp / totalOps) * 100.0
})
setTimeout(resolve, 10)
} else {
resolve()
}
})
}
}
for (var i = 0; i < p; i++) {
await smix(B, i * 128 * r, r, N, V, XY)
if (typeof shold_stop_bip38 !== 'undefined') break;
}
return pbkdf2.pbkdf2Sync(key, B, 1, dkLen, 'sha256')
// all of these functions are actually moved to the top
// due to function hoisting
async function smix (B, Bi, r, N, V, XY) {
var Xi = 0
var Yi = 128 * r
var i
B.copy(XY, Xi, Bi, Bi + Yi)
for (i = 0; i < N; i++) {
XY.copy(V, i * Yi, Xi, Xi + Yi)
blockmix_salsa8(XY, Xi, Yi, r)
if (tickCallback) {
await tickCallback()
if (typeof shold_stop_bip38 !== 'undefined') break;
}
}
for (i = 0; i < N; i++) {
var offset = Xi + (2 * r - 1) * 64
var j = XY.readUInt32LE(offset) & (N - 1)
blockxor(V, j * Yi, XY, Xi, Yi)
blockmix_salsa8(XY, Xi, Yi, r)
if (tickCallback) {
await tickCallback()
if (typeof shold_stop_bip38 !== 'undefined') break;
}
}
XY.copy(B, Bi, Xi, Xi + Yi)
}
function blockmix_salsa8 (BY, Bi, Yi, r) {
var i
arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64)
for (i = 0; i < 2 * r; i++) {
blockxor(BY, i * 64, _X, 0, 64)
salsa20_8(_X)
arraycopy(_X, 0, BY, Yi + (i * 64), 64)
}
for (i = 0; i < r; i++) {
arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64)
}
for (i = 0; i < r; i++) {
arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64)
}
}
function R (a, b) {
return (a << b) | (a >>> (32 - b))
}
function salsa20_8 (B) {
var i
for (i = 0; i < 16; i++) {
B32[i] = (B[i * 4 + 0] & 0xff) << 0
B32[i] |= (B[i * 4 + 1] & 0xff) << 8
B32[i] |= (B[i * 4 + 2] & 0xff) << 16
B32[i] |= (B[i * 4 + 3] & 0xff) << 24
// B32[i] = B.readUInt32LE(i*4) <--- this is signficantly slower even in Node.js
}
arraycopy(B32, 0, x, 0, 16)
for (i = 8; i > 0; i -= 2) {
x[4] ^= R(x[0] + x[12], 7)
x[8] ^= R(x[4] + x[0], 9)
x[12] ^= R(x[8] + x[4], 13)
x[0] ^= R(x[12] + x[8], 18)
x[9] ^= R(x[5] + x[1], 7)
x[13] ^= R(x[9] + x[5], 9)
x[1] ^= R(x[13] + x[9], 13)
x[5] ^= R(x[1] + x[13], 18)
x[14] ^= R(x[10] + x[6], 7)
x[2] ^= R(x[14] + x[10], 9)
x[6] ^= R(x[2] + x[14], 13)
x[10] ^= R(x[6] + x[2], 18)
x[3] ^= R(x[15] + x[11], 7)
x[7] ^= R(x[3] + x[15], 9)
x[11] ^= R(x[7] + x[3], 13)
x[15] ^= R(x[11] + x[7], 18)
x[1] ^= R(x[0] + x[3], 7)
x[2] ^= R(x[1] + x[0], 9)
x[3] ^= R(x[2] + x[1], 13)
x[0] ^= R(x[3] + x[2], 18)
x[6] ^= R(x[5] + x[4], 7)
x[7] ^= R(x[6] + x[5], 9)
x[4] ^= R(x[7] + x[6], 13)
x[5] ^= R(x[4] + x[7], 18)
x[11] ^= R(x[10] + x[9], 7)
x[8] ^= R(x[11] + x[10], 9)
x[9] ^= R(x[8] + x[11], 13)
x[10] ^= R(x[9] + x[8], 18)
x[12] ^= R(x[15] + x[14], 7)
x[13] ^= R(x[12] + x[15], 9)
x[14] ^= R(x[13] + x[12], 13)
x[15] ^= R(x[14] + x[13], 18)
}
for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i]
for (i = 0; i < 16; i++) {
var bi = i * 4
B[bi + 0] = (B32[i] >> 0 & 0xff)
B[bi + 1] = (B32[i] >> 8 & 0xff)
B[bi + 2] = (B32[i] >> 16 & 0xff)
B[bi + 3] = (B32[i] >> 24 & 0xff)
// B.writeInt32LE(B32[i], i*4) //<--- this is signficantly slower even in Node.js
}
}
// naive approach... going back to loop unrolling may yield additional performance
function blockxor (S, Si, D, Di, len) {
for (var i = 0; i < len; i++) {
D[Di + i] ^= S[Si + i]
}
}
}
function arraycopy (src, srcPos, dest, destPos, length) {
if (Buffer.isBuffer(src) && Buffer.isBuffer(dest)) {
src.copy(dest, destPos, srcPos, srcPos + length)
} else {
while (length--) {
dest[destPos++] = src[srcPos++]
}
}
}
module.exports = scrypt

View file

@ -1,87 +0,0 @@
{
"_from": "scryptsy@^2.0.0",
"_id": "scryptsy@2.0.0",
"_inBundle": false,
"_integrity": "sha1-Jiw28CMc+nZU4jY/o5TNLexm83g=",
"_location": "/scryptsy",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "scryptsy@^2.0.0",
"name": "scryptsy",
"escapedName": "scryptsy",
"rawSpec": "^2.0.0",
"saveSpec": null,
"fetchSpec": "^2.0.0"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.0.0.tgz",
"_shasum": "262c36f0231cfa7654e2363fa394cd2dec66f378",
"_spec": "scryptsy@^2.0.0",
"_where": "/home/burn/Documents/bip38",
"author": "",
"bugs": {
"url": "https://github.com/cryptocoinjs/scryptsy/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "Pure JavaScript implementation of the scrypt key deriviation function that is fully compatible with Node.js and the browser.",
"devDependencies": {
"coveralls": "^2.10.0",
"istanbul": "^0.3.5",
"mocha": "^2.2.0",
"mochify": "^2.1.0",
"standard": "^7.1.1"
},
"homepage": "https://github.com/cryptocoinjs/scryptsy#readme",
"keywords": [
"crytpo",
"cryptography",
"scrypt",
"kdf",
"litecoin",
"dogecoin",
"bitcoin",
"bip38"
],
"license": "MIT",
"main": "lib/scrypt.js",
"name": "scryptsy",
"repository": {
"url": "git+ssh://git@github.com/cryptocoinjs/scryptsy.git",
"type": "git"
},
"scripts": {
"browser-test": "mochify --wd -R spec",
"coverage": "istanbul cover ./node_modules/.bin/_mocha -- --reporter list test/*.js",
"coveralls": "npm run-script coverage && node ./node_modules/.bin/coveralls < coverage/lcov.info",
"lint": "standard",
"test": "mocha --ui bdd",
"unit": "mocha"
},
"version": "2.0.0",
"react-native": {
"path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify"
},
"browser": {
"path": "path-browserify",
"fs": "react-native-level-fs",
"_stream_transform": "readable-stream/transform",
"_stream_readable": "readable-stream/readable",
"_stream_writable": "readable-stream/writable",
"_stream_duplex": "readable-stream/duplex",
"_stream_passthrough": "readable-stream/passthrough",
"stream": "stream-browserify"
}
}

View file

@ -1,5 +1,5 @@
import { useAsyncStorage } from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-community/clipboard';
import Clipboard from '@react-native-clipboard/clipboard';
function BlueClipboard() {
BlueClipboard.STORAGE_KEY = 'ClipboardReadAllowed';

View file

@ -30,6 +30,7 @@ async function setPrefferedCurrency(item) {
async function getPreferredCurrency() {
const preferredCurrency = await JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY));
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.set('preferredCurrency', preferredCurrency.endPointKey);
await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_'));
return preferredCurrency;

View file

@ -1,7 +1,8 @@
import { getSystemName } from 'react-native-device-info';
import { getSystemName, isTablet } from 'react-native-device-info';
import isCatalyst from 'react-native-is-catalyst';
const isMacCatalina = getSystemName() === 'Mac OS X';
module.exports.isMacCatalina = isMacCatalina;
module.exports.isCatalyst = isCatalyst;
module.exports.isTablet = isTablet;

View file

@ -7,8 +7,8 @@ import DocumentPicker from 'react-native-document-picker';
import isCatalyst from 'react-native-is-catalyst';
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
import { presentCameraNotAuthorizedAlert } from '../class/camera';
import Clipboard from '@react-native-community/clipboard';
import ActionSheet from '../screen/ActionSheet';
import BlueClipboard from './clipboard';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const writeFileAndExport = async function (filename, contents) {
@ -170,31 +170,31 @@ const showFilePickerAndReadFile = async function () {
// this is either binary file from ElectrumDesktop OR string file with base64 string in there
file = await _readPsbtFileIntoBase64(uri);
return { data: file, uri: decodeURI(res.uri) };
} else {
if (res.type === DocumentPicker.types.images || res.type.startsWith('image/')) {
return new Promise(resolve => {
const uri = Platform.OS === 'ios' ? res.uri.toString().replace('file://', '') : res.uri;
LocalQRCode.decode(decodeURI(uri), (error, result) => {
if (!error) {
resolve({ data: result, uri: decodeURI(res.uri) });
} else {
resolve({ data: false, uri: false });
}
});
});
} else {
file = await RNFS.readFile(uri);
return { data: file, uri: decodeURI(res.uri) };
}
}
if (res?.type === DocumentPicker.types.images || res?.type?.startsWith('image/')) {
return new Promise(resolve => {
const uri = Platform.OS === 'ios' ? res.uri.toString().replace('file://', '') : res.uri;
LocalQRCode.decode(decodeURI(uri), (error, result) => {
if (!error) {
resolve({ data: result, uri: decodeURI(res.uri) });
} else {
resolve({ data: false, uri: false });
}
});
});
}
file = await RNFS.readFile(uri);
return { data: file, uri: decodeURI(res.uri) };
} catch (err) {
return { data: false, uri: false };
}
};
// Intended for macOS Catalina. Not for long press shortcut
const showActionSheet = async () => {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
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) {
@ -206,7 +206,7 @@ const showActionSheet = async () => {
const importFileButtonIndex = options.length - 1;
return new Promise(resolve =>
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, async buttonIndex => {
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0, anchor: props.anchor }, async buttonIndex => {
if (buttonIndex === 1) {
takePhotoWithImagePickerAndReadPhoto().then(resolve);
} else if (buttonIndex === 2) {
@ -214,7 +214,7 @@ const showActionSheet = async () => {
.then(resolve)
.catch(error => alert(error.message));
} else if (buttonIndex === copyFromClipboardIndex) {
const clipboard = await Clipboard.getString();
const clipboard = await BlueClipboard.getClipboardContent();
resolve(clipboard);
} else if (importFileButtonIndex) {
const { data } = await showFilePickerAndReadFile();

View file

@ -1,9 +1,10 @@
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import { Alert } from 'react-native';
import { Alert, Platform } from 'react-native';
import Frisbee from 'frisbee';
import { getApplicationName, getVersion, getSystemName, getSystemVersion } from 'react-native-device-info';
import { getApplicationName, getVersion, getSystemName, getSystemVersion, hasGmsSync, hasHmsSync } from 'react-native-device-info';
import AsyncStorage from '@react-native-async-storage/async-storage';
import loc from '../loc';
const PushNotification = require('react-native-push-notification');
const constants = require('./constants');
const PUSH_TOKEN = 'PUSH_TOKEN';
@ -28,6 +29,7 @@ function Notifications(props) {
return false;
};
Notifications.isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
/**
* Calls `configure`, which tries to obtain push token, save it, and registers all associated with
* notifications callbacks
@ -120,6 +122,7 @@ function Notifications(props) {
* @returns {Promise<boolean>} TRUE if permissions were obtained, FALSE otherwise
*/
Notifications.tryToObtainPermissions = async function () {
if (!Notifications.isNotificationsCapable) return false;
if (await Notifications.getPushToken()) {
// we already have a token, no sense asking again, just configure pushes to register callbacks and we are done
if (!alreadyConfigured) configureNotifications(); // no await so it executes in background while we return TRUE and use token
@ -127,7 +130,7 @@ function Notifications(props) {
}
if (await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG)) {
// user doesnt want them
// user doesn't want them
return false;
}

View file

@ -1,7 +1,7 @@
import { Platform } from 'react-native';
import prompt from 'react-native-prompt-android';
module.exports = (title, text, isCancelable = true, type = 'secure-text') => {
module.exports = (title, text, isCancelable = true, type = 'secure-text', isOKDestructive = false) => {
const keyboardType = type === 'numeric' ? 'numeric' : 'default';
if (Platform.OS === 'ios' && type === 'numeric') {
@ -25,6 +25,7 @@ module.exports = (title, text, isCancelable = true, type = 'secure-text') => {
console.log('OK Pressed');
resolve(password);
},
style: isOKDestructive ? 'destructive' : 'default',
},
]
: [

View file

@ -1,3 +1,9 @@
3.0.0 / 2019-03-12
------------------
- **breaking** Import gives an object with two functions `scrypt` and `scryptSync`
- `scryptSync` is the old synchronus function.
- `scrypt` will return a promise with the buffer.
2.0.0 / 2016-05-26
------------------
- **breaking** Node v0.10 not supported anymore.

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 cryptocoinjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,157 @@
scryptsy
========
[![build status](https://secure.travis-ci.org/cryptocoinjs/scryptsy.svg)](http://travis-ci.org/cryptocoinjs/scryptsy)
[![Coverage Status](https://img.shields.io/coveralls/cryptocoinjs/scryptsy.svg)](https://coveralls.io/r/cryptocoinjs/scryptsy)
[![Version](http://img.shields.io/npm/v/scryptsy.svg)](https://www.npmjs.org/package/scryptsy)
`scryptsy` is a pure Javascript implementation of the [scrypt][wiki] key derivation function that is fully compatible with Node.js and the browser (via Browserify).
Why?
----
`Scrypt` is an integral part of many crypto currencies. It's a part of the [BIP38](https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki) standard for encrypting private Bitcoin keys. It also serves as the [proof-of-work system](http://en.wikipedia.org/wiki/Proof-of-work_system) for many crypto currencies, most notably: Litecoin and Dogecoin.
Installation
------------
npm install --save scryptsy
Browserify Note
------------
When using a browserified bundle, be sure to add `setImmediate` as a shim.
Example
-------
```js
const scrypt = require('scryptsy')
async function main () {
var key = "pleaseletmein"
var salt = "SodiumChloride"
var data1 = scrypt(key, salt, 16384, 8, 1, 64)
console.log(data1.toString('hex'))
// => 7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887
// async is actually slower, but it will free up the event loop occasionally
// which will allow for front end GUI elements to update and cause it to not
// freeze up.
// See benchmarks below
// Passing 300 below means every 300 iterations internally will call setImmediate once
var data2 = await scrypt.async(key, salt, 16384, 8, 1, 64, undefined, 300)
console.log(data2.toString('hex'))
// => 7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887
}
main().catch(console.error)
```
Benchmarks
-------
Internal iterations are N * p, so changing r doesn't affect the number of calls to setImmediate.
Decreasing pI decreases performance in exchange for more frequently freeing the event loop.
(pI Default is 5000 loops per setImmediate call)
Note: these benchmarks were done on node v10 on a CPU with good single thread performance.
browsers show a much larger difference. Please tinker with the pI setting to balance between
performance and GUI responsiveness.
If `pI >= N`, setImmediate will only be called `p * 2` times total (on the i = 0 of each for loop).
```
---------------------------
time : type : (N,r,p,pI) (pI = promiseInterval)
---------------------------
2266 ms : sync (2^16,16,1)
2548 ms : async (2^16,16,1,5000)
12.44% increase
---------------------------
2616 ms : sync (2^16,1,16)
2995 ms : async (2^16,1,16,5000)
14.49% increase
---------------------------
2685 ms : sync (2^20,1,1)
3090 ms : async (2^20,1,1,5000)
15.08% increase
---------------------------
2235 ms : sync (2^16,16,1)
2627 ms : async (2^16,16,1,10)
17.54% increase
---------------------------
2592 ms : sync (2^16,1,16)
3305 ms : async (2^16,1,16,10)
27.51% increase
---------------------------
2705 ms : sync (2^20,1,1)
3363 ms : async (2^20,1,1,10)
24.33% increase
---------------------------
2278 ms : sync (2^16,16,1)
2773 ms : async (2^16,16,1,1)
21.73% increase
---------------------------
2617 ms : sync (2^16,1,16)
5632 ms : async (2^16,1,16,1)
115.21% increase
---------------------------
2727 ms : sync (2^20,1,1)
5723 ms : async (2^20,1,1,1)
109.86% increase
---------------------------
```
API
---
### scrypt(key, salt, N, r, p, keyLenBytes, [progressCallback])
- **key**: The key. Either `Buffer` or `string`.
- **salt**: The salt. Either `Buffer` or `string`.
- **N**: The number of iterations. `number` (integer)
- **r**: Memory factor. `number` (integer)
- **p**: Parallelization factor. `number` (integer)
- **keyLenBytes**: The number of bytes to return. `number` (integer)
- **progressCallback**: Call callback on every `1000` ops. Passes in `{current, total, percent}` as first parameter to `progressCallback()`.
Returns `Buffer`.
### scrypt.async(key, salt, N, r, p, keyLenBytes, [progressCallback, promiseInterval])
- **key**: The key. Either `Buffer` or `string`.
- **salt**: The salt. Either `Buffer` or `string`.
- **N**: The number of iterations. `number` (integer)
- **r**: Memory factor. `number` (integer)
- **p**: Parallelization factor. `number` (integer)
- **keyLenBytes**: The number of bytes to return. `number` (integer)
- **progressCallback**: Call callback on every `1000` ops. Passes in `{current, total, percent}` as first parameter to `progressCallback()`.
- **promiseInterval**: The number of internal iterations before calling setImmediate once to free the event loop.
Returns `Promise<Buffer>`.
Resources
---------
- [Tarsnap Blurb on Scrypt][tarsnap]
- [Scrypt Whitepaper](http://www.tarsnap.com/scrypt/scrypt.pdf)
- [IETF Scrypt](https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-00) (Test vector params are [incorrect](https://twitter.com/dchest/status/247734446881640448).)
License
-------
MIT
[wiki]: http://en.wikipedia.org/wiki/Scrypt
[tarsnap]: http://www.tarsnap.com/scrypt.html

View file

@ -0,0 +1,3 @@
const scrypt = require('./scryptSync')
scrypt.async = require('./scrypt')
module.exports = scrypt

View file

@ -0,0 +1,26 @@
let pbkdf2 = require('pbkdf2')
const {
checkAndInit,
smix
} = require('./utils')
// N = Cpu cost, r = Memory cost, p = parallelization cost
async function scrypt (key, salt, N, r, p, dkLen, progressCallback, promiseInterval) {
const {
XY,
V,
B32,
x,
_X,
B,
tickCallback
} = checkAndInit(key, salt, N, r, p, dkLen, progressCallback)
for (var i = 0; i < p; i++) {
await smix(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback, promiseInterval)
}
return pbkdf2.pbkdf2Sync(key, B, 1, dkLen, 'sha256')
}
module.exports = scrypt

View file

@ -0,0 +1,26 @@
let pbkdf2 = require('pbkdf2')
const {
checkAndInit,
smixSync
} = require('./utils')
// N = Cpu cost, r = Memory cost, p = parallelization cost
function scrypt (key, salt, N, r, p, dkLen, progressCallback) {
const {
XY,
V,
B32,
x,
_X,
B,
tickCallback
} = checkAndInit(key, salt, N, r, p, dkLen, progressCallback)
for (var i = 0; i < p; i++) {
smixSync(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback)
}
return pbkdf2.pbkdf2Sync(key, B, 1, dkLen, 'sha256')
}
module.exports = scrypt

View file

@ -0,0 +1,216 @@
let pbkdf2 = require('pbkdf2')
const MAX_VALUE = 0x7fffffff
const DEFAULT_PROMISE_INTERVAL = 5000
/* eslint-disable camelcase */
function checkAndInit (key, salt, N, r, p, dkLen, progressCallback) {
if (N === 0 || (N & (N - 1)) !== 0) throw Error('N must be > 0 and a power of 2')
if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large')
if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large')
let XY = Buffer.alloc(256 * r)
let V = Buffer.alloc(128 * r * N)
// pseudo global
let B32 = new Int32Array(16) // salsa20_8
let x = new Int32Array(16) // salsa20_8
let _X = Buffer.alloc(64) // blockmix_salsa8
// pseudo global
let B = pbkdf2.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256')
let tickCallback
if (progressCallback) {
let totalOps = p * N * 2
let currentOp = 0
tickCallback = function () {
++currentOp
// send progress notifications once every 1,000 ops
if (currentOp % 1000 === 0) {
progressCallback({
current: currentOp,
total: totalOps,
percent: (currentOp / totalOps) * 100.0
})
}
}
}
return {
XY,
V,
B32,
x,
_X,
B,
tickCallback
}
}
async function smix (B, Bi, r, N, V, XY, _X, B32, x, tickCallback, promiseInterval) {
promiseInterval = promiseInterval || DEFAULT_PROMISE_INTERVAL
let Xi = 0
let Yi = 128 * r
let i
B.copy(XY, Xi, Bi, Bi + Yi)
for (i = 0; i < N; i++) {
XY.copy(V, i * Yi, Xi, Xi + Yi)
if (i % promiseInterval === 0) {
await new Promise(resolve => setImmediate(resolve))
}
blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x)
if (tickCallback) tickCallback()
}
for (i = 0; i < N; i++) {
let offset = Xi + (2 * r - 1) * 64
let j = XY.readUInt32LE(offset) & (N - 1)
blockxor(V, j * Yi, XY, Xi, Yi)
if (i % promiseInterval === 0) {
await new Promise(resolve => setImmediate(resolve))
}
blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x)
if (tickCallback) tickCallback()
}
XY.copy(B, Bi, Xi, Xi + Yi)
}
function smixSync (B, Bi, r, N, V, XY, _X, B32, x, tickCallback) {
let Xi = 0
let Yi = 128 * r
let i
B.copy(XY, Xi, Bi, Bi + Yi)
for (i = 0; i < N; i++) {
XY.copy(V, i * Yi, Xi, Xi + Yi)
blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x)
if (tickCallback) tickCallback()
}
for (i = 0; i < N; i++) {
let offset = Xi + (2 * r - 1) * 64
let j = XY.readUInt32LE(offset) & (N - 1)
blockxor(V, j * Yi, XY, Xi, Yi)
blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x)
if (tickCallback) tickCallback()
}
XY.copy(B, Bi, Xi, Xi + Yi)
}
function blockmix_salsa8 (BY, Bi, Yi, r, _X, B32, x) {
let i
arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64)
for (i = 0; i < 2 * r; i++) {
blockxor(BY, i * 64, _X, 0, 64)
salsa20_8(_X, B32, x)
arraycopy(_X, 0, BY, Yi + (i * 64), 64)
}
for (i = 0; i < r; i++) {
arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64)
}
for (i = 0; i < r; i++) {
arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64)
}
}
function R (a, b) {
return (a << b) | (a >>> (32 - b))
}
function salsa20_8 (B, B32, x) {
let i
for (i = 0; i < 16; i++) {
B32[i] = (B[i * 4 + 0] & 0xff) << 0
B32[i] |= (B[i * 4 + 1] & 0xff) << 8
B32[i] |= (B[i * 4 + 2] & 0xff) << 16
B32[i] |= (B[i * 4 + 3] & 0xff) << 24
// B32[i] = B.readUInt32LE(i*4) <--- this is signficantly slower even in Node.js
}
arraycopy(B32, 0, x, 0, 16)
for (i = 8; i > 0; i -= 2) {
x[4] ^= R(x[0] + x[12], 7)
x[8] ^= R(x[4] + x[0], 9)
x[12] ^= R(x[8] + x[4], 13)
x[0] ^= R(x[12] + x[8], 18)
x[9] ^= R(x[5] + x[1], 7)
x[13] ^= R(x[9] + x[5], 9)
x[1] ^= R(x[13] + x[9], 13)
x[5] ^= R(x[1] + x[13], 18)
x[14] ^= R(x[10] + x[6], 7)
x[2] ^= R(x[14] + x[10], 9)
x[6] ^= R(x[2] + x[14], 13)
x[10] ^= R(x[6] + x[2], 18)
x[3] ^= R(x[15] + x[11], 7)
x[7] ^= R(x[3] + x[15], 9)
x[11] ^= R(x[7] + x[3], 13)
x[15] ^= R(x[11] + x[7], 18)
x[1] ^= R(x[0] + x[3], 7)
x[2] ^= R(x[1] + x[0], 9)
x[3] ^= R(x[2] + x[1], 13)
x[0] ^= R(x[3] + x[2], 18)
x[6] ^= R(x[5] + x[4], 7)
x[7] ^= R(x[6] + x[5], 9)
x[4] ^= R(x[7] + x[6], 13)
x[5] ^= R(x[4] + x[7], 18)
x[11] ^= R(x[10] + x[9], 7)
x[8] ^= R(x[11] + x[10], 9)
x[9] ^= R(x[8] + x[11], 13)
x[10] ^= R(x[9] + x[8], 18)
x[12] ^= R(x[15] + x[14], 7)
x[13] ^= R(x[12] + x[15], 9)
x[14] ^= R(x[13] + x[12], 13)
x[15] ^= R(x[14] + x[13], 18)
}
for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i]
for (i = 0; i < 16; i++) {
let bi = i * 4
B[bi + 0] = (B32[i] >> 0 & 0xff)
B[bi + 1] = (B32[i] >> 8 & 0xff)
B[bi + 2] = (B32[i] >> 16 & 0xff)
B[bi + 3] = (B32[i] >> 24 & 0xff)
// B.writeInt32LE(B32[i], i*4) //<--- this is signficantly slower even in Node.js
}
}
// naive approach... going back to loop unrolling may yield additional performance
function blockxor (S, Si, D, Di, len) {
for (let i = 0; i < len; i++) {
D[Di + i] ^= S[Si + i]
}
}
function arraycopy (src, srcPos, dest, destPos, length) {
if (Buffer.isBuffer(src) && Buffer.isBuffer(dest)) {
src.copy(dest, destPos, srcPos, srcPos + length)
} else {
while (length--) {
dest[destPos++] = src[srcPos++]
}
}
}
module.exports = {
checkAndInit,
smix,
smixSync
}

View file

@ -0,0 +1,65 @@
{
"_from": "scryptsy@2.1.0",
"_id": "scryptsy@2.1.0",
"_inBundle": false,
"_integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==",
"_location": "/scryptsy",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "scryptsy@2.1.0",
"name": "scryptsy",
"escapedName": "scryptsy",
"rawSpec": "2.1.0",
"saveSpec": null,
"fetchSpec": "2.1.0"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz",
"_shasum": "8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790",
"_spec": "scryptsy@2.1.0",
"_where": "/home/overtorment/Documents/BlueWallet",
"author": "",
"bugs": {
"url": "https://github.com/cryptocoinjs/scryptsy/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "Pure JavaScript implementation of the scrypt key deriviation function that is fully compatible with Node.js and the browser.",
"devDependencies": {},
"files": [
"lib"
],
"homepage": "https://github.com/cryptocoinjs/scryptsy#readme",
"keywords": [
"crytpo",
"cryptography",
"scrypt",
"kdf",
"litecoin",
"dogecoin",
"bitcoin",
"bip38"
],
"license": "MIT",
"main": "lib/index.js",
"name": "scryptsy",
"repository": {
"url": "git+ssh://git@github.com/cryptocoinjs/scryptsy.git",
"type": "git"
},
"scripts": {
"browser-test": "mochify --wd -R spec",
"coverage": "nyc --check-coverage --statements 80 --branches 60 --functions 90 --lines 90 mocha",
"coveralls": "npm run-script coverage && coveralls < coverage/lcov.info",
"lint": "standard",
"test": "mocha --ui bdd",
"unit": "mocha"
},
"version": "2.1.0"
}

View file

@ -1,23 +1,34 @@
/* eslint-disable react/prop-types */
import { useAsyncStorage } from '@react-native-async-storage/async-storage';
import React, { createContext, useEffect, useState } from 'react';
import { LayoutAnimation } from 'react-native';
import { AppStorage } from '../class';
import { FiatUnit } from '../models/fiatUnit';
const BlueApp = require('../BlueApp');
const BlueElectrum = require('./BlueElectrum');
const _lastTimeTriedToRefetchWallet = {}; // hashmap of timestamps we _started_ refetching some wallet
export const WalletTransactionsStatus = { NONE: false, ALL: true };
export const BlueStorageContext = createContext();
export const BlueStorageProvider = ({ children }) => {
const [wallets, setWallets] = useState([]);
const [pendingWallets, setPendingWallets] = useState([]);
const [selectedWallet, setSelectedWallet] = useState('');
const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState(WalletTransactionsStatus.NONE);
const [walletsInitialized, setWalletsInitialized] = useState(false);
const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState();
const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState(FiatUnit.USD);
const [language, _setLanguage] = useState();
const getPreferredCurrencyAsyncStorage = useAsyncStorage(AppStorage.PREFERRED_CURRENCY).getItem;
const getLanguageAsyncStorage = useAsyncStorage(AppStorage.LANG).getItem;
const [newWalletAdded, setNewWalletAdded] = useState(false);
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
const [isDrawerListBlurred, _setIsDrawerListBlurred] = useState(false);
const setIsHandOffUseEnabledAsyncStorage = value => {
setIsHandOffUseEnabled(value);
return BlueApp.setItem(AppStorage.HANDOFF_STORAGE_KEY, value === true ? '1' : '');
};
const saveToDisk = async () => {
BlueApp.tx_metadata = txMetadata;
await BlueApp.saveToDisk();
@ -29,6 +40,23 @@ export const BlueStorageProvider = ({ children }) => {
setWallets(BlueApp.getWallets());
}, []);
useEffect(() => {
(async () => {
try {
const enabledHandoff = await BlueApp.getItem(AppStorage.HANDOFF_STORAGE_KEY);
setIsHandOffUseEnabled(!!enabledHandoff);
} catch (_e) {
setIsHandOffUseEnabledAsyncStorage(false);
setIsHandOffUseEnabled(false);
}
})();
}, []);
const setIsDrawerListBlurred = value => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
_setIsDrawerListBlurred(value);
};
const getPreferredCurrency = async () => {
const item = await getPreferredCurrencyAsyncStorage();
_setPreferredFiatCurrency(item);
@ -62,9 +90,12 @@ export const BlueStorageProvider = ({ children }) => {
saveToDisk();
};
const refreshAllWalletTransactions = async lastSnappedTo => {
const refreshAllWalletTransactions = async (lastSnappedTo, showUpdateStatusIndicator = true) => {
let noErr = true;
try {
if (showUpdateStatusIndicator) {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.ALL);
}
await BlueElectrum.waitTillConnected();
const balanceStart = +new Date();
await fetchWalletBalances(lastSnappedTo);
@ -77,6 +108,8 @@ export const BlueStorageProvider = ({ children }) => {
} catch (err) {
noErr = false;
console.warn(err);
} finally {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
}
if (noErr) await saveToDisk(); // caching
};
@ -86,6 +119,7 @@ export const BlueStorageProvider = ({ children }) => {
let noErr = true;
try {
// 5sec debounce:
setWalletTransactionUpdateStatus(walletID);
if (+new Date() - _lastTimeTriedToRefetchWallet[walletID] < 5000) {
console.log('re-fetch wallet happens too fast; NOP');
return;
@ -104,6 +138,8 @@ export const BlueStorageProvider = ({ children }) => {
} catch (err) {
noErr = false;
console.warn(err);
} finally {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
}
if (noErr) await saveToDisk(); // caching
};
@ -133,8 +169,6 @@ export const BlueStorageProvider = ({ children }) => {
const getHodlHodlApiKey = BlueApp.getHodlHodlApiKey;
const createFakeStorage = BlueApp.createFakeStorage;
const decryptStorage = BlueApp.decryptStorage;
const isDeleteWalletAfterUninstallEnabled = BlueApp.isDeleteWalletAfterUninstallEnabled;
const setResetOnAppUninstallTo = BlueApp.setResetOnAppUninstallTo;
const isPasswordInUse = BlueApp.isPasswordInUse;
const cachedPassword = BlueApp.cachedPassword;
const setIsAdancedModeEnabled = BlueApp.setIsAdancedModeEnabled;
@ -178,19 +212,21 @@ export const BlueStorageProvider = ({ children }) => {
sleep,
setHodlHodlApiKey,
createFakeStorage,
newWalletAdded,
setNewWalletAdded,
resetWallets,
getHodlHodlApiKey,
isDeleteWalletAfterUninstallEnabled,
decryptStorage,
setResetOnAppUninstallTo,
isPasswordInUse,
setIsAdancedModeEnabled,
setPreferredFiatCurrency,
preferredFiatCurrency,
setLanguage,
language,
isHandOffUseEnabled,
setIsHandOffUseEnabledAsyncStorage,
walletTransactionUpdateStatus,
setWalletTransactionUpdateStatus,
isDrawerListBlurred,
setIsDrawerListBlurred,
}}
>
{children}

View file

@ -14,9 +14,9 @@ import {
LightningCustodianWallet,
HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet,
HDAezeedWallet,
MultisigHDWallet,
} from './';
import { Platform } from 'react-native';
const encryption = require('../blue_modules/encryption');
const Realm = require('realm');
const createHash = require('create-hash');
@ -33,10 +33,10 @@ export class AppStorage {
static ELECTRUM_SERVER_HISTORY = 'electrum_server_history';
static PREFERRED_CURRENCY = 'preferredCurrency';
static ADVANCED_MODE_ENABLED = 'advancedmodeenabled';
static DELETE_WALLET_AFTER_UNINSTALL = 'deleteWalletAfterUninstall';
static HODL_HODL_API_KEY = 'HODL_HODL_API_KEY';
static HODL_HODL_SIGNATURE_KEY = 'HODL_HODL_SIGNATURE_KEY';
static HODL_HODL_CONTRACTS = 'HODL_HODL_CONTRACTS';
static HANDOFF_STORAGE_KEY = 'HandOff';
constructor() {
/** {Array.<AbstractWallet>} */
@ -76,17 +76,6 @@ export class AppStorage {
}
};
setResetOnAppUninstallTo = async value => {
if (Platform.OS === 'ios') {
await this.setItem(AppStorage.DELETE_WALLET_AFTER_UNINSTALL, value ? '1' : '');
try {
RNSecureKeyStore.setResetOnAppUninstallTo(value);
} catch (Error) {
console.warn(Error);
}
}
};
storageIsEncrypted = async () => {
let data;
try {
@ -121,11 +110,7 @@ export class AppStorage {
let decrypted;
let num = 0;
for (const value of data) {
try {
decrypted = encryption.decrypt(value, password);
} catch (e) {
console.log(e.message);
}
decrypted = encryption.decrypt(value, password);
if (decrypted) {
usedBucketNum = num;
@ -140,7 +125,6 @@ export class AppStorage {
decryptStorage = async password => {
if (password === this.cachedPassword) {
this.cachedPassword = undefined;
await this.setResetOnAppUninstallTo(true);
await this.saveToDisk();
this.wallets = [];
this.tx_metadata = [];
@ -150,16 +134,6 @@ export class AppStorage {
}
};
isDeleteWalletAfterUninstallEnabled = async () => {
let deleteWalletsAfterUninstall;
try {
deleteWalletsAfterUninstall = await this.getItem(AppStorage.DELETE_WALLET_AFTER_UNINSTALL);
} catch (_e) {
deleteWalletsAfterUninstall = true;
}
return !!deleteWalletsAfterUninstall;
};
encryptStorage = async password => {
// assuming the storage is not yet encrypted
await this.saveToDisk();
@ -242,106 +216,104 @@ export class AppStorage {
* @returns {Promise.<boolean>}
*/
async loadFromDisk(password) {
try {
let data = await this.getItem('data');
if (password) {
data = this.decryptData(data, password);
if (data) {
// password is good, cache it
this.cachedPassword = password;
}
let data = await this.getItem('data');
if (password) {
data = this.decryptData(data, password);
if (data) {
// password is good, cache it
this.cachedPassword = password;
}
if (data !== null) {
const realm = await this.getRealm();
data = JSON.parse(data);
if (!data.wallets) return false;
const wallets = data.wallets;
for (const key of wallets) {
// deciding which type is wallet and instatiating correct object
const tempObj = JSON.parse(key);
let unserializedWallet;
switch (tempObj.type) {
case PlaceholderWallet.type:
}
if (data !== null) {
const realm = await this.getRealm();
data = JSON.parse(data);
if (!data.wallets) return false;
const wallets = data.wallets;
for (const key of wallets) {
// deciding which type is wallet and instatiating correct object
const tempObj = JSON.parse(key);
let unserializedWallet;
switch (tempObj.type) {
case PlaceholderWallet.type:
continue;
case SegwitBech32Wallet.type:
unserializedWallet = SegwitBech32Wallet.fromJson(key);
break;
case SegwitP2SHWallet.type:
unserializedWallet = SegwitP2SHWallet.fromJson(key);
break;
case WatchOnlyWallet.type:
unserializedWallet = WatchOnlyWallet.fromJson(key);
unserializedWallet.init();
if (unserializedWallet.isHd() && !unserializedWallet.isXpubValid()) {
continue;
case SegwitBech32Wallet.type:
unserializedWallet = SegwitBech32Wallet.fromJson(key);
break;
case SegwitP2SHWallet.type:
unserializedWallet = SegwitP2SHWallet.fromJson(key);
break;
case WatchOnlyWallet.type:
unserializedWallet = WatchOnlyWallet.fromJson(key);
unserializedWallet.init();
if (unserializedWallet.isHd() && !unserializedWallet.isXpubValid()) {
continue;
}
break;
case HDLegacyP2PKHWallet.type:
unserializedWallet = HDLegacyP2PKHWallet.fromJson(key);
break;
case HDSegwitP2SHWallet.type:
unserializedWallet = HDSegwitP2SHWallet.fromJson(key);
break;
case HDSegwitBech32Wallet.type:
unserializedWallet = HDSegwitBech32Wallet.fromJson(key);
break;
case HDLegacyBreadwalletWallet.type:
unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key);
break;
case HDLegacyElectrumSeedP2PKHWallet.type:
unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key);
break;
case HDSegwitElectrumSeedP2WPKHWallet.type:
unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key);
break;
case MultisigHDWallet.type:
unserializedWallet = MultisigHDWallet.fromJson(key);
break;
case LightningCustodianWallet.type: {
/** @type {LightningCustodianWallet} */
unserializedWallet = LightningCustodianWallet.fromJson(key);
let lndhub = false;
try {
lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB);
} catch (Error) {
console.warn(Error);
}
if (unserializedWallet.baseURI) {
unserializedWallet.setBaseURI(unserializedWallet.baseURI); // not really necessary, just for the sake of readability
console.log('using saved uri for for ln wallet:', unserializedWallet.baseURI);
} else if (lndhub) {
console.log('using wallet-wide settings ', lndhub, 'for ln wallet');
unserializedWallet.setBaseURI(lndhub);
} else {
console.log('using default', LightningCustodianWallet.defaultBaseUri, 'for ln wallet');
unserializedWallet.setBaseURI(LightningCustodianWallet.defaultBaseUri);
}
unserializedWallet.init();
break;
}
case LegacyWallet.type:
default:
unserializedWallet = LegacyWallet.fromJson(key);
break;
}
break;
case HDLegacyP2PKHWallet.type:
unserializedWallet = HDLegacyP2PKHWallet.fromJson(key);
break;
case HDSegwitP2SHWallet.type:
unserializedWallet = HDSegwitP2SHWallet.fromJson(key);
break;
case HDSegwitBech32Wallet.type:
unserializedWallet = HDSegwitBech32Wallet.fromJson(key);
break;
case HDLegacyBreadwalletWallet.type:
unserializedWallet = HDLegacyBreadwalletWallet.fromJson(key);
break;
case HDLegacyElectrumSeedP2PKHWallet.type:
unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key);
break;
case HDSegwitElectrumSeedP2WPKHWallet.type:
unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key);
break;
case MultisigHDWallet.type:
unserializedWallet = MultisigHDWallet.fromJson(key);
break;
case HDAezeedWallet.type:
unserializedWallet = HDAezeedWallet.fromJson(key);
break;
case LightningCustodianWallet.type: {
/** @type {LightningCustodianWallet} */
unserializedWallet = LightningCustodianWallet.fromJson(key);
let lndhub = false;
try {
lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB);
} catch (Error) {
console.warn(Error);
}
this.inflateWalletFromRealm(realm, unserializedWallet);
// done
if (!this.wallets.some(wallet => wallet.getSecret() === unserializedWallet.secret)) {
this.wallets.push(unserializedWallet);
this.tx_metadata = data.tx_metadata;
if (unserializedWallet.baseURI) {
unserializedWallet.setBaseURI(unserializedWallet.baseURI); // not really necessary, just for the sake of readability
console.log('using saved uri for for ln wallet:', unserializedWallet.baseURI);
} else if (lndhub) {
console.log('using wallet-wide settings ', lndhub, 'for ln wallet');
unserializedWallet.setBaseURI(lndhub);
} else {
console.log('using default', LightningCustodianWallet.defaultBaseUri, 'for ln wallet');
unserializedWallet.setBaseURI(LightningCustodianWallet.defaultBaseUri);
}
unserializedWallet.init();
break;
}
case LegacyWallet.type:
default:
unserializedWallet = LegacyWallet.fromJson(key);
break;
}
this.inflateWalletFromRealm(realm, unserializedWallet);
// done
if (!this.wallets.some(wallet => wallet.getSecret() === unserializedWallet.secret)) {
this.wallets.push(unserializedWallet);
this.tx_metadata = data.tx_metadata;
}
realm.close();
return true;
} else {
return false; // failed loading data or loading/decryptin data
}
} catch (error) {
console.warn(error.message);
return false;
realm.close();
return true;
} else {
return false; // failed loading data or loading/decryptin data
}
}

View file

@ -1,4 +1,4 @@
import { AppStorage, LightningCustodianWallet } from './';
import { AppStorage, LightningCustodianWallet, WatchOnlyWallet } from './';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RNFS from 'react-native-fs';
import url from 'url';
@ -47,7 +47,6 @@ class DeeplinkSchemaMatch {
if (context.wallets.length >= 0) {
const wallet = context.wallets[0];
const action = event.url.split('widget?action=')[1];
const secret = wallet.getSecret();
if (wallet.chain === Chain.ONCHAIN) {
if (action === 'openSend') {
completionHandler([
@ -55,7 +54,7 @@ class DeeplinkSchemaMatch {
{
screen: 'SendDetails',
params: {
secret,
walletID: wallet.getID(),
},
},
]);
@ -179,6 +178,17 @@ class DeeplinkSchemaMatch {
params: Azteco.getParamsFromUrl(event.url),
},
]);
} else if (new WatchOnlyWallet().setSecret(event.url).init().valid()) {
completionHandler([
'AddWalletRoot',
{
screen: 'ImportWallet',
params: {
triggerImport: true,
label: event.url,
},
},
]);
} else {
const urlObject = url.parse(event.url, true); // eslint-disable-line node/no-deprecated-api
(async () => {
@ -322,7 +332,7 @@ class DeeplinkSchemaMatch {
screen: 'SendDetails',
params: {
uri: uri.bitcoin,
fromWallet: wallet,
walletID: wallet.getID(),
},
},
];

View file

@ -1,24 +0,0 @@
import { Platform } from 'react-native';
const BlueApp = require('../BlueApp');
export default class HandoffSettings {
static STORAGEKEY = 'HandOff';
static async isHandoffUseEnabled() {
if (Platform.OS !== 'ios') {
return false;
}
try {
const enabledHandoff = await BlueApp.getItem(HandoffSettings.STORAGEKEY);
return !!enabledHandoff;
} catch (_e) {
await BlueApp.setItem(HandoffSettings.STORAGEKEY, '');
return false;
}
}
static async setHandoffUseEnabled(value) {
await BlueApp.setItem(HandoffSettings.STORAGEKEY, value === true && Platform.OS === 'ios' ? '1' : '');
}
}

View file

@ -76,39 +76,6 @@ export class HodlHodlApi {
return (this._countries = json.countries);
}
async getMyCountryCode() {
const _api = new Frisbee({ baseURI: 'https://ifconfig.co/' });
const _api2 = new Frisbee({ baseURI: 'https://geolocation-db.com/' });
let response;
let allowedTries = 6;
while (allowedTries > 0) {
// this API fails a lot, so lets retry several times
response = await _api.get('/country-iso', { headers: { 'Access-Control-Allow-Origin': '*' } });
let body = response.body;
if (typeof body === 'string') body = body.replace('\n', '');
if (!body || body.length !== 2) {
// trying api2
const response = await _api2.get('/json/', { headers: { 'Access-Control-Allow-Origin': '*' } });
body = response.body;
let json;
try {
json = JSON.parse(body);
} catch (_) {}
if (json && json.country_code) return (this._myCountryCode = json.country_code);
// failed, retry
allowedTries--;
await (async () => new Promise(resolve => setTimeout(resolve, 3000)))(); // sleep
} else {
return (this._myCountryCode = body);
}
}
throw new Error('API failure after several tries: ' + JSON.stringify(response));
}
async getPaymentMethods(country) {
const response = await this._api.get('/api/v1/payment_methods?filters[country]=' + country, this._getHeaders());

View file

@ -13,6 +13,7 @@ export * from './wallets/hd-segwit-bech32-wallet';
export * from './wallets/placeholder-wallet';
export * from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
export * from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
export * from './wallets/hd-aezeed-wallet';
export * from './wallets/multisig-hd-wallet';
export * from './hd-segwit-bech32-transaction';
export * from './multisig-cosigner';

View file

@ -0,0 +1,13 @@
function DeviceQuickActions() {
DeviceQuickActions.STORAGE_KEY = 'DeviceQuickActionsEnabled';
DeviceQuickActions.setEnabled = () => {};
DeviceQuickActions.getEnabled = async () => {
return false;
};
return null;
}
export default DeviceQuickActions;

View file

@ -10,19 +10,21 @@ import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet';
import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
import { MultisigHDWallet } from './wallets/multisig-hd-wallet';
import { HDAezeedWallet } from "./wallets/hd-aezeed-wallet";
import { useTheme } from '@react-navigation/native';
export default class WalletGradient {
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
static hdSegwitBech32Wallet = ['#68bbe1', '#3b73d4'];
static segwitBech32Wallet = ['#f8bbe1', '#945a90'];
static watchOnlyWallet = ['#7d7d7d', '#4a4a4a'];
static legacyWallet = ['#40fad1', '#15be98'];
static hdLegacyP2PKHWallet = ['#e36dfa', '#bd10e0'];
static hdSegwitP2SHWallet = ['#007AFF', '#0040FF'];
static hdSegwitBech32Wallet = ['#6CD9FC', '#44BEE5'];
static segwitBech32Wallet = ['#6CD9FC', '#44BEE5'];
static watchOnlyWallet = ['#474646', '#282828'];
static legacyWallet = ['#37E8C0', '#15BE98'];
static hdLegacyP2PKHWallet = ['#FD7478', '#E73B40'];
static hdLegacyBreadWallet = ['#fe6381', '#f99c42'];
static multisigHdWallet = ['#1ce6eb', '#296fc5', '#3500A2'];
static defaultGradients = ['#c65afb', '#9053fe'];
static lightningCustodianWallet = ['#f1be07', '#f79056'];
static defaultGradients = ['#B770F6', '#9013FE'];
static lightningCustodianWallet = ['#F1AA07', '#FD7E37'];
static aezeedWallet = ['#8584FF', '#5351FB'];
static createWallet = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
@ -65,6 +67,9 @@ export default class WalletGradient {
case MultisigHDWallet.type:
gradient = WalletGradient.multisigHdWallet;
break;
case HDAezeedWallet.type:
gradient = WalletGradient.aezeedWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;
@ -119,6 +124,9 @@ export default class WalletGradient {
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;
case HDAezeedWallet.type:
gradient = WalletGradient.aezeedWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;

View file

@ -12,6 +12,7 @@ import {
SegwitBech32Wallet,
HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet,
HDAezeedWallet,
MultisigHDWallet,
} from '.';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
@ -19,13 +20,14 @@ import loc from '../loc';
import { useContext } from 'react';
import { BlueStorageContext } from '../blue_modules/storage-context';
import Notifications from '../blue_modules/notifications';
import IdleTimerManager from 'react-native-idle-timer';
const A = require('../blue_modules/analytics');
const bip38 = require('../blue_modules/bip38');
const wif = require('wif');
const prompt = require('../blue_modules/prompt');
function WalletImport() {
const { wallets, pendingWallets, setPendingWallets, saveToDisk, addWallet, setNewWalletAdded } = useContext(BlueStorageContext);
const { wallets, pendingWallets, setPendingWallets, saveToDisk, addWallet } = useContext(BlueStorageContext);
/**
*
@ -35,6 +37,7 @@ function WalletImport() {
* @private
*/
WalletImport._saveWallet = async (w, additionalProperties) => {
IdleTimerManager.setIdleTimerDisabled(false);
if (WalletImport.isWalletImported(w)) {
WalletImport.presentWalletAlreadyExistsAlert();
return;
@ -50,7 +53,6 @@ function WalletImport() {
}
addWallet(w);
await saveToDisk();
setNewWalletAdded(true);
A(A.ENUM.CREATED_WALLET);
alert(loc.wallets.import_success);
Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []);
@ -91,6 +93,7 @@ function WalletImport() {
* @returns {Promise<void>}
*/
WalletImport.processImportText = async (importText, additionalProperties) => {
IdleTimerManager.setIdleTimerDisabled(true);
// Plan:
// -2. check if BIP38 encrypted
// -1a. check if multisig
@ -100,6 +103,7 @@ function WalletImport() {
// 2. check if its HDLegacyP2PKHWallet (BIP44)
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")
// 3.1 check HD Electrum legacy
// 3.2 check if its AEZEED
// 4. check if its Segwit WIF (P2SH)
// 5. check if its Legacy WIF
// 6. check if its address (watch-only wallet)
@ -112,9 +116,7 @@ function WalletImport() {
password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password, false);
} while (!password);
const decryptedKey = await bip38.decrypt(importText, password, status => {
console.warn(status.percent + '%');
});
const decryptedKey = await bip38.decrypt(importText, password);
if (decryptedKey) {
importText = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed);
@ -158,11 +160,33 @@ function WalletImport() {
const hd4 = new HDSegwitBech32Wallet();
hd4.setSecret(importText);
if (hd4.validateMnemonic()) {
await hd4.fetchBalance();
if (hd4.getBalance() > 0) {
// await hd4.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
// OK its a valid BIP39 seed
if (await hd4.wasEverUsed()) {
await hd4.fetchBalance(); // fetching balance for BIP84 only on purpose
return WalletImport._saveWallet(hd4);
}
const hd2 = new HDSegwitP2SHWallet();
hd2.setSecret(importText);
if (await hd2.wasEverUsed()) {
return WalletImport._saveWallet(hd2);
}
const hd3 = new HDLegacyP2PKHWallet();
hd3.setSecret(importText);
if (await hd3.wasEverUsed()) {
return WalletImport._saveWallet(hd3);
}
const hd1 = new HDLegacyBreadwalletWallet();
hd1.setSecret(importText);
if (await hd1.wasEverUsed()) {
return WalletImport._saveWallet(hd1);
}
// no scheme (BIP84/BIP49/BIP44/Bread) was ever used. lets import as default BIP84:
return WalletImport._saveWallet(hd4);
}
const segwitWallet = new SegwitP2SHWallet();
@ -170,28 +194,24 @@ function WalletImport() {
if (segwitWallet.getAddress()) {
// ok its a valid WIF
const legacyWallet = new LegacyWallet();
legacyWallet.setSecret(importText);
const segwitBech32Wallet = new SegwitBech32Wallet();
segwitBech32Wallet.setSecret(importText);
await legacyWallet.fetchBalance();
await segwitBech32Wallet.fetchBalance();
if (legacyWallet.getBalance() > 0) {
// yep, its legacy we're importing
await legacyWallet.fetchTransactions();
return WalletImport._saveWallet(legacyWallet);
} else if (segwitBech32Wallet.getBalance() > 0) {
if (await segwitBech32Wallet.wasEverUsed()) {
// yep, its single-address bech32 wallet
await segwitBech32Wallet.fetchTransactions();
await segwitBech32Wallet.fetchBalance();
return WalletImport._saveWallet(segwitBech32Wallet);
} else {
// by default, we import wif as Segwit P2SH
}
if (await segwitWallet.wasEverUsed()) {
// yep, its single-address bech32 wallet
await segwitWallet.fetchBalance();
await segwitWallet.fetchTransactions();
return WalletImport._saveWallet(segwitWallet);
}
// default wallet is Legacy
const legacyWallet = new LegacyWallet();
legacyWallet.setSecret(importText);
return WalletImport._saveWallet(legacyWallet);
}
// case - WIF is valid, just has uncompressed pubkey
@ -206,20 +226,10 @@ function WalletImport() {
// if we're here - nope, its not a valid WIF
const hd1 = new HDLegacyBreadwalletWallet();
hd1.setSecret(importText);
if (hd1.validateMnemonic()) {
await hd1.fetchBalance();
if (hd1.getBalance() > 0) {
// await hd1.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
return WalletImport._saveWallet(hd1);
}
}
try {
const hdElectrumSeedLegacy = new HDSegwitElectrumSeedP2WPKHWallet();
hdElectrumSeedLegacy.setSecret(importText);
if (await hdElectrumSeedLegacy.wasEverUsed()) {
if (hdElectrumSeedLegacy.validateMnemonic()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy);
}
@ -228,70 +238,39 @@ function WalletImport() {
try {
const hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
hdElectrumSeedLegacy.setSecret(importText);
if (await hdElectrumSeedLegacy.wasEverUsed()) {
if (hdElectrumSeedLegacy.validateMnemonic()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy);
}
} catch (_) {}
const hd2 = new HDSegwitP2SHWallet();
hd2.setSecret(importText);
if (hd2.validateMnemonic()) {
await hd2.fetchBalance();
if (hd2.getBalance() > 0) {
// await hd2.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
return WalletImport._saveWallet(hd2);
}
}
// is it AEZEED?
try {
const aezeed = new HDAezeedWallet();
aezeed.setSecret(importText);
if (await aezeed.validateMnemonicAsync()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(aezeed);
} else {
// there is a chance that a password is required
if (await aezeed.mnemonicInvalidPassword()) {
const password = await prompt(loc.wallets.enter_bip38_password, '', false);
if (!password) {
// no passord is basically cancel whole aezeed import process
throw new Error(loc._.bad_password);
}
const hd3 = new HDLegacyP2PKHWallet();
hd3.setSecret(importText);
if (hd3.validateMnemonic()) {
await hd3.fetchBalance();
if (hd3.getBalance() > 0) {
// await hd3.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
return WalletImport._saveWallet(hd3);
const mnemonics = importText.split(':')[0];
return WalletImport.processImportText(mnemonics + ':' + password);
}
}
}
// no balances? how about transactions count?
if (hd1.validateMnemonic()) {
await hd1.fetchTransactions();
if (hd1.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd1);
}
}
if (hd2.validateMnemonic()) {
await hd2.fetchTransactions();
if (hd2.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd2);
}
}
if (hd3.validateMnemonic()) {
await hd3.fetchTransactions();
if (hd3.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd3);
}
}
if (hd4.validateMnemonic()) {
await hd4.fetchTransactions();
if (hd4.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd4);
}
}
// is it even valid? if yes we will import as:
if (hd4.validateMnemonic()) {
return WalletImport._saveWallet(hd4);
}
} catch (_) {}
// not valid? maybe its a watch-only address?
const watchOnly = new WatchOnlyWallet();
watchOnly.setSecret(importText);
if (watchOnly.valid()) {
// await watchOnly.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
await watchOnly.fetchBalance();
return WalletImport._saveWallet(watchOnly, additionalProperties);
}
@ -299,10 +278,10 @@ function WalletImport() {
// nope?
// TODO: try a raw private key
IdleTimerManager.setIdleTimerDisabled(false);
throw new Error('Could not recognize format');
};
IdleTimerManager.setIdleTimerDisabled(false);
return null;
}

View file

@ -1,6 +1,7 @@
import bip39 from 'bip39';
import BigNumber from 'bignumber.js';
import b58 from 'bs58check';
import { randomBytes } from '../rng';
import { AbstractHDWallet } from './abstract-hd-wallet';
const bitcoin = require('bitcoinjs-lib');
@ -695,7 +696,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
u.txid = u.txId;
u.amount = u.value;
u.wif = this._getWifForAddress(u.address);
u.confirmations = u.height ? 1 : 0;
if (!u.confirmations && u.height) u.confirmations = BlueElectrum.estimateCurrentBlockheight() - u.height;
}
this.utxo = this.utxo.sort((a, b) => a.amount - b.amount);
@ -818,6 +819,22 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
return false;
}
/**
* Finds WIF corresponding to address and returns it
*
* @param address {string} Address that belongs to this wallet
* @returns {string|false} WIF or false
*/
_getWIFbyAddress(address) {
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._getExternalAddressByIndex(c) === address) return this._getWIFByIndex(false, 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);
}
return null;
}
weOwnAddress(address) {
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._getExternalAddressByIndex(c) === address) return true;
@ -1048,4 +1065,70 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
}
return false;
}
calculateHowManySignaturesWeHaveFromPsbt(psbt) {
let sigsHave = 0;
for (const inp of psbt.data.inputs) {
if (inp.finalScriptSig || inp.finalScriptWitness || inp.partialSig) sigsHave++;
}
return sigsHave;
}
/**
* Tries to signs passed psbt object (by reference). If there are enough signatures - tries to finalize psbt
* and returns Transaction (ready to extract hex)
*
* @param psbt {Psbt}
* @returns {{ tx: Transaction }}
*/
cosignPsbt(psbt) {
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const hdRoot = HDNode.fromSeed(seed);
for (let cc = 0; cc < psbt.inputCount; cc++) {
try {
psbt.signInputHD(cc, hdRoot);
} catch (e) {} // protects agains duplicate cosignings
if (!psbt.inputHasHDKey(cc, hdRoot)) {
for (const derivation of psbt.data.inputs[cc].bip32Derivation || []) {
const splt = derivation.path.split('/');
const internal = +splt[splt.length - 2];
const index = +splt[splt.length - 1];
const wif = this._getWIFByIndex(internal, index);
const keyPair = bitcoin.ECPair.fromWIF(wif);
try {
psbt.signInput(cc, keyPair);
} catch (e) {} // protects agains duplicate cosignings or if this output can't be signed with current wallet
}
}
}
let tx = false;
if (this.calculateHowManySignaturesWeHaveFromPsbt(psbt) === psbt.inputCount) {
tx = psbt.finalizeAllInputs().extractTransaction();
}
return { tx };
}
/**
* @param mnemonic {string} Mnemonic seed phrase
* @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes
*/
static seedToFingerprint(mnemonic) {
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.bip32.fromSeed(seed);
let hex = root.fingerprint.toString('hex');
while (hex.length < 8) hex = '0' + hex; // leading zeroes
return hex.toUpperCase();
}
/**
* @returns {string} Hex string of fingerprint derived from wallet mnemonics. Always has lenght of 8 chars and correct leading zeroes
*/
getMasterFingerprintHex() {
return AbstractHDElectrumWallet.seedToFingerprint(this.secret);
}
}

View file

@ -19,6 +19,8 @@ export class AbstractWallet {
constructor() {
this.type = this.constructor.type;
this.typeReadable = this.constructor.typeReadable;
this.segwitType = this.constructor.segwitType;
this._derivationPath = this.constructor.derivationPath;
this.label = '';
this.secret = ''; // private key or recovery phrase
this.balance = 0;
@ -99,18 +101,10 @@ export class AbstractWallet {
return true;
}
allowSendMax(): boolean {
return false;
}
allowRBF() {
return false;
}
allowBatchSend() {
return false;
}
allowHodlHodlTrading() {
return false;
}
@ -119,6 +113,18 @@ export class AbstractWallet {
return false;
}
allowCosignPsbt() {
return false;
}
allowSignVerifyMessage() {
return false;
}
allowMasterFingerprint() {
return false;
}
weOwnAddress(address) {
throw Error('not implemented');
}
@ -164,12 +170,24 @@ export class AbstractWallet {
}
try {
const parsedSecret = JSON.parse(this.secret);
let parsedSecret;
// regex might've matched invalid data. if so, parse newSecret.
if (this.secret.trim().length > 0) {
try {
parsedSecret = JSON.parse(this.secret);
} catch (e) {
parsedSecret = JSON.parse(newSecret);
}
} else {
parsedSecret = JSON.parse(newSecret);
}
if (parsedSecret && parsedSecret.keystore && parsedSecret.keystore.xpub) {
let masterFingerprint = false;
if (parsedSecret.keystore.ckcc_xfp) {
// It is a ColdCard Hardware Wallet
masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp);
} else if (parsedSecret.keystore.root_fingerprint) {
masterFingerprint = Number(parsedSecret.keystore.root_fingerprint);
}
if (parsedSecret.keystore.label) {
this.setLabel(parsedSecret.keystore.label);
@ -308,4 +326,11 @@ export class AbstractWallet {
if ('frozen' in opts) meta.frozen = opts.frozen;
this._utxoMetadata[`${txid}:${vout}`] = meta;
}
/**
* @returns {string} Root derivation path for wallet if any
*/
getDerivationPath() {
return this._derivationPath ?? '';
}
}

View file

@ -0,0 +1,161 @@
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
const bitcoin = require('bitcoinjs-lib');
const { CipherSeed } = require('aezeed');
/**
* AEZEED mnemonics support, which is used in LND
* Support only BIP84 (native segwit) derivations
*
* @see https://github.com/lightningnetwork/lnd/tree/master/aezeed
* @see https://github.com/bitcoinjs/aezeed
* @see https://github.com/lightningnetwork/lnd/issues/4960
* @see https://github.com/guggero/chantools/blob/master/doc/chantools_genimportscript.md
* @see https://github.com/lightningnetwork/lnd/blob/master/keychain/derivation.go
*/
export class HDAezeedWallet extends AbstractHDElectrumWallet {
static type = 'HDAezeedWallet';
static typeReadable = 'HD Aezeed';
static segwitType = 'p2wpkh';
static derivationPath = "m/84'/0'/0'";
setSecret(newSecret) {
this.secret = newSecret.trim();
this.secret = this.secret.replace(/[^a-zA-Z0-9:]/g, ' ').replace(/\s+/g, ' ');
return this;
}
_getEntropyCached() {
if (this._entropyHex) {
// cache hit
return Buffer.from(this._entropyHex, 'hex');
} else {
throw new Error('Entropy cache is not filled');
}
}
validateMnemonic(): boolean {
throw new Error('Use validateMnemonicAsync()');
}
async validateMnemonicAsync() {
const [mnemonic3, password] = this.secret.split(':');
try {
const cipherSeed1 = await CipherSeed.fromMnemonic(mnemonic3, password || 'aezeed');
this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache
return !!cipherSeed1.entropy;
} catch (_) {
return false;
}
}
async mnemonicInvalidPassword() {
const [mnemonic3, password] = this.secret.split(':');
try {
const cipherSeed1 = await CipherSeed.fromMnemonic(mnemonic3, password || 'aezeed');
this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache
} catch (error) {
return error.message === 'Invalid Password';
}
return false;
}
async generate() {
throw new Error('Not implemented');
}
_getNode0() {
const root = bitcoin.bip32.fromSeed(this._getEntropyCached());
const node = root.derivePath("m/84'/0'/0'");
return node.derive(0);
}
_getNode1() {
const root = bitcoin.bip32.fromSeed(this._getEntropyCached());
const node = root.derivePath("m/84'/0'/0'");
return node.derive(1);
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
this._node1 = this._node1 || this._getNode1(); // cache
const address = bitcoin.payments.p2wpkh({
pubkey: this._node1.derive(index).publicKey,
}).address;
return (this.internal_addresses_cache[index] = address);
}
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
this._node0 = this._node0 || this._getNode0(); // cache
const address = bitcoin.payments.p2wpkh({
pubkey: this._node0.derive(index).publicKey,
}).address;
return (this.external_addresses_cache[index] = address);
}
_getWIFByIndex(internal, index) {
if (!this.secret) return false;
const root = bitcoin.bip32.fromSeed(this._getEntropyCached());
const path = `m/84'/0'/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);
return child.toWIF();
}
_getNodePubkeyByIndex(node, index) {
index = index * 1; // cast to int
if (node === 0 && !this._node0) {
this._node0 = this._getNode0();
}
if (node === 1 && !this._node1) {
this._node1 = this._getNode1();
}
if (node === 0) {
return this._node0.derive(index).publicKey;
}
if (node === 1) {
return this._node1.derive(index).publicKey;
}
}
getIdentityPubkey() {
const root = bitcoin.bip32.fromSeed(this._getEntropyCached());
const node = root.derivePath("m/1017'/0'/6'/0/0");
return node.publicKey.toString('hex');
}
// since its basically a bip84 wallet, we allow all other standard BIP84 features:
allowSend() {
return true;
}
allowHodlHodlTrading() {
return true;
}
allowRBF() {
return true;
}
allowPayJoin() {
return true;
}
allowSignVerifyMessage() {
return true;
}
}

View file

@ -12,15 +12,12 @@ const BlueElectrum = require('../../blue_modules/BlueElectrum');
export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet {
static type = 'HDLegacyBreadwallet';
static typeReadable = 'HD Legacy Breadwallet (P2PKH)';
static derivationPath = "m/0'";
// track address index at which wallet switched to segwit
_external_segwit_index = null; // eslint-disable-line camelcase
_internal_segwit_index = null; // eslint-disable-line camelcase
allowSendMax() {
return true;
}
/**
* @see https://github.com/bitcoinjs/bitcoinjs-lib/issues/584
* @see https://github.com/bitcoinjs/bitcoinjs-lib/issues/914

View file

@ -18,6 +18,7 @@ const MNEMONIC_TO_SEED_OPTS = {
export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
static type = 'HDlegacyElectrumSeedP2PKH';
static typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)';
static derivationPath = 'm';
validateMnemonic() {
return mn.validateMnemonic(this.secret, PREFIX);
@ -69,10 +70,6 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
return child.toWIF();
}
allowSendMax() {
return true;
}
_getNodePubkeyByIndex(node, index) {
index = index * 1; // cast to int

View file

@ -12,12 +12,21 @@ const BlueElectrum = require('../../blue_modules/BlueElectrum');
export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
static type = 'HDlegacyP2PKH';
static typeReadable = 'HD Legacy (BIP44 P2PKH)';
static derivationPath = "m/44'/0'/0'";
allowSend() {
return true;
}
allowSendMax() {
allowCosignPsbt() {
return true;
}
allowSignVerifyMessage() {
return true;
}
allowMasterFingerprint() {
return true;
}

View file

@ -8,19 +8,13 @@ import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
static type = 'HDsegwitBech32';
static typeReadable = 'HD SegWit (BIP84 Bech32 Native)';
static segwitType = 'p2wpkh';
static derivationPath = "m/84'/0'/0'";
allowSend() {
return true;
}
allowBatchSend() {
return true;
}
allowSendMax() {
return true;
}
allowHodlHodlTrading() {
return true;
}
@ -32,4 +26,16 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
allowPayJoin() {
return true;
}
allowCosignPsbt() {
return true;
}
allowSignVerifyMessage() {
return true;
}
allowMasterFingerprint() {
return true;
}
}

View file

@ -18,6 +18,7 @@ const MNEMONIC_TO_SEED_OPTS = {
export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
static type = 'HDSegwitElectrumSeedP2WPKHWallet';
static typeReadable = 'HD Electrum (BIP32 P2WPKH)';
static derivationPath = "m/0'";
validateMnemonic() {
return mn.validateMnemonic(this.secret, PREFIX);
@ -69,10 +70,6 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
return child.toWIF();
}
allowSendMax() {
return true;
}
_getNodePubkeyByIndex(node, index) {
index = index * 1; // cast to int

View file

@ -12,12 +12,26 @@ const HDNode = require('bip32');
export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
static type = 'HDsegwitP2SH';
static typeReadable = 'HD SegWit (BIP49 P2SH)';
static segwitType = 'p2sh(p2wpkh)';
static derivationPath = "m/49'/0'/0'";
allowSend() {
return true;
}
allowSendMax(): boolean {
allowCosignPsbt() {
return true;
}
allowSignVerifyMessage() {
return true;
}
allowHodlHodlTrading() {
return true;
}
allowMasterFingerprint() {
return true;
}
@ -133,8 +147,4 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
});
return address;
}
allowHodlHodlTrading() {
return true;
}
}

View file

@ -1,3 +1,5 @@
import BigNumber from 'bignumber.js';
import bitcoinMessage from 'bitcoinjs-message';
import { randomBytes } from '../rng';
import { AbstractWallet } from './abstract-wallet';
import { HDSegwitBech32Wallet } from '..';
@ -161,12 +163,72 @@ export class LegacyWallet extends AbstractWallet {
if (!u.confirmations && u.height) u.confirmations = BlueElectrum.estimateCurrentBlockheight() - u.height;
ret.push(u);
}
if (ret.length === 0) {
ret = this.getDerivedUtxoFromOurTransaction(); // oy vey, no stored utxo. lets attempt to derive it from stored transactions
}
if (!respectFrozen) {
ret = ret.filter(({ txid, vout }) => !this.getUTXOMetadata(txid, vout).frozen);
}
return ret;
}
getDerivedUtxoFromOurTransaction(returnSpentUtxoAsWell = false) {
const utxos = [];
const ownedAddressesHashmap = {};
ownedAddressesHashmap[this.getAddress()] = true;
/**
* below copypasted from
* @see AbstractHDElectrumWallet.getDerivedUtxoFromOurTransaction
*/
for (const tx of this.getTransactions()) {
for (const output of tx.outputs) {
let address = false;
if (output.scriptPubKey && output.scriptPubKey.addresses && output.scriptPubKey.addresses[0]) {
address = output.scriptPubKey.addresses[0];
}
if (ownedAddressesHashmap[address]) {
const value = new BigNumber(output.value).multipliedBy(100000000).toNumber();
utxos.push({
txid: tx.txid,
txId: tx.txid,
vout: output.n,
address,
value,
amount: value,
confirmations: tx.confirmations,
wif: false,
height: BlueElectrum.estimateCurrentBlockheight() - tx.confirmations,
});
}
}
}
if (returnSpentUtxoAsWell) return utxos;
// got all utxos we ever had. lets filter out the ones that are spent:
const ret = [];
for (const utxo of utxos) {
let spent = false;
for (const tx of this.getTransactions()) {
for (const input of tx.inputs) {
if (input.txid === utxo.txid && input.vout === utxo.vout) spent = true;
// utxo we got previously was actually spent right here ^^
}
}
if (!spent) {
ret.push(utxo);
}
}
return ret;
}
/**
* Fetches transactions via Electrum. Returns VOID.
* Use getter to get the actual list. *
@ -391,17 +453,15 @@ export class LegacyWallet extends AbstractWallet {
* @returns {boolean|string} Either p2pkh address or false
*/
static scriptPubKeyToAddress(scriptPubKey) {
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
let ret;
try {
ret = bitcoin.payments.p2pkh({
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
return bitcoin.payments.p2pkh({
output: scriptPubKey2,
network: bitcoin.networks.bitcoin,
}).address;
} catch (_) {
return false;
}
return ret;
}
weOwnAddress(address) {
@ -416,7 +476,7 @@ export class LegacyWallet extends AbstractWallet {
return false;
}
allowSendMax() {
allowSignVerifyMessage() {
return true;
}
@ -430,4 +490,54 @@ export class LegacyWallet extends AbstractWallet {
addressIsChange(address) {
return false;
}
/**
* Finds WIF corresponding to address and returns it
*
* @param address {string} Address that belongs to this wallet
* @returns {string|false} WIF or false
*/
_getWIFbyAddress(address) {
return this.getAddress() === address ? this.secret : null;
}
/**
* Signes text message using address private key and returs signature
*
* @param message {string}
* @param address {string}
* @returns {string} base64 encoded signature
*/
signMessage(message, address) {
const wif = this._getWIFbyAddress(address);
if (wif === null) throw new Error('Invalid address');
const keyPair = bitcoin.ECPair.fromWIF(wif);
const privateKey = keyPair.privateKey;
const options = this.segwitType ? { segwitType: this.segwitType } : undefined;
const signature = bitcoinMessage.sign(message, privateKey, keyPair.compressed, options);
return signature.toString('base64');
}
/**
* Verifies text message signature by address
*
* @param message {string}
* @param address {string}
* @param signature {string}
* @returns {boolean} base64 encoded signature
*/
verifyMessage(message, address, signature) {
// null, true so it can verify Electrum signatores without errors
return bitcoinMessage.verify(message, address, signature, null, true);
}
/**
* Probes address for transactions, if there are any returns TRUE
*
* @returns {Promise<boolean>}
*/
async wasEverUsed() {
const txs = await BlueElectrum.getTransactionsByAddress(this.getAddress());
return txs.length > 0;
}
}

View file

@ -605,6 +605,10 @@ export class LightningCustodianWallet extends LegacyWallet {
return true;
}
allowSignVerifyMessage() {
return false;
}
/**
* Example return:
* { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f',

View file

@ -105,10 +105,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
}
}
getDerivationPath() {
return this._derivationPath;
}
getCustomDerivationPathForCosigner(index) {
if (index === 0) throw new Error('cosigners indexation starts from 1');
if (index > this.getN()) return false;
@ -310,18 +306,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
return child.toBase58();
}
/**
* @param mnemonic {string} Mnemonic seed phrase
* @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes
*/
static seedToFingerprint(mnemonic) {
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.bip32.fromSeed(seed);
let hex = root.fingerprint.toString('hex');
while (hex.length < 8) hex = '0' + hex; // leading zeroes
return hex.toUpperCase();
}
/**
* Returns xpub with correct prefix accodting to this objects set derivation path, for example 'Zpub' (with
* capital Z) for bech32 multisig
@ -480,7 +464,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
}
// is it electrum json?
if (json && json.wallet_type) {
if (json && json.wallet_type && json.wallet_type !== 'standard') {
const mofn = json.wallet_type.split('of');
this.setM(parseInt(mofn[0].trim()));
const n = parseInt(mofn[1].trim());
@ -606,6 +590,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
this.setLegacy();
break;
case MultisigHDWallet.FORMAT_P2SH_P2WSH:
case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT:
this.setWrappedSegwit();
break;
default:
@ -840,9 +825,9 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
psbt.addOutput(outputData);
});
let signaturesMade = 0;
if (!skipSigning) {
for (let cc = 0; cc < c; cc++) {
let signaturesMade = 0;
for (const cosigner of this._cosigners) {
if (!MultisigHDWallet.isXpubString(cosigner)) {
// ok this is a mnemonic, lets try to sign
@ -1107,10 +1092,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
return /^[0-9A-F]{8}$/i.test(fp);
}
allowBatchSend() {
return true;
}
/**
* Returns TRUE only for _multisignature_ xpubs as per SLIP-0132
* (capital Z, capital Y, or just xpub)

View file

@ -4,6 +4,7 @@ const bitcoin = require('bitcoinjs-lib');
export class SegwitBech32Wallet extends LegacyWallet {
static type = 'segwitBech32';
static typeReadable = 'P2 WPKH';
static segwitType = 'p2wpkh';
getAddress() {
if (this._address) return this._address;
@ -26,11 +27,15 @@ export class SegwitBech32Wallet extends LegacyWallet {
}
static witnessToAddress(witness) {
const pubKey = Buffer.from(witness, 'hex');
return bitcoin.payments.p2wpkh({
pubkey: pubKey,
network: bitcoin.networks.bitcoin,
}).address;
try {
const pubKey = Buffer.from(witness, 'hex');
return bitcoin.payments.p2wpkh({
pubkey: pubKey,
network: bitcoin.networks.bitcoin,
}).address;
} catch (_) {
return false;
}
}
/**
@ -40,17 +45,15 @@ export class SegwitBech32Wallet extends LegacyWallet {
* @returns {boolean|string} Either bech32 address or false
*/
static scriptPubKeyToAddress(scriptPubKey) {
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
let ret;
try {
ret = bitcoin.payments.p2wpkh({
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
return bitcoin.payments.p2wpkh({
output: scriptPubKey2,
network: bitcoin.networks.bitcoin,
}).address;
} catch (_) {
return false;
}
return ret;
}
/**
@ -127,7 +130,7 @@ export class SegwitBech32Wallet extends LegacyWallet {
return true;
}
allowSendMax() {
allowSignVerifyMessage() {
return true;
}
}

View file

@ -19,10 +19,15 @@ function pubkeyToP2shSegwitAddress(pubkey, network) {
export class SegwitP2SHWallet extends LegacyWallet {
static type = 'segwitP2SH';
static typeReadable = 'SegWit (P2SH)';
static segwitType = 'p2sh(p2wpkh)';
static witnessToAddress(witness) {
const pubKey = Buffer.from(witness, 'hex');
return pubkeyToP2shSegwitAddress(pubKey);
try {
const pubKey = Buffer.from(witness, 'hex');
return pubkeyToP2shSegwitAddress(pubKey);
} catch (_) {
return false;
}
}
/**
@ -32,17 +37,15 @@ export class SegwitP2SHWallet extends LegacyWallet {
* @returns {boolean|string} Either p2sh address or false
*/
static scriptPubKeyToAddress(scriptPubKey) {
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
let ret;
try {
ret = bitcoin.payments.p2sh({
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
return bitcoin.payments.p2sh({
output: scriptPubKey2,
network: bitcoin.networks.bitcoin,
}).address;
} catch (_) {
return false;
}
return ret;
}
getAddress() {
@ -136,7 +139,7 @@ export class SegwitP2SHWallet extends LegacyWallet {
return { tx, inputs, outputs, fee, psbt };
}
allowSendMax() {
allowSignVerifyMessage() {
return true;
}
}

View file

@ -21,18 +21,8 @@ export class WatchOnlyWallet extends LegacyWallet {
);
}
allowBatchSend() {
return (
this.useWithHardwareWalletEnabled() &&
this._hdWalletInstance instanceof HDSegwitBech32Wallet &&
this._hdWalletInstance.allowBatchSend()
);
}
allowSendMax() {
return (
this.useWithHardwareWalletEnabled() && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSendMax()
);
allowSignVerifyMessage() {
return false;
}
getAddress() {
@ -56,13 +46,15 @@ export class WatchOnlyWallet extends LegacyWallet {
* this method creates appropriate HD wallet class, depending on whether we have xpub, ypub or zpub
* as a property of `this`, and in case such property exists - it recreates it and copies data from old one.
* this is needed after serialization/save/load/deserialization procedure.
*
* @return {WatchOnlyWallet} this
*/
init() {
let hdWalletInstance;
if (this.secret.startsWith('xpub')) hdWalletInstance = new HDLegacyP2PKHWallet();
else if (this.secret.startsWith('ypub')) hdWalletInstance = new HDSegwitP2SHWallet();
else if (this.secret.startsWith('zpub')) hdWalletInstance = new HDSegwitBech32Wallet();
else return;
else return this;
hdWalletInstance._xpub = this.secret;
if (this._hdWalletInstance) {
// now, porting all properties from old object to new one
@ -75,6 +67,8 @@ export class WatchOnlyWallet extends LegacyWallet {
delete hdWalletInstance._node0;
}
this._hdWalletInstance = hdWalletInstance;
return this;
}
prepareForSerialization() {
@ -217,6 +211,10 @@ export class WatchOnlyWallet extends LegacyWallet {
return this.isHd();
}
allowMasterFingerprint() {
return this.getSecret().startsWith('zpub');
}
useWithHardwareWalletEnabled() {
return !!this.use_with_hardware_wallet;
}

120
components/AddressInput.js Normal file
View file

@ -0,0 +1,120 @@
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import { Text } from 'react-native-elements';
import { findNodeHandle, Image, Keyboard, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
import { getSystemName } from 'react-native-device-info';
import { useTheme } from '@react-navigation/native';
import loc from '../loc';
import * as NavigationService from '../NavigationService';
const fs = require('../blue_modules/fs');
const isDesktop = getSystemName() === 'Mac OS X';
const AddressInput = ({
isLoading = false,
address = '',
placeholder = loc.send.details_address,
onChangeText,
onBarScanned,
launchedBy,
}) => {
const { colors } = useTheme();
const scanButtonRef = useRef();
const stylesHook = StyleSheet.create({
root: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
scan: {
backgroundColor: colors.scanLabel,
},
scanText: {
color: colors.inverseForegroundColor,
},
});
return (
<View style={[styles.root, stylesHook.root]}>
<TextInput
testID="AddressInput"
onChangeText={onChangeText}
placeholder={placeholder}
numberOfLines={1}
placeholderTextColor="#81868e"
value={address}
style={styles.input}
editable={!isLoading}
onSubmitEditing={Keyboard.dismiss}
/>
<TouchableOpacity
testID="BlueAddressInputScanQrButton"
disabled={isLoading}
onPress={() => {
Keyboard.dismiss();
if (isDesktop) {
fs.showActionSheet({ anchor: findNodeHandle(scanButtonRef.current) }).then(onBarScanned);
} else {
NavigationService.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy,
onBarScanned,
},
});
}
}}
style={[styles.scan, stylesHook.scan]}
>
<Image source={require('../img/scan-white.png')} />
<Text style={[styles.scanText, stylesHook.scanText]}>{loc.send.details_scan}</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
root: {
flexDirection: 'row',
borderWidth: 1.0,
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
},
input: {
flex: 1,
marginHorizontal: 8,
minHeight: 33,
color: '#81868e',
},
scan: {
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
},
scanText: {
marginLeft: 4,
},
});
AddressInput.propTypes = {
isLoading: PropTypes.bool,
onChangeText: PropTypes.func,
onBarScanned: PropTypes.func.isRequired,
launchedBy: PropTypes.string,
address: PropTypes.string,
placeholder: PropTypes.string,
};
export default AddressInput;

313
components/AmountInput.js Normal file
View file

@ -0,0 +1,313 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import BigNumber from 'bignumber.js';
import { Text } from 'react-native-elements';
import { Image, StyleSheet, TextInput, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
import { useTheme } from '@react-navigation/native';
import { BitcoinUnit } from '../models/bitcoinUnits';
import loc, { formatBalanceWithoutSuffix, formatBalancePlain, removeTrailingZeros } from '../loc';
const currency = require('../blue_modules/currency');
class AmountInput extends Component {
static propTypes = {
isLoading: PropTypes.bool,
/**
* amount is a sting thats always in current unit denomination, e.g. '0.001' or '9.43' or '10000'
*/
amount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* callback that returns currently typed amount, in current denomination, e.g. 0.001 or 10000 or $9.34
* (btc, sat, fiat)
*/
onChangeText: PropTypes.func.isRequired,
/**
* callback thats fired to notify of currently selected denomination, returns <BitcoinUnit.*>
*/
onAmountUnitChange: PropTypes.func.isRequired,
disabled: PropTypes.bool,
colors: PropTypes.object.isRequired,
pointerEvents: PropTypes.string,
unit: PropTypes.string,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
};
/**
* cache of conversions fiat amount => satoshi
* @type {{}}
*/
static conversionCache = {};
static getCachedSatoshis = amount => {
return AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] || false;
};
static setCachedSatoshis = (amount, sats) => {
AmountInput.conversionCache[amount + BitcoinUnit.LOCAL_CURRENCY] = sats;
};
/**
* here we must recalculate old amont value (which was denominated in `previousUnit`) to new denomination `newUnit`
* and fill this value in input box, so user can switch between, for example, 0.001 BTC <=> 100000 sats
*
* @param previousUnit {string} one of {BitcoinUnit.*}
* @param newUnit {string} one of {BitcoinUnit.*}
*/
onAmountUnitChange(previousUnit, newUnit) {
const amount = this.props.amount || 0;
console.log('was:', amount, previousUnit, '; converting to', newUnit);
let sats = 0;
switch (previousUnit) {
case BitcoinUnit.BTC:
sats = new BigNumber(amount).multipliedBy(100000000).toString();
break;
case BitcoinUnit.SATS:
sats = amount;
break;
case BitcoinUnit.LOCAL_CURRENCY:
sats = new BigNumber(currency.fiatToBTC(amount)).multipliedBy(100000000).toString();
break;
}
if (previousUnit === BitcoinUnit.LOCAL_CURRENCY && AmountInput.conversionCache[amount + previousUnit]) {
// cache hit! we reuse old value that supposedly doesnt have rounding errors
sats = AmountInput.conversionCache[amount + previousUnit];
}
console.log('so, in sats its', sats);
const newInputValue = formatBalancePlain(sats, newUnit, false);
console.log('and in', newUnit, 'its', newInputValue);
if (newUnit === BitcoinUnit.LOCAL_CURRENCY && previousUnit === BitcoinUnit.SATS) {
// we cache conversion, so when we will need reverse conversion there wont be a rounding error
AmountInput.conversionCache[newInputValue + newUnit] = amount;
}
this.props.onChangeText(newInputValue);
this.props.onAmountUnitChange(newUnit);
}
/**
* responsible for cycling currently selected denomination, BTC->SAT->LOCAL_CURRENCY->BTC
*/
changeAmountUnit = () => {
let previousUnit = this.props.unit;
let newUnit;
if (previousUnit === BitcoinUnit.BTC) {
newUnit = BitcoinUnit.SATS;
} else if (previousUnit === BitcoinUnit.SATS) {
newUnit = BitcoinUnit.LOCAL_CURRENCY;
} else if (previousUnit === BitcoinUnit.LOCAL_CURRENCY) {
newUnit = BitcoinUnit.BTC;
} else {
newUnit = BitcoinUnit.BTC;
previousUnit = BitcoinUnit.SATS;
}
this.onAmountUnitChange(previousUnit, newUnit);
};
maxLength = () => {
switch (this.props.unit) {
case BitcoinUnit.BTC:
return 10;
case BitcoinUnit.SATS:
return 15;
default:
return 15;
}
};
textInput = React.createRef();
handleTextInputOnPress = () => {
this.textInput.current.focus();
};
handleChangeText = text => {
text = text.trim();
if (this.props.unit !== BitcoinUnit.LOCAL_CURRENCY) {
text = text.replace(',', '.');
const split = text.split('.');
if (split.length >= 2) {
text = `${parseInt(split[0], 10)}.${split[1]}`;
} else {
text = `${parseInt(split[0], 10)}`;
}
text = this.props.unit === BitcoinUnit.BTC ? text.replace(/[^0-9.]/g, '') : text.replace(/[^0-9]/g, '');
if (text.startsWith('.')) {
text = '0.';
}
} else if (this.props.unit === BitcoinUnit.LOCAL_CURRENCY) {
text = text.replace(/,/gi, '');
if (text.split('.').length > 2) {
// too many dots. stupid code to remove all but first dot:
let rez = '';
let first = true;
for (const part of text.split('.')) {
rez += part;
if (first) {
rez += '.';
first = false;
}
}
text = rez;
}
text = text.replace(/[^\d.,-]/g, ''); // remove all but numbers, dots & commas
text = text.replace(/(\..*)\./g, '$1');
}
this.props.onChangeText(text);
};
render() {
const { colors, disabled, unit } = this.props;
const amount = this.props.amount || 0;
let secondaryDisplayCurrency = formatBalanceWithoutSuffix(amount, BitcoinUnit.LOCAL_CURRENCY, false);
// if main display is sat or btc - secondary display is fiat
// if main display is fiat - secondary dislay is btc
let sat;
switch (unit) {
case BitcoinUnit.BTC:
sat = new BigNumber(amount).multipliedBy(100000000).toString();
secondaryDisplayCurrency = formatBalanceWithoutSuffix(sat, BitcoinUnit.LOCAL_CURRENCY, false);
break;
case BitcoinUnit.SATS:
secondaryDisplayCurrency = formatBalanceWithoutSuffix((isNaN(amount) ? 0 : amount).toString(), BitcoinUnit.LOCAL_CURRENCY, false);
break;
case BitcoinUnit.LOCAL_CURRENCY:
secondaryDisplayCurrency = currency.fiatToBTC(parseFloat(isNaN(amount) ? 0 : amount));
if (AmountInput.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY]) {
// cache hit! we reuse old value that supposedly doesn't have rounding errors
const sats = AmountInput.conversionCache[isNaN(amount) ? 0 : amount + BitcoinUnit.LOCAL_CURRENCY];
secondaryDisplayCurrency = currency.satoshiToBTC(sats);
}
break;
}
if (amount === BitcoinUnit.MAX) secondaryDisplayCurrency = ''; // we don't want to display NaN
const stylesHook = StyleSheet.create({
center: { padding: amount === BitcoinUnit.MAX ? 0 : 15 },
localCurrency: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2 },
input: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2, fontSize: amount.length > 10 ? 20 : 36 },
cryptoCurrency: { color: disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2 },
});
return (
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
<View style={styles.root}>
{!disabled && <View style={[styles.center, stylesHook.center]} />}
<View style={styles.flex}>
<View style={styles.container}>
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
<Text style={[styles.localCurrency, stylesHook.localCurrency]}>{currency.getCurrencySymbol() + ' '}</Text>
)}
<TextInput
{...this.props}
testID="BitcoinAmountInput"
keyboardType="numeric"
adjustsFontSizeToFit
onChangeText={this.handleChangeText}
onBlur={() => {
if (this.props.onBlur) this.props.onBlur();
}}
onFocus={() => {
if (this.props.onFocus) this.props.onFocus();
}}
placeholder="0"
maxLength={this.maxLength()}
ref={textInput => (this.textInput = textInput)}
editable={!this.props.isLoading && !disabled}
value={amount === BitcoinUnit.MAX ? loc.units.MAX : parseFloat(amount) >= 0 ? String(amount) : undefined}
placeholderTextColor={disabled ? colors.buttonDisabledTextColor : colors.alternativeTextColor2}
style={[styles.input, stylesHook.input]}
/>
{unit !== BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX && (
<Text style={[styles.cryptoCurrency, stylesHook.cryptoCurrency]}>{' ' + loc.units[unit]}</Text>
)}
</View>
<View style={styles.secondaryRoot}>
<Text style={styles.secondaryText}>
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX
? removeTrailingZeros(secondaryDisplayCurrency)
: secondaryDisplayCurrency}
{unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${loc.units[BitcoinUnit.BTC]}` : null}
</Text>
</View>
</View>
{!disabled && amount !== BitcoinUnit.MAX && (
<TouchableOpacity testID="changeAmountUnitButton" style={styles.changeAmountUnit} onPress={this.changeAmountUnit}>
<Image source={require('../img/round-compare-arrows-24-px.png')} />
</TouchableOpacity>
)}
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = StyleSheet.create({
root: {
flexDirection: 'row',
justifyContent: 'space-between',
},
center: {
alignSelf: 'center',
},
flex: {
flex: 1,
},
container: {
flexDirection: 'row',
alignContent: 'space-between',
justifyContent: 'center',
paddingTop: 16,
paddingBottom: 2,
},
localCurrency: {
fontSize: 18,
marginHorizontal: 4,
fontWeight: 'bold',
alignSelf: 'center',
justifyContent: 'center',
},
input: {
fontWeight: 'bold',
},
cryptoCurrency: {
fontSize: 15,
marginHorizontal: 4,
fontWeight: '600',
alignSelf: 'center',
justifyContent: 'center',
},
secondaryRoot: {
alignItems: 'center',
marginBottom: 22,
},
secondaryText: {
fontSize: 16,
color: '#9BA0A9',
fontWeight: '600',
},
changeAmountUnit: {
alignSelf: 'center',
marginRight: 16,
paddingLeft: 16,
paddingVertical: 16,
},
});
const AmountInputWithStyle = props => {
const { colors } = useTheme();
return <AmountInput {...props} colors={colors} />;
};
// expose static methods
AmountInputWithStyle.conversionCache = AmountInput.conversionCache;
AmountInputWithStyle.getCachedSatoshis = AmountInput.getCachedSatoshis;
AmountInputWithStyle.setCachedSatoshis = AmountInput.setCachedSatoshis;
export default AmountInputWithStyle;

View file

@ -24,6 +24,9 @@ const BottomModal = ({ onBackButtonPress, onBackdropPress, onClose, windowHeight
onBackButtonPress={handleBackButtonPress}
onBackdropPress={handleBackdropPress}
{...props}
accessibilityViewIsModal
useNativeDriver
useNativeDriverForBackdrop
/>
);
};

View file

@ -34,19 +34,20 @@ const styles = StyleSheet.create({
},
});
const CoinsSelected = ({ number, onClose }) => (
<View style={styles.root}>
const CoinsSelected = ({ number, onContainerPress, onClose }) => (
<TouchableOpacity style={styles.root} onPress={onContainerPress}>
<View style={styles.labelContainer}>
<Text style={styles.labelText}>{loc.formatString(loc.cc.coins_selected, { number })}</Text>
</View>
<TouchableOpacity style={styles.buttonContainer} onPress={onClose}>
<Avatar rounded containerStyle={[styles.ball]} icon={{ name: 'close', size: 22, type: 'ionicons', color: 'white' }} />
</TouchableOpacity>
</View>
</TouchableOpacity>
);
CoinsSelected.propTypes = {
number: PropTypes.number.isRequired,
onContainerPress: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};

View file

@ -27,7 +27,7 @@ export class DynamicQRCode extends Component {
fragments = [];
componentDidMount() {
const { value, capacity = 800, hideControls = true } = this.props;
const { value, capacity = 200, hideControls = true } = this.props;
try {
this.fragments = encodeUR(value, capacity);
this.setState(
@ -105,6 +105,7 @@ export class DynamicQRCode extends Component {
return (
<View style={animatedQRCodeStyle.container}>
<TouchableOpacity
testID="DynamicCode"
style={animatedQRCodeStyle.qrcodeContainer}
onPress={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);

View file

@ -1,4 +1,4 @@
import React, { useState, useRef } from 'react';
import React, { useState, useRef, forwardRef } from 'react';
import PropTypes from 'prop-types';
import { View, Text, TouchableOpacity, StyleSheet, Dimensions, PixelRatio } from 'react-native';
import { useTheme } from '@react-navigation/native';
@ -9,23 +9,27 @@ const ICON_MARGIN = 7;
const cStyles = StyleSheet.create({
root: {
position: 'absolute',
alignSelf: 'center',
height: '6.3%',
minHeight: 44,
},
rootAbsolute: {
position: 'absolute',
bottom: 30,
},
rootInline: {},
rootPre: {
position: 'absolute',
bottom: -1000,
},
rootPost: {
bottom: 30,
borderRadius: BORDER_RADIUS,
flexDirection: 'row',
overflow: 'hidden',
},
});
export const FContainer = ({ children }) => {
export const FContainer = forwardRef((props, ref) => {
const [newWidth, setNewWidth] = useState();
const layoutCalculated = useRef(false);
@ -34,7 +38,7 @@ export const FContainer = ({ children }) => {
const maxWidth = Dimensions.get('window').width - BORDER_RADIUS - 20;
const { width } = event.nativeEvent.layout;
const withPaddings = Math.ceil(width + PADDINGS * 2);
const len = React.Children.toArray(children).filter(Boolean).length;
const len = React.Children.toArray(props.children).filter(Boolean).length;
let newWidth = withPaddings * len > maxWidth ? Math.floor(maxWidth / len) : withPaddings;
if (len === 1 && newWidth < 90) newWidth = 90; // to add Paddings for lonely small button, like Scan on main screen
setNewWidth(newWidth);
@ -42,9 +46,13 @@ export const FContainer = ({ children }) => {
};
return (
<View onLayout={onLayout} style={[cStyles.root, newWidth ? cStyles.rootPost : cStyles.rootPre]}>
<View
ref={ref}
onLayout={onLayout}
style={[cStyles.root, props.inline ? cStyles.rootInline : cStyles.rootAbsolute, newWidth ? cStyles.rootPost : cStyles.rootPre]}
>
{newWidth
? React.Children.toArray(children)
? React.Children.toArray(props.children)
.filter(Boolean)
.map((c, index, array) =>
React.cloneElement(c, {
@ -54,13 +62,14 @@ export const FContainer = ({ children }) => {
last: index === array.length - 1,
}),
)
: children}
: props.children}
</View>
);
};
});
FContainer.propTypes = {
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.element), PropTypes.element]),
inline: PropTypes.bool,
};
const buttonFontSize =
@ -95,6 +104,9 @@ export const FButton = ({ text, icon, width, first, last, ...props }) => {
text: {
color: colors.buttonAlternativeTextColor,
},
textDisabled: {
color: colors.formBorder,
},
});
const style = {};
@ -109,7 +121,7 @@ export const FButton = ({ text, icon, width, first, last, ...props }) => {
return (
<TouchableOpacity style={[bStyles.root, bStylesHook.root, style]} {...props}>
<View style={bStyles.icon}>{icon}</View>
<Text numberOfLines={1} style={[bStyles.text, bStylesHook.text]}>
<Text numberOfLines={1} style={[bStyles.text, props.disabled ? bStylesHook.textDisabled : bStylesHook.text]}>
{text}
</Text>
</TouchableOpacity>
@ -122,4 +134,5 @@ FButton.propTypes = {
width: PropTypes.number,
first: PropTypes.bool,
last: PropTypes.bool,
disabled: PropTypes.bool,
};

View file

@ -1,10 +1,10 @@
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
import React from 'react';
import React, { forwardRef } from 'react';
import { TouchableOpacity, View, Text } from 'react-native';
import { Icon } from 'react-native-elements';
import { useTheme } from '@react-navigation/native';
export const SquareButton = props => {
export const SquareButton = forwardRef((props, ref) => {
const { colors } = useTheme();
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor;
let fontColor = colors.buttonTextColor;
@ -28,6 +28,7 @@ export const SquareButton = props => {
alignItems: 'center',
}}
{...props}
ref={ref}
>
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
@ -35,4 +36,4 @@ export const SquareButton = props => {
</View>
</TouchableOpacity>
);
};
});

View file

@ -0,0 +1,34 @@
import React, { forwardRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import showPopupMenu from 'react-native-popup-menu-android';
const ToolTipMenu = (props, ref) => {
const handleToolTipSelection = selection => {
const action = props.actions.find(action => action.id === selection.id);
action.onPress();
};
const showMenu = () => {
const actions = props.actions.map(action => ({ id: action.id, label: action.text }));
showPopupMenu(actions, handleToolTipSelection, props.anchorRef.current);
};
const hideMenu = () => {
console.log('not implemented');
};
useEffect(() => {
ref.current.showMenu = showMenu;
ref.current.hideMenu = hideMenu;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref, props.actions]);
return <View ref={ref} />;
};
export default forwardRef(ToolTipMenu);
ToolTipMenu.propTypes = {
actions: PropTypes.arrayOf(PropTypes.shape).isRequired,
anchorRef: PropTypes.node,
};

View file

@ -0,0 +1,34 @@
import React, { useRef, forwardRef, useEffect } from 'react';
import ToolTip from 'react-native-tooltip';
import PropTypes from 'prop-types';
import { View } from 'react-native';
const ToolTipMenu = (props, ref) => {
const toolTip = useRef();
const showMenu = () => {
console.log('Showing ToolTip');
toolTip.current?.showMenu();
};
const hideMenu = () => {
console.log('Hiding ToolTip');
toolTip.current?.hideMenu();
};
useEffect(() => {
ref.current.showMenu = showMenu;
ref.current.hideMenu = hideMenu;
}, [ref]);
return (
<View ref={ref}>
<ToolTip ref={toolTip} actions={props.actions} />
</View>
);
};
export default forwardRef(ToolTipMenu);
ToolTipMenu.propTypes = {
actions: PropTypes.arrayOf(PropTypes.shape).isRequired,
};

22
components/TooltipMenu.js Normal file
View file

@ -0,0 +1,22 @@
import React, { forwardRef, useEffect } from 'react';
import { View } from 'react-native';
const ToolTipMenu = (props, ref) => {
const showMenu = () => {
console.log('not implemented');
};
const hideMenu = () => {
console.log('not implemented');
};
useEffect(() => {
ref.current.showMenu = showMenu;
ref.current.hideMenu = hideMenu;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref]);
return <View ref={ref} />;
};
export default forwardRef(ToolTipMenu);

View file

@ -20,7 +20,6 @@ import loc, { formatBalance, transactionTimeToReadable } from '../loc';
import { LightningCustodianWallet, MultisigHDWallet, PlaceholderWallet } from '../class';
import WalletGradient from '../class/wallet-gradient';
import { BluePrivateBalance } from '../BlueComponents';
import { BlueStorageContext } from '../blue_modules/storage-context';
const nStyles = StyleSheet.create({
@ -82,7 +81,7 @@ const iStyles = StyleSheet.create({
},
grad: {
padding: 15,
borderRadius: 10,
borderRadius: 12,
minHeight: 164,
elevation: 5,
},
@ -127,6 +126,7 @@ const iStyles = StyleSheet.create({
const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedWallet }) => {
const scaleValue = new Animated.Value(1.0);
const { colors } = useTheme();
const { walletTransactionUpdateStatus } = useContext(BlueStorageContext);
const onPressedIn = () => {
const props = { duration: 50 };
@ -178,7 +178,7 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
{item.getIsFailure() ? loc.wallets.import_placeholder_fail : loc.wallets.import_placeholder_inprogress}
</Text>
{item.getIsFailure() ? (
<Text numberOfLines={0} style={[iStyles.importError, { color: colors.inverseForegroundColor }]}>
<Text testID="ImportError" numberOfLines={0} style={[iStyles.importError, { color: colors.inverseForegroundColor }]}>
{loc.wallets.list_import_error}
</Text>
) : (
@ -203,12 +203,23 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
image = require('../img/btc-shape.png');
}
const latestTransactionText =
walletTransactionUpdateStatus === true || walletTransactionUpdateStatus === item.getID()
? loc.transactions.updating
: item.getBalance() !== 0 && item.getLatestTransactionTime() === 0
? loc.wallets.pull_to_refresh
: item.getTransactions().find(tx => tx.confirmations === 0)
? loc.transactions.pending
: transactionTimeToReadable(item.getLatestTransactionTime());
const balance = !item.hideBalance && formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true);
return (
<Animated.View
style={[iStyles.root, { opacity, transform: [{ scale: scaleValue }] }]}
shadowOpacity={40 / 100}
shadowOffset={{ width: 0, height: 0 }}
shadowRadius={5}
shadowOpacity={25 / 100}
shadowOffset={{ width: 0, height: 3 }}
shadowRadius={8}
>
<TouchableWithoutFeedback
testID={item.getLabel()}
@ -230,18 +241,22 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
{item.hideBalance ? (
<BluePrivateBalance />
) : (
<Text numberOfLines={1} adjustsFontSizeToFit style={[iStyles.balance, { color: colors.inverseForegroundColor }]}>
{formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
<Text
numberOfLines={1}
key={balance} // force component recreation on balance change. To fix right-to-left languages, like Farsi
adjustsFontSizeToFit
style={[iStyles.balance, { color: colors.inverseForegroundColor }]}
>
{balance}
</Text>
)}
<Text style={iStyles.br} />
<Text numberOfLines={1} style={[iStyles.latestTx, { color: colors.inverseForegroundColor }]}>
{loc.wallets.list_latest_transaction}
</Text>
<Text numberOfLines={1} style={[iStyles.latestTxTime, { color: colors.inverseForegroundColor }]}>
{item.getBalance() !== 0 && item.getLatestTransactionTime() === 0
? loc.wallets.pull_to_refresh
: transactionTimeToReadable(item.getLatestTransactionTime())}
{latestTransactionText}
</Text>
</LinearGradient>
</TouchableWithoutFeedback>
@ -263,7 +278,7 @@ const cStyles = StyleSheet.create({
alignItems: 'center',
},
content: {
left: 20,
left: 16,
},
});
@ -331,7 +346,7 @@ const WalletsCarousel = forwardRef((props, ref) => {
WalletsCarousel.propTypes = {
vertical: PropTypes.bool,
selectedWallet: PropTypes.object,
selectedWallet: PropTypes.string,
onPress: PropTypes.func.isRequired,
handleLongPress: PropTypes.func.isRequired,
};

15
components/handoff.ios.js Normal file
View file

@ -0,0 +1,15 @@
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 && props && props.url ? <Handoff {...props} /> : null;
};
export default HandoffComponent;
HandoffComponent.propTypes = {
url: PropTypes.string,
};

5
components/handoff.js Normal file
View file

@ -0,0 +1,5 @@
const HandoffComponent = () => {
return null;
};
export default HandoffComponent;

View file

@ -21,7 +21,7 @@ const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, form
navigation.goBack(null);
};
headerRight = () => (
<TouchableOpacity style={styles.button} onPress={handleClose}>
<TouchableOpacity style={styles.button} onPress={handleClose} testID="NavigationCloseButton">
<Image source={theme.closeImage} />
</TouchableOpacity>
);

View file

@ -47,6 +47,7 @@ export const BlueDefaultTheme = {
feeText: '#81868e',
feeLabel: '#d2f8d6',
feeValue: '#37c0a1',
feeActive: '#d2f8d6',
labelText: '#81868e',
cta2: '#062453',
outputValue: '#13244D',
@ -92,6 +93,7 @@ export const BlueDarkTheme = {
feeText: '#81868e',
feeLabel: '#8EFFE5',
feeValue: '#000000',
feeActive: 'rgba(210,248,214,.2)',
cta2: '#ffffff',
outputValue: '#ffffff',
elevated: '#121212',

30
helpers/scan-qr.js Normal file
View file

@ -0,0 +1,30 @@
/**
* Helper function that navigates to ScanQR screen, and returns promise that will resolve with the result of a scan,
* and then navigates back. If QRCode scan was closed, promise resolves to null.
*
* @param navigateFunc {function}
* @param currentScreenName {string}
* @param showFileImportButton {boolean}
*
* @return {Promise<string>}
*/
module.exports = function scanQrHelper(navigateFunc, currentScreenName, showFileImportButton = true) {
return new Promise(resolve => {
const params = {};
params.showFileImportButton = !!showFileImportButton;
params.onBarScanned = function (data) {
setTimeout(() => resolve(data.data || data), 1);
navigateFunc(currentScreenName);
};
params.onDismiss = function () {
setTimeout(() => resolve(null), 1);
};
navigateFunc('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params,
});
});
};

BIN
img/btc-shape-rtl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
img/lnd-shape-rtl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
img/vault-shape-rtl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View file

@ -18,7 +18,6 @@
3271B0BB236E329400DA766F /* TodayAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3271B0BA236E329400DA766F /* TodayAPI.swift */; };
32B5A32A2334450100F8D608 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5A3292334450100F8D608 /* Bridge.swift */; };
32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F0A2992311DBB20095C559 /* ComplicationController.swift */; };
5875B7B2D85DC56E00F292FF /* libPods-WalletInformationWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CC8A6C610EAE90F810EADCC /* libPods-WalletInformationWidgetExtension.a */; platformFilter = ios; };
590C62D2ED8BF487C33945B0 /* libPods-WalletInformationAndMarketWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */; platformFilter = ios; };
6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D2A6463258BA92D0092292B /* Stickers.xcassets */; };
6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@ -31,6 +30,17 @@
6D2AA80A2568B8F40090B089 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
6D2AA80B2568B8F40090B089 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D32C5C52596CE3A008C077C /* EventEmitter.m */; };
6D4AF15925D21172009DD853 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; };
6D4AF16325D21185009DD853 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; };
6D4AF16D25D21192009DD853 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; };
6D4AF17725D211A3009DD853 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; };
6D4AF17825D211A3009DD853 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
6D4AF18525D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
6D4AF18625D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
6D4AF18725D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
6D4AF18825D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
6D4AF18925D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; };
6D641F18255226DA003792DF /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; };
6D641F19255226DA003792DF /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; };
6D641F2325525054003792DF /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; };
@ -51,7 +61,6 @@
6D6CA5332558ED54009312A5 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; };
6D6CA53C2558F316009312A5 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; };
6D6CA5452558F365009312A5 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; };
6D6CA54E2558F497009312A5 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; };
6D99465F2555A660000E52E8 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; platformFilter = ios; };
6D9946602555A660000E52E8 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; platformFilter = ios; };
6D9946632555A660000E52E8 /* MarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9946622555A660000E52E8 /* MarketWidget.swift */; };
@ -225,7 +234,6 @@
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 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>"; };
0CC8A6C610EAE90F810EADCC /* libPods-WalletInformationWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WalletInformationWidgetExtension.a"; 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>"; };
@ -254,7 +262,6 @@
334051161886419EA186F4BA /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
367FA8CEB35BC9431019D98A /* Pods-MarketWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MarketWidgetExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MarketWidgetExtension/Pods-MarketWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
3703B10AAB374CF896CCC2EA /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = "<group>"; };
3D9CAFACCECFFBFCD9B2BC7E /* Pods-WalletInformationWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalletInformationWidgetExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WalletInformationWidgetExtension/Pods-WalletInformationWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
3F7F1B8332C6439793D55B45 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MarketWidgetExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; };
44BC9E3EE0E9476A830CCCB9 /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = "<group>"; };
@ -263,8 +270,8 @@
4D746BBE67E84684848246E2 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
4F12F501B686459183E0BE0D /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; };
5A8F67CF29564E41882ECEF8 /* FontAwesome5_Brands.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Brands.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"; sourceTree = "<group>"; };
64774910B0DBEC412995CB89 /* Pods-WalletInformationWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalletInformationWidgetExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WalletInformationWidgetExtension/Pods-WalletInformationWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
6A65D81712444D37BA152B06 /* libRNRandomBytes.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNRandomBytes.a; sourceTree = "<group>"; };
6D203C2025D4ED2500493AD1 /* BlueWalletWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BlueWalletWatch.entitlements; sourceTree = "<group>"; };
6D294A7324D510AC0039E22B /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Interface.strings; sourceTree = "<group>"; };
6D294A7424D510AC0039E22B /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/MainInterface.strings; sourceTree = "<group>"; };
6D294A7524D510D60039E22B /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Interface.strings; sourceTree = "<group>"; };
@ -318,6 +325,8 @@
6D32C5C52596CE3A008C077C /* EventEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EventEmitter.m; sourceTree = "<group>"; };
6D333B3A252FE1A3004D72DF /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
6D333B3C252FE1A3004D72DF /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWalletWatch-Bridging-Header.h"; sourceTree = "<group>"; };
6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
6D641F17255226DA003792DF /* MarketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketView.swift; sourceTree = "<group>"; };
6D641F2225525053003792DF /* WalletInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationView.swift; sourceTree = "<group>"; };
6D641F3425526311003792DF /* SendReceiveButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReceiveButtons.swift; sourceTree = "<group>"; };
@ -473,7 +482,6 @@
files = (
6DEB4AAF254FB59C00E9F9AA /* SwiftUI.framework in Frameworks */,
6DEB4AAE254FB59C00E9F9AA /* WidgetKit.framework in Frameworks */,
5875B7B2D85DC56E00F292FF /* libPods-WalletInformationWidgetExtension.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -550,7 +558,6 @@
6D333B3A252FE1A3004D72DF /* WidgetKit.framework */,
6D333B3C252FE1A3004D72DF /* SwiftUI.framework */,
98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */,
0CC8A6C610EAE90F810EADCC /* libPods-WalletInformationWidgetExtension.a */,
41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */,
);
name = Frameworks;
@ -671,6 +678,8 @@
6D9A2E08254BA348007B5B82 /* Assets.xcassets */,
6DEB4BFA254FBA0E00E9F9AA /* Models.swift */,
6DEB4C3A254FBF4800E9F9AA /* Colors.swift */,
6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */,
6D4AF18225D215D0009DD853 /* BlueWalletWatch-Bridging-Header.h */,
);
path = Shared;
sourceTree = "<group>";
@ -735,8 +744,6 @@
FF45EB303C9601ED114589A4 /* Pods-MarketWidgetExtension.release.xcconfig */,
AA7DCFB2C7887DF26EDB5710 /* Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig */,
7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */,
64774910B0DBEC412995CB89 /* Pods-WalletInformationWidgetExtension.debug.xcconfig */,
3D9CAFACCECFFBFCD9B2BC7E /* Pods-WalletInformationWidgetExtension.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@ -744,6 +751,7 @@
B40D4E31225841EC00428FCC /* BlueWalletWatch */ = {
isa = PBXGroup;
children = (
6D203C2025D4ED2500493AD1 /* BlueWalletWatch.entitlements */,
B40D4E32225841EC00428FCC /* Interface.storyboard */,
B40D4E35225841ED00428FCC /* Assets.xcassets */,
B40D4E37225841ED00428FCC /* Info.plist */,
@ -834,6 +842,7 @@
2130DE983D1D45AC8FC45F7E /* Upload Debug Symbols to Sentry */,
3271B0B6236E2E0700DA766F /* Embed App Extensions */,
C18D00A61007A84C9887DEDE /* [CP] Copy Pods Resources */,
68CD4C52AC5B27E333599B5C /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -942,7 +951,6 @@
isa = PBXNativeTarget;
buildConfigurationList = 6DEB4ABB254FB59E00E9F9AA /* Build configuration list for PBXNativeTarget "WalletInformationWidgetExtension" */;
buildPhases = (
ACD6FC5C1476108D882D6B61 /* [CP] Check Pods Manifest.lock */,
6DEB4AA9254FB59B00E9F9AA /* Sources */,
6DEB4AAA254FB59B00E9F9AA /* Frameworks */,
6DEB4AAB254FB59B00E9F9AA /* Resources */,
@ -1034,13 +1042,12 @@
B40D4E2F225841EC00428FCC = {
CreatedOnToolsVersion = 10.2;
DevelopmentTeam = A7W54YZ4WU;
ProvisioningStyle = Manual;
LastSwiftMigration = 1240;
};
B40D4E3B225841ED00428FCC = {
CreatedOnToolsVersion = 10.2;
DevelopmentTeam = A7W54YZ4WU;
LastSwiftMigration = 1130;
ProvisioningStyle = Manual;
SystemCapabilities = {
com.apple.Keychain = {
enabled = 0;
@ -1176,6 +1183,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6D4AF17725D211A3009DD853 /* FiatUnits.plist in Resources */,
B4EE583C226703320003363C /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1233,6 +1241,24 @@
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;
};
68CD4C52AC5B27E333599B5C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL/OpenSSL.framework/OpenSSL",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
6F7747C31A9EE6DDC5108476 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -1255,28 +1281,6 @@
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;
};
ACD6FC5C1476108D882D6B61 /* [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-WalletInformationWidgetExtension-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;
};
C18D00A61007A84C9887DEDE /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -1369,6 +1373,7 @@
files = (
3271B0BB236E329400DA766F /* TodayAPI.swift in Sources */,
3271B0AE236E2E0700DA766F /* TodayViewController.swift in Sources */,
6D4AF18525D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
32002D9D236FAA9F00B93396 /* TodayDataStore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1384,6 +1389,7 @@
6D6CA5292558EC52009312A5 /* PriceView.swift in Sources */,
6D2AA80B2568B8F40090B089 /* FiatUnit.swift in Sources */,
6D6CA53C2558F316009312A5 /* WidgetAPI.swift in Sources */,
6D4AF18925D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
6D6CA5332558ED54009312A5 /* Models.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1402,6 +1408,7 @@
6D99467B2555A68A000E52E8 /* MarketView.swift in Sources */,
6D9946872555A695000E52E8 /* UserDefaultsGroup.swift in Sources */,
6D9946632555A660000E52E8 /* MarketWidget.swift in Sources */,
6D4AF18625D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
6D6CA5152558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1422,6 +1429,7 @@
6DEB4BFB254FBA0E00E9F9AA /* Models.swift in Sources */,
6D641F18255226DA003792DF /* MarketView.swift in Sources */,
6D6CA5162558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */,
6D4AF18825D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1439,7 +1447,7 @@
6D641F3625526311003792DF /* SendReceiveButtons.swift in Sources */,
6DEB4BFC254FBA0E00E9F9AA /* Models.swift in Sources */,
6D641F19255226DA003792DF /* MarketView.swift in Sources */,
6D6CA54E2558F497009312A5 /* WidgetAPI+Electrum.swift in Sources */,
6D4AF18725D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1448,20 +1456,25 @@
buildActionMask = 2147483647;
files = (
B43D037C225847C500FBAA95 /* Wallet.swift in Sources */,
6D4AF17825D211A3009DD853 /* FiatUnit.swift in Sources */,
B43D037A225847C500FBAA95 /* Transaction.swift in Sources */,
32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */,
B40D4E602258425500428FCC /* SpecifyInterfaceController.swift in Sources */,
B43D0379225847C500FBAA95 /* WatchDataSource.swift in Sources */,
6D4AF16325D21185009DD853 /* WidgetDataStore.swift in Sources */,
6DFC807224EA2FA9007B8700 /* ViewQRCodefaceController.swift in Sources */,
B40D4E46225841ED00428FCC /* NotificationController.swift in Sources */,
B40D4E5D2258425500428FCC /* InterfaceController.swift in Sources */,
B43D037B225847C500FBAA95 /* TransactionTableRow.swift in Sources */,
B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */,
6D4AF15925D21172009DD853 /* WidgetAPI.swift in Sources */,
B40D4E642258425500428FCC /* WalletDetailsInterfaceController.swift in Sources */,
B40D4E44225841ED00428FCC /* ExtensionDelegate.swift in Sources */,
B40D4E682258426B00428FCC /* KeychainSwiftDistrib.swift in Sources */,
6D4AF16D25D21192009DD853 /* Models.swift in Sources */,
B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */,
B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */,
6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */,
B40D4E5E2258425500428FCC /* NumericKeypadInterfaceController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1617,7 +1630,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1660,7 +1673,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1688,8 +1701,8 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
@ -1701,12 +1714,12 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price";
PROVISIONING_PROFILE_SPECIFIER = io.bluewallet.bluewallet.TodayExtension;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
@ -1726,8 +1739,8 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
@ -1740,11 +1753,11 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price";
PROVISIONING_PROFILE_SPECIFIER = io.bluewallet.bluewallet.TodayExtension;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_OPTIMIZATION_LEVEL = "-O";
@ -1772,7 +1785,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
@ -1793,9 +1806,9 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
@ -1803,11 +1816,11 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = Stickers;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
@ -1826,8 +1839,8 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
@ -1839,14 +1852,14 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = PriceWidget;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -1867,8 +1880,8 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
@ -1881,13 +1894,13 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = PriceWidget;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -1908,27 +1921,27 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/MarketWidget/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = MarketWidget;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -1950,27 +1963,27 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/MarketWidget/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = MarketWidget;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -1991,28 +2004,28 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_EXPAND_BUILD_SETTINGS = YES;
INFOPLIST_FILE = WalletInformationWidget/Widgets/MarketWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = WalletInformationAndMarketWidget;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -2034,8 +2047,8 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
@ -2043,20 +2056,20 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_EXPAND_BUILD_SETTINGS = YES;
INFOPLIST_FILE = WalletInformationWidget/Widgets/MarketWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = WalletInformationAndMarketWidget;
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -2065,7 +2078,6 @@
};
6DEB4AB9254FB59E00E9F9AA /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 64774910B0DBEC412995CB89 /* Pods-WalletInformationWidgetExtension.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
@ -2077,27 +2089,27 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = WalletInformationWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = WalletInformationWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Wallet Information Widget";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -2107,7 +2119,6 @@
};
6DEB4ABA254FB59E00E9F9AA /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3D9CAFACCECFFBFCD9B2BC7E /* Pods-WalletInformationWidgetExtension.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
@ -2119,27 +2130,27 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = WalletInformationWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = WalletInformationWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Wallet Information Widget";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -2263,11 +2274,11 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "BlueWalletWatch Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
@ -2275,12 +2286,12 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
PRODUCT_NAME = "${TARGET_NAME}";
PROVISIONING_PROFILE_SPECIFIER = "BlueWallet for Apple Watch Extension Dist";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
@ -2302,12 +2313,12 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = "BlueWalletWatch Extension/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
@ -2315,11 +2326,11 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
PRODUCT_NAME = "${TARGET_NAME}";
PROVISIONING_PROFILE_SPECIFIER = "BlueWallet for Apple Watch Extension Dist";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
@ -2338,26 +2349,29 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_ENTITLEMENTS = BlueWalletWatch/BlueWalletWatch.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "BlueWallet for Apple Watch Distribution";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
@ -2373,26 +2387,29 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_ENTITLEMENTS = BlueWalletWatch/BlueWalletWatch.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 6.0.3;
MARKETING_VERSION = 6.0.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "BlueWallet for Apple Watch Distribution";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OBJC_BRIDGING_HEADER = "WalletInformationWidget/Widgets/Shared/BlueWalletWatch-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;

View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6D99465D2555A660000E52E8"
BuildableName = "MarketWidgetExtension.appex"
BlueprintName = "MarketWidgetExtension"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "medium"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6D6CA4B7255872E3009312A5"
BuildableName = "PriceWidgetExtension.appex"
BlueprintName = "PriceWidgetExtension"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<RemoteRunnable
runnableDebuggingMode = "1"
BundleIdentifier = "com.apple.widgetkit.simulator"
RemotePath = "/System/Library/CoreServices/WidgetKit Simulator.app/Contents/MacOS/WidgetKit Simulator">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6D6CA4B7255872E3009312A5"
BuildableName = "PriceWidgetExtension.appex"
BlueprintName = "PriceWidgetExtension"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "medium"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1240"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6D2A6460258BA92C0092292B"
BuildableName = "Stickers.appex"
BlueprintName = "Stickers"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

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