Merge branch 'master' into patch-1

This commit is contained in:
MC Saeid 2021-02-03 13:30:07 +03:30 committed by GitHub
commit 182911d5c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
231 changed files with 9608 additions and 3947 deletions

View file

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

2
.gitignore vendored
View file

@ -65,6 +65,8 @@ artifacts/
# Editors
.vscode/
/.vs
*.mx
*.realm
*.realm.lock

View file

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

102
App.js
View file

@ -1,6 +1,17 @@
import 'react-native-gesture-handler'; // should be on top
import React, { useContext, useEffect, useRef, useState } from 'react';
import { AppState, DeviceEventEmitter, KeyboardAvoidingView, Linking, Platform, StyleSheet, useColorScheme, View } from 'react-native';
import {
AppState,
DeviceEventEmitter,
NativeModules,
NativeEventEmitter,
KeyboardAvoidingView,
Linking,
Platform,
StyleSheet,
useColorScheme,
View,
} from 'react-native';
import { NavigationContainer, CommonActions } from '@react-navigation/native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { navigationRef } from './NavigationService';
@ -27,6 +38,8 @@ import WidgetCommunication from './blue_modules/WidgetCommunication';
import changeNavigationBarColor from 'react-native-navigation-bar-color';
const A = require('./blue_modules/analytics');
const eventEmitter = new NativeEventEmitter(NativeModules.EventEmitter);
if (process.env.NODE_ENV !== 'development') {
Sentry.init({
dsn: 'https://23377936131848ca8003448a893cb622@sentry.io/1295736',
@ -39,11 +52,13 @@ const ClipboardContentType = Object.freeze({
});
const App = () => {
const { walletsInitialized, wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
const { walletsInitialized, wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions } = useContext(
BlueStorageContext,
);
const appState = useRef(AppState.currentState);
const [isClipboardContentModalVisible, setIsClipboardContentModalVisible] = useState(false);
const [clipboardContentType, setClipboardContentType] = useState();
const [clipboardContent, setClipboardContent] = useState('');
const clipboardContent = useRef();
const colorScheme = useColorScheme();
const stylesHook = StyleSheet.create({
modalContent: {
@ -51,6 +66,25 @@ const App = () => {
},
});
const onNotificationReceived = async notification => {
const payload = Object.assign({}, notification, notification.data);
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
payload.foreground = true;
await Notifications.addNotification(payload);
// if user is staring at the app when he receives the notification we process it instantly
// so app refetches related wallet
if (payload.foreground) await processPushNotifications();
};
const openSettings = () => {
NavigationService.dispatch(
CommonActions.navigate({
name: 'Settings',
}),
);
};
useEffect(() => {
if (walletsInitialized) {
addListeners();
@ -62,6 +96,8 @@ const App = () => {
return () => {
Linking.removeEventListener('url', handleOpenURL);
AppState.removeEventListener('change', handleAppStateChange);
eventEmitter.removeAllListeners('onNotificationReceived');
eventEmitter.removeAllListeners('openSettings');
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -83,6 +119,12 @@ const App = () => {
DeviceEventEmitter.addListener('quickActionShortcut', walletQuickActions);
QuickActions.popInitialAction().then(popInitialAction);
handleAppStateChange(undefined);
/*
When a notification on iOS is shown while the app is on foreground;
On willPresent on AppDelegate.m
*/
eventEmitter.addListener('onNotificationReceived', onNotificationReceived);
eventEmitter.addListener('openSettings', openSettings);
};
const popInitialAction = async data => {
@ -147,13 +189,17 @@ const App = () => {
* @private
*/
const processPushNotifications = async () => {
Notifications.setApplicationIconBadgeNumber(0);
await new Promise(resolve => setTimeout(resolve, 200));
// sleep needed as sometimes unsuspend is faster than notification module actually saves notifications to async storage
const notifications2process = await Notifications.getStoredNotifications();
await Notifications.clearStoredNotifications();
Notifications.setApplicationIconBadgeNumber(0);
const deliveredNotifications = await Notifications.getDeliveredNotifications();
setTimeout(() => Notifications.removeAllDeliveredNotifications(), 5000); // so notification bubble wont disappear too fast
for (const payload of notifications2process) {
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction === true);
if (!wasTapped) continue;
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction);
console.log('processing push notification:', payload);
let wallet;
@ -171,28 +217,32 @@ const App = () => {
if (wallet) {
const walletID = wallet.getID();
fetchAndSaveWalletTransactions(walletID);
NavigationService.dispatch(
CommonActions.navigate({
name: 'WalletTransactions',
key: `WalletTransactions-${wallet.getID()}`,
params: {
walletID,
walletType: wallet.type,
},
}),
);
// no delay (1ms) as we dont need to wait for transaction propagation. 500ms is a delay to wait for the navigation
await Notifications.clearStoredNotifications();
return true;
if (wasTapped) {
NavigationService.dispatch(
CommonActions.navigate({
name: 'WalletTransactions',
key: `WalletTransactions-${wallet.getID()}`,
params: {
walletID,
walletType: wallet.type,
},
}),
);
return true;
}
} else {
console.log('could not find wallet while processing push notification tap, NOP');
console.log('could not find wallet while processing push notification, NOP');
}
} // end foreach notifications loop
if (deliveredNotifications.length > 0) {
// notification object is missing userInfo. We know we received a notification but don't have sufficient
// data to refresh 1 wallet. let's refresh all.
refreshAllWalletTransactions();
}
// TODO: if we are here - we did not act upon any push, so we need to iterate over _not tapped_ pushes
// and refetch appropriate wallet and redraw screen
await Notifications.clearStoredNotifications();
// if we are here - we did not act upon any push
return false;
};
@ -217,7 +267,7 @@ const App = () => {
const isBothBitcoinAndLightning = DeeplinkSchemaMatch.isBothBitcoinAndLightning(clipboard);
if (
!isAddressFromStoredWallet &&
clipboardContent !== clipboard &&
clipboardContent.current !== clipboard &&
(isBitcoinAddress || isLightningInvoice || isLNURL || isBothBitcoinAndLightning)
) {
if (isBitcoinAddress) {
@ -229,7 +279,7 @@ const App = () => {
}
setIsClipboardContentModalVisible(true);
}
setClipboardContent(clipboard);
clipboardContent.current = clipboard;
}
if (nextAppState) {
appState.current = nextAppState;

View file

@ -1,5 +1,4 @@
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
/* global alert */
import React, { Component, useState, useMemo, useCallback, useContext } from 'react';
import PropTypes from 'prop-types';
import { Icon, Input, Text, Header, ListItem, Avatar } from 'react-native-elements';
@ -26,33 +25,30 @@ import {
} from 'react-native';
import Clipboard from '@react-native-community/clipboard';
import LinearGradient from 'react-native-linear-gradient';
import ActionSheet from './screen/ActionSheet';
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 { launchCamera, launchImageLibrary } from 'react-native-image-picker';
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-community/async-storage';
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 Lnurl from './class/lnurl';
import { BlueStorageContext } from './blue_modules/storage-context';
import { presentCameraNotAuthorizedAlert } from './class/camera';
/** @type {AppStorage} */
const { height, width } = Dimensions.get('window');
const aspectRatio = height / width;
const BigNumber = require('bignumber.js');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const currency = require('./blue_modules/currency');
const fs = require('./blue_modules/fs');
let isIpad;
if (aspectRatio > 1.6) {
isIpad = false;
@ -514,90 +510,6 @@ export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure
);
};
export const BlueNavigationStyle = (navigation, withNavigationCloseButton = false, customCloseButtonFunction = undefined) => {
let headerRight;
const { colors, closeImage } = useTheme();
if (withNavigationCloseButton) {
headerRight = () => (
<TouchableOpacity
style={{ width: 40, height: 40, padding: 14 }}
onPress={
customCloseButtonFunction === undefined
? () => {
Keyboard.dismiss();
navigation.goBack(null);
}
: customCloseButtonFunction
}
>
<Image style={{ alignSelf: 'center' }} source={closeImage} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: colors.foregroundColor,
},
headerRight,
headerBackTitleVisible: false,
headerTintColor: colors.foregroundColor,
};
};
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => {
const { colors, closeImage } = useTheme();
let headerRight;
if (withAdvancedOptionsMenuButton) {
headerRight = () => (
<TouchableOpacity
style={{ minWidth: 40, height: 40, justifyContent: 'center' }}
onPress={advancedOptionsMenuButtonAction}
testID="advancedOptionsMenuButton"
>
<Icon size={22} name="kebab-horizontal" type="octicon" color={colors.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: colors.foregroundColor,
},
headerTintColor: colors.foregroundColor,
headerLeft: () => (
<TouchableOpacity
style={{ minWidth: 40, height: 40, justifyContent: 'center', paddingHorizontal: 14 }}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
<Image style={{}} source={closeImage} />
</TouchableOpacity>
),
headerRight,
headerBackTitle: null,
};
};
export const BluePrivateBalance = () => {
return Platform.select({
ios: (
@ -662,7 +574,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>
@ -835,48 +747,6 @@ export const BlueHeader = props => {
export const BlueHeaderDefaultSub = props => {
const { colors } = useTheme();
return (
<SafeAreaView style={{ backgroundColor: colors.brandingColor }}>
<Header
backgroundColor={colors.background}
leftContainerStyle={{ minWidth: '100%' }}
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
leftComponent={
<Text
adjustsFontSizeToFit
style={{
fontWeight: 'bold',
fontSize: 30,
color: colors.foregroundColor,
}}
>
{props.leftText}
</Text>
}
rightComponent={
<TouchableOpacity
onPress={() => {
if (props.onClose) props.onClose();
}}
>
<View style={stylesBlueIcon.box}>
<View style={stylesBlueIcon.ballTransparrent}>
<Image source={require('./img/close.png')} />
</View>
</View>
</TouchableOpacity>
}
{...props}
/>
</SafeAreaView>
);
};
export const BlueHeaderDefaultSubHooks = props => {
const { colors } = useTheme();
return (
<SafeAreaView>
@ -966,30 +836,44 @@ export const BlueSpacing10 = props => {
return <View {...props} style={{ height: 10, opacity: 0 }} />;
};
export class BlueUseAllFundsButton extends Component {
static InputAccessoryViewID = 'useMaxInputAccessoryViewID';
static propTypes = {
balance: PropTypes.string.isRequired,
canUseAll: PropTypes.bool.isRequired,
onUseAllPressed: PropTypes.func.isRequired,
};
render() {
const inputView = (
<View
style={{
flex: 1,
flexDirection: 'row',
maxHeight: 44,
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
}}
>
<View style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'flex-start' }}>
export const BlueUseAllFundsButton = ({ balance, canUseAll, onUseAllPressed }) => {
const { colors } = useTheme();
const inputView = (
<View
style={{
flex: 1,
flexDirection: 'row',
maxHeight: 44,
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: colors.inputBackgroundColor,
}}
>
<View style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'flex-start' }}>
<Text
style={{
color: colors.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
{loc.send.input_total}
</Text>
{canUseAll ? (
<BlueButtonLink
onPress={onUseAllPressed}
style={{ marginLeft: 8, paddingRight: 0, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title={`${balance} ${BitcoinUnit.BTC}`}
/>
) : (
<Text
style={{
color: BlueCurrentTheme.colors.alternativeTextColor,
color: colors.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
@ -999,48 +883,32 @@ export class BlueUseAllFundsButton extends Component {
paddingBottom: 12,
}}
>
{loc.send.input_total}
{balance} {BitcoinUnit.BTC}
</Text>
{this.props.canUseAll ? (
<BlueButtonLink
onPress={this.props.onUseAllPressed}
style={{ marginLeft: 8, paddingRight: 0, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title={`${this.props.balance} ${BitcoinUnit.BTC}`}
/>
) : (
<Text
style={{
color: BlueCurrentTheme.colors.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
{this.props.balance} {BitcoinUnit.BTC}
</Text>
)}
</View>
<View style={{ flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'flex-end' }}>
<BlueButtonLink
style={{ paddingRight: 8, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title={loc.send.input_done}
onPress={() => Keyboard.dismiss()}
/>
</View>
)}
</View>
);
<View style={{ flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'flex-end' }}>
<BlueButtonLink
style={{ paddingRight: 8, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title={loc.send.input_done}
onPress={Keyboard.dismiss}
/>
</View>
</View>
);
if (Platform.OS === 'ios') {
return <InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
} else {
return <KeyboardAvoidingView style={{ height: 44 }}>{inputView}</KeyboardAvoidingView>;
}
if (Platform.OS === 'ios') {
return <InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
} else {
return <KeyboardAvoidingView style={{ height: 44 }}>{inputView}</KeyboardAvoidingView>;
}
}
};
BlueUseAllFundsButton.InputAccessoryViewID = 'useMaxInputAccessoryViewID';
BlueUseAllFundsButton.propTypes = {
balance: PropTypes.string.isRequired,
canUseAll: PropTypes.bool.isRequired,
onUseAllPressed: PropTypes.func.isRequired,
};
export const BlueDismissKeyboardInputAccessory = () => {
const { colors } = useTheme();
@ -1401,7 +1269,7 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme();
const { navigate } = useNavigation();
const { txMetadata, wallets } = useContext(BlueStorageContext);
const { txMetadata, wallets, preferredFiatCurrency, language } = useContext(BlueStorageContext);
const containerStyle = useMemo(
() => ({
backgroundColor: 'transparent',
@ -1419,7 +1287,8 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
} else {
return transactionTimeToReadable(item.received);
}
}, [item.confirmations, item.received]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item.confirmations, item.received, language]);
const txMemo = txMetadata[item.hash]?.memo ?? '';
const subtitle = useMemo(() => {
let sub = item.confirmations < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : '';
@ -1450,7 +1319,8 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
} else {
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
}
}, [item, itemPriceUnit]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [item, itemPriceUnit, preferredFiatCurrency]);
const rowTitleStyle = useMemo(() => {
let color = colors.successColor;
@ -1633,77 +1503,6 @@ export const BlueAddressInput = ({
launchedBy,
}) => {
const { colors } = useTheme();
const choosePhoto = () => {
launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.uri;
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
}
},
);
};
const takePhoto = () => {
launchCamera(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.uri;
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
presentCameraNotAuthorizedAlert(response.error);
}
},
);
};
const copyFromClipbard = async () => {
onBarScanned(await Clipboard.getString());
};
const showActionSheet = async () => {
const isClipboardEmpty = (await Clipboard.getString()).trim().length === 0;
let copyFromClipboardIndex;
if (Platform.OS === 'ios') {
const options = [loc._.cancel, loc.wallets.list_long_choose, isDesktop ? loc.wallets.take_photo : loc.wallets.list_long_scan];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
copyFromClipboardIndex = options.length - 1;
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
choosePhoto();
} else if (buttonIndex === 2) {
takePhoto();
} else if (buttonIndex === copyFromClipboardIndex) {
copyFromClipbard();
}
});
}
};
return (
<View
@ -1739,7 +1538,7 @@ export const BlueAddressInput = ({
onPress={() => {
Keyboard.dismiss();
if (isDesktop) {
showActionSheet();
fs.showActionSheet().then(onBarScanned);
} else {
NavigationService.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
@ -2147,7 +1946,7 @@ export class BlueBitcoinAmount extends Component {
maxLength={this.maxLength()}
ref={textInput => (this.textInput = textInput)}
editable={!this.props.isLoading && !this.props.disabled}
value={parseFloat(amount) >= 0 || amount === BitcoinUnit.MAX ? amount : undefined}
value={amount === BitcoinUnit.MAX ? loc.units.MAX : parseFloat(amount) >= 0 ? amount : undefined}
placeholderTextColor={
this.props.disabled ? BlueCurrentTheme.colors.buttonDisabledTextColor : BlueCurrentTheme.colors.alternativeTextColor2
}
@ -2172,7 +1971,7 @@ export class BlueBitcoinAmount extends Component {
justifyContent: 'center',
}}
>
{' ' + this.state.unit}
{' ' + loc.units[this.state.unit]}
</Text>
)}
</View>
@ -2181,7 +1980,7 @@ export class BlueBitcoinAmount extends Component {
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX
? removeTrailingZeros(secondaryDisplayCurrency)
: secondaryDisplayCurrency}
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${BitcoinUnit.BTC}` : null}
{this.state.unit === BitcoinUnit.LOCAL_CURRENCY && amount !== BitcoinUnit.MAX ? ` ${loc.units[BitcoinUnit.BTC]}` : null}
</Text>
</View>
</View>

View file

@ -7,6 +7,8 @@ When you tag a new release, use the following example:
`git tag -m "REL v1.4.0: 157c9c2" v1.4.0`
You may get the commit hash from git log. Don't forget to push tags `git push origin --tags`
Alternative way to tag: `git tag -a v6.0.0 2e1a00609d5a0dbc91bcda2421df0f61bdfc6b10 -m "v6.0.0"`
When tagging a new release, make sure to increment version in package.json and other files (we have a script for that: `./scripts/edit-version-number.sh`)
In the commit where you up version you can have the commit message as
`"REL vX.X.X: Summary message"`.

24
FAQ.md Normal file
View file

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

View file

@ -2,6 +2,7 @@ import React from 'react';
import { createStackNavigator, TransitionPresets } from '@react-navigation/stack';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { Platform, useWindowDimensions, Dimensions } from 'react-native';
import { useTheme } from '@react-navigation/native';
import Settings from './screen/settings/settings';
import About from './screen/settings/about';
@ -109,148 +110,197 @@ const defaultStackScreenOptions =
};
const WalletsStack = createStackNavigator();
const WalletsRoot = () => (
<WalletsStack.Navigator {...(Platform.OS === 'android' ? { screenOptions: defaultScreenOptions } : null)}>
<WalletsStack.Screen name="WalletsList" component={WalletsList} />
<WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions} />
<WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions} />
<WalletsStack.Screen name="TransactionDetails" component={TransactionDetails} options={TransactionDetails.navigationOptions} />
<WalletsStack.Screen name="TransactionStatus" component={TransactionStatus} options={TransactionStatus.navigationOptions} />
<WalletsStack.Screen name="HodlHodl" component={HodlHodl} options={HodlHodl.navigationOptions} />
<WalletsStack.Screen name="CPFP" component={CPFP} options={CPFP.navigationOptions} />
<WalletsStack.Screen name="RBFBumpFee" component={RBFBumpFee} options={RBFBumpFee.navigationOptions} />
<WalletsStack.Screen name="RBFCancel" component={RBFCancel} options={RBFCancel.navigationOptions} />
<WalletsStack.Screen name="Settings" component={Settings} options={Settings.navigationOptions} />
<WalletsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<WalletsStack.Screen name="Currency" component={Currency} options={Currency.navigationOptions} />
<WalletsStack.Screen name="About" component={About} options={About.navigationOptions} />
<WalletsStack.Screen name="ReleaseNotes" component={ReleaseNotes} options={ReleaseNotes.navigationOptions} />
<WalletsStack.Screen name="Selftest" component={Selftest} options={Selftest.navigationOptions} />
<WalletsStack.Screen name="Licensing" component={Licensing} options={Licensing.navigationOptions} />
<WalletsStack.Screen name="DefaultView" component={DefaultView} options={DefaultView.navigationOptions} />
<WalletsStack.Screen name="Language" component={Language} options={Language.navigationOptions} />
<WalletsStack.Screen name="EncryptStorage" component={EncryptStorage} options={EncryptStorage.navigationOptions} />
<WalletsStack.Screen name="GeneralSettings" component={GeneralSettings} options={GeneralSettings.navigationOptions} />
<WalletsStack.Screen name="NetworkSettings" component={NetworkSettings} options={NetworkSettings.navigationOptions} />
<WalletsStack.Screen name="NotificationSettings" component={NotificationSettings} options={NotificationSettings.navigationOptions} />
<WalletsStack.Screen name="PlausibleDeniability" component={PlausibleDeniability} options={PlausibleDeniability.navigationOptions} />
<WalletsStack.Screen name="LightningSettings" component={LightningSettings} options={LightningSettings.navigationOptions} />
<WalletsStack.Screen name="ElectrumSettings" component={ElectrumSettings} options={ElectrumSettings.navigationOptions} />
<WalletsStack.Screen name="SettingsPrivacy" component={SettingsPrivacy} options={SettingsPrivacy.navigationOptions} />
<WalletsStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions} />
<WalletsStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions}
/>
<WalletsStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions}
/>
<WalletsStack.Screen name="HodlHodlViewOffer" component={HodlHodlViewOffer} options={HodlHodlViewOffer.navigationOptions} />
<WalletsStack.Screen name="Broadcast" component={Broadcast} options={Broadcast.navigationOptions} />
<WalletsStack.Screen name="IsItMyAddress" component={IsItMyAddress} options={IsItMyAddress.navigationOptions} />
<WalletsStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions} />
<WalletsStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions} />
<WalletsStack.Screen
name="Success"
component={Success}
options={{
headerShown: false,
gestureEnabled: false,
}}
/>
</WalletsStack.Navigator>
);
const WalletsRoot = () => {
const theme = useTheme();
return (
<WalletsStack.Navigator {...(Platform.OS === 'android' ? { screenOptions: defaultScreenOptions } : null)}>
<WalletsStack.Screen name="WalletsList" component={WalletsList} />
<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)} />
<WalletsStack.Screen name="TransactionStatus" component={TransactionStatus} options={TransactionStatus.navigationOptions(theme)} />
<WalletsStack.Screen name="HodlHodl" component={HodlHodl} options={HodlHodl.navigationOptions(theme)} />
<WalletsStack.Screen name="HodlHodlViewOffer" component={HodlHodlViewOffer} options={HodlHodlViewOffer.navigationOptions(theme)} />
<WalletsStack.Screen name="CPFP" component={CPFP} options={CPFP.navigationOptions(theme)} />
<WalletsStack.Screen name="RBFBumpFee" component={RBFBumpFee} options={RBFBumpFee.navigationOptions(theme)} />
<WalletsStack.Screen name="RBFCancel" component={RBFCancel} options={RBFCancel.navigationOptions(theme)} />
<WalletsStack.Screen name="Settings" component={Settings} options={Settings.navigationOptions(theme)} />
<WalletsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<WalletsStack.Screen name="Currency" component={Currency} options={Currency.navigationOptions(theme)} />
<WalletsStack.Screen name="About" component={About} options={About.navigationOptions(theme)} />
<WalletsStack.Screen name="ReleaseNotes" component={ReleaseNotes} options={ReleaseNotes.navigationOptions(theme)} />
<WalletsStack.Screen name="Selftest" component={Selftest} options={Selftest.navigationOptions(theme)} />
<WalletsStack.Screen name="Licensing" component={Licensing} options={Licensing.navigationOptions(theme)} />
<WalletsStack.Screen name="DefaultView" component={DefaultView} options={DefaultView.navigationOptions(theme)} />
<WalletsStack.Screen name="Language" component={Language} options={Language.navigationOptions(theme)} />
<WalletsStack.Screen name="EncryptStorage" component={EncryptStorage} options={EncryptStorage.navigationOptions(theme)} />
<WalletsStack.Screen name="GeneralSettings" component={GeneralSettings} options={GeneralSettings.navigationOptions(theme)} />
<WalletsStack.Screen name="NetworkSettings" component={NetworkSettings} options={NetworkSettings.navigationOptions(theme)} />
<WalletsStack.Screen
name="NotificationSettings"
component={NotificationSettings}
options={NotificationSettings.navigationOptions(theme)}
/>
<WalletsStack.Screen
name="PlausibleDeniability"
component={PlausibleDeniability}
options={PlausibleDeniability.navigationOptions(theme)}
/>
<WalletsStack.Screen name="LightningSettings" component={LightningSettings} options={LightningSettings.navigationOptions(theme)} />
<WalletsStack.Screen name="ElectrumSettings" component={ElectrumSettings} options={ElectrumSettings.navigationOptions(theme)} />
<WalletsStack.Screen name="SettingsPrivacy" component={SettingsPrivacy} options={SettingsPrivacy.navigationOptions(theme)} />
<WalletsStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions(theme)} />
<WalletsStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions(theme)}
/>
<WalletsStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions(theme)}
/>
<WalletsStack.Screen name="Broadcast" component={Broadcast} options={Broadcast.navigationOptions(theme)} />
<WalletsStack.Screen name="IsItMyAddress" component={IsItMyAddress} options={IsItMyAddress.navigationOptions(theme)} />
<WalletsStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions(theme)} />
<WalletsStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions(theme)} />
<WalletsStack.Screen
name="Success"
component={Success}
options={{
headerShown: false,
gestureEnabled: false,
}}
/>
</WalletsStack.Navigator>
);
};
const AddWalletStack = createStackNavigator();
const AddWalletRoot = () => (
<AddWalletStack.Navigator screenOptions={defaultStackScreenOptions}>
<AddWalletStack.Screen name="AddWallet" component={AddWallet} options={AddWallet.navigationOptions} />
<AddWalletStack.Screen name="ImportWallet" component={ImportWallet} options={ImportWallet.navigationOptions} />
<AddWalletStack.Screen name="PleaseBackup" component={PleaseBackup} options={PleaseBackup.navigationOptions} />
<AddWalletStack.Screen name="PleaseBackupLNDHub" component={PleaseBackupLNDHub} options={PleaseBackupLNDHub.navigationOptions} />
<AddWalletStack.Screen name="ProvideEntropy" component={ProvideEntropy} options={ProvideEntropy.navigationOptions} />
<AddWalletStack.Screen name="WalletsAddMultisig" component={WalletsAddMultisig} options={WalletsAddMultisig.navigationOptions} />
<AddWalletStack.Screen
name="WalletsAddMultisigStep2"
component={WalletsAddMultisigStep2}
options={WalletsAddMultisigStep2.navigationOptions}
/>
<AddWalletStack.Screen
name="WalletsAddMultisigHelp"
component={WalletsAddMultisigHelp}
options={WalletsAddMultisigHelp.navigationOptions}
/>
</AddWalletStack.Navigator>
);
const AddWalletRoot = () => {
const theme = useTheme();
return (
<AddWalletStack.Navigator screenOptions={defaultStackScreenOptions}>
<AddWalletStack.Screen name="AddWallet" component={AddWallet} options={AddWallet.navigationOptions(theme)} />
<AddWalletStack.Screen name="ImportWallet" component={ImportWallet} options={ImportWallet.navigationOptions(theme)} />
<AddWalletStack.Screen name="PleaseBackup" component={PleaseBackup} options={PleaseBackup.navigationOptions(theme)} />
<AddWalletStack.Screen
name="PleaseBackupLNDHub"
component={PleaseBackupLNDHub}
options={PleaseBackupLNDHub.navigationOptions(theme)}
/>
<AddWalletStack.Screen name="ProvideEntropy" component={ProvideEntropy} options={ProvideEntropy.navigationOptions(theme)} />
<AddWalletStack.Screen
name="WalletsAddMultisig"
component={WalletsAddMultisig}
options={WalletsAddMultisig.navigationOptions(theme)}
/>
<AddWalletStack.Screen
name="WalletsAddMultisigStep2"
component={WalletsAddMultisigStep2}
options={WalletsAddMultisigStep2.navigationOptions(theme)}
/>
<AddWalletStack.Screen
name="WalletsAddMultisigHelp"
component={WalletsAddMultisigHelp}
options={WalletsAddMultisigHelp.navigationOptions(theme)}
/>
</AddWalletStack.Navigator>
);
};
// CreateTransactionStackNavigator === SendDetailsStack
const SendDetailsStack = createStackNavigator();
const SendDetailsRoot = () => (
<SendDetailsStack.Navigator screenOptions={defaultStackScreenOptions}>
<SendDetailsStack.Screen name="SendDetails" component={SendDetails} options={SendDetails.navigationOptions} />
<SendDetailsStack.Screen name="Confirm" component={Confirm} options={Confirm.navigationOptions} />
<SendDetailsStack.Screen
name="PsbtWithHardwareWallet"
component={PsbtWithHardwareWallet}
options={PsbtWithHardwareWallet.navigationOptions}
/>
<SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions} />
<SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions} />
<SendDetailsStack.Screen name="PsbtMultisigQRCode" component={PsbtMultisigQRCode} options={PsbtMultisigQRCode.navigationOptions} />
<SendDetailsStack.Screen
name="Success"
component={Success}
options={{
headerShown: false,
gestureEnabled: false,
}}
/>
<SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<SendDetailsStack.Screen name="CoinControl" component={CoinControl} options={CoinControl.navigationOptions} />
</SendDetailsStack.Navigator>
);
const SendDetailsRoot = () => {
const theme = useTheme();
return (
<SendDetailsStack.Navigator screenOptions={defaultStackScreenOptions}>
<SendDetailsStack.Screen name="SendDetails" component={SendDetails} options={SendDetails.navigationOptions(theme)} />
<SendDetailsStack.Screen name="Confirm" component={Confirm} options={Confirm.navigationOptions(theme)} />
<SendDetailsStack.Screen
name="PsbtWithHardwareWallet"
component={PsbtWithHardwareWallet}
options={PsbtWithHardwareWallet.navigationOptions(theme)}
/>
<SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions(theme)} />
<SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions(theme)} />
<SendDetailsStack.Screen
name="PsbtMultisigQRCode"
component={PsbtMultisigQRCode}
options={PsbtMultisigQRCode.navigationOptions(theme)}
/>
<SendDetailsStack.Screen
name="Success"
component={Success}
options={{
headerShown: false,
gestureEnabled: false,
}}
/>
<SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<SendDetailsStack.Screen name="CoinControl" component={CoinControl} options={CoinControl.navigationOptions(theme)} />
</SendDetailsStack.Navigator>
);
};
const LNDCreateInvoiceStack = createStackNavigator();
const LNDCreateInvoiceRoot = () => (
<LNDCreateInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<LNDCreateInvoiceStack.Screen name="LNDCreateInvoice" component={LNDCreateInvoice} options={LNDCreateInvoice.navigationOptions} />
<LNDCreateInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<LNDCreateInvoiceStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions} />
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions}
/>
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions}
/>
</LNDCreateInvoiceStack.Navigator>
);
const LNDCreateInvoiceRoot = () => {
const theme = useTheme();
return (
<LNDCreateInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<LNDCreateInvoiceStack.Screen
name="LNDCreateInvoice"
component={LNDCreateInvoice}
options={LNDCreateInvoice.navigationOptions(theme)}
/>
<LNDCreateInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<LNDCreateInvoiceStack.Screen name="LNDViewInvoice" component={LNDViewInvoice} options={LNDViewInvoice.navigationOptions(theme)} />
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={LNDViewAdditionalInvoiceInformation.navigationOptions(theme)}
/>
<LNDCreateInvoiceStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={LNDViewAdditionalInvoicePreImage.navigationOptions(theme)}
/>
</LNDCreateInvoiceStack.Navigator>
);
};
// LightningScanInvoiceStackNavigator === ScanLndInvoiceStack
const ScanLndInvoiceStack = createStackNavigator();
const ScanLndInvoiceRoot = () => (
<ScanLndInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<ScanLndInvoiceStack.Screen name="ScanLndInvoice" component={ScanLndInvoice} options={ScanLndInvoice.navigationOptions} />
<ScanLndInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
<ScanLndInvoiceStack.Screen name="Success" component={Success} options={{ headerShown: false, gestureEnabled: false }} />
<ScanLndInvoiceStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions} />
<ScanLndInvoiceStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions} />
</ScanLndInvoiceStack.Navigator>
);
const ScanLndInvoiceRoot = () => {
const theme = useTheme();
return (
<ScanLndInvoiceStack.Navigator screenOptions={defaultStackScreenOptions}>
<ScanLndInvoiceStack.Screen name="ScanLndInvoice" component={ScanLndInvoice} options={ScanLndInvoice.navigationOptions(theme)} />
<ScanLndInvoiceStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions(theme)} />
<ScanLndInvoiceStack.Screen name="Success" component={Success} options={{ headerShown: false, gestureEnabled: false }} />
<ScanLndInvoiceStack.Screen name="LnurlPay" component={LnurlPay} options={LnurlPay.navigationOptions(theme)} />
<ScanLndInvoiceStack.Screen name="LnurlPaySuccess" component={LnurlPaySuccess} options={LnurlPaySuccess.navigationOptions(theme)} />
</ScanLndInvoiceStack.Navigator>
);
};
const AztecoRedeemStack = createStackNavigator();
const AztecoRedeemRoot = () => (
<AztecoRedeemStack.Navigator screenOptions={defaultStackScreenOptions}>
<AztecoRedeemStack.Screen name="AztecoRedeem" component={AztecoRedeem} options={AztecoRedeem.navigationOptions} />
<AztecoRedeemStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
</AztecoRedeemStack.Navigator>
);
const AztecoRedeemRoot = () => {
const theme = useTheme();
return (
<AztecoRedeemStack.Navigator screenOptions={defaultStackScreenOptions}>
<AztecoRedeemStack.Screen name="AztecoRedeem" component={AztecoRedeem} options={AztecoRedeem.navigationOptions(theme)} />
<AztecoRedeemStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
</AztecoRedeemStack.Navigator>
);
};
const ScanQRCodeStack = createStackNavigator();
const ScanQRCodeRoot = () => (
@ -274,18 +324,26 @@ const UnlockWithScreenRoot = () => (
);
const HodlHodlLoginStack = createStackNavigator();
const HodlHodlLoginRoot = () => (
<HodlHodlLoginStack.Navigator name="HodlHodlLoginRoot" screenOptions={defaultStackScreenOptions}>
<HodlHodlLoginStack.Screen name="HodlHodlLogin" component={HodlHodlLogin} options={HodlHodlLogin.navigationOptions} />
</HodlHodlLoginStack.Navigator>
);
const HodlHodlLoginRoot = () => {
const theme = useTheme();
return (
<HodlHodlLoginStack.Navigator name="HodlHodlLoginRoot" screenOptions={defaultStackScreenOptions}>
<HodlHodlLoginStack.Screen name="HodlHodlLogin" component={HodlHodlLogin} options={HodlHodlLogin.navigationOptions(theme)} />
</HodlHodlLoginStack.Navigator>
);
};
const ReorderWalletsStack = createStackNavigator();
const ReorderWalletsStackRoot = () => (
<ReorderWalletsStack.Navigator name="ReorderWalletsRoot" screenOptions={defaultStackScreenOptions}>
<ReorderWalletsStack.Screen name="ReorderWallets" component={ReorderWallets} options={ReorderWallets.navigationOptions} />
</ReorderWalletsStack.Navigator>
);
const ReorderWalletsStackRoot = () => {
const theme = useTheme();
return (
<ReorderWalletsStack.Navigator name="ReorderWalletsRoot" screenOptions={defaultStackScreenOptions}>
<ReorderWalletsStack.Screen name="ReorderWallets" component={ReorderWallets} options={ReorderWallets.navigationOptions(theme)} />
</ReorderWalletsStack.Navigator>
);
};
const Drawer = createDrawerNavigator();
function DrawerRoot() {
@ -305,32 +363,48 @@ function DrawerRoot() {
}
const ReceiveDetailsStack = createStackNavigator();
const ReceiveDetailsStackRoot = () => (
<ReceiveDetailsStack.Navigator name="ReceiveDetailsRoot" screenOptions={defaultStackScreenOptions} initialRouteName="ReceiveDetails">
<RootStack.Screen name="ReceiveDetails" component={ReceiveDetails} options={ReceiveDetails.navigationOptions} />
</ReceiveDetailsStack.Navigator>
);
const ReceiveDetailsStackRoot = () => {
const theme = useTheme();
return (
<ReceiveDetailsStack.Navigator name="ReceiveDetailsRoot" screenOptions={defaultStackScreenOptions} initialRouteName="ReceiveDetails">
<RootStack.Screen name="ReceiveDetails" component={ReceiveDetails} options={ReceiveDetails.navigationOptions(theme)} />
</ReceiveDetailsStack.Navigator>
);
};
const WalletXpubStack = createStackNavigator();
const WalletXpubStackRoot = () => (
<WalletXpubStack.Navigator name="WalletXpubRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletXpub">
<WalletXpubStack.Screen name="WalletXpub" component={WalletXpub} options={WalletXpub.navigationOptions} />
</WalletXpubStack.Navigator>
);
const WalletXpubStackRoot = () => {
const theme = useTheme();
return (
<WalletXpubStack.Navigator name="WalletXpubRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletXpub">
<WalletXpubStack.Screen name="WalletXpub" component={WalletXpub} options={WalletXpub.navigationOptions(theme)} />
</WalletXpubStack.Navigator>
);
};
const WalletExportStack = createStackNavigator();
const WalletExportStackRoot = () => (
<WalletExportStack.Navigator name="WalletExportRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletExport">
<WalletExportStack.Screen name="WalletExport" component={WalletExport} options={WalletExport.navigationOptions} />
</WalletExportStack.Navigator>
);
const WalletExportStackRoot = () => {
const theme = useTheme();
return (
<WalletExportStack.Navigator name="WalletExportRoot" screenOptions={defaultStackScreenOptions} initialRouteName="WalletExport">
<WalletExportStack.Screen name="WalletExport" component={WalletExport} options={WalletExport.navigationOptions(theme)} />
</WalletExportStack.Navigator>
);
};
const LappBrowserStack = createStackNavigator();
const LappBrowserStackRoot = () => (
<LappBrowserStack.Navigator name="LappBrowserRoot" screenOptions={defaultStackScreenOptions} initialRouteName="LappBrowser">
<LappBrowserStack.Screen name="LappBrowser" component={LappBrowser} options={LappBrowser.navigationOptions} />
</LappBrowserStack.Navigator>
);
const LappBrowserStackRoot = () => {
const theme = useTheme();
return (
<LappBrowserStack.Navigator name="LappBrowserRoot" screenOptions={defaultStackScreenOptions} initialRouteName="LappBrowser">
<LappBrowserStack.Screen name="LappBrowser" component={LappBrowser} options={LappBrowser.navigationOptions(theme)} />
</LappBrowserStack.Navigator>
);
};
const InitStack = createStackNavigator();
const InitRoot = () => (
@ -341,79 +415,91 @@ const InitRoot = () => (
component={UnlockWithScreenRoot}
options={{ headerShown: false, animationEnabled: false }}
/>
<InitStack.Screen name="ReorderWallets" component={ReorderWalletsStackRoot} options={{ headerShown: false }} />
<InitStack.Screen name="ReorderWallets" component={ReorderWalletsStackRoot} options={{ headerShown: false, gestureEnabled: false }} />
<InitStack.Screen name="DrawerRoot" component={DrawerRoot} options={{ headerShown: false, animationEnabled: false }} />
</InitStack.Navigator>
);
const ViewEditMultisigCosignersStack = createStackNavigator();
const ViewEditMultisigCosignersRoot = () => (
<ViewEditMultisigCosignersStack.Navigator
name="ViewEditMultisigCosignersRoot"
screenOptions={defaultStackScreenOptions}
initialRouteName="ViewEditMultisigCosigners"
>
<ViewEditMultisigCosignersStack.Screen
name="ViewEditMultisigCosigners"
component={ViewEditMultisigCosigners}
options={ViewEditMultisigCosigners.navigationOptions}
/>
</ViewEditMultisigCosignersStack.Navigator>
);
const ViewEditMultisigCosignersRoot = () => {
const theme = useTheme();
return (
<ViewEditMultisigCosignersStack.Navigator
name="ViewEditMultisigCosignersRoot"
screenOptions={defaultStackScreenOptions}
initialRouteName="ViewEditMultisigCosigners"
>
<ViewEditMultisigCosignersStack.Screen
name="ViewEditMultisigCosigners"
component={ViewEditMultisigCosigners}
options={ViewEditMultisigCosigners.navigationOptions(theme)}
/>
</ViewEditMultisigCosignersStack.Navigator>
);
};
const ExportMultisigCoordinationSetupStack = createStackNavigator();
const ExportMultisigCoordinationSetupRoot = () => (
<ExportMultisigCoordinationSetupStack.Navigator
name="ExportMultisigCoordinationSetupRoot"
screenOptions={defaultStackScreenOptions}
initialRouteName="ExportMultisigCoordinationSetup"
>
<ExportMultisigCoordinationSetupStack.Screen
name="ExportMultisigCoordinationSetup"
component={ExportMultisigCoordinationSetup}
options={ExportMultisigCoordinationSetup.navigationOptions}
/>
</ExportMultisigCoordinationSetupStack.Navigator>
);
const ExportMultisigCoordinationSetupRoot = () => {
const theme = useTheme();
return (
<ExportMultisigCoordinationSetupStack.Navigator
name="ExportMultisigCoordinationSetupRoot"
screenOptions={defaultStackScreenOptions}
initialRouteName="ExportMultisigCoordinationSetup"
>
<ExportMultisigCoordinationSetupStack.Screen
name="ExportMultisigCoordinationSetup"
component={ExportMultisigCoordinationSetup}
options={ExportMultisigCoordinationSetup.navigationOptions(theme)}
/>
</ExportMultisigCoordinationSetupStack.Navigator>
);
};
const RootStack = createStackNavigator();
const Navigation = () => (
<RootStack.Navigator mode="modal" screenOptions={defaultScreenOptions} initialRouteName="LoadingScreenRoot">
{/* stacks */}
<RootStack.Screen name="WalletsRoot" component={WalletsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AddWalletRoot" component={AddWalletRoot} options={{ headerShown: false }} />
<RootStack.Screen name="SendDetailsRoot" component={SendDetailsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AztecoRedeemRoot" component={AztecoRedeemRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlLoginRoot" component={HodlHodlLoginRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlMyContracts" component={HodlHodlMyContracts} options={HodlHodlMyContracts.navigationOptions} />
<RootStack.Screen name="HodlHodlWebview" component={HodlHodlWebview} options={HodlHodlWebview.navigationOptions} />
const Navigation = () => {
const theme = useTheme();
{/* screens */}
<RootStack.Screen name="WalletExportRoot" component={WalletExportStackRoot} options={{ headerShown: false }} />
<RootStack.Screen
name="ExportMultisigCoordinationSetupRoot"
component={ExportMultisigCoordinationSetupRoot}
options={{ headerShown: false }}
/>
<RootStack.Screen name="ViewEditMultisigCosignersRoot" component={ViewEditMultisigCosignersRoot} options={{ headerShown: false }} />
<RootStack.Screen name="WalletXpubRoot" component={WalletXpubStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="BuyBitcoin" component={BuyBitcoin} options={BuyBitcoin.navigationOptions} />
<RootStack.Screen name="Marketplace" component={Marketplace} options={Marketplace.navigationOptions} />
<RootStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
<RootStack.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LappBrowserRoot" component={LappBrowserStackRoot} options={{ headerShown: false }} />
return (
<RootStack.Navigator mode="modal" screenOptions={defaultScreenOptions} initialRouteName="LoadingScreenRoot">
{/* stacks */}
<RootStack.Screen name="WalletsRoot" component={WalletsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AddWalletRoot" component={AddWalletRoot} options={{ headerShown: false }} />
<RootStack.Screen name="SendDetailsRoot" component={SendDetailsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AztecoRedeemRoot" component={AztecoRedeemRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlLoginRoot" component={HodlHodlLoginRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlMyContracts" component={HodlHodlMyContracts} options={HodlHodlMyContracts.navigationOptions(theme)} />
<RootStack.Screen name="HodlHodlWebview" component={HodlHodlWebview} options={HodlHodlWebview.navigationOptions(theme)} />
<RootStack.Screen
name="ScanQRCodeRoot"
component={ScanQRCodeRoot}
options={{
...(Platform.OS === 'ios' ? TransitionPresets.ModalTransition : TransitionPresets.ScaleFromCenterAndroid),
headerShown: false,
}}
/>
</RootStack.Navigator>
);
{/* screens */}
<RootStack.Screen name="WalletExportRoot" component={WalletExportStackRoot} options={{ headerShown: false }} />
<RootStack.Screen
name="ExportMultisigCoordinationSetupRoot"
component={ExportMultisigCoordinationSetupRoot}
options={{ headerShown: false }}
/>
<RootStack.Screen name="ViewEditMultisigCosignersRoot" component={ViewEditMultisigCosignersRoot} options={{ headerShown: false }} />
<RootStack.Screen name="WalletXpubRoot" component={WalletXpubStackRoot} 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 }} />
<RootStack.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LappBrowserRoot" component={LappBrowserStackRoot} options={{ headerShown: false }} />
<RootStack.Screen
name="ScanQRCodeRoot"
component={ScanQRCodeRoot}
options={{
...(Platform.OS === 'ios' ? TransitionPresets.ModalTransition : TransitionPresets.ScaleFromCenterAndroid),
headerShown: false,
}}
/>
</RootStack.Navigator>
);
};
export default InitRoot;

View file

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

View file

@ -9,8 +9,8 @@
Thin Bitcoin Wallet.
Built with React Native and Electrum.
[![Appstore](https://bluewallet.io/img/app-store-badge.svg)](https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8)
[![Playstore](https://bluewallet.io/img/play-store-badge.svg)](https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet)
[![Appstore](https://bluewallet.io/uploads/app-store-badge-blue.svg)](https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8)
[![Playstore](https://bluewallet.io/uploads/play-store-badge-blue.svg)](https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet)
Website: [bluewallet.io](http://bluewallet.io)

View file

@ -0,0 +1 @@
export default from '@react-native-async-storage/async-storage/jest/async-storage-mock'

View file

@ -1 +0,0 @@
export default from '@react-native-community/async-storage/jest/async-storage-mock'

View file

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

View file

@ -14,6 +14,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">

View file

@ -52,7 +52,7 @@ subprojects {
afterEvaluate {project ->
if (project.hasProperty("android")) {
android {
compileSdkVersion 28
compileSdkVersion 29
buildToolsVersion '28.0.0'
}
}

View file

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

View file

@ -1,4 +1,4 @@
import AsyncStorage from '@react-native-community/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
import { AppStorage, LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../class';
import DefaultPreference from 'react-native-default-preference';
@ -26,7 +26,8 @@ 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;
@ -77,7 +78,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;
}
@ -459,10 +460,78 @@ module.exports.waitTillConnected = async function () {
});
};
// Returns the value at a given percentile in a sorted numeric array.
// "Linear interpolation between closest ranks" method
function percentile(arr, p) {
if (arr.length === 0) return 0;
if (typeof p !== 'number') throw new TypeError('p must be a number');
if (p <= 0) return arr[0];
if (p >= 1) return arr[arr.length - 1];
const index = (arr.length - 1) * p;
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= arr.length) return arr[lower];
return arr[lower] * (1 - weight) + arr[upper] * weight;
}
/**
* The histogram is an array of [fee, vsize] pairs, where vsizen is the cumulative virtual size of mempool transactions
* with a fee rate in the interval [feen-1, feen], and feen-1 > feen.
*
* @param numberOfBlocks {Number}
* @param feeHistorgram {Array}
* @returns {number}
*/
module.exports.calcEstimateFeeFromFeeHistorgam = function (numberOfBlocks, feeHistorgram) {
// first, transforming histogram:
let totalVsize = 0;
const histogramToUse = [];
for (const h of feeHistorgram) {
let [fee, vsize] = h;
let timeToStop = false;
if (totalVsize + vsize >= 1000000 * numberOfBlocks) {
vsize = 1000000 * numberOfBlocks - totalVsize; // only the difference between current summarized sige to tip of the block
timeToStop = true;
}
histogramToUse.push({ fee, vsize });
totalVsize += vsize;
if (timeToStop) break;
}
// now we have histogram of precisely size for numberOfBlocks.
// lets spread it into flat array so its easier to calculate percentile:
let histogramFlat = [];
for (const hh of histogramToUse) {
histogramFlat = histogramFlat.concat(Array(Math.round(hh.vsize / 25000)).fill(hh.fee));
// division is needed so resulting flat array is not too huge
}
histogramFlat = histogramFlat.sort(function (a, b) {
return a - b;
});
return Math.round(percentile(histogramFlat, 0.5) || 1);
};
module.exports.estimateFees = async function () {
const fast = await module.exports.estimateFee(1);
const medium = await module.exports.estimateFee(18);
const slow = await module.exports.estimateFee(144);
const histogram = await mainClient.mempool_getFeeHistogram();
// fetching what electrum (which uses bitcoin core) thinks about fees:
const _fast = await module.exports.estimateFee(1);
const _medium = await module.exports.estimateFee(18);
const _slow = await module.exports.estimateFee(144);
// calculating fast fees from mempool:
const fast = module.exports.calcEstimateFeeFromFeeHistorgam(1, histogram);
// recalculating medium and slow fees using bitcoincore estimations only like relative weights:
// (minimum 1 sat, just for any case)
const medium = Math.max(1, Math.round((fast * _medium) / _fast));
const slow = Math.max(1, Math.round((fast * _slow) / _fast));
return { fast, medium, slow };
};

View file

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

View file

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

5
blue_modules/Privacy.js Normal file
View file

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

View file

@ -1,4 +1,6 @@
function WidgetCommunication(props) {
WidgetCommunication.isBalanceDisplayAllowed = false;
WidgetCommunication.setBalanceDisplayAllowed = () => {};
return null;
}

View file

@ -2,15 +2,35 @@ import { useContext, useEffect } from 'react';
import { BlueStorageContext } from './storage-context';
import DefaultPreference from 'react-native-default-preference';
import RNWidgetCenter from 'react-native-widget-center';
const WidgetCommunicationAllWalletsSatoshiBalance = 'WidgetCommunicationAllWalletsSatoshiBalance';
const WidgetCommunicationAllWalletsLatestTransactionTime = 'WidgetCommunicationAllWalletsLatestTransactionTime';
import AsyncStorage from '@react-native-async-storage/async-storage';
function WidgetCommunication() {
WidgetCommunication.WidgetCommunicationAllWalletsSatoshiBalance = 'WidgetCommunicationAllWalletsSatoshiBalance';
WidgetCommunication.WidgetCommunicationAllWalletsLatestTransactionTime = 'WidgetCommunicationAllWalletsLatestTransactionTime';
WidgetCommunication.WidgetCommunicationDisplayBalanceAllowed = 'WidgetCommunicationDisplayBalanceAllowed';
WidgetCommunication.LatestTransactionIsUnconfirmed = 'WidgetCommunicationLatestTransactionIsUnconfirmed';
const { wallets, walletsInitialized, isStorageEncrypted } = useContext(BlueStorageContext);
WidgetCommunication.isBalanceDisplayAllowed = async () => {
try {
const displayBalance = JSON.parse(await AsyncStorage.getItem(WidgetCommunication.WidgetCommunicationDisplayBalanceAllowed));
if (displayBalance !== null) {
return displayBalance;
} else {
return true;
}
} catch (e) {
return true;
}
};
WidgetCommunication.setBalanceDisplayAllowed = async value => {
await AsyncStorage.setItem(WidgetCommunication.WidgetCommunicationDisplayBalanceAllowed, JSON.stringify(value));
setValues();
};
const allWalletsBalanceAndTransactionTime = async () => {
if (await isStorageEncrypted()) {
if ((await isStorageEncrypted()) || !(await WidgetCommunication.isBalanceDisplayAllowed())) {
return { allWalletsBalance: 0, latestTransactionTime: 0 };
} else {
let balance = 0;
@ -21,7 +41,11 @@ function WidgetCommunication() {
}
balance += wallet.getBalance();
if (wallet.getLatestTransactionTimeEpoch() > latestTransactionTime) {
latestTransactionTime = wallet.getLatestTransactionTimeEpoch();
if (wallet.getTransactions()[0].confirmations === 0) {
latestTransactionTime = WidgetCommunication.LatestTransactionIsUnconfirmed;
} else {
latestTransactionTime = wallet.getLatestTransactionTimeEpoch();
}
}
}
return { allWalletsBalance: balance, latestTransactionTime };
@ -30,8 +54,11 @@ function WidgetCommunication() {
const setValues = async () => {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
const { allWalletsBalance, latestTransactionTime } = await allWalletsBalanceAndTransactionTime();
await DefaultPreference.set(WidgetCommunicationAllWalletsSatoshiBalance, JSON.stringify(allWalletsBalance));
await DefaultPreference.set(WidgetCommunicationAllWalletsLatestTransactionTime, JSON.stringify(latestTransactionTime));
await DefaultPreference.set(WidgetCommunication.WidgetCommunicationAllWalletsSatoshiBalance, JSON.stringify(allWalletsBalance));
await DefaultPreference.set(
WidgetCommunication.WidgetCommunicationAllWalletsLatestTransactionTime,
JSON.stringify(latestTransactionTime),
);
RNWidgetCenter.reloadAllTimelines();
};

View file

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

View file

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

View file

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

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

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

View file

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

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

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

File diff suppressed because it is too large Load diff

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import Frisbee from 'frisbee';
import AsyncStorage from '@react-native-community/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { AppStorage } from '../class';
import { FiatServerResponse, FiatUnit } from '../models/fiatUnit';
import DefaultPreference from 'react-native-default-preference';

View file

@ -0,0 +1,7 @@
import { getSystemName } 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;

View file

@ -5,6 +5,10 @@ import Share from 'react-native-share';
import loc from '../loc';
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';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const writeFileAndExport = async function (filename, contents) {
@ -92,6 +96,56 @@ const _readPsbtFileIntoBase64 = async function (uri) {
}
};
const showImagePickerAndReadImage = () => {
return new Promise((resolve, reject) =>
launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
resolve(result);
} else {
reject(new Error(loc.send.qr_error_no_qrcode));
}
});
}
},
),
);
};
const takePhotoWithImagePickerAndReadPhoto = () => {
return new Promise((resolve, reject) =>
launchCamera(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
resolve(result);
} else {
reject(new Error(loc.send.qr_error_no_qrcode));
}
});
} else if (response.error) {
presentCameraNotAuthorizedAlert(response.error);
}
},
),
);
};
const showFilePickerAndReadFile = async function () {
try {
const res = await DocumentPicker.pick({
@ -138,6 +192,43 @@ const showFilePickerAndReadFile = async function () {
}
};
// Intended for macOS Catalina. Not for long press shortcut
const showActionSheet = async () => {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
let copyFromClipboardIndex;
const options = [loc._.cancel, loc.wallets.take_photo, loc.wallets.list_long_choose];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
copyFromClipboardIndex = options.length - 1;
}
options.push(loc.wallets.import_file);
const importFileButtonIndex = options.length - 1;
return new Promise(resolve =>
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, async buttonIndex => {
if (buttonIndex === 1) {
takePhotoWithImagePickerAndReadPhoto().then(resolve);
} else if (buttonIndex === 2) {
showImagePickerAndReadImage()
.then(resolve)
.catch(error => alert(error.message));
} else if (buttonIndex === copyFromClipboardIndex) {
const clipboard = await Clipboard.getString();
resolve(clipboard);
} else if (importFileButtonIndex) {
const { data } = await showFilePickerAndReadFile();
if (data) {
resolve(data);
}
}
}),
);
};
module.exports.writeFileAndExport = writeFileAndExport;
module.exports.openSignedTransaction = openSignedTransaction;
module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile;
module.exports.showImagePickerAndReadImage = showImagePickerAndReadImage;
module.exports.takePhotoWithImagePickerAndReadPhoto = takePhotoWithImagePickerAndReadPhoto;
module.exports.showActionSheet = showActionSheet;

View file

@ -1,9 +1,10 @@
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import { Alert, Platform } from 'react-native';
import Frisbee from 'frisbee';
import { getApplicationName, getVersion, getSystemName, getSystemVersion } from 'react-native-device-info';
import AsyncStorage from '@react-native-community/async-storage';
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
@ -54,32 +56,19 @@ function Notifications(props) {
//
// ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage)
if (Platform.OS === 'ios' && notification.foreground === true && notification.userInteraction === false) {
// iOS hack
// @see https://github.com/zo0r/react-native-push-notification/issues/1585
notification.userInteraction = true;
// also, on iOS app is not suspending/unsuspending when user taps a notification bubble,so we simulate it
// since its where we actually handle notifications:
setTimeout(() => props.onProcessNotifications(), 500);
}
const payload = Object.assign({}, notification, notification.data);
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
delete payload.data;
// ^^^ weird, but sometimes payload data is not in `data` but in root level
let notifications = [];
try {
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
notifications = JSON.parse(stringified);
if (!Array.isArray(notifications)) notifications = [];
const payload = Object.assign({}, notification, notification.data);
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
delete payload.data;
// ^^^ weird, but sometimes payload data is not in `data` but in root level
notifications.push(payload);
await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify(notifications));
} catch (_) {}
await Notifications.addNotification(payload);
// (required) Called when a remote is received or opened, or local notification is opened
notification.finish(PushNotificationIOS.FetchResult.NoData);
// if user is staring at the app when he receives the notification we process it instantly
// so app refetches related wallet
if (payload.foreground) props.onProcessNotifications();
},
// (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)
@ -133,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
@ -140,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;
}
@ -364,6 +354,18 @@ function Notifications(props) {
return notifications;
};
Notifications.addNotification = async function (notification) {
let notifications = [];
try {
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
notifications = JSON.parse(stringified);
if (!Array.isArray(notifications)) notifications = [];
} catch (_) {}
notifications.push(notification);
await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify(notifications));
};
const postTokenConfig = async function () {
const pushToken = await Notifications.getPushToken();
if (!pushToken || !pushToken.token || !pushToken.os) return;
@ -394,10 +396,24 @@ function Notifications(props) {
} catch (_) {}
};
Notifications.getDeliveredNotifications = () => {
return new Promise(resolve => {
PushNotification.getDeliveredNotifications(notifications => resolve(notifications));
});
};
Notifications.removeDeliveredNotifications = (identifiers = []) => {
PushNotification.removeDeliveredNotifications(identifiers);
};
Notifications.setApplicationIconBadgeNumber = function (badges) {
PushNotification.setApplicationIconBadgeNumber(badges);
};
Notifications.removeAllDeliveredNotifications = () => {
PushNotification.removeAllDeliveredNotifications();
};
// on app launch (load module):
(async () => {
// first, fetching to see if app uses custom GroundControl server, not the default one

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,71 @@
{
"_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": {
"coveralls": "^3.0.3",
"mocha": "^6.0.2",
"mochify": "^6.1.0",
"nyc": "^13.3.0",
"standard": "^12.0.1"
},
"files": [
"lib"
],
"homepage": "https://github.com/cryptocoinjs/scryptsy#readme",
"keywords": [
"crytpo",
"cryptography",
"scrypt",
"kdf",
"litecoin",
"dogecoin",
"bitcoin",
"bip38"
],
"license": "MIT",
"main": "lib/index.js",
"name": "scryptsy",
"repository": {
"url": "git+ssh://git@github.com/cryptocoinjs/scryptsy.git",
"type": "git"
},
"scripts": {
"browser-test": "mochify --wd -R spec",
"coverage": "nyc --check-coverage --statements 80 --branches 60 --functions 90 --lines 90 mocha",
"coveralls": "npm run-script coverage && coveralls < coverage/lcov.info",
"lint": "standard",
"test": "mocha --ui bdd",
"unit": "mocha"
},
"version": "2.1.0"
}

View file

@ -1,15 +1,35 @@
/* 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(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();
@ -21,6 +41,47 @@ 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);
};
const setPreferredFiatCurrency = () => {
getPreferredCurrency();
};
const getLanguage = async () => {
const item = await getLanguageAsyncStorage();
_setLanguage(item);
};
const setLanguage = () => {
getLanguage();
};
useEffect(() => {
getPreferredCurrency();
getLanguageAsyncStorage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const resetWallets = () => {
setWallets(BlueApp.getWallets());
};
@ -30,9 +91,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);
@ -45,6 +109,8 @@ export const BlueStorageProvider = ({ children }) => {
} catch (err) {
noErr = false;
console.warn(err);
} finally {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
}
if (noErr) await saveToDisk(); // caching
};
@ -53,7 +119,14 @@ export const BlueStorageProvider = ({ children }) => {
const index = wallets.findIndex(wallet => wallet.getID() === walletID);
let noErr = true;
try {
// await BlueElectrum.ping();
// 5sec debounce:
setWalletTransactionUpdateStatus(walletID);
if (+new Date() - _lastTimeTriedToRefetchWallet[walletID] < 5000) {
console.log('re-fetch wallet happens too fast; NOP');
return;
}
_lastTimeTriedToRefetchWallet[walletID] = +new Date();
await BlueElectrum.waitTillConnected();
const balanceStart = +new Date();
await fetchWalletBalances(index);
@ -66,6 +139,8 @@ export const BlueStorageProvider = ({ children }) => {
} catch (err) {
noErr = false;
console.warn(err);
} finally {
setWalletTransactionUpdateStatus(WalletTransactionsStatus.NONE);
}
if (noErr) await saveToDisk(); // caching
};
@ -95,8 +170,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;
@ -144,11 +217,19 @@ export const BlueStorageProvider = ({ children }) => {
setNewWalletAdded,
resetWallets,
getHodlHodlApiKey,
isDeleteWalletAfterUninstallEnabled,
decryptStorage,
setResetOnAppUninstallTo,
isPasswordInUse,
setIsAdancedModeEnabled,
setPreferredFiatCurrency,
preferredFiatCurrency,
setLanguage,
language,
isHandOffUseEnabled,
setIsHandOffUseEnabledAsyncStorage,
walletTransactionUpdateStatus,
setWalletTransactionUpdateStatus,
isDrawerListBlurred,
setIsDrawerListBlurred,
}}
>
{children}

View file

@ -1,5 +1,5 @@
/* global alert */
import AsyncStorage from '@react-native-community/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
import {
HDLegacyBreadwalletWallet,
@ -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');
@ -30,12 +30,13 @@ export class AppStorage {
static ELECTRUM_HOST = 'electrum_host';
static ELECTRUM_TCP_PORT = 'electrum_tcp_port';
static ELECTRUM_SSL_PORT = 'electrum_ssl_port';
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>} */
@ -75,16 +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;
@ -139,7 +130,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 = [];
@ -149,16 +139,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();
@ -296,6 +276,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);

View file

@ -62,7 +62,7 @@ function Biometric() {
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
if (isDeviceBiometricCapable) {
return new Promise(resolve => {
FingerprintScanner.authenticate({ description: 'Please confirm your identity.', fallbackEnabled: true })
FingerprintScanner.authenticate({ description: loc.settings.biom_conf_identity, fallbackEnabled: true })
.then(() => resolve(true))
.catch(() => resolve(false))
.finally(() => FingerprintScanner.release());
@ -87,8 +87,8 @@ function Biometric() {
const isAuthenticated = await PasscodeAuth.authenticate();
if (isAuthenticated) {
Alert.alert(
'Storage',
`All your wallets will be removed and your storage will be decrypted. Are you sure you want to proceed?`,
loc.settings.encrypt_tstorage,
loc.settings.biom_remove_decrypt,
[
{ text: loc._.cancel, style: 'cancel' },
{
@ -104,15 +104,15 @@ function Biometric() {
isDevicePasscodeSupported = undefined;
}
if (isDevicePasscodeSupported === false) {
alert('Your device does not have a passcode. In order to proceed, please configure a passcode in the Settings app.');
alert(loc.settings.biom_no_passcode);
}
};
Biometric.showKeychainWipeAlert = () => {
if (Platform.OS === 'ios') {
Alert.alert(
'Storage',
`You have attempted to enter your password 10 times. Would you like to reset your storage? This will remove all wallets and decrypt your storage.`,
loc.settings.encrypt_tstorage,
loc.settings.biom_10times,
[
{
text: loc._.cancel,

View file

@ -1,5 +1,5 @@
import { AppStorage, LightningCustodianWallet } from './';
import AsyncStorage from '@react-native-community/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RNFS from 'react-native-fs';
import url from 'url';
import { Chain } from '../models/bitcoinUnits';
@ -181,7 +181,6 @@ class DeeplinkSchemaMatch {
]);
} else {
const urlObject = url.parse(event.url, true); // eslint-disable-line node/no-deprecated-api
console.log('parsed', event.url, 'into', urlObject);
(async () => {
if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') {
switch (urlObject.host) {
@ -244,12 +243,56 @@ class DeeplinkSchemaMatch {
]);
break;
}
case 'setelectrumserver':
completionHandler([
'ElectrumSettings',
{
server: DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(event.url),
},
]);
break;
case 'setlndhuburl':
completionHandler([
'LightningSettings',
{
url: DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction(event.url),
},
]);
break;
}
}
})();
}
}
/**
* Extracts server from a deeplink like `bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As`
* returns FALSE if none found
*
* @param url {string}
* @return {string|boolean}
*/
static getServerFromSetElectrumServerAction(url) {
if (!url.startsWith('bluewallet:setelectrumserver') && !url.startsWith('setelectrumserver')) return false;
const splt = url.split('server=');
if (splt[1]) return decodeURIComponent(splt[1]);
return false;
}
/**
* Extracts url from a deeplink like `bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com`
* returns FALSE if none found
*
* @param url {string}
* @return {string|boolean}
*/
static getUrlFromSetLndhubUrlAction(url) {
if (!url.startsWith('bluewallet:setlndhuburl') && !url.startsWith('setlndhuburl')) return false;
const splt = url.split('url=');
if (splt[1]) return decodeURIComponent(splt[1]);
return false;
}
static isTXNFile(filePath) {
return (
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import AsyncStorage from '@react-native-community/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
const BlueApp = require('../BlueApp');
export default class OnAppLaunch {

View file

@ -1,23 +1,28 @@
import QuickActions from 'react-native-quick-actions';
import { Platform } from 'react-native';
import { formatBalance } from '../loc';
import AsyncStorage from '@react-native-community/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useContext, useEffect } from 'react';
import { BlueStorageContext } from '../blue_modules/storage-context';
function DeviceQuickActions() {
DeviceQuickActions.STORAGE_KEY = 'DeviceQuickActionsEnabled';
const { wallets, walletsInitialized, isStorageEncryted } = useContext(BlueStorageContext);
const { wallets, walletsInitialized, isStorageEncrypted } = useContext(BlueStorageContext);
useEffect(() => {
if (walletsInitialized) {
if (isStorageEncryted) {
QuickActions.clearShortcutItems();
} else {
setQuickActions();
}
isStorageEncrypted()
.then(value => {
if (value) {
QuickActions.clearShortcutItems();
} else {
setQuickActions();
}
})
.catch(() => QuickActions.clearShortcutItems());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallets, walletsInitialized, isStorageEncryted]);
}, [wallets, walletsInitialized]);
DeviceQuickActions.setEnabled = (enabled = true) => {
return AsyncStorage.setItem(DeviceQuickActions.STORAGE_KEY, JSON.stringify(enabled)).then(() => {

View file

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

View file

@ -10,6 +10,7 @@ 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 {
@ -23,6 +24,7 @@ export default class WalletGradient {
static multisigHdWallet = ['#1ce6eb', '#296fc5', '#3500A2'];
static defaultGradients = ['#c65afb', '#9053fe'];
static lightningCustodianWallet = ['#f1be07', '#f79056'];
static aezeedWallet = ['#550271', '#530140'];
static createWallet = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
@ -65,6 +67,9 @@ export default class WalletGradient {
case MultisigHDWallet.type:
gradient = WalletGradient.multisigHdWallet;
break;
case HDAezeedWallet.type:
gradient = WalletGradient.aezeedWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;
@ -119,6 +124,9 @@ export default class WalletGradient {
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;
case HDAezeedWallet.type:
gradient = WalletGradient.aezeedWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;

View file

@ -12,6 +12,7 @@ import {
SegwitBech32Wallet,
HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet,
HDAezeedWallet,
MultisigHDWallet,
} from '.';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
@ -19,6 +20,7 @@ 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');
@ -35,6 +37,7 @@ function WalletImport() {
* @private
*/
WalletImport._saveWallet = async (w, additionalProperties) => {
IdleTimerManager.setIdleTimerDisabled(false);
if (WalletImport.isWalletImported(w)) {
WalletImport.presentWalletAlreadyExistsAlert();
return;
@ -91,6 +94,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 +104,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)
@ -234,6 +239,28 @@ function WalletImport() {
}
} catch (_) {}
// 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 mnemonics = importText.split(':')[0];
return WalletImport.processImportText(mnemonics + ':' + password);
}
}
} catch (_) {}
const hd2 = new HDSegwitP2SHWallet();
hd2.setSecret(importText);
if (hd2.validateMnemonic()) {
@ -299,10 +326,10 @@ function WalletImport() {
// nope?
// TODO: try a raw private key
IdleTimerManager.setIdleTimerDisabled(false);
throw new Error('Could not recognize format');
};
IdleTimerManager.setIdleTimerDisabled(false);
return null;
}

View file

@ -695,7 +695,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);

View file

@ -0,0 +1,163 @@
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';
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;
}
allowBatchSend() {
return true;
}
allowSendMax() {
return true;
}
allowHodlHodlTrading() {
return true;
}
allowRBF() {
return true;
}
allowPayJoin() {
return true;
}
}

View file

@ -1,6 +1,7 @@
import { randomBytes } from '../rng';
import { AbstractWallet } from './abstract-wallet';
import { HDSegwitBech32Wallet } from '..';
import BigNumber from 'bignumber.js';
const bitcoin = require('bitcoinjs-lib');
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const coinSelectAccumulative = require('coinselect/accumulative');
@ -161,12 +162,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. *
@ -276,8 +337,8 @@ export class LegacyWallet extends AbstractWallet {
if (!changeAddress) throw new Error('No change address provided');
let algo = coinSelectAccumulative;
if (targets.length === 1 && targets[0] && !targets[0].value) {
// we want to send MAX
// if targets has output without a value, we want send MAX to it
if (targets.some(i => !('value' in i))) {
algo = coinSelectSplit;
}
@ -285,7 +346,7 @@ export class LegacyWallet extends AbstractWallet {
// .inputs and .outputs will be undefined if no solution was found
if (!inputs || !outputs) {
throw new Error('Not enough balance. Try sending smaller amount');
throw new Error('Not enough balance. Try sending smaller amount or decrease the fee.');
}
return { inputs, outputs, fee };

View file

@ -3,8 +3,6 @@ import bip39 from 'bip39';
import b58 from 'bs58check';
import { decodeUR } from 'bc-ur';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const coinSelectAccumulative = require('coinselect/accumulative');
const coinSelectSplit = require('coinselect/split');
const HDNode = require('bip32');
const bitcoin = require('bitcoinjs-lib');
const createHash = require('create-hash');
@ -556,15 +554,17 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
// is it wallet descriptor?
// @see https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
// @see https://github.com/Fonta1n3/FullyNoded/blob/master/Docs/Wallets/Wallet-Export-Spec.md
if (!json && secret.indexOf('sortedmulti(')) {
// provided secret was NOT json but plain wallet descriptor text. lets mock json
json = { descriptor: secret, label: 'Multisig vault' };
}
if (secret.indexOf('sortedmulti(') !== -1 && json.descriptor) {
if (json.label) this.setLabel(json.label);
if (json.descriptor.startsWith('wsh(')) {
this.setNativeSegwit();
}
if (json.descriptor.startsWith('sh(')) {
this.setLegacy();
}
if (json.descriptor.startsWith('sh(wsh(')) {
} else if (json.descriptor.startsWith('sh(wsh(')) {
this.setWrappedSegwit();
} else if (json.descriptor.startsWith('sh(')) {
this.setLegacy();
}
@ -579,7 +579,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
if (m && m.length === 3) {
let hexFingerprint = m[1].split('/')[0];
if (hexFingerprint.length === 8) {
hexFingerprint = Buffer.from(hexFingerprint, 'hex').reverse().toString('hex');
hexFingerprint = Buffer.from(hexFingerprint, 'hex').toString('hex');
}
const path = 'm/' + m[1].split('/').slice(1).join('/').replace(/[h]/g, "'");
@ -606,6 +606,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
this.setLegacy();
break;
case MultisigHDWallet.FORMAT_P2SH_P2WSH:
case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT:
this.setWrappedSegwit();
break;
default:
@ -809,22 +810,9 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
if (targets.length === 0) throw new Error('No destination provided');
if (this.howManySignaturesCanWeMake() === 0) skipSigning = true;
if (!changeAddress) throw new Error('No change address provided');
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress);
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
let algo = coinSelectAccumulative;
if (targets.length === 1 && targets[0] && !targets[0].value) {
// we want to send MAX
algo = coinSelectSplit;
}
const { inputs, outputs, fee } = algo(utxos, targets, feeRate);
// .inputs and .outputs will be undefined if no solution was found
if (!inputs || !outputs) {
throw new Error('Not enough balance. Try sending smaller amount');
}
let psbt = new bitcoin.Psbt();
let c = 0;
@ -855,9 +843,14 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
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
if (signaturesMade >= this.getM()) {
// dont sign more than we need, otherwise there will be "Too many signatures" error
continue;
}
let seed = bip39.mnemonicToSeed(cosigner);
if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) {
seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner);
@ -865,6 +858,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
const hdRoot = bitcoin.bip32.fromSeed(seed);
psbt.signInputHD(cc, hdRoot);
signaturesMade++;
}
}
}
@ -923,13 +917,13 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
await super.fetchUtxo();
// now we need to fetch txhash for each input as required by PSBT
const txhexes = await BlueElectrum.multiGetTransactionByTxid(
this.getUtxo().map(x => x.txid),
this.getUtxo(true).map(x => x.txid),
50,
false,
);
const newUtxos = [];
for (const u of this.getUtxo()) {
for (const u of this.getUtxo(true)) {
if (txhexes[u.txid]) u.txhex = txhexes[u.txid];
newUtxos.push(u);
}
@ -998,7 +992,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
*/
cosignPsbt(psbt) {
for (let cc = 0; cc < psbt.inputCount; cc++) {
for (const cosigner of this._cosigners) {
for (const [cosignerIndex, cosigner] of this._cosigners.entries()) {
if (!MultisigHDWallet.isXpubString(cosigner)) {
// ok this is a mnemonic, lets try to sign
const seed = bip39.mnemonicToSeed(cosigner);
@ -1006,6 +1000,34 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
try {
psbt.signInputHD(cc, hdRoot);
} catch (_) {} // protects agains duplicate cosignings
if (!psbt.inputHasHDKey(cc, hdRoot)) {
// failed signing as HD. probably bitcoinjs-lib could not match provided hdRoot's
// fingerprint (or path?) to the ones in psbt, which is the case of stupid Electrum desktop which can
// put bullshit paths and fingerprints in created psbt.
// lets try to find correct priv key and sign manually.
for (const derivation of psbt.data.inputs[cc].bip32Derivation || []) {
// okay, here we assume that fingerprint is irrelevant, but ending of the path is somewhat correct and
// correctly points to `/internal/index`, so we extract pubkey from our stored mnemonics+path and
// match it to the one provided in PSBT's input, and if we have a match - we are in luck! we can sign
// with this private key.
const seed = bip39.mnemonicToSeed(cosigner);
const root = HDNode.fromSeed(seed);
const splt = derivation.path.split('/');
const internal = +splt[splt.length - 2];
const index = +splt[splt.length - 1];
const path = this.getCustomDerivationPathForCosigner(cosignerIndex + 1) + `/${internal ? 1 : 0}/${index}`;
// ^^^ we assume that counterparty has Zpub for specified derivation path
const child = root.derivePath(path);
if (psbt.inputHasPubkey(cc, child.publicKey)) {
const keyPair = bitcoin.ECPair.fromPrivateKey(child.privateKey);
try {
psbt.signInput(cc, keyPair);
} catch (_) {}
}
}
}
}
}
}
@ -1085,4 +1107,20 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
if (fp.length !== 8) return false;
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)
* @see https://github.com/satoshilabs/slips/blob/master/slip-0132.md
*
* @param xpub
* @return {boolean}
*/
static isXpubForMultisig(xpub) {
return ['xpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4));
}
}

View file

@ -19,6 +19,7 @@ export class PlaceholderWallet extends AbstractWallet {
}
getLabel() {
// no longer used in wallets carousel
return this.getIsFailure() ? 'Wallet Import' : 'Importing Wallet...';
}

View file

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

View file

@ -1,4 +1,4 @@
import React, { useRef, useCallback, useState, useImperativeHandle, forwardRef } from 'react';
import React, { useRef, useCallback, useState, useImperativeHandle, forwardRef, useContext } from 'react';
import PropTypes from 'prop-types';
import {
ActivityIndicator,
@ -21,6 +21,8 @@ import { LightningCustodianWallet, MultisigHDWallet, PlaceholderWallet } from '.
import WalletGradient from '../class/wallet-gradient';
import { BluePrivateBalance } from '../BlueComponents';
import { BlueStorageContext } from '../blue_modules/storage-context';
const nStyles = StyleSheet.create({
root: {
marginVertical: 17,
@ -125,6 +127,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 };
@ -173,7 +176,7 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
<Image source={require('../img/btc-shape.png')} style={iStyles.image} />
<Text style={iStyles.br} />
<Text numberOfLines={1} style={[iStyles.label, { color: colors.inverseForegroundColor }]}>
{item.getLabel()}
{item.getIsFailure() ? loc.wallets.import_placeholder_fail : loc.wallets.import_placeholder_inprogress}
</Text>
{item.getIsFailure() ? (
<Text numberOfLines={0} style={[iStyles.importError, { color: colors.inverseForegroundColor }]}>
@ -201,6 +204,15 @@ 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());
return (
<Animated.View
style={[iStyles.root, { opacity, transform: [{ scale: scaleValue }] }]}
@ -236,10 +248,9 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
<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>
@ -268,6 +279,7 @@ const cStyles = StyleSheet.create({
const WalletsCarousel = forwardRef((props, ref) => {
const carouselRef = useRef();
const [loading, setLoading] = useState(true);
const { preferredFiatCurrency, language } = useContext(BlueStorageContext);
const renderItem = useCallback(
({ item, index }) => (
<WalletCarouselItem
@ -278,7 +290,8 @@ const WalletsCarousel = forwardRef((props, ref) => {
onPress={props.onPress}
/>
),
[props.vertical, props.selectedWallet, props.handleLongPress, props.onPress],
// eslint-disable-next-line react-hooks/exhaustive-deps
[props.vertical, props.selectedWallet, props.handleLongPress, props.onPress, preferredFiatCurrency, language],
);
useImperativeHandle(ref, () => ({

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

@ -0,0 +1,15 @@
import React, { useContext } from 'react';
import Handoff from 'react-native-handoff';
import { BlueStorageContext } from '../blue_modules/storage-context';
import PropTypes from 'prop-types';
const HandoffComponent = props => {
const { isHandOffUseEnabled } = useContext(BlueStorageContext);
return isHandOffUseEnabled && props && props.url ? <Handoff {...props} /> : null;
};
export default HandoffComponent;
HandoffComponent.propTypes = {
url: PropTypes.string,
};

5
components/handoff.js Normal file
View file

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

View file

@ -0,0 +1,92 @@
import React from 'react';
import { Image, Keyboard, TouchableOpacity, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
button: {
minWidth: 40,
height: 40,
justifyContent: 'center',
paddingHorizontal: 14,
},
});
const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, formatter) => {
return theme => ({ navigation, route }) => {
let headerRight = null;
if (closeButton) {
const handleClose = closeButtonFunc
? () => closeButtonFunc({ navigation, route })
: () => {
Keyboard.dismiss();
navigation.goBack(null);
};
headerRight = () => (
<TouchableOpacity style={styles.button} onPress={handleClose}>
<Image source={theme.closeImage} />
</TouchableOpacity>
);
}
let options = {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: theme.colors.foregroundColor,
},
headerRight,
headerBackTitleVisible: false,
headerTintColor: theme.colors.foregroundColor,
...opts,
};
if (formatter) {
options = formatter(options, { theme, navigation, route });
}
return options;
};
};
export default navigationStyle;
export const navigationStyleTx = (opts, formatter) => {
return theme => ({ navigation, route }) => {
let options = {
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: theme.colors.foregroundColor,
},
// headerBackTitle: null,
headerBackTitleVisible: false,
headerTintColor: theme.colors.foregroundColor,
headerLeft: () => (
<TouchableOpacity
style={styles.button}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
<Image source={theme.closeImage} />
</TouchableOpacity>
),
...opts,
};
if (formatter) {
options = formatter(options, { theme, navigation, route });
}
return options;
};
};

View file

@ -20,6 +20,8 @@
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, ); }; };
6D2AA7FA2568B8750090B089 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; };
6D2AA7FB2568B8750090B089 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; };
6D2AA7FC2568B8750090B089 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; };
@ -28,6 +30,7 @@
6D2AA8092568B8F40090B089 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; };
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 */; };
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 */; };
@ -118,6 +121,13 @@
remoteGlobalIDString = 3271B0A8236E2E0700DA766F;
remoteInfo = TodayExtension;
};
6D2A6466258BA92D0092292B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6D2A6460258BA92C0092292B;
remoteInfo = Stickers;
};
6D6CA4C1255872E7009312A5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
@ -176,6 +186,7 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */,
6D9946692555A661000E52E8 /* MarketWidgetExtension.appex in Embed App Extensions */,
6D6CA4C3255872E7009312A5 /* PriceWidgetExtension.appex in Embed App Extensions */,
6D9A2E0D254BA348007B5B82 /* WalletInformationAndMarketWidgetExtension.appex in Embed App Extensions */,
@ -298,8 +309,13 @@
6D294A9C24D512770039E22B /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/MainInterface.strings; sourceTree = "<group>"; };
6D294A9D24D5127F0039E22B /* xh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = xh; path = xh.lproj/Interface.strings; sourceTree = "<group>"; };
6D294A9E24D512800039E22B /* xh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = xh; path = xh.lproj/MainInterface.strings; sourceTree = "<group>"; };
6D2A6461258BA92C0092292B /* Stickers.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Stickers.appex; sourceTree = BUILT_PRODUCTS_DIR; };
6D2A6463258BA92D0092292B /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = "<group>"; };
6D2A6465258BA92D0092292B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6D2AA7F92568B8750090B089 /* FiatUnits.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = FiatUnits.plist; sourceTree = "<group>"; };
6D2AA8072568B8F40090B089 /* FiatUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiatUnit.swift; sourceTree = "<group>"; };
6D32C5C42596CE2F008C077C /* EventEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EventEmitter.h; sourceTree = "<group>"; };
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; };
6D641F17255226DA003792DF /* MarketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketView.swift; sourceTree = "<group>"; };
@ -461,6 +477,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
8889F8F93C39BB72C97DD77E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B40D4E39225841ED00428FCC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -503,6 +526,8 @@
32B5A3292334450100F8D608 /* Bridge.swift */,
32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */,
6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */,
6D32C5C42596CE2F008C077C /* EventEmitter.h */,
6D32C5C52596CE3A008C077C /* EventEmitter.m */,
);
name = BlueWallet;
sourceTree = "<group>";
@ -566,6 +591,15 @@
name = Resources;
sourceTree = "<group>";
};
6D2A6462258BA92C0092292B /* Stickers */ = {
isa = PBXGroup;
children = (
6D2A6463258BA92D0092292B /* Stickers.xcassets */,
6D2A6465258BA92D0092292B /* Info.plist */,
);
path = Stickers;
sourceTree = "<group>";
};
6D2AA8062568B8E50090B089 /* Fiat */ = {
isa = PBXGroup;
children = (
@ -664,6 +698,7 @@
B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */,
3271B0AC236E2E0700DA766F /* TodayExtension */,
6DEB4B18254FB7D700E9F9AA /* Widgets */,
6D2A6462258BA92C0092292B /* Stickers */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
B40FE50A21FAD228005D5578 /* Recovered References */,
@ -686,6 +721,7 @@
6DEB4AAD254FB59B00E9F9AA /* WalletInformationWidgetExtension.appex */,
6D99465E2555A660000E52E8 /* MarketWidgetExtension.appex */,
6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */,
6D2A6461258BA92C0092292B /* Stickers.appex */,
);
name = Products;
sourceTree = "<group>";
@ -809,6 +845,7 @@
6D9946452555A583000E52E8 /* PBXTargetDependency */,
6D9946682555A661000E52E8 /* PBXTargetDependency */,
6D6CA4C2255872E7009312A5 /* PBXTargetDependency */,
6D2A6467258BA92D0092292B /* PBXTargetDependency */,
);
name = BlueWallet;
productName = "Hello World";
@ -832,6 +869,22 @@
productReference = 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */;
productType = "com.apple.product-type.app-extension";
};
6D2A6460258BA92C0092292B /* Stickers */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6D2A646B258BA92D0092292B /* Build configuration list for PBXNativeTarget "Stickers" */;
buildPhases = (
6D2A645F258BA92C0092292B /* Resources */,
8889F8F93C39BB72C97DD77E /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Stickers;
productName = Stickers;
productReference = 6D2A6461258BA92C0092292B /* Stickers.appex */;
productType = "com.apple.product-type.app-extension.messages-sticker-pack";
};
6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6D6CA4C6255872E7009312A5 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */;
@ -963,6 +1016,9 @@
CreatedOnToolsVersion = 11.2;
LastSwiftMigration = 1130;
};
6D2A6460258BA92C0092292B = {
CreatedOnToolsVersion = 12.1;
};
6D6CA4B7255872E3009312A5 = {
CreatedOnToolsVersion = 12.1;
};
@ -1040,6 +1096,7 @@
6DEB4AAC254FB59B00E9F9AA /* WalletInformationWidgetExtension */,
6D9A2E01254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension */,
6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */,
6D2A6460258BA92C0092292B /* Stickers */,
);
};
/* End PBXProject section */
@ -1062,6 +1119,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
6D2A645F258BA92C0092292B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6D6CA4B6255872E3009312A5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -1217,12 +1282,45 @@
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-input-files.xcfilelist",
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources.sh",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-output-files.xcfilelist",
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -1259,6 +1357,7 @@
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
32B5A32A2334450100F8D608 /* Bridge.swift in Sources */,
);
@ -1376,6 +1475,11 @@
target = 3271B0A8236E2E0700DA766F /* TodayExtension */;
targetProxy = 3271B0B3236E2E0700DA766F /* PBXContainerItemProxy */;
};
6D2A6467258BA92D0092292B /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6D2A6460258BA92C0092292B /* Stickers */;
targetProxy = 6D2A6466258BA92D0092292B /* PBXContainerItemProxy */;
};
6D6CA4C2255872E7009312A5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilter = ios;
@ -1493,7 +1597,7 @@
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = A7W54YZ4WU;
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
@ -1513,7 +1617,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1542,7 +1646,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEVELOPMENT_TEAM = A7W54YZ4WU;
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
HEADER_SEARCH_PATHS = "$(inherited)";
@ -1556,7 +1660,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1586,7 +1690,7 @@
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1597,7 +1701,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
@ -1625,7 +1729,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1636,7 +1740,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price";
@ -1649,6 +1753,66 @@
};
name = Release;
};
6D2A6469258BA92D0092292B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
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 = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
6D2A646A258BA92D0092292B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "iMessage App Icon";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
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[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = Stickers/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = Stickers;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
6D6CA4C4255872E7009312A5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1664,7 +1828,7 @@
CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1675,7 +1839,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
@ -1706,7 +1870,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1717,7 +1881,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1746,7 +1910,7 @@
CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1757,7 +1921,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
@ -1789,7 +1953,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1800,7 +1964,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1829,7 +1993,7 @@
CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1841,7 +2005,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
@ -1873,7 +2037,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1885,7 +2049,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1915,7 +2079,7 @@
CODE_SIGN_ENTITLEMENTS = WalletInformationWidgetExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1926,7 +2090,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
@ -1958,7 +2122,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1969,7 +2133,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -2101,7 +2265,7 @@
CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -2111,7 +2275,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
@ -2141,7 +2305,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
@ -2151,7 +2315,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
PRODUCT_NAME = "${TARGET_NAME}";
@ -2179,13 +2343,13 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
@ -2215,13 +2379,13 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 302;
CURRENT_PROJECT_VERSION = 310;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 5.7.0;
MARKETING_VERSION = 6.0.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -2257,6 +2421,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6D2A646B258BA92D0092292B /* Build configuration list for PBXNativeTarget "Stickers" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6D2A6469258BA92D0092292B /* Debug */,
6D2A646A258BA92D0092292B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6D6CA4C6255872E7009312A5 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View file

@ -6,15 +6,15 @@
*/
#import "AppDelegate.h"
#import <React/RCTLinkingManager.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "RNQuickActionManager.h"
#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>
#import "EventEmitter.h"
#if !TARGET_OS_MACCATALYST
#ifdef FB_SONARKIT_ENABLED
#if DEBUG
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
@ -38,10 +38,11 @@ static void InitializeFlipper(UIApplication *application) {
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if !TARGET_OS_MACCATALYST
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
#endif
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"BlueWallet"
@ -52,7 +53,6 @@ static void InitializeFlipper(UIApplication *application) {
} else {
rootView.backgroundColor = [UIColor clearColor];
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
@ -61,11 +61,9 @@ static void InitializeFlipper(UIApplication *application) {
UIView* launchScreenView = [[UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil] instantiateInitialViewController].view;
launchScreenView.frame = self.window.bounds;
rootView.loadingView = launchScreenView;
// Define UNUserNotificationCenter
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
return YES;
}
@ -91,48 +89,39 @@ static void InitializeFlipper(UIApplication *application) {
[RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler];
}
//Called when a notification is delivered to a foreground app.
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
NSDictionary *userInfo = notification.request.content.userInfo;
[EventEmitter.sharedInstance sendNotification:userInfo];
completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);
}
// Required to register for notifications
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[RNCPushNotificationIOS didRegisterUserNotificationSettings:notificationSettings];
}
// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
[RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// IOS 10+ Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
@ -141,19 +130,42 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
completionHandler();
}
// IOS 4-10 Required for the localNotification event.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[RNCPushNotificationIOS didReceiveLocalNotification:notification];
}
- (void)openPreferences {
[EventEmitter.sharedInstance openSettings];
}
- (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder {
[super buildMenuWithBuilder:builder];
[builder removeMenuForIdentifier:UIMenuServices];
[builder removeMenuForIdentifier:UIMenuFormat];
[builder removeMenuForIdentifier:UIMenuToolbar];
[builder removeMenuForIdentifier:UIMenuFile];
UIKeyCommand *preferencesCommand = [UIKeyCommand keyCommandWithInput:@"," modifierFlags:UIKeyModifierCommand action:@selector(openPreferences)];
[preferencesCommand setTitle:@"Preferences..."];
UIMenu *preferences = [UIMenu menuWithTitle:@"Preferences..." image:nil identifier:@"openPreferences" options:UIMenuOptionsDisplayInline children:@[preferencesCommand]];
[builder insertSiblingMenu:preferences afterMenuForIdentifier:UIMenuAbout];
}
-(void)showHelp:(id)sender {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://help.bluewallet.io"] options:@{} completionHandler:nil];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == @selector(showHelp:)) {
return true;
} else {
return [super canPerformAction:action withSender:sender];
}
}
@end

View file

@ -5,13 +5,17 @@
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<array>
<string>iCloud.io.bluewallet.bluewallet</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array/>
<array>
<string>iCloud.io.bluewallet.bluewallet</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
<key>com.apple.security.app-sandbox</key>

View file

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.bluewallet.bluewallet.fetchTxsForWallet</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
@ -116,6 +120,8 @@
<string>public.app-category.finance</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
<string>http</string>
<string>bankid</string>
<string>swish</string>
</array>
@ -143,7 +149,7 @@
<key>NSCalendarsUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSCameraUsageDescription</key>
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
<string>In order to quickly scan the recipient&apos;s address, we need your permission to use the camera to scan their QR Code.</string>
<key>NSFaceIDUsageDescription</key>
<string>In order to use FaceID please confirm your permission.</string>
<key>NSLocationAlwaysUsageDescription</key>
@ -184,6 +190,8 @@
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>

18
ios/EventEmitter.h Normal file
View file

@ -0,0 +1,18 @@
//
// EventEmitter.h
// BlueWallet
//
// Created by Marcos Rodriguez on 12/25/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface EventEmitter : RCTEventEmitter <RCTBridgeModule>
+ (EventEmitter *)sharedInstance;
- (void)sendNotification:(NSDictionary *)userInfo;
- (void)openSettings;
@end

47
ios/EventEmitter.m Normal file
View file

@ -0,0 +1,47 @@
//
// EventEmitter.m
// BlueWallet
//
// Created by Marcos Rodriguez on 12/25/20.
// Copyright © 2020 BlueWallet. All rights reserved.
//
#import "EventEmitter.h"
static EventEmitter *sharedInstance;
@implementation EventEmitter
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup {
return YES;
}
+ (EventEmitter *)sharedInstance {
return sharedInstance;
}
- (instancetype)init {
sharedInstance = [super init];
return sharedInstance;
}
- (NSArray<NSString *> *)supportedEvents {
return @[@"onNotificationReceived",@"openSettings"];
}
- (void)sendNotification:(NSDictionary *)userInfo
{
[sharedInstance sendEventWithName:@"onNotificationReceived" body:userInfo];
}
- (void)openSettings
{
[sharedInstance sendEventWithName:@"openSettings" body:nil];
}
@end

View file

@ -263,10 +263,10 @@ PODS:
- React
- react-native-fingerprint-scanner (6.0.0):
- React
- react-native-geolocation (2.0.2):
- React
- react-native-image-picker (3.0.1):
- React
- react-native-idle-timer (2.1.6):
- React-Core
- react-native-image-picker (3.1.4):
- React-Core
- react-native-is-catalyst (1.0.0):
- React
- react-native-randombytes (3.5.3):
@ -342,20 +342,18 @@ PODS:
- React-Core (= 0.63.3)
- React-cxxreact (= 0.63.3)
- React-jsi (= 0.63.3)
- ReactNativePrivacySnapshot (1.0.0):
- React
- RealmJS (6.1.4):
- GCDWebServer
- React
- RemobileReactNativeQrcodeLocalImage (1.0.4):
- React
- RNCAsyncStorage (1.12.1):
- RNCAsyncStorage (1.13.3):
- React-Core
- RNCClipboard (1.5.0):
- RNCClipboard (1.5.1):
- React-Core
- RNCMaskedView (0.1.10):
- React
- RNCPushNotificationIOS (1.7.1):
- RNCPushNotificationIOS (1.8.0):
- React-Core
- RNDefaultPreference (1.4.3):
- React
@ -371,6 +369,8 @@ PODS:
- React-Core
- RNLocalize (1.4.2):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React
- RNQuickAction (0.3.13):
- React
- RNRate (1.2.4):
@ -390,7 +390,7 @@ PODS:
- React-Core
- RNSVG (12.1.0):
- React
- RNVectorIcons (6.6.0):
- RNVectorIcons (7.1.0):
- React
- RNWatch (1.0.3):
- React
@ -450,7 +450,7 @@ DEPENDENCIES:
- react-native-camera (from `../node_modules/react-native-camera`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
- "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)"
- react-native-idle-timer (from `../node_modules/react-native-idle-timer`)
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-is-catalyst (from `../node_modules/react-native-is-catalyst`)
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
@ -469,10 +469,9 @@ DEPENDENCIES:
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- ReactNativePrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
- RealmJS (from `../node_modules/realm`)
- "RemobileReactNativeQrcodeLocalImage (from `../node_modules/@remobile/react-native-qrcode-local-image`)"
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
- "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)"
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
@ -483,6 +482,7 @@ DEPENDENCIES:
- RNHandoff (from `../node_modules/react-native-handoff`)
- RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNPrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
- RNRate (from `../node_modules/react-native-rate`)
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
@ -564,8 +564,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker"
react-native-fingerprint-scanner:
:path: "../node_modules/react-native-fingerprint-scanner"
react-native-geolocation:
:path: "../node_modules/@react-native-community/geolocation"
react-native-idle-timer:
:path: "../node_modules/react-native-idle-timer"
react-native-image-picker:
:path: "../node_modules/react-native-image-picker"
react-native-is-catalyst:
@ -602,14 +602,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/Vibration"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
ReactNativePrivacySnapshot:
:path: "../node_modules/react-native-privacy-snapshot"
RealmJS:
:path: "../node_modules/realm"
RemobileReactNativeQrcodeLocalImage:
:path: "../node_modules/@remobile/react-native-qrcode-local-image"
RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage"
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
:path: "../node_modules/@react-native-community/clipboard"
RNCMaskedView:
@ -630,6 +628,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-inappbrowser-reborn"
RNLocalize:
:path: "../node_modules/react-native-localize"
RNPrivacySnapshot:
:path: "../node_modules/react-native-privacy-snapshot"
RNQuickAction:
:path: "../node_modules/react-native-quick-actions"
RNRate:
@ -702,8 +702,8 @@ SPEC CHECKSUMS:
react-native-camera: 5c1fbfecf63b802b8ca4a71c60d30a71550fb348
react-native-document-picker: c5752781fbc0c126c627c1549b037c139444a4cf
react-native-fingerprint-scanner: c68136ca57e3704d7bdf5faa554ea535ce15b1d0
react-native-geolocation: cbd9d6bd06bac411eed2671810f454d4908484a8
react-native-image-picker: 4efc5b7f3a780975bcc677335eb670e52d203424
react-native-idle-timer: 97b8283237d45146a7a5c25bdebe9e1e85f3687b
react-native-image-picker: 248afb60a0c5a24153cb7c7483b257f30541323b
react-native-is-catalyst: 52ee70e0123c82419dd4ce47dc4cc94b22467512
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6
@ -721,13 +721,12 @@ SPEC CHECKSUMS:
React-RCTText: 65a6de06a7389098ce24340d1d3556015c38f746
React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454
ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3
ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015
RealmJS: 899b4839a8bee46e248bc277995ad58da855e41f
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
RNCClipboard: 8f9f12fabf3c06e976f19f87a62c89e28dfedfca
RNCAsyncStorage: 32c7cbe1d43dff5f03f3d0b9e9d2c44d8ec91a3a
RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNCPushNotificationIOS: eaf01f848a0b872b194d31bcad94bb864299e01e
RNCPushNotificationIOS: 5b1cf9ad2aaa107ecb92d5d2d7005ba521b2b97a
RNDefaultPreference: 21816c0a6f61a2829ccc0cef034392e9b509ee5f
RNDeviceInfo: 57bb2806fb7bd982a1434e9f0b4e6a6ab1f6702e
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
@ -735,6 +734,7 @@ SPEC CHECKSUMS:
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNInAppBrowser: 3733c1aa6699983a1c9b4963e85d5e5a48ad297e
RNLocalize: 4071198b59b461f3b74eebc5fee8c50f13e39e79
RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: 2b31dad120cd1b78e33c6034808561c386a3dddf
RNReactNativeHapticFeedback: 653a8c126a0f5e88ce15ffe280b3ff37e1fbb285
@ -744,7 +744,7 @@ SPEC CHECKSUMS:
RNSentry: 1adaa43b01c6a3ab5091d4d1add66b7c58558898
RNShare: 7a7277f3c313652422d9de072ac50714dff5e8a4
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59
RNWatch: e4c5d19506c94506860032fb68aedd5991beb985
Sentry: 8fa58a051237554f22507fb483b9a1de0171a2dc
SwiftSocket: c8d482e867ae4d3eb4c769e9382e123c1f1f833b
@ -754,4 +754,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: b3a4c68ed1fc7dace2357d96ad07c3e104e0afe2
COCOAPODS: 1.9.3
COCOAPODS: 1.10.0

31
ios/Stickers/Info.plist Normal file
View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>BlueWallet</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.message-payload-provider</string>
<key>NSExtensionPrincipalClass</key>
<string>StickerBrowserViewController</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"filename" : "bitcoin@3x.png"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"filename" : "bitcoin-logo-png-transparent.png"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"filename" : "marketing-1024x1024.png"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

View file

@ -0,0 +1,29 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"grid-size" : "regular"
},
"stickers" : [
{
"filename" : "Bitcoin.sticker"
},
{
"filename" : "BlueWallet.sticker"
},
{
"filename" : "bluebeast.sticker"
},
{
"filename" : "Lightning.sticker"
},
{
"filename" : "Vault.sticker"
},
{
"filename" : "Bitcoin (Blue).sticker"
}
]
}

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"filename" : "lightning@3x.png"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"filename" : "vault_main@3x.png"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"filename" : "bluebeast.png"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,91 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"images" : [
{
"size" : "29x29",
"scale" : "2x",
"idiom" : "iphone",
"filename" : "icon-29x29@2x.png"
},
{
"scale" : "3x",
"idiom" : "iphone",
"size" : "29x29",
"filename" : "icon-29x29@3x.png"
},
{
"filename" : "icon-60x45@2x.png",
"scale" : "2x",
"idiom" : "iphone",
"size" : "60x45"
},
{
"scale" : "3x",
"idiom" : "iphone",
"filename" : "icon-60x45@3x.png",
"size" : "60x45"
},
{
"scale" : "2x",
"idiom" : "ipad",
"filename" : "icon-29x29~ipad@2x.png",
"size" : "29x29"
},
{
"size" : "67x50",
"scale" : "2x",
"filename" : "icon-67x50~ipad@2x.png",
"idiom" : "ipad"
},
{
"size" : "74x55",
"idiom" : "ipad",
"filename" : "icon-74x55~ipad@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"filename" : "marketing-1024x1024.png",
"scale" : "1x",
"idiom" : "ios-marketing"
},
{
"filename" : "icon-27x20@2x.png",
"platform" : "ios",
"size" : "27x20",
"idiom" : "universal",
"scale" : "2x"
},
{
"scale" : "3x",
"size" : "27x20",
"platform" : "ios",
"filename" : "icon-27x20@3x.png",
"idiom" : "universal"
},
{
"size" : "32x24",
"idiom" : "universal",
"filename" : "icon-32x24@2x.png",
"platform" : "ios",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "icon-32x24@3x.png",
"scale" : "3x",
"size" : "32x24",
"platform" : "ios"
},
{
"scale" : "1x",
"filename" : "marketing-1024x768.png",
"platform" : "ios",
"size" : "1024x768",
"idiom" : "ios-marketing"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

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