Merge branch 'master' into opsrn
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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} />
|
||||
|
|
48
BlueApp.js
|
@ -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);
|
||||
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;
|
||||
let success = false;
|
||||
let wasException = false;
|
||||
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);
|
||||
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 (hadToRefresh && noErr) {
|
||||
await BlueApp.saveToDisk(); // caching
|
||||
} */
|
||||
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');
|
||||
// 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') {
|
||||
|
|
|
@ -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)}
|
||||
<ToolTipMenu
|
||||
ref={this.tooltip}
|
||||
anchorRef={this.walletBalanceText}
|
||||
actions={
|
||||
this.state.wallet.hideBalance
|
||||
? [
|
||||
{
|
||||
text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
|
||||
id: 'walletBalanceVisibility',
|
||||
text: loc.transactions.details_balance_show,
|
||||
onPress: this.handleBalanceVisibility,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
text: this.state.wallet.hideBalance ? loc.transactions.details_balance_show : loc.transactions.details_balance_hide,
|
||||
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) {
|
||||
toolTip.current.showMenu();
|
||||
}, []);
|
||||
|
||||
const handleOnExpandNote = useCallback(() => {
|
||||
setSubtitleNumberOfLines(0);
|
||||
}
|
||||
}, [subtitleNumberOfLines]);
|
||||
// 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 (
|
||||
<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}
|
||||
titleNumberOfLines={subtitleNumberOfLines}
|
||||
subtitleNumberOfLines={subtitleNumberOfLines}
|
||||
subtitle={subtitle}
|
||||
subtitleProps={subtitleProps}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
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
|
@ -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 doesn’t 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.
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
|
|
@ -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 }} />
|
||||
|
|
12
Privacy.js
|
@ -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);
|
||||
}
|
||||
}
|
10
README.md
|
@ -9,8 +9,8 @@
|
|||
Thin Bitcoin Wallet.
|
||||
Built with React Native and Electrum.
|
||||
|
||||
[](https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8)
|
||||
[](https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet)
|
||||
[](https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8)
|
||||
[](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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) });
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
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 };
|
||||
};
|
||||
|
||||
|
|
10
blue_modules/Privacy.android.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import Obscure from 'react-native-obscure';
|
||||
export default class Privacy {
|
||||
static enableBlur() {
|
||||
Obscure.activateObscure();
|
||||
}
|
||||
|
||||
static disableBlur() {
|
||||
Obscure.deactivateObscure();
|
||||
}
|
||||
}
|
10
blue_modules/Privacy.ios.js
Normal 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
|
@ -0,0 +1,5 @@
|
|||
export default class Privacy {
|
||||
static enableBlur() {}
|
||||
|
||||
static disableBlur() {}
|
||||
}
|
|
@ -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,9 +41,13 @@ function WidgetCommunication() {
|
|||
}
|
||||
balance += wallet.getBalance();
|
||||
if (wallet.getLatestTransactionTimeEpoch() > latestTransactionTime) {
|
||||
if (wallet.getTransactions()[0].confirmations === 0) {
|
||||
latestTransactionTime = WidgetCommunication.LatestTransactionIsUnconfirmed;
|
||||
} else {
|
||||
latestTransactionTime = wallet.getLatestTransactionTimeEpoch();
|
||||
}
|
||||
}
|
||||
}
|
||||
return { allWalletsBalance: balance, latestTransactionTime };
|
||||
}
|
||||
};
|
||||
|
|
21
blue_modules/aezeed/LICENSE
Normal 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.
|
2
blue_modules/aezeed/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# aezeed
|
||||
A package for encoding, decoding, and generating mnemonics of the aezeed specification. (WIP)
|
85
blue_modules/aezeed/package.json
Normal 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
|
@ -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;
|
||||
}
|
105
blue_modules/aezeed/src/cipherseed.js
Normal 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
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="node" />
|
||||
export declare function mnemonicFromBytes(bytes: Buffer): string;
|
||||
export declare function mnemonicToBytes(mnemonic: string): Buffer;
|
2091
blue_modules/aezeed/src/mnemonic.js
Normal file
8
blue_modules/aezeed/src/params.d.ts
vendored
Normal 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;
|
14
blue_modules/aezeed/src/params.js
Normal 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;
|
|
@ -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')
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
test/
|
||||
.gitignore
|
||||
.min-wd
|
|
@ -1,70 +0,0 @@
|
|||
scryptsy
|
||||
========
|
||||
|
||||
[](http://travis-ci.org/cryptocoinjs/scryptsy)
|
||||
[](https://coveralls.io/r/cryptocoinjs/scryptsy)
|
||||
[](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
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,8 +170,9 @@ 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/')) {
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
@ -182,19 +183,18 @@ const showFilePickerAndReadFile = async function () {
|
|||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
]
|
||||
: [
|
||||
|
|
|
@ -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.
|
21
blue_modules/scryptsy/LICENSE
Normal 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.
|
157
blue_modules/scryptsy/README.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
scryptsy
|
||||
========
|
||||
|
||||
[](http://travis-ci.org/cryptocoinjs/scryptsy)
|
||||
[](https://coveralls.io/r/cryptocoinjs/scryptsy)
|
||||
[](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
|
3
blue_modules/scryptsy/lib/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
const scrypt = require('./scryptSync')
|
||||
scrypt.async = require('./scrypt')
|
||||
module.exports = scrypt
|
26
blue_modules/scryptsy/lib/scrypt.js
Normal 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
|
26
blue_modules/scryptsy/lib/scryptSync.js
Normal 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
|
216
blue_modules/scryptsy/lib/utils.js
Normal 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
|
||||
}
|
65
blue_modules/scryptsy/package.json
Normal 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"
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
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,7 +216,6 @@ export class AppStorage {
|
|||
* @returns {Promise.<boolean>}
|
||||
*/
|
||||
async loadFromDisk(password) {
|
||||
try {
|
||||
let data = await this.getItem('data');
|
||||
if (password) {
|
||||
data = this.decryptData(data, password);
|
||||
|
@ -297,6 +270,9 @@ export class AppStorage {
|
|||
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);
|
||||
|
@ -339,10 +315,6 @@ export class AppStorage {
|
|||
} else {
|
||||
return false; // failed loading data or loading/decryptin data
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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(),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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' : '');
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
13
class/quick-actions.windows.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
function DeviceQuickActions() {
|
||||
DeviceQuickActions.STORAGE_KEY = 'DeviceQuickActionsEnabled';
|
||||
|
||||
DeviceQuickActions.setEnabled = () => {};
|
||||
|
||||
DeviceQuickActions.getEnabled = async () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default DeviceQuickActions;
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ?? '';
|
||||
}
|
||||
}
|
||||
|
|
161
class/wallets/hd-aezeed-wallet.js
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -605,6 +605,10 @@ export class LightningCustodianWallet extends LegacyWallet {
|
|||
return true;
|
||||
}
|
||||
|
||||
allowSignVerifyMessage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example return:
|
||||
* { destination: '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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;
|
|
@ -24,6 +24,9 @@ const BottomModal = ({ onBackButtonPress, onBackdropPress, onClose, windowHeight
|
|||
onBackButtonPress={handleBackButtonPress}
|
||||
onBackdropPress={handleBackdropPress}
|
||||
{...props}
|
||||
accessibilityViewIsModal
|
||||
useNativeDriver
|
||||
useNativeDriverForBackdrop
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
34
components/TooltipMenu.android.js
Normal 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,
|
||||
};
|
34
components/TooltipMenu.ios.js
Normal 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
|
@ -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);
|
|
@ -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
|
@ -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
|
@ -0,0 +1,5 @@
|
|||
const HandoffComponent = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default HandoffComponent;
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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
|
@ -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
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9 KiB After Width: | Height: | Size: 13 KiB |
BIN
img/lnd-shape-rtl.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 11 KiB |
BIN
img/vault-shape-rtl.png
Normal file
After Width: | Height: | Size: 7 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 9.3 KiB |
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|