Browse Source

Merge branch 'master' into feesheet

feesheet
Marcos Rodriguez 5 years ago
parent
commit
2b8894831d
  1. 110
      App.js
  2. 4
      BlueApp.js
  3. 710
      BlueComponents.js
  4. 7
      BlueElectrum.js
  5. 1
      CODE_OF_CONDUCT.md
  6. 61
      MainBottomTabs.js
  7. 25
      README.md
  8. 27
      RELEASE.md
  9. 5
      SECURITY.md
  10. 98
      UnlockWith.js
  11. 12
      WatchConnectivity.android.js
  12. 94
      WatchConnectivity.ios.js
  13. 10
      analytics.js
  14. 17
      android/.project
  15. 6
      android/app/.classpath
  16. 23
      android/app/.project
  17. 2
      android/app/.settings/org.eclipse.buildship.core.prefs
  18. 4
      android/app/build.gradle
  19. 13
      android/app/src/main/AndroidManifest.xml
  20. 3
      android/app/src/main/assets/appcenter-config.json
  21. BIN
      android/app/src/main/res/drawable/quickactions.png
  22. 2
      android/app/src/main/res/values/strings.xml
  23. 5
      android/sentry.properties
  24. 45
      appcenter-post-build-get-pr-number.js
  25. 20
      appcenter-post-build.sh
  26. 19
      class/abstract-hd-wallet.js
  27. 30
      class/abstract-wallet.js
  28. 78
      class/app-storage.js
  29. 61
      class/biometrics.js
  30. 23
      class/hd-legacy-breadwallet-wallet.js
  31. 28
      class/hd-legacy-p2pkh-wallet.js
  32. 2
      class/hd-segwit-bech32-transaction.js
  33. 160
      class/hd-segwit-bech32-wallet.js
  34. 32
      class/hd-segwit-p2sh-wallet.js
  35. 6
      class/legacy-wallet.js
  36. 14
      class/lightning-custodian-wallet.js
  37. 45
      class/onAppLaunch.js
  38. 46
      class/quickActions.js
  39. 30
      class/segwit-bech-wallet.js
  40. 47
      class/segwit-p2sh-wallet.js
  41. 67
      class/watch-only-wallet.js
  42. 21
      currency.js
  43. 2
      edit-version-number.sh
  44. 1
      img/bluewalletsplash.json
  45. BIN
      img/close-white.png
  46. BIN
      img/close-white@2x.png
  47. BIN
      img/close-white@3x.png
  48. BIN
      img/faceid.png
  49. BIN
      img/fingerprint.png
  50. 62
      index.js
  51. 8
      ios/AppCenter-Config.plist
  52. 4
      ios/BlueWallet-Bridging-Header.h
  53. 330
      ios/BlueWallet.xcodeproj/project.pbxproj
  54. 22
      ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme
  55. 9
      ios/BlueWallet.xcworkspace/contents.xcworkspacedata
  56. 8
      ios/BlueWallet.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  57. 6
      ios/BlueWallet/AppDelegate.h
  58. 34
      ios/BlueWallet/AppDelegate.m
  59. 20
      ios/BlueWallet/Base.lproj/LaunchScreen.xib
  60. 10
      ios/BlueWallet/BlueWallet.entitlements
  61. 6
      ios/BlueWallet/Info.plist
  62. 5
      ios/BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements
  63. 56
      ios/BlueWalletWatch Extension/ComplicationController.swift
  64. 17
      ios/BlueWalletWatch Extension/Info.plist
  65. 4
      ios/BlueWalletWatch Extension/InterfaceController.swift
  66. 6
      ios/BlueWalletWatch Extension/Objects/WalletGradient.swift
  67. 16
      ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift
  68. 15
      ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift
  69. 7
      ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift
  70. 14
      ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift
  71. 32
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json
  72. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular38mm@2x.png
  73. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular40mm@2x.png
  74. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular42mm@2x.png
  75. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular44mm@2x.png
  76. 48
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Contents.json
  77. 32
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json
  78. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large38mm@2x.png
  79. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large40mm@2x.png
  80. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large42mm@2x.png
  81. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large44mm@2x.png
  82. 30
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json
  83. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/graphic-bezel40mm@2x.png
  84. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/graphic-bezel44mm@2x.png
  85. 30
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json
  86. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/graphic-circular40mm@2x.png
  87. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/graphic-circular44mm@2x.png
  88. 30
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json
  89. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/graphic-corner40mm@2x.png
  90. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/graphic-corner44mm@2x.png
  91. 28
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json
  92. 32
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json
  93. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular38mm@2x.png
  94. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular40mm@2x.png
  95. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular42mm@2x.png
  96. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular44mm@2x.png
  97. 32
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json
  98. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/utility38mm@2x.png
  99. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/utility40mm@2x.png
  100. BIN
      ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/utility42mm@2x.png

110
App.js

@ -1,5 +1,5 @@
import React from 'react';
import { Linking, AppState, Clipboard, StyleSheet, KeyboardAvoidingView, Platform, View } from 'react-native';
import { Linking, DeviceEventEmitter, AppState, Clipboard, StyleSheet, KeyboardAvoidingView, Platform, View } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Modal from 'react-native-modal';
import { NavigationActions } from 'react-navigation';
@ -9,6 +9,17 @@ import { BlueTextCentered, BlueButton } from './BlueComponents';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import url from 'url';
import { AppStorage, LightningCustodianWallet } from './class';
import { Chain } from './models/bitcoinUnits';
import QuickActions from 'react-native-quick-actions';
import * as Sentry from '@sentry/react-native';
import OnAppLaunch from './class/onAppLaunch';
if (process.env.NODE_ENV !== 'development') {
Sentry.init({
dsn: 'https://23377936131848ca8003448a893cb622@sentry.io/1295736',
});
}
const bitcoin = require('bitcoinjs-lib');
const bitcoinModalString = 'Bitcoin address';
const lightningModalString = 'Lightning Invoice';
@ -26,18 +37,71 @@ export default class App extends React.Component {
clipboardContent: '',
};
componentDidMount() {
Linking.getInitialURL()
.then(url => {
if (this.hasSchema(url)) {
this.handleOpenURL({ url });
}
})
.catch(console.error);
async componentDidMount() {
Linking.addEventListener('url', this.handleOpenURL);
AppState.addEventListener('change', this._handleAppStateChange);
QuickActions.popInitialAction().then(this.popInitialAction);
DeviceEventEmitter.addListener('quickActionShortcut', this.walletQuickActions);
}
popInitialAction = async data => {
if (data) {
// eslint-disable-next-line no-unused-expressions
this.navigator.dismiss;
const wallet = BlueApp.getWallets().find(wallet => wallet.getID() === data.userInfo.url.split('wallet/')[1]);
this.navigator.dispatch(
NavigationActions.navigate({
key: `WalletTransactions-${wallet.getID()}`,
routeName: 'WalletTransactions',
params: {
wallet,
},
}),
);
} else {
const url = await Linking.getInitialURL();
if (url) {
if (this.hasSchema(url)) {
this.handleOpenURL({ url });
}
} else {
const isViewAllWalletsEnabled = await OnAppLaunch.isViewAllWalletsEnabled();
if (!isViewAllWalletsEnabled) {
// eslint-disable-next-line no-unused-expressions
this.navigator.dismiss;
const selectedDefaultWallet = await OnAppLaunch.getSelectedDefaultWallet();
const wallet = BlueApp.getWallets().find(wallet => wallet.getID() === selectedDefaultWallet.getID());
if (wallet) {
this.navigator.dispatch(
NavigationActions.navigate({
routeName: 'WalletTransactions',
key: `WalletTransactions-${wallet.getID()}`,
params: {
wallet,
},
}),
);
}
}
}
}
};
walletQuickActions = data => {
const wallet = BlueApp.getWallets().find(wallet => wallet.getID() === data.userInfo.url.split('wallet/')[1]);
// eslint-disable-next-line no-unused-expressions
this.navigator.dismiss;
this.navigator.dispatch(
NavigationActions.navigate({
routeName: 'WalletTransactions',
key: `WalletTransactions-${wallet.getID()}`,
params: {
wallet,
},
}),
);
};
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL);
AppState.removeEventListener('change', this._handleAppStateChange);
@ -47,7 +111,14 @@ export default class App extends React.Component {
if (BlueApp.getWallets().length > 0) {
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
const clipboard = await Clipboard.getString();
if (this.state.clipboardContent !== clipboard && (this.isBitcoinAddress(clipboard) || this.isLightningInvoice(clipboard))) {
const isAddressFromStoredWallet = BlueApp.getWallets().some(wallet =>
wallet.chain === Chain.ONCHAIN ? wallet.weOwnAddress(clipboard) : wallet.isInvoiceGeneratedByWallet(clipboard),
);
if (
!isAddressFromStoredWallet &&
this.state.clipboardContent !== clipboard &&
(this.isBitcoinAddress(clipboard) || this.isLightningInvoice(clipboard) || this.isLnUrl(clipboard))
) {
this.setState({ isClipboardContentModalVisible: true });
}
this.setState({ clipboardContent: clipboard });
@ -88,13 +159,20 @@ export default class App extends React.Component {
isLightningInvoice(invoice) {
let isValidLightningInvoice = false;
if (invoice.indexOf('lightning:lnb') === 0 || invoice.indexOf('LIGHTNING:lnb') === 0 || invoice.toLowerCase().startsWith('lnb')) {
if (invoice.toLowerCase().startsWith('lightning:lnb') || invoice.toLowerCase().startsWith('lnb')) {
this.setState({ clipboardContentModalAddressType: lightningModalString });
isValidLightningInvoice = true;
}
return isValidLightningInvoice;
}
isLnUrl(text) {
if (text.toLowerCase().startsWith('lightning:lnurl') || text.toLowerCase().startsWith('lnurl')) {
return true;
}
return false;
}
isSafelloRedirect(event) {
let urlObject = url.parse(event.url, true) // eslint-disable-line
@ -128,6 +206,16 @@ export default class App extends React.Component {
},
}),
);
} else if (this.isLnUrl(event.url)) {
this.navigator &&
this.navigator.dispatch(
NavigationActions.navigate({
routeName: 'LNDCreateInvoice',
params: {
uri: event.url,
},
}),
);
} else if (this.isSafelloRedirect(event)) {
let urlObject = url.parse(event.url, true) // eslint-disable-line

4
BlueApp.js

@ -2,11 +2,11 @@
* @exports {AppStorage}
*/
import { AppStorage } from './class';
import DeviceQuickActions from './class/quickActions';
let prompt = require('./prompt');
let EV = require('./events');
let currency = require('./currency');
let loc = require('./loc');
let A = require('./analytics');
let BlueElectrum = require('./BlueElectrum'); // eslint-disable-line
/** @type {AppStorage} */
@ -20,6 +20,7 @@ async function startAndDecrypt(retry) {
}
let password = false;
if (await BlueApp.storageIsEncrypted()) {
DeviceQuickActions.clearShortcutItems();
do {
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false);
} while (!password);
@ -65,7 +66,6 @@ async function startAndDecrypt(retry) {
}
}
A(A.ENUM.INIT);
BlueApp.startAndDecrypt = startAndDecrypt;
currency.startUpdater();

710
BlueComponents.js

@ -1,7 +1,6 @@
/* eslint react/prop-types: 0 */
/* global alert */
/** @type {AppStorage} */
import React, { Component } from 'react';
import React, { Component, useEffect, useState } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';
import PropTypes from 'prop-types';
import { Icon, FormLabel, FormInput, Text, Header, List, ListItem } from 'react-native-elements';
@ -11,6 +10,7 @@ import {
Animated,
ActivityIndicator,
View,
KeyboardAvoidingView,
UIManager,
StyleSheet,
Dimensions,
@ -27,9 +27,12 @@ import { LightningCustodianWallet } from './class';
import Carousel from 'react-native-snap-carousel';
import { BitcoinUnit } from './models/bitcoinUnits';
import NavigationService from './NavigationService';
import ImagePicker from 'react-native-image-picker';
import WalletGradient from './class/walletGradient';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
import ToolTip from 'react-native-tooltip';
import { BlurView } from '@react-native-community/blur';
import showPopupMenu from 'react-native-popup-menu-android';
import NetworkTransactionFees, { NetworkTransactionFeeType } from './models/networkTransactionFees';
import Biometric from './class/biometrics';
let loc = require('./loc/');
/** @type {AppStorage} */
let BlueApp = require('./BlueApp');
@ -98,7 +101,6 @@ export class BitcoinButton extends Component {
borderRadius: 5,
backgroundColor: (this.props.active && BlueApp.settings.hdbackgroundColor) || BlueApp.settings.brandingColor,
// eslint-disable-next-line
width: this.props.style.width,
minWidth: this.props.style.width,
// eslint-disable-next-line
minHeight: this.props.style.height,
@ -136,7 +138,6 @@ export class LightningButton extends Component {
borderRadius: 5,
backgroundColor: (this.props.active && BlueApp.settings.lnbackgroundColor) || BlueApp.settings.brandingColor,
// eslint-disable-next-line
width: this.props.style.width,
minWidth: this.props.style.width,
// eslint-disable-next-line
minHeight: this.props.style.height,
@ -157,6 +158,193 @@ export class LightningButton extends Component {
}
}
export class BlueWalletNavigationHeader extends Component {
static propTypes = {
wallet: PropTypes.shape().isRequired,
onWalletUnitChange: PropTypes.func,
};
static getDerivedStateFromProps(props, _state) {
return { wallet: props.wallet, onWalletUnitChange: props.onWalletUnitChange };
}
constructor(props) {
super(props);
this.state = { wallet: props.wallet, walletPreviousPreferredUnit: props.wallet.getPreferredBalanceUnit() };
}
handleCopyPress = _item => {
Clipboard.setString(loc.formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString());
};
handleBalanceVisibility = async _item => {
const wallet = this.state.wallet;
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
if (isBiometricsEnabled && wallet.hideBalance) {
if (!(await Biometric.unlockWithBiometrics())) {
return this.props.navigation.goBack();
}
}
wallet.hideBalance = !wallet.hideBalance;
this.setState({ wallet });
await BlueApp.saveToDisk();
};
showAndroidTooltip = () => {
showPopupMenu(this.toolTipMenuOptions(), this.handleToolTipSelection, this.walletBalanceText);
};
handleToolTipSelection = item => {
if (item === loc.transactions.details.copy || item.id === loc.transactions.details.copy) {
this.handleCopyPress();
} else if (item === 'balancePrivacy' || item.id === 'balancePrivacy') {
this.handleBalanceVisibility();
}
};
toolTipMenuOptions() {
return Platform.select({
// NOT WORKING ATM.
// ios: [
// { text: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance', onPress: this.handleBalanceVisibility },
// { text: loc.transactions.details.copy, onPress: this.handleCopyPress },
// ],
android: this.state.wallet.hideBalance
? [{ id: 'balancePrivacy', label: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance' }]
: [
{ id: 'balancePrivacy', label: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance' },
{ id: loc.transactions.details.copy, label: loc.transactions.details.copy },
],
});
}
changeWalletBalanceUnit() {
let walletPreviousPreferredUnit = this.state.wallet.getPreferredBalanceUnit();
const wallet = this.state.wallet;
if (walletPreviousPreferredUnit === BitcoinUnit.BTC) {
wallet.preferredBalanceUnit = BitcoinUnit.SATS;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
} else if (walletPreviousPreferredUnit === BitcoinUnit.SATS) {
wallet.preferredBalanceUnit = BitcoinUnit.LOCAL_CURRENCY;
walletPreviousPreferredUnit = BitcoinUnit.SATS;
} else if (walletPreviousPreferredUnit === BitcoinUnit.LOCAL_CURRENCY) {
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
} else {
wallet.preferredBalanceUnit = BitcoinUnit.BTC;
walletPreviousPreferredUnit = BitcoinUnit.BTC;
}
this.setState({ wallet, walletPreviousPreferredUnit: walletPreviousPreferredUnit }, () => {
this.props.onWalletUnitChange(wallet);
});
}
manageFundsPressed = () => {
this.props.onManageFundsPressed();
};
render() {
return (
<LinearGradient
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
>
<Image
source={
(LightningCustodianWallet.type === this.state.wallet.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png')
}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: '#fff',
}}
>
{this.state.wallet.getLabel()}
</Text>
{Platform.OS === 'ios' && (
<ToolTip
ref={tooltip => (this.tooltip = tooltip)}
actions={
this.state.wallet.hideBalance
? [{ text: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance', onPress: this.handleBalanceVisibility }]
: [
{ text: this.state.wallet.hideBalance ? 'Show Balance' : 'Hide Balance', onPress: this.handleBalanceVisibility },
{ text: loc.transactions.details.copy, onPress: this.handleCopyPress },
]
}
/>
)}
<TouchableOpacity
style={styles.balance}
onPress={() => this.changeWalletBalanceUnit()}
ref={ref => (this.walletBalanceText = ref)}
onLongPress={() => (Platform.OS === 'ios' ? this.tooltip.showMenu() : this.showAndroidTooltip())}
>
{this.state.wallet.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
numberOfLines={1}
adjustsFontSizeToFit
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: '#fff',
}}
>
{loc.formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString()}
</Text>
)}
</TouchableOpacity>
{this.state.wallet.type === LightningCustodianWallet.type && (
<TouchableOpacity onPress={this.manageFundsPressed}>
<View
style={{
marginTop: 14,
marginBottom: 10,
backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 9,
minWidth: 119,
minHeight: 39,
width: 119,
height: 39,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text
style={{
fontWeight: '500',
fontSize: 14,
color: '#FFFFFF',
}}
>
{loc.lnd.title}
</Text>
</View>
</TouchableOpacity>
)}
</LinearGradient>
);
}
}
export class BlueButtonLink extends Component {
render() {
return (
@ -204,10 +392,57 @@ export const BlueNavigationStyle = (navigation, withNavigationCloseButton = fals
headerBackTitle: null,
});
export const BlueCopyToClipboardButton = ({ stringToCopy }) => {
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => ({
headerStyle: {
backgroundColor: BlueApp.settings.brandingColor,
borderBottomWidth: 0,
elevation: 0,
},
headerTitleStyle: {
fontWeight: '600',
color: BlueApp.settings.foregroundColor,
},
headerTintColor: BlueApp.settings.foregroundColor,
headerLeft: (
<TouchableOpacity
style={{ minWwidth: 40, height: 40, padding: 14 }}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
</TouchableOpacity>
),
headerRight: withAdvancedOptionsMenuButton ? (
<TouchableOpacity style={{ minWidth: 40, height: 40, padding: 14 }} onPress={advancedOptionsMenuButtonAction}>
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueApp.settings.foregroundColor} />
</TouchableOpacity>
) : null,
headerBackTitle: null,
});
export const BluePrivateBalance = () => {
return Platform.select({
ios: (
<View style={{ flexDirection: 'row' }}>
<BlurView style={styles.balanceBlur} blurType="light" blurAmount={25} />
<Icon name="eye-slash" type="font-awesome" color="#FFFFFF" />
</View>
),
android: (
<View style={{ flexDirection: 'row' }}>
<View style={{ backgroundColor: '#FFFFFF', opacity: 0.5, height: 30, width: 100, marginRight: 8 }} />
<Icon name="eye-slash" type="font-awesome" color="#FFFFFF" />
</View>
),
});
};
export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => {
return (
<TouchableOpacity {...this.props} onPress={() => Clipboard.setString(stringToCopy)}>
<Text style={{ fontSize: 13, fontWeight: '400', color: '#68bbe1' }}>{loc.transactions.details.copy}</Text>
<Text style={{ fontSize: 13, fontWeight: '400', color: '#68bbe1' }}>{displayText || loc.transactions.details.copy}</Text>
</TouchableOpacity>
);
};
@ -366,10 +601,6 @@ export class BlueFormMultiInput extends Component {
};
}
onSelectionChange = ({ nativeEvent: { selection, text } }) => {
this.setState({ selection: { start: selection.end, end: selection.end } });
};
render() {
return (
<TextInput
@ -392,8 +623,6 @@ export class BlueFormMultiInput extends Component {
spellCheck={false}
{...this.props}
selectTextOnFocus={false}
onSelectionChange={this.onSelectionChange}
selection={this.state.selection}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
/>
);
@ -571,16 +800,71 @@ export class BlueUseAllFundsButton extends Component {
};
render() {
return (
<InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<Text style={{ color: BlueApp.settings.alternativeTextColor, fontSize: 16, marginHorizontal: 8 }}>
Total: {this.props.wallet.getBalance()} {BitcoinUnit.BTC}
const inputView = (
<View
style={{
flex: 1,
flexDirection: 'row',
maxHeight: 44,
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#eef0f4',
}}
>
<View style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'flex-start' }}>
<Text
style={{
color: BlueApp.settings.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
Total:
</Text>
<BlueButtonLink title="Use All" onPress={this.props.onUseAllPressed} />
{this.props.wallet.allowSendMax() && this.props.wallet.getBalance() > 0 ? (
<BlueButtonLink
onPress={this.props.onUseAllPressed}
style={{ marginLeft: 8, paddingRight: 0, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title={`${loc.formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} ${
BitcoinUnit.BTC
}`}
/>
) : (
<Text
style={{
color: BlueApp.settings.alternativeTextColor,
fontSize: 16,
marginLeft: 8,
marginRight: 0,
paddingRight: 0,
paddingLeft: 0,
paddingTop: 12,
paddingBottom: 12,
}}
>
{loc.formatBalanceWithoutSuffix(this.props.wallet.getBalance(), BitcoinUnit.BTC, true).toString()} {BitcoinUnit.BTC}
</Text>
)}
</View>
</InputAccessoryView>
<View style={{ flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'flex-end' }}>
<BlueButtonLink
style={{ paddingRight: 8, paddingLeft: 0, paddingTop: 12, paddingBottom: 12 }}
title="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>;
}
}
}
@ -600,13 +884,47 @@ export class BlueDismissKeyboardInputAccessory extends Component {
alignItems: 'center',
}}
>
<BlueButtonLink title="Done" onPress={Keyboard.dismiss} />
<BlueButtonLink title="Done" onPress={() => Keyboard.dismiss()} />
</View>
</InputAccessoryView>
);
}
}
export class BlueDoneAndDismissKeyboardInputAccessory extends Component {
static InputAccessoryViewID = 'BlueDoneAndDismissKeyboardInputAccessory';
onPasteTapped = async () => {
const clipboard = await Clipboard.getString();
this.props.onPasteTapped(clipboard);
};
render() {
const inputView = (
<View
style={{
backgroundColor: '#eef0f4',
height: 44,
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
<BlueButtonLink title="Clear" onPress={this.props.onClearTapped} />
<BlueButtonLink title="Paste" onPress={this.onPasteTapped} />
<BlueButtonLink title="Done" onPress={() => Keyboard.dismiss()} />
</View>
);
if (Platform.OS === 'ios') {
return <InputAccessoryView nativeID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}>{inputView}</InputAccessoryView>;
} else {
return <KeyboardAvoidingView style={{ height: 44 }}>{inputView}</KeyboardAvoidingView>;
}
}
}
export class BlueLoading extends Component {
render() {
return (
@ -740,11 +1058,11 @@ export class BlueTransactionPendingIcon extends Component {
<View style={stylesBlueIcon.ball}>
<Icon
{...this.props}
name="ellipsis-h"
name="kebab-horizontal"
size={16}
type="font-awesome"
type="octicon"
color={BlueApp.settings.foregroundColor}
iconStyle={{ left: 0, top: 6 }}
iconStyle={{ left: 0, top: 7 }}
/>
</View>
</View>
@ -1074,25 +1392,28 @@ export class NewWalletPanel extends Component {
}
}
export class BlueTransactionListItem extends Component {
static propTypes = {
item: PropTypes.shape().isRequired,
itemPriceUnit: PropTypes.string,
};
static defaultProps = {
itemPriceUnit: BitcoinUnit.BTC,
export const BlueTransactionListItem = ({ item, itemPriceUnit = BitcoinUnit.BTC }) => {
const calculateTimeLabel = () => {
const transactionTimeToReadable = loc.transactionTimeToReadable(item.received);
return setTransactionTimeToReadable(transactionTimeToReadable);
};
txMemo = () => {
if (BlueApp.tx_metadata[this.props.item.hash] && BlueApp.tx_metadata[this.props.item.hash]['memo']) {
return BlueApp.tx_metadata[this.props.item.hash]['memo'];
const interval = setInterval(() => calculateTimeLabel(), 60000);
const [transactionTimeToReadable, setTransactionTimeToReadable] = useState('...');
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
useEffect(() => {
calculateTimeLabel();
return () => clearInterval(interval);
}, [item, itemPriceUnit]);
const txMemo = () => {
if (BlueApp.tx_metadata[item.hash] && BlueApp.tx_metadata[item.hash]['memo']) {
return BlueApp.tx_metadata[item.hash]['memo'];
}
return '';
};
rowTitle = () => {
const item = this.props.item;
const rowTitle = () => {
if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (isNaN(item.value)) {
item.value = '0';
@ -1102,21 +1423,20 @@ export class BlueTransactionListItem extends Component {
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration > now) {
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
return loc.formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} else if (invoiceExpiration < now) {
if (item.ispaid) {
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
return loc.formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} else {
return loc.lnd.expired;
}
}
} else {
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
return loc.formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
}
};
rowTitleStyle = () => {
const item = this.props.item;
const rowTitleStyle = () => {
let color = BlueApp.settings.successColor;
if (item.type === 'user_invoice' || item.type === 'payment_request') {
@ -1144,9 +1464,9 @@ export class BlueTransactionListItem extends Component {
};
};
avatar = () => {
const avatar = () => {
// is it lightning refill tx?
if (this.props.item.category === 'receive' && this.props.item.confirmations < 3) {
if (item.category === 'receive' && item.confirmations < 3) {
return (
<View style={{ width: 25 }}>
<BlueTransactionPendingIcon />
@ -1154,14 +1474,14 @@ export class BlueTransactionListItem extends Component {
);
}
if (this.props.item.type && this.props.item.type === 'bitcoind_tx') {
if (item.type && item.type === 'bitcoind_tx') {
return (
<View style={{ width: 25 }}>
<BlueTransactionOnchainIcon />
</View>
);
}
if (this.props.item.type === 'paid_invoice') {
if (item.type === 'paid_invoice') {
// is it lightning offchain payment?
return (
<View style={{ width: 25 }}>
@ -1170,11 +1490,11 @@ export class BlueTransactionListItem extends Component {
);
}
if (this.props.item.type === 'user_invoice' || this.props.item.type === 'payment_request') {
if (!this.props.item.ispaid) {
if (item.type === 'user_invoice' || item.type === 'payment_request') {
if (!item.ispaid) {
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = this.props.item.timestamp + this.props.item.expire_time;
const invoiceExpiration = item.timestamp + item.expire_time;
if (invoiceExpiration < now) {
return (
<View style={{ width: 25 }}>
@ -1191,13 +1511,13 @@ export class BlueTransactionListItem extends Component {
}
}
if (!this.props.item.confirmations) {
if (!item.confirmations) {
return (
<View style={{ width: 25 }}>
<BlueTransactionPendingIcon />
</View>
);
} else if (this.props.item.value < 0) {
} else if (item.value < 0) {
return (
<View style={{ width: 25 }}>
<BlueTransactionOutgoingIcon />
@ -1212,32 +1532,24 @@ export class BlueTransactionListItem extends Component {
}
};
subtitle = () => {
return (
(this.props.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + this.props.item.confirmations + ' ' : '') +
this.txMemo() +
(this.props.item.memo || '')
);
const subtitle = () => {
return (item.confirmations < 7 ? loc.transactions.list.conf + ': ' + item.confirmations + ' ' : '') + txMemo() + (item.memo || '');
};
onPress = () => {
if (this.props.item.hash) {
NavigationService.navigate('TransactionDetails', { hash: this.props.item.hash });
} else if (
this.props.item.type === 'user_invoice' ||
this.props.item.type === 'payment_request' ||
this.props.item.type === 'paid_invoice'
) {
const onPress = () => {
if (item.hash) {
NavigationService.navigate('TransactionStatus', { hash: item.hash });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
const lightningWallet = BlueApp.getWallets().filter(wallet => {
if (typeof wallet === 'object') {
if (wallet.hasOwnProperty('secret')) {
return wallet.getSecret() === this.props.item.fromWallet;
return wallet.getSecret() === item.fromWallet;
}
}
});
if (lightningWallet.length === 1) {
NavigationService.navigate('LNDViewInvoice', {
invoice: this.props.item,
invoice: item,
fromWallet: lightningWallet[0],
isModal: false,
});
@ -1245,25 +1557,32 @@ export class BlueTransactionListItem extends Component {
}
};
render() {
return (
<BlueListItem
avatar={this.avatar()}
title={loc.transactionTimeToReadable(this.props.item.received)}
subtitle={this.subtitle()}
onPress={this.onPress}
badge={{
value: 3,
textStyle: { color: 'orange' },
containerStyle: { marginTop: 0 },
}}
hideChevron
rightTitle={this.rowTitle()}
rightTitleStyle={this.rowTitleStyle()}
/>
);
}
}
const onLongPress = () => {
if (subtitleNumberOfLines === 1) {
setSubtitleNumberOfLines(0);
}
};
return (
<BlueListItem
avatar={avatar()}
title={transactionTimeToReadable}
titleNumberOfLines={subtitleNumberOfLines}
subtitle={subtitle()}
subtitleNumberOfLines={subtitleNumberOfLines}
onPress={onPress}
onLongPress={onLongPress}
badge={{
value: 3,
textStyle: { color: 'orange' },
containerStyle: { marginTop: 0 },
}}
hideChevron
rightTitle={rowTitle()}
rightTitleStyle={rowTitleStyle()}
/>
);
};
export class BlueListTransactionItem extends Component {
static propTypes = {
@ -1413,7 +1732,7 @@ export class BlueListTransactionItem extends Component {
onPress = () => {
if (this.props.item.hash) {
NavigationService.navigate('TransactionDetails', { hash: this.props.item.hash });
NavigationService.navigate('TransactionStatus', { hash: this.props.item.hash });
} else if (
this.props.item.type === 'user_invoice' ||
this.props.item.type === 'payment_request' ||
@ -1544,18 +1863,22 @@ export class WalletsCarousel extends Component {
>
{item.getLabel()}
</Text>
<Text
numberOfLines={1}
adjustsFontSizeToFit
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
</Text>
{item.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
numberOfLines={1}
adjustsFontSizeToFit
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
</Text>
)}
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
@ -1651,43 +1974,21 @@ export class BlueAddressInput extends Component {
value={this.props.address}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.props.isLoading}
onSubmitEditing={Keyboard.dismiss}
onSubmitEditing={() => Keyboard.dismiss()}
{...this.props}
/>
<TouchableOpacity
disabled={this.props.isLoading}
onPress={() => {
NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned });
Keyboard.dismiss();
ImagePicker.showImagePicker(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
customButtons: [{ name: 'navigatetoQRScan', title: 'Use Camera' }],
},
response => {
if (response.customButton) {
NavigationService.navigate('ScanQrAddress', { onBarScanned: this.props.onBarScanned });
} else if (response.uri) {
const uri = response.uri.toString().replace('file://', '');
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.props.onBarScanned(result);
} else {
alert('The selected image does not contain a QR Code.');
}
});
}
},
);
}}
style={{
width: 75,
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#bebebe',
backgroundColor: '#9AA0AA',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
@ -1695,13 +1996,139 @@ export class BlueAddressInput extends Component {
}}
>
<Icon name="qrcode" size={22} type="font-awesome" color={BlueApp.settings.inverseForegroundColor} />
<Text style={{ color: BlueApp.settings.inverseForegroundColor }}>{loc.send.details.scan}</Text>
<Text style={{ marginLeft: 4, color: BlueApp.settings.inverseForegroundColor }}>{loc.send.details.scan}</Text>
</TouchableOpacity>
</View>
);
}
}
export class BlueReplaceFeeSuggestions extends Component {
static propTypes = {
onFeeSelected: PropTypes.func.isRequired,
transactionMinimum: PropTypes.number.isRequired,
};
static defaultProps = {
onFeeSelected: undefined,
transactionMinimum: 1,
};
state = { networkFees: undefined, selectedFeeType: NetworkTransactionFeeType.FAST, customFeeValue: 0 };
async componentDidMount() {
const networkFees = await NetworkTransactionFees.recommendedFees();
this.setState({ networkFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
}
onFeeSelected = selectedFeeType => {
if (selectedFeeType !== NetworkTransactionFeeType.CUSTOM) {
Keyboard.dismiss();
}
if (selectedFeeType === NetworkTransactionFeeType.FAST) {
this.props.onFeeSelected(this.state.networkFees.fastestFee);
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.fastestFee));
} else if (selectedFeeType === NetworkTransactionFeeType.MEDIUM) {
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.halfHourFee));
} else if (selectedFeeType === NetworkTransactionFeeType.SLOW) {
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.hourFee));
} else if (selectedFeeType === NetworkTransactionFeeType.CUSTOM) {
this.props.onFeeSelected(this.state.customFeeValue);
}
};
onCustomFeeTextChange = customFee => {
this.setState({ customFeeValue: Number(customFee), selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => {
this.onFeeSelected(NetworkTransactionFeeType.CUSTOM);
});
};
render() {
return (
<View>
{this.state.networkFees && (
<>
<BlueText>Suggestions</BlueText>
<TouchableOpacity onPress={() => this.onFeeSelected(NetworkTransactionFeeType.FAST)}>
<BlueListItem
title={'Fast'}
rightTitle={`${this.state.networkFees.fastestFee} sat/b`}
{...(this.state.selectedFeeType === NetworkTransactionFeeType.FAST
? { rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" /> }
: { hideChevron: true })}
/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.onFeeSelected(NetworkTransactionFeeType.MEDIUM)}>
<BlueListItem
title={'Medium'}
rightTitle={`${this.state.networkFees.halfHourFee} sat/b`}
{...(this.state.selectedFeeType === NetworkTransactionFeeType.MEDIUM
? { rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" /> }
: { hideChevron: true })}
/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.onFeeSelected(NetworkTransactionFeeType.SLOW)}>
<BlueListItem
title={'Slow'}
rightTitle={`${this.state.networkFees.hourFee} sat/b`}
{...(this.state.selectedFeeType === NetworkTransactionFeeType.SLOW
? { rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" /> }
: { hideChevron: true })}
/>
</TouchableOpacity>
</>
)}
<TouchableOpacity onPress={() => this.customTextInput.focus()}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginLeft: 18, marginRight: 18, alignItems: 'center' }}>
<Text style={{ color: BlueApp.settings.foregroundColor, fontSize: 16, fontWeight: '500' }}>Custom</Text>
<View
style={{
flexDirection: 'row',
minHeight: 44,
height: 44,
minWidth: 48,
alignItems: 'center',
justifyContent: 'flex-end',
marginVertical: 8,
}}
>
<TextInput
onChangeText={this.onCustomFeeTextChange}
keyboardType={'numeric'}
value={this.state.customFeeValue}
ref={ref => (this.customTextInput = ref)}
maxLength={9}
style={{
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
borderRadius: 4,
minHeight: 33,
maxWidth: 100,
minWidth: 44,
backgroundColor: '#f5f5f5',
textAlign: 'right',
}}
onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)}
defaultValue={`${this.props.transactionMinimum}`}
placeholder="Custom sat/b"
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/>
<Text style={{ color: BlueApp.settings.alternativeTextColor, marginHorizontal: 8 }}>sat/b</Text>
{this.state.selectedFeeType === NetworkTransactionFeeType.CUSTOM && <Icon name="check" type="font-awesome" color="#0c2550" />}
</View>
<BlueDismissKeyboardInputAccessory />
</View>
</TouchableOpacity>
<BlueText>
The total fee rate (satoshi per byte) you want to pay should be higher than {this.props.transactionMinimum} sat/byte
</BlueText>
</View>
);
}
}
export class BlueBitcoinAmount extends Component {
static propTypes = {
isLoading: PropTypes.bool,
@ -1725,22 +2152,41 @@ export class BlueBitcoinAmount extends Component {
} else {
localCurrency = loc.formatBalanceWithoutSuffix(amount.toString(), BitcoinUnit.LOCAL_CURRENCY, false);
}
if (amount === BitcoinUnit.MAX) localCurrency = ''; // we dont want to display NaN
return (
<TouchableWithoutFeedback disabled={this.props.pointerEvents === 'none'} onPress={() => this.textInput.focus()}>
<View>
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 2 }}>
<TextInput
{...this.props}
keyboardType="numeric"
onChangeText={text => {
text = text.trim();
text = text.replace(',', '.');
const split = text.split('.');
if (split.length >= 2) {
text = `${parseInt(split[0], 10)}.${split[1]}`;
} else {
text = `${parseInt(split[0], 10)}`;
}
text = this.props.unit === BitcoinUnit.BTC ? text.replace(/[^0-9.]/g, '') : text.replace(/[^0-9]/g, '');
text = text.replace(/(\..*)\./g, '$1');
if (text.startsWith('.')) {
text = '0.';
}
text = text.replace(/(0{1,}.)\./g, '$1');
if (this.props.unit !== BitcoinUnit.BTC) {
text = text.replace(/[^0-9.]/g, '');
}
this.props.onChangeText(text);
}}
onBlur={() => {
if (this.props.onBlur) this.props.onBlur();
}}
onFocus={() => {
if (this.props.onFocus) this.props.onFocus();
}}
placeholder="0"
maxLength={10}
ref={textInput => (this.textInput = textInput)}
@ -1774,3 +2220,11 @@ export class BlueBitcoinAmount extends Component {
);
}
}
const styles = StyleSheet.create({
balanceBlur: {
height: 30,
width: 100,
marginRight: 16,
},
});

7
BlueElectrum.js

@ -1,7 +1,7 @@
import AsyncStorage from '@react-native-community/async-storage';
import { AppStorage } from './class';
const bitcoin = require('bitcoinjs-lib');
const ElectrumClient = require('electrum-client');
let bitcoin = require('bitcoinjs-lib');
let reverse = require('buffer-reverse');
let BigNumber = require('bignumber.js');
@ -43,12 +43,11 @@ async function connectMain() {
};
await mainClient.connect();
const ver = await mainClient.server_version('2.7.11', '1.4');
let peers = await mainClient.serverPeers_subscribe();
if (peers && peers.length > 0) {
if (ver && ver[0]) {
console.log('connected to ', ver);
mainConnected = true;
wasConnectedAtLeastOnce = true;
AsyncStorage.setItem(storageKey, JSON.stringify(peers));
// AsyncStorage.setItem(storageKey, JSON.stringify(peers)); TODO: refactor
}
} catch (e) {
mainConnected = false;

1
CODE_OF_CONDUCT.md

@ -0,0 +1 @@
Do what you wish, that is the law

61
MainBottomTabs.js

@ -1,6 +1,6 @@
import { createStackNavigator, createAppContainer } from 'react-navigation';
import Settings from './screen/settings/settings';
import SettingsContainer from './screen/settings/settings';
import About from './screen/settings/about';
import ReleaseNotes from './screen/settings/releasenotes';
import Selftest from './screen/selftest';
@ -10,6 +10,8 @@ import EncryptStorage from './screen/settings/encryptStorage';
import PlausibleDeniability from './screen/plausibledeniability';
import LightningSettings from './screen/settings/lightningSettings';
import ElectrumSettings from './screen/settings/electrumSettings';
import DefaultView from './screen/settings/defaultView';
import WalletsList from './screen/wallets/list';
import WalletTransactions from './screen/wallets/transactions';
import AddWallet from './screen/wallets/add';
@ -19,11 +21,13 @@ import WalletDetails from './screen/wallets/details';
import WalletExport from './screen/wallets/export';
import WalletXpub from './screen/wallets/xpub';
import BuyBitcoin from './screen/wallets/buyBitcoin';
import Marketplace from './screen/wallets/marketplace';
import scanQrWif from './screen/wallets/scanQrWif';
import ReorderWallets from './screen/wallets/reorderWallets';
import SelectWallet from './screen/wallets/selectWallet';
import details from './screen/transactions/details';
import TransactionStatus from './screen/transactions/transactionStatus';
import rbf from './screen/transactions/RBF';
import createrbf from './screen/transactions/RBF-create';
import cpfp from './screen/transactions/CPFP';
@ -34,12 +38,12 @@ import receiveDetails from './screen/receive/details';
import setReceiveAmount from './screen/receive/receiveAmount';
import sendDetails from './screen/send/details';
import sendScanQrAddress from './screen/send/scanQrAddress';
import ScanQRCode from './screen/send/scanQrAddress';
import sendCreate from './screen/send/create';
import Confirm from './screen/send/confirm';
import PsbtWithHardwareWallet from './screen/send/psbtWithHardwareWallet';
import Success from './screen/send/success';
import ManageFunds from './screen/lnd/manageFunds';
import ScanLndInvoice from './screen/lnd/scanLndInvoice';
import LappBrowser from './screen/lnd/browser';
import LNDCreateInvoice from './screen/lnd/lndCreateInvoice';
@ -60,6 +64,11 @@ const WalletsStackNavigator = createStackNavigator(
},
WalletTransactions: {
screen: WalletTransactions,
path: 'WalletTransactions',
routeName: 'WalletTransactions',
},
TransactionStatus: {
screen: TransactionStatus,
},
TransactionDetails: {
screen: details,
@ -83,7 +92,7 @@ const WalletsStackNavigator = createStackNavigator(
screen: rbfCancel,
},
Settings: {
screen: Settings,
screen: SettingsContainer,
path: 'Settings',
navigationOptions: {
headerStyle: {
@ -94,6 +103,9 @@ const WalletsStackNavigator = createStackNavigator(
headerTintColor: '#0c2550',
},
},
SelectWallet: {
screen: SelectWallet,
},
Currency: {
screen: Currency,
},
@ -108,6 +120,10 @@ const WalletsStackNavigator = createStackNavigator(
Selftest: {
screen: Selftest,
},
DefaultView: {
screen: DefaultView,
path: 'DefaultView',
},
Language: {
screen: Language,
path: 'Language',
@ -147,6 +163,9 @@ const CreateTransactionStackNavigator = createStackNavigator({
Confirm: {
screen: Confirm,
},
PsbtWithHardwareWallet: {
screen: PsbtWithHardwareWallet,
},
CreateTransaction: {
screen: sendCreate,
navigationOptions: {
@ -162,20 +181,8 @@ const CreateTransactionStackNavigator = createStackNavigator({
},
SelectWallet: {
screen: SelectWallet,
},
});
const ManageFundsStackNavigator = createStackNavigator({
ManageFunds: {
screen: ManageFunds,
},
SelectWallet: {
screen: SelectWallet,
},
SendDetails: {
screen: CreateTransactionStackNavigator,
navigationOptions: {
header: null,
headerRight: null,
},
},
});
@ -212,6 +219,9 @@ const LightningScanInvoiceStackNavigator = createStackNavigator({
},
SelectWallet: {
screen: SelectWallet,
navigationOptions: {
headerRight: null,
},
},
Success: {
screen: Success,
@ -245,6 +255,9 @@ const MainBottomTabs = createStackNavigator(
BuyBitcoin: {
screen: BuyBitcoin,
},
Marketplace: {
screen: Marketplace,
},
//
SendDetails: {
screen: CreateTransactionStackNavigator,
@ -252,6 +265,12 @@ const MainBottomTabs = createStackNavigator(
header: null,
},
},
SelectWallet: {
screen: SelectWallet,
navigationOptions: {
headerLeft: null,
},
},
//
@ -267,12 +286,6 @@ const MainBottomTabs = createStackNavigator(
// LND:
ManageFunds: {
screen: ManageFundsStackNavigator,
navigationOptions: {
header: null,
},
},
ScanLndInvoice: {
screen: LightningScanInvoiceStackNavigator,
navigationOptions: {
@ -280,7 +293,7 @@ const MainBottomTabs = createStackNavigator(
},
},
ScanQrAddress: {
screen: sendScanQrAddress,
screen: ScanQRCode,
},
LappBrowser: {
screen: LappBrowser,

25
README.md

@ -36,15 +36,38 @@ Community: [telegram group](https://t.me/bluewallet)
git clone https://github.com/BlueWallet/BlueWallet.git
cd BlueWallet
npm install
npm start android
```
* To run on Android:
```
npm start android
```
* To run on iOS:
```
cd ios
pod install
cd ..
npm start ios
```
## TESTS
```bash
npm run test
```
## QA
Builds automated and tested with BrowserStack
<a href="https://www.browserstack.com/"><img src="https://i.imgur.com/syscHCN.png" width="160px"></a>
## MOTIVATION TO BUILD IT

27
RELEASE.md

@ -0,0 +1,27 @@
# How to make a release
## Apple
* test the build on real device. its imperative that you run selftest and it gives you OK
* if necessary, up version number in all relevant files (you can use `./edit-version-number.sh`)
* run `./release-notes.sh` - it prints changelog between latest tag and now, put this output under
new version in file `ios/fastlane/metadata/en-US/release_notes.txt` (on top); if file got too big
delete the oldest version from the bottom of the file
* now is a good time to commit ver bump and release notes changes
* create this release version in App Store Connect (iTunes) and attach appropriate build. note
last 4 digits of the build and announce it - this is now a RC. no need to fill release notes yet
* `cd ios/` and then run `DELIVER_USERNAME="my_itunes_email@example.com" DELIVER_PASSWORD="my_itunes_password" fastlane deliver --force --skip_binary_upload --skip_screenshots --ignore_language_directory_validation -a io.bluewallet.bluewallet --app_version "6.6.6"`
but replace `6.6.6` with your version number - this will upload release notes to all locales in itunes
* go back to App Store Connect and press `Submit for Review`. choose Yes, we use identifiers - for installs tracking
* once its approved and released it is safe to cut a release tag: run `git tag -m "REL v6.6.6: 76ed479" v6.6.6`
where `76ed479` is a latest commit in this version. replace the version as well. then run `git push origin --tags`
* you are awesome!
## Android
* do android after ios usually
* test the build on real device. its imperative that you run selftest and it gives you OK. note which build you are testing
* go to appcenter.ms, find this exact build under `master` builds, and press `Distribute` -> `Store` -> `Production`.
in `Release notes` write `Bug fixes and performance improvements`, this field is to small to include actual changelog
* wait till appcenter displays message that it is succesfully distributed
* noice!

5
SECURITY.md

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
bluewallet at bluewallet dot io

98
UnlockWith.js

@ -0,0 +1,98 @@
import React, { Component } from 'react';
import { View, Image, TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
import Biometric from './class/biometrics';
import PropTypes from 'prop-types';
import { SafeAreaView } from 'react-navigation';
/** @type {AppStorage} */
const BlueApp = require('./BlueApp');
export default class UnlockWith extends Component {
state = { biometricType: false, isStorageEncrypted: false, isAuthenticating: false };
async componentDidMount() {
let biometricType = false;
if (await Biometric.isBiometricUseCapableAndEnabled()) {
biometricType = await Biometric.biometricType();
}
const isStorageEncrypted = await BlueApp.storageIsEncrypted();
this.setState({ biometricType, isStorageEncrypted }, async () => {
if (!biometricType || isStorageEncrypted) {
this.unlockWithKey();
} else if (typeof biometricType === 'string') this.unlockWithBiometrics();
});
}
successfullyAuthenticated = () => {
this.props.onSuccessfullyAuthenticated();
};
unlockWithBiometrics = async () => {
if (await BlueApp.storageIsEncrypted()) {
this.unlockWithKey();
}
this.setState({ isAuthenticating: true }, async () => {
if (await Biometric.unlockWithBiometrics()) {
await BlueApp.startAndDecrypt();
return this.props.onSuccessfullyAuthenticated();
}
this.setState({ isAuthenticating: false });
});
};
unlockWithKey = () => {
this.setState({ isAuthenticating: true }, async () => {
await BlueApp.startAndDecrypt();
this.props.onSuccessfullyAuthenticated();
});
};
render() {
if (!this.state.biometricType && !this.state.isStorageEncrypted) {
return <View />;
}
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 2, justifyContent: 'space-between', alignItems: 'center' }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Image source={require('./img/qr-code.png')} style={{ width: 120, height: 120 }} />
</View>
<View style={{ flex: 0.2, justifyContent: 'flex-end', marginBottom: 58 }}>
<View style={{ justifyContent: 'center', flexDirection: 'row' }}>
{(this.state.biometricType === Biometric.TouchID || this.state.biometricType === Biometric.Biometrics) &&
!this.state.isStorageEncrypted && (
<>
<TouchableOpacity disabled={this.state.isAuthenticating} onPress={this.unlockWithBiometrics}>
<Image source={require('./img/fingerprint.png')} style={{ width: 64, height: 64 }} />
</TouchableOpacity>
</>
)}
{this.state.biometricType === Biometric.FaceID && !this.state.isStorageEncrypted && (
<>
<TouchableOpacity disabled={this.state.isAuthenticating} onPress={this.unlockWithBiometrics}>
<Image source={require('./img/faceid.png')} style={{ width: 64, height: 64 }} />
</TouchableOpacity>
</>
)}
{this.state.biometricType !== false && this.state.isStorageEncrypted && (
<View style={{ backgroundColor: 'gray', width: 0.5, height: 20, marginHorizontal: 16 }} />
)}
{this.state.isStorageEncrypted && (
<>
<TouchableOpacity disabled={this.state.isAuthenticating} onPress={this.unlockWithKey}>
<Icon name="key" size={64} type="font-awesome" />
</TouchableOpacity>
</>
)}
</View>
</View>
</View>
</SafeAreaView>
);
}
}
UnlockWith.propTypes = {
onSuccessfullyAuthenticated: PropTypes.func,
};

12
WatchConnectivity.android.js

@ -0,0 +1,12 @@
export default class WatchConnectivity {
isAppInstalled = false;
static shared = new WatchConnectivity();
wallets;
fetchTransactionsFunction = () => {};
getIsWatchAppInstalled() {}
async handleLightningInvoiceCreateRequest(_walletIndex, _amount, _description) {}
async sendWalletsToWatch() {}
}

94
WatchConnectivity.js → WatchConnectivity.ios.js

@ -1,39 +1,51 @@
import * as watch from 'react-native-watch-connectivity';
import * as Watch from 'react-native-watch-connectivity';
import { InteractionManager } from 'react-native';
const loc = require('./loc');
export default class WatchConnectivity {
isAppInstalled = false;
BlueApp = require('./BlueApp');
static shared = new WatchConnectivity();
wallets;
fetchTransactionsFunction = () => {};
constructor() {
this.getIsWatchAppInstalled();
}
getIsWatchAppInstalled() {
watch.getIsWatchAppInstalled((err, isAppInstalled) => {
Watch.getIsWatchAppInstalled((err, isAppInstalled) => {
if (!err) {
this.isAppInstalled = isAppInstalled;
this.sendWalletsToWatch();
}
});
watch.subscribeToMessages(async (err, message, reply) => {
if (!err) {
if (message.request === 'createInvoice') {
const createInvoiceRequest = await this.handleLightningInvoiceCreateRequest(
message.walletIndex,
message.amount,
message.description,
);
reply({ invoicePaymentRequest: createInvoiceRequest });
}
} else {
reply(err);
WatchConnectivity.shared.isAppInstalled = isAppInstalled;
Watch.subscribeToWatchState((err, watchState) => {
if (!err) {
if (watchState === 'Activated') {
WatchConnectivity.shared.sendWalletsToWatch();
}
}
});
Watch.subscribeToMessages(async (err, message, reply) => {
if (!err) {
if (message.request === 'createInvoice') {
const createInvoiceRequest = await this.handleLightningInvoiceCreateRequest(
message.walletIndex,
message.amount,
message.description,
);
reply({ invoicePaymentRequest: createInvoiceRequest });
} else if (message.message === 'sendApplicationContext') {
await WatchConnectivity.shared.sendWalletsToWatch(WatchConnectivity.shared.wallets);
} else if (message.message === 'fetchTransactions') {
await WatchConnectivity.shared.fetchTransactionsFunction();
}
} else {
reply(err);
}
});
}
});
}
async handleLightningInvoiceCreateRequest(walletIndex, amount, description) {
const wallet = this.BlueApp.getWallets()[walletIndex];
const wallet = WatchConnectivity.shared.wallets[walletIndex];
if (wallet.allowReceive() && amount > 0 && description.trim().length > 0) {
try {
const invoiceRequest = await wallet.addInvoice(amount, description);
@ -44,16 +56,29 @@ export default class WatchConnectivity {
}
}
async sendWalletsToWatch() {
InteractionManager.runAfterInteractions(async () => {
if (this.isAppInstalled) {
const allWallets = this.BlueApp.getWallets();
async sendWalletsToWatch(allWallets) {
if (allWallets === undefined && WatchConnectivity.shared.wallets !== undefined) {
allWallets = WatchConnectivity.shared.wallets;
}
if (allWallets && allWallets.length === 0) {
return;
}
return InteractionManager.runAfterInteractions(async () => {
if (WatchConnectivity.shared.isAppInstalled) {
let wallets = [];
for (const wallet of allWallets) {
let receiveAddress = '';
if (wallet.allowReceive()) {
if (wallet.getAddressAsync) {
receiveAddress = await wallet.getAddressAsync();
try {
await wallet.getAddressAsync();
receiveAddress = wallet.getAddress();
} catch (error) {
console.log(error);
receiveAddress = wallet.getAddress();
}
} else {
receiveAddress = wallet.getAddress();
}
@ -65,7 +90,7 @@ export default class WatchConnectivity {
let memo = '';
let amount = 0;
if (transaction.hasOwnProperty('confirmations') && !transaction.confirmations > 0) {
if (transaction.hasOwnProperty('confirmations') && !(transaction.confirmations > 0)) {
type = 'pendingConfirmation';
} else if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') {
const currentDate = new Date();
@ -87,9 +112,7 @@ export default class WatchConnectivity {
type = 'received';
}
if (transaction.type === 'user_invoice' || transaction.type === 'payment_request') {
if (isNaN(transaction.value)) {
amount = '0';
}
amount = isNaN(transaction.value) ? '0' : amount;
const currentDate = new Date();
const now = (currentDate.getTime() / 1000) | 0;
const invoiceExpiration = transaction.timestamp + transaction.expire_time;
@ -108,8 +131,8 @@ export default class WatchConnectivity {
} else {
amount = loc.formatBalance(transaction.value, wallet.getPreferredBalanceUnit(), true).toString();
}
if (this.BlueApp.tx_metadata[transaction.hash] && this.BlueApp.tx_metadata[transaction.hash]['memo']) {
memo = this.BlueApp.tx_metadata[transaction.hash]['memo'];
if (WatchConnectivity.shared.tx_metadata[transaction.hash] && WatchConnectivity.shared.tx_metadata[transaction.hash]['memo']) {
memo = WatchConnectivity.shared.tx_metadata[transaction.hash]['memo'];
} else if (transaction.memo) {
memo = transaction.memo;
}
@ -125,14 +148,9 @@ export default class WatchConnectivity {
transactions: watchTransactions,
});
}
watch.updateApplicationContext({ wallets });
Watch.updateApplicationContext({ wallets, randomID: Math.floor(Math.random() * 11) });
return { wallets };
}
});
}
}
WatchConnectivity.init = function() {
if (WatchConnectivity.shared) return;
WatchConnectivity.shared = new WatchConnectivity();
};

10
analytics.js

@ -1,11 +1,17 @@
import amplitude from 'amplitude-js';
import Analytics from 'appcenter-analytics';
amplitude.getInstance().init('8b7cf19e8eea3cdcf16340f5fbf16330', null, {
useNativeDeviceInfo: true,
});
let A = function(event) {
amplitude.getInstance().logEvent(event);
let A = async event => {
amplitude.getInstance().logEvent(event, {});
try {
Analytics.trackEvent(event);
} catch (err) {
console.log(err);
}
};
A.ENUM = {

17
android/.project

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>BlueWallet</name>
<comment>Project android created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

6
android/app/.classpath

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

23
android/app/.project

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app</name>
<comment>Project app created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

2
android/app/.settings/org.eclipse.buildship.core.prefs

@ -0,0 +1,2 @@
connection.project.dir=..
eclipse.preferences.version=1

4
android/app/build.gradle

@ -80,6 +80,7 @@ project.ext.react = [
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/@sentry/react-native/sentry.gradle"
/**
* Set this to true to create two separate APKs instead of one:
@ -118,7 +119,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "4.2.0"
versionName "4.8.1"
multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general'
}
@ -154,7 +155,6 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+" // From node_modules
// JSC from node_modules
if (useIntlJsc) {
implementation 'org.webkit:android-jsc-intl:+'

13
android/app/src/main/AndroidManifest.xml

@ -2,6 +2,8 @@
package="io.bluewallet.bluewallet">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application
android:name=".MainApplication"
@ -9,6 +11,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
@ -19,6 +22,16 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bitcoin" />
<data android:scheme="lightning" />
<data android:scheme="bluewallet" />
<data android:scheme="lapp" />
<data android:scheme="blue" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

3
android/app/src/main/assets/appcenter-config.json

@ -0,0 +1,3 @@
{
"app_secret": "7a010505-cccc-4e40-aa6b-fbbe0624c8d9"
}

BIN
android/app/src/main/res/drawable/quickactions.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

2
android/app/src/main/res/values/strings.xml

@ -1,3 +1,5 @@
<resources>
<string name="app_name">BlueWallet</string>
<string name="appCenterCrashes_whenToSendCrashes" moduleConfig="true" translatable="false">ASK_JAVASCRIPT</string>
<string name="appCenterAnalytics_whenToEnableAnalytics" moduleConfig="true" translatable="false">ALWAYS_SEND</string>
</resources>

5
android/sentry.properties

@ -0,0 +1,5 @@
defaults.url=https://sentry.io/
defaults.org=bluewallet
defaults.project=bluewallet
auth.token=8020b31b54e94e7b86b4a69f60869f3d5a1320f44efe4f22bb9fa27e0b371448
cli.executable=node_modules/@sentry/cli/bin/sentry-cli

45
appcenter-post-build-get-pr-number.js

@ -0,0 +1,45 @@
const https = require('https');
const auth = 'Basic ' + Buffer.from(process.env.GITHUB).toString('base64');
const branch = require('child_process')
.execSync('git ls-remote --heads origin | grep $(git rev-parse HEAD) | cut -d / -f 3')
.toString()
.trim();
const req = https.request(
{
hostname: 'api.github.com',
port: 443,
path: '/repos/BlueWallet/BlueWallet/pulls',
method: 'GET',
headers: { 'User-Agent': 'BlueWallet bot', Authorization: auth },
},
resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
try {
const prs = JSON.parse(data);
for (let pr of prs) {
if (branch === pr.head.ref) {
console.log(pr.number);
}
}
} catch (err) {
console.log(err);
console.log('got json: ', data);
}
});
},
);
req.on('error', e => {
console.error(e);
});
req.end();

20
appcenter-post-build.sh

@ -0,0 +1,20 @@
#!/usr/bin/env bash
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
echo -n "Branch 2 "
git log -n 1 --pretty=%d HEAD | awk '{print $2}' | sed 's/origin\///' | sed 's/)//'
FILENAME="$APPCENTER_OUTPUT_DIRECTORY/app-release.apk"
if [ -f $FILENAME ]; then
APTZ=`curl "https://$APPETIZE@api.appetize.io/v1/apps" -F "file=@$FILENAME" -F "platform=android"`
echo Apptezize response:
echo $APTZ
APPURL=`node -e "let e = JSON.parse('$APTZ'); console.log(e.publicURL);"`
echo App url: $APPURL
PR=`node appcenter-post-build-get-pr-number.js`
echo PR: $PR
curl -X POST --data "{\"body\":\"This was a triumph. I'm making a note here: HUGE SUCCESS.\n\n [android] $APPURL\"}" -u "$GITHUB" "https://api.github.com/repos/BlueWallet/BlueWallet/issues/$PR/comments"
fi

19
class/abstract-hd-wallet.js

@ -1,7 +1,7 @@
import { LegacyWallet } from './legacy-wallet';
import Frisbee from 'frisbee';
const bip39 = require('bip39');
const bitcoin = require('bitcoinjs-lib');
const bip39 = require('bip39');
const BlueElectrum = require('../BlueElectrum');
export class AbstractHDWallet extends LegacyWallet {
@ -138,7 +138,7 @@ export class AbstractHDWallet extends LegacyWallet {
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
this.next_free_address_index += c + 1; // now points to the one _after_
}
this._address = freeAddress;
return freeAddress;
}
@ -176,7 +176,7 @@ export class AbstractHDWallet extends LegacyWallet {
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
this.next_free_address_index += c + 1; // now points to the one _after_
}
this._address = freeAddress;
return freeAddress;
}
@ -187,7 +187,7 @@ export class AbstractHDWallet extends LegacyWallet {
* @return {string}
*/
getAddress() {
return '';
return this._address;
}
_getExternalWIFByIndex(index) {
@ -498,8 +498,7 @@ export class AbstractHDWallet extends LegacyWallet {
unspent.vout = unspent.tx_output_n;
unspent.amount = unspent.value;
let chunksIn = bitcoin.script.decompile(Buffer.from(unspent.script, 'hex'));
unspent.address = bitcoin.address.fromOutputScript(chunksIn);
unspent.address = bitcoin.address.fromOutputScript(Buffer.from(unspent.script, 'hex'));
utxos.push(unspent);
}
} catch (err) {
@ -562,4 +561,12 @@ export class AbstractHDWallet extends LegacyWallet {
return hashmap[addr] === 1;
}
_getDerivationPathByAddress(address) {
throw new Error('Not implemented');
}
_getNodePubkeyByIndex(address) {
throw new Error('Not implemented');
}
}

30
class/abstract-wallet.js

@ -1,5 +1,5 @@
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
const createHash = require('create-hash');
export class AbstractWallet {
static type = 'abstract';
static typeReadable = 'abstract';
@ -28,6 +28,14 @@ export class AbstractWallet {
this._lastBalanceFetch = 0;
this.preferredBalanceUnit = BitcoinUnit.BTC;
this.chain = Chain.ONCHAIN;
this.hideBalance = false;
}
getID() {
return createHash('sha256')
.update(this.getSecret())
.digest()
.toString('hex');
}
getTransactions() {
@ -42,6 +50,10 @@ export class AbstractWallet {
return this.label;
}
getXpub() {
return this._address;
}
/**
*
* @returns {number} Available to spend amount, int, in sats
@ -67,6 +79,10 @@ export class AbstractWallet {
return true;
}
allowSendMax(): boolean {
return false;
}
allowRBF() {
return false;
}
@ -75,6 +91,10 @@ export class AbstractWallet {
return false;
}
weOwnAddress(address) {
return this._address === address;
}
/**
* Returns delta of unconfirmed balance. For example, if theres no
* unconfirmed balance its 0
@ -104,4 +124,12 @@ export class AbstractWallet {
}
// createTx () { throw Error('not implemented') }
getAddress() {
throw Error('not implemented');
}
getAddressAsync() {
return new Promise(resolve => resolve(this.getAddress()));
}
}

78
class/app-storage.js

@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-community/async-storage';
import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store';
import {
HDLegacyBreadwalletWallet,
HDSegwitP2SHWallet,
@ -11,6 +12,7 @@ import {
} from './';
import { LightningCustodianWallet } from './lightning-custodian-wallet';
import WatchConnectivity from '../WatchConnectivity';
import DeviceQuickActions from './quickActions';
const encryption = require('../encryption');
export class AppStorage {
@ -56,10 +58,41 @@ export class AppStorage {
};
}
/**
* Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is
* used for cli/tests
*
* @param key
* @param value
* @returns {Promise<any>|Promise<any> | Promise<void> | * | Promise | void}
*/
setItem(key, value) {
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
return RNSecureKeyStore.set(key, value, { accessible: ACCESSIBLE.WHEN_UNLOCKED });
} else {
return AsyncStorage.setItem(key, value);
}
}
/**
* Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is
* used for cli/tests
*
* @param key
* @returns {Promise<any>|*}
*/
getItem(key) {
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
return RNSecureKeyStore.get(key);
} else {
return AsyncStorage.getItem(key);
}
}
async storageIsEncrypted() {
let data;
try {
data = await AsyncStorage.getItem(AppStorage.FLAG_ENCRYPTED);
data = await this.getItem(AppStorage.FLAG_ENCRYPTED);
} catch (error) {
return false;
}
@ -95,7 +128,7 @@ export class AppStorage {
async encryptStorage(password) {
// assuming the storage is not yet encrypted
await this.saveToDisk();
let data = await AsyncStorage.getItem('data');
let data = await this.getItem('data');
// TODO: refactor ^^^ (should not save & load to fetch data)
let encrypted = encryption.encrypt(data, password);
@ -103,8 +136,10 @@ export class AppStorage {
data.push(encrypted); // putting in array as we might have many buckets with storages
data = JSON.stringify(data);
this.cachedPassword = password;
await AsyncStorage.setItem('data', data);
await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, '1');
await this.setItem('data', data);
await this.setItem(AppStorage.FLAG_ENCRYPTED, '1');
DeviceQuickActions.clearShortcutItems();
DeviceQuickActions.removeAllWallets();
}
/**
@ -122,13 +157,13 @@ export class AppStorage {
tx_metadata: {},
};
let buckets = await AsyncStorage.getItem('data');
let buckets = await this.getItem('data');
buckets = JSON.parse(buckets);
buckets.push(encryption.encrypt(JSON.stringify(data), fakePassword));
this.cachedPassword = fakePassword;
const bucketsString = JSON.stringify(buckets);
await AsyncStorage.setItem('data', bucketsString);
return (await AsyncStorage.getItem('data')) === bucketsString;
await this.setItem('data', bucketsString);
return (await this.getItem('data')) === bucketsString;
}
/**
@ -140,7 +175,7 @@ export class AppStorage {
*/
async loadFromDisk(password) {
try {
let data = await AsyncStorage.getItem('data');
let data = await this.getItem('data');
if (password) {
data = this.decryptData(data, password);
if (data) {
@ -212,8 +247,15 @@ export class AppStorage {
this.tx_metadata = data.tx_metadata;
}
}
WatchConnectivity.init();
await WatchConnectivity.shared.sendWalletsToWatch();
WatchConnectivity.shared.wallets = this.wallets;
WatchConnectivity.shared.tx_metadata = this.tx_metadata;
WatchConnectivity.shared.fetchTransactionsFunction = async () => {
await this.fetchWalletTransactions();
await this.saveToDisk();
};
await WatchConnectivity.shared.sendWalletsToWatch(this.wallets);
DeviceQuickActions.setWallets(this.wallets);
DeviceQuickActions.setQuickActions();
return true;
} else {
return false; // failed loading data or loading/decryptin data
@ -233,6 +275,7 @@ export class AppStorage {
deleteWallet(wallet) {
let secret = wallet.getSecret();
let tempWallets = [];
for (let value of this.wallets) {
if (value.getSecret() === secret) {
// the one we should delete
@ -250,7 +293,7 @@ export class AppStorage {
* If cached password is saved - finds the correct bucket
* to save to, encrypts and then saves.
*
* @returns {Promise} Result of AsyncStorage save
* @returns {Promise} Result of storage save
*/
async saveToDisk() {
let walletsToSave = [];
@ -267,7 +310,7 @@ export class AppStorage {
if (this.cachedPassword) {
// should find the correct bucket, encrypt and then save
let buckets = await AsyncStorage.getItem('data');
let buckets = await this.getItem('data');
buckets = JSON.parse(buckets);
let newData = [];
for (let bucket of buckets) {
@ -279,16 +322,19 @@ export class AppStorage {
// decrypted ok, this is our bucket
// we serialize our object's data, encrypt it, and add it to buckets
newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword));
await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, '1');
await this.setItem(AppStorage.FLAG_ENCRYPTED, '1');
}
}
data = newData;
} else {
await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, ''); // drop the flag
await this.setItem(AppStorage.FLAG_ENCRYPTED, ''); // drop the flag
}
WatchConnectivity.init();
WatchConnectivity.shared.wallets = this.wallets;
WatchConnectivity.shared.tx_metadata = this.tx_metadata;
WatchConnectivity.shared.sendWalletsToWatch();
return AsyncStorage.setItem('data', JSON.stringify(data));
DeviceQuickActions.setWallets(this.wallets);
DeviceQuickActions.setQuickActions();
return this.setItem('data', JSON.stringify(data));
}
/**

61
class/biometrics.js

@ -0,0 +1,61 @@
import Biometrics from 'react-native-biometrics';
const BlueApp = require('../BlueApp');
export default class Biometric {
static STORAGEKEY = 'Biometrics';
static FaceID = Biometrics.FaceID;
static TouchID = Biometrics.TouchID;
static Biometrics = Biometrics.Biometrics;
static async isDeviceBiometricCapable() {
const isDeviceBiometricCapable = await Biometrics.isSensorAvailable();
if (isDeviceBiometricCapable.available) {
return true;
}
Biometric.setBiometricUseEnabled(false);
return false;
}
static async biometricType() {
try {
const isSensorAvailable = await Biometrics.isSensorAvailable();
return isSensorAvailable.biometryType;
} catch (e) {
console.log(e);
}
return false;
}
static async isBiometricUseEnabled() {
try {
const enabledBiometrics = await BlueApp.getItem(Biometric.STORAGEKEY);
return !!enabledBiometrics;
} catch (_e) {
await BlueApp.setItem(Biometric.STORAGEKEY, '');
return false;
}
}
static async isBiometricUseCapableAndEnabled() {
const isBiometricUseEnabled = await Biometric.isBiometricUseEnabled();
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
return isBiometricUseEnabled && isDeviceBiometricCapable;
}
static async setBiometricUseEnabled(value) {
await BlueApp.setItem(Biometric.STORAGEKEY, value === true ? '1' : '');
}
static async unlockWithBiometrics() {
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
if (isDeviceBiometricCapable) {
try {
const isConfirmed = await Biometrics.simplePrompt({ promptMessage: 'Please confirm your identity.' });
return isConfirmed.success;
} catch (_e) {
return false;
}
}
return false;
}
}

23
class/hd-legacy-breadwallet-wallet.js

@ -1,7 +1,8 @@
import { AbstractHDWallet } from './abstract-hd-wallet';
import Frisbee from 'frisbee';
import bitcoin from 'bitcoinjs-lib';
import bip39 from 'bip39';
const bip32 = require('bip32');
const bitcoinjs = require('bitcoinjs-lib');
/**
* HD Wallet (BIP39).
@ -22,7 +23,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
}
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bip32.fromSeed(seed);
const path = "m/0'";
const child = root.derivePath(path).neutered();
@ -36,12 +37,16 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bip32.fromSeed(seed);
const path = "m/0'/0/" + index;
const child = root.derivePath(path);
return (this.external_addresses_cache[index] = child.getAddress());
const address = bitcoinjs.payments.p2pkh({
pubkey: child.publicKey,
}).address;
return (this.external_addresses_cache[index] = address);
}
_getInternalAddressByIndex(index) {
@ -49,12 +54,16 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bip32.fromSeed(seed);
const path = "m/0'/1/" + index;
const child = root.derivePath(path);
return (this.internal_addresses_cache[index] = child.getAddress());
const address = bitcoinjs.payments.p2pkh({
pubkey: child.publicKey,
}).address;
return (this.internal_addresses_cache[index] = address);
}
_getExternalWIFByIndex(index) {
@ -75,7 +84,7 @@ export class HDLegacyBreadwalletWallet extends AbstractHDWallet {
_getWIFByIndex(internal, index) {
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bitcoinjs.bip32.fromSeed(seed);
const path = `m/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);

28
class/hd-legacy-p2pkh-wallet.js

@ -1,8 +1,9 @@
import { AbstractHDWallet } from './abstract-hd-wallet';
import bitcoin from 'bitcoinjs-lib';
import bip39 from 'bip39';
import BigNumber from 'bignumber.js';
import signer from '../models/signer';
const bitcoin = require('bitcoinjs-lib');
const HDNode = require('bip32');
/**
* HD Wallet (BIP39).
@ -23,7 +24,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
}
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bitcoin.bip32.fromSeed(seed);
const path = "m/44'/0'/0'";
const child = root.derivePath(path).neutered();
@ -50,22 +51,22 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
_getWIFByIndex(internal, index) {
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = HDNode.fromSeed(seed);
const path = `m/44'/0'/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);
return child.keyPair.toWIF();
return child.toWIF();
}
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
const node = bitcoin.HDNode.fromBase58(this.getXpub());
const address = node
.derive(0)
.derive(index)
.getAddress();
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2pkh({
pubkey: node.derive(0).derive(index).publicKey,
}).address;
return (this.external_addresses_cache[index] = address);
}
@ -74,11 +75,10 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
const node = bitcoin.HDNode.fromBase58(this.getXpub());
const address = node
.derive(1)
.derive(index)
.getAddress();
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2pkh({
pubkey: node.derive(1).derive(index).publicKey,
}).address;
return (this.internal_addresses_cache[index] = address);
}

2
class/hd-segwit-bech32-transaction.js

@ -1,5 +1,5 @@
import { HDSegwitBech32Wallet, SegwitBech32Wallet } from './';
const bitcoin = require('bitcoinjs5');
const bitcoin = require('bitcoinjs-lib');
const BlueElectrum = require('../BlueElectrum');
const reverse = require('buffer-reverse');
const BigNumber = require('bignumber.js');

160
class/hd-segwit-bech32-wallet.js

@ -3,8 +3,8 @@ import { NativeModules } from 'react-native';
import bip39 from 'bip39';
import BigNumber from 'bignumber.js';
import b58 from 'bs58check';
const bitcoin = require('bitcoinjs-lib');
const BlueElectrum = require('../BlueElectrum');
const bitcoin5 = require('bitcoinjs5');
const HDNode = require('bip32');
const coinSelectAccumulative = require('coinselect/accumulative');
const coinSelectSplit = require('coinselect/split');
@ -36,6 +36,10 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
return true;
}
allowSendMax(): boolean {
return true;
}
/**
* @inheritDoc
*/
@ -110,10 +114,11 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
* Get internal/external WIF by wallet index
* @param {Boolean} internal
* @param {Number} index
* @returns {*}
* @returns {string|false} Either string WIF or FALSE if error happened
* @private
*/
_getWIFByIndex(internal, index) {
if (!this.secret) return false;
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = HDNode.fromSeed(seed);
@ -163,6 +168,30 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
}
}
_getNodePubkeyByIndex(node, index) {
index = index * 1; // cast to int
if (node === 0 && !this._node0) {
const xpub = this.constructor._zpubToXpub(this.getXpub());
const hdNode = HDNode.fromBase58(xpub);
this._node0 = hdNode.derive(node);
}
if (node === 1 && !this._node1) {
const xpub = this.constructor._zpubToXpub(this.getXpub());
const hdNode = HDNode.fromBase58(xpub);
this._node1 = hdNode.derive(node);
}
if (node === 0) {
return this._node0.derive(index).publicKey;
}
if (node === 1) {
return this._node1.derive(index).publicKey;
}
}
_getExternalAddressByIndex(index) {
return this._getNodeAddressByIndex(0, index);
}
@ -290,7 +319,7 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
for (let tx of Object.values(txdatas)) {
for (let vin of tx.vin) {
if (vin.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) {
if (vin.addresses && vin.addresses.indexOf(this._getExternalAddressByIndex(c)) !== -1) {
// this TX is related to our address
this._txs_by_external_index[c] = this._txs_by_external_index[c] || [];
let clonedTx = Object.assign({}, tx);
@ -337,7 +366,7 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
for (let tx of Object.values(txdatas)) {
for (let vin of tx.vin) {
if (vin.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) {
if (vin.addresses && vin.addresses.indexOf(this._getInternalAddressByIndex(c)) !== -1) {
// this TX is related to our address
this._txs_by_internal_index[c] = this._txs_by_internal_index[c] || [];
let clonedTx = Object.assign({}, tx);
@ -646,6 +675,29 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
return this._utxo;
}
_getDerivationPathByAddress(address) {
const path = "m/84'/0'/0'";
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._getExternalAddressByIndex(c) === address) return path + '/0/' + c;
}
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._getInternalAddressByIndex(c) === address) return path + '/1/' + c;
}
return false;
}
_getPubkeyByAddress(address) {
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._getExternalAddressByIndex(c) === address) return this._getNodePubkeyByIndex(0, c);
}
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
if (this._getInternalAddressByIndex(c) === address) return this._getNodePubkeyByIndex(1, c);
}
return false;
}
weOwnAddress(address) {
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
if (this._getExternalAddressByIndex(c) === address) return true;
@ -670,9 +722,10 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
* @param feeRate {Number} satoshi per byte
* @param changeAddress {String} Excessive coins will go back to that address
* @param sequence {Number} Used in RBF
* @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number}}
* @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case
* @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}}
*/
createTransaction(utxos, targets, feeRate, changeAddress, sequence) {
createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false) {
if (!changeAddress) throw new Error('No change address provided');
sequence = sequence || HDSegwitBech32Wallet.defaultRBFSequence;
@ -689,37 +742,108 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
throw new Error('Not enough balance. Try sending smaller amount');
}
let txb = new bitcoin5.TransactionBuilder();
let psbt = new bitcoin.Psbt();
let c = 0;
let keypairs = {};
let values = {};
inputs.forEach(input => {
const keyPair = bitcoin5.ECPair.fromWIF(this._getWifForAddress(input.address));
keypairs[c] = keyPair;
let keyPair;
if (!skipSigning) {
// skiping signing related stuff
keyPair = bitcoin.ECPair.fromWIF(this._getWifForAddress(input.address));
keypairs[c] = keyPair;
}
values[c] = input.value;
c++;
if (!input.address || !this._getWifForAddress(input.address)) throw new Error('Internal error: no address or WIF to sign input');
const p2wpkh = bitcoin5.payments.p2wpkh({ pubkey: keyPair.publicKey });
txb.addInput(input.txId, input.vout, sequence, p2wpkh.output); // NOTE: provide the prevOutScript!
if (!skipSigning) {
// skiping signing related stuff
if (!input.address || !this._getWifForAddress(input.address)) throw new Error('Internal error: no address or WIF to sign input');
}
let pubkey = this._getPubkeyByAddress(input.address);
let masterFingerprint = Buffer.from([0x00, 0x00, 0x00, 0x00]);
// this is not correct fingerprint, as we dont know real fingerprint - we got zpub with 84/0, but fingerpting
// should be from root. basically, fingerprint should be provided from outside by user when importing zpub
let path = this._getDerivationPathByAddress(input.address);
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey });
psbt.addInput({
hash: input.txId,
index: input.vout,
sequence,
bip32Derivation: [
{
masterFingerprint,
path,
pubkey,
},
],
witnessUtxo: {
script: p2wpkh.output,
value: input.value,
},
});
});
outputs.forEach(output => {
// if output has no address - this is change output
let change = false;
if (!output.address) {
change = true;
output.address = changeAddress;
}
txb.addOutput(output.address, output.value);
let path = this._getDerivationPathByAddress(output.address);
let pubkey = this._getPubkeyByAddress(output.address);
let masterFingerprint = Buffer.from([0x00, 0x00, 0x00, 0x00]);
// this is not correct fingerprint, as we dont know realfingerprint - we got zpub with 84/0, but fingerpting
// should be from root. basically, fingerprint should be provided from outside by user when importing zpub
let outputData = {
address: output.address,
value: output.value,
};
if (change) {
outputData['bip32Derivation'] = [
{
masterFingerprint,
path,
pubkey,
},
];
}
psbt.addOutput(outputData);
});
for (let cc = 0; cc < c; cc++) {
txb.sign(cc, keypairs[cc], null, null, values[cc]); // NOTE: no redeem script
if (!skipSigning) {
// skiping signing related stuff
for (let cc = 0; cc < c; cc++) {
psbt.signInput(cc, keypairs[cc]);
}
}
const tx = txb.build();
return { tx, inputs, outputs, fee };
let tx;
if (!skipSigning) {
tx = psbt.finalizeAllInputs().extractTransaction();
}
return { tx, inputs, outputs, fee, psbt };
}
/**
* Combines 2 PSBTs into final transaction from which you can
* get HEX and broadcast
*
* @param base64one {string}
* @param base64two {string}
* @returns {Transaction}
*/
combinePsbt(base64one, base64two) {
const final1 = bitcoin.Psbt.fromBase64(base64one);
const final2 = bitcoin.Psbt.fromBase64(base64two);
final1.combine(final2);
return final1.finalizeAllInputs().extractTransaction();
}
/**
@ -729,7 +853,7 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet {
* @returns {String}
*/
static _nodeToBech32SegwitAddress(hdNode) {
return bitcoin5.payments.p2wpkh({
return bitcoin.payments.p2wpkh({
pubkey: hdNode.publicKey,
}).address;
}

32
class/hd-segwit-p2sh-wallet.js

@ -5,8 +5,8 @@ import bip39 from 'bip39';
import BigNumber from 'bignumber.js';
import b58 from 'bs58check';
import signer from '../models/signer';
import { BitcoinUnit } from '../models/bitcoinUnits';
const bitcoin = require('bitcoinjs-lib');
const bitcoin5 = require('bitcoinjs5');
const HDNode = require('bip32');
const { RNRandomBytes } = NativeModules;
@ -18,6 +18,7 @@ const { RNRandomBytes } = NativeModules;
*/
function ypubToXpub(ypub) {
let data = b58.decode(ypub);
if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!');
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
@ -30,8 +31,8 @@ function ypubToXpub(ypub) {
* @returns {String}
*/
function nodeToP2shSegwitAddress(hdNode) {
const { address } = bitcoin5.payments.p2sh({
redeem: bitcoin5.payments.p2wpkh({ pubkey: hdNode.publicKey }),
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: hdNode.publicKey }),
});
return address;
}
@ -49,6 +50,10 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
return true;
}
allowSendMax(): boolean {
return true;
}
async generate() {
let that = this;
return new Promise(function(resolve) {
@ -90,11 +95,11 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
_getWIFByIndex(internal, index) {
const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.HDNode.fromSeedBuffer(seed);
const root = bitcoin.bip32.fromSeed(seed);
const path = `m/49'/0'/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);
return child.keyPair.toWIF();
return bitcoin.ECPair.fromPrivateKey(child.privateKey).toWIF();
}
_getExternalAddressByIndex(index) {
@ -255,12 +260,29 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
}
}
/**
*
* @param utxos
* @param amount Either float (BTC) or string 'MAX' (BitcoinUnit.MAX) to send all
* @param fee
* @param address
* @returns {string}
*/
createTx(utxos, amount, fee, address) {
for (let utxo of utxos) {
utxo.wif = this._getWifForAddress(utxo.address);
}
let amountPlusFee = parseFloat(new BigNumber(amount).plus(fee).toString(10));
if (amount === BitcoinUnit.MAX) {
amountPlusFee = new BigNumber(0);
for (let utxo of utxos) {
amountPlusFee = amountPlusFee.plus(utxo.amount);
}
amountPlusFee = amountPlusFee.dividedBy(100000000).toString(10);
}
return signer.createHDSegwitTransaction(
utxos,
address,

6
class/legacy-wallet.js

@ -3,9 +3,9 @@ import { SegwitBech32Wallet } from './';
import { useBlockcypherTokens } from './constants';
import Frisbee from 'frisbee';
import { NativeModules } from 'react-native';
const bitcoin = require('bitcoinjs-lib');
const { RNRandomBytes } = NativeModules;
const BigNumber = require('bignumber.js');
const bitcoin = require('bitcoinjs-lib');
const signer = require('../models/signer');
const BlueElectrum = require('../BlueElectrum');
@ -85,7 +85,9 @@ export class LegacyWallet extends AbstractWallet {
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
address = keyPair.getAddress();
address = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
}).address;
} catch (err) {
return false;
}

14
class/lightning-custodian-wallet.js

@ -44,7 +44,11 @@ export class LightningCustodianWallet extends LegacyWallet {
}
getAddress() {
return '';
if (this.refill_addressess.length > 0) {
return this.refill_addressess[0];
} else {
return undefined;
}
}
getSecret() {
@ -199,6 +203,10 @@ export class LightningCustodianWallet extends LegacyWallet {
await this.getUserInvoices();
}
isInvoiceGeneratedByWallet(paymentRequest) {
return this.user_invoices_raw.some(invoice => invoice.payment_request === paymentRequest);
}
async addInvoice(amt, memo) {
let response = await this._api.post('/addinvoice', {
body: { amt: amt + '', memo: memo },
@ -354,6 +362,10 @@ export class LightningCustodianWallet extends LegacyWallet {
}
}
async getAddressAsync() {
return this.fetchBtcAddress();
}
getTransactions() {
let txs = [];
this.pending_transactions_raw = this.pending_transactions_raw || [];

45
class/onAppLaunch.js

@ -0,0 +1,45 @@
import AsyncStorage from '@react-native-community/async-storage';
const BlueApp = require('../BlueApp');
export default class OnAppLaunch {
static STORAGE_KEY = 'ONAPP_LAUNCH_SELECTED_DEFAULT_WALLET_KEY';
static async isViewAllWalletsEnabled() {
try {
const selectedDefaultWallet = await AsyncStorage.getItem(OnAppLaunch.STORAGE_KEY);
return selectedDefaultWallet === '' || selectedDefaultWallet === null;
} catch (_e) {
return true;
}
}
static async setViewAllWalletsEnabled(value) {
if (!value) {
const selectedDefaultWallet = await OnAppLaunch.getSelectedDefaultWallet();
if (!selectedDefaultWallet) {
const firstWallet = BlueApp.getWallets()[0];
await OnAppLaunch.setSelectedDefaultWallet(firstWallet.getID());
}
} else {
await AsyncStorage.setItem(OnAppLaunch.STORAGE_KEY, '');
}
}
static async getSelectedDefaultWallet() {
let selectedWallet = false;
try {
const selectedWalletID = JSON.parse(await AsyncStorage.getItem(OnAppLaunch.STORAGE_KEY));
selectedWallet = BlueApp.getWallets().find(wallet => wallet.getID() === selectedWalletID);
if (!selectedWallet) {
await AsyncStorage.setItem(OnAppLaunch.STORAGE_KEY, '');
}
} catch (_e) {
return false;
}
return selectedWallet;
}
static async setSelectedDefaultWallet(value) {
await AsyncStorage.setItem(OnAppLaunch.STORAGE_KEY, JSON.stringify(value));
}
}

46
class/quickActions.js

@ -0,0 +1,46 @@
import QuickActions from 'react-native-quick-actions';
import { Platform } from 'react-native';
export default class DeviceQuickActions {
static shared = new DeviceQuickActions();
wallets;
static setWallets(wallets) {
DeviceQuickActions.shared.wallets = wallets.slice(0, 4);
}
static removeAllWallets() {
DeviceQuickActions.shared.wallets = undefined;
}
static setQuickActions() {
if (DeviceQuickActions.shared.wallets === undefined) {
return;
}
QuickActions.isSupported((error, supported) => {
if (supported && error === null) {
let shortcutItems = [];
const loc = require('../loc/');
for (const wallet of DeviceQuickActions.shared.wallets) {
shortcutItems.push({
type: 'Wallets', // Required
title: wallet.getLabel(), // Optional, if empty, `type` will be used instead
subtitle:
wallet.hideBalance || wallet.getBalance() <= 0
? ''
: loc.formatBalance(Number(wallet.getBalance()), wallet.getPreferredBalanceUnit(), true),
userInfo: {
url: `bluewallet://wallet/${wallet.getID()}`, // Provide any custom data like deep linking URL
},
icon: Platform.select({ android: 'quickactions', ios: 'bookmark' }),
});
}
QuickActions.setShortcutItems(shortcutItems);
}
});
}
static clearShortcutItems() {
QuickActions.clearShortcutItems();
}
}

30
class/segwit-bech-wallet.js

@ -10,9 +10,9 @@ export class SegwitBech32Wallet extends LegacyWallet {
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
let pubKey = keyPair.getPublicKeyBuffer();
let scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey));
address = bitcoin.address.fromOutputScript(scriptPubKey);
address = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
}).address;
} catch (err) {
return false;
}
@ -23,13 +23,29 @@ export class SegwitBech32Wallet extends LegacyWallet {
static witnessToAddress(witness) {
const pubKey = Buffer.from(witness, 'hex');
const pubKeyHash = bitcoin.crypto.hash160(pubKey);
const scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash);
return bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.bitcoin);
return bitcoin.payments.p2wpkh({
pubkey: pubKey,
network: bitcoin.networks.bitcoin,
}).address;
}
/**
* Converts script pub key to bech32 address if it can. Returns FALSE if it cant.
*
* @param scriptPubKey
* @returns {boolean|string} Either bech32 address or false
*/
static scriptPubKeyToAddress(scriptPubKey) {
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
return bitcoin.address.fromOutputScript(scriptPubKey2, bitcoin.networks.bitcoin);
let ret;
try {
ret = bitcoin.payments.p2wpkh({
output: scriptPubKey2,
network: bitcoin.networks.bitcoin,
}).address;
} catch (_) {
return false;
}
return ret;
}
}

47
class/segwit-p2sh-wallet.js

@ -3,6 +3,21 @@ const bitcoin = require('bitcoinjs-lib');
const signer = require('../models/signer');
const BigNumber = require('bignumber.js');
/**
* Creates Segwit P2SH Bitcoin address
* @param pubkey
* @param network
* @returns {String}
*/
function pubkeyToP2shSegwitAddress(pubkey, network) {
network = network || bitcoin.networks.bitcoin;
const { address } = bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey, network }),
network,
});
return address;
}
export class SegwitP2SHWallet extends LegacyWallet {
static type = 'segwitP2SH';
static typeReadable = 'SegWit (P2SH)';
@ -13,11 +28,27 @@ export class SegwitP2SHWallet extends LegacyWallet {
static witnessToAddress(witness) {
const pubKey = Buffer.from(witness, 'hex');
const pubKeyHash = bitcoin.crypto.hash160(pubKey);
const redeemScript = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash);
const redeemScriptHash = bitcoin.crypto.hash160(redeemScript);
const scriptPubkey = bitcoin.script.scriptHash.output.encode(redeemScriptHash);
return bitcoin.address.fromOutputScript(scriptPubkey, bitcoin.networks.bitcoin);
return pubkeyToP2shSegwitAddress(pubKey);
}
/**
* Converts script pub key to p2sh address if it can. Returns FALSE if it cant.
*
* @param scriptPubKey
* @returns {boolean|string} Either p2sh address or false
*/
static scriptPubKeyToAddress(scriptPubKey) {
const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex');
let ret;
try {
ret = bitcoin.payments.p2sh({
output: scriptPubKey2,
network: bitcoin.networks.bitcoin,
}).address;
} catch (_) {
return false;
}
return ret;
}
getAddress() {
@ -25,14 +56,12 @@ export class SegwitP2SHWallet extends LegacyWallet {
let address;
try {
let keyPair = bitcoin.ECPair.fromWIF(this.secret);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKey = keyPair.publicKey;
if (!keyPair.compressed) {
console.warn('only compressed public keys are good for segwit');
return false;
}
let witnessScript = bitcoin.script.witnessPubKeyHash.output.encode(bitcoin.crypto.hash160(pubKey));
let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(witnessScript));
address = bitcoin.address.fromOutputScript(scriptPubKey);
address = pubkeyToP2shSegwitAddress(pubKey);
} catch (err) {
return false;
}

67
class/watch-only-wallet.js

@ -8,8 +8,25 @@ export class WatchOnlyWallet extends LegacyWallet {
static type = 'watchOnly';
static typeReadable = 'Watch-only';
constructor() {
super();
this.use_with_hardware_wallet = false;
}
allowSend() {
return false;
return !!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSend();
}
allowBatchSend() {
return (
!!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowBatchSend()
);
}
allowSendMax() {
return (
!!this.use_with_hardware_wallet && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSendMax()
);
}
getAddress() {
@ -48,6 +65,10 @@ export class WatchOnlyWallet extends LegacyWallet {
for (let k of Object.keys(this._hdWalletInstance)) {
hdWalletInstance[k] = this._hdWalletInstance[k];
}
// deleting properties that cant survive serialization/deserialization:
delete hdWalletInstance._node1;
delete hdWalletInstance._node0;
}
this._hdWalletInstance = hdWalletInstance;
}
@ -83,7 +104,51 @@ export class WatchOnlyWallet extends LegacyWallet {
}
async getAddressAsync() {
if (this.isAddressValid(this.secret)) return new Promise(resolve => resolve(this.secret));
if (this._hdWalletInstance) return this._hdWalletInstance.getAddressAsync();
throw new Error('Not initialized');
}
async _getExternalAddressByIndex(index) {
if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index);
throw new Error('Not initialized');
}
async getChangeAddressAsync() {
if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync();
throw new Error('Not initialized');
}
async fetchUtxo() {
if (this._hdWalletInstance) return this._hdWalletInstance.fetchUtxo();
throw new Error('Not initialized');
}
getUtxo() {
if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo();
throw new Error('Not initialized');
}
combinePsbt(base64one, base64two) {
if (this._hdWalletInstance) return this._hdWalletInstance.combinePsbt(base64one, base64two);
throw new Error('Not initialized');
}
broadcastTx(hex) {
if (this._hdWalletInstance) return this._hdWalletInstance.broadcastTx(hex);
throw new Error('Not initialized');
}
/**
* signature of this method is the same ad BIP84 createTransaction, BUT this method should be used to create
* unsinged PSBT to be used with HW wallet (or other external signer)
* @see HDSegwitBech32Wallet.createTransaction
*/
createTransaction(utxos, targets, feeRate, changeAddress, sequence) {
if (this._hdWalletInstance instanceof HDSegwitBech32Wallet) {
return this._hdWalletInstance.createTransaction(utxos, targets, feeRate, changeAddress, sequence, true);
} else {
throw new Error('Not a zpub watch-only wallet, cant create PSBT (or just not initialized)');
}
}
}

21
currency.js

@ -2,6 +2,8 @@ import Frisbee from 'frisbee';
import AsyncStorage from '@react-native-community/async-storage';
import { AppStorage } from './class';
import { FiatUnit } from './models/fiatUnit';
import DefaultPreference from 'react-native-default-preference';
import DeviceQuickActions from './class/quickActions';
let BigNumber = require('bignumber.js');
let preferredFiatCurrency = FiatUnit.USD;
let exchangeRates = {};
@ -19,10 +21,17 @@ const STRUCT = {
*/
async function setPrefferedCurrency(item) {
await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(item));
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.set('preferredCurrency', item.endPointKey);
await DefaultPreference.set('preferredCurrencyLocale', item.locale.replace('-', '_'));
DeviceQuickActions.setQuickActions();
}
async function getPreferredCurrency() {
return JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY));
let preferredCurrency = await JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY));
await DefaultPreference.set('preferredCurrency', preferredCurrency.endPointKey);
await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_'));
return preferredCurrency;
}
async function updateExchangeRate() {
@ -48,6 +57,8 @@ async function updateExchangeRate() {
}
} catch (Err) {
console.warn(Err);
const lastSavedExchangeRate = JSON.parse(await AsyncStorage.getItem(AppStorage.EXCHANGE_RATES));
exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = lastSavedExchangeRate['BTC_' + preferredFiatCurrency.endPointKey] * 1;
return;
}
@ -55,6 +66,7 @@ async function updateExchangeRate() {
exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = json.bpi[preferredFiatCurrency.endPointKey].rate_float * 1;
await AsyncStorage.setItem(AppStorage.EXCHANGE_RATES, JSON.stringify(exchangeRates));
await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency));
DeviceQuickActions.setQuickActions();
}
let interval = false;
@ -69,7 +81,10 @@ async function startUpdater() {
}
function satoshiToLocalCurrency(satoshi) {
if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) return satoshi;
if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) {
startUpdater();
return '...';
}
let b = new BigNumber(satoshi);
b = b
@ -109,7 +124,7 @@ function BTCToLocalCurrency(bitcoin) {
function satoshiToBTC(satoshi) {
let b = new BigNumber(satoshi);
b = b.dividedBy(100000000);
return b.toString(10) + ' BTC';
return b.toString(10);
}
module.exports.updateExchangeRate = updateExchangeRate;

2
edit-version-number.sh

@ -1,5 +1,7 @@
vim ios/BlueWallet/Info.plist
vim ios/BlueWalletWatch/Info.plist
vim "ios/BlueWalletWatch Extension/Info.plist"
vim "ios/TodayExtension/Info.plist"
vim android/app/build.gradle
vim package.json
vim package-lock.json

1
img/bluewalletsplash.json

File diff suppressed because one or more lines are too long

BIN
img/close-white.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

BIN
img/close-white@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

BIN
img/close-white@3x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

BIN
img/faceid.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
img/fingerprint.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

62
index.js

@ -2,16 +2,15 @@ import 'intl';
import 'intl/locale-data/jsonp/en';
import React from 'react';
import './shim.js';
import App from './App';
import { Sentry } from 'react-native-sentry';
import { AppRegistry } from 'react-native';
import WalletMigrate from './screen/wallets/walletMigrate';
import { name as appName } from './app.json';
import App from './App';
import LottieView from 'lottie-react-native';
import UnlockWith from './UnlockWith.js';
/** @type {AppStorage} */
const BlueApp = require('./BlueApp');
if (process.env.NODE_ENV !== 'development') {
Sentry.config('https://23377936131848ca8003448a893cb622@sentry.io/1295736').install();
}
const A = require('./analytics');
if (!Error.captureStackTrace) {
// captureStackTrace is only available when debugging
@ -21,16 +20,61 @@ if (!Error.captureStackTrace) {
class BlueAppComponent extends React.Component {
constructor(props) {
super(props);
this.state = { isMigratingData: true };
this.state = { isMigratingData: true, onAnimationFinished: false, successfullyAuthenticated: false };
}
componentDidMount() {
const walletMigrate = new WalletMigrate(this.setIsMigratingData);
walletMigrate.start();
}
setIsMigratingData = async () => {
await BlueApp.startAndDecrypt();
A(A.ENUM.INIT);
this.setState({ isMigratingData: false });
};
onAnimationFinish = () => {
if (this.state.isMigratingData) {
this.loadingSplash.play(0);
} else {
this.setState({ onAnimationFinished: true });
}
};
onSuccessfullyAuthenticated = () => {
this.setState({ successfullyAuthenticated: true });
};
render() {
return this.state.isMigratingData ? <WalletMigrate onComplete={this.setIsMigratingData} /> : <App />;
if (this.state.isMigratingData) {
return (
<LottieView
ref={ref => (this.loadingSplash = ref)}
onAnimationFinish={this.onAnimationFinish}
source={require('./img/bluewalletsplash.json')}
autoPlay
loop={false}
/>
);
} else {
if (this.state.onAnimationFinished) {
return this.state.successfullyAuthenticated ? (
<App />
) : (
<UnlockWith onSuccessfullyAuthenticated={this.onSuccessfullyAuthenticated} />
);
} else {
return (
<LottieView
ref={ref => (this.loadingSplash = ref)}
onAnimationFinish={this.onAnimationFinish}
source={require('./img/bluewalletsplash.json')}
autoPlay
loop={false}
/>
);
}
}
}
}

8
ios/AppCenter-Config.plist

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppSecret</key>
<string>e83710b1-61c2-497b-b0f7-c3b6ab79f2d8</string>
</dict>
</plist>

4
ios/BlueWallet-Bridging-Header.h

@ -0,0 +1,4 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

330
ios/BlueWallet.xcodeproj/project.pbxproj

@ -8,10 +8,7 @@
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* BlueWalletTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* BlueWalletTests.m */; };
01AB943FA0794E91B65F0BFE /* libRCTPrivacySnapshot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FC63C7054F1C4FDFB7A830E5 /* libRCTPrivacySnapshot.a */; };
036397B3AA70DD314F31661C /* libPods-BlueWalletWatch Extension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 154B05BEF3C3512F67A08374 /* libPods-BlueWalletWatch Extension.a */; };
0AF37AC0E67044038B49FB3B /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4D746BBE67E84684848246E2 /* SimpleLineIcons.ttf */; };
0B2C4EBFB4CB4960AAD777BC /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 44BC9E3EE0E9476A830CCCB9 /* Entypo.ttf */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
@ -22,17 +19,18 @@
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2D16E6881FA4F8E400B85C8A /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D16E6891FA4F8E400B85C8A /* libReact.a */; };
2DCD954D1E0B4F2C00145EB5 /* BlueWalletTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* BlueWalletTests.m */; };
34582CAA4AD140F7B80C961A /* libTcpSockets.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DF4E6C040764E4BA1ACC1EB /* libTcpSockets.a */; };
34CC55B441594DBB95AD1B50 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */; };
32002D9D236FAA9F00B93396 /* TodayDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32002D9C236FAA9F00B93396 /* TodayDataStore.swift */; };
3208E93922F63279007F5A27 /* AppCenter-Config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3208E93822F63279007F5A27 /* AppCenter-Config.plist */; };
3271B0AB236E2E0700DA766F /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3271B0AA236E2E0700DA766F /* NotificationCenter.framework */; };
3271B0AE236E2E0700DA766F /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3271B0AD236E2E0700DA766F /* TodayViewController.swift */; };
3271B0B1236E2E0700DA766F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3271B0AF236E2E0700DA766F /* MainInterface.storyboard */; };
3271B0B5236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
3271B0BB236E329400DA766F /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3271B0BA236E329400DA766F /* API.swift */; };
32B5A32A2334450100F8D608 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5A3292334450100F8D608 /* Bridge.swift */; };
32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F0A2992311DBB20095C559 /* ComplicationController.swift */; };
398DED6337DF58F0ECFD8F2E /* libPods-BlueWalletTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 70089FECE936F9A0AC45B7CE /* libPods-BlueWalletTests.a */; };
3EEBC6F85642487DA7C4EE35 /* AntDesign.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C4496FB303574862B40A878A /* AntDesign.ttf */; };
62A1DD9674CD479ABAA3D622 /* Feather.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A9166D490AEF4938BD6621CF /* Feather.ttf */; };
66AB95FA29464B0BA106AA67 /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 04466491BA2D4876A71222FC /* Foundation.ttf */; };
6C313BF9BC3E4BD2A65AA547 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CF4A4D7AAD974D67A2D62B3E /* MaterialIcons.ttf */; };
7140A1CC26204118BA18DFA2 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 47C436B1EF23484B8181DBEA /* Zocial.ttf */; };
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B468CC34D5B41F3950078EF /* libsqlite3.0.tbd */; };
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B9D9B3A7B2CB4255876B67AF /* libz.tbd */; };
854972E4A6134C14A1D3A5F9 /* FontAwesome5_Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 47564776A7A3427DB36C087D /* FontAwesome5_Regular.ttf */; };
906451CAD44154C2950030EC /* libPods-BlueWallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */; };
B058E2132B704E9E874BDB29 /* libRNRandomBytes-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 253243E162CE4822BF3A3B7D /* libRNRandomBytes-tvOS.a */; };
B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E32225841EC00428FCC /* Interface.storyboard */; };
@ -55,16 +53,9 @@
B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0377225847C500FBAA95 /* WalletInformation.swift */; };
B44D665E562B4F289F09D327 /* libRNSVG-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F1F51A83D044F3BB26A35FC /* libRNSVG-tvOS.a */; };
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
C1056BF235EE4E23AAF21975 /* libRCTQRCodeLocalImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */; };
C50F1706310E40F3B28D4856 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3F7F1B8332C6439793D55B45 /* EvilIcons.ttf */; };
CACD479D705745BC8CF1026B /* FontAwesome5_Brands.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5A8F67CF29564E41882ECEF8 /* FontAwesome5_Brands.ttf */; };
CE21ACFC6EE18FE5B91A0212 /* libPods-BlueWalletWatch.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB0B98D8054B95DEE18B907F /* libPods-BlueWalletWatch.a */; };
CF81A1855609466D90635511 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CA741BA794714D3F80251AC9 /* Ionicons.ttf */; };
D5B495319D1B4542BE945CEA /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2FCC2CD6FF4448229D0CE0F3 /* MaterialCommunityIcons.ttf */; };
D6ED210441144516A0355B4A /* libRNVectorIcons-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E6B44173A8854B6D85D7F933 /* libRNVectorIcons-tvOS.a */; };
D8E3A15E21994BC3AF6CEECE /* FontAwesome5_Solid.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 78A87E7251D94144A71A2F67 /* FontAwesome5_Solid.ttf */; };
ED2971652150620600B7C4FE /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2971642150620600B7C4FE /* JavaScriptCore.framework */; };
F21429E1449249038A7F3444 /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 334051161886419EA186F4BA /* FontAwesome.ttf */; };
FBB34FB8F9B248A89346FE61 /* libRNDeviceInfo-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EB3338E347F4AFAA8C85C04 /* libRNDeviceInfo-tvOS.a */; };
/* End PBXBuildFile section */
@ -83,6 +74,13 @@
remoteGlobalIDString = 2D02E47A1E0B4A5D006451C7;
remoteInfo = "BlueWallet-tvOS";
};
3271B0B3236E2E0700DA766F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 3271B0A8236E2E0700DA766F;
remoteInfo = TodayExtension;
};
B40D4E3E225841ED00428FCC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
@ -100,6 +98,17 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
3271B0B6236E2E0700DA766F /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
3271B0B5236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
B40D4E2D225841C300428FCC /* Embed Watch Content */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@ -146,6 +155,20 @@
2D02E4901E0B4A5D006451C7 /* BlueWallet-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "BlueWallet-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
2FCC2CD6FF4448229D0CE0F3 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = "<group>"; };
32002D9C236FAA9F00B93396 /* TodayDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayDataStore.swift; sourceTree = "<group>"; };
3208E93822F63279007F5A27 /* AppCenter-Config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "AppCenter-Config.plist"; sourceTree = "<group>"; };
32475F792370F6D30070E6CF /* BlueWallet - Bitcoin Price.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWallet - Bitcoin Price.entitlements"; sourceTree = "<group>"; };
3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "BlueWallet - Bitcoin Price.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
3271B0AA236E2E0700DA766F /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; };
3271B0AD236E2E0700DA766F /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = "<group>"; };
3271B0B0236E2E0700DA766F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
3271B0B2236E2E0700DA766F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3271B0BA236E329400DA766F /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = "<group>"; };
32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWallet-Bridging-Header.h"; sourceTree = "<group>"; };
32B5A3292334450100F8D608 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = "<group>"; };
32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWalletWatch Extension.entitlements"; sourceTree = "<group>"; };
32F0A2502310B0910095C559 /* BlueWallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWallet.entitlements; path = BlueWallet/BlueWallet.entitlements; sourceTree = "<group>"; };
32F0A2992311DBB20095C559 /* ComplicationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationController.swift; sourceTree = "<group>"; };
334051161886419EA186F4BA /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
3703B10AAB374CF896CCC2EA /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = "<group>"; };
3F7F1B8332C6439793D55B45 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
@ -240,9 +263,6 @@
906451CAD44154C2950030EC /* libPods-BlueWallet.a in Frameworks */,
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
C1056BF235EE4E23AAF21975 /* libRCTQRCodeLocalImage.a in Frameworks */,
34582CAA4AD140F7B80C961A /* libTcpSockets.a in Frameworks */,
01AB943FA0794E91B65F0BFE /* libRCTPrivacySnapshot.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -267,6 +287,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
3271B0A6236E2E0700DA766F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3271B0AB236E2E0700DA766F /* NotificationCenter.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
421830728822A20A50D8A07C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -306,6 +334,8 @@
13B07FAE1A68108700A75B9A /* BlueWallet */ = {
isa = PBXGroup;
children = (
32F0A2502310B0910095C559 /* BlueWallet.entitlements */,
3208E93822F63279007F5A27 /* AppCenter-Config.plist */,
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
@ -313,6 +343,8 @@
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
32B5A3292334450100F8D608 /* Bridge.swift */,
32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */,
);
name = BlueWallet;
sourceTree = "<group>";
@ -334,10 +366,33 @@
154B05BEF3C3512F67A08374 /* libPods-BlueWalletWatch Extension.a */,
70089FECE936F9A0AC45B7CE /* libPods-BlueWalletTests.a */,
731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */,
3271B0AA236E2E0700DA766F /* NotificationCenter.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
3271B0AC236E2E0700DA766F /* TodayExtension */ = {
isa = PBXGroup;
children = (
32475F792370F6D30070E6CF /* BlueWallet - Bitcoin Price.entitlements */,
32A5F95E236E4A2A00443927 /* API */,
3271B0AD236E2E0700DA766F /* TodayViewController.swift */,
3271B0AF236E2E0700DA766F /* MainInterface.storyboard */,
3271B0B2236E2E0700DA766F /* Info.plist */,
32002D9C236FAA9F00B93396 /* TodayDataStore.swift */,
);
path = TodayExtension;
sourceTree = "<group>";
};
32A5F95E236E4A2A00443927 /* API */ = {
isa = PBXGroup;
children = (
3271B0BA236E329400DA766F /* API.swift */,
);
name = API;
path = "New Group";
sourceTree = "<group>";
};
4B0CACE36C3348E1BCEA92C8 /* Resources */ = {
isa = PBXGroup;
children = (
@ -367,6 +422,7 @@
00E356EF1AD99517003FC87E /* BlueWalletTests */,
B40D4E31225841EC00428FCC /* BlueWalletWatch */,
B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */,
3271B0AC236E2E0700DA766F /* TodayExtension */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
B40FE50A21FAD228005D5578 /* Recovered References */,
@ -387,6 +443,7 @@
2D02E4901E0B4A5D006451C7 /* BlueWallet-tvOSTests.xctest */,
B40D4E30225841EC00428FCC /* BlueWalletWatch.app */,
B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */,
3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */,
);
name = Products;
sourceTree = "<group>";
@ -419,8 +476,10 @@
B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */ = {
isa = PBXGroup;
children = (
32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */,
B43D03242258474500FBAA95 /* Objects */,
B40D4E672258426B00428FCC /* KeychainSwiftDistrib.swift */,
32F0A2992311DBB20095C559 /* ComplicationController.swift */,
B40D4E43225841ED00428FCC /* ExtensionDelegate.swift */,
B40D4E45225841ED00428FCC /* NotificationController.swift */,
B40D4E552258425400428FCC /* InterfaceController.swift */,
@ -513,11 +572,14 @@
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
B40D4E2D225841C300428FCC /* Embed Watch Content */,
791C03B6EF06B63A39F55115 /* [CP] Copy Pods Resources */,
2130DE983D1D45AC8FC45F7E /* Upload Debug Symbols to Sentry */,
3271B0B6236E2E0700DA766F /* Embed App Extensions */,
);
buildRules = (
);
dependencies = (
B40D4E4C225841ED00428FCC /* PBXTargetDependency */,
3271B0B4236E2E0700DA766F /* PBXTargetDependency */,
);
name = BlueWallet;
productName = "Hello World";
@ -560,6 +622,23 @@
productReference = 2D02E4901E0B4A5D006451C7 /* BlueWallet-tvOSTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
3271B0A8236E2E0700DA766F /* TodayExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3271B0B9236E2E0700DA766F /* Build configuration list for PBXNativeTarget "TodayExtension" */;
buildPhases = (
3271B0A5236E2E0700DA766F /* Sources */,
3271B0A6236E2E0700DA766F /* Frameworks */,
3271B0A7236E2E0700DA766F /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = TodayExtension;
productName = TodayExtension;
productReference = 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */;
productType = "com.apple.product-type.app-extension";
};
B40D4E2F225841EC00428FCC /* BlueWalletWatch */ = {
isa = PBXNativeTarget;
buildConfigurationList = B40D4E52225841ED00428FCC /* Build configuration list for PBXNativeTarget "BlueWalletWatch" */;
@ -603,7 +682,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1020;
LastSwiftUpdateCheck = 1120;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
@ -614,7 +693,13 @@
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = A7W54YZ4WU;
ProvisioningStyle = Manual;
LastSwiftMigration = 1030;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Keychain = {
enabled = 0;
};
};
};
2D02E47A1E0B4A5D006451C7 = {
CreatedOnToolsVersion = 8.2.1;
@ -627,6 +712,11 @@
ProvisioningStyle = Automatic;
TestTargetID = 2D02E47A1E0B4A5D006451C7;
};
3271B0A8236E2E0700DA766F = {
CreatedOnToolsVersion = 11.2;
DevelopmentTeam = A7W54YZ4WU;
ProvisioningStyle = Automatic;
};
B40D4E2F225841EC00428FCC = {
CreatedOnToolsVersion = 10.2;
DevelopmentTeam = A7W54YZ4WU;
@ -636,6 +726,11 @@
CreatedOnToolsVersion = 10.2;
DevelopmentTeam = A7W54YZ4WU;
ProvisioningStyle = Manual;
SystemCapabilities = {
com.apple.Keychain = {
enabled = 0;
};
};
};
};
};
@ -659,6 +754,7 @@
2D02E48F1E0B4A5D006451C7 /* BlueWallet-tvOSTests */,
B40D4E2F225841EC00428FCC /* BlueWalletWatch */,
B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */,
3271B0A8236E2E0700DA766F /* TodayExtension */,
);
};
/* End PBXProject section */
@ -677,21 +773,7 @@
files = (
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
3EEBC6F85642487DA7C4EE35 /* AntDesign.ttf in Resources */,
0B2C4EBFB4CB4960AAD777BC /* Entypo.ttf in Resources */,
C50F1706310E40F3B28D4856 /* EvilIcons.ttf in Resources */,
62A1DD9674CD479ABAA3D622 /* Feather.ttf in Resources */,
F21429E1449249038A7F3444 /* FontAwesome.ttf in Resources */,
CACD479D705745BC8CF1026B /* FontAwesome5_Brands.ttf in Resources */,
854972E4A6134C14A1D3A5F9 /* FontAwesome5_Regular.ttf in Resources */,
D8E3A15E21994BC3AF6CEECE /* FontAwesome5_Solid.ttf in Resources */,
66AB95FA29464B0BA106AA67 /* Foundation.ttf in Resources */,
CF81A1855609466D90635511 /* Ionicons.ttf in Resources */,
D5B495319D1B4542BE945CEA /* MaterialCommunityIcons.ttf in Resources */,
6C313BF9BC3E4BD2A65AA547 /* MaterialIcons.ttf in Resources */,
34CC55B441594DBB95AD1B50 /* Octicons.ttf in Resources */,
0AF37AC0E67044038B49FB3B /* SimpleLineIcons.ttf in Resources */,
7140A1CC26204118BA18DFA2 /* Zocial.ttf in Resources */,
3208E93922F63279007F5A27 /* AppCenter-Config.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -710,6 +792,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
3271B0A7236E2E0700DA766F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3271B0B1236E2E0700DA766F /* MainInterface.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B40D4E2E225841EC00428FCC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -742,7 +832,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport NODE_BINARY=node\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh\n";
};
0E6D0FA885BDBE9988699506 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
@ -766,6 +856,20 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
2130DE983D1D45AC8FC45F7E /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Upload Debug Symbols to Sentry";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym";
};
2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -778,7 +882,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport NODE_BINARY=node\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh";
};
310D9B5C1860199135C315EC /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
@ -911,6 +1015,7 @@
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
32B5A32A2334450100F8D608 /* Bridge.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -931,12 +1036,23 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
3271B0A5236E2E0700DA766F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3271B0BB236E329400DA766F /* API.swift in Sources */,
3271B0AE236E2E0700DA766F /* TodayViewController.swift in Sources */,
32002D9D236FAA9F00B93396 /* TodayDataStore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B40D4E38225841ED00428FCC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B43D037C225847C500FBAA95 /* Wallet.swift in Sources */,
B43D037A225847C500FBAA95 /* Transaction.swift in Sources */,
32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */,
B40D4E602258425500428FCC /* SpecifyInterfaceController.swift in Sources */,
B43D0379225847C500FBAA95 /* WatchDataSource.swift in Sources */,
B40D4E46225841ED00428FCC /* NotificationController.swift in Sources */,
@ -965,6 +1081,11 @@
target = 2D02E47A1E0B4A5D006451C7 /* BlueWallet-tvOS */;
targetProxy = 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */;
};
3271B0B4236E2E0700DA766F /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 3271B0A8236E2E0700DA766F /* TodayExtension */;
targetProxy = 3271B0B3236E2E0700DA766F /* PBXContainerItemProxy */;
};
B40D4E3F225841ED00428FCC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */;
@ -987,6 +1108,14 @@
path = BlueWallet;
sourceTree = "<group>";
};
3271B0AF236E2E0700DA766F /* MainInterface.storyboard */ = {
isa = PBXVariantGroup;
children = (
3271B0B0236E2E0700DA766F /* Base */,
);
name = MainInterface.storyboard;
sourceTree = "<group>";
};
B40D4E32225841EC00428FCC /* Interface.storyboard */ = {
isa = PBXVariantGroup;
children = (
@ -1002,6 +1131,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = A1B6AA2DE9A6E425682F4F3C /* Pods-BlueWalletTests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_PREPROCESSOR_DEFINITIONS = (
@ -1052,6 +1182,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 6AB6574CC4ECAAA359683D0F /* Pods-BlueWalletTests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = A7W54YZ4WU;
@ -1099,15 +1230,18 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9B3A324B70BC8C6D9314FD4F /* Pods-BlueWallet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = A7W54YZ4WU;
DEVELOPMENT_TEAM = "";
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = BlueWallet/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
@ -1120,7 +1254,10 @@
);
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet;
PRODUCT_NAME = BlueWallet;
PROVISIONING_PROFILE_SPECIFIER = "io.bluewallet.bluewallet AppStore";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
@ -1130,14 +1267,17 @@
isa = XCBuildConfiguration;
baseConfigurationReference = B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = A7W54YZ4WU;
DEVELOPMENT_TEAM = "";
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = BlueWallet/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
@ -1150,7 +1290,9 @@
);
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet;
PRODUCT_NAME = BlueWallet;
PROVISIONING_PROFILE_SPECIFIER = "io.bluewallet.bluewallet AppStore";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "BlueWallet-Bridging-Header.h";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
@ -1370,6 +1512,67 @@
};
name = Release;
};
3271B0B7236E2E0700DA766F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = TodayExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
3271B0B8236E2E0700DA766F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = TodayExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1421,7 +1624,7 @@
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@ -1469,7 +1672,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
VALIDATE_PRODUCT = YES;
};
name = Release;
@ -1485,6 +1688,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
DEBUG_INFORMATION_FORMAT = dwarf;
@ -1503,7 +1707,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Debug;
};
@ -1518,6 +1722,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements";
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
@ -1535,7 +1740,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Release;
};
@ -1567,9 +1772,9 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Debug;
};
@ -1600,9 +1805,9 @@
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 5.0;
};
name = Release;
};
@ -1645,6 +1850,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
3271B0B9236E2E0700DA766F /* Build configuration list for PBXNativeTarget "TodayExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3271B0B7236E2E0700DA766F /* Debug */,
3271B0B8236E2E0700DA766F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "BlueWallet" */ = {
isa = XCConfigurationList;
buildConfigurations = (

22
ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme

@ -27,6 +27,15 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
@ -39,17 +48,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "BlueWallet.app"
BlueprintName = "BlueWallet"
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -71,8 +69,6 @@
ReferencedContainer = "container:BlueWallet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

9
ios/BlueWallet.xcworkspace/contents.xcworkspacedata

@ -7,13 +7,4 @@
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
<FileRef
location = "group:../node_modules/react-native-tcp/ios/TcpSockets.xcodeproj">
</FileRef>
<FileRef
location = "group:../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage.xcodeproj">
</FileRef>
<FileRef
location = "group:../node_modules/react-native-privacy-snapshot/RCTPrivacySnapshot.xcodeproj">
</FileRef>
</Workspace>

8
ios/BlueWallet.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@ -0,0 +1,8 @@
<?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>PreviewsEnabled</key>
<false/>
</dict>
</plist>

6
ios/BlueWallet/AppDelegate.h

@ -6,13 +6,9 @@
*/
#import <UIKit/UIKit.h>
@import WatchConnectivity;
@class WatchBridge;
@interface AppDelegate : UIResponder <UIApplicationDelegate, WCSessionDelegate>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@property(nonatomic, strong) WatchBridge *watchBridge;
@property(nonatomic, strong) WCSession *session;
@end

34
ios/BlueWallet/AppDelegate.m

@ -10,17 +10,20 @@
#import <React/RCTLinkingManager.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#if __has_include(<React/RNSentry.h>)
#import <React/RNSentry.h> // This is used for versions of react >= 0.40
#else
#import "RNSentry.h" // This is used for versions of react < 0.40
#endif
#import "WatchBridge.h"
#import "RNQuickActionManager.h"
#import <AppCenterReactNativeShared/AppCenterReactNativeShared.h>
#import <AppCenterReactNative.h>
#import <AppCenterReactNativeAnalytics.h>
#import <AppCenterReactNativeCrashes.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[AppCenterReactNative register];
[AppCenterReactNativeAnalytics registerWithInitiallyEnabled:true];
[AppCenterReactNativeCrashes registerWithAutomaticProcessing];
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
@ -36,11 +39,6 @@
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
self.watchBridge = [WatchBridge shared];
self.session = self.watchBridge.session;
[self.session activateSession];
self.session.delegate = self;
return YES;
}
@ -52,18 +50,8 @@
return NO;
}
- (void)sessionDidDeactivate:(WCSession *)session {
[session activateSession];
}
- (void)session:(nonnull WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(nullable NSError *)error {
}
- (void)sessionDidBecomeInactive:(nonnull WCSession *)session {
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler {
[RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler];
}
@end

20
ios/BlueWallet/Base.lproj/LaunchScreen.xib

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -14,26 +14,10 @@
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="icon" translatesAutoresizingMaskIntoConstraints="NO" id="5GZ-ze-kHb">
<rect key="frame" x="154" y="184" width="173" height="112"/>
<constraints>
<constraint firstAttribute="height" constant="112" id="Zm5-a8-A8d"/>
<constraint firstAttribute="width" constant="173" id="t2s-74-qaQ"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="5GZ-ze-kHb" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="UkL-ek-FgS"/>
<constraint firstItem="5GZ-ze-kHb" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="mWu-9a-P4V"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="547.20000000000005" y="454.27286356821594"/>
</view>
</objects>
<resources>
<image name="icon" width="512" height="512"/>
</resources>
</document>

10
ios/BlueWallet/BlueWallet.entitlements

@ -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.application-groups</key>
<array>
<string>group.io.bluewallet.bluewallet</string>
</array>
</dict>
</plist>

6
ios/BlueWallet/Info.plist

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.2.0</string>
<string>4.8.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -43,6 +43,8 @@
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
@ -54,6 +56,8 @@
</dict>
<key>NSAppleMusicUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSFaceIDUsageDescription</key>
<string>In order to confirm your identity, we need your permission to use FaceID.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSCalendarsUsageDescription</key>

5
ios/BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements

@ -0,0 +1,5 @@
<?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/>
</plist>

56
ios/BlueWalletWatch Extension/ComplicationController.swift

@ -0,0 +1,56 @@
//
// ComplicationController.swift
// T WatchKit Extension
//
// Created by Marcos Rodriguez on 8/24/19.
// Copyright © 2019 Marcos Rodriguez. All rights reserved.
//
import ClockKit
class ComplicationController: NSObject, CLKComplicationDataSource {
// MARK: - Timeline Configuration
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
handler([.forward, .backward])
}
func getTimelineStartDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
handler(nil)
}
func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
handler(nil)
}
func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
handler(.showOnLockScreen)
}
// MARK: - Timeline Population
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries prior to the given date
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries after to the given date
handler(nil)
}
// MARK: - Placeholder Templates
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(nil)
}
}

17
ios/BlueWalletWatch Extension/Info.plist

@ -17,9 +17,24 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>4.2.0</string>
<string>4.8.1</string>
<key>CFBundleVersion</key>
<string>239</string>
<key>CLKComplicationPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ComplicationController</string>
<key>CLKComplicationSupportedFamilies</key>
<array>
<string>CLKComplicationFamilyCircularSmall</string>
<string>CLKComplicationFamilyExtraLarge</string>
<string>CLKComplicationFamilyGraphicBezel</string>
<string>CLKComplicationFamilyGraphicCircular</string>
<string>CLKComplicationFamilyGraphicCorner</string>
<string>CLKComplicationFamilyModularLarge</string>
<string>CLKComplicationFamilyModularSmall</string>
<string>CLKComplicationFamilyUtilitarianLarge</string>
<string>CLKComplicationFamilyUtilitarianSmall</string>
<string>CLKComplicationFamilyUtilitarianSmallFlat</string>
</array>
<key>LSApplicationCategoryType</key>
<string></string>
<key>NSExtension</key>

4
ios/BlueWalletWatch Extension/InterfaceController.swift

@ -19,7 +19,6 @@ class InterfaceController: WKInterfaceController {
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
WCSession.default.sendMessage(["message" : "sendApplicationContext"], replyHandler: nil, errorHandler: nil)
if (WatchDataSource.shared.wallets.isEmpty) {
loadingIndicatorGroup.setHidden(true)
@ -31,8 +30,6 @@ class InterfaceController: WKInterfaceController {
}
@objc private func processWalletsTable() {
loadingIndicatorGroup.setHidden(false)
walletsTable.setHidden(true)
walletsTable.setNumberOfRows(WatchDataSource.shared.wallets.count, withRowType: WalletInformation.identifier)
for index in 0..<walletsTable.numberOfRows {
@ -45,7 +42,6 @@ class InterfaceController: WKInterfaceController {
controller.balance = wallet.balance
controller.type = WalletGradient(rawValue: wallet.type) ?? .SegwitHD
}
loadingIndicatorGroup.setHidden(true)
noWalletsAvailableLabel.setHidden(!WatchDataSource.shared.wallets.isEmpty)
walletsTable.setHidden(WatchDataSource.shared.wallets.isEmpty)
}

6
ios/BlueWalletWatch Extension/Objects/WalletGradient.swift

@ -12,15 +12,15 @@ enum WalletGradient: String {
case SegwitHD = "HDsegwitP2SH"
case Segwit = "segwitP2SH"
case LightningCustodial = "lightningCustodianWallet"
case ACINQStrike = "LightningACINQ"
case SegwitNative = "HDsegwitBech32"
case WatchOnly = "watchOnly"
var imageString: String{
switch self {
case .Segwit:
return "wallet"
case .ACINQStrike:
return "walletACINQ"
case .SegwitNative:
return "walletHDSegwitNative"
case .SegwitHD:
return "walletHD"
case .WatchOnly:

16
ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift

@ -32,10 +32,10 @@ class WatchDataSource: NSObject, WCSessionDelegate {
}
func processWalletsData(walletsInfo: [String: Any]) {
if let walletsToProcess = walletsInfo["wallets"] as? [[String: Any]] {
if let walletsToProcess = walletsInfo["wallets"] as? [[String: Any]], !walletsToProcess.isEmpty {
wallets.removeAll();
for (index, entry) in walletsToProcess.enumerated() {
guard let label = entry["label"] as? String, let balance = entry["balance"] as? String, let type = entry["type"] as? String, let preferredBalanceUnit = entry["preferredBalanceUnit"] as? String, let receiveAddress = entry["receiveAddress"] as? String, let transactions = entry["transactions"] as? [[String: Any]] else {
guard let label = entry["label"] as? String, let balance = entry["balance"] as? String, let type = entry["type"] as? String, let preferredBalanceUnit = entry["preferredBalanceUnit"] as? String, let transactions = entry["transactions"] as? [[String: Any]] else {
continue
}
var transactionsProcessed = [Transaction]()
@ -44,6 +44,7 @@ class WatchDataSource: NSObject, WCSessionDelegate {
let transaction = Transaction(time: time, memo: memo, type: type, amount: amount)
transactionsProcessed.append(transaction)
}
let receiveAddress = entry["receiveAddress"] as? String ?? ""
let wallet = Wallet(label: label, balance: balance, type: type, preferredBalanceUnit: preferredBalanceUnit, receiveAddress: receiveAddress, transactions: transactionsProcessed, identifier: index)
wallets.append(wallet)
}
@ -73,7 +74,7 @@ class WatchDataSource: NSObject, WCSessionDelegate {
}) { (error) in
print(error)
responseHandler("")
}
}
@ -85,18 +86,17 @@ class WatchDataSource: NSObject, WCSessionDelegate {
WatchDataSource.shared.processWalletsData(walletsInfo: applicationContext)
}
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
// WatchDataSource.shared.processWalletsData(walletsInfo: userInfo)
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if activationState == .activated {
WCSession.default.sendMessage([:], replyHandler: nil, errorHandler: nil)
if let existingData = keychain.getData(Wallet.identifier), let walletData = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(existingData) as? [Wallet] {
guard let walletData = walletData, walletData != self.wallets else { return }
wallets = walletData
WatchDataSource.postDataUpdatedNotification()
}
WCSession.default.sendMessage(["message" : "sendApplicationContext"], replyHandler: { (replyData) in
}) { (error) in
print(error)
}
}
}

15
ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift

@ -7,6 +7,7 @@
//
import WatchKit
import WatchConnectivity
import Foundation
import EFQRCode
@ -16,26 +17,29 @@ class ReceiveInterfaceController: WKInterfaceController {
@IBOutlet weak var imageInterface: WKInterfaceImage!
private var wallet: Wallet?
private var isRenderingQRCode: Bool?
private var receiveMethod: String = "receive"
@IBOutlet weak var loadingIndicator: WKInterfaceGroup!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
guard let identifier = context as? Int, WatchDataSource.shared.wallets.count > identifier else {
guard let passedContext = context as? (Int, String), WatchDataSource.shared.wallets.count >= passedContext.0 else {
pop()
return
}
let identifier = passedContext.0
let wallet = WatchDataSource.shared.wallets[identifier]
self.wallet = wallet
receiveMethod = passedContext.1
NotificationCenter.default.addObserver(forName: SpecifyInterfaceController.NotificationName.createQRCode, object: nil, queue: nil) { [weak self] (notification) in
self?.isRenderingQRCode = true
if let wallet = self?.wallet, wallet.type == "lightningCustodianWallet", let object = notification.object as? SpecifyInterfaceController.SpecificQRCodeContent, let amount = object.amount {
if let wallet = self?.wallet, wallet.type == "lightningCustodianWallet", self?.receiveMethod == "createInvoice", let object = notification.object as? SpecifyInterfaceController.SpecificQRCodeContent, let amount = object.amount {
self?.imageInterface.setHidden(true)
self?.loadingIndicator.setHidden(false)
WatchDataSource.requestLightningInvoice(walletIdentifier: identifier, amount: amount, description: object.description, responseHandler: { (invoice) in
DispatchQueue.main.async {
if (!invoice.isEmpty) {
guard let cgImage = EFQRCode.generate(
content: "lightning:\(invoice)") else {
content: "lightning:\(invoice)", inputCorrectionLevel: .h, pointShape: .circle) else {
return
}
let image = UIImage(cgImage: cgImage)
@ -43,6 +47,7 @@ class ReceiveInterfaceController: WKInterfaceController {
self?.imageInterface.setHidden(false)
self?.imageInterface.setImage(nil)
self?.imageInterface.setImage(image)
WCSession.default.sendMessage(["message": "fetchTransactions"], replyHandler: nil, errorHandler: nil)
} else {
self?.pop()
self?.presentAlert(withTitle: "Error", message: "Unable to create invoice. Please, make sure your iPhone is paired and nearby.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in
@ -83,7 +88,7 @@ class ReceiveInterfaceController: WKInterfaceController {
}
guard !wallet.receiveAddress.isEmpty, let cgImage = EFQRCode.generate(
content: wallet.receiveAddress) else {
content: wallet.receiveAddress), receiveMethod != "createInvoice" else {
return
}
@ -93,7 +98,7 @@ class ReceiveInterfaceController: WKInterfaceController {
override func didAppear() {
super.didAppear()
if wallet?.type == "lightningCustodianWallet" {
if wallet?.type == "lightningCustodianWallet" && receiveMethod == "createInvoice" {
if isRenderingQRCode == nil {
presentController(withName: SpecifyInterfaceController.identifier, context: wallet?.identifier)
isRenderingQRCode = false

7
ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift

@ -14,6 +14,8 @@ class SpecifyInterfaceController: WKInterfaceController {
static let identifier = "SpecifyInterfaceController"
@IBOutlet weak var descriptionButton: WKInterfaceButton!
@IBOutlet weak var amountButton: WKInterfaceButton!
@IBOutlet weak var createButton: WKInterfaceButton!
struct SpecificQRCodeContent {
var amount: Double?
var description: String?
@ -36,6 +38,7 @@ class SpecifyInterfaceController: WKInterfaceController {
}
let wallet = WatchDataSource.shared.wallets[identifier]
self.wallet = wallet
self.createButton.setAlpha(0.5)
self.specifiedQRContent.bitcoinUnit = wallet.type == "lightningCustodianWallet" ? .SATS : .BTC
NotificationCenter.default.addObserver(forName: NumericKeypadInterfaceController.NotificationName.keypadDataChanged, object: nil, queue: nil) { [weak self] (notification) in
guard let amountObject = notification.object as? [String], !amountObject.isEmpty else { return }
@ -53,6 +56,10 @@ class SpecifyInterfaceController: WKInterfaceController {
if let amountDouble = Double(title), let keyPadType = self?.specifiedQRContent.bitcoinUnit {
self?.specifiedQRContent.amount = amountDouble
self?.amountButton.setTitle("\(title) \(keyPadType)")
let isShouldCreateButtonBeEnabled = amountDouble > 0 && !title.isEmpty
self?.createButton.setEnabled(isShouldCreateButtonBeEnabled)
self?.createButton.setAlpha(isShouldCreateButtonBeEnabled ? 1.0 : 0.5)
}
}
}

14
ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift

@ -16,11 +16,13 @@ class WalletDetailsInterfaceController: WKInterfaceController {
static let identifier = "WalletDetailsInterfaceController"
@IBOutlet weak var walletBasicsGroup: WKInterfaceGroup!
@IBOutlet weak var walletBalanceLabel: WKInterfaceLabel!
@IBOutlet weak var createInvoiceButton: WKInterfaceButton!
@IBOutlet weak var walletNameLabel: WKInterfaceLabel!
@IBOutlet weak var receiveButton: WKInterfaceButton!
@IBOutlet weak var noTransactionsLabel: WKInterfaceLabel!
@IBOutlet weak var transactionsTable: WKInterfaceTable!
override func awake(withContext context: Any?) {
super.awake(withContext: context)
guard let identifier = context as? Int else {
@ -32,7 +34,7 @@ class WalletDetailsInterfaceController: WKInterfaceController {
walletBalanceLabel.setText(wallet.balance)
walletNameLabel.setText(wallet.label)
walletBasicsGroup.setBackgroundImageNamed(WalletGradient(rawValue: wallet.type)?.imageString)
createInvoiceButton.setHidden(wallet.type != "lightningCustodianWallet")
processWalletsTable()
}
@ -40,12 +42,14 @@ class WalletDetailsInterfaceController: WKInterfaceController {
super.willActivate()
transactionsTable.setHidden(wallet?.transactions.isEmpty ?? true)
noTransactionsLabel.setHidden(!(wallet?.transactions.isEmpty ?? false))
receiveButton.setHidden(wallet?.receiveAddress.isEmpty ?? true)
}
@IBAction func receiveMenuItemTapped() {
presentController(withName: ReceiveInterfaceController.identifier, context: wallet)
presentController(withName: ReceiveInterfaceController.identifier, context: (wallet, "receive"))
}
@objc private func processWalletsTable() {
transactionsTable.setNumberOfRows(wallet?.transactions.count ?? 0, withRowType: TransactionTableRow.identifier)
@ -61,8 +65,12 @@ class WalletDetailsInterfaceController: WKInterfaceController {
noTransactionsLabel.setHidden(!(wallet?.transactions.isEmpty ?? false))
}
@IBAction func createInvoiceTapped() {
pushController(withName: ReceiveInterfaceController.identifier, context: (wallet?.identifier, "createInvoice"))
}
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
return wallet?.identifier
return (wallet?.identifier, "receive")
}
}

32
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json

@ -0,0 +1,32 @@
{
"images" : [
{
"idiom" : "watch",
"screen-width" : "<=145",
"filename" : "circular38mm@2x.png",
"scale" : "2x"
},
{
"screen-width" : ">161",
"scale" : "2x",
"idiom" : "watch",
"filename" : "circular40mm@2x.png"
},
{
"scale" : "2x",
"idiom" : "watch",
"filename" : "circular42mm@2x.png",
"screen-width" : ">145"
},
{
"filename" : "circular44mm@2x.png",
"scale" : "2x",
"idiom" : "watch",
"screen-width" : ">183"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular38mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular40mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular42mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Circular.imageset/circular44mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

48
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Contents.json

@ -0,0 +1,48 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"assets" : [
{
"idiom" : "watch",
"role" : "circular",
"filename" : "Circular.imageset"
},
{
"idiom" : "watch",
"filename" : "Modular.imageset",
"role" : "modular"
},
{
"idiom" : "watch",
"filename" : "Utilitarian.imageset",
"role" : "utilitarian"
},
{
"idiom" : "watch",
"role" : "extra-large",
"filename" : "Extra Large.imageset"
},
{
"role" : "graphic-corner",
"idiom" : "watch",
"filename" : "Graphic Corner.imageset"
},
{
"filename" : "Graphic Circular.imageset",
"role" : "graphic-circular",
"idiom" : "watch"
},
{
"idiom" : "watch",
"filename" : "Graphic Bezel.imageset",
"role" : "graphic-bezel"
},
{
"idiom" : "watch",
"role" : "graphic-large-rectangular",
"filename" : "Graphic Large Rectangular.imageset"
}
]
}

32
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json

@ -0,0 +1,32 @@
{
"images" : [
{
"filename" : "extra-large38mm@2x.png",
"screen-width" : "<=145",
"idiom" : "watch",
"scale" : "2x"
},
{
"screen-width" : ">161",
"filename" : "extra-large40mm@2x.png",
"idiom" : "watch",
"scale" : "2x"
},
{
"screen-width" : ">145",
"idiom" : "watch",
"filename" : "extra-large42mm@2x.png",
"scale" : "2x"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183",
"filename" : "extra-large44mm@2x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large38mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large40mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large42mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Extra Large.imageset/extra-large44mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

30
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json

@ -0,0 +1,30 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"filename" : "graphic-bezel40mm@2x.png",
"screen-width" : ">161",
"scale" : "2x"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"filename" : "graphic-bezel44mm@2x.png",
"screen-width" : ">183",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/graphic-bezel40mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/graphic-bezel44mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

30
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json

@ -0,0 +1,30 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"filename" : "graphic-circular40mm@2x.png",
"screen-width" : ">161",
"scale" : "2x"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"filename" : "graphic-circular44mm@2x.png",
"screen-width" : ">183",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/graphic-circular40mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/graphic-circular44mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

30
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json

@ -0,0 +1,30 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"filename" : "graphic-corner40mm@2x.png",
"screen-width" : ">161",
"scale" : "2x"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"filename" : "graphic-corner44mm@2x.png",
"screen-width" : ">183",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/graphic-corner40mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/graphic-corner44mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

28
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json

@ -0,0 +1,28 @@
{
"images" : [
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

32
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json

@ -0,0 +1,32 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"images" : [
{
"screen-width" : "<=145",
"scale" : "2x",
"idiom" : "watch",
"filename" : "modular38mm@2x.png"
},
{
"screen-width" : ">161",
"scale" : "2x",
"filename" : "modular40mm@2x.png",
"idiom" : "watch"
},
{
"scale" : "2x",
"idiom" : "watch",
"filename" : "modular42mm@2x.png",
"screen-width" : ">145"
},
{
"filename" : "modular44mm@2x.png",
"screen-width" : ">183",
"idiom" : "watch",
"scale" : "2x"
}
]
}

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular38mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular40mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular42mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Modular.imageset/modular44mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

32
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json

@ -0,0 +1,32 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"images" : [
{
"scale" : "2x",
"filename" : "utility38mm@2x.png",
"screen-width" : "<=145",
"idiom" : "watch"
},
{
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161",
"filename" : "utility40mm@2x.png"
},
{
"scale" : "2x",
"idiom" : "watch",
"filename" : "utility42mm@2x.png",
"screen-width" : ">145"
},
{
"idiom" : "watch",
"screen-width" : ">183",
"filename" : "utility44mm@2x.png",
"scale" : "2x"
}
]
}

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/utility38mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/utility40mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
ios/BlueWalletWatch/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/utility42mm@2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

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

Loading…
Cancel
Save