mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-28 11:24:07 +01:00
commit
2533a848ca
50 changed files with 3963 additions and 2719 deletions
|
@ -67,4 +67,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
|||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[version]
|
||||
^0.85.0
|
||||
^0.86.0
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -54,3 +54,7 @@ buck-out/
|
|||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
#BlueWallet
|
||||
release-notes.json
|
||||
release-notes.txt
|
|
@ -7,6 +7,7 @@ let EV = require('./events');
|
|||
let currency = require('./currency');
|
||||
let loc = require('./loc');
|
||||
let A = require('./analytics');
|
||||
let BlueElectrum = require('./BlueElectrum'); // eslint-disable-line
|
||||
|
||||
/** @type {AppStorage} */
|
||||
let BlueApp = new AppStorage();
|
||||
|
@ -31,7 +32,7 @@ async function startAndDecrypt(retry) {
|
|||
let securityAlert = require('./security-alert');
|
||||
await securityAlert.start();
|
||||
// now, lets try to fetch balance and txs for first wallet if it is time for it
|
||||
let hadToRefresh = false;
|
||||
/* let hadToRefresh = false;
|
||||
let noErr = true;
|
||||
try {
|
||||
let wallets = BlueApp.getWallets();
|
||||
|
@ -57,7 +58,7 @@ async function startAndDecrypt(retry) {
|
|||
|
||||
if (hadToRefresh && noErr) {
|
||||
await BlueApp.saveToDisk(); // caching
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
if (!success && password) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon, Button, FormLabel, FormInput, Text, Header, List, ListItem } from 'react-native-elements';
|
||||
import { Icon, FormLabel, FormInput, Text, Header, List, ListItem } from 'react-native-elements';
|
||||
import {
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
|
@ -15,10 +15,11 @@ import {
|
|||
StyleSheet,
|
||||
Dimensions,
|
||||
Image,
|
||||
Keyboard,
|
||||
SafeAreaView,
|
||||
InputAccessoryView,
|
||||
Clipboard,
|
||||
Platform,
|
||||
LayoutAnimation,
|
||||
TextInput,
|
||||
} from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
@ -44,14 +45,19 @@ if (aspectRatio > 1.6) {
|
|||
|
||||
export class BlueButton extends Component {
|
||||
render() {
|
||||
const backgroundColor = this.props.disabled ? '#99a0ab' : '#ccddf9';
|
||||
let backgroundColor = '#ccddf9';
|
||||
let fontColor = '#0c2550';
|
||||
if (this.props.hasOwnProperty('disabled') && this.props.disabled === true) {
|
||||
backgroundColor = '#eef0f4';
|
||||
fontColor = '#9aa0aa';
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
borderWidth: 0.7,
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: this.props.hasOwnProperty('backgroundColor') ? this.props.backgroundColor : backgroundColor,
|
||||
backgroundColor: backgroundColor,
|
||||
minHeight: 45,
|
||||
height: 45,
|
||||
maxHeight: 45,
|
||||
|
@ -64,7 +70,7 @@ export class BlueButton extends Component {
|
|||
>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
|
||||
{this.props.icon && <Icon name={this.props.icon.name} type={this.props.icon.type} color={this.props.icon.color} />}
|
||||
{this.props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: '#0c2550' }}>{this.props.title}</Text>}
|
||||
{this.props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor }}>{this.props.title}</Text>}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
@ -141,26 +147,18 @@ export class LightningButton extends Component {
|
|||
|
||||
export class BlueButtonLink extends Component {
|
||||
render() {
|
||||
// eslint-disable-next-line
|
||||
this.props.buttonStyle = this.props.buttonStyle || {};
|
||||
|
||||
return (
|
||||
<Button
|
||||
activeOpacity={0.1}
|
||||
delayPressIn={0}
|
||||
{...this.props}
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
marginTop: 20,
|
||||
borderWidth: 0.7,
|
||||
borderColor: 'transparent',
|
||||
minHeight: 60,
|
||||
minWidth: 100,
|
||||
height: 60,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
buttonStyle={{
|
||||
height: 45,
|
||||
width: width / 1.5,
|
||||
}}
|
||||
backgroundColor="transparent"
|
||||
color="#0c2550"
|
||||
/>
|
||||
{...this.props}
|
||||
>
|
||||
<Text style={{ color: '#0c2550', textAlign: 'center', fontSize: 16 }}>{this.props.title}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -204,36 +202,32 @@ export class BlueCopyTextToClipboard extends Component {
|
|||
text: '',
|
||||
};
|
||||
|
||||
state = { hasTappedText: false };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (Platform.OS === 'android') UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (Platform.OS === 'android') {
|
||||
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
this.state = { hasTappedText: false, address: props.text };
|
||||
}
|
||||
|
||||
copyToClipboard = () => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring, () => {
|
||||
this.setState({ hasTappedText: true }, () => {
|
||||
Clipboard.setString(this.props.text);
|
||||
setTimeout(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
|
||||
this.setState({ hasTappedText: false });
|
||||
}, 1000);
|
||||
this.setState({ address: loc.wallets.xpub.copiedToClipboard }, () => {
|
||||
setTimeout(() => {
|
||||
this.setState({ hasTappedText: false, address: this.props.text });
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
this.setState({ hasTappedText: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
|
||||
<TouchableOpacity onPress={this.copyToClipboard} disabled={this.state.hasTappedText}>
|
||||
<Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
|
||||
{this.props.text}
|
||||
</Text>
|
||||
{this.state.hasTappedText && (
|
||||
<Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
|
||||
{loc.wallets.xpub.copiedToClipboard}
|
||||
</Text>
|
||||
)}
|
||||
<Animated.Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
|
||||
{this.state.address}
|
||||
</Animated.Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
@ -551,6 +545,27 @@ export class BlueList extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export class BlueUseAllFundsButton extends Component {
|
||||
static InputAccessoryViewID = 'useMaxInputAccessoryViewID';
|
||||
static propTypes = {
|
||||
wallet: PropTypes.shape().isRequired,
|
||||
onUseAllPressed: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<InputAccessoryView nativeID={BlueUseAllFundsButton.InputAccessoryViewID}>
|
||||
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Text style={{ color: '#9aa0aa', fontSize: 16, marginHorizontal: 8 }}>
|
||||
Total: {this.props.wallet.getBalance()} {BitcoinUnit.BTC}
|
||||
</Text>
|
||||
<BlueButtonLink title="Use All" onPress={this.props.onUseAllPressed} />
|
||||
</View>
|
||||
</InputAccessoryView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class BlueLoading extends Component {
|
||||
render() {
|
||||
return (
|
||||
|
@ -576,7 +591,7 @@ const stylesBlueIcon = StyleSheet.create({
|
|||
paddingHorizontal: 14,
|
||||
paddingTop: 8,
|
||||
},
|
||||
boxIncomming: {
|
||||
boxIncoming: {
|
||||
position: 'relative',
|
||||
},
|
||||
ball: {
|
||||
|
@ -585,14 +600,14 @@ const stylesBlueIcon = StyleSheet.create({
|
|||
borderRadius: 15,
|
||||
backgroundColor: '#ccddf9',
|
||||
},
|
||||
ballIncomming: {
|
||||
ballIncoming: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
backgroundColor: '#d2f8d6',
|
||||
transform: [{ rotate: '-45deg' }],
|
||||
},
|
||||
ballIncommingWithoutRotate: {
|
||||
ballIncomingWithoutRotate: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
|
@ -655,12 +670,12 @@ export class BluePlusIcon extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export class BlueTransactionIncommingIcon extends Component {
|
||||
export class BlueTransactionIncomingIcon extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View {...this.props}>
|
||||
<View style={stylesBlueIcon.boxIncomming}>
|
||||
<View style={stylesBlueIcon.ballIncomming}>
|
||||
<View style={stylesBlueIcon.boxIncoming}>
|
||||
<View style={stylesBlueIcon.ballIncoming}>
|
||||
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color="#37c0a1" iconStyle={{ left: 0, top: 8 }} />
|
||||
</View>
|
||||
</View>
|
||||
|
@ -673,7 +688,7 @@ export class BlueTransactionPendingIcon extends Component {
|
|||
render() {
|
||||
return (
|
||||
<View {...this.props}>
|
||||
<View style={stylesBlueIcon.boxIncomming}>
|
||||
<View style={stylesBlueIcon.boxIncoming}>
|
||||
<View style={stylesBlueIcon.ball}>
|
||||
<Icon
|
||||
{...this.props}
|
||||
|
@ -694,7 +709,7 @@ export class BlueTransactionExpiredIcon extends Component {
|
|||
render() {
|
||||
return (
|
||||
<View {...this.props}>
|
||||
<View style={stylesBlueIcon.boxIncomming}>
|
||||
<View style={stylesBlueIcon.boxIncoming}>
|
||||
<View style={stylesBlueIcon.ballOutgoingWithoutRotate}>
|
||||
<Icon {...this.props} name="hourglass-end" size={16} type="font-awesome" color="#d0021b" iconStyle={{ left: 0, top: 6 }} />
|
||||
</View>
|
||||
|
@ -708,8 +723,8 @@ export class BlueTransactionOnchainIcon extends Component {
|
|||
render() {
|
||||
return (
|
||||
<View {...this.props}>
|
||||
<View style={stylesBlueIcon.boxIncomming}>
|
||||
<View style={stylesBlueIcon.ballIncomming}>
|
||||
<View style={stylesBlueIcon.boxIncoming}>
|
||||
<View style={stylesBlueIcon.ballIncoming}>
|
||||
<Icon
|
||||
{...this.props}
|
||||
name="link"
|
||||
|
@ -729,7 +744,7 @@ export class BlueTransactionOffchainIcon extends Component {
|
|||
render() {
|
||||
return (
|
||||
<View {...this.props}>
|
||||
<View style={stylesBlueIcon.boxIncomming}>
|
||||
<View style={stylesBlueIcon.boxIncoming}>
|
||||
<View style={stylesBlueIcon.ballOutgoingWithoutRotate}>
|
||||
<Icon {...this.props} name="bolt" size={16} type="font-awesome" color="#d0021b" iconStyle={{ left: 0, top: 7 }} />
|
||||
</View>
|
||||
|
@ -743,8 +758,8 @@ export class BlueTransactionOffchainIncomingIcon extends Component {
|
|||
render() {
|
||||
return (
|
||||
<View {...this.props}>
|
||||
<View style={stylesBlueIcon.boxIncomming}>
|
||||
<View style={stylesBlueIcon.ballIncommingWithoutRotate}>
|
||||
<View style={stylesBlueIcon.boxIncoming}>
|
||||
<View style={stylesBlueIcon.ballIncomingWithoutRotate}>
|
||||
<Icon {...this.props} name="bolt" size={16} type="font-awesome" color="#37c0a1" iconStyle={{ left: 0, top: 7 }} />
|
||||
</View>
|
||||
</View>
|
||||
|
@ -757,7 +772,7 @@ export class BlueTransactionOutgoingIcon extends Component {
|
|||
render() {
|
||||
return (
|
||||
<View {...this.props}>
|
||||
<View style={stylesBlueIcon.boxIncomming}>
|
||||
<View style={stylesBlueIcon.boxIncoming}>
|
||||
<View style={stylesBlueIcon.ballOutgoing}>
|
||||
<Icon {...this.props} name="arrow-down" size={16} type="font-awesome" color="#d0021b" iconStyle={{ left: 0, top: 8 }} />
|
||||
</View>
|
||||
|
@ -983,6 +998,386 @@ export class NewWalletPanel extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
export class BlueTransactionListItem extends Component {
|
||||
static propTypes = {
|
||||
item: PropTypes.shape().isRequired,
|
||||
itemPriceUnit: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
itemPriceUnit: BitcoinUnit.BTC,
|
||||
};
|
||||
|
||||
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'];
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
rowTitle = () => {
|
||||
const item = this.props.item;
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (isNaN(item.value)) {
|
||||
item.value = '0';
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
||||
} else {
|
||||
return loc.lnd.expired;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
||||
}
|
||||
};
|
||||
|
||||
rowTitleStyle = () => {
|
||||
const item = this.props.item;
|
||||
let color = '#37c0a1';
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
color = '#37c0a1';
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
color = '#37c0a1';
|
||||
} else {
|
||||
color = '#FF0000';
|
||||
}
|
||||
}
|
||||
} else if (item.value / 100000000 < 0) {
|
||||
color = BlueApp.settings.foregroundColor;
|
||||
}
|
||||
|
||||
return {
|
||||
fontWeight: '600',
|
||||
fontSize: 16,
|
||||
color: color,
|
||||
};
|
||||
};
|
||||
|
||||
avatar = () => {
|
||||
// is it lightning refill tx?
|
||||
if (this.props.item.category === 'receive' && this.props.item.confirmations < 3) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.item.type && this.props.item.type === 'bitcoind_tx') {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOnchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (this.props.item.type === 'paid_invoice') {
|
||||
// is it lightning offchain payment?
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.item.type === 'user_invoice' || this.props.item.type === 'payment_request') {
|
||||
if (!this.props.item.ispaid) {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = this.props.item.timestamp + this.props.item.expire_time;
|
||||
if (invoiceExpiration < now) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionExpiredIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.props.item.confirmations) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
} else if (this.props.item.value < 0) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOutgoingIcon />
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
subtitle = () => {
|
||||
return (
|
||||
(this.props.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + this.props.item.confirmations + ' ' : '') +
|
||||
this.txMemo() +
|
||||
(this.props.item.memo || '')
|
||||
);
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
if (this.props.item.hash) {
|
||||
NavigationService.navigate('TransactionDetails', { hash: this.props.item.hash });
|
||||
} else if (
|
||||
this.props.item.type === 'user_invoice' ||
|
||||
this.props.item.type === 'payment_request' ||
|
||||
this.props.item.type === 'paid_invoice'
|
||||
) {
|
||||
const lightningWallet = BlueApp.getWallets().filter(wallet => {
|
||||
if (typeof wallet === 'object') {
|
||||
if (wallet.hasOwnProperty('secret')) {
|
||||
return wallet.getSecret() === this.props.item.fromWallet;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (lightningWallet.length === 1) {
|
||||
NavigationService.navigate('LNDViewInvoice', {
|
||||
invoice: this.props.item,
|
||||
fromWallet: lightningWallet[0],
|
||||
isModal: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<BlueListItem
|
||||
avatar={this.avatar()}
|
||||
title={loc.transactionTimeToReadable(this.props.item.received)}
|
||||
subtitle={this.subtitle()}
|
||||
onPress={this.onPress}
|
||||
badge={{
|
||||
value: 3,
|
||||
textStyle: { color: 'orange' },
|
||||
containerStyle: { marginTop: 0 },
|
||||
}}
|
||||
hideChevron
|
||||
rightTitle={this.rowTitle()}
|
||||
rightTitleStyle={this.rowTitleStyle()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class BlueListTransactionItem extends Component {
|
||||
static propTypes = {
|
||||
item: PropTypes.shape().isRequired,
|
||||
itemPriceUnit: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
itemPriceUnit: BitcoinUnit.BTC,
|
||||
};
|
||||
|
||||
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'];
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
rowTitle = () => {
|
||||
const item = this.props.item;
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (isNaN(item.value)) {
|
||||
item.value = '0';
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
||||
} else {
|
||||
return loc.lnd.expired;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.props.itemPriceUnit, true).toString();
|
||||
}
|
||||
};
|
||||
|
||||
rowTitleStyle = () => {
|
||||
const item = this.props.item;
|
||||
let color = '#37c0a1';
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
color = '#37c0a1';
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
color = '#37c0a1';
|
||||
} else {
|
||||
color = '#FF0000';
|
||||
}
|
||||
}
|
||||
} else if (item.value / 100000000 < 0) {
|
||||
color = BlueApp.settings.foregroundColor;
|
||||
}
|
||||
|
||||
return {
|
||||
fontWeight: '600',
|
||||
fontSize: 16,
|
||||
color: color,
|
||||
};
|
||||
};
|
||||
|
||||
avatar = () => {
|
||||
// is it lightning refill tx?
|
||||
if (this.props.item.category === 'receive' && this.props.item.confirmations < 3) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.item.type && this.props.item.type === 'bitcoind_tx') {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOnchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (this.props.item.type === 'paid_invoice') {
|
||||
// is it lightning offchain payment?
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.item.type === 'user_invoice' || this.props.item.type === 'payment_request') {
|
||||
if (!this.props.item.ispaid) {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = this.props.item.timestamp + this.props.item.expire_time;
|
||||
if (invoiceExpiration < now) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionExpiredIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.props.item.confirmations) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
} else if (this.props.item.value < 0) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOutgoingIcon />
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
subtitle = () => {
|
||||
return (
|
||||
(this.props.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + this.props.item.confirmations + ' ' : '') +
|
||||
this.txMemo() +
|
||||
(this.props.item.memo || '')
|
||||
);
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
if (this.props.item.hash) {
|
||||
NavigationService.navigate('TransactionDetails', { hash: this.props.item.hash });
|
||||
} else if (
|
||||
this.props.item.type === 'user_invoice' ||
|
||||
this.props.item.type === 'payment_request' ||
|
||||
this.props.item.type === 'paid_invoice'
|
||||
) {
|
||||
const lightningWallet = BlueApp.getWallets().filter(wallet => {
|
||||
if (typeof wallet === 'object') {
|
||||
if (wallet.hasOwnProperty('secret')) {
|
||||
return wallet.getSecret() === this.props.item.fromWallet;
|
||||
}
|
||||
}
|
||||
});
|
||||
NavigationService.navigate('LNDViewInvoice', {
|
||||
invoice: this.props.item,
|
||||
fromWallet: lightningWallet[0],
|
||||
isModal: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<BlueListItem
|
||||
avatar={this.avatar()}
|
||||
title={loc.transactionTimeToReadable(this.props.item.received)}
|
||||
subtitle={this.subtitle()}
|
||||
onPress={this.onPress}
|
||||
badge={{
|
||||
value: 3,
|
||||
textStyle: { color: 'orange' },
|
||||
containerStyle: { marginTop: 0 },
|
||||
}}
|
||||
hideChevron
|
||||
rightTitle={this.rowTitle()}
|
||||
rightTitleStyle={this.rowTitleStyle()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sliderWidth = width * 1;
|
||||
const itemWidth = width * 0.82;
|
||||
const sliderHeight = 190;
|
||||
|
@ -999,12 +1394,17 @@ export class WalletsCarousel extends Component {
|
|||
|
||||
_renderItem({ item, index }) {
|
||||
let scaleValue = new Animated.Value(1.0);
|
||||
|
||||
let props = { duration: 50 };
|
||||
if (Platform.OS === 'android') {
|
||||
props['useNativeDriver'] = true;
|
||||
}
|
||||
this.onPressedIn = () => {
|
||||
Animated.spring(scaleValue, { toValue: 0.9, duration: 100, useNativeDriver: Platform.OS === 'android' }).start();
|
||||
props.toValue = 0.9;
|
||||
Animated.spring(scaleValue, props).start();
|
||||
};
|
||||
this.onPressedOut = () => {
|
||||
Animated.spring(scaleValue, { toValue: 1.0, duration: 100, useNativeDriver: Platform.OS === 'android' }).start();
|
||||
props.toValue = 1.0;
|
||||
Animated.spring(scaleValue, props).start();
|
||||
};
|
||||
|
||||
if (!item) {
|
||||
|
@ -1173,6 +1573,7 @@ export class BlueAddressInput extends Component {
|
|||
value={this.props.address}
|
||||
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
|
||||
editable={!this.props.isLoading}
|
||||
onSubmitEditing={Keyboard.dismiss}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
disabled={this.props.isLoading}
|
||||
|
@ -1258,7 +1659,6 @@ export class BlueBitcoinAmount extends Component {
|
|||
ref={textInput => (this.textInput = textInput)}
|
||||
editable={!this.props.isLoading && !this.props.disabled}
|
||||
value={amount}
|
||||
autoFocus={this.props.pointerEvents !== 'none'}
|
||||
placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'}
|
||||
style={{
|
||||
color: this.props.disabled ? '#99a0ab' : '#0f5cc0',
|
||||
|
|
195
BlueElectrum.js
Normal file
195
BlueElectrum.js
Normal file
|
@ -0,0 +1,195 @@
|
|||
import { AsyncStorage } from 'react-native';
|
||||
const ElectrumClient = require('electrum-client');
|
||||
let bitcoin = require('bitcoinjs-lib');
|
||||
let reverse = require('buffer-reverse');
|
||||
|
||||
const storageKey = 'ELECTRUM_PEERS';
|
||||
const defaultPeer = { host: 'electrum.coinucopia.io', tcp: 50001 };
|
||||
|
||||
let mainClient = false;
|
||||
let mainConnected = false;
|
||||
|
||||
async function connectMain() {
|
||||
let usingPeer = await getRandomHardcodedPeer();
|
||||
try {
|
||||
console.log('begin connection:', JSON.stringify(usingPeer));
|
||||
mainClient = new ElectrumClient(usingPeer.tcp, usingPeer.host, 'tcp');
|
||||
await mainClient.connect();
|
||||
const ver = await mainClient.server_version('2.7.11', '1.2');
|
||||
console.log('connected to ', ver);
|
||||
let peers = await mainClient.serverPeers_subscribe();
|
||||
if (peers && peers.length > 0) {
|
||||
mainConnected = true;
|
||||
AsyncStorage.setItem(storageKey, JSON.stringify(peers));
|
||||
}
|
||||
} catch (e) {
|
||||
mainConnected = false;
|
||||
console.log('bad connection:', JSON.stringify(usingPeer));
|
||||
}
|
||||
|
||||
if (!mainConnected) {
|
||||
console.log('retry');
|
||||
setTimeout(connectMain, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
connectMain();
|
||||
|
||||
/**
|
||||
* Returns random hardcoded electrum server guaranteed to work
|
||||
* at the time of writing.
|
||||
*
|
||||
* @returns {Promise<{tcp, host}|*>}
|
||||
*/
|
||||
async function getRandomHardcodedPeer() {
|
||||
let hardcodedPeers = [
|
||||
{ host: 'node.ispol.sk', tcp: '50001' },
|
||||
{ host: 'electrum.vom-stausee.de', tcp: '50001' },
|
||||
{ host: 'orannis.com', tcp: '50001' },
|
||||
{ host: '139.162.14.142', tcp: '50001' },
|
||||
{ host: 'daedalus.bauerj.eu', tcp: '50001' },
|
||||
{ host: 'electrum.eff.ro', tcp: '50001' },
|
||||
{ host: 'electrum.anduck.net', tcp: '50001' },
|
||||
{ host: 'mooo.not.fyi', tcp: '50011' },
|
||||
{ host: 'electrum.coinucopia.io', tcp: '50001' },
|
||||
];
|
||||
return hardcodedPeers[(hardcodedPeers.length * Math.random()) | 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns random electrum server out of list of servers
|
||||
* previous electrum server told us. Nearly half of them is
|
||||
* usually offline.
|
||||
* Not used for now.
|
||||
*
|
||||
* @returns {Promise<{tcp: number, host: string}>}
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
async function getRandomDynamicPeer() {
|
||||
try {
|
||||
let peers = JSON.parse(await AsyncStorage.getItem(storageKey));
|
||||
peers = peers.sort(() => Math.random() - 0.5); // shuffle
|
||||
for (let peer of peers) {
|
||||
let ret = {};
|
||||
ret.host = peer[1];
|
||||
for (let item of peer[2]) {
|
||||
if (item.startsWith('t')) {
|
||||
ret.tcp = item.replace('t', '');
|
||||
}
|
||||
}
|
||||
if (ret.host && ret.tcp) return ret;
|
||||
}
|
||||
|
||||
return defaultPeer; // failed to find random client, using default
|
||||
} catch (_) {
|
||||
return defaultPeer; // smth went wrong, using default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param address {String}
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getBalanceByAddress(address) {
|
||||
if (!mainClient) throw new Error('Electrum client is not connected');
|
||||
let script = bitcoin.address.toOutputScript(address);
|
||||
let hash = bitcoin.crypto.sha256(script);
|
||||
let reversedHash = Buffer.from(reverse(hash));
|
||||
let balance = await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex'));
|
||||
balance.addr = address;
|
||||
return balance;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param address {String}
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
async function getTransactionsByAddress(address) {
|
||||
if (!mainClient) throw new Error('Electrum client is not connected');
|
||||
let script = bitcoin.address.toOutputScript(address);
|
||||
let hash = bitcoin.crypto.sha256(script);
|
||||
let reversedHash = Buffer.from(reverse(hash));
|
||||
let history = await mainClient.blockchainScripthash_getHistory(reversedHash.toString('hex'));
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param addresses {Array}
|
||||
* @returns {Promise<{balance: number, unconfirmed_balance: number}>}
|
||||
*/
|
||||
async function multiGetBalanceByAddress(addresses) {
|
||||
if (!mainClient) throw new Error('Electrum client is not connected');
|
||||
let balance = 0;
|
||||
let unconfirmedBalance = 0;
|
||||
for (let addr of addresses) {
|
||||
let b = await getBalanceByAddress(addr);
|
||||
|
||||
balance += b.confirmed;
|
||||
unconfirmedBalance += b.unconfirmed_balance;
|
||||
}
|
||||
|
||||
return { balance, unconfirmed_balance: unconfirmedBalance };
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple waiter till `mainConnected` becomes true (which means
|
||||
* it Electrum was connected in other function), or timeout 30 sec.
|
||||
*
|
||||
*
|
||||
* @returns {Promise<Promise<*> | Promise<*>>}
|
||||
*/
|
||||
async function waitTillConnected() {
|
||||
let waitTillConnectedInterval = false;
|
||||
let retriesCounter = 0;
|
||||
return new Promise(function(resolve, reject) {
|
||||
waitTillConnectedInterval = setInterval(() => {
|
||||
if (mainConnected) {
|
||||
clearInterval(waitTillConnectedInterval);
|
||||
resolve(true);
|
||||
}
|
||||
if (retriesCounter++ >= 30) {
|
||||
clearInterval(waitTillConnectedInterval);
|
||||
reject(new Error('Waiting for Electrum connection timeout'));
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.getBalanceByAddress = getBalanceByAddress;
|
||||
module.exports.getTransactionsByAddress = getTransactionsByAddress;
|
||||
module.exports.multiGetBalanceByAddress = multiGetBalanceByAddress;
|
||||
module.exports.waitTillConnected = waitTillConnected;
|
||||
|
||||
module.exports.forceDisconnect = () => {
|
||||
mainClient.keepAlive = () => {}; // dirty hack to make it stop reconnecting
|
||||
mainClient.reconnect = () => {}; // dirty hack to make it stop reconnecting
|
||||
mainClient.close();
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
let addr4elect = 'bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej';
|
||||
let script = bitcoin.address.toOutputScript(addr4elect);
|
||||
let hash = bitcoin.crypto.sha256(script);
|
||||
let reversedHash = Buffer.from(hash.reverse());
|
||||
console.log(addr4elect, ' maps to ', reversedHash.toString('hex'));
|
||||
console.log(await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex')));
|
||||
|
||||
addr4elect = '1BWwXJH3q6PRsizBkSGm2Uw4Sz1urZ5sCj';
|
||||
script = bitcoin.address.toOutputScript(addr4elect);
|
||||
hash = bitcoin.crypto.sha256(script);
|
||||
reversedHash = Buffer.from(hash.reverse());
|
||||
console.log(addr4elect, ' maps to ', reversedHash.toString('hex'));
|
||||
console.log(await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex')));
|
||||
|
||||
// let peers = await mainClient.serverPeers_subscribe();
|
||||
// console.log(peers);
|
||||
mainClient.keepAlive = () => {}; // dirty hack to make it stop reconnecting
|
||||
mainClient.reconnect = () => {}; // dirty hack to make it stop reconnecting
|
||||
mainClient.close();
|
||||
// setTimeout(()=>process.exit(), 3000); */
|
65
Electrum.test.js
Normal file
65
Electrum.test.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
/* global it, describe, jasmine */
|
||||
global.net = require('net');
|
||||
let BlueElectrum = require('./BlueElectrum');
|
||||
let assert = require('assert');
|
||||
|
||||
describe('Electrum', () => {
|
||||
it('ElectrumClient can connect and query', async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
const ElectrumClient = require('electrum-client');
|
||||
let bitcoin = require('bitcoinjs-lib');
|
||||
// let bitcoin = require('bitcoinjs-lib');
|
||||
|
||||
const peer = { host: 'electrum.coinucopia.io', ssl: 50002, tcp: 50001, pruning: null, http: null, https: null };
|
||||
console.log('begin connection:', JSON.stringify(peer));
|
||||
let mainClient = new ElectrumClient(peer.tcp, peer.host, 'tcp');
|
||||
|
||||
try {
|
||||
await mainClient.connect();
|
||||
const ver = await mainClient.server_version('2.7.11', '1.2');
|
||||
console.log('connected to ', ver);
|
||||
} catch (e) {
|
||||
console.log('bad connection:', JSON.stringify(peer));
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
let addr4elect = 'bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej';
|
||||
let script = bitcoin.address.toOutputScript(addr4elect);
|
||||
let hash = bitcoin.crypto.sha256(script);
|
||||
let reversedHash = Buffer.from(hash.reverse());
|
||||
let balance = await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex'));
|
||||
assert.ok(balance.confirmed > 0);
|
||||
|
||||
addr4elect = '3GCvDBAktgQQtsbN6x5DYiQCMmgZ9Yk8BK';
|
||||
script = bitcoin.address.toOutputScript(addr4elect);
|
||||
hash = bitcoin.crypto.sha256(script);
|
||||
reversedHash = Buffer.from(hash.reverse());
|
||||
balance = await mainClient.blockchainScripthash_getBalance(reversedHash.toString('hex'));
|
||||
assert.ok(balance.confirmed === 51432);
|
||||
|
||||
// let peers = await mainClient.serverPeers_subscribe();
|
||||
// console.log(peers);
|
||||
mainClient.keepAlive = () => {}; // dirty hack to make it stop reconnecting
|
||||
mainClient.reconnect = () => {}; // dirty hack to make it stop reconnecting
|
||||
mainClient.close();
|
||||
// setTimeout(()=>process.exit(), 3000);
|
||||
});
|
||||
|
||||
it('BlueElectrum works', async function() {
|
||||
let address = '3GCvDBAktgQQtsbN6x5DYiQCMmgZ9Yk8BK';
|
||||
await BlueElectrum.waitTillConnected();
|
||||
let balance = await BlueElectrum.getBalanceByAddress(address);
|
||||
assert.strictEqual(balance.confirmed, 51432);
|
||||
assert.strictEqual(balance.unconfirmed, 0);
|
||||
assert.strictEqual(balance.addr, address);
|
||||
|
||||
let txs = await BlueElectrum.getTransactionsByAddress(address);
|
||||
assert.strictEqual(txs.length, 1);
|
||||
for (let tx of txs) {
|
||||
assert.ok(tx.tx_hash);
|
||||
assert.ok(tx.height);
|
||||
}
|
||||
|
||||
BlueElectrum.forceDisconnect();
|
||||
});
|
||||
});
|
|
@ -1,8 +1,21 @@
|
|||
/* global it, jasmine */
|
||||
/* global it, jasmine, afterAll, beforeAll */
|
||||
import { SegwitP2SHWallet, SegwitBech32Wallet, HDSegwitP2SHWallet, HDLegacyBreadwalletWallet, HDLegacyP2PKHWallet } from './class';
|
||||
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
|
||||
let assert = require('assert');
|
||||
let bitcoin = require('bitcoinjs-lib');
|
||||
global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js
|
||||
let BlueElectrum = require('./BlueElectrum'); // so it connects ASAP
|
||||
|
||||
afterAll(() => {
|
||||
// after all tests we close socket so the test suite can actually terminate
|
||||
return BlueElectrum.forceDisconnect();
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
// awaiting for Electrum to be connected. For RN Electrum would naturally connect
|
||||
// while app starts up, but for tests we need to wait for it
|
||||
await BlueElectrum.waitTillConnected();
|
||||
});
|
||||
|
||||
it('can convert witness to address', () => {
|
||||
let address = SegwitP2SHWallet.witnessToAddress('035c618df829af694cb99e664ce1b34f80ad2c3b49bcd0d9c0b1836c66b2d25fd8');
|
||||
|
@ -47,7 +60,7 @@ it('can create a Segwit HD (BIP49)', async function() {
|
|||
assert.strictEqual(hd._getInternalAddressByIndex(hd.next_free_change_address_index), freeChangeAddress);
|
||||
});
|
||||
|
||||
it('Segwit HD (BIP49) can generate addressess only via ypub', async function() {
|
||||
it('Segwit HD (BIP49) can generate addressess only via ypub', function() {
|
||||
let ypub = 'ypub6WhHmKBmHNjcrUVNCa3sXduH9yxutMipDcwiKW31vWjcMbfhQHjXdyx4rqXbEtVgzdbhFJ5mZJWmfWwnP4Vjzx97admTUYKQt6b9D7jjSCp';
|
||||
let hd = new HDSegwitP2SHWallet();
|
||||
hd._xpub = ypub;
|
||||
|
@ -148,6 +161,30 @@ it('Segwit HD (BIP49) can fetch UTXO', async function() {
|
|||
);
|
||||
});
|
||||
|
||||
it('Segwit HD (BIP49) can fetch balance with many used addresses in hierarchy', async function() {
|
||||
if (!process.env.HD_MNEMONIC_BIP49_MANY_TX) {
|
||||
console.error('process.env.HD_MNEMONIC_BIP49_MANY_TX not set, skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 90 * 1000;
|
||||
let hd = new HDSegwitP2SHWallet();
|
||||
hd.setSecret(process.env.HD_MNEMONIC_BIP49_MANY_TX);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
let start = +new Date();
|
||||
await hd.fetchBalance();
|
||||
let end = +new Date();
|
||||
const took = (end - start) / 1000;
|
||||
took > 15 && console.warn('took', took, "sec to fetch huge HD wallet's balance");
|
||||
assert.strictEqual(hd.getBalance(), 0.00051432);
|
||||
|
||||
await hd.fetchUtxo();
|
||||
assert.ok(hd.utxo.length > 0);
|
||||
|
||||
await hd.fetchTransactions();
|
||||
assert.strictEqual(hd.getTransactions().length, 107);
|
||||
});
|
||||
|
||||
it('can work with malformed mnemonic', () => {
|
||||
let mnemonic =
|
||||
'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode';
|
||||
|
|
|
@ -348,4 +348,35 @@ describe('LightningCustodianWallet', () => {
|
|||
await l2.fetchBalance();
|
||||
assert.strictEqual(oldBalance - l2.balance, 3);
|
||||
});
|
||||
|
||||
it('cant pay negative free amount', async () => {
|
||||
let l1 = new LightningCustodianWallet();
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000;
|
||||
assert.ok(l1.refill_addressess.length === 0);
|
||||
assert.ok(l1._refresh_token_created_ts === 0);
|
||||
assert.ok(l1._access_token_created_ts === 0);
|
||||
l1.balance = 'FAKE';
|
||||
|
||||
await l1.createAccount(true);
|
||||
await l1.authorize();
|
||||
await l1.fetchBalance();
|
||||
|
||||
assert.ok(l1.access_token);
|
||||
assert.ok(l1.refresh_token);
|
||||
assert.ok(l1._refresh_token_created_ts > 0);
|
||||
assert.ok(l1._access_token_created_ts > 0);
|
||||
assert.ok(l1.balance === 0);
|
||||
|
||||
let invoice = await l1.addInvoice(0, 'zero amt inv');
|
||||
|
||||
let error = false;
|
||||
try {
|
||||
await l1.payInvoice(invoice, -1);
|
||||
} catch (Err) {
|
||||
error = true;
|
||||
}
|
||||
await l1.fetchBalance();
|
||||
assert.strictEqual(l1.balance, 0);
|
||||
assert.ok(error);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import { createStackNavigator, createAppContainer } from 'react-navigation';
|
|||
|
||||
import Settings from './screen/settings/settings';
|
||||
import About from './screen/settings/about';
|
||||
import ReleaseNotes from './screen/settings/releasenotes';
|
||||
import Selftest from './screen/selftest';
|
||||
import Language from './screen/settings/language';
|
||||
import Currency from './screen/settings/currency';
|
||||
|
@ -86,6 +87,10 @@ const WalletsStackNavigator = createStackNavigator(
|
|||
screen: About,
|
||||
path: 'About',
|
||||
},
|
||||
ReleaseNotes: {
|
||||
screen: ReleaseNotes,
|
||||
path: 'ReleaseNotes',
|
||||
},
|
||||
Selftest: {
|
||||
screen: Selftest,
|
||||
},
|
||||
|
@ -180,6 +185,15 @@ const CreateWalletStackNavigator = createStackNavigator({
|
|||
},
|
||||
});
|
||||
|
||||
const LightningScanInvoiceStackNavigator = createStackNavigator({
|
||||
ScanLndInvoice: {
|
||||
screen: ScanLndInvoice,
|
||||
},
|
||||
Success: {
|
||||
screen: Success,
|
||||
},
|
||||
});
|
||||
|
||||
const MainBottomTabs = createStackNavigator(
|
||||
{
|
||||
Wallets: {
|
||||
|
@ -236,7 +250,10 @@ const MainBottomTabs = createStackNavigator(
|
|||
},
|
||||
},
|
||||
ScanLndInvoice: {
|
||||
screen: ScanLndInvoice,
|
||||
screen: LightningScanInvoiceStackNavigator,
|
||||
navigationOptions: {
|
||||
header: null,
|
||||
},
|
||||
},
|
||||
ScanQrAddress: {
|
||||
screen: sendScanQrAddress,
|
||||
|
|
|
@ -64,3 +64,8 @@ MIT
|
|||
Grab an issue from [the backlog](https://github.com/BlueWallet/BlueWallet/projects/1), try to start or submit a PR, any doubts we will try to guide you.
|
||||
|
||||
Join us at our [telegram group](https://t.me/bluewallet) where we hangout :+1:
|
||||
|
||||
## Responsible disclosure
|
||||
|
||||
Found critical bugs/vulnerabilities? Please email them bluewallet@bluewallet.io
|
||||
Thanks!
|
||||
|
|
|
@ -108,7 +108,10 @@
|
|||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaPrecompile" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javac" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/legacy_multidex_aapt_derived_proguard_rules" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/legacy_multidex_main_dex_list" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/linked_res_for_bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint_jar" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifest-checker" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_assets" />
|
||||
|
|
|
@ -102,10 +102,11 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "3.7.0"
|
||||
versionName "3.7.2"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
multiDexEnabled true
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
|
@ -137,6 +138,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':react-native-tcp')
|
||||
implementation project(':@remobile_react-native-qrcode-local-image')
|
||||
implementation project(':react-native-image-picker')
|
||||
implementation project(':react-native-webview')
|
||||
|
@ -155,6 +157,7 @@ dependencies {
|
|||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.bluewallet.bluewallet;
|
|||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.peel.react.TcpSocketsModule;
|
||||
import com.remobile.qrcodeLocalImage.RCTQRCodeLocalImagePackage;
|
||||
import com.imagepicker.ImagePickerPackage;
|
||||
import com.reactnativecommunity.webview.RNCWebViewPackage;
|
||||
|
@ -55,6 +56,7 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new TcpSocketsModule(),
|
||||
new RCTQRCodeLocalImagePackage(),
|
||||
new ImagePickerPackage(),
|
||||
new RNCWebViewPackage(),
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
rootProject.name = 'BlueWallet'
|
||||
include ':react-native-tcp'
|
||||
project(':react-native-tcp').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-tcp/android')
|
||||
include ':@remobile_react-native-qrcode-local-image'
|
||||
project(':@remobile_react-native-qrcode-local-image').projectDir = new File(rootProject.projectDir, '../node_modules/@remobile/react-native-qrcode-local-image/android')
|
||||
include ':react-native-image-picker'
|
||||
|
|
|
@ -4,6 +4,7 @@ import { WatchOnlyWallet } from './watch-only-wallet';
|
|||
const bip39 = require('bip39');
|
||||
const BigNumber = require('bignumber.js');
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const BlueElectrum = require('../BlueElectrum');
|
||||
|
||||
export class AbstractHDWallet extends LegacyWallet {
|
||||
static type = 'abstract';
|
||||
|
@ -346,24 +347,71 @@ export class AbstractHDWallet extends LegacyWallet {
|
|||
|
||||
async fetchBalance() {
|
||||
try {
|
||||
const api = new Frisbee({ baseURI: 'https://www.blockonomics.co' });
|
||||
let response = await api.post('/api/balance', { body: JSON.stringify({ addr: this.getXpub() }) });
|
||||
// doing binary search for last used externa address
|
||||
|
||||
if (response && response.body && response.body.response) {
|
||||
this.balance = 0;
|
||||
this.unconfirmed_balance = 0;
|
||||
this.usedAddresses = [];
|
||||
for (let addr of response.body.response) {
|
||||
this.balance += addr.confirmed;
|
||||
this.unconfirmed_balance += addr.unconfirmed;
|
||||
this.usedAddresses.push(addr.addr);
|
||||
let that = this;
|
||||
|
||||
// refactor me
|
||||
// eslint-disable-next-line
|
||||
async function binarySearchIterationForInternalAddress(index, maxUsedIndex = 0, minUnusedIndex = 100500100, depth = 0) {
|
||||
if (depth >= 20) return maxUsedIndex + 1; // fail
|
||||
let txs = await BlueElectrum.getTransactionsByAddress(that._getInternalAddressByIndex(index));
|
||||
if (txs.length === 0) {
|
||||
if (index === 0) return 0;
|
||||
minUnusedIndex = Math.min(minUnusedIndex, index); // set
|
||||
index = Math.floor((index - maxUsedIndex) / 2 + maxUsedIndex);
|
||||
} else {
|
||||
maxUsedIndex = Math.max(maxUsedIndex, index); // set
|
||||
let txs2 = await BlueElectrum.getTransactionsByAddress(that._getInternalAddressByIndex(index + 1));
|
||||
if (txs2.length === 0) return index + 1; // thats our next free address
|
||||
|
||||
index = Math.round((minUnusedIndex - index) / 2 + index);
|
||||
}
|
||||
this.balance = new BigNumber(this.balance).dividedBy(100000000).toString() * 1;
|
||||
this.unconfirmed_balance = new BigNumber(this.unconfirmed_balance).dividedBy(100000000).toString() * 1;
|
||||
this._lastBalanceFetch = +new Date();
|
||||
} else {
|
||||
throw new Error('Could not fetch balance from API: ' + response.err);
|
||||
|
||||
return binarySearchIterationForInternalAddress(index, maxUsedIndex, minUnusedIndex, depth + 1);
|
||||
}
|
||||
|
||||
this.next_free_change_address_index = await binarySearchIterationForInternalAddress(100);
|
||||
|
||||
// refactor me
|
||||
// eslint-disable-next-line
|
||||
async function binarySearchIterationForExternalAddress(index, maxUsedIndex = 0, minUnusedIndex = 100500100, depth = 0) {
|
||||
if (depth >= 20) return maxUsedIndex + 1; // fail
|
||||
let txs = await BlueElectrum.getTransactionsByAddress(that._getExternalAddressByIndex(index));
|
||||
if (txs.length === 0) {
|
||||
if (index === 0) return 0;
|
||||
minUnusedIndex = Math.min(minUnusedIndex, index); // set
|
||||
index = Math.floor((index - maxUsedIndex) / 2 + maxUsedIndex);
|
||||
} else {
|
||||
maxUsedIndex = Math.max(maxUsedIndex, index); // set
|
||||
let txs2 = await BlueElectrum.getTransactionsByAddress(that._getExternalAddressByIndex(index + 1));
|
||||
if (txs2.length === 0) return index + 1; // thats our next free address
|
||||
|
||||
index = Math.round((minUnusedIndex - index) / 2 + index);
|
||||
}
|
||||
|
||||
return binarySearchIterationForExternalAddress(index, maxUsedIndex, minUnusedIndex, depth + 1);
|
||||
}
|
||||
|
||||
this.next_free_address_index = await binarySearchIterationForExternalAddress(100);
|
||||
|
||||
this.balance = 0;
|
||||
this.unconfirmed_balance = 0;
|
||||
this.usedAddresses = [];
|
||||
|
||||
// generating all involved addresses:
|
||||
for (let c = 0; c < this.next_free_address_index; c++) {
|
||||
this.usedAddresses.push(this._getExternalAddressByIndex(c));
|
||||
}
|
||||
for (let c = 0; c < this.next_free_change_address_index; c++) {
|
||||
this.usedAddresses.push(this._getInternalAddressByIndex(c));
|
||||
}
|
||||
|
||||
// finally fetching balance
|
||||
let balance = await BlueElectrum.multiGetBalanceByAddress(this.usedAddresses);
|
||||
this.balance = new BigNumber(balance.balance).dividedBy(100000000).toNumber();
|
||||
this.unconfirmed_balance = new BigNumber(balance.unconfirmed_balance).dividedBy(100000000).toNumber();
|
||||
this._lastBalanceFetch = +new Date();
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
}
|
||||
|
|
6
index.js
6
index.js
|
@ -24,13 +24,13 @@ class BlueAppComponent extends React.Component {
|
|||
this.state = { isMigratingData: true };
|
||||
}
|
||||
|
||||
async setIsMigratingData() {
|
||||
setIsMigratingData = async () => {
|
||||
await BlueApp.startAndDecrypt();
|
||||
this.setState({ isMigratingData: false });
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return this.state.isMigratingData ? <WalletMigrate onComplete={() => this.setIsMigratingData()} /> : <App />;
|
||||
return this.state.isMigratingData ? <WalletMigrate onComplete={this.setIsMigratingData} /> : <App />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
2DCD954D1E0B4F2C00145EB5 /* BlueWalletTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* BlueWalletTests.m */; };
|
||||
2DF0FFEE2056DD460020B375 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3EA31DF850E9000B6D8A /* libReact.a */; };
|
||||
2F707BDB2EF14D17AF9A2908 /* libReactNativePermissions.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DD63E4B5C8344BB9880C9EC /* libReactNativePermissions.a */; };
|
||||
34582CAA4AD140F7B80C961A /* libTcpSockets.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DF4E6C040764E4BA1ACC1EB /* libTcpSockets.a */; };
|
||||
34CC55B441594DBB95AD1B50 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */; };
|
||||
3EEBC6F85642487DA7C4EE35 /* AntDesign.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C4496FB303574862B40A878A /* AntDesign.ttf */; };
|
||||
4D6390DDA5B7485F91A6C750 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2654894D4DE44A4C8F71773D /* CoreData.framework */; };
|
||||
|
@ -524,6 +525,13 @@
|
|||
remoteGlobalIDString = 32D980DD1BE9F11C00FA27E5;
|
||||
remoteInfo = RCTQRCodeLocalImage;
|
||||
};
|
||||
B47720652202510900DD0E81 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 910283A2A9EB4D00902DE78E /* TcpSockets.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 134814201AA4EA6300B7C361;
|
||||
remoteInfo = TcpSockets;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -579,8 +587,10 @@
|
|||
7EA61BC8FF6E4AD2A67F1557 /* RCTQRCodeLocalImage.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTQRCodeLocalImage.xcodeproj; path = "../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage.xcodeproj"; sourceTree = "<group>"; };
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
8637D4B5E14D443A9031DA95 /* libRNFS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFS.a; sourceTree = "<group>"; };
|
||||
910283A2A9EB4D00902DE78E /* TcpSockets.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = TcpSockets.xcodeproj; path = "../node_modules/react-native-tcp/ios/TcpSockets.xcodeproj"; sourceTree = "<group>"; };
|
||||
94565BFC6A0C4235B3EC7B01 /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = "<group>"; };
|
||||
95208B2A05884A76B5BB99C0 /* libRCTGoogleAnalyticsBridge.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTGoogleAnalyticsBridge.a; sourceTree = "<group>"; };
|
||||
9DF4E6C040764E4BA1ACC1EB /* libTcpSockets.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libTcpSockets.a; sourceTree = "<group>"; };
|
||||
9EA3788F4C6643B7B0182587 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
|
||||
9F1F51A83D044F3BB26A35FC /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNSVG-tvOS.a"; sourceTree = "<group>"; };
|
||||
A71D2FDE64CF4F729C7298EA /* RNFS.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFS.xcodeproj; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = "<group>"; };
|
||||
|
@ -661,6 +671,7 @@
|
|||
02DEC1C9F61B405E8E357B2E /* libRCTWKWebView.a in Frameworks */,
|
||||
A6E5EEC7A4B54F5A9C9D92FC /* libRNImagePicker.a in Frameworks */,
|
||||
C1056BF235EE4E23AAF21975 /* libRCTQRCodeLocalImage.a in Frameworks */,
|
||||
34582CAA4AD140F7B80C961A /* libTcpSockets.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -898,6 +909,7 @@
|
|||
E7173EC6B95B4981AD3D4C70 /* RCTWKWebView.xcodeproj */,
|
||||
1A03CFBC35DD4AC28FA4A619 /* RNImagePicker.xcodeproj */,
|
||||
7EA61BC8FF6E4AD2A67F1557 /* RCTQRCodeLocalImage.xcodeproj */,
|
||||
910283A2A9EB4D00902DE78E /* TcpSockets.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
@ -972,6 +984,7 @@
|
|||
E7078D2FED444DA4B0BD57F9 /* libRCTWKWebView.a */,
|
||||
FC98DC24A81A463AB8B2E6B1 /* libRNImagePicker.a */,
|
||||
B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */,
|
||||
9DF4E6C040764E4BA1ACC1EB /* libTcpSockets.a */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
|
@ -1119,6 +1132,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B47720622202510900DD0E81 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B47720662202510900DD0E81 /* libTcpSockets.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -1352,6 +1373,10 @@
|
|||
ProductGroup = B40FE5CC21FAD27D005D5578 /* Products */;
|
||||
ProjectRef = 9EA3788F4C6643B7B0182587 /* RNVectorIcons.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = B47720622202510900DD0E81 /* Products */;
|
||||
ProjectRef = 910283A2A9EB4D00902DE78E /* TcpSockets.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
|
@ -1791,6 +1816,13 @@
|
|||
remoteRef = B4327F5021FC1B9300F7ADFA /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
B47720662202510900DD0E81 /* libTcpSockets.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libTcpSockets.a;
|
||||
remoteRef = B47720652202510900DD0E81 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
|
@ -1964,6 +1996,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = BlueWalletTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
|
@ -1992,6 +2025,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
|
@ -2028,6 +2062,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = BlueWalletTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
|
@ -2056,6 +2091,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
|
@ -2093,6 +2129,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = BlueWallet/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
|
@ -2102,7 +2139,7 @@
|
|||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet;
|
||||
PRODUCT_NAME = BlueWallet;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
@ -2134,6 +2171,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = BlueWallet/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
|
@ -2143,7 +2181,7 @@
|
|||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet;
|
||||
PRODUCT_NAME = BlueWallet;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
@ -2182,6 +2220,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = "BlueWallet-tvOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
@ -2209,6 +2248,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
|
@ -2254,6 +2294,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = "BlueWallet-tvOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
@ -2281,6 +2322,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
|
@ -2325,6 +2367,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = "BlueWallet-tvOSTests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
|
@ -2352,6 +2395,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
|
@ -2396,6 +2440,7 @@
|
|||
"$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
|
||||
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
|
||||
"$(SRCROOT)/../node_modules/@remobile/react-native-qrcode-local-image/ios/RCTQRCodeLocalImage",
|
||||
"$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
|
||||
);
|
||||
INFOPLIST_FILE = "BlueWallet-tvOSTests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
|
@ -2423,6 +2468,7 @@
|
|||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
moduleName:@"BlueWallet"
|
||||
initialProperties:nil
|
||||
launchOptions:launchOptions];
|
||||
rootView.backgroundColor = [UIColor blackColor];
|
||||
rootView.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.7.0</string>
|
||||
<string>3.7.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
@ -56,9 +56,11 @@
|
|||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSMotionUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
|
@ -67,8 +69,6 @@
|
|||
<string>In order to import an image for scanning, we need your permission to access your photo library.</string>
|
||||
<key>NSSpeechRecognitionUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>AntDesign.ttf</string>
|
||||
|
|
61
loc/de_DE.js
61
loc/de_DE.js
|
@ -4,23 +4,23 @@ module.exports = {
|
|||
enter_password: 'Gib das Passwort ein',
|
||||
bad_password: 'Fasches Passwort, nächster Versuch',
|
||||
never: 'nie',
|
||||
continue: 'Continue',
|
||||
continue: 'Weiter',
|
||||
ok: 'OK',
|
||||
},
|
||||
wallets: {
|
||||
select_wallet: 'Wähle Wallet',
|
||||
select_wallet: 'Wähle eine Wallet',
|
||||
options: 'Einstellungen',
|
||||
createBitcoinWallet:
|
||||
'In order to use a Lightning wallet, a Bitcoin wallet is needed in order to fund it. Please, create or import a Bitcoin wallet.',
|
||||
'Um eine Lightning wallet zu verwenden, muss erstmal eine Bitcoin Wallet eingerichtet werden. Bitte erstell oder importier eine Bitcoin Wallet.',
|
||||
list: {
|
||||
app_name: 'BlueWallet',
|
||||
title: 'Wallets',
|
||||
header:
|
||||
'Eine Wallet (Brieftasche) spiegelt ein Paar von kryptographischen Schlüssel wider. Einen geheimen und eine Adresse als öffentlichen Schlüssel. Letztern kann man zum Erhalt von Bitcoin teilen.',
|
||||
'Eine Wallet spiegelt ein Paar kryptographische Schlüssel wider. Einen geheimen Schlüseel und eine Adresse als öffentlichen Schlüssel. Den öffentlichen Schlüssel kann man zum Empfang von Bitcoin teilen.',
|
||||
add: 'Wallet hinzufügen',
|
||||
create_a_wallet: 'Wallet erstellen',
|
||||
create_a_wallet1: 'Es ist kostenlos und du kannst',
|
||||
create_a_wallet2: 'so viele erstellen, wie du möchtest',
|
||||
create_a_wallet2: 'so viele Wallets erstellen, wie du möchtest',
|
||||
latest_transaction: 'Lezte Transaktion',
|
||||
empty_txs1: 'Deine Transaktionen erscheinen hier',
|
||||
empty_txs2: 'Noch keine Transaktionen',
|
||||
|
@ -42,7 +42,7 @@ module.exports = {
|
|||
or: 'oder',
|
||||
import_wallet: 'Wallet importieren',
|
||||
imported: 'Importiert',
|
||||
coming_soon: 'Folgt bald',
|
||||
coming_soon: 'Demnächst verfügbar',
|
||||
lightning: 'Lightning',
|
||||
bitcoin: 'Bitcoin',
|
||||
},
|
||||
|
@ -77,15 +77,15 @@ module.exports = {
|
|||
imported: 'Importiert',
|
||||
error: 'Fehler beim Import. Ist die Eingabe korrekt?',
|
||||
success: 'Erfolg',
|
||||
do_import: 'Importiere',
|
||||
do_import: 'Importieren',
|
||||
scan_qr: 'oder QR-Code scannen?',
|
||||
},
|
||||
scanQrWif: {
|
||||
go_back: 'Zurück',
|
||||
cancel: 'Abbrechen',
|
||||
decoding: 'Decodieren',
|
||||
decoding: 'Entschlüsseln',
|
||||
input_password: 'Passwort eingeben',
|
||||
password_explain: 'Das ist ein BIP38 verschlüsselter geheimer Schlüssel',
|
||||
password_explain: 'Das ist ein mit BIP38 verschlüsselter geheimer Schlüssel',
|
||||
bad_password: 'Falsches Passwort',
|
||||
wallet_already_exists: 'Diese Wallet existiert bereits',
|
||||
bad_wif: 'Falsches WIF',
|
||||
|
@ -116,11 +116,11 @@ module.exports = {
|
|||
header: 'Senden',
|
||||
details: {
|
||||
title: 'Transaktion erstellen',
|
||||
amount_field_is_not_valid: 'Betrageingabe ist nicht valide',
|
||||
fee_field_is_not_valid: 'Gebühreingabe ist nicht valide',
|
||||
address_field_is_not_valid: 'Adresseingabe ist nicht valide',
|
||||
amount_field_is_not_valid: 'Betrageingabe ist nicht korrekt',
|
||||
fee_field_is_not_valid: 'Gebühreingabe ist nicht korrekt',
|
||||
address_field_is_not_valid: 'Adresseingabe ist nicht korrekt',
|
||||
total_exceeds_balance: 'Der zu sendende Betrag ist größer als der verfügbare Betrag.',
|
||||
create_tx_error: 'Fehler beim Erstellen der Transaktion. Bitte stelle sicher, dass die Adresse valide ist.',
|
||||
create_tx_error: 'Fehler beim Erstellen der Transaktion. Bitte stelle sicher, dass die Adresse korrekt ist.',
|
||||
address: 'Adresse',
|
||||
amount_placeholder: 'Betrag (in BTC)',
|
||||
fee_placeholder: 'plus Gebühr (in BTC)',
|
||||
|
@ -172,36 +172,36 @@ module.exports = {
|
|||
},
|
||||
settings: {
|
||||
header: 'Einstellungen',
|
||||
plausible_deniability: 'Glaubhafte Abstreitbarkeit...',
|
||||
storage_not_encrypted: 'Speicher: nicht verschlüsselt',
|
||||
storage_encrypted: 'Speicher: verschlüsselt',
|
||||
plausible_deniability: 'Glaubhafte Täuschung...',
|
||||
storage_not_encrypted: 'Speicher nicht verschlüsselt',
|
||||
storage_encrypted: 'Speicher verschlüsselt',
|
||||
password: 'Passwort',
|
||||
password_explain: 'Erstelle das Passwort zum Entschlüsseln des Speichers',
|
||||
retype_password: 'Passwort wiederholen',
|
||||
passwords_do_not_match: 'Passwörter stimmen nicht überein',
|
||||
encrypt_storage: 'Speicher verschlüsseln',
|
||||
lightning_settings: 'Lightning settings',
|
||||
lightning_settings: 'Lightning Einstellungen',
|
||||
lightning_settings_explain:
|
||||
'To connect to your own LND node please install LndHub' +
|
||||
' and put its URL here in settings. Leave blank to use default ' +
|
||||
'ndHub\n (lndhub.io)',
|
||||
save: 'save',
|
||||
'Bitte installier Lndhub, um mit deiner eigenen LND Node zu verbinden' +
|
||||
' und setz seine URL hier in den Einstellungen. Lass das Feld leer, um Standard- ' +
|
||||
'LndHub\n (lndhub.io) zu verwenden',
|
||||
save: 'Speichern',
|
||||
about: 'Über',
|
||||
language: 'Sprache',
|
||||
currency: 'Währung',
|
||||
},
|
||||
plausibledeniability: {
|
||||
title: 'Glaubhafte Abstreitbarkeit',
|
||||
title: 'Glaubhafte Täuschung',
|
||||
help:
|
||||
'Unter bestimmten Umständen könntest du dazu gezwungen werden, ' +
|
||||
'dein Passwort preiszugeben. Um deine Bitcoins zu sichern, kann ' +
|
||||
'BlueWallet einen weiteren verschlüsselten Speicher mit einem ' +
|
||||
'anderen Passwort erstellen. Unter äußeren Druck kannst du das ' +
|
||||
'anderen Passwort erstellen. Unter Druck kannst du das ' +
|
||||
'zweite Passwort an Fremde weitergeben. Wenn eingegeben, öffnet ' +
|
||||
'BlueWallet einen anderen Speicher zur Täuschung. Dies wirkt ' +
|
||||
'auf Fremde täuschen echt und dein Hauptspeicher bleibt geheim ' +
|
||||
'auf Fremde täuschend echt und dein Hauptspeicher bleibt geheim ' +
|
||||
'und sicher.',
|
||||
help2: 'Der weitere Speicher ist voll funktional und man kann einen Minimalbetrag für die Glaubhaftigkeit hinterlegen.',
|
||||
help2: 'Der andere Speicher ist voll funktional und man kann einen Minimalbetrag für die Glaubhaftigkeit hinterlegen.',
|
||||
create_fake_storage: 'Erstelle verschlüsselten Speicher zur Täuschung',
|
||||
go_back: 'Zurück',
|
||||
create_password: 'Erstelle ein Passwort',
|
||||
|
@ -209,14 +209,15 @@ module.exports = {
|
|||
password_should_not_match: 'Das Passwort für den täuschenden Speicher darf nicht mit dem deines Hauptspeichers übereinstimmen',
|
||||
retype_password: 'Passwort wiederholen',
|
||||
passwords_do_not_match: 'Passwörter stimmen nicht überein. Neuer Versuch',
|
||||
success: 'Erfolg',
|
||||
success: 'Erfolg!',
|
||||
},
|
||||
lnd: {
|
||||
title: 'Beträge verwalten',
|
||||
choose_source_wallet: 'Wähle eine Wallet als Quelle',
|
||||
refill_lnd_balance: 'Fülle deine Lightning Wallet auf',
|
||||
refill: 'Auffüllen',
|
||||
choose_source_wallet: 'Wähle eine Wallet als Zahlungsquelle',
|
||||
refill_lnd_balance: 'Lade deine Lightning Wallet auf',
|
||||
refill: 'Aufladen',
|
||||
withdraw: 'Abheben',
|
||||
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
|
||||
sameWalletAsInvoiceError:
|
||||
'Du kannst nicht die Rechnung mit der Wallet begleichen, die du für die Erstellung dieser Rechnung verwendet hast.',
|
||||
},
|
||||
};
|
||||
|
|
164
loc/ru.js
164
loc/ru.js
|
@ -1,78 +1,78 @@
|
|||
module.exports = {
|
||||
_: {
|
||||
storage_is_encrypted: 'Ваше хранилище зашифровано. Введите пароль для расшифровки',
|
||||
enter_password: 'Введите пароль',
|
||||
bad_password: 'Неверный пароль, попробуйте еще раз',
|
||||
never: 'никогда',
|
||||
continue: 'Continue',
|
||||
storage_is_encrypted: 'Твоё хранилище зашифровано. Введи пароль для расшифровки',
|
||||
enter_password: 'Введи пароль',
|
||||
bad_password: 'Неверный пароль, попробуй еще раз',
|
||||
never: 'Никогда',
|
||||
continue: 'Продолжить',
|
||||
ok: 'OK',
|
||||
},
|
||||
wallets: {
|
||||
options: 'options',
|
||||
select_wallet: 'Select Wallet',
|
||||
createBitcoinWallet: 'In order to use a Lightning wallet, a Bitcoin wallet is needed to fund it. Would you like to continue anyway?',
|
||||
options: 'Настройки',
|
||||
select_wallet: 'Выбрать кошелек',
|
||||
createBitcoinWallet: 'Чтобы воспользоватья кошельком Lightning, нужно сначала пополнить его с помощью кошелька Bitcoin. Продолжить?',
|
||||
|
||||
list: {
|
||||
app_name: 'BlueWallet',
|
||||
title: 'кошельки',
|
||||
header: 'Кошелек это секретный (приватный) ключ, и соответствующий ему адрес на который можно получать биткоины',
|
||||
title: 'Кошельки',
|
||||
header: 'Кошелек - это секретный (приватный) ключ и соответствующий ему адрес на который можно получать Bitcoin',
|
||||
add: 'Добавить Кошелек',
|
||||
create_a_wallet: 'Создать кошелек',
|
||||
create_a_wallet1: 'Это бесплатно и вы можете создать',
|
||||
create_a_wallet1: 'Это бесплатно и ты можешь создать',
|
||||
create_a_wallet2: 'неограниченное количество',
|
||||
latest_transaction: 'последняя транзакция',
|
||||
latest_transaction: 'Последняя транзакция',
|
||||
empty_txs1: 'Список транзакций пока пуст',
|
||||
empty_txs2: ' ',
|
||||
tap_here_to_buy: 'Tap here to buy Bitcoin',
|
||||
tap_here_to_buy: 'Купить Bitcoin',
|
||||
},
|
||||
reorder: {
|
||||
title: 'Reorder Wallets',
|
||||
title: 'Отсортировать кошельки',
|
||||
},
|
||||
add: {
|
||||
title: 'добавить кошелек',
|
||||
title: 'Добавить кошелек',
|
||||
description:
|
||||
'Вы можете отсканировать QR код (в формате WIF - Wallet Import Format), или создать новый кошелек. Segwit поддерживается по умолчанию.',
|
||||
'Ты можешь отсканировать QR код (в формате WIF - Wallet Import Format) или создать новый кошелек. Segwit поддерживается по умолчанию.',
|
||||
scan: 'Отсканировать',
|
||||
create: 'Создать',
|
||||
label_new_segwit: 'Новый SegWit',
|
||||
label_new_lightning: 'Новый Lightning',
|
||||
wallet_name: 'имя кошелька',
|
||||
wallet_type: 'тип кошелька',
|
||||
or: 'or',
|
||||
wallet_name: 'Имя кошелька',
|
||||
wallet_type: 'Тип кошелька',
|
||||
or: 'или',
|
||||
import_wallet: 'Импортировать кошелек',
|
||||
imported: 'Импортирован',
|
||||
coming_soon: 'Пока недоступно',
|
||||
imported: 'Кошелек импортирован',
|
||||
coming_soon: 'Скоро будет',
|
||||
lightning: 'Lightning',
|
||||
bitcoin: 'Bitcoin',
|
||||
},
|
||||
details: {
|
||||
title: 'Информация о Кошельке',
|
||||
title: 'Информация о кошельке',
|
||||
address: 'Адрес',
|
||||
type: 'Тип',
|
||||
label: 'Метка',
|
||||
delete: 'Delete',
|
||||
save: 'Save',
|
||||
are_you_sure: 'Вы уверены?',
|
||||
delete: 'Удалить',
|
||||
save: 'Сохранить',
|
||||
are_you_sure: 'Точно удалить?',
|
||||
yes_delete: 'Да, удалить',
|
||||
destination: 'destination',
|
||||
description: 'description',
|
||||
destination: 'Назначение',
|
||||
description: 'Описание',
|
||||
no_cancel: 'Нет, отмена',
|
||||
delete_this_wallet: 'Удалить этот кошелек',
|
||||
export_backup: 'Экспорт / резервная копия',
|
||||
buy_bitcoin: 'Buy Bitcoin',
|
||||
show_xpub: 'Show wallet XPUB',
|
||||
buy_bitcoin: 'Купить Bitcoin',
|
||||
show_xpub: 'Показать XPUB',
|
||||
},
|
||||
export: {
|
||||
title: 'Экспорт Кошелька',
|
||||
},
|
||||
xpub: {
|
||||
title: 'wallet XPUB',
|
||||
copiedToClipboard: 'скопировано',
|
||||
title: 'XPUB кошелька',
|
||||
copiedToClipboard: 'Скопировано',
|
||||
},
|
||||
import: {
|
||||
title: 'import',
|
||||
explanation: 'Напишите тут вашу мнемонику, приватный ключ, WIF, что угодно, BlueWallet постарается угадать верный формат',
|
||||
imported: 'Импортирован',
|
||||
title: 'Импорт',
|
||||
explanation: 'Напиши тут свою мнемоническую фразу, приватный ключ, WIF - что угодно! BlueWallet постарается угадать верный формат',
|
||||
imported: 'Импорт завершен',
|
||||
error: 'Не удалось импортировать',
|
||||
success: 'Успех',
|
||||
do_import: 'Импортировать',
|
||||
|
@ -81,8 +81,8 @@ module.exports = {
|
|||
scanQrWif: {
|
||||
go_back: 'Назад',
|
||||
cancel: 'Отмена',
|
||||
decoding: 'Декодирую',
|
||||
input_password: 'Введите пароль',
|
||||
decoding: 'Расшивровываю',
|
||||
input_password: 'Введи пароль',
|
||||
password_explain: 'Приватный ключ зашифрован по стандарту BIP38',
|
||||
bad_password: 'Неверный пароль',
|
||||
wallet_already_exists: 'Такой кошелек уже существует',
|
||||
|
@ -106,40 +106,40 @@ module.exports = {
|
|||
from: 'От',
|
||||
to: 'Кому',
|
||||
copy: 'копировать',
|
||||
transaction_details: 'Transaction details',
|
||||
show_in_block_explorer: 'Show in block explorer',
|
||||
transaction_details: 'Детали транзакции',
|
||||
show_in_block_explorer: 'Показать транзакцию в блокчейне',
|
||||
},
|
||||
},
|
||||
send: {
|
||||
header: 'Отправить',
|
||||
confirm: {
|
||||
header: 'Confirm',
|
||||
sendNow: 'Send now',
|
||||
header: 'Подтвердить',
|
||||
sendNow: 'Отправить',
|
||||
},
|
||||
success: {
|
||||
done: 'Done',
|
||||
done: 'Готово',
|
||||
},
|
||||
details: {
|
||||
title: 'Создать Транзакцию',
|
||||
amount_field_is_not_valid: 'Поле не валидно',
|
||||
fee_field_is_not_valid: 'Поле `комиссия` не валидно',
|
||||
address_field_is_not_valid: 'Поле `адрес` не валидно',
|
||||
amount_field_is_not_valid: 'Введенная сумма неверна',
|
||||
fee_field_is_not_valid: 'Введенная комиссия неверна',
|
||||
address_field_is_not_valid: 'Введенный адрес неверный',
|
||||
receiver_placeholder: 'Адрес получателя',
|
||||
amount_placeholder: 'сколько отправить (в BTC)',
|
||||
fee_placeholder: 'плюс комиссия за перевод (в BTC)',
|
||||
note_placeholder: 'примечание платежа',
|
||||
create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.',
|
||||
create_tx_error: 'Ошибка при создании транзакции. Пожалуйста, проверь правильность адреса.',
|
||||
cancel: 'Отмена',
|
||||
scan: 'Скан QR',
|
||||
scan: 'Отсканировать QR',
|
||||
create: 'Создать',
|
||||
send: 'Send',
|
||||
address: 'Address',
|
||||
total_exceeds_balance: 'Total amount exceeds balance.',
|
||||
send: 'Отправить',
|
||||
address: 'Адрес',
|
||||
total_exceeds_balance: 'Общая сумма превышает баланс.',
|
||||
remaining_balance: 'Остаток баланса',
|
||||
},
|
||||
create: {
|
||||
title: 'Создать Транзакцию',
|
||||
details: 'Details',
|
||||
details: 'Детали',
|
||||
error: 'Ошибка при создании транзакции. Неправильный адрес назначения или недостаточно средств?',
|
||||
go_back: 'Назад',
|
||||
this_is_hex: 'Это данные транзакции. Транзакция подписана и готова быть транслирована в сеть. Продолжить?',
|
||||
|
@ -150,75 +150,75 @@ module.exports = {
|
|||
satoshi_per_byte: 'Сатоши на байт',
|
||||
memo: 'Примечание',
|
||||
broadcast: 'Отправить',
|
||||
not_enough_fee: 'Слишком маленькая комиссия. Увеличьте комиссию',
|
||||
not_enough_fee: 'Слишком маленькая комиссия. Увеличь комиссию',
|
||||
},
|
||||
},
|
||||
buyBitcoin: {
|
||||
header: 'Buy Bitcoin',
|
||||
tap_your_address: 'Tap your address to copy it to clipboard:',
|
||||
copied: 'Copied to Clipboard!',
|
||||
header: 'Купить Bitcoin',
|
||||
tap_your_address: 'Нажми на адрес, чтобы скопировать его:',
|
||||
copied: 'Скопировано',
|
||||
},
|
||||
receive: {
|
||||
header: 'Получить',
|
||||
details: {
|
||||
title: 'Покажите этот адрес плательщику',
|
||||
title: 'Покажи этот адрес плательщику',
|
||||
share: 'Отправить',
|
||||
copiedToClipboard: 'скопировано',
|
||||
label: 'Description',
|
||||
create: 'Create',
|
||||
setAmount: 'Receive with amount',
|
||||
copiedToClipboard: 'Скопировано',
|
||||
label: 'Описание',
|
||||
create: 'Создать',
|
||||
setAmount: 'Получить сумму',
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
tabBarLabel: 'Настройки',
|
||||
header: 'Настройки',
|
||||
plausible_deniability: 'Правдоподобное отрицание...',
|
||||
plausible_deniability: 'Правдоподобная имитация...',
|
||||
storage_not_encrypted: 'Хранилище: не зашифровано',
|
||||
storage_encrypted: 'Хранилище: зашифровано',
|
||||
password: 'Пароль',
|
||||
password_explain: 'Придумайте пароль для расшифровки хранилища',
|
||||
retype_password: 'Наберите пароль повторно',
|
||||
password_explain: 'Придумай пароль для расшифровки хранилища',
|
||||
retype_password: 'Набери пароль повторно',
|
||||
passwords_do_not_match: 'Пароли не совпадают',
|
||||
encrypt_storage: 'Зашифровать хранилище',
|
||||
lightning_settings: 'Lightning settings',
|
||||
lightning_settings: 'Настройки Lightning',
|
||||
lightning_settings_explain:
|
||||
'To connect to your own LND node please install LndHub' +
|
||||
' and put its URL here in settings. Leave blank to use default ' +
|
||||
'ndHub\n (lndhub.io)',
|
||||
save: 'save',
|
||||
'Чтобы подключиться к своему узлу LND, пожалуйста, установи LndHub' +
|
||||
' и добавь его URL в настройки. Оставь поле пустым, чтобы использоавать стандартный ' +
|
||||
'LndHub\n (lndhub.io)',
|
||||
save: 'Сохранить',
|
||||
about: 'О программе',
|
||||
language: 'Язык',
|
||||
currency: 'Валюта',
|
||||
},
|
||||
plausibledeniability: {
|
||||
title: 'Правдоподобное Отрицание',
|
||||
title: 'Правдоподобная имитация',
|
||||
help:
|
||||
'При определенных обстоятельствах вас могут вынудить раскрыть пароль. ' +
|
||||
'Чтобы сохранить ваши биткоины в безопасности, BlueWallet может создать ' +
|
||||
'еще одно зашифрованое хранилище, с другим паролем. Под давлением, ' +
|
||||
'вы можете раскрыть третьим лицам этот пароль. Если ввести этот пароль в ' +
|
||||
'При определенных обстоятельствах тебя могут вынудить раскрыть пароль. ' +
|
||||
'Чтобы сохранить свой Bitcoin в безопасности, BlueWallet может создать ' +
|
||||
'еще одно зашифрованое хранилище, с другим паролем. В случае шантажа ' +
|
||||
'ты можешь раскрыть третьим лицам этот пароль. Если ввести этот пароль в ' +
|
||||
"BlueWallet, разблокируется 'фальшивое' хранилище. Это будет выглядеть " +
|
||||
'правдоподобно для третьих лиц, но при этом сохранит ваше основное хранилище ' +
|
||||
'с биткоинами в безопасности.',
|
||||
'с Bitcoin в безопасности.',
|
||||
help2:
|
||||
'Новое хранилище будет полностью функциональным и вы даже можете хранить на нем немного биткоинов ' +
|
||||
'Новое хранилище будет полностью функциональным и ты даже можешь хранить на нем немного Bitcoin, ' +
|
||||
'чтобы это выглядело более правдоподобно.',
|
||||
create_fake_storage: 'Создать фальшивое хранилище',
|
||||
go_back: 'Назад',
|
||||
create_password: 'Придумайте пароль',
|
||||
create_password: 'Придумай пароль',
|
||||
create_password_explanation: 'Пароль для фальшивого хранилища не должен быть таким же как основной пароль',
|
||||
password_should_not_match: 'Пароль для фальшивого хранилища не должен быть таким же как основной пароль',
|
||||
retype_password: 'Наберите пароль повторно',
|
||||
passwords_do_not_match: 'Пароли не совпадают, попробуйте еще раз',
|
||||
success: 'Операция успешна',
|
||||
retype_password: 'Набери пароль повторно',
|
||||
passwords_do_not_match: 'Пароли не совпадают, попробуй еще раз',
|
||||
success: 'Готово',
|
||||
},
|
||||
lnd: {
|
||||
title: 'мои средства',
|
||||
choose_source_wallet: 'Выберите с какого кошелька',
|
||||
title: 'Мои средства',
|
||||
choose_source_wallet: 'Выбери с какого кошелька',
|
||||
refill_lnd_balance: 'Пополнить баланс Lightning кошелька',
|
||||
refill: 'Пополнить',
|
||||
withdraw: 'Вывести',
|
||||
expired: 'Expired',
|
||||
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
|
||||
expired: 'Истекший',
|
||||
sameWalletAsInvoiceError: 'Ты не можешь оплатить счет тем же кошельком, который ты использовал для его создания.',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -11,6 +11,8 @@ export const FiatUnit = Object.freeze({
|
|||
INR: { endPointKey: 'INR', symbol: '₹', locale: 'hi-HN' },
|
||||
JPY: { endPointKey: 'JPY', symbol: '¥', locale: 'ja-JP' },
|
||||
MXN: { endPointKey: 'MXN', symbol: '$', locale: 'es-MX' },
|
||||
MYR: { endPointKey: 'MYR', symbol: 'RM', locale: 'ms-MY' },
|
||||
NZD: { endPointKey: 'NZD', symbol: '$', locale: 'en-NZ' },
|
||||
PLN: { endPointKey: 'PLN', symbol: 'zł', locale: 'pl-PL' },
|
||||
RUB: { endPointKey: 'RUB', symbol: '₽', locale: 'ru-RU' },
|
||||
SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' },
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import Frisbee from 'frisbee';
|
||||
|
||||
export class NetworkTransactionFee {
|
||||
static StorageKey = 'NetworkTransactionFee';
|
||||
|
||||
constructor(fastestFee = 1, halfHourFee = 1, hourFee = 1) {
|
||||
this.fastestFee = fastestFee;
|
||||
this.halfHourFee = halfHourFee;
|
||||
|
|
4520
package-lock.json
generated
4520
package-lock.json
generated
File diff suppressed because it is too large
Load diff
35
package.json
35
package.json
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "BlueWallet",
|
||||
"version": "3.7.0",
|
||||
"version": "3.7.2",
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^24.0.0",
|
||||
"eslint": "^5.12.1",
|
||||
"eslint": "^5.13.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-import": "^2.15.0",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
|
@ -19,10 +19,11 @@
|
|||
"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",
|
||||
"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",
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"postinstall": "./node_modules/.bin/rn-nodeify --install buffer,events,process,stream,util,inherits,fs,path --hack",
|
||||
"postinstall": "./node_modules/.bin/rn-nodeify --install buffer,events,process,stream,util,inherits,fs,path --hack; npm run releasenotes2json",
|
||||
"test": "npm run unit && npm run jest && npm run lint",
|
||||
"jest": "node node_modules/jest/bin/jest.js",
|
||||
"lint": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ --fix",
|
||||
|
@ -45,31 +46,32 @@
|
|||
"buffer-reverse": "^1.0.1",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"dayjs": "^1.8.0",
|
||||
"eslint-config-prettier": "^3.6.0",
|
||||
"electrum-client": "git+https://github.com/Overtorment/node-electrum-client.git",
|
||||
"eslint-config-prettier": "^4.0.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-config-standard-react": "^7.0.2",
|
||||
"eslint-plugin-prettier": "^3.0.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"frisbee": "^1.6.4",
|
||||
"frisbee": "^2.0.5",
|
||||
"intl": "^1.2.5",
|
||||
"mocha": "^5.2.0",
|
||||
"node-libs-react-native": "^1.0.1",
|
||||
"path-browserify": "0.0.0",
|
||||
"prettier": "^1.16.1",
|
||||
"path-browserify": "^1.0.0",
|
||||
"prettier": "^1.16.3",
|
||||
"process": "^0.11.10",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.7.0",
|
||||
"react-localization": "^1.0.10",
|
||||
"react-native": "^0.58.1",
|
||||
"react-native-camera": "^1.9.2",
|
||||
"react-native-camera": "^1.10.0",
|
||||
"react-native-custom-qr-codes": "^2.0.0",
|
||||
"react-native-device-info": "^0.25.1",
|
||||
"react-native-device-info": "^0.26.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.0.15",
|
||||
"react-native-google-analytics-bridge": "^7.0.0",
|
||||
"react-native-haptic-feedback": "^1.4.2",
|
||||
"react-native-haptic-feedback": "^1.5.0",
|
||||
"react-native-image-picker": "^0.28.0",
|
||||
"react-native-level-fs": "^3.0.1",
|
||||
"react-native-linear-gradient": "^2.5.3",
|
||||
|
@ -79,19 +81,20 @@
|
|||
"react-native-qrcode": "^0.2.7",
|
||||
"react-native-randombytes": "^3.5.2",
|
||||
"react-native-rate": "^1.1.6",
|
||||
"react-native-sentry": "^0.40.2",
|
||||
"react-native-sentry": "^0.41.1",
|
||||
"react-native-snap-carousel": "^3.7.5",
|
||||
"react-native-sortable-list": "0.0.22",
|
||||
"react-native-svg": "^9.0.4",
|
||||
"react-native-svg": "^9.1.1",
|
||||
"react-native-tcp": "^3.3.0",
|
||||
"react-native-vector-icons": "^6.2.0",
|
||||
"react-native-webview": "^3.2.1",
|
||||
"react-native-webview": "4.1.0",
|
||||
"react-native-wkwebview-reborn": "^2.0.0",
|
||||
"react-navigation": "^3.0.9",
|
||||
"react-navigation": "^3.1.2",
|
||||
"react-test-render": "^1.1.1",
|
||||
"readable-stream": "^1.1.14",
|
||||
"readable-stream": "^3.1.1",
|
||||
"request-promise-native": "^1.0.5",
|
||||
"secure-random": "^1.1.1",
|
||||
"stream-browserify": "^1.0.0",
|
||||
"stream-browserify": "^2.0.2",
|
||||
"util": "^0.11.1",
|
||||
"wif": "^2.0.1"
|
||||
},
|
||||
|
|
|
@ -495,7 +495,7 @@ export default class Browser extends Component {
|
|||
|
||||
Browser.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
getParam: PropTypes.function,
|
||||
getParam: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -103,6 +103,7 @@ export default class LNDCreateInvoice extends Component {
|
|||
numberOfLines={1}
|
||||
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
|
||||
editable={!this.state.isLoading}
|
||||
onSubmitEditing={Keyboard.dismiss}
|
||||
/>
|
||||
</View>
|
||||
{this.renderCreateButton()}
|
||||
|
@ -116,7 +117,7 @@ export default class LNDCreateInvoice extends Component {
|
|||
|
||||
LNDCreateInvoice.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
}),
|
||||
|
|
|
@ -82,8 +82,8 @@ export default class LNDViewAdditionalInvoiceInformation extends Component {
|
|||
|
||||
LNDViewAdditionalInvoiceInformation.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
getParam: PropTypes.function,
|
||||
dismiss: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -236,9 +236,9 @@ export default class LNDViewInvoice extends Component {
|
|||
|
||||
LNDViewInvoice.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
navigate: PropTypes.function,
|
||||
getParam: PropTypes.function,
|
||||
dismiss: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -83,10 +83,10 @@ export default class ManageFunds extends Component {
|
|||
|
||||
ManageFunds.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
dismiss: PropTypes.function,
|
||||
navigate: PropTypes.function,
|
||||
getParam: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
fromSecret: PropTypes.string,
|
||||
|
|
|
@ -72,12 +72,6 @@ export default class ScanLndInvoice extends React.Component {
|
|||
|
||||
processInvoice = data => {
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
if (this.ignoreRead) return;
|
||||
this.ignoreRead = true;
|
||||
setTimeout(() => {
|
||||
this.ignoreRead = false;
|
||||
}, 6000);
|
||||
|
||||
if (!this.state.fromWallet) {
|
||||
alert('Before paying a Lightning invoice, you must first add a Lightning wallet.');
|
||||
return this.props.navigation.goBack();
|
||||
|
@ -149,23 +143,20 @@ export default class ScanLndInvoice extends React.Component {
|
|||
return alert(loc.lnd.sameWalletAsInvoiceError);
|
||||
}
|
||||
|
||||
let start = +new Date();
|
||||
let end;
|
||||
try {
|
||||
await fromWallet.payInvoice(this.state.invoice, this.state.decoded.num_satoshis);
|
||||
end = +new Date();
|
||||
} catch (Err) {
|
||||
console.log(Err.message);
|
||||
this.setState({ isLoading: false });
|
||||
this.props.navigation.goBack();
|
||||
return alert('Error');
|
||||
return alert(Err.message);
|
||||
}
|
||||
|
||||
console.log('payInvoice took', (end - start) / 1000, 'sec');
|
||||
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
|
||||
|
||||
alert('Success');
|
||||
this.props.navigation.goBack();
|
||||
this.props.navigation.navigate('Success', {
|
||||
amount: this.state.decoded.num_satoshis,
|
||||
amountUnit: BitcoinUnit.SATS,
|
||||
invoiceDescription: this.state.decoded.description,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -237,24 +228,26 @@ export default class ScanLndInvoice extends React.Component {
|
|||
)}
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
{this.state.isLoading ? (
|
||||
<View>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : (
|
||||
<BlueButton
|
||||
icon={{
|
||||
name: 'bolt',
|
||||
type: 'font-awesome',
|
||||
color: BlueApp.settings.buttonTextColor,
|
||||
}}
|
||||
title={'Pay'}
|
||||
onPress={() => {
|
||||
this.pay();
|
||||
}}
|
||||
disabled={this.shouldDisablePayButton()}
|
||||
/>
|
||||
)}
|
||||
<BlueCard>
|
||||
{this.state.isLoading ? (
|
||||
<View>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : (
|
||||
<BlueButton
|
||||
icon={{
|
||||
name: 'bolt',
|
||||
type: 'font-awesome',
|
||||
color: BlueApp.settings.buttonTextColor,
|
||||
}}
|
||||
title={'Pay'}
|
||||
onPress={() => {
|
||||
this.pay();
|
||||
}}
|
||||
disabled={this.shouldDisablePayButton()}
|
||||
/>
|
||||
)}
|
||||
</BlueCard>
|
||||
</BlueCard>
|
||||
</SafeBlueArea>
|
||||
</TouchableWithoutFeedback>
|
||||
|
@ -264,10 +257,10 @@ export default class ScanLndInvoice extends React.Component {
|
|||
|
||||
ScanLndInvoice.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
navigate: PropTypes.function,
|
||||
getParam: PropTypes.function,
|
||||
dismiss: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
uri: PropTypes.string,
|
||||
|
|
|
@ -79,7 +79,6 @@ export default class ReceiveDetails extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret);
|
||||
if (this.state.isLoading) {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
|
@ -128,8 +127,8 @@ export default class ReceiveDetails extends Component {
|
|||
|
||||
ReceiveDetails.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
navigate: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
address: PropTypes.string,
|
||||
|
|
|
@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
|
|||
import { SegwitP2SHWallet, LegacyWallet, HDSegwitP2SHWallet } from '../class';
|
||||
let BigNumber = require('bignumber.js');
|
||||
let encryption = require('../encryption');
|
||||
let bitcoin = require('bitcoinjs-lib');
|
||||
let BlueElectrum = require('../BlueElectrum');
|
||||
|
||||
export default class Selftest extends Component {
|
||||
static navigationOptions = () => ({
|
||||
|
@ -42,6 +44,20 @@ export default class Selftest extends Component {
|
|||
|
||||
//
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
let addr4elect = '3GCvDBAktgQQtsbN6x5DYiQCMmgZ9Yk8BK';
|
||||
let electrumBalance = await BlueElectrum.getBalanceByAddress(addr4elect);
|
||||
if (electrumBalance.confirmed !== 51432)
|
||||
throw new Error('BlueElectrum getBalanceByAddress failure, got ' + JSON.stringify(electrumBalance));
|
||||
|
||||
let electrumTxs = await BlueElectrum.getTransactionsByAddress(addr4elect);
|
||||
if (electrumTxs.length !== 1) throw new Error('BlueElectrum getTransactionsByAddress failure, got ' + JSON.stringify(electrumTxs));
|
||||
} else {
|
||||
console.warn('skipping RN-specific test');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let l = new LegacyWallet();
|
||||
l.setSecret('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct');
|
||||
if (l.getAddress() !== '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31') {
|
||||
|
@ -158,7 +174,6 @@ export default class Selftest extends Component {
|
|||
];
|
||||
|
||||
let tx = l.createTx(utxo, 0.001, 0.0001, '1QHf8Gp3wfmFiSdEX4FtrssCGR68diN1cj');
|
||||
let bitcoin = require('bitcoinjs-lib');
|
||||
let txDecoded = bitcoin.Transaction.fromHex(tx);
|
||||
let txid = txDecoded.getId();
|
||||
|
||||
|
|
|
@ -105,15 +105,7 @@ export default class Confirm extends Component {
|
|||
<Text style={styles.transactionDetailsTitle}>{loc.send.create.to}</Text>
|
||||
<Text style={styles.transactionDetailsSubtitle}>{this.state.address}</Text>
|
||||
<BlueSpacing40 />
|
||||
{this.state.isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<BlueButton
|
||||
onPress={() => this.broadcast()}
|
||||
title={loc.send.confirm.sendNow}
|
||||
style={{ maxWidth: 263, paddingHorizontal: 56 }}
|
||||
/>
|
||||
)}
|
||||
{this.state.isLoading ? <ActivityIndicator /> : <BlueButton onPress={() => this.broadcast()} title={loc.send.confirm.sendNow} />}
|
||||
<TouchableOpacity
|
||||
style={{ marginVertical: 24 }}
|
||||
onPress={() =>
|
||||
|
@ -154,10 +146,10 @@ const styles = StyleSheet.create({
|
|||
|
||||
Confirm.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
getParam: PropTypes.function,
|
||||
navigate: PropTypes.function,
|
||||
dismiss: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
amount: PropTypes.string,
|
||||
|
|
|
@ -108,10 +108,10 @@ const styles = StyleSheet.create({
|
|||
|
||||
SendCreate.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
getParam: PropTypes.function,
|
||||
navigate: PropTypes.function,
|
||||
dismiss: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
amount: PropTypes.string,
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
StyleSheet,
|
||||
Platform,
|
||||
Slider,
|
||||
AsyncStorage,
|
||||
Text,
|
||||
} from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
|
@ -71,13 +72,14 @@ export default class SendDetails extends Component {
|
|||
fromAddress,
|
||||
fromWallet,
|
||||
fromSecret,
|
||||
isLoading: true,
|
||||
isLoading: false,
|
||||
address,
|
||||
memo,
|
||||
fee: 1,
|
||||
networkTransactionFees: new NetworkTransactionFee(1, 1, 1),
|
||||
feeSliderValue: 1,
|
||||
bip70TransactionExpiration: null,
|
||||
renderWalletSelectionButtonHidden: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -91,7 +93,7 @@ export default class SendDetails extends Component {
|
|||
const dataWithoutSchema = data.replace('bitcoin:', '');
|
||||
if (btcAddressRx.test(dataWithoutSchema) || dataWithoutSchema.indexOf('bc1') === 0) {
|
||||
this.setState({
|
||||
address: data,
|
||||
address: dataWithoutSchema,
|
||||
bip70TransactionExpiration: null,
|
||||
isLoading: false,
|
||||
});
|
||||
|
@ -126,20 +128,27 @@ export default class SendDetails extends Component {
|
|||
};
|
||||
|
||||
async componentDidMount() {
|
||||
let recommendedFees = await NetworkTransactionFees.recommendedFees().catch(response => {
|
||||
this.setState({
|
||||
fee: response.halfHourFee,
|
||||
networkTransactionFees: response,
|
||||
feeSliderValue: response.halfHourFee,
|
||||
isLoading: false,
|
||||
});
|
||||
});
|
||||
if (recommendedFees) {
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
||||
try {
|
||||
const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey));
|
||||
|
||||
if (cachedNetworkTransactionFees && cachedNetworkTransactionFees.hasOwnProperty('halfHourFee')) {
|
||||
this.setState({
|
||||
fee: cachedNetworkTransactionFees.halfHourFee,
|
||||
networkTransactionFees: cachedNetworkTransactionFees,
|
||||
feeSliderValue: cachedNetworkTransactionFees.halfHourFee,
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
let recommendedFees = await NetworkTransactionFees.recommendedFees();
|
||||
if (recommendedFees && recommendedFees.hasOwnProperty('halfHourFee')) {
|
||||
await AsyncStorage.setItem(NetworkTransactionFee.StorageKey, JSON.stringify(recommendedFees));
|
||||
this.setState({
|
||||
fee: recommendedFees.halfHourFee,
|
||||
networkTransactionFees: recommendedFees,
|
||||
feeSliderValue: recommendedFees.halfHourFee,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
if (this.props.navigation.state.params.uri) {
|
||||
|
@ -148,16 +157,34 @@ export default class SendDetails extends Component {
|
|||
} else {
|
||||
try {
|
||||
const { address, amount, memo } = this.decodeBitcoinUri(this.props.navigation.getParam('uri'));
|
||||
this.setState({ address, amount, memo });
|
||||
this.setState({ address, amount, memo, isLoading: false });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.setState({ isLoading: false });
|
||||
alert('Error: Unable to decode Bitcoin address');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
} else {
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
}
|
||||
|
||||
_keyboardDidShow = () => {
|
||||
this.setState({ renderWalletSelectionButtonHidden: true });
|
||||
};
|
||||
|
||||
_keyboardDidHide = () => {
|
||||
this.setState({ renderWalletSelectionButtonHidden: false });
|
||||
};
|
||||
|
||||
decodeBitcoinUri(uri) {
|
||||
try {
|
||||
let amount = '';
|
||||
|
@ -382,9 +409,9 @@ export default class SendDetails extends Component {
|
|||
}
|
||||
|
||||
onWalletSelect = wallet => {
|
||||
this.setState({ fromAddress: wallet.getAddress(), fromSecret: wallet.getSecret(), fromWallet: wallet }, () =>
|
||||
this.props.navigation.goBack(null),
|
||||
);
|
||||
this.setState({ fromAddress: wallet.getAddress(), fromSecret: wallet.getSecret(), fromWallet: wallet }, () => {
|
||||
this.props.navigation.pop();
|
||||
});
|
||||
};
|
||||
|
||||
renderFeeSelectionModal = () => {
|
||||
|
@ -392,7 +419,13 @@ export default class SendDetails extends Component {
|
|||
<Modal
|
||||
isVisible={this.state.isFeeSelectionModalVisible}
|
||||
style={styles.bottomModal}
|
||||
onBackdropPress={() => this.setState({ isFeeSelectionModalVisible: false })}
|
||||
onBackdropPress={() => {
|
||||
if (this.state.fee < 1 || this.state.feeSliderValue < 1) {
|
||||
this.setState({ fee: Number(1), feeSliderValue: Number(1) });
|
||||
}
|
||||
Keyboard.dismiss();
|
||||
this.setState({ isFeeSelectionModalVisible: false });
|
||||
}}
|
||||
>
|
||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
|
||||
<View style={styles.modalContent}>
|
||||
|
@ -403,12 +436,14 @@ export default class SendDetails extends Component {
|
|||
this.textInput = ref;
|
||||
}}
|
||||
value={this.state.fee.toString()}
|
||||
onEndEditing={() => {
|
||||
if (this.state.fee < 1 || this.state.feeSliderValue < 1) {
|
||||
this.setState({ fee: Number(1), feeSliderValue: Number(1) });
|
||||
}
|
||||
}}
|
||||
onChangeText={value => {
|
||||
let newValue = value.replace(/\D/g, '');
|
||||
if (newValue.length === 0) {
|
||||
newValue = 1;
|
||||
}
|
||||
this.setState({ fee: newValue, feeSliderValue: newValue });
|
||||
this.setState({ fee: Number(newValue), feeSliderValue: Number(newValue) });
|
||||
}}
|
||||
maxLength={9}
|
||||
editable={!this.state.isLoading}
|
||||
|
@ -466,6 +501,7 @@ export default class SendDetails extends Component {
|
|||
};
|
||||
|
||||
renderWalletSelectionButton = () => {
|
||||
if (this.state.renderWalletSelectionButtonHidden) return;
|
||||
return (
|
||||
<View style={{ marginBottom: 24, alignItems: 'center' }}>
|
||||
{!this.state.isLoading && (
|
||||
|
@ -500,7 +536,6 @@ export default class SendDetails extends Component {
|
|||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
|
||||
<View style={{ flex: 1, justifyContent: 'space-between' }}>
|
||||
|
@ -556,6 +591,7 @@ export default class SendDetails extends Component {
|
|||
numberOfLines={1}
|
||||
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
|
||||
editable={!this.state.isLoading}
|
||||
onSubmitEditing={Keyboard.dismiss}
|
||||
/>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
|
@ -620,7 +656,7 @@ const styles = StyleSheet.create({
|
|||
|
||||
SendDetails.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
pop: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
|
|
|
@ -16,10 +16,12 @@ export default class CameraExample extends React.Component {
|
|||
};
|
||||
|
||||
onBarCodeScanned(ret) {
|
||||
console.warn(ret);
|
||||
const onBarScanned = this.props.navigation.getParam('onBarScanned');
|
||||
onBarScanned(ret.data);
|
||||
this.props.navigation.goBack(null);
|
||||
if (this.state.isLoading) return;
|
||||
this.setState({ isLoading: true }, () => {
|
||||
const onBarScanned = this.props.navigation.getParam('onBarScanned');
|
||||
this.props.navigation.goBack();
|
||||
onBarScanned(ret.data);
|
||||
});
|
||||
} // end
|
||||
|
||||
componentDidMount() {
|
||||
|
|
|
@ -18,7 +18,9 @@ export default class Success extends Component {
|
|||
|
||||
this.state = {
|
||||
amount: props.navigation.getParam('amount'),
|
||||
fee: props.navigation.getParam('fee'),
|
||||
fee: props.navigation.getParam('fee') || 0,
|
||||
amountUnit: props.navigation.getParam('amountUnit') || BitcoinUnit.BTC,
|
||||
invoiceDescription: props.navigation.getParam('invoiceDescription') || '',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -51,21 +53,38 @@ export default class Success extends Component {
|
|||
alignSelf: 'flex-end',
|
||||
}}
|
||||
>
|
||||
{' ' + BitcoinUnit.BTC}
|
||||
{' ' + this.state.amountUnit}
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
color: '#37c0a1',
|
||||
fontSize: 14,
|
||||
marginHorizontal: 4,
|
||||
paddingBottom: 6,
|
||||
fontWeight: '500',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
{loc.send.create.fee}: {loc.formatBalance(this.state.fee, BitcoinUnit.SATS)}
|
||||
</Text>
|
||||
{this.state.fee > 0 && (
|
||||
<Text
|
||||
style={{
|
||||
color: '#37c0a1',
|
||||
fontSize: 14,
|
||||
marginHorizontal: 4,
|
||||
paddingBottom: 6,
|
||||
fontWeight: '500',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
{loc.send.create.fee}: {loc.formatBalance(this.state.fee, BitcoinUnit.SATS)}
|
||||
</Text>
|
||||
)}
|
||||
{this.state.fee <= 0 && (
|
||||
<Text
|
||||
numberOfLines={0}
|
||||
style={{
|
||||
color: '#37c0a1',
|
||||
fontSize: 14,
|
||||
marginHorizontal: 4,
|
||||
paddingBottom: 6,
|
||||
fontWeight: '500',
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
>
|
||||
{this.state.invoiceDescription}
|
||||
</Text>
|
||||
)}
|
||||
</BlueCard>
|
||||
<View
|
||||
style={{
|
||||
|
@ -87,7 +106,6 @@ export default class Success extends Component {
|
|||
this.props.navigation.dismiss();
|
||||
}}
|
||||
title={loc.send.success.done}
|
||||
style={{ maxWidth: 263, paddingHorizontal: 56 }}
|
||||
/>
|
||||
</BlueCard>
|
||||
</SafeBlueArea>
|
||||
|
@ -97,10 +115,10 @@ export default class Success extends Component {
|
|||
|
||||
Success.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
getParam: PropTypes.function,
|
||||
navigate: PropTypes.function,
|
||||
dismiss: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
getParam: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
amount: PropTypes.string,
|
||||
|
|
|
@ -127,6 +127,14 @@ export default class About extends Component {
|
|||
<BlueText h4>* bignumber.js</BlueText>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueButton
|
||||
onPress={() => {
|
||||
this.props.navigation.navigate('ReleaseNotes');
|
||||
}}
|
||||
title="Release notes"
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueButton
|
||||
onPress={() => {
|
||||
this.props.navigation.navigate('Selftest');
|
||||
|
@ -135,8 +143,9 @@ export default class About extends Component {
|
|||
/>
|
||||
<BlueTextCentered />
|
||||
<BlueTextCentered>
|
||||
{DeviceInfo.getApplicationName()} ver {DeviceInfo.getVersion()} (build number {DeviceInfo.getBuildNumber()})
|
||||
{DeviceInfo.getApplicationName()} ver {DeviceInfo.getVersion()} (build {DeviceInfo.getBuildNumber()})
|
||||
</BlueTextCentered>
|
||||
<BlueTextCentered>{new Date(DeviceInfo.getBuildNumber() * 1000).toGMTString()}</BlueTextCentered>
|
||||
<BlueTextCentered>{DeviceInfo.getBundleId()}</BlueTextCentered>
|
||||
<BlueTextCentered>
|
||||
w, h = {width}, {height}
|
||||
|
|
51
screen/settings/releasenotes.js
Normal file
51
screen/settings/releasenotes.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import React, { Component } from 'react';
|
||||
import { ScrollView } from 'react-native';
|
||||
import { BlueLoading, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle } from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
/** @type {AppStorage} */
|
||||
const notes = require('../../release-notes');
|
||||
|
||||
export default class ReleaseNotes extends Component {
|
||||
static navigationOptions = () => ({
|
||||
...BlueNavigationStyle(),
|
||||
title: 'Release notes',
|
||||
});
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
console.log(notes);
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
notes: notes,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.isLoading) {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
|
||||
<ScrollView>
|
||||
<BlueCard>
|
||||
<BlueText>{this.state.notes}</BlueText>
|
||||
</BlueCard>
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReleaseNotes.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
navigate: PropTypes.func,
|
||||
goBack: PropTypes.func,
|
||||
}),
|
||||
};
|
|
@ -234,7 +234,7 @@ export default class SendCreate extends Component {
|
|||
|
||||
SendCreate.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
|
|
|
@ -191,7 +191,7 @@ export default class RBF extends Component {
|
|||
|
||||
RBF.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
|
|
|
@ -89,22 +89,25 @@ export default class TransactionsDetails extends Component {
|
|||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
|
||||
<BlueHeaderDefaultSub leftText={loc.transactions.details.title} rightComponent={null} />
|
||||
<ScrollView style={{ flex: 1 }}>
|
||||
{(() => {
|
||||
if (this.state.tx.confirmations === 0 && this.state.wallet && this.state.wallet.allowRBF()) {
|
||||
return (
|
||||
<BlueButton
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('RBF', {
|
||||
txid: this.state.tx.hash,
|
||||
})
|
||||
}
|
||||
title="Replace-By-Fee (RBF)"
|
||||
/>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
|
||||
<BlueCard>
|
||||
{(() => {
|
||||
if (this.state.tx.confirmations === 0 && this.state.wallet && this.state.wallet.allowRBF()) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BlueButton
|
||||
onPress={() =>
|
||||
this.props.navigation.navigate('RBF', {
|
||||
txid: this.state.tx.hash,
|
||||
})
|
||||
}
|
||||
title="Replace-By-Fee (RBF)"
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
|
||||
{(() => {
|
||||
if (BlueApp.tx_metadata[this.state.tx.hash]) {
|
||||
if (BlueApp.tx_metadata[this.state.tx.hash]['memo']) {
|
||||
|
@ -176,14 +179,14 @@ export default class TransactionsDetails extends Component {
|
|||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{this.state.tx.hasOwnProperty('block_height') && this.state.block_height > 0 && (
|
||||
{this.state.tx.hasOwnProperty('block_height') && this.state.tx.block_height > 0 && (
|
||||
<React.Fragment>
|
||||
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Block Height</BlueText>
|
||||
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.block_height}</BlueText>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{this.state.tx.hasOwnProperty('confirmations') && (
|
||||
{this.state.tx.hasOwnProperty('confirmations') && this.state.tx.confirmations > 0 && (
|
||||
<React.Fragment>
|
||||
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Confirmations</BlueText>
|
||||
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.confirmations}</BlueText>
|
||||
|
@ -197,7 +200,7 @@ export default class TransactionsDetails extends Component {
|
|||
</React.Fragment>
|
||||
)}
|
||||
|
||||
{this.state.tx.hasOwnProperty('outputs') && (
|
||||
{this.state.tx.hasOwnProperty('outputs') && this.state.tx.outputs.length > 0 && (
|
||||
<React.Fragment>
|
||||
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Outputs</BlueText>
|
||||
<BlueText style={{ marginBottom: 26, color: 'grey' }}>{this.state.tx.outputs.length}</BlueText>
|
||||
|
@ -212,7 +215,7 @@ export default class TransactionsDetails extends Component {
|
|||
|
||||
TransactionsDetails.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
|
|
|
@ -72,7 +72,7 @@ export default class WalletsAdd extends Component {
|
|||
|
||||
return (
|
||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false} style={{ flex: 1 }}>
|
||||
<BlueCard>
|
||||
<BlueFormLabel>{loc.wallets.add.wallet_name}</BlueFormLabel>
|
||||
<View
|
||||
|
|
|
@ -67,7 +67,6 @@ export default class BuyBitcoin extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
console.log('render() receive/details, address,secret=', this.state.address, ',', this.state.secret);
|
||||
if (this.state.isLoading) {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
|
@ -107,7 +106,7 @@ export default class BuyBitcoin extends Component {
|
|||
|
||||
BuyBitcoin.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.function,
|
||||
goBack: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
address: PropTypes.string,
|
||||
|
|
|
@ -215,38 +215,39 @@ export default class WalletsImport extends Component {
|
|||
this.setLabel(text);
|
||||
}}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<BlueButton
|
||||
disabled={!this.state.label}
|
||||
title={loc.wallets.import.do_import}
|
||||
buttonStyle={{
|
||||
width: width / 1.5,
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (!this.state.label) {
|
||||
return;
|
||||
}
|
||||
this.setState({ isLoading: true });
|
||||
setTimeout(async () => {
|
||||
await this.importMnemonic(this.state.label.trim());
|
||||
this.setState({ isLoading: false });
|
||||
}, 1);
|
||||
}}
|
||||
/>
|
||||
<BlueButtonLink
|
||||
title={loc.wallets.import.scan_qr}
|
||||
onPress={() => {
|
||||
this.props.navigation.navigate('ScanQrWif');
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<BlueButton
|
||||
disabled={!this.state.label}
|
||||
title={loc.wallets.import.do_import}
|
||||
buttonStyle={{
|
||||
width: width / 1.5,
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (!this.state.label) {
|
||||
return;
|
||||
}
|
||||
this.setState({ isLoading: true });
|
||||
setTimeout(async () => {
|
||||
await this.importMnemonic(this.state.label.trim());
|
||||
this.setState({ isLoading: false });
|
||||
}, 1);
|
||||
}}
|
||||
/>
|
||||
<BlueButtonLink
|
||||
title={loc.wallets.import.scan_qr}
|
||||
onPress={() => {
|
||||
this.props.navigation.navigate('ScanQrWif');
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { View, TouchableOpacity, Text, FlatList, RefreshControl, ScrollView } from 'react-native';
|
||||
import {
|
||||
BlueTransactionOnchainIcon,
|
||||
BlueLoading,
|
||||
SafeBlueArea,
|
||||
WalletsCarousel,
|
||||
BlueTransactionIncommingIcon,
|
||||
BlueTransactionOutgoingIcon,
|
||||
BlueTransactionPendingIcon,
|
||||
BlueTransactionOffchainIcon,
|
||||
BlueTransactionExpiredIcon,
|
||||
BlueList,
|
||||
BlueListItem,
|
||||
BlueHeaderDefaultMain,
|
||||
BlueTransactionOffchainIncomingIcon,
|
||||
} from '../../BlueComponents';
|
||||
import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueListTransactionItem } from '../../BlueComponents';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { NavigationEvents } from 'react-navigation';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
|
@ -82,7 +68,10 @@ export default class WalletsList extends Component {
|
|||
// more responsive
|
||||
let noErr = true;
|
||||
try {
|
||||
let balanceStart = +new Date();
|
||||
await BlueApp.fetchWalletBalances(that.lastSnappedTo || 0);
|
||||
let balanceEnd = +new Date();
|
||||
console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
let start = +new Date();
|
||||
await BlueApp.fetchWalletTransactions(that.lastSnappedTo || 0);
|
||||
let end = +new Date();
|
||||
|
@ -229,60 +218,9 @@ export default class WalletsList extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
rowTitle = item => {
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (isNaN(item.value)) {
|
||||
item.value = '0';
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, item.walletPreferredBalanceUnit, true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, item.walletPreferredBalanceUnit, true).toString();
|
||||
} else {
|
||||
return loc.lnd.expired;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, item.walletPreferredBalanceUnit, true).toString();
|
||||
}
|
||||
};
|
||||
|
||||
rowTitleStyle = item => {
|
||||
let color = '#37c0a1';
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
color = '#37c0a1';
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
color = '#37c0a1';
|
||||
} else {
|
||||
color = '#FF0000';
|
||||
}
|
||||
}
|
||||
} else if (item.value / 100000000 < 0) {
|
||||
color = BlueApp.settings.foregroundColor;
|
||||
}
|
||||
|
||||
return {
|
||||
fontWeight: '600',
|
||||
fontSize: 16,
|
||||
color: color,
|
||||
};
|
||||
};
|
||||
_renderItem = data => <BlueListTransactionItem item={data.item} itemPriceUnit={data.item.walletPreferredBalanceUnit} />;
|
||||
|
||||
render() {
|
||||
const { navigate } = this.props.navigation;
|
||||
|
||||
if (this.state.isLoading) {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
|
@ -335,117 +273,7 @@ export default class WalletsList extends Component {
|
|||
data={this.state.dataSource}
|
||||
extraData={this.state.dataSource}
|
||||
keyExtractor={this._keyExtractor}
|
||||
renderItem={rowData => {
|
||||
return (
|
||||
<BlueListItem
|
||||
avatar={(() => {
|
||||
// is it lightning refill tx?
|
||||
if (rowData.item.category === 'receive' && rowData.item.confirmations < 3) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (rowData.item.type && rowData.item.type === 'bitcoind_tx') {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOnchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (rowData.item.type === 'paid_invoice') {
|
||||
// is it lightning offchain payment?
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (rowData.item.type === 'user_invoice' || rowData.item.type === 'payment_request') {
|
||||
if (!rowData.item.ispaid) {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = rowData.item.timestamp + rowData.item.expire_time;
|
||||
if (invoiceExpiration < now) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionExpiredIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rowData.item.confirmations) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
} else if (rowData.item.value < 0) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOutgoingIcon />
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionIncommingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
title={loc.transactionTimeToReadable(rowData.item.received)}
|
||||
subtitle={
|
||||
(rowData.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + rowData.item.confirmations + ' ' : '') +
|
||||
this.txMemo(rowData.item.hash) +
|
||||
(rowData.item.memo || '')
|
||||
}
|
||||
onPress={() => {
|
||||
if (rowData.item.hash) {
|
||||
navigate('TransactionDetails', {
|
||||
hash: rowData.item.hash,
|
||||
});
|
||||
} else if (
|
||||
rowData.item.type === 'user_invoice' ||
|
||||
rowData.item.type === 'payment_request' ||
|
||||
rowData.item.type === 'paid_invoice'
|
||||
) {
|
||||
const lightningWallet = this.state.wallets.filter(wallet => {
|
||||
if (typeof wallet === 'object') {
|
||||
if (wallet.hasOwnProperty('secret')) {
|
||||
return wallet.getSecret() === rowData.item.fromWallet;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.props.navigation.navigate('LNDViewInvoice', {
|
||||
invoice: rowData.item,
|
||||
fromWallet: lightningWallet[0],
|
||||
isModal: false,
|
||||
});
|
||||
}
|
||||
}}
|
||||
badge={{
|
||||
value: 3,
|
||||
textStyle: { color: 'orange' },
|
||||
containerStyle: { marginTop: 0 },
|
||||
}}
|
||||
hideChevron
|
||||
rightTitle={this.rowTitle(rowData.item)}
|
||||
rightTitleStyle={this.rowTitleStyle(rowData.item)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
renderItem={this._renderItem}
|
||||
/>
|
||||
</BlueList>
|
||||
</ScrollView>
|
||||
|
|
|
@ -28,6 +28,7 @@ export default class ScanQrWif extends React.Component {
|
|||
};
|
||||
|
||||
async onBarCodeScanned(ret) {
|
||||
this.setState({ isLoading: true });
|
||||
if (+new Date() - this.lastTimeIveBeenHere < 6000) {
|
||||
this.lastTimeIveBeenHere = +new Date();
|
||||
return;
|
||||
|
@ -57,16 +58,17 @@ export default class ScanQrWif extends React.Component {
|
|||
ret.data = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed);
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
this.setState({ message: false });
|
||||
this.setState({ message: false, isLoading: false });
|
||||
return alert(loc.wallets.scanQrWif.bad_password);
|
||||
}
|
||||
|
||||
this.setState({ message: false });
|
||||
this.setState({ message: false, isLoading: false });
|
||||
}
|
||||
|
||||
for (let w of BlueApp.wallets) {
|
||||
if (w.getSecret() === ret.data) {
|
||||
// lookig for duplicates
|
||||
this.setState({ isLoading: false });
|
||||
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
|
||||
}
|
||||
}
|
||||
|
@ -78,10 +80,10 @@ export default class ScanQrWif extends React.Component {
|
|||
for (let w of BlueApp.wallets) {
|
||||
if (w.getSecret() === hd.getSecret()) {
|
||||
// lookig for duplicates
|
||||
this.setState({ isLoading: false });
|
||||
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
|
||||
}
|
||||
}
|
||||
this.setState({ isLoading: true });
|
||||
await hd.fetchTransactions();
|
||||
if (hd.getTransactions().length !== 0) {
|
||||
await hd.fetchBalance();
|
||||
|
@ -91,6 +93,7 @@ export default class ScanQrWif extends React.Component {
|
|||
alert(loc.wallets.import.success);
|
||||
this.props.navigation.popToTop();
|
||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +106,7 @@ export default class ScanQrWif extends React.Component {
|
|||
for (let w of BlueApp.wallets) {
|
||||
if (w.getSecret() === hd.getSecret()) {
|
||||
// lookig for duplicates
|
||||
this.setState({ isLoading: false });
|
||||
return alert(loc.wallets.scanQrWif.wallet_already_exists); // duplicate, not adding
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +119,7 @@ export default class ScanQrWif extends React.Component {
|
|||
alert(loc.wallets.import.success);
|
||||
this.props.navigation.popToTop();
|
||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
// nope
|
||||
|
@ -130,6 +135,7 @@ export default class ScanQrWif extends React.Component {
|
|||
await lnd.fetchBalance();
|
||||
} catch (Err) {
|
||||
console.log(Err);
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -138,6 +144,7 @@ export default class ScanQrWif extends React.Component {
|
|||
this.props.navigation.popToTop();
|
||||
alert(loc.wallets.import.success);
|
||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
// nope
|
||||
|
@ -165,6 +172,7 @@ export default class ScanQrWif extends React.Component {
|
|||
await watchOnly.fetchTransactions();
|
||||
await BlueApp.saveToDisk();
|
||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
// nope
|
||||
|
@ -176,6 +184,7 @@ export default class ScanQrWif extends React.Component {
|
|||
|
||||
if (newWallet.getAddress() === false || newLegacyWallet.getAddress() === false) {
|
||||
alert(loc.wallets.scanQrWif.bad_wif);
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -196,7 +205,7 @@ export default class ScanQrWif extends React.Component {
|
|||
alert(loc.wallets.scanQrWif.imported_wif + ret.data + loc.wallets.scanQrWif.with_address + newWallet.getAddress());
|
||||
}
|
||||
await BlueApp.saveToDisk();
|
||||
this.props.navigation.popToTop();
|
||||
this.props.navigation.dismiss();
|
||||
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
|
||||
} // end
|
||||
|
||||
|
@ -210,7 +219,7 @@ export default class ScanQrWif extends React.Component {
|
|||
render() {
|
||||
if (this.state.isLoading) {
|
||||
return (
|
||||
<View style={{ flex: 1, paddingTop: 20 }}>
|
||||
<View style={{ flex: 1, paddingTop: 20, justifyContent: 'center', alignContent: 'center' }}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
);
|
||||
|
@ -275,6 +284,7 @@ ScanQrWif.propTypes = {
|
|||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
popToTop: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -3,20 +3,7 @@ import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity, StatusBa
|
|||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavigationEvents } from 'react-navigation';
|
||||
import {
|
||||
BlueText,
|
||||
BlueTransactionOnchainIcon,
|
||||
ManageFundsBigButton,
|
||||
BlueTransactionExpiredIcon,
|
||||
BlueTransactionIncommingIcon,
|
||||
BlueTransactionOutgoingIcon,
|
||||
BlueTransactionPendingIcon,
|
||||
BlueTransactionOffchainIcon,
|
||||
BlueSendButtonIcon,
|
||||
BlueReceiveButtonIcon,
|
||||
BlueListItem,
|
||||
BlueTransactionOffchainIncomingIcon,
|
||||
} from '../../BlueComponents';
|
||||
import { BlueText, ManageFundsBigButton, BlueSendButtonIcon, BlueReceiveButtonIcon, BlueTransactionListItem } from '../../BlueComponents';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { LightningCustodianWallet } from '../../class';
|
||||
|
@ -150,9 +137,12 @@ export default class WalletTransactions extends Component {
|
|||
try {
|
||||
/** @type {LegacyWallet} */
|
||||
let wallet = that.state.wallet;
|
||||
let balanceStart = +new Date();
|
||||
const oldBalance = wallet.getBalance();
|
||||
await wallet.fetchBalance();
|
||||
if (oldBalance !== wallet.getBalance()) smthChanged = true;
|
||||
let balanceEnd = +new Date();
|
||||
console.log(wallet.getLabel(), 'fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
let start = +new Date();
|
||||
const oldTxLen = wallet.getTransactions().length;
|
||||
await wallet.fetchTransactions();
|
||||
|
@ -269,13 +259,6 @@ export default class WalletTransactions extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
txMemo(hash) {
|
||||
if (BlueApp.tx_metadata[hash] && BlueApp.tx_metadata[hash]['memo']) {
|
||||
return BlueApp.tx_metadata[hash]['memo'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
_keyExtractor = (_item, index) => index.toString();
|
||||
|
||||
renderListHeaderComponent = () => {
|
||||
|
@ -305,55 +288,8 @@ export default class WalletTransactions extends Component {
|
|||
this.onWillBlur();
|
||||
}
|
||||
|
||||
rowTitle = item => {
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (isNaN(item.value)) {
|
||||
item.value = 0;
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit(), true).toString();
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit(), true).toString();
|
||||
} else {
|
||||
return loc.lnd.expired;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit(), true).toString();
|
||||
}
|
||||
};
|
||||
|
||||
rowTitleStyle = item => {
|
||||
let color = '#37c0a1';
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = item.timestamp + item.expire_time;
|
||||
|
||||
if (invoiceExpiration > now) {
|
||||
color = '#37c0a1';
|
||||
} else if (invoiceExpiration < now) {
|
||||
if (item.ispaid) {
|
||||
color = '#37c0a1';
|
||||
} else {
|
||||
color = '#FF0000';
|
||||
}
|
||||
}
|
||||
} else if (item.value / 100000000 < 0) {
|
||||
color = BlueApp.settings.foregroundColor;
|
||||
}
|
||||
|
||||
return {
|
||||
fontWeight: '600',
|
||||
fontSize: 16,
|
||||
color: color,
|
||||
};
|
||||
renderItem = item => {
|
||||
return <BlueTransactionListItem item={item.item} itemPriceUnit={this.state.wallet.getPreferredBalanceUnit()} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -413,8 +349,9 @@ export default class WalletTransactions extends Component {
|
|||
<FlatList
|
||||
ListHeaderComponent={this.renderListHeaderComponent}
|
||||
ListEmptyComponent={
|
||||
<View style={{ top: 50, minHeight: 200 }}>
|
||||
<View style={{ top: 50, minHeight: 200, paddingHorizontal: 16 }}>
|
||||
<Text
|
||||
numberOfLines={0}
|
||||
style={{
|
||||
fontSize: 18,
|
||||
color: '#9aa0aa',
|
||||
|
@ -422,7 +359,7 @@ export default class WalletTransactions extends Component {
|
|||
}}
|
||||
>
|
||||
{(this.isLightning() &&
|
||||
'Lightning wallet should be used for your daily\ntransactions. Fees are unfairly cheap and\nspeed is blazing fast.') ||
|
||||
'Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.') ||
|
||||
loc.wallets.list.empty_txs1}
|
||||
</Text>
|
||||
<Text
|
||||
|
@ -432,7 +369,7 @@ export default class WalletTransactions extends Component {
|
|||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
{(this.isLightning() && '\nTo start using it tap on "manage funds"\nand topup your balance') ||
|
||||
{(this.isLightning() && '\nTo start using it tap on "manage funds" and topup your balance') ||
|
||||
loc.wallets.list.empty_txs2}
|
||||
</Text>
|
||||
|
||||
|
@ -462,110 +399,8 @@ export default class WalletTransactions extends Component {
|
|||
refreshControl={<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={this.state.isTransactionsLoading} />}
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this._keyExtractor}
|
||||
renderItem={rowData => {
|
||||
return (
|
||||
<BlueListItem
|
||||
avatar={(() => {
|
||||
// is it lightning refill tx?
|
||||
if (rowData.item.category === 'receive' && rowData.item.confirmations < 3) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (rowData.item.type && rowData.item.type === 'bitcoind_tx') {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOnchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (rowData.item.type === 'paid_invoice') {
|
||||
// is it lightning offchain payment?
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (rowData.item.type === 'user_invoice' || rowData.item.type === 'payment_request') {
|
||||
if (!rowData.item.ispaid) {
|
||||
const currentDate = new Date();
|
||||
const now = (currentDate.getTime() / 1000) | 0;
|
||||
const invoiceExpiration = rowData.item.timestamp + rowData.item.expire_time;
|
||||
if (invoiceExpiration < now) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionExpiredIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOffchainIncomingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rowData.item.confirmations) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionPendingIcon />
|
||||
</View>
|
||||
);
|
||||
} else if (rowData.item.value < 0) {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionOutgoingIcon />
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={{ width: 25 }}>
|
||||
<BlueTransactionIncommingIcon />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
title={loc.transactionTimeToReadable(rowData.item.received)}
|
||||
subtitle={
|
||||
(rowData.item.confirmations < 7 ? loc.transactions.list.conf + ': ' + rowData.item.confirmations + ' ' : '') +
|
||||
this.txMemo(rowData.item.hash) +
|
||||
(rowData.item.memo || '')
|
||||
}
|
||||
onPress={() => {
|
||||
if (rowData.item.hash) {
|
||||
navigate('TransactionDetails', {
|
||||
hash: rowData.item.hash,
|
||||
});
|
||||
} else if (
|
||||
rowData.item.type === 'user_invoice' ||
|
||||
rowData.item.type === 'payment_request' ||
|
||||
rowData.item.type === 'paid_invoice'
|
||||
) {
|
||||
this.props.navigation.navigate('LNDViewInvoice', {
|
||||
invoice: rowData.item,
|
||||
fromWallet: this.state.wallet,
|
||||
isModal: false,
|
||||
});
|
||||
}
|
||||
}}
|
||||
badge={{
|
||||
value: 3,
|
||||
textStyle: { color: 'orange' },
|
||||
containerStyle: { marginTop: 0 },
|
||||
}}
|
||||
hideChevron
|
||||
rightTitle={this.rowTitle(rowData.item)}
|
||||
rightTitleStyle={this.rowTitleStyle(rowData.item)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
initialNumToRender={10}
|
||||
renderItem={this.renderItem}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
|
|
1
shim.js
1
shim.js
|
@ -1,4 +1,5 @@
|
|||
/* global __DEV__, localStorage */
|
||||
global.net = require('react-native-tcp');
|
||||
if (typeof __dirname === 'undefined') global.__dirname = '/';
|
||||
if (typeof __filename === 'undefined') global.__filename = '';
|
||||
if (typeof process === 'undefined') {
|
||||
|
|
Loading…
Add table
Reference in a new issue