Browse Source

Merge branch 'master' into aci

aci
Marcos Rodriguez 5 years ago
parent
commit
7453574add
  1. 81
      App.js
  2. 2
      BlueApp.js
  3. 192
      BlueComponents.js
  4. 7
      BlueElectrum.js
  5. 53
      MainBottomTabs.js
  6. 25
      README.md
  7. 97
      UnlockWith.js
  8. 12
      WatchConnectivity.android.js
  9. 102
      WatchConnectivity.ios.js
  10. 17
      android/.project
  11. 6
      android/app/.classpath
  12. 23
      android/app/.project
  13. 2
      android/app/.settings/org.eclipse.buildship.core.prefs
  14. 6
      android/app/build.gradle
  15. BIN
      android/app/src/main/res/drawable/quickactions.png
  16. 5
      android/sentry.properties
  17. 45
      appcenter-post-build-get-pr-number.js
  18. 20
      appcenter-post-build.sh
  19. 19
      class/abstract-hd-wallet.js
  20. 17
      class/abstract-wallet.js
  21. 22
      class/app-storage.js
  22. 60
      class/biometrics.js
  23. 23
      class/hd-legacy-breadwallet-wallet.js
  24. 28
      class/hd-legacy-p2pkh-wallet.js
  25. 2
      class/hd-segwit-bech32-transaction.js
  26. 156
      class/hd-segwit-bech32-wallet.js
  27. 10
      class/hd-segwit-p2sh-wallet.js
  28. 6
      class/legacy-wallet.js
  29. 10
      class/lightning-custodian-wallet.js
  30. 45
      class/onAppLaunch.js
  31. 46
      class/quickActions.js
  32. 30
      class/segwit-bech-wallet.js
  33. 47
      class/segwit-p2sh-wallet.js
  34. 67
      class/watch-only-wallet.js
  35. 17
      currency.js
  36. 1
      edit-version-number.sh
  37. BIN
      img/faceid.png
  38. BIN
      img/fingerprint.png
  39. 23
      index.js
  40. 292
      ios/BlueWallet.xcodeproj/project.pbxproj
  41. 22
      ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme
  42. 9
      ios/BlueWallet.xcworkspace/contents.xcworkspacedata
  43. 8
      ios/BlueWallet.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  44. 6
      ios/BlueWallet/AppDelegate.h
  45. 25
      ios/BlueWallet/AppDelegate.m
  46. 7
      ios/BlueWallet/BlueWallet.entitlements
  47. 4
      ios/BlueWallet/Info.plist
  48. 2
      ios/BlueWalletWatch Extension/Info.plist
  49. 4
      ios/BlueWalletWatch Extension/InterfaceController.swift
  50. 16
      ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift
  51. 13
      ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift
  52. 7
      ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift
  53. 14
      ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift
  54. 35
      ios/BlueWalletWatch/Base.lproj/Interface.storyboard
  55. 2
      ios/BlueWalletWatch/Info.plist
  56. 44
      ios/Podfile
  57. 142
      ios/Podfile.lock
  58. 125
      ios/TodayExtension/Base.lproj/MainInterface.storyboard
  59. 10
      ios/TodayExtension/BlueWallet - Bitcoin Price.entitlements
  60. 31
      ios/TodayExtension/Info.plist
  61. 65
      ios/TodayExtension/New Group/API.swift
  62. 89
      ios/TodayExtension/TodayDataStore.swift
  63. 130
      ios/TodayExtension/TodayViewController.swift
  64. 92
      ios/fastlane/metadata/en-US/release_notes.txt
  65. 2
      ios/sentry.properties
  66. 236
      loc/ZAR_Afr.js
  67. 239
      loc/ZAR_Xho.js
  68. 2
      loc/cs_CZ.js
  69. 2
      loc/da_DK.js
  70. 2
      loc/de_DE.js
  71. 2
      loc/el.js
  72. 4
      loc/en.js
  73. 2
      loc/es.js
  74. 2
      loc/fi_FI.js
  75. 2
      loc/fr_FR.js
  76. 2
      loc/hr_HR.js
  77. 3
      loc/hu_HU.js
  78. 2
      loc/id_ID.js
  79. 13
      loc/index.js
  80. 2
      loc/it.js
  81. 2
      loc/nb_NO.js
  82. 4
      loc/nl_NL.js
  83. 3
      loc/pt_BR.js
  84. 9
      loc/pt_PT.js
  85. 2
      loc/ru.js
  86. 2
      loc/sv_SE.js
  87. 7
      loc/th_TH.js
  88. 2
      loc/tr_TR.js
  89. 2
      loc/ua.js
  90. 244
      loc/vi_VN.js
  91. 2
      loc/zh_cn.js
  92. 235
      loc/zh_tw.js
  93. 168
      models/signer.js
  94. 6196
      package-lock.json
  95. 48
      package.json
  96. 12
      patches/transaction.js.patch
  97. 12
      patches/transaction_builder.js.patch
  98. 2
      podinstall.sh
  99. 43
      screen/lnd/browser.js
  100. 4
      screen/lnd/lndCreateInvoice.js

81
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';
@ -10,6 +10,16 @@ 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';
@ -27,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);

2
BlueApp.js

@ -2,6 +2,7 @@
* @exports {AppStorage}
*/
import { AppStorage } from './class';
import DeviceQuickActions from './class/quickActions';
let prompt = require('./prompt');
let EV = require('./events');
let currency = require('./currency');
@ -19,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);

192
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';
@ -18,7 +17,6 @@ import {
Image,
Keyboard,
SafeAreaView,
InteractionManager,
InputAccessoryView,
Clipboard,
Platform,
@ -29,14 +27,13 @@ import { LightningCustodianWallet, ACINQStrikeLightningWallet } 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 dayjs = require('dayjs');
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';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
import Biometric from './class/biometrics';
let loc = require('./loc/');
/** @type {AppStorage} */
let BlueApp = require('./BlueApp');
@ -183,6 +180,15 @@ export class BlueWalletNavigationHeader extends Component {
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();
@ -238,6 +244,10 @@ export class BlueWalletNavigationHeader extends Component {
});
}
manageFundsPressed = () => {
this.props.onManageFundsPressed();
};
render() {
return (
<LinearGradient
@ -304,7 +314,7 @@ export class BlueWalletNavigationHeader extends Component {
)}
</TouchableOpacity>
{this.state.wallet.type === LightningCustodianWallet.type && (
<TouchableOpacity onPress={() => NavigationService.navigate('ManageFunds', { fromWallet: this.state.wallet })}>
<TouchableOpacity onPress={this.manageFundsPressed}>
<View
style={{
marginTop: 14,
@ -430,10 +440,10 @@ export const BluePrivateBalance = () => {
});
};
export const BlueCopyToClipboardButton = ({ stringToCopy }) => {
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>
);
};
@ -1383,27 +1393,28 @@ export class NewWalletPanel extends Component {
}
}
export class BlueTransactionListItem extends Component {
static propTypes = {
item: PropTypes.shape().isRequired,
itemPriceUnit: PropTypes.string,
export const BlueTransactionListItem = ({ item, itemPriceUnit = BitcoinUnit.BTC }) => {
const calculateTimeLabel = () => {
const transactionTimeToReadable = loc.transactionTimeToReadable(item.received);
return setTransactionTimeToReadable(transactionTimeToReadable);
};
static defaultProps = {
itemPriceUnit: BitcoinUnit.BTC,
};
state = { transactionTimeToReadable: '...', subtitleNumberOfLines: 1 };
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';
@ -1413,10 +1424,10 @@ 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;
}
@ -1431,12 +1442,11 @@ export class BlueTransactionListItem extends Component {
}
return loc.formatBalanceWithoutSuffix(item.amount_satoshi, this.props.itemPriceUnit, true).toString();
} 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') {
@ -1471,9 +1481,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 />
@ -1481,14 +1491,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 }}>
@ -1497,11 +1507,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 }}>
@ -1518,7 +1528,7 @@ export class BlueTransactionListItem extends Component {
}
}
if (this.props.item.object === 'charge') {
if (item.object === 'charge') {
if (this.props.item.paid) {
return (
<View style={{ width: 25 }}>
@ -1526,7 +1536,7 @@ export class BlueTransactionListItem extends Component {
</View>
);
} else {
const expectedExpiration = dayjs(this.props.item.updated + 3600000);
const expectedExpiration = dayjs(item.updated + 3600000);
if (expectedExpiration.isBefore(dayjs())) {
return (
<View style={{ width: 25 }}>
@ -1542,13 +1552,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 />
@ -1563,37 +1573,28 @@ export class BlueTransactionListItem extends Component {
}
};
subtitle = () => {
const subtitle = () => {
if (this.props.item.object === 'charge') {
return this.props.item.description;
} else {
return (
(this.props.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + this.props.item.confirmations + ' ' : '') +
this.txMemo() +
(this.props.item.memo || '')
);
return (item.confirmations < 7 ? loc.transactions.list.conf + ': ' + item.confirmations + ' ' : '') + txMemo() + (item.memo || '');
}
};
onPress = () => {
if (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' ||
this.props.item.type === 'paid_invoice' ||
this.props.item.object === 'charge'
) {
const onPress = () => {
if (item.hash) {
NavigationService.navigate('TransactionStatus', { hash: item.hash });
} else if (item.type === charge || 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,
});
@ -1601,40 +1602,32 @@ export class BlueTransactionListItem extends Component {
}
};
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
const transactionTimeToReadable = loc.transactionTimeToReadable(this.props.item.received || this.props.item.updated);
this.setState({ transactionTimeToReadable });
});
}
onLongPress = () => {
if (this.state.subtitleNumberOfLines === 1) {
this.setState({ subtitleNumberOfLines: 0 });
const onLongPress = () => {
if (subtitleNumberOfLines === 1) {
setSubtitleNumberOfLines(0);
}
};
render() {
return (
<BlueListItem
avatar={this.avatar()}
title={this.state.transactionTimeToReadable}
subtitle={this.subtitle()}
subtitleNumberOfLines={this.state.subtitleNumberOfLines}
onPress={this.onPress}
onLongPress={this.onLongPress}
badge={{
value: 3,
textStyle: { color: 'orange' },
containerStyle: { marginTop: 0 },
}}
hideChevron
rightTitle={this.rowTitle()}
rightTitleStyle={this.rowTitleStyle()}
/>
);
}
}
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 = {
@ -2037,29 +2030,8 @@ export class BlueAddressInput extends Component {
<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={{
height: 36,

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;

53
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';
@ -36,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';
@ -62,6 +64,8 @@ const WalletsStackNavigator = createStackNavigator(
},
WalletTransactions: {
screen: WalletTransactions,
path: 'WalletTransactions',
routeName: 'WalletTransactions',
},
TransactionStatus: {
screen: TransactionStatus,
@ -88,7 +92,7 @@ const WalletsStackNavigator = createStackNavigator(
screen: rbfCancel,
},
Settings: {
screen: Settings,
screen: SettingsContainer,
path: 'Settings',
navigationOptions: {
headerStyle: {
@ -99,6 +103,9 @@ const WalletsStackNavigator = createStackNavigator(
headerTintColor: '#0c2550',
},
},
SelectWallet: {
screen: SelectWallet,
},
Currency: {
screen: Currency,
},
@ -113,6 +120,10 @@ const WalletsStackNavigator = createStackNavigator(
Selftest: {
screen: Selftest,
},
DefaultView: {
screen: DefaultView,
path: 'DefaultView',
},
Language: {
screen: Language,
path: 'Language',
@ -152,6 +163,9 @@ const CreateTransactionStackNavigator = createStackNavigator({
Confirm: {
screen: Confirm,
},
PsbtWithHardwareWallet: {
screen: PsbtWithHardwareWallet,
},
CreateTransaction: {
screen: sendCreate,
navigationOptions: {
@ -167,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,
},
},
});
@ -217,6 +219,9 @@ const LightningScanInvoiceStackNavigator = createStackNavigator({
},
SelectWallet: {
screen: SelectWallet,
navigationOptions: {
headerRight: null,
},
},
Success: {
screen: Success,
@ -260,6 +265,12 @@ const MainBottomTabs = createStackNavigator(
header: null,
},
},
SelectWallet: {
screen: SelectWallet,
navigationOptions: {
headerLeft: null,
},
},
//
@ -275,12 +286,6 @@ const MainBottomTabs = createStackNavigator(
// LND:
ManageFunds: {
screen: ManageFundsStackNavigator,
navigationOptions: {
header: null,
},
},
ScanLndInvoice: {
screen: LightningScanInvoiceStackNavigator,
navigationOptions: {
@ -288,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

97
UnlockWith.js

@ -0,0 +1,97 @@
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.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() {}
}

102
WatchConnectivity.js → WatchConnectivity.ios.js

@ -1,42 +1,51 @@
import * as watch from 'react-native-watch-connectivity';
import { InteractionManager, Platform } from 'react-native';
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() {
if (Platform.OS === 'ios') {
this.getIsWatchAppInstalled();
}
this.getIsWatchAppInstalled();
}
getIsWatchAppInstalled() {
if (Platform.OS !== 'ios') return;
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);
@ -47,17 +56,29 @@ export default class WatchConnectivity {
}
}
async sendWalletsToWatch() {
if (Platform.OS !== 'ios') return;
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();
}
@ -69,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();
@ -91,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;
@ -112,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;
}
@ -129,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 || Platform.OS !== 'ios') return;
WatchConnectivity.shared = new WatchConnectivity();
};

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

6
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.5.0"
versionName "4.8.0"
multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general'
}
@ -154,9 +155,6 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+" // From node_modules
def appCenterSdkVersion = '2.1.0'
implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
// JSC from node_modules
if (useIntlJsc) {
implementation 'org.webkit:android-jsc-intl:+'

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

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');
}
}

17
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';
@ -31,6 +31,13 @@ export class AbstractWallet {
this.hideBalance = false;
}
getID() {
return createHash('sha256')
.update(this.getSecret())
.digest()
.toString('hex');
}
getTransactions() {
return this.transactions;
}
@ -117,4 +124,12 @@ export class AbstractWallet {
}
// createTx () { throw Error('not implemented') }
getAddress() {
throw Error('not implemented');
}
getAddressAsync() {
return new Promise(resolve => resolve(this.getAddress()));
}
}

22
class/app-storage.js

@ -13,6 +13,7 @@ import {
} from './';
import { LightningCustodianWallet } from './lightning-custodian-wallet';
import WatchConnectivity from '../WatchConnectivity';
import DeviceQuickActions from './quickActions';
const encryption = require('../encryption');
export class AppStorage {
@ -138,6 +139,8 @@ export class AppStorage {
this.cachedPassword = password;
await this.setItem('data', data);
await this.setItem(AppStorage.FLAG_ENCRYPTED, '1');
DeviceQuickActions.clearShortcutItems();
DeviceQuickActions.removeAllWallets();
}
/**
@ -248,8 +251,15 @@ export class AppStorage {
this.tx_metadata = data.tx_metadata;
}
}
WatchConnectivity.init();
WatchConnectivity.shared && (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
@ -269,6 +279,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
@ -322,8 +333,11 @@ export class AppStorage {
} else {
await this.setItem(AppStorage.FLAG_ENCRYPTED, ''); // drop the flag
}
WatchConnectivity.init();
WatchConnectivity.shared && WatchConnectivity.shared.sendWalletsToWatch();
WatchConnectivity.shared.wallets = this.wallets;
WatchConnectivity.shared.tx_metadata = this.tx_metadata;
WatchConnectivity.shared.sendWalletsToWatch();
DeviceQuickActions.setWallets(this.wallets);
DeviceQuickActions.setQuickActions();
return this.setItem('data', JSON.stringify(data));
}

60
class/biometrics.js

@ -0,0 +1,60 @@
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 async isDeviceBiometricCapable() {
const isDeviceBiometricCapable = await Biometric.biometricType();
if (typeof isDeviceBiometricCapable === 'string') {
return isDeviceBiometricCapable;
}
Biometric.setBiometricUseEnabled(false);
return false;
}
static async biometricType() {
try {
const isSensorAvailable = await Biometrics.isSensorAvailable();
return isSensorAvailable;
} 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 {
await Biometrics.simplePrompt('Please confirm your identity.');
return true;
} 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');

156
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');
@ -114,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);
@ -167,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);
}
@ -294,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);
@ -341,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);
@ -650,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;
@ -674,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;
@ -693,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]);
}
}
let tx;
if (!skipSigning) {
tx = psbt.finalizeAllInputs().extractTransaction();
}
return { tx, inputs, outputs, fee, psbt };
}
const tx = txb.build();
return { tx, inputs, outputs, fee };
/**
* 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();
}
/**
@ -733,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;
}

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

@ -7,7 +7,6 @@ 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;
@ -19,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]);
@ -31,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;
}
@ -95,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) {

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;
}

10
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() {
@ -358,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;
}
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)');
}
}
}

17
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() {
@ -57,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;
@ -71,7 +81,10 @@ async function startUpdater() {
}
function satoshiToLocalCurrency(satoshi) {
if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) return '...';
if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) {
startUpdater();
return '...';
}
let b = new BigNumber(satoshi);
b = b

1
edit-version-number.sh

@ -1,6 +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

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

23
index.js

@ -2,19 +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');
let A = require('./analytics');
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
@ -24,7 +20,7 @@ if (!Error.captureStackTrace) {
class BlueAppComponent extends React.Component {
constructor(props) {
super(props);
this.state = { isMigratingData: true, onAnimationFinished: false };
this.state = { isMigratingData: true, onAnimationFinished: false, successfullyAuthenticated: false };
}
componentDidMount() {
@ -33,7 +29,6 @@ class BlueAppComponent extends React.Component {
}
setIsMigratingData = async () => {
await BlueApp.startAndDecrypt();
A(A.ENUM.INIT);
this.setState({ isMigratingData: false });
};
@ -46,6 +41,10 @@ class BlueAppComponent extends React.Component {
}
};
onSuccessfullyAuthenticated = () => {
this.setState({ successfullyAuthenticated: true });
};
render() {
if (this.state.isMigratingData) {
return (
@ -59,7 +58,11 @@ class BlueAppComponent extends React.Component {
);
} else {
if (this.state.onAnimationFinished) {
return <App />;
return this.state.successfullyAuthenticated ? (
<App />
) : (
<UnlockWith onSuccessfullyAuthenticated={this.onSuccessfullyAuthenticated} />
);
} else {
return (
<LottieView

292
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,20 +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 */; };
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 */; };
34582CAA4AD140F7B80C961A /* libTcpSockets.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DF4E6C040764E4BA1ACC1EB /* libTcpSockets.a */; };
34CC55B441594DBB95AD1B50 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */; };
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 */; };
@ -58,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 */
@ -86,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 */;
@ -103,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;
@ -149,7 +155,15 @@
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>"; };
@ -249,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;
};
@ -276,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;
@ -347,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 = (
@ -380,6 +422,7 @@
00E356EF1AD99517003FC87E /* BlueWalletTests */,
B40D4E31225841EC00428FCC /* BlueWalletWatch */,
B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */,
3271B0AC236E2E0700DA766F /* TodayExtension */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
B40FE50A21FAD228005D5578 /* Recovered References */,
@ -400,6 +443,7 @@
2D02E4901E0B4A5D006451C7 /* BlueWallet-tvOSTests.xctest */,
B40D4E30225841EC00428FCC /* BlueWalletWatch.app */,
B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */,
3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */,
);
name = Products;
sourceTree = "<group>";
@ -528,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";
@ -575,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" */;
@ -618,7 +682,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1020;
LastSwiftUpdateCheck = 1120;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
@ -630,7 +694,7 @@
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = A7W54YZ4WU;
LastSwiftMigration = 1030;
ProvisioningStyle = Manual;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Keychain = {
enabled = 0;
@ -648,6 +712,11 @@
ProvisioningStyle = Automatic;
TestTargetID = 2D02E47A1E0B4A5D006451C7;
};
3271B0A8236E2E0700DA766F = {
CreatedOnToolsVersion = 11.2;
DevelopmentTeam = A7W54YZ4WU;
ProvisioningStyle = Automatic;
};
B40D4E2F225841EC00428FCC = {
CreatedOnToolsVersion = 10.2;
DevelopmentTeam = A7W54YZ4WU;
@ -685,6 +754,7 @@
2D02E48F1E0B4A5D006451C7 /* BlueWallet-tvOSTests */,
B40D4E2F225841EC00428FCC /* BlueWalletWatch */,
B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */,
3271B0A8236E2E0700DA766F /* TodayExtension */,
);
};
/* End PBXProject section */
@ -703,22 +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 */,
3208E93922F63279007F5A27 /* AppCenter-Config.plist 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 */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -737,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;
@ -769,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;
@ -793,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;
@ -805,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;
@ -959,6 +1036,16 @@
);
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;
@ -994,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 */;
@ -1016,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 = (
@ -1130,16 +1230,18 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9B3A324B70BC8C6D9314FD4F /* Pods-BlueWallet.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
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)",
@ -1152,7 +1254,7 @@
);
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;
@ -1165,15 +1267,17 @@
isa = XCBuildConfiguration;
baseConfigurationReference = B459EE96941AE09BCB547DC0 /* Pods-BlueWallet.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
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)",
@ -1186,7 +1290,7 @@
);
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";
@ -1408,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 = {
@ -1459,7 +1624,7 @@
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@ -1507,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;
@ -1523,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;
@ -1541,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;
};
@ -1556,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;
@ -1573,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;
};
@ -1605,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;
};
@ -1638,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;
};
@ -1683,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

25
ios/BlueWallet/AppDelegate.m

@ -10,12 +10,7 @@
#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>
@ -44,10 +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;
}
@ -59,18 +50,8 @@
return NO;
}
- (void)sessionDidDeactivate:(WCSession *)session {
[session activateSession];
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler {
[RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler];
}
- (void)session:(nonnull WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(nullable NSError *)error {
}
- (void)sessionDidBecomeInactive:(nonnull WCSession *)session {
}
@end

7
ios/BlueWallet/BlueWallet.entitlements

@ -1,5 +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/>
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.bluewallet.bluewallet</string>
</array>
</dict>
</plist>

4
ios/BlueWallet/Info.plist

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.5.0</string>
<string>4.8.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -56,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>

2
ios/BlueWalletWatch Extension/Info.plist

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>4.5.0</string>
<string>4.8.0</string>
<key>CFBundleVersion</key>
<string>239</string>
<key>CLKComplicationPrincipalClass</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)
}

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)
}
}
}

13
ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift

@ -7,6 +7,7 @@
//
import WatchKit
import WatchConnectivity
import Foundation
import EFQRCode
@ -16,19 +17,22 @@ 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
@ -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")
}
}

35
ios/BlueWalletWatch/Base.lproj/Interface.storyboard

@ -1,18 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="14490.70" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="AgC-eL-Hgc">
<device id="watch44" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="15505" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="AgC-eL-Hgc">
<device id="watch44"/>
<dependencies>
<deployment identifier="watchOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="14490.21"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15510"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="15501"/>
</dependencies>
<scenes>
<!--BlueWallet-->
<scene sceneID="aou-V4-d1y">
<objects>
<controller title="BlueWallet" fullBounds="YES" id="AgC-eL-Hgc" customClass="InterfaceController" customModule="BlueWalletWatch" customModuleProvider="target">
<controller title="BlueWallet" id="AgC-eL-Hgc" customClass="InterfaceController" customModule="BlueWalletWatch" customModuleProvider="target">
<items>
<table alignment="left" id="jUH-JS-ccp">
<items>
@ -74,7 +72,7 @@
</group>
</items>
</group>
<button width="1" alignment="left" title="Receive" id="bPO-h8-ccD">
<button width="1" alignment="left" hidden="YES" title="Receive" id="bPO-h8-ccD">
<color key="titleColor" red="0.18431372549019609" green="0.37254901960784315" blue="0.70196078431372544" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" red="0.80000000000000004" green="0.8666666666666667" blue="0.97647058823529409" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="16"/>
@ -82,6 +80,14 @@
<segue destination="egq-Yw-qK5" kind="push" identifier="ReceiveInterfaceController" id="zEG-Xi-Smb"/>
</connections>
</button>
<button width="1" alignment="left" hidden="YES" title="Create Invoice" id="7bc-tt-Pab" userLabel="Create Invoice">
<color key="titleColor" red="0.1843137255" green="0.37254901959999998" blue="0.70196078429999997" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" red="0.80000000000000004" green="0.86666666670000003" blue="0.97647058819999999" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="16"/>
<connections>
<action selector="createInvoiceTapped" destination="XWa-4i-Abg" id="0YZ-PF-kAC"/>
</connections>
</button>
<label alignment="center" verticalAlignment="bottom" text="No Transactions" textAlignment="left" id="pi4-Bk-Jiq"/>
<table alignment="left" id="nyQ-lX-DX0">
<items>
@ -125,6 +131,7 @@
</table>
</items>
<connections>
<outlet property="createInvoiceButton" destination="7bc-tt-Pab" id="CcN-EV-pnQ"/>
<outlet property="noTransactionsLabel" destination="pi4-Bk-Jiq" id="zft-Hw-KuZ"/>
<outlet property="receiveButton" destination="bPO-h8-ccD" id="xBq-42-9qP"/>
<outlet property="transactionsTable" destination="nyQ-lX-DX0" id="N1x-px-s08"/>
@ -163,9 +170,9 @@
<!--ReceiveInterfaceController-->
<scene sceneID="tQ7-Qr-5i4">
<objects>
<controller identifier="ReceiveInterfaceController" fullBounds="YES" fullScreen="YES" id="egq-Yw-qK5" customClass="ReceiveInterfaceController" customModule="BlueWalletWatch_Extension">
<controller identifier="ReceiveInterfaceController" fullBounds="YES" id="egq-Yw-qK5" customClass="ReceiveInterfaceController" customModule="BlueWalletWatch_Extension">
<items>
<imageView height="1" alignment="left" id="Dnb-sM-wdN"/>
<imageView height="0.90000000000000002" alignment="left" id="Dnb-sM-wdN"/>
<group width="1" alignment="center" verticalAlignment="center" hidden="YES" layout="vertical" id="0If-FP-smM">
<items>
<imageView width="60" height="60" alignment="center" image="loadingIndicator" contentMode="scaleAspectFit" id="nQb-s6-ySB"/>
@ -209,7 +216,7 @@
<separator alignment="left" alpha="0.0" id="i7u-PI-g7Q">
<color key="color" red="0.63137254899999995" green="0.63137254899999995" blue="0.63137254899999995" alpha="0.84999999999999998" colorSpace="calibratedRGB"/>
</separator>
<button width="1" alignment="left" verticalAlignment="bottom" title="Create" id="6eh-lx-UEe">
<button width="1" alignment="left" verticalAlignment="bottom" title="Create" enabled="NO" id="6eh-lx-UEe">
<color key="titleColor" red="0.1843137255" green="0.37254901959999998" blue="0.70196078429999997" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" red="0.80000000000000004" green="0.86666666670000003" blue="0.97647058819999999" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="font" type="system" weight="medium" pointSize="16"/>
@ -220,6 +227,7 @@
</items>
<connections>
<outlet property="amountButton" destination="0Hm-hv-Yi3" id="9DN-zh-BGB"/>
<outlet property="createButton" destination="6eh-lx-UEe" id="1T3-m4-oVN"/>
<outlet property="descriptionButton" destination="fcI-6Z-moQ" id="a7M-ZD-Zsi"/>
</connections>
</controller>
@ -335,5 +343,10 @@
<point key="canvasLocation" x="220" y="1029"/>
</scene>
</scenes>
<resources>
<image name="loadingIndicator" width="108" height="108"/>
<image name="pendingConfirmation" width="12" height="12"/>
<image name="walletHD" width="249" height="100.5"/>
</resources>
<color key="tintColor" red="0.40784313725490196" green="0.73333333333333328" blue="0.88235294117647056" alpha="1" colorSpace="calibratedRGB"/>
</document>

2
ios/BlueWalletWatch/Info.plist

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.5.0</string>
<string>4.8.0</string>
<key>CFBundleVersion</key>
<string>239</string>
<key>UISupportedInterfaceOrientations</key>

44
ios/Podfile

@ -1,18 +1,18 @@
# Uncomment the next line to define a global platform for your project
platform :ios, '10.0'
platform :ios, '9.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
workspace 'BlueWallet'
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
puts "Setting Swift Version setting for #{target.name}..."
puts "Setting Swift Version and deployment target setting for #{target.name}..."
config.build_settings['SWIFT_VERSION'] = '4.2'
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
end
end
end
def sharedPods
pod 'React', :path => '../node_modules/react-native/'
pod 'React-Core', :path => '../node_modules/react-native/React'
@ -38,21 +38,17 @@ def sharedPods
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
use_native_modules!
end
target 'BlueWallet' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# use_frameworks!
project 'BlueWallet.xcodeproj'
platform :ios, '10.0'
platform :ios, '9.0'
# Pods for BlueWallet
# React Native requirements
sharedPods
pod 'SentryReactNative', :path => '../node_modules/react-native-sentry'
sharedPods
end
target 'BlueWalletTests' do
@ -65,35 +61,13 @@ target 'BlueWalletWatch' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
# use_frameworks!
# Pods for BlueWalletWatch
platform :watchos, '5.1'
platform :watchos, '5.0'
end
target 'BlueWalletWatch Extension' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
# use_frameworks!
platform :watchos, '5.1'
pod 'EFQRCode', '5.0.0'
platform :watchos, '5.0'
pod 'EFQRCode', '5.1.0'
# Pods for BlueWalletWatch Extension
end
target 'TcpSockets' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
# use_frameworks!
project '../node_modules/react-native-tcp/ios/TcpSockets.xcodeproj'
sharedPods
end
target 'RCTQRCodeLocalImage' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
# use_frameworks!
project '../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage.xcodeproj'
sharedPods
end
target 'RCTPrivacySnapshot' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
# use_frameworks!
project '../node_modules/react-native-privacy-snapshot/RCTPrivacySnapshot.xcodeproj'
sharedPods
end
end

142
ios/Podfile.lock

@ -1,27 +1,27 @@
PODS:
- appcenter (2.3.0):
- appcenter (2.6.0):
- AppCenterReactNativeShared
- React
- appcenter-analytics (2.3.0):
- appcenter-analytics (2.6.0):
- AppCenter/Analytics
- AppCenterReactNativeShared
- React
- appcenter-crashes (2.3.0):
- AppCenter/Crashes (= 2.3.0)
- appcenter-crashes (2.6.0):
- AppCenter/Crashes
- AppCenterReactNativeShared
- React
- AppCenter/Analytics (2.3.0):
- AppCenter/Analytics (2.5.1):
- AppCenter/Core
- AppCenter/Core (2.3.0)
- AppCenter/Crashes (2.3.0):
- AppCenter/Core (2.5.1)
- AppCenter/Crashes (2.5.1):
- AppCenter/Core
- AppCenterReactNativeShared (2.3.0):
- AppCenter/Core (= 2.3.0)
- AppCenterReactNativeShared (2.6.0):
- AppCenter/Core (= 2.5.1)
- boost-for-react-native (1.63.0)
- BVLinearGradient (2.5.4):
- React
- DoubleConversion (1.1.6)
- EFQRCode (5.0.0):
- EFQRCode (5.1.0):
- swift_qrcodejs (~> 1.1.1)
- Folly (2018.10.22.00):
- boost-for-react-native
@ -37,8 +37,6 @@ PODS:
- lottie-react-native (3.1.1):
- lottie-ios (~> 3.0.3)
- React
- RCTSystemSetting (1.7.2):
- React
- React (0.60.5):
- React-Core (= 0.60.5)
- React-DevSupport (= 0.60.5)
@ -84,19 +82,21 @@ PODS:
- React-cxxreact (= 0.60.5)
- React-jsi (= 0.60.5)
- React-jsinspector (0.60.5)
- react-native-biometrics (1.6.1):
- React
- react-native-blur (0.8.0):
- React
- react-native-camera (2.11.1):
- react-native-camera (3.4.0):
- React
- react-native-camera/RCT (= 2.11.1)
- react-native-camera/RN (= 2.11.1)
- react-native-camera/RCT (2.11.1):
- react-native-camera/RCT (= 3.4.0)
- react-native-camera/RN (= 3.4.0)
- react-native-camera/RCT (3.4.0):
- React
- react-native-camera/RN (2.11.1):
- react-native-camera/RN (3.4.0):
- React
- react-native-haptic-feedback (1.7.1):
- React
- react-native-image-picker (0.28.1):
- react-native-image-picker (1.1.0):
- React
- react-native-randombytes (3.5.3):
- React
@ -127,9 +127,15 @@ PODS:
- React-Core (= 0.60.5)
- React-RCTWebSocket (0.60.5):
- React-Core (= 0.60.5)
- RNCAsyncStorage (1.5.1):
- ReactNativePrivacySnapshot (1.0.0):
- React
- RemobileReactNativeQrcodeLocalImage (1.0.4):
- React
- RNCAsyncStorage (1.6.2):
- React
- RNDefaultPreference (1.4.1):
- React
- RNDeviceInfo (2.2.2):
- RNDeviceInfo (4.0.1):
- React
- RNFS (2.13.3):
- React
@ -137,40 +143,43 @@ PODS:
- React
- RNHandoff (0.0.3):
- React
- RNQuickAction (0.3.12):
- React
- RNRate (1.0.1):
- React
- RNSecureKeyStore (1.0.0):
- React
- RNSentry (1.0.9):
- React
- Sentry (~> 4.4.0)
- RNShare (2.0.0):
- React
- RNSVG (9.5.1):
- React
- RNVectorIcons (6.6.0):
- React
- RNWatch (0.2.1):
- RNWatch (0.4.1):
- React
- Sentry (4.1.3):
- Sentry/Core (= 4.1.3)
- Sentry/Core (4.1.3)
- SentryReactNative (0.43.2):
- React
- Sentry (~> 4.1.3)
- Sentry (4.4.2):
- Sentry/Core (= 4.4.2)
- Sentry/Core (4.4.2)
- swift_qrcodejs (1.1.2)
- TcpSockets (3.3.2):
- React
- ToolTipMenu (5.2.1):
- React
- yoga (0.60.5.React)
DEPENDENCIES:
- appcenter (from `../node_modules/appcenter`)
- appcenter-analytics (from `../node_modules/appcenter-analytics`)
- appcenter-crashes (from `../node_modules/appcenter-crashes`)
- appcenter (from `../node_modules/appcenter/ios`)
- appcenter-analytics (from `../node_modules/appcenter-analytics/ios`)
- appcenter-crashes (from `../node_modules/appcenter-crashes/ios`)
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EFQRCode (= 5.0.0)
- EFQRCode (= 5.1.0)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- lottie-react-native (from `../node_modules/lottie-react-native`)
- RCTSystemSetting (from `../node_modules/react-native-system-setting`)
- React (from `../node_modules/react-native/`)
- React-Core (from `../node_modules/react-native/React`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
@ -178,6 +187,7 @@ DEPENDENCIES:
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-biometrics (from `../node_modules/react-native-biometrics`)
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- react-native-camera (from `../node_modules/react-native-camera`)
- react-native-haptic-feedback (from `../node_modules/react-native-haptic-feedback`)
@ -195,18 +205,23 @@ DEPENDENCIES:
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- React-RCTWebSocket (from `../node_modules/react-native/Libraries/WebSocket`)
- ReactNativePrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
- "RemobileReactNativeQrcodeLocalImage (from `../node_modules/@remobile/react-native-qrcode-local-image`)"
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- RNDefaultPreference (from `../node_modules/react-native-default-preference`)
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNHandoff (from `../node_modules/react-native-handoff`)
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
- RNRate (from `../node_modules/react-native-rate/ios`)
- RNSecureKeyStore (from `../node_modules/react-native-secure-key-store/ios`)
- "RNSentry (from `../node_modules/@sentry/react-native`)"
- RNShare (from `../node_modules/react-native-share`)
- RNSVG (from `../node_modules/react-native-svg`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- RNWatch (from `../node_modules/react-native-watch-connectivity`)
- SentryReactNative (from `../node_modules/react-native-sentry`)
- TcpSockets (from `../node_modules/react-native-tcp`)
- ToolTipMenu (from `../node_modules/react-native-tooltip`)
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@ -222,11 +237,11 @@ SPEC REPOS:
EXTERNAL SOURCES:
appcenter:
:path: "../node_modules/appcenter"
:path: "../node_modules/appcenter/ios"
appcenter-analytics:
:path: "../node_modules/appcenter-analytics"
:path: "../node_modules/appcenter-analytics/ios"
appcenter-crashes:
:path: "../node_modules/appcenter-crashes"
:path: "../node_modules/appcenter-crashes/ios"
BVLinearGradient:
:path: "../node_modules/react-native-linear-gradient"
DoubleConversion:
@ -237,8 +252,6 @@ EXTERNAL SOURCES:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
lottie-react-native:
:path: "../node_modules/lottie-react-native"
RCTSystemSetting:
:path: "../node_modules/react-native-system-setting"
React:
:path: "../node_modules/react-native/"
React-Core:
@ -253,6 +266,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-biometrics:
:path: "../node_modules/react-native-biometrics"
react-native-blur:
:path: "../node_modules/@react-native-community/blur"
react-native-camera:
@ -287,8 +302,14 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/Vibration"
React-RCTWebSocket:
:path: "../node_modules/react-native/Libraries/WebSocket"
ReactNativePrivacySnapshot:
:path: "../node_modules/react-native-privacy-snapshot"
RemobileReactNativeQrcodeLocalImage:
:path: "../node_modules/@remobile/react-native-qrcode-local-image"
RNCAsyncStorage:
:path: "../node_modules/@react-native-community/async-storage"
RNDefaultPreference:
:path: "../node_modules/react-native-default-preference"
RNDeviceInfo:
:path: "../node_modules/react-native-device-info"
RNFS:
@ -297,10 +318,14 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-gesture-handler"
RNHandoff:
:path: "../node_modules/react-native-handoff"
RNQuickAction:
:path: "../node_modules/react-native-quick-actions"
RNRate:
:path: "../node_modules/react-native-rate/ios"
RNSecureKeyStore:
:path: "../node_modules/react-native-secure-key-store/ios"
RNSentry:
:path: "../node_modules/@sentry/react-native"
RNShare:
:path: "../node_modules/react-native-share"
RNSVG:
@ -309,28 +334,27 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-vector-icons"
RNWatch:
:path: "../node_modules/react-native-watch-connectivity"
SentryReactNative:
:path: "../node_modules/react-native-sentry"
TcpSockets:
:path: "../node_modules/react-native-tcp"
ToolTipMenu:
:path: "../node_modules/react-native-tooltip"
yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
AppCenter: 9784d2fc998c9bd0b8fbaf4fb9ed69526d12ce1a
appcenter: e55cb133b100379be61158f511082196b8022424
appcenter-analytics: 9db55feacae3ca037c310d31620760ae054d50c6
appcenter-crashes: 47c9257ce4c9a7625face00f77908e69fa459394
AppCenterReactNativeShared: 7ac481cba7f926848a7be76dca4dcb2692df3b06
AppCenter: fddcbac6e4baae3d93a196ceb0bfe0e4ce407dec
appcenter: dc687dcf81280ccab1dc938b0b974d265144a802
appcenter-analytics: fa8dba207d07733dcbda749d262fde3e7161258d
appcenter-crashes: fa97ffec69882486d7183193cc9394473757d1ad
AppCenterReactNativeShared: d5e360f8a4cb5126d29e31ab98051d2f070ba631
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: 8cbc5155c978f2e43098818c91d206d07aae6d30
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
EFQRCode: 07437cfbce3a1e497397a4f3d766c980d8972608
EFQRCode: 07ee69f29196329e974b9bffa5381240cc09ea4c
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
lottie-ios: c38c3178ae8c4a8f200661aa5f80b9f1ca7f56b3
lottie-react-native: d8caf2aa9ab8bb76312e44997c1c91804a23d44d
RCTSystemSetting: 9279ff44c49bb4fb0a5d335a0851852c8f3eda99
React: 53c53c4d99097af47cf60594b8706b4e3321e722
React-Core: ba421f6b4f4cbe2fb17c0b6fc675f87622e78a64
React-cxxreact: 8384287780c4999351ad9b6e7a149d9ed10a2395
@ -338,10 +362,11 @@ SPEC CHECKSUMS:
React-jsi: 4d8c9efb6312a9725b18d6fc818ffc103f60fec2
React-jsiexecutor: 90ad2f9db09513fc763bc757fdc3c4ff8bde2a30
React-jsinspector: e08662d1bf5b129a3d556eb9ea343a3f40353ae4
react-native-biometrics: 4aaf49f9f8bd28c6aa3ec53534ca1b6b00486f6a
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: 96a3c81f27da57b816fbb6808dde20dc96d1431e
react-native-camera: 203091b4bf99d48b788a0682ad573e8718724893
react-native-haptic-feedback: 22c9dc85fd8059f83bf9edd9212ac4bd4ae6074d
react-native-image-picker: fd93361c666f397bdf72f9c6c23f13d2685b9173
react-native-image-picker: 3637d63fef7e32a230141ab4660d3ceb773c824f
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
react-native-slider: 6d83f7b8076a84e965a43fbdcfcf9dac19cea42e
react-native-webview: f72ac4078e115dfa741cc588acb1cca25566457d
@ -355,23 +380,28 @@ SPEC CHECKSUMS:
React-RCTText: b074d89033583d4f2eb5faf7ea2db3a13c7553a2
React-RCTVibration: 2105b2e0e2b66a6408fc69a46c8a7fb5b2fdade0
React-RCTWebSocket: cd932a16b7214898b6b7f788c8bddb3637246ac4
RNCAsyncStorage: 2808c378f6a70f22f66eeb6c11a8d69f326dd795
RNDeviceInfo: a88be26a64ada7cbc2bc0ebbd1662d340304874e
ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
RNCAsyncStorage: 5ae4d57458804e99f73d427214442a6b10a53856
RNDefaultPreference: 12d246dd2222e66dadcd76cc1250560663befc3a
RNDeviceInfo: 12faae605ba42a1a5041c3c41a77834bc23f049d
RNFS: c9bbde46b0d59619f8e7b735991c60e0f73d22c1
RNGestureHandler: 5329a942fce3d41c68b84c2c2276ce06a696d8b0
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNQuickAction: eca9a5dd04b5cdf8a0dd32d8be8844dc33aba2bd
RNRate: 29be49c24b314c4e8ec09d848c3965f61cb0be47
RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8
RNSentry: 2803ba8c8129dcf26b79e9b4d8c80168be6e4390
RNShare: 8b171d4b43c1d886917fdd303bf7a4b87167b05c
RNSVG: 0eb087cfb5d7937be93c45b163b26352a647e681
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
RNWatch: c2d3bff3adf9ce06e10eb8c2d75c9043f5df0e35
Sentry: 4e8a17b61ddd116f89536cc81d567fdee1ebca96
SentryReactNative: 07237139c00366ea2e75ae3e5c566e7a71c27a90
RNWatch: a14e378448e187cc12f307f61d41fe8a65400e86
Sentry: bba998b0fb157fdd6596aa73290a9d67ae47be79
swift_qrcodejs: 4d024fc98b0778b804ec6a5c810880fd092aec9d
ToolTipMenu: c158702a26154d892bc9e6eaa7d7382f0f1ee16e
TcpSockets: 8d839b9b14f6f344d98e4642ded13ab3112b462d
ToolTipMenu: bdcaa0e888bcf44778a67fe34639b094352e904e
yoga: 312528f5bbbba37b4dcea5ef00e8b4033fdd9411
PODFILE CHECKSUM: 1a39cb1ff2ae237255b84fcbe9415cb089e659c7
PODFILE CHECKSUM: 4ac476f363ad99e4ad854f97a5c1854ea0423bf6
COCOAPODS: 1.7.5

125
ios/TodayExtension/Base.lproj/MainInterface.storyboard

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="M4Y-Lb-cyx">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Today View Controller-->
<scene sceneID="cwh-vc-ff4">
<objects>
<viewController id="M4Y-Lb-cyx" customClass="TodayViewController" customModule="BlueWallet___Bitcoin_Price" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" simulatedAppContext="notificationCenter" id="S3S-Oj-5AN">
<rect key="frame" x="0.0" y="0.0" width="320" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bitcoin Price" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aaf-Pc-Y9i">
<rect key="frame" x="16" y="8" width="288" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="fON-Nf-oBQ"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bcB-MD-aJf">
<rect key="frame" x="104" y="73" width="200" height="15"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Last Updated:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vU4-uK-6ow">
<rect key="frame" x="16" y="73" width="80" height="15"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="USD" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lkL-gv-1a1">
<rect key="frame" x="16" y="40" width="35" height="33"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="35" id="4Nq-zK-gi3"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bEQ-e6-Puo">
<rect key="frame" x="59" y="46.5" width="14" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrow.up" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="eST-DU-WIK">
<rect key="frame" x="221" y="47.5" width="17" height="18"/>
<constraints>
<constraint firstAttribute="width" constant="17" id="gkK-pz-TDJ"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="..." textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gm7-vT-KrH" userLabel="...">
<rect key="frame" x="290" y="40" width="14" height="33"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="14" id="KoT-51-551"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="from" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aqr-Mt-cor">
<rect key="frame" x="246" y="40" width="36" height="33"/>
<constraints>
<constraint firstAttribute="height" constant="33" id="u9N-WC-Os2"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="aqr-Mt-cor" firstAttribute="top" secondItem="aaf-Pc-Y9i" secondAttribute="bottom" constant="11" id="0ca-1C-JqG"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="aaf-Pc-Y9i" secondAttribute="trailing" constant="16" id="197-jr-Kn5"/>
<constraint firstItem="eST-DU-WIK" firstAttribute="centerY" secondItem="aqr-Mt-cor" secondAttribute="centerY" id="2yd-pY-y1Y"/>
<constraint firstItem="bcB-MD-aJf" firstAttribute="leading" secondItem="vU4-uK-6ow" secondAttribute="trailing" constant="8" id="5bB-Zv-Yeq"/>
<constraint firstItem="lkL-gv-1a1" firstAttribute="leading" secondItem="ssy-KU-ocm" secondAttribute="leading" constant="16" id="Bgx-xM-CSS"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="bottom" secondItem="bcB-MD-aJf" secondAttribute="bottom" constant="12" id="EPP-OS-3b6"/>
<constraint firstItem="vU4-uK-6ow" firstAttribute="leading" secondItem="ssy-KU-ocm" secondAttribute="leading" constant="16" id="EkD-jp-arv"/>
<constraint firstItem="gm7-vT-KrH" firstAttribute="centerY" secondItem="aqr-Mt-cor" secondAttribute="centerY" id="Eo2-n1-zbi"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="bottom" secondItem="vU4-uK-6ow" secondAttribute="bottom" constant="12" id="JSh-ZE-k1H"/>
<constraint firstItem="bcB-MD-aJf" firstAttribute="centerY" secondItem="vU4-uK-6ow" secondAttribute="centerY" id="MUL-tE-LmX"/>
<constraint firstItem="bEQ-e6-Puo" firstAttribute="leading" secondItem="lkL-gv-1a1" secondAttribute="trailing" constant="8" id="Ml2-4o-Yqk"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="gm7-vT-KrH" secondAttribute="trailing" constant="16" id="OLV-lQ-T8a"/>
<constraint firstItem="gm7-vT-KrH" firstAttribute="centerY" secondItem="bEQ-e6-Puo" secondAttribute="centerY" id="Rle-PT-j9m"/>
<constraint firstItem="gm7-vT-KrH" firstAttribute="leading" secondItem="aqr-Mt-cor" secondAttribute="trailing" constant="8" id="Rtu-ah-AvP"/>
<constraint firstItem="gm7-vT-KrH" firstAttribute="firstBaseline" secondItem="aqr-Mt-cor" secondAttribute="firstBaseline" id="YIV-xq-qlw"/>
<constraint firstItem="bEQ-e6-Puo" firstAttribute="centerY" secondItem="lkL-gv-1a1" secondAttribute="centerY" id="Ys3-7f-RIc"/>
<constraint firstItem="aaf-Pc-Y9i" firstAttribute="leading" secondItem="S3S-Oj-5AN" secondAttribute="leading" constant="16" id="a1b-Yq-aZb"/>
<constraint firstItem="aaf-Pc-Y9i" firstAttribute="top" secondItem="S3S-Oj-5AN" secondAttribute="top" constant="8" id="aIo-h1-w4F"/>
<constraint firstItem="lkL-gv-1a1" firstAttribute="top" secondItem="aaf-Pc-Y9i" secondAttribute="bottom" constant="11" id="dET-8J-W4K"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="bcB-MD-aJf" secondAttribute="trailing" constant="16" id="kkD-VZ-BAt"/>
<constraint firstItem="vU4-uK-6ow" firstAttribute="firstBaseline" secondItem="lkL-gv-1a1" secondAttribute="baseline" constant="16" symbolType="layoutAnchor" id="lml-Hc-8Sv"/>
<constraint firstItem="aqr-Mt-cor" firstAttribute="leading" secondItem="eST-DU-WIK" secondAttribute="trailing" constant="8" id="t4j-U9-sOm"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ssy-KU-ocm"/>
</view>
<extendedEdge key="edgesForExtendedLayout"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<size key="freeformSize" width="320" height="100"/>
<connections>
<outlet property="currencyLabel" destination="lkL-gv-1a1" id="Gzb-TF-E6W"/>
<outlet property="lastPrice" destination="gm7-vT-KrH" id="YIE-KJ-0qW"/>
<outlet property="lastPriceArrowImage" destination="eST-DU-WIK" id="VWH-ED-VL1"/>
<outlet property="lastPriceFromLabel" destination="aqr-Mt-cor" id="p17-Aq-ufa"/>
<outlet property="lastUpdatedDate" destination="bcB-MD-aJf" id="A9U-MB-qJq"/>
<outlet property="priceLabel" destination="bEQ-e6-Puo" id="0IP-Yg-dWr"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="vXp-U4-Rya" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="137.68115942028987" y="96.428571428571431"/>
</scene>
</scenes>
<resources>
<image name="arrow.up" catalog="system" width="60" height="64"/>
</resources>
</document>

10
ios/TodayExtension/BlueWallet - Bitcoin Price.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>

31
ios/TodayExtension/Info.plist

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

65
ios/TodayExtension/New Group/API.swift

@ -0,0 +1,65 @@
//
// API.swift
// TodayExtension
//
// Created by Marcos Rodriguez on 11/2/19.
// Copyright © 2019 Facebook. All rights reserved.
//
import Foundation
class API {
static func fetchPrice(currency: String, completion: @escaping ((Dictionary<String, Any>?, Error?) -> Void)) {
guard let url = URL(string: "https://api.coindesk.com/v1/bpi/currentPrice/\(currency).json") else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
let json = try? JSONSerialization.jsonObject(with: dataResponse, options: .mutableContainers) as? Dictionary<String, Any>,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
completion(nil, error)
return }
completion(json, nil)
}.resume()
}
static func getUserPreferredCurrency() -> String {
guard let userDefaults = UserDefaults(suiteName: "group.io.bluewallet.bluewallet"),
let preferredCurrency = userDefaults.string(forKey: "preferredCurrency")
else {
return "USD"
}
if preferredCurrency != API.getLastSelectedCurrency() {
UserDefaults.standard.removeObject(forKey: TodayData.TodayCachedDataStoreKey)
UserDefaults.standard.removeObject(forKey: TodayData.TodayDataStoreKey)
UserDefaults.standard.synchronize()
}
return preferredCurrency
}
static func getUserPreferredCurrencyLocale() -> String {
guard let userDefaults = UserDefaults(suiteName: "group.io.bluewallet.bluewallet"),
let preferredCurrency = userDefaults.string(forKey: "preferredCurrencyLocale")
else {
return "en_US"
}
return preferredCurrency
}
static func getLastSelectedCurrency() -> String {
guard let dataStore = UserDefaults.standard.string(forKey: "currency") else {
return "USD"
}
return dataStore
}
static func saveNewSelectedCurrency() {
UserDefaults.standard.setValue(API.getUserPreferredCurrency(), forKey: "currency")
}
}

89
ios/TodayExtension/TodayDataStore.swift

@ -0,0 +1,89 @@
//
// TodayDataStore.swift
// TodayExtension
//
// Created by Marcos Rodriguez on 11/3/19.
// Copyright © 2019 Facebook. All rights reserved.
//
import Foundation
struct TodayDataStore {
let rate: String
let lastUpdate: String
var formattedDate: String? {
let isoDateFormatter = ISO8601DateFormatter()
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
dateFormatter.dateStyle = .short
if let date = isoDateFormatter.date(from: lastUpdate) {
return dateFormatter.string(from: date)
}
return nil
}
var rateDoubleValue: Double? {
let rateDigits = rate.replacingOccurrences(of: ",", with: "");
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 2
numberFormatter.minimumFractionDigits = 2
if let rateDoubleValue = numberFormatter.number(from: rateDigits) {
return rateDoubleValue.doubleValue
}
return nil
}
var formattedRate: String? {
let rateDigits = rate.replacingOccurrences(of: ",", with: "");
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 2
numberFormatter.minimumFractionDigits = 2
if let rateNumber = numberFormatter.number(from: rateDigits) {
numberFormatter.numberStyle = .currency
numberFormatter.locale = Locale(identifier: API.getUserPreferredCurrencyLocale())
return numberFormatter.string(from: rateNumber);
}
return nil
}
}
class TodayData {
static let TodayDataStoreKey = "TodayDataStoreKey"
static let TodayCachedDataStoreKey = "TodayCachedDataStoreKey"
static func savePriceRateAndLastUpdate(rate: String, lastUpdate: String) {
UserDefaults.standard.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: TodayDataStoreKey)
UserDefaults.standard.synchronize()
}
static func getPriceRateAndLastUpdate() -> TodayDataStore? {
guard let dataStore = UserDefaults.standard.value(forKey: TodayDataStoreKey) as? [String: String], let rate = dataStore["rate"], let lastUpdate = dataStore["lastUpdate"] else {
return nil
}
return TodayDataStore(rate: rate, lastUpdate: lastUpdate)
}
static func saveCachePriceRateAndLastUpdate(rate: String, lastUpdate: String) {
UserDefaults.standard.setValue(["rate": rate, "lastUpdate": lastUpdate], forKey: TodayCachedDataStoreKey)
UserDefaults.standard.synchronize()
}
static func getCachedPriceRateAndLastUpdate() -> TodayDataStore? {
guard let dataStore = UserDefaults.standard.value(forKey: TodayCachedDataStoreKey) as? [String: String], var rate = dataStore["rate"], let lastUpdate = dataStore["lastUpdate"] else {
return nil
}
rate = rate.replacingOccurrences(of: ",", with: "");
return TodayDataStore(rate: rate, lastUpdate: lastUpdate)
}
}

130
ios/TodayExtension/TodayViewController.swift

@ -0,0 +1,130 @@
//
// TodayViewController.swift
// TodayExtension
//
// Created by Marcos Rodriguez on 11/2/19.
// Copyright © 2019 Facebook. All rights reserved.
//
import UIKit
import NotificationCenter
class TodayViewController: UIViewController, NCWidgetProviding {
@IBOutlet weak var currencyLabel: UILabel!
@IBOutlet weak var lastUpdatedDate: UILabel!
@IBOutlet weak var priceLabel: UILabel!
@IBOutlet weak var lastPriceArrowImage: UIImageView!
@IBOutlet weak var lastPrice: UILabel!
@IBOutlet weak var lastPriceFromLabel: UILabel!
private var lastPriceNumber: NSNumber?
override func viewDidLoad() {
super.viewDidLoad()
setLastPriceOutletsHidden(isHidden: true)
if let lastStoredTodayStore = TodayData.getPriceRateAndLastUpdate() {
processRateAndLastUpdate(todayStore: lastStoredTodayStore)
} else {
setLastPriceOutletsHidden(isHidden: true)
}
}
func setLastPriceOutletsHidden(isHidden: Bool) {
lastPrice.isHidden = isHidden
lastPriceFromLabel.isHidden = isHidden
lastPriceArrowImage.isHidden = isHidden
}
func processRateAndLastUpdate(todayStore: TodayDataStore) {
guard let rateString = todayStore.formattedRate, let dateFormatted = todayStore.formattedDate else { return }
priceLabel.text = rateString
lastUpdatedDate.text = dateFormatted
}
func processStoredRateAndLastUpdate(todayStore: TodayDataStore) {
guard let lastPriceNumber = todayStore.formattedRate else { return }
lastPrice.text = lastPriceNumber
}
func processCachedStoredRateAndLastUpdate(new: TodayDataStore, cached: TodayDataStore) {
guard let newPriceDoubleValue = new.rateDoubleValue, let cachedPriceNumber = cached.formattedRate, let cachedPriceNumberDoubleValue = cached.rateDoubleValue else { return }
lastPrice.text = cachedPriceNumber
if newPriceDoubleValue > cachedPriceNumberDoubleValue {
self.lastPriceArrowImage.image = UIImage(systemName: "arrow.up")
self.setLastPriceOutletsHidden(isHidden: false)
} else {
self.lastPriceArrowImage.image = UIImage(systemName: "arrow.down")
self.setLastPriceOutletsHidden(isHidden: false)
}
}
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
// Perform any setup necessary in order to update the view.
// If an error is encountered, use NCUpdateResult.Failed
// If there's no update required, use NCUpdateResult.NoData
// If there's an update, use NCUpdateResult.NewData
let userPreferredCurrency = API.getUserPreferredCurrency();
self.currencyLabel.text = userPreferredCurrency
API.fetchPrice(currency: userPreferredCurrency, completion: { (result, error) in
DispatchQueue.main.async { [unowned self] in
guard let result = result else {
completionHandler(.failed)
return
}
guard let bpi = result["bpi"] as? Dictionary<String, Any>, let preferredCurrency = bpi[userPreferredCurrency] as? Dictionary<String, Any>, let rateString = preferredCurrency["rate"] as? String,
let time = result["time"] as? Dictionary<String, Any>, let lastUpdatedString = time["updatedISO"] as? String
else {
return
}
let latestRateDataStore = TodayDataStore(rate: rateString, lastUpdate: lastUpdatedString)
if let lastStoredTodayStore = TodayData.getPriceRateAndLastUpdate(), lastStoredTodayStore.lastUpdate == latestRateDataStore.lastUpdate, rateString == lastStoredTodayStore.rate, API.getLastSelectedCurrency() == userPreferredCurrency {
if let cached = TodayData.getCachedPriceRateAndLastUpdate() {
self.processCachedStoredRateAndLastUpdate(new: lastStoredTodayStore, cached: cached)
} else {
self.setLastPriceOutletsHidden(isHidden: true)
}
completionHandler(.noData)
} else {
self.processRateAndLastUpdate(todayStore: latestRateDataStore)
let priceRiceAndLastUpdate = TodayData.getPriceRateAndLastUpdate()
if let rate = priceRiceAndLastUpdate?.rate, let lastUpdate = priceRiceAndLastUpdate?.lastUpdate {
TodayData.saveCachePriceRateAndLastUpdate(rate: rate, lastUpdate: lastUpdate);
}
if let latestRateDataStore = latestRateDataStore.rateDoubleValue, let lastStoredPriceNumber = priceRiceAndLastUpdate?.rateDoubleValue, API.getLastSelectedCurrency() == userPreferredCurrency {
if latestRateDataStore > lastStoredPriceNumber {
self.lastPriceArrowImage.image = UIImage(systemName: "arrow.up")
self.setLastPriceOutletsHidden(isHidden: false)
} else {
self.lastPriceArrowImage.image = UIImage(systemName: "arrow.down")
self.setLastPriceOutletsHidden(isHidden: false)
}
self.lastPrice.text = priceRiceAndLastUpdate?.formattedRate
} else {
self.setLastPriceOutletsHidden(isHidden: true)
}
TodayData.savePriceRateAndLastUpdate(rate: latestRateDataStore.rate, lastUpdate: latestRateDataStore.lastUpdate)
API.saveNewSelectedCurrency()
completionHandler(.newData)
}
}
})
}
}

92
ios/fastlane/metadata/en-US/release_notes.txt

@ -1,61 +1,49 @@
Always backup your keys! While your coins are safe, bugs happen, and app
could sometimes lose your wallets (fixed).
v4.4.1
v4.8.0
======
* FIX: crash on ManageFunds
* ADD: marketplace for regular onchain btc wallets
* ADD: WatchApp Complication.
* FIX: Wallet gradient for Bech32 on watch app
* FIX: v3.9.8 fails to fill value #470
* FIX: Don't allow more than one leading zero
* ADD: Today Extension and Quick Actions
* ADD: Send max option on advanced menu
* ADD: Add Onchain address view for Lightning
* FIX: Allow textfield to be visible above keyboard
* FIX: lapp browser when typing URL without https scheme it doesnt work
* ADD: Value and memo to the success screen fix logic for both sent and receive
* FIX: layout for small devices with flexbox
* FIX: Dont allow zero invoices to enable create invoice button
* FIX: Change create button on Receive LN payment should be create invoice
* FIX: Update for watch
v4.4.0
v4.7.1
======
* ADD: send MAX (for BIP84 & BIP49)
* ADD: Scan to receive (lnurl spec)
* ADD: New TX status screen
* ADD: Share QRCode along with address
* ADD: Allow user to hide their balance
* ADD: KRW Fiat
* FIX: wallet selector on send view
* FIX: all data is stored in OS secure keystore
* FIX: broken LIGHTNING scheme handling
* FIX: import legacy address by WIF with uncompressed pubkey (closes #590)
* ADD: Handoff for a transaction and wallets
* FIX: QR Correctness for watch app
* FIX: Tap to copy preimage text
* FIX: Show last saved exchange rate
v4.2.0
* ADD: Lapp browser
* FIX: White screen on boot
* FIX: Lightning wallet was not shown on Watch app
* FIX: crash on PSBT tx broadcast (when using with hardware wallet)
* REF: mnemonic backup screen
* DEL: Auto brightenss
v4.7.0
======
* ADD: bumpfee (RBF) & cancelTx (RBF) for BIP84 HD wallet
* ADD: CPFP for BIP84 HD wallets
* ADD: Electrum server configuration
* ADD: Hungarian localization
* ADD: HUF fiat unit
* FIX: receive address generation with no internet
* FIX: wait for electrum connection before fetching tx or broadcast
* FIX: Receive UI jumpiness
* FIX: iOS App crashes when scaning QR Code
* FIX: Amount when scanning was not displayed in amount UI
* FIX: ln transaction details screen
* FIX: import LN wallet with custom lndhub
* FIX: BIP84 txs with low confirmations not updating
* FIX: crash
* REF: Swedish translation updates
* REF: better initial HD rescan
v4.1.0
* ADD: external marketplace link
* FIX: electrum connection
* FIX: Now able to use biometrics with encrypted storage (not for unlocking)
* FIX: LApp marketplace address is now editable
* FIX: single address watch-only wallet Receive button crash
v4.6.0
======
* REF: faster initial HD rescan
* FIX: ln transaction details screen
* FIX: import LN wallet with custom lndhub
* FIX: BIP84 txs with low confirmations not updating
* FIX: crash
* REF: Swedish translation updates
* ADD: Optional biometrics unlock (this does not exclude full encryption)
* ADD: Cryptoadvance HW wallet support (for BIP84) via PSBT and QR codes
* ADD: LN Refill with external wallet
* ADD: Default into wallet on launch
* FIX: NaN when sending onchain
* FIX: zero on send success screen
* FIX: Time shown for top-most transaction
* FIX: minor issue with scanQrWif
* FIX: typo on NL language
* FIX: better wallet export QR readability
* FIX: RBF tx memo porting
* REF: better initial HD rescan

2
ios/sentry.properties

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

236
loc/ZAR_Afr.js

@ -0,0 +1,236 @@
module.exports = {
_: {
storage_is_encrypted: 'U geheue spasie is nou ge-enkripteer. ‘n Wagwoord word benodig om toegang te verkry. ',
enter_password: 'Sleutel wagwoord in',
bad_password: 'Verkeerde wagwoord, probeer weer',
never: 'nooit',
continue: 'Gaan voort',
ok: 'OK',
},
wallets: {
select_wallet: 'Kies Beursie',
options: 'opsies',
createBitcoinWallet:
'U het nie huidiglik `n geldige Bitcoin Beursie nie. Skep of voer eers ‘n Bitcoin Beursie in, sodat ‘n Bitcoin Lightning Beursie geskep en bevonds mag word. Wil U voortgaan?',
list: {
app_name: 'BlueWallet',
title: 'beursies',
header:
'U beursie verteenwoordig ‘n sleutelkombinasie, bestaande uit geheims (privaat sleutel) en address' +
'wat u kan gebruik om fondse te ontvang.',
add: 'Skep Beursie',
create_a_wallet: 'Skep ‘n beursie',
create_a_wallet1: 'Dit is gratis so skep',
create_a_wallet2: 'soveel as wat u benodig',
latest_transaction: 'laaste transaksie',
empty_txs1: 'U transaksies is hier beskikbaar,',
empty_txs2: 'huidiglik geen transaksies',
tap_here_to_buy: 'Raak hier om Bitcoin te koop',
},
reorder: {
title: 'Herorganiseer Beursies',
},
add: {
title: 'skep beursie',
description:
'U kan ‘n beursie invoer (in WIF - Wallet Import Format), of ‘n nuwe beursie skep. Beursies ondersteun Segwit as standaard.',
scan: 'Skandeer',
create: 'Skep',
label_new_segwit: 'Nuwe SegWit',
label_new_lightning: 'Nuwe Lightning',
wallet_name: 'beursie naam',
wallet_type: 'tipe',
or: 'of',
import_wallet: 'Beursie Invoer',
imported: 'Ingevoer',
coming_soon: 'In die toekoms',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'Beursiet',
address: 'AdresAddress',
type: 'Tipe',
label: 'Etiket',
destination: 'bestemming',
description: 'beskrywing',
are_you_sure: 'Is u Seker?',
yes_delete: 'Ja, vernietig',
no_cancel: 'Nee, kanseleerl',
delete: 'Vernietig',
save: 'Berg',
delete_this_wallet: 'Vernietig hierdie beursie',
export_backup: 'voer uit / kopieer',
buy_bitcoin: 'Koop Bitcoin',
show_xpub: 'Wys beursie XPUB',
},
export: {
title: 'beursie uitvoer',
},
xpub: {
title: 'beursie XPUB',
copiedToClipboard: 'Gestuur na klipbord.',
},
import: {
title: 'Invoer',
explanation:
'Sleutel mnemonic, privaat sleutel, WIF, of enige text verwysing. BlueWallet sal die korrekte formaat kies en u beursie importeer ',
imported: 'Invoer suksesvol',
error: 'U invoer het misluk. Maak asseblief seker u data is korrek en geldig.',
success: 'Suksesvol',
do_import: 'Invoer',
scan_qr: 'of skandeer QR kode?',
},
scanQrWif: {
go_back: 'Gaan Terug',
cancel: 'Kanseleer',
decoding: 'Decoding',
input_password: 'Input password',
password_explain: 'Hierdie is ‘n BIP38 ge-enkripteerde privaat sleutel',
bad_password: 'Wagwoord verkeerd',
wallet_already_exists: 'Hierdie beursie bestaan alreeds',
bad_wif: 'WIF verkeerd',
imported_wif: 'WIF invoer suksesvol ',
with_address: ' met adres ',
imported_segwit: 'Segwit Invoer Suksesvol',
imported_legacy: 'Legacy Invoer',
imported_watchonly: 'Kyk Slegs invoer suksesvol',
},
},
transactions: {
list: {
tabBarLabel: 'Transaksies',
title: 'transaksies',
description: 'Lys met inkomende en uitgaande transaksies van u beursies',
conf: 'bev.',
},
details: {
title: 'Transaksie',
from: 'Inset',
to: 'Resultaat',
copy: 'Kopieer',
transaction_details: 'Transaksie besonderhede',
show_in_block_explorer: 'Wys in blok verkenner',
},
},
send: {
header: 'Stuur',
details: {
title: 'skep transaksie',
amount_field_is_not_valid: 'Bedrag is ongeldig',
fee_field_is_not_valid: 'Fooi spasie is ongeldig',
address_field_is_not_valid: 'Adres is ongeldig',
total_exceeds_balance: 'Die bedrag is meer as die beskikbare balans.',
create_tx_error: 'Daar was ‘n probleem met die skepping van die transaksie. Bevestig asseblief die adres is geldig.',
address: 'adres',
amount_placeholder: 'bedrag om te stuur (in BTC)',
fee_placeholder: 'plus transaksie fooi (in BTC)',
note_placeholder: 'persoonlike notas',
cancel: 'Kanselleer',
scan: 'Skandeer',
send: 'Stuur',
create: 'Skep',
remaining_balance: 'Oorblywende balans',
},
confirm: {
header: 'Bevestig',
sendNow: 'Stuur nou',
},
success: {
done: 'Klaar',
},
create: {
details: 'Besonderhede',
title: 'skep transaksie',
error: 'Daar is ‘n probleem met die transaksie. Ongeldige adres of bedrag?',
go_back: 'Gaan Terug',
this_is_hex: 'Hierdie is die transaksie hex, geteken en gereed om na die netwerk uitgesaai te word.',
to: 'Aan',
amount: 'Bedrag',
fee: 'Fooi',
tx_size: 'TX groote',
satoshi_per_byte: 'Satoshi per byte',
memo: 'Memo',
broadcast: 'Saai uit',
not_enough_fee: 'Fooi te laag. Vermeerder die fooi',
},
},
receive: {
header: 'Ontvang',
details: {
title: 'Deel adres met krediteur',
share: 'deel',
copiedToClipboard: 'Gekopieer na klipbord.',
label: 'Beskrywing',
create: 'Skep',
setAmount: 'Bedrag ontvang',
},
},
buyBitcoin: {
header: 'Koop Bitcoin',
tap_your_address: 'Raak aan die adres om dit na die klipbord the stuur:',
copied: 'Gekopieer na klipbord!',
},
settings: {
header: 'instellings',
plausible_deniability: 'Geloofwaardige ontkenbaarheid...',
storage_not_encrypted: 'Berging: Nie-geenkripteer nie',
storage_encrypted: 'Berging: Ge-enkripteer',
password: 'Wagwoord',
password_explain: 'Skep die wagwoord wat u sal gebruik om u berging te de-enkripteer',
retype_password: 'Hervoer wagwoord',
passwords_do_not_match: 'Wagwoorde stem nie oor een nie',
encrypt_storage: 'Enkripteer Berging',
lightning_settings: 'Lightning Instellings',
lightning_settings_explain:
'Om u eie LND node te konnekteer, installeer asseblief LndHub' +
' and put its URL here in settings. Leave blank om die standaard LndHub' +
'(lndhub.io) te gebruik',
save: 'stoor',
about: 'info',
language: 'Taal',
currency: 'Geldeenheid',
},
plausibledeniability: {
title: 'Geloofwaardige Ontkenbaarheid',
help:
'Onder sekere omstandighede mag u dalk geforseer word om u ' +
'wagwoord te deel teen u wil. Om u te beskerm kan Bluewallet ‘n ' +
'tweede “fantasie” beursie skep wat as skerm kan dien. Indien u ' +
'hierdie wagwoord deel sal die 3de party nie toegang tot u hoof fondse kry nie.',
help2: 'Fantasie berging is heeltemal funksioneel',
create_fake_storage: 'Skep fantasie berging wagwoord',
go_back: 'Gaan terug',
create_password: 'Skep ‘n wagwoord',
create_password_explanation: 'Die wagwoord vir fantasie berging moet verskil van die wagwoord vir hoof berging',
password_should_not_match: 'Die wagwoord vir fantasie berging moet verskil van die wagwoord vir hoof berging.',
retype_password: 'Hervoer wagwoord',
passwords_do_not_match: 'Wagwoorde vergelyk nie, probeer weer',
success: 'Sukses',
},
lnd: {
title: 'bestuur fondse',
choose_source_wallet: 'Kies ‘n bron beursie',
refill_lnd_balance: 'Herlaai Lightning beursie',
refill: 'Herlaai',
withdraw: 'Ontrek',
expired: 'Verval',
placeholder: 'Faktuur',
sameWalletAsInvoiceError: 'U kan nie ‘n faktuur betaal met die selfde beursie waarmee die faktuur geksep is nie.',
},
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
has_been_paid: 'This invoice has been paid for',
please_pay: 'Please pay',
sats: 'sats',
for: 'For:',
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
};

239
loc/ZAR_Xho.js

@ -0,0 +1,239 @@
module.exports = {
_: {
storage_is_encrypted: 'Ukugcinwa kwakho kubhaliwe. Inombolo yokuvula iyadingeka ukuba ichithwe',
enter_password: 'Faka inombolo yokuvula',
bad_password: 'Iphasiwedi engalunganga, zama kwakhona',
never: 'Ungalingi',
continue: 'Qhubeka',
ok: 'OK',
},
wallets: {
select_wallet: 'Khetha ingxowa',
options: 'Ukhetho',
createBitcoinWallet:
'Okwangoku awunayo ingxowa yebitcoin. Ukuze kuxhaswe ingxowa ekhawulezayo, Ingxowa yeBitcoin kufuneka idalwe okanye ikhutshelwe. Ungathanda ukuqhubeka ?',
list: {
app_name: 'BlueWallet',
title: 'Ingxowa',
header: 'Ingxowa imele ukuba nemfihlelo yokuyivula nekheli kwaye unokuyisebenzisa ukwamkela imali.',
add: 'Yongeza Ingxowa',
create_a_wallet: 'Yenza ingxowa',
create_a_wallet1: 'Ayihlawulelwa kwaye ungayenza',
create_a_wallet2: 'Ungenza zibeninzi indlela zokuhlawula',
latest_transaction: 'Utshintsho olutsha',
empty_txs1: 'Intengiso yakho iya kubonakala apha,',
empty_txs2: 'akuho nanye okwangoku',
tap_here_to_buy: 'Cofa apha ukuthenga ibitcoin',
},
reorder: {
title: 'Yenza kwakhona ingxowa',
},
add: {
title: 'yongeza ingxowa',
description:
'Unokukhangela ingxowa yephepha yokugcinwa kwephepha ( kwi-WIF – indlela lokungenisa ingxowa), okanye wenze ingxowa entsha. Ingxowa yeSegwit exhaswa yi-default.',
scan: 'Ukuqondisa',
create: 'Yakha',
label_new_segwit: 'SegWit entsha',
label_new_lightning: 'Umbane omtsha',
wallet_name: 'igama lengxowa',
wallet_type: 'uhlobo',
or: 'okanye',
import_wallet: 'Ukungenisa ingxowa',
imported: 'ngeniswa',
coming_soon: 'Kuza ngokukhawuleza',
lightning: 'Umbane',
bitcoin: 'Bitcoin',
},
details: {
title: 'Ingxowa',
address: 'Ikheli',
type: 'Uhlobo',
label: 'Igama',
destination: 'ukuya kuyo',
description: 'ukuya kuyo',
are_you_sure: 'Ingaba uqinisekile?',
yes_delete: 'Ewe, yisuse',
no_cancel: 'Hayi, rhoxisa',
delete: 'Cima',
save: 'Gcina',
delete_this_wallet: 'Cima le ngxowa',
export_backup: 'Ukuthumela ngaphandle / yokugcina',
buy_bitcoin: 'Thenga ibitcoin',
show_xpub: 'Bonise ingxowa XPUB',
},
export: {
title: 'ukuthunyelwa kweebhanki ',
},
xpub: {
title: 'ingxowa XPUB',
copiedToClipboard: 'Ikopishwe kwi-clipboard',
},
import: {
title: 'ukungenisa',
explanation:
'Bhale apha imnemonic yakho, ngundoqo, WIF , okanye nantoni na onayo. BlueWallet uya kwenza konke okusemandleni ukuqiqa ifomathi efanelekileyo kwaye ingenise ingxowa yakho',
imported: 'Ngenisiwe',
error: 'Ayiphumelelanga ukungenisa. Nceda, uqiniseka ukuba idata ehlinzekiweyo iyasebenza.',
success: 'Iphumelele',
do_import: 'Ngeniswe',
scan_qr: 'okanye ukukhangela iQR code?',
},
scanQrWif: {
go_back: 'Buya Umva',
cancel: 'Rhoxisa',
decoding: 'Ukumisela',
input_password: 'Igama lokungena',
password_explain: 'Yi le BIP38 ikhifidi yangasese itsixe',
bad_password: 'Inombolo yokuvula eli ngalunganga ',
wallet_already_exists: 'Ikhredithi enjalo sele ikhona',
bad_wif: 'Ezimbi WIF',
imported_wif: 'Ngeniswa WIF ',
with_address: ' Nge dilesi ',
imported_segwit: 'Ngeniswa SegWit',
imported_legacy: 'Ngeniswa ilifa',
imported_watchonly: 'Ngeniswa bukele-kuphela',
},
},
transactions: {
list: {
tabBarLabel: 'Ngeniswa',
title: 'ngeniswa',
description: 'Uluhlu lokungena okanye ukuphuma kweekhredithi zakho',
conf: 'conf',
},
details: {
title: 'Ngeniswa',
from: 'Negalelo',
to: 'Mveliso',
copy: 'Ikopi',
transaction_details: 'Iinkcukacha zentengiselwano',
show_in_block_explorer: 'Bonisa ibhloko umhloi',
},
},
send: {
header: 'Thumela',
details: {
title: 'ukudala ukuthenga',
amount_field_is_not_valid: 'intsimi yexabiso ayivumelekanga',
fee_field_is_not_valid: 'Intsimi yentlawulo ayivumelekanga ',
address_field_is_not_valid: 'Intsimi yeedilesi ayivumelekanga',
total_exceeds_balance: 'Imali yokuthumela idlula imali ekhoyo.',
create_tx_error: 'Kukho impazamo yokudala ukuthengiselana. Nceda, qinisekisa ukuba idilesi iyasebenza.',
address: 'idilesi',
amount_placeholder: 'inani lokuthumela (nge BTC)',
fee_placeholder: 'kunye nentlawulo yokuthengiswa (nge BTC)',
note_placeholder: 'inqaku kumntu',
cancel: 'Rhoxisa',
scan: 'Ukutshekisha',
send: 'Thumela',
create: 'Yenza',
remaining_balance: 'Ibhalansi eseleyo',
},
confirm: {
header: 'Qiniseka',
sendNow: 'Thumela ngoku',
},
success: {
done: 'Kwenzekile',
},
create: {
details: 'Iinkcukacha',
title: 'ukudala ukuthenga',
error: 'Impazamo yokudala ukuthengiselana. Idilesi engavumelekanga okanye imali yokuthumela?',
go_back: 'Buya umva',
this_is_hex: 'Le yi ntengo hex, ityikityiwe ilungele ukukhutshelwa kumnatha.',
to: 'Iya ku',
amount: 'Isixa',
fee: 'Ntlawulo',
tx_size: 'TX ubungakanani',
satoshi_per_byte: 'Satoshi nge-byte',
memo: 'Memo',
broadcast: 'Sasazwa',
not_enough_fee: 'Akukho mali e neleyo. UKwandisa intlawulo ',
},
},
receive: {
header: 'Fumana',
details: {
title: 'Wabelane ngale dilesi nomhlawuli',
share: 'yabelana',
copiedToClipboard: 'Ikhutshelwe kwi-clipboard',
label: 'Inkcazo',
create: 'Yenza',
setAmount: 'Fumana ngexabiso',
},
},
buyBitcoin: {
header: 'Thenga Ibitcoin',
tap_your_address: 'Thepha idilesi yakho ukuyikopisha kwi-clipboard:',
copied: 'Ikhutshelwe kwi-clipboard!',
},
settings: {
header: 'Izicwangciso',
plausible_deniability: 'Ukuphika...',
storage_not_encrypted: 'Ukugciwa: hayi ngekhodi',
storage_encrypted: 'Ukugciwa: ngekhodi',
password: 'Inombolo yokuvula',
password_explain: 'Ukudala iinombolo yokuvula oyisebenzisayo ukucima ukugcina',
retype_password: 'Phina inombolo yokuvula',
passwords_do_not_match: 'Inombolo yokuvula azifani',
encrypt_storage: 'Kubhala u kubhala',
lightning_settings: 'Izixhobo zokukhanyisa',
lightning_settings_explain:
'Ukuxhuma kwi-node yakho ye-LND nceda ufake iLndHub' +
' kwaye ufake iURL apha izicwangciso. Shiya kungenanto yokusebenzisa iLndHub (Indhub.io)',
save: 'ndoloza',
about: 'Malunga',
language: 'Ulwimi',
currency: 'Lwemali',
},
plausibledeniability: {
title: 'Ukuphika',
help:
'Phantsi kweemeko unokunyaneliswa ukuba uchaze a ' +
'inombolo yokuvula. BlueWallet ukugcina imali yakho ikhuselekile, unokudala enye ' +
'ukugcinwa kwekhowudi, ngegama eligqithisiweyo. Phantsi kwefuthe, ' +
'unako ukutyhila le phasiwedi kumntu wesithatu. Ukuba ungenayo ' +
'BlueWallet, iya kuvula ukugcinwa kwetyala ‘entsha’. Oku kuya kubonakala ' +
'Umlenze kumntu wesithathu kodwa uza kugcina ngasese ukugcinwa kwakho ' +
'ngemali ekhuselekile..',
help2:
'Igumbi lokugcina elitsha liza kusebenza ngokupheleleyo, kwaye unako ukugcina okunye ‘ + ‘lxabiso elincinci apho likhangeleka ngakumbi.',
create_fake_storage: 'Ukudala igumbi lokugcina elifihlakeleyo',
go_back: 'Buya Umva',
create_password: 'Yenza inombolo yokuvula',
create_password_explanation:
'Inombolo yakho yokuvula igumbi lokugcina inkohliso akumele ifane ne nombolo yokuvula igumbi lakho elinyanisekileyo',
password_should_not_match:
'Inombolo yakho yokuvula igumbi lokugcina inkohliso akumele ifane ne nombolo yokuvula igumbi lakho elinyanisekileyo',
retype_password: 'Phinda inombolo yokuvula',
passwords_do_not_match: 'Inombolo yokuvula ayihambelani, zama kwakhona',
success: 'Iphumelele',
},
lnd: {
title: 'lawula imali',
choose_source_wallet: 'Ukhethe ingxowa yomthombo',
refill_lnd_balance: 'Gcwalisa ingxowa yakho yemali',
refill: 'Gcwalisa',
withdraw: 'Khupha imali',
expired: 'Iphelewe lixesha',
placeholder: 'Invoyisi',
sameWalletAsInvoiceError: ': Awukwazi ukuhlawula i-invoyisi kunye ngengxowa oyisebenzisile ukudala leyo invoyisi.',
},
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
has_been_paid: 'This invoice has been paid for',
please_pay: 'Please pay',
sats: 'sats',
for: 'For:',
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
};

2
loc/cs_CZ.js

@ -228,7 +228,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/da_DK.js

@ -227,7 +227,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/de_DE.js

@ -232,7 +232,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/el.js

@ -231,7 +231,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

4
loc/en.js

@ -130,7 +130,7 @@ module.exports = {
cancel: 'Cancel',
scan: 'Scan',
send: 'Send',
create: 'Create',
create: 'Create Invoice',
remaining_balance: 'Remaining balance',
},
confirm: {
@ -229,7 +229,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/es.js

@ -232,7 +232,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/fi_FI.js

@ -231,7 +231,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/fr_FR.js

@ -231,7 +231,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/hr_HR.js

@ -226,7 +226,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

3
loc/hu_HU.js

@ -228,8 +228,7 @@ module.exports = {
title: 'A tárcád elkészült...',
text:
'Kérlek írd le az alábbi emlékeztető szavakat egy papírlapra, és tedd el egy biztonságos helyre. ' +
'Ez egy biztonsági mentés, amellyel helyreállíthatod a tárcádat egy másik eszközön. Helyreállításhoz ' +
'használhatod az Electrum tárcát (https://electrum.org/) az asztali számítógépeden.',
'Ez egy biztonsági mentés, amellyel helyreállíthatod a tárcádat egy másik eszközön.',
ok: 'Rendben, leírtam!',
},
lndViewInvoice: {

2
loc/id_ID.js

@ -228,7 +228,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

13
loc/index.js

@ -28,6 +28,10 @@ dayjs.extend(relativeTime);
lang = 'zh-cn';
require('dayjs/locale/zh-cn');
break;
case 'zh_tw':
lang = 'zh-tw';
require('dayjs/locale/zh-tw');
break;
case 'ru':
require('dayjs/locale/ru');
break;
@ -82,6 +86,9 @@ dayjs.extend(relativeTime);
case 'tr_tr':
require('dayjs/locale/tr');
break;
case 'vi_vn':
require('dayjs/locale/vi');
break;
default:
localeForDayJSAvailable = false;
break;
@ -113,9 +120,13 @@ strings = new Localization({
hu_hu: require('./hu_HU.js'),
id_id: require('./id_ID.js'),
zh_cn: require('./zh_cn.js'),
zh_tw: require('./zh_tw.js'),
sv_se: require('./sv_SE.js'),
nb_no: require('./nb_NO.js'),
tr_tr: require('./tr_TR.js'),
vi_vn: require('./vi_VN.js'),
zar_xho: require('./ZAR_Xho.js'),
zar_afr: require('./ZAR_Afr.js'),
});
strings.saveLanguage = lang => AsyncStorage.setItem(AppStorage.LANG, lang);
@ -177,7 +188,7 @@ strings.formatBalance = (balance, toUnit, withFormatting = false) => {
* @param toUnit {String} Value from models/bitcoinUnits.js
* @returns {string}
*/
strings.formatBalanceWithoutSuffix = (balance, toUnit, withFormatting = false) => {
strings.formatBalanceWithoutSuffix = (balance = 0, toUnit, withFormatting = false) => {
if (toUnit === undefined) {
return balance;
}

2
loc/it.js

@ -231,7 +231,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/nb_NO.js

@ -229,7 +229,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

4
loc/nl_NL.js

@ -115,7 +115,7 @@ module.exports = {
},
},
send: {
header: 'Vertuur',
header: 'Verstuur',
details: {
title: 'transacties aanmaken',
amount_field_is_not_valid: 'Bedrag veld is niet geldig',
@ -232,7 +232,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

3
loc/pt_BR.js

@ -235,8 +235,7 @@ module.exports = {
title: 'Sua carteira foi criada...',
text:
'Por favor anote num pedaço de papel essa seqüência de palavras, elas serão o seu backup e você as pode usar para' +
' recuperar a sua carteira em outros dispositivos. Por exemplo, você pode usar o programa Electrum para desktops' +
' (https://electrum.org/) para acessar esta mesma carteira.',
' recuperar a sua carteira em outros dispositivos.',
ok: 'Certo, já anotei!',
},
lndViewInvoice: {

9
loc/pt_PT.js

@ -187,9 +187,10 @@ module.exports = {
encrypt_storage: 'Encriptar',
lightning_settings: 'Definições do Lightning',
lightning_settings_explain:
'Para se ligar ao seu próprio node LND, por favor instale o LndHub' +
' e coloque o seu endereço aqui nas definições. Deixe em branco para usar o valor por omissão' +
'ndHub\n (lndhub.io)',
'Para se ligar ao seu próprio node LND, por favor instale o LndHub ' +
'e coloque o seu endereço aqui nas definições. ' +
'Deixe em branco para usar o LNDHub da BlueWallet (lndhub.io). ' +
'Wallets criadas depois desta alteração ligar-se-ão ao LNDHub especificado.',
electrum_settings: 'Definições do Electrum',
electrum_settings_explain: 'Deixe em branco para usar o valor por omissão',
save: 'Guardar',
@ -232,7 +233,7 @@ module.exports = {
pleasebackup: {
title: 'A sua wallet foi criada...',
text:
"Por favor escreva esta frase mnemónica numa folha de papel. É o seu backup e pode usá-lo para restaurar a sua wallet noutro device. Pode usar o Electrum wallet em desktop (https://electrum.org/) para restaurar a sua wallet.",
'Por favor escreva esta frase mnemónica numa folha de papel. É o seu backup e pode usá-lo para restaurar a sua wallet noutro device.',
ok: 'OK, eu escrevi-a num papel!',
},
lndViewInvoice: {

2
loc/ru.js

@ -233,7 +233,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/sv_SE.js

@ -229,7 +229,7 @@ module.exports = {
pleasebackup: {
title: 'Din plånbok har skapats...',
text:
'Innan du går vidare, var snäll och skriv ned dessa ord på ett papper och förvara på ett säkert ställe. De är din backup och säkerställer att du kan återställa din plånbok igen om något händer. Den går att återställa även i andra plånböcker såsom t.ex. Electrum (https://electrum.org/).',
'Innan du går vidare, var snäll och skriv ned dessa ord på ett papper och förvara på ett säkert ställe. De är din backup och säkerställer att du kan återställa din plånbok igen om något händer.',
ok: 'OK, jag har skrivit ned orden!',
},
lndViewInvoice: {

7
loc/th_TH.js

@ -23,9 +23,8 @@ module.exports = {
latest_transaction: 'ธุรกรรมล่าสุด',
empty_txs1: 'ธุรกรรมจะปรากฏที่นี่,',
empty_txs2: 'ไม่มี ณ ขณะนี้',
empty_txs1_lightning:
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
empty_txs1_lightning: 'ควรใช้ไลท์นิงเน็ตเวิร์คสำหรับธุรกรรมประจำวันเท่านั้น ธุรกรรมทันใจและมีค่าธรรมเนียมน้อยมาก',
empty_txs2_lightning: '\nแตะที่ "จัดการเงิน" เพื่อเริ่มใช้งาน และเติมเงิน',
tap_here_to_buy: 'กดที่นี่เพื่อซื้อบิตคอยน์',
},
reorder: {
@ -229,7 +228,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/tr_TR.js

@ -229,7 +229,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

2
loc/ua.js

@ -233,7 +233,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

244
loc/vi_VN.js

@ -0,0 +1,244 @@
module.exports = {
_: {
storage_is_encrypted: 'Lưu trữ của bạn được mã hoá. Mật khẩu được yêu cầu để giải mã',
enter_password: 'Enter password',
bad_password: 'Bad password, try again',
never: 'never',
continue: 'Continue',
ok: 'OK',
},
wallets: {
select_wallet: 'Select Wallet',
options: 'options',
createBitcoinWallet:
'You currently do not have a Bitcoin wallet. In order to fund a Lightning wallet, a Bitcoin wallet needs to be created or imported. Would you like to continue anyway?',
list: {
app_name: 'BlueWallet',
title: 'wallets',
header: 'A wallet represents a pair of a secret (private key) and an address' + 'you can share to receive coins.',
add: 'Add Wallet',
create_a_wallet: 'Create a wallet',
create_a_wallet1: "It's free and you can create",
create_a_wallet2: 'as many as you like',
latest_transaction: 'latest transaction',
empty_txs1: 'Your transactions will appear here,',
empty_txs2: 'none at the moment',
empty_txs1_lightning:
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
tap_here_to_buy: 'Tap here to buy Bitcoin',
},
reorder: {
title: 'Reorder Wallets',
},
add: {
title: 'add wallet',
description:
'You can either scan backup paper wallet (in WIF - Wallet Import Format), or create a new wallet. Segwit wallets supported by default.',
scan: 'Scan',
create: 'Create',
label_new_segwit: 'New SegWit',
label_new_lightning: 'New Lightning',
wallet_name: 'name',
wallet_type: 'type',
or: 'or',
import_wallet: 'Import wallet',
imported: 'Imported',
coming_soon: 'Coming soon',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'Wallet',
address: 'Address',
type: 'Type',
label: 'Label',
destination: 'destination',
description: 'description',
are_you_sure: 'Are you sure?',
yes_delete: 'Yes, delete',
no_cancel: 'No, cancel',
delete: 'Delete',
save: 'Save',
delete_this_wallet: 'Delete this wallet',
export_backup: 'Export / backup',
buy_bitcoin: 'Buy Bitcoin',
show_xpub: 'Show wallet XPUB',
},
export: {
title: 'wallet export',
},
xpub: {
title: 'wallet XPUB',
copiedToClipboard: 'Copied to clipboard.',
},
import: {
title: 'import',
explanation:
"Write here your mnemonic, private key, WIF, or anything you've got. BlueWallet will do its best to guess the correct format and import your wallet",
imported: 'Imported',
error: 'Failed to import. Please, make sure that the provided data is valid.',
success: 'Success',
do_import: 'Import',
scan_qr: 'or scan QR code instead?',
},
scanQrWif: {
go_back: 'Go Back',
cancel: 'Cancel',
decoding: 'Decoding',
input_password: 'Input password',
password_explain: 'This is BIP38 encrypted private key',
bad_password: 'Bad password',
wallet_already_exists: 'Such wallet already exists',
bad_wif: 'Bad WIF',
imported_wif: 'Imported WIF ',
with_address: ' with address ',
imported_segwit: 'Imported SegWit',
imported_legacy: 'Imported Legacy',
imported_watchonly: 'Imported Watch-only',
},
},
transactions: {
list: {
tabBarLabel: 'Transactions',
title: 'transactions',
description: 'A list of ingoing or outgoing transactions of your wallets',
conf: 'conf',
},
details: {
title: 'Transaction',
from: 'Input',
to: 'Output',
copy: 'Copy',
transaction_details: 'Transaction details',
show_in_block_explorer: 'View in block explorer',
},
},
send: {
header: 'Send',
details: {
title: 'create transaction',
amount_field_is_not_valid: 'Amount field is not valid',
fee_field_is_not_valid: 'Fee field is not valid',
address_field_is_not_valid: 'Address field is not valid',
total_exceeds_balance: 'The sending amount exceeds the available balance.',
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
address: 'address',
amount_placeholder: 'amount to send (in BTC)',
fee_placeholder: 'plus transaction fee (in BTC)',
note_placeholder: 'note to self',
cancel: 'Cancel',
scan: 'Scan',
send: 'Send',
create: 'Create',
remaining_balance: 'Remaining balance',
},
confirm: {
header: 'Confirm',
sendNow: 'Send now',
},
success: {
done: 'Done',
},
create: {
details: 'Details',
title: 'create transaction',
error: 'Error creating transaction. Invalid address or send amount?',
go_back: 'Go Back',
this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.',
to: 'To',
amount: 'Amount',
fee: 'Fee',
tx_size: 'TX size',
satoshi_per_byte: 'Satoshi per byte',
memo: 'Memo',
broadcast: 'Broadcast',
not_enough_fee: 'Not enough fee. Increase the fee',
},
},
receive: {
header: 'Receive',
details: {
title: 'Share this address with payer',
share: 'share',
copiedToClipboard: 'Copied to clipboard.',
label: 'Description',
create: 'Create',
setAmount: 'Receive with amount',
},
scan_lnurl: 'Scan to receive',
},
buyBitcoin: {
header: 'Buy Bitcoin',
tap_your_address: 'Tap your address to copy it to clipboard:',
copied: 'Copied to Clipboard!',
},
settings: {
header: 'settings',
plausible_deniability: 'Plausible deniability...',
storage_not_encrypted: 'Storage: not encrypted',
storage_encrypted: 'Storage: encrypted',
password: 'Password',
password_explain: 'Create the password you will use to decrypt the storage',
retype_password: 'Re-type password',
passwords_do_not_match: 'Passwords do not match',
encrypt_storage: 'Encrypt storage',
lightning_settings: 'Lightning Settings',
lightning_settings_explain:
'To connect to your own LND node please install LndHub' +
" and put its URL here in settings. Leave blank to use BlueWallet's LNDHub (lndhub.io). Wallets created after saving changes will connect to the specified LNDHub.",
electrum_settings: 'Electrum Settings',
electrum_settings_explain: 'Set to blank to use default',
save: 'Save',
about: 'About',
language: 'Language',
currency: 'Currency',
advanced_options: 'Advanced Options',
enable_advanced_mode: 'Enable advanced mode',
},
plausibledeniability: {
title: 'Plausible Deniability',
help:
'Under certain circumstances, you might be forced to disclose a ' +
'password. To keep your coins safe, BlueWallet can create another ' +
'encrypted storage, with a different password. Under pressure, ' +
'you can disclose this password to a 3rd party. If entered in ' +
"BlueWallet, it will unlock new 'fake' storage. This will seem " +
'legit to a 3rd party, but will secretly keep your main storage ' +
'with coins safe.',
help2: 'New storage will be fully functional, and you can store some ' + 'minimum amounts there so it looks more believable.',
create_fake_storage: 'Create fake encrypted storage',
go_back: 'Go Back',
create_password: 'Create a password',
create_password_explanation: 'Password for fake storage should not match password for your main storage',
password_should_not_match: 'Password for fake storage should not match password for your main storage',
retype_password: 'Retype password',
passwords_do_not_match: 'Passwords do not match, try again',
success: 'Success',
},
lnd: {
title: 'manage funds',
choose_source_wallet: 'Choose a source wallet',
refill_lnd_balance: 'Refill Lightning wallet balance',
refill: 'Refill',
withdraw: 'Withdraw',
expired: 'Expired',
placeholder: 'Invoice',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
},
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
has_been_paid: 'This invoice has been paid for',
please_pay: 'Please pay',
sats: 'sats',
for: 'For:',
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
};

2
loc/zh_cn.js

@ -223,7 +223,7 @@ module.exports = {
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device. You can use Electrum wallet on desktop (https://electrum.org/) to restore the same wallet.",
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {

235
loc/zh_tw.js

@ -0,0 +1,235 @@
module.exports = {
_: {
storage_is_encrypted: '你的資訊已經被加密, 請輸入密碼解密',
enter_password: '輸入密碼',
bad_password: '密碼無效,請重試',
never: '不',
continue: '繼續',
ok: '好的',
},
wallets: {
select_wallet: '選擇錢包',
options: '選項',
createBitcoinWallet: '您當前沒有bitcoin錢包. 為了支援閃電錢包, 我們需要建立或者匯入一個比特幣錢包. 是否需要繼續?',
list: {
app_name: 'BlueWallet',
title: '錢包',
header: '一個錢包代表一對的私鑰和地址' + '你可以通過分享收款.',
add: '新增錢包',
create_a_wallet: '建立一個錢包',
create_a_wallet1: '建立錢包是免費的,你可以',
create_a_wallet2: '想建立多少就建立多少個',
latest_transaction: '最近的轉賬',
empty_txs1: '你的轉賬資訊將展示在這裡',
empty_txs2: '當前無資訊',
empty_txs1_lightning:
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.',
empty_txs2_lightning: '\nTo start using it tap on "manage funds" and topup your balance.',
tap_here_to_buy: '點選購買比特幣',
},
reorder: {
title: '重新排列錢包',
},
add: {
title: '新增錢包',
description: '你可以掃描你的紙質備份錢包 (WIF格式), 或者建立一個新錢包. 預設支援隔離見證錢包',
scan: '掃描',
create: '建立',
label_new_segwit: '新隔離見證(Segwit)',
label_new_lightning: '新閃電',
wallet_name: '錢包名稱',
wallet_type: '類型',
or: '或',
import_wallet: '匯入錢包',
imported: '已經匯入',
coming_soon: '即將來臨',
lightning: '閃電',
bitcoin: '比特幣',
},
details: {
title: '錢包',
address: '地址',
type: '類型',
label: '標籤',
destination: '目的',
description: '描述',
are_you_sure: '你確認麼?',
yes_delete: '是的,刪除',
no_cancel: '不,取消',
delete: '刪除',
save: '儲存',
delete_this_wallet: '刪除這個錢包',
export_backup: '匯出備份',
buy_bitcoin: '購買比特幣',
show_xpub: '展示錢包 XPUB',
},
export: {
title: '錢包匯出',
},
xpub: {
title: '錢包 XPUB',
copiedToClipboard: '複製到貼上板.',
},
import: {
title: '匯入',
explanation: '輸入你的助記詞,私鑰或者WIF, 或者其他格式的資料. BlueWallet將盡可能的自動識別資料格式並匯入錢包',
imported: '已經匯入',
error: '匯入失敗,請確認你提供的資訊是有效的',
success: '成功',
do_import: '匯入',
scan_qr: '或掃面二維碼',
},
scanQrWif: {
go_back: '回退',
cancel: '取消',
decoding: '解碼中',
input_password: '輸入密碼',
password_explain: '這是一個BIP38加密的私鑰',
bad_password: '密碼錯誤',
wallet_already_exists: '當前錢包已經存在',
bad_wif: 'WIF格式無效',
imported_wif: 'WIF已經匯入',
with_address: ' 地址為',
imported_segwit: 'SegWit已經匯入',
imported_legacy: 'Legacy已經匯入',
imported_watchonly: '匯入只讀',
},
},
transactions: {
list: {
tabBarLabel: '轉賬',
title: '轉賬',
description: '當前所有錢包的轉入和轉出記錄',
conf: '配置',
},
details: {
title: '轉賬',
from: '輸入',
to: '輸出',
copy: '複製',
transaction_details: '轉賬詳情',
show_in_block_explorer: '區塊瀏覽器展示',
},
},
send: {
header: '傳送',
details: {
title: '建立交易',
amount_field_is_not_valid: '金額格式無效',
fee_field_is_not_valid: '費用格式無效',
address_field_is_not_valid: '地址內容無效',
total_exceeds_balance: '餘額不足',
create_tx_error: '建立交易失敗. 請確認地址格式正確.',
address: '地址',
amount_placeholder: '傳送金額(in BTC)',
fee_placeholder: '手續費用 (in BTC)',
note_placeholder: '訊息',
cancel: '取消',
scan: '掃描',
send: '傳送',
create: '建立',
remaining_balance: '剩餘金額',
},
confirm: {
header: '確認',
sendNow: '現在傳送',
},
success: {
done: '完成',
},
create: {
details: '詳情',
title: '建立詳情',
error: '建立交易失敗. 無效地址或金額?',
go_back: '回退',
this_is_hex: '這個是交易的十六進位制資料, 簽名並廣播到全網路.',
to: '到',
amount: '金額',
fee: '手續費',
tx_size: '交易大小',
satoshi_per_byte: '蔥每byte',
memo: '訊息',
broadcast: '廣播',
not_enough_fee: '手續費不夠,請增加手續費',
},
},
receive: {
header: '收款',
details: {
title: '分享這個地址給付款人',
share: '分享',
copiedToClipboard: '複製到貼上板.',
label: '描述',
create: '建立',
setAmount: '收款金額',
},
},
buyBitcoin: {
header: '購買比特幣',
tap_your_address: '點選地址複製到貼上板:',
copied: '複製到貼上板!',
},
settings: {
header: '設定',
plausible_deniability: '可否認性...',
storage_not_encrypted: '儲存:未加密',
storage_encrypted: '儲存:加密中',
password: '密碼',
password_explain: '建立你的加密密碼',
retype_password: '再次輸入密碼',
passwords_do_not_match: '兩次輸入密碼不同',
encrypt_storage: '加密儲存',
lightning_settings: '閃電網路設定',
lightning_settings_explain: '如要要連線你自己的閃電節點請安裝LndHub' + ' 並把url地址輸入到下面. 空白將使用預設的LndHub (lndhub.io)',
save: '儲存',
about: '關於',
language: '語言',
currency: '貨幣',
advanced_options: 'Advanced Options',
enable_advanced_mode: 'Enable advanced mode',
},
plausibledeniability: {
title: '可否認性',
help:
'在某些情況下, 你不得不暴露 ' +
'密碼. 為了讓你的比特幣更加安全, BlueWallet可以建立一些 ' +
'加密空間, 用不同的密碼. 在壓力之下, ' +
'你可以暴露這個錢包密碼. 再次進入 ' +
'BlueWallet, 我們會解鎖一些虛擬空間. 對第三方來說看上去' +
'是合理的, 但會偷偷的幫你保證主錢包的安全 ' +
'幣也就安全了.',
help2: '新的空間具備完整的功能,你可以存在 ' + '少量的金額在裡面.',
create_fake_storage: '建立虛擬加密儲存',
go_back: '回退',
create_password: '建立密碼',
create_password_explanation: '虛擬儲存空間密碼不能和主儲存空間密碼相同',
password_should_not_match: '虛擬儲存空間密碼不能和主儲存空間密碼相同',
retype_password: '重輸密碼',
passwords_do_not_match: '兩次輸入密碼不同,請重新輸入',
success: '成功',
},
lnd: {
title: '配置資金支援',
choose_source_wallet: '選擇一個資金源錢包',
refill_lnd_balance: '給閃電錢包充值',
refill: '充值',
withdraw: '提取',
expired: '超時',
sameWalletAsInvoiceError: '你不能用建立賬單的錢包去支付該賬單',
},
pleasebackup: {
title: 'Your wallet is created...',
text:
"Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
ok: 'OK, I wrote this down!',
},
lndViewInvoice: {
wasnt_paid_and_expired: 'This invoice was not paid for and has expired',
has_been_paid: 'This invoice has been paid for',
please_pay: 'Please pay',
sats: 'sats',
for: 'For:',
additional_info: 'Additional Information',
open_direct_channel: 'Open direct channel with this node:',
},
};

168
models/signer.js

@ -6,13 +6,16 @@
* https://github.com/Overtorment/Cashier-BTC
*
**/
let bitcoinjs = require('bitcoinjs-lib');
const bitcoinjs = require('bitcoinjs-lib');
const _p2wpkh = bitcoinjs.payments.p2wpkh;
const _p2sh = bitcoinjs.payments.p2sh;
const toSatoshi = num => parseInt((num * 100000000).toFixed(0));
exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) {
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
let txb = new bitcoinjs.TransactionBuilder();
txb.setVersion(1);
let unspentAmountSatoshi = 0;
let ourOutputs = {};
let outputNum = 0;
@ -50,7 +53,11 @@ exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, chang
// now, signing every input with a corresponding key
for (let c = 0; c <= outputNum; c++) {
txb.sign(c, ourOutputs[c].keyPair);
txb.sign({
prevOutScriptType: 'p2pkh',
vin: c,
keyPair: ourOutputs[c].keyPair,
});
}
let tx = txb.build();
@ -60,23 +67,35 @@ exports.createHDTransaction = function(utxos, toAddress, amount, fixedFee, chang
exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee, changeAddress) {
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
let amountToOutputSatoshi = parseInt(((amount - fixedFee) * 100000000).toFixed(0)); // how much payee should get
let txb = new bitcoinjs.TransactionBuilder();
let psbt = new bitcoinjs.Psbt();
psbt.setVersion(1);
let unspentAmountSatoshi = 0;
let ourOutputs = {};
let ourOutputs = [];
let outputNum = 0;
for (const unspent of utxos) {
if (unspent.confirmations < 1) {
// using only confirmed outputs
continue;
}
txb.addInput(unspent.txid, unspent.vout);
ourOutputs[outputNum] = ourOutputs[outputNum] || {};
let keyPair = bitcoinjs.ECPair.fromWIF(unspent.wif);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKeyHash = bitcoinjs.crypto.hash160(pubKey);
let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash);
let p2wpkh = _p2wpkh({
pubkey: keyPair.publicKey,
});
let p2sh = _p2sh({
redeem: p2wpkh,
});
psbt.addInput({
hash: unspent.txid,
index: unspent.vout,
witnessUtxo: {
script: p2sh.output,
value: unspent.amount,
},
redeemScript: p2wpkh.output,
});
ourOutputs[outputNum] = ourOutputs[outputNum] || {};
ourOutputs[outputNum].keyPair = keyPair;
ourOutputs[outputNum].redeemScript = redeemScript;
ourOutputs[outputNum].redeemScript = p2wpkh.output;
ourOutputs[outputNum].amount = unspent.amount;
unspentAmountSatoshi += unspent.amount;
if (unspentAmountSatoshi >= amountToOutputSatoshi + feeInSatoshis) {
@ -92,23 +111,29 @@ exports.createHDSegwitTransaction = function(utxos, toAddress, amount, fixedFee,
// adding outputs
txb.addOutput(toAddress, amountToOutputSatoshi);
psbt.addOutput({
address: toAddress,
value: amountToOutputSatoshi,
});
if (amountToOutputSatoshi + feeInSatoshis < unspentAmountSatoshi) {
// sending less than we have, so the rest should go back
if (unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis > 3 * feeInSatoshis) {
// to prevent @dust error change transferred amount should be at least 3xfee.
// if not - we just dont send change and it wil add to fee
txb.addOutput(changeAddress, unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis);
psbt.addOutput({
address: changeAddress,
value: unspentAmountSatoshi - amountToOutputSatoshi - feeInSatoshis,
});
}
}
// now, signing every input with a corresponding key
for (let c = 0; c <= outputNum; c++) {
txb.sign(c, ourOutputs[c].keyPair, ourOutputs[c].redeemScript, null, ourOutputs[c].amount);
psbt.signInput(c, ourOutputs[c].keyPair);
}
let tx = txb.build();
let tx = psbt.finalizeAllInputs().extractTransaction();
return tx.toHex();
};
@ -120,37 +145,57 @@ exports.createSegwitTransaction = function(utxos, toAddress, amount, fixedFee, W
let feeInSatoshis = parseInt((fixedFee * 100000000).toFixed(0));
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKeyHash = bitcoinjs.crypto.hash160(pubKey);
let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash);
let txb = new bitcoinjs.TransactionBuilder();
let p2wpkh = _p2wpkh({
pubkey: keyPair.publicKey,
});
let p2sh = _p2sh({
redeem: p2wpkh,
});
let psbt = new bitcoinjs.Psbt();
psbt.setVersion(1);
let unspentAmount = 0;
for (const unspent of utxos) {
if (unspent.confirmations < 2) {
// using only confirmed outputs
continue;
}
txb.addInput(unspent.txid, unspent.vout, sequence);
unspentAmount += parseInt((unspent.amount * 100000000).toFixed(0));
const satoshis = parseInt((unspent.amount * 100000000).toFixed(0));
psbt.addInput({
hash: unspent.txid,
index: unspent.vout,
sequence,
witnessUtxo: {
script: p2sh.output,
value: satoshis,
},
redeemScript: p2wpkh.output,
});
unspentAmount += satoshis;
}
let amountToOutput = parseInt(((amount - fixedFee) * 100000000).toFixed(0));
txb.addOutput(toAddress, amountToOutput);
psbt.addOutput({
address: toAddress,
value: amountToOutput,
});
if (amountToOutput + feeInSatoshis < unspentAmount) {
// sending less than we have, so the rest should go back
if (unspentAmount - amountToOutput - feeInSatoshis > 3 * feeInSatoshis) {
// to prevent @dust error change transferred amount should be at least 3xfee.
// if not - we just dont send change and it wil add to fee
txb.addOutput(changeAddress, unspentAmount - amountToOutput - feeInSatoshis);
psbt.addOutput({
address: changeAddress,
value: unspentAmount - amountToOutput - feeInSatoshis,
});
}
}
for (let c = 0; c < utxos.length; c++) {
txb.sign(c, keyPair, redeemScript, null, parseInt((utxos[c].amount * 100000000).toFixed(0)));
psbt.signInput(c, keyPair);
}
let tx = txb.build();
let tx = psbt.finalizeAllInputs().extractTransaction();
return tx.toHex();
};
@ -168,11 +213,33 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta
highestSequence = i.sequence;
}
}
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let p2wpkh = _p2wpkh({
pubkey: keyPair.publicKey,
});
let p2sh = _p2sh({
redeem: p2wpkh,
});
// creating TX
let txb = new bitcoinjs.TransactionBuilder();
let psbt = new bitcoinjs.Psbt();
psbt.setVersion(1);
for (let unspent of tx.ins) {
txb.addInput(unspent.hash.reverse().toString('hex'), unspent.index, highestSequence + 1);
let txid = Buffer.from(unspent.hash)
.reverse()
.toString('hex');
let index = unspent.index;
let amount = utxodata[txid][index];
psbt.addInput({
hash: txid,
index,
sequence: highestSequence + 1,
witnessUtxo: {
script: p2sh.output,
value: amount,
},
redeemScript: p2wpkh.output,
});
}
for (let o of tx.outs) {
@ -180,37 +247,36 @@ exports.createRBFSegwitTransaction = function(txhex, addressReplaceMap, feeDelta
if (addressReplaceMap[outAddress]) {
// means this is DESTINATION address, not messing with it's amount
// but replacing the address itseld
txb.addOutput(addressReplaceMap[outAddress], o.value);
psbt.addOutput({
address: addressReplaceMap[outAddress],
value: o.value,
});
} else {
// CHANGE address, so we deduct increased fee from here
let feeDeltaInSatoshi = parseInt((feeDelta * 100000000).toFixed(0));
txb.addOutput(outAddress, o.value - feeDeltaInSatoshi);
psbt.addOutput({
address: outAddress,
value: o.value - feeDeltaInSatoshi,
});
}
}
// signing
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let pubKey = keyPair.getPublicKeyBuffer();
let pubKeyHash = bitcoinjs.crypto.hash160(pubKey);
let redeemScript = bitcoinjs.script.witnessPubKeyHash.output.encode(pubKeyHash);
for (let c = 0; c < tx.ins.length; c++) {
let txid = tx.ins[c].hash.reverse().toString('hex');
let index = tx.ins[c].index;
let amount = utxodata[txid][index];
txb.sign(c, keyPair, redeemScript, null, amount);
psbt.signInput(c, keyPair);
}
let newTx = txb.build();
let newTx = psbt.finalizeAllInputs().extractTransaction();
return newTx.toHex();
};
exports.generateNewSegwitAddress = function() {
let keyPair = bitcoinjs.ECPair.makeRandom();
let pubKey = keyPair.getPublicKeyBuffer();
let witnessScript = bitcoinjs.script.witnessPubKeyHash.output.encode(bitcoinjs.crypto.hash160(pubKey));
let scriptPubKey = bitcoinjs.script.scriptHash.output.encode(bitcoinjs.crypto.hash160(witnessScript));
let address = bitcoinjs.address.fromOutputScript(scriptPubKey);
let address = bitcoinjs.payments.p2sh({
redeem: bitcoinjs.payments.p2wpkh({
pubkey: keyPair.publicKey,
}),
}).address;
return {
address: address,
@ -235,10 +301,11 @@ exports.URI = function(paymentInfo) {
exports.WIF2segwitAddress = function(WIF) {
let keyPair = bitcoinjs.ECPair.fromWIF(WIF);
let pubKey = keyPair.getPublicKeyBuffer();
let witnessScript = bitcoinjs.script.witnessPubKeyHash.output.encode(bitcoinjs.crypto.hash160(pubKey));
let scriptPubKey = bitcoinjs.script.scriptHash.output.encode(bitcoinjs.crypto.hash160(witnessScript));
return bitcoinjs.address.fromOutputScript(scriptPubKey);
return bitcoinjs.payments.p2sh({
redeem: bitcoinjs.payments.p2wpkh({
pubkey: keyPair.publicKey,
}),
}).address;
};
exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF, fromAddress) {
@ -246,6 +313,7 @@ exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF,
let amountToOutput = toSatoshi(_amount - _fixedFee);
let pk = bitcoinjs.ECPair.fromWIF(WIF); // eslint-disable-line new-cap
let txb = new bitcoinjs.TransactionBuilder();
txb.setVersion(1);
let unspentAmount = 0;
for (const unspent of utxos) {
if (unspent.confirmations < 2) {
@ -263,7 +331,11 @@ exports.createTransaction = function(utxos, toAddress, _amount, _fixedFee, WIF,
}
for (let c = 0; c < utxos.length; c++) {
txb.sign(c, pk);
txb.sign({
prevOutScriptType: 'p2pkh',
vin: c,
keyPair: pk,
});
}
return txb.build().toHex();

6196
package-lock.json

File diff suppressed because it is too large

48
package.json

@ -1,6 +1,6 @@
{
"name": "BlueWallet",
"version": "4.5.0",
"version": "4.8.0",
"devDependencies": {
"@babel/core": "^7.5.0",
"@babel/runtime": "^7.5.1",
@ -24,7 +24,7 @@
"rn-nodeify": "github:tradle/rn-nodeify"
},
"scripts": {
"prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch; git apply patches/transaction_builder.js.patch; git apply ./patches/transaction.js.patch",
"prepare": "./patches/fix_mangle.sh; git apply patches/minifier.js.patch; git apply patches/minify.js.patch",
"clean": "cd android/; ./gradlew clean; cd ..; rm -r -f /tmp/metro-cache/; rm -r -f node_modules/; npm cache clean --force; npm i; npm start -- --reset-cache",
"releasenotes2json": "./release-notes.sh > release-notes.txt; node -e 'console.log(JSON.stringify(require(\"fs\").readFileSync(\"release-notes.txt\", \"utf8\")));' > release-notes.json",
"podinstall": "./podinstall.sh",
@ -41,30 +41,34 @@
"preset": "react-native",
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
}
},
"setupFiles": [
"./tests/setup.js"
]
},
"dependencies": {
"@babel/preset-env": "7.5.0",
"@react-native-community/async-storage": "1.5.1",
"@react-native-community/async-storage": "1.6.2",
"@react-native-community/blur": "3.3.1",
"@react-native-community/slider": "2.0.0-rc.1",
"@remobile/react-native-qrcode-local-image": "1.0.4",
"amplitude-js": "4.7.0-react-native",
"appcenter": "2.3.0",
"appcenter-analytics": "2.3.0",
"appcenter-crashes": "2.3.0",
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git",
"@sentry/react-native": "1.0.9",
"amplitude-js": "5.6.0",
"appcenter": "2.6.0",
"appcenter-analytics": "2.6.0",
"appcenter-crashes": "2.6.0",
"bech32": "1.1.3",
"bignumber.js": "9.0.0",
"bip21": "2.0.2",
"bip32": "2.0.3",
"bip39": "2.5.0",
"bitcoinjs-lib": "3.3.2",
"bitcoinjs5": "git+https://github.com/Overtorment/bitcoinjs5.git#846c018",
"bitcoinjs-lib": "5.1.6",
"buffer": "5.2.1",
"buffer-reverse": "1.0.1",
"coinselect": "3.1.11",
"crypto-js": "3.1.9-1",
"dayjs": "1.8.14",
"ecurve": "1.0.6",
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git",
"eslint-config-prettier": "6.0.0",
"eslint-config-standard": "12.0.0",
@ -84,39 +88,41 @@
"react": "16.8.6",
"react-localization": "1.0.13",
"react-native": "0.60.5",
"react-native-camera": "2.11.1",
"react-native-device-info": "2.2.2",
"react-native-biometrics": "git+https://github.com/BlueWallet/react-native-biometrics.git",
"react-native-camera": "3.4.0",
"react-native-default-preference": "1.4.1",
"react-native-device-info": "4.0.1",
"react-native-elements": "0.19.0",
"react-native-flexi-radio-button": "0.2.2",
"react-native-fs": "2.13.3",
"react-native-gesture-handler": "1.3.0",
"react-native-handoff": "git+https://github.com/marcosrdz/react-native-handoff.git",
"react-native-haptic-feedback": "1.7.1",
"react-native-image-picker": "0.28.1",
"react-native-image-picker": "1.1.0",
"react-native-level-fs": "3.0.1",
"react-native-linear-gradient": "2.5.4",
"react-native-modal": "11.1.0",
"react-native-obscure": "1.2.1",
"react-native-popup-menu-android": "1.0.3",
"react-native-privacy-snapshot": "1.0.0",
"react-native-privacy-snapshot": "git+https://github.com/BlueWallet/react-native-privacy-snapshot.git",
"react-native-prompt-android": "git+https://github.com/marcosrdz/react-native-prompt-android.git",
"react-native-qrcode-svg": "5.1.2",
"react-native-quick-actions": "0.3.12",
"react-native-randombytes": "3.5.3",
"react-native-rate": "1.1.7",
"react-native-secure-key-store": "git+https://github.com/marcosrdz/react-native-secure-key-store.git",
"react-native-sentry": "0.43.2",
"react-native-share": "2.0.0",
"react-native-snap-carousel": "3.8.0",
"react-native-snap-carousel": "3.8.4",
"react-native-sortable-list": "0.0.23",
"react-native-svg": "9.5.1",
"react-native-system-setting": "git+https://github.com/marcosrdz/react-native-system-setting.git",
"react-native-tcp": "3.3.1",
"react-native-tcp": "git+https://github.com/aprock/react-native-tcp.git",
"react-native-tooltip": "git+https://github.com/marcosrdz/react-native-tooltip.git",
"react-native-vector-icons": "6.6.0",
"react-native-watch-connectivity": "0.2.1",
"react-native-watch-connectivity": "0.4.1",
"react-native-webview": "6.9.0",
"react-navigation": "3.11.0",
"react-test-render": "1.1.1",
"react-navigation-hooks": "1.1.0",
"react-test-render": "1.1.2",
"readable-stream": "3.4.0",
"secure-random": "1.1.2",
"stream-browserify": "2.0.2",

12
patches/transaction.js.patch

@ -1,12 +0,0 @@
--- a/node_modules/bitcoinjs-lib/src/transaction.js 2018-07-18 00:17:03.540824839 +0100
+++ b/node_modules/bitcoinjs-lib/src/transaction.js 2018-07-18 00:24:55.840803782 +0100
@@ -408,7 +408,8 @@
Transaction.prototype.getId = function () {
// transaction hash's are displayed in reverse order
- return this.getHash().reverse().toString('hex')
+ var bufferReverse = require('buffer-reverse')
+ return bufferReverse(this.getHash()).toString('hex')
}
Transaction.prototype.toBuffer = function (buffer, initialOffset) {

12
patches/transaction_builder.js.patch

@ -1,12 +0,0 @@
--- a/node_modules/bitcoinjs-lib/src/transaction_builder.js 2018-07-18 00:09:22.924845375 +0100
+++ b/node_modules/bitcoinjs-lib/src/transaction_builder.js 2018-07-18 00:14:20.996832086 +0100
@@ -536,7 +536,8 @@
// is it a hex string?
if (typeof txHash === 'string') {
// transaction hashs's are displayed in reverse order, un-reverse it
- txHash = Buffer.from(txHash, 'hex').reverse()
+ var bufferReverse = require('buffer-reverse')
+ txHash = bufferReverse(new Buffer(txHash, 'hex'))
// is it a Transaction object?
} else if (txHash instanceof Transaction) {

2
podinstall.sh

@ -5,5 +5,5 @@ echo
if [[ "$APPCENTER_XCODE_PROJECT" == "ios/BlueWallet.xcworkspace" && "$OSTYPE" == "darwin"* ]]; then
echo "Running pod update..."
cd ios
pod update
pod install
fi

43
screen/lnd/browser.js

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { TouchableOpacity, ActivityIndicator, TextInput, Keyboard, BackHandler, View, Alert } from 'react-native';
import { TouchableOpacity, ActivityIndicator, TextInput, Keyboard, BackHandler, View, Alert, Platform } from 'react-native';
import { WebView } from 'react-native-webview';
import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import Ionicons from 'react-native-vector-icons/Ionicons';
@ -393,27 +393,36 @@ export default class Browser extends Component {
value={this.state.stateURL}
numberOfLines={1}
style={{ flex: 1, marginLeft: 4, minHeight: 33 }}
editable={false}
onSubmitEditing={Keyboard.dismiss}
editable
onSubmitEditing={() => {
Keyboard.dismiss();
let url = this.state.stateURL;
if (!url.toLowerCase().startsWith('http://') && !url.toLowerCase().startsWith('https://')) {
url = 'https://' + url;
}
this.setState({ url });
}}
/>
</View>
</View>
<View style={{ alignContent: 'flex-end', height: 44, flexDirection: 'row', marginHorizontal: 8 }}>
<TouchableOpacity
onPress={() => {
processedInvoices = {};
this.setState({ url: 'https://bluewallet.io/marketplace/' });
}}
>
<Ionicons
name={'ios-home'}
size={36}
style={{
color: this.state.weblnEnabled ? 'green' : 'red',
backgroundColor: 'transparent',
{Platform.OS !== 'ios' && ( // on iOS lappbrowser opens blank page, thus, no HOME button
<TouchableOpacity
onPress={() => {
processedInvoices = {};
this.setState({ url: 'https://bluewallet.io/marketplace/' });
}}
/>
</TouchableOpacity>
>
<Ionicons
name={'ios-home'}
size={36}
style={{
color: this.state.weblnEnabled ? 'green' : 'red',
backgroundColor: 'transparent',
}}
/>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={() => {

4
screen/lnd/lndCreateInvoice.js

@ -54,7 +54,7 @@ export default class LNDCreateInvoice extends Component {
};
}
componentDidMount () {
componentDidMount() {
if (this.props.navigation.state.params.uri) {
this.processLnurl(this.props.navigation.getParam('uri'));
}
@ -159,7 +159,7 @@ export default class LNDCreateInvoice extends Component {
{this.state.isLoading ? (
<ActivityIndicator />
) : (
<BlueButton disabled={!this.state.amount > 0} onPress={() => this.createInvoice()} title={loc.send.details.create} />
<BlueButton disabled={!(this.state.amount > 0)} onPress={() => this.createInvoice()} title={loc.send.details.create} />
)}
</View>
);

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

Loading…
Cancel
Save