mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-21 14:34:55 +01:00
Merge branch 'master' into ops
This commit is contained in:
commit
aa8234b1d6
118 changed files with 14305 additions and 6735 deletions
|
@ -71,5 +71,5 @@ untyped-import
|
|||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.113.0
|
||||
^0.122.0
|
||||
|
12
.travis.yml
12
.travis.yml
|
@ -89,15 +89,3 @@ script:
|
|||
- npm run e2e:release-test || npm run e2e:release-test
|
||||
|
||||
after_failure: ./tests/e2e/upload-artifacts.sh
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -r -f node_modules/
|
||||
- curl "${GRAVIS}.clean_gradle_cache.sh" --output ~/.clean_gradle_cache.sh
|
||||
- bash ~/.clean_gradle_cache.sh > /dev/null
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
- node_modules/
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
|
||||
/* global alert */
|
||||
import React, { Component, useState } from 'react';
|
||||
import Ionicons from 'react-native-vector-icons/Ionicons';
|
||||
import React, { Component, useState, useMemo, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon, Input, Text, Header, ListItem } from 'react-native-elements';
|
||||
import { Icon, Input, Text, Header, ListItem, Avatar } from 'react-native-elements';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Animated,
|
||||
Dimensions,
|
||||
FlatList,
|
||||
Image,
|
||||
InputAccessoryView,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
PixelRatio,
|
||||
Platform,
|
||||
PlatformColor,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
Switch,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
TouchableWithoutFeedback,
|
||||
Animated,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
View,
|
||||
KeyboardAvoidingView,
|
||||
UIManager,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
Image,
|
||||
Keyboard,
|
||||
SafeAreaView,
|
||||
InputAccessoryView,
|
||||
Platform,
|
||||
FlatList,
|
||||
TextInput,
|
||||
PixelRatio,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import ActionSheet from './screen/ActionSheet';
|
||||
import { LightningCustodianWallet, PlaceholderWallet } from './class';
|
||||
import { LightningCustodianWallet, MultisigHDWallet, PlaceholderWallet } from './class';
|
||||
import Carousel from 'react-native-snap-carousel';
|
||||
import { BitcoinUnit } from './models/bitcoinUnits';
|
||||
import * as NavigationService from './NavigationService';
|
||||
|
@ -60,57 +62,25 @@ if (aspectRatio > 1.6) {
|
|||
} else {
|
||||
isIpad = true;
|
||||
}
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
Platform.OS === 'android' ? (ActivityIndicator.defaultProps.color = PlatformColor('?attr/colorControlActivated')) : null;
|
||||
|
||||
export class BlueButton extends Component {
|
||||
render() {
|
||||
let backgroundColor = this.props.backgroundColor ? this.props.backgroundColor : BlueCurrentTheme.colors.mainColor;
|
||||
let fontColor = BlueCurrentTheme.colors.buttonTextColor;
|
||||
if (this.props.disabled === true) {
|
||||
backgroundColor = BlueCurrentTheme.colors.buttonDisabledBackgroundColor;
|
||||
fontColor = BlueCurrentTheme.colors.buttonDisabledTextColor;
|
||||
}
|
||||
let buttonWidth = this.props.width ? this.props.width : width / 1.5;
|
||||
if ('noMinWidth' in this.props) {
|
||||
buttonWidth = 0;
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
borderWidth: 0.7,
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: backgroundColor,
|
||||
minHeight: 45,
|
||||
height: 45,
|
||||
maxHeight: 45,
|
||||
borderRadius: 25,
|
||||
minWidth: buttonWidth,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
{...this.props}
|
||||
>
|
||||
<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: fontColor }}>{this.props.title}</Text>}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const BlueButtonHook = props => {
|
||||
export const BlueButton = props => {
|
||||
const { colors } = useTheme();
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor;
|
||||
let fontColor = colors.buttonTextColor;
|
||||
if (props.disabled === true) {
|
||||
backgroundColor = colors.buttonDisabledBackgroundColor;
|
||||
fontColor = colors.buttonDisabledTextColor;
|
||||
}
|
||||
|
||||
let buttonWidth = props.width ? props.width : width / 1.5;
|
||||
if ('noMinWidth' in props) {
|
||||
buttonWidth = 0;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
|
@ -136,6 +106,38 @@ export const BlueButtonHook = props => {
|
|||
);
|
||||
};
|
||||
|
||||
export const BlueButtonHook = props => {
|
||||
const { colors } = useTheme();
|
||||
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor;
|
||||
let fontColor = colors.buttonTextColor;
|
||||
if (props.disabled === true) {
|
||||
backgroundColor = colors.buttonDisabledBackgroundColor;
|
||||
fontColor = colors.buttonDisabledTextColor;
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
borderWidth: 0.7,
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: backgroundColor,
|
||||
minHeight: 45,
|
||||
height: 45,
|
||||
maxHeight: 45,
|
||||
borderRadius: 25,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
|
||||
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
|
||||
{props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor }}>{props.title}</Text>}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export class SecondButton extends Component {
|
||||
render() {
|
||||
let backgroundColor = this.props.backgroundColor ? this.props.backgroundColor : BlueCurrentTheme.colors.buttonBlueBackgroundColor;
|
||||
|
@ -144,10 +146,10 @@ export class SecondButton extends Component {
|
|||
backgroundColor = BlueCurrentTheme.colors.buttonDisabledBackgroundColor;
|
||||
fontColor = BlueCurrentTheme.colors.buttonDisabledTextColor;
|
||||
}
|
||||
let buttonWidth = this.props.width ? this.props.width : width / 1.5;
|
||||
if ('noMinWidth' in this.props) {
|
||||
buttonWidth = 0;
|
||||
}
|
||||
// let buttonWidth = this.props.width ? this.props.width : width / 1.5;
|
||||
// if ('noMinWidth' in this.props) {
|
||||
// buttonWidth = 0;
|
||||
// }
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
|
@ -159,7 +161,6 @@ export class SecondButton extends Component {
|
|||
height: 45,
|
||||
maxHeight: 45,
|
||||
borderRadius: 25,
|
||||
minWidth: buttonWidth,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
|
@ -190,7 +191,7 @@ export const BitcoinButton = props => {
|
|||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<View style={{ marginTop: 16, marginLeft: 16, marginBottom: 16 }}>
|
||||
<View style={{ margin: 16 }}>
|
||||
<Text style={{ color: colors.hdborderColor, fontWeight: 'bold' }}>{loc.wallets.add_bitcoin}</Text>
|
||||
</View>
|
||||
<Image
|
||||
|
@ -218,7 +219,7 @@ export const LightningButton = props => {
|
|||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<View style={{ marginTop: 16, marginLeft: 16, marginBottom: 16 }}>
|
||||
<View style={{ margin: 16 }}>
|
||||
<Text style={{ color: colors.lnborderColor, fontWeight: 'bold' }}>{loc.wallets.add_lightning}</Text>
|
||||
</View>
|
||||
<Image
|
||||
|
@ -339,9 +340,16 @@ export class BlueWalletNavigationHeader extends Component {
|
|||
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
|
||||
>
|
||||
<Image
|
||||
source={
|
||||
(LightningCustodianWallet.type === this.state.wallet.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png')
|
||||
}
|
||||
source={(() => {
|
||||
switch (this.state.wallet.type) {
|
||||
case LightningCustodianWallet.type:
|
||||
return require('./img/lnd-shape.png');
|
||||
case MultisigHDWallet.type:
|
||||
return require('./img/vault-shape.png');
|
||||
default:
|
||||
return require('./img/btc-shape.png');
|
||||
}
|
||||
})()}
|
||||
style={{
|
||||
width: 99,
|
||||
height: 94,
|
||||
|
@ -406,9 +414,9 @@ export class BlueWalletNavigationHeader extends Component {
|
|||
marginBottom: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.2)',
|
||||
borderRadius: 9,
|
||||
minWidth: 119,
|
||||
minHeight: 39,
|
||||
width: 119,
|
||||
alignSelf: 'flex-start',
|
||||
paddingHorizontal: 12,
|
||||
height: 39,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
@ -699,60 +707,56 @@ export const BlueTextCenteredHooks = props => {
|
|||
return <Text {...props} style={{ color: colors.foregroundColor, textAlign: 'center' }} />;
|
||||
};
|
||||
|
||||
export const BlueListItem = React.memo(props => (
|
||||
<ListItem
|
||||
testID={props.testID}
|
||||
bottomDivider
|
||||
containerStyle={{
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: BlueCurrentTheme.colors.lightBorder,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 16,
|
||||
}}
|
||||
titleStyle={{
|
||||
color: props.disabled ? BlueCurrentTheme.colors.buttonDisabledTextColor : BlueCurrentTheme.colors.foregroundColor,
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
}}
|
||||
subtitleStyle={{ flexWrap: 'wrap', color: BlueCurrentTheme.colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }}
|
||||
subtitleNumberOfLines={1}
|
||||
titleNumberOfLines={0}
|
||||
Component={TouchableOpacity}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
export const BlueListItemHooks = props => {
|
||||
export const BlueListItem = React.memo(props => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<ListItem
|
||||
testID={props.testID}
|
||||
containerStyle={props.containerStyle ?? { backgroundColor: 'transparent' }}
|
||||
Component={props.Component ?? TouchableOpacity}
|
||||
bottomDivider
|
||||
containerStyle={{
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: colors.lightBorder,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 16,
|
||||
}}
|
||||
titleStyle={{
|
||||
color: props.disabled ? colors.buttonDisabledTextColor : colors.foregroundColor,
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
}}
|
||||
rightTitleStyle={{ flexWrap: 'wrap', color: colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }}
|
||||
subtitleStyle={{ flexWrap: 'wrap', color: colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }}
|
||||
subtitleNumberOfLines={1}
|
||||
titleNumberOfLines={0}
|
||||
Component={TouchableOpacity}
|
||||
{...props}
|
||||
/>
|
||||
testID={props.testID}
|
||||
onPress={props.onPress}
|
||||
>
|
||||
{props.leftAvatar && <Avatar>{props.leftAvatar}</Avatar>}
|
||||
{props.leftIcon && <Avatar icon={props.leftIcon} />}
|
||||
<ListItem.Content>
|
||||
<ListItem.Title
|
||||
style={{
|
||||
color: props.disabled ? colors.buttonDisabledTextColor : colors.foregroundColor,
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
}}
|
||||
numberOfLines={0}
|
||||
>
|
||||
{props.title}
|
||||
</ListItem.Title>
|
||||
{props.subtitle && (
|
||||
<ListItem.Subtitle
|
||||
numberOfLines={1}
|
||||
style={{ flexWrap: 'wrap', color: colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }}
|
||||
>
|
||||
{props.subtitle}
|
||||
</ListItem.Subtitle>
|
||||
)}
|
||||
</ListItem.Content>
|
||||
<ListItem.Content right>
|
||||
{props.rightTitle && (
|
||||
<ListItem.Title style={props.rightTitleStyle} numberOfLines={0} right>
|
||||
{props.rightTitle}
|
||||
</ListItem.Title>
|
||||
)}
|
||||
</ListItem.Content>
|
||||
{props.chevron && <ListItem.Chevron />}
|
||||
{props.rightIcon && <Avatar icon={props.rightIcon} />}
|
||||
{props.switch && <Switch {...props.switch} />}
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export const BlueFormLabel = props => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return <Text {...props} style={{ color: colors.foregroundColor, fontWeight: '400', marginLeft: 20 }} />;
|
||||
return <Text {...props} style={{ color: colors.foregroundColor, fontWeight: '400', marginHorizontal: 20 }} />;
|
||||
};
|
||||
|
||||
export class BlueFormInput extends Component {
|
||||
|
@ -902,95 +906,37 @@ export const BlueHeaderDefaultSubHooks = props => {
|
|||
);
|
||||
};
|
||||
|
||||
export const BlueHeaderDefaultMainHooks = props => {
|
||||
export const BlueHeaderDefaultMain = props => {
|
||||
const { colors } = useTheme();
|
||||
const { isDrawerList } = props;
|
||||
return (
|
||||
<Header
|
||||
{...props}
|
||||
leftComponent={{
|
||||
text: props.leftText,
|
||||
style: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 34,
|
||||
color: colors.foregroundColor,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
}}
|
||||
leftContainerStyle={{
|
||||
minWidth: '70%',
|
||||
height: 80,
|
||||
placement="left"
|
||||
containerStyle={{
|
||||
borderTopColor: isDrawerList ? colors.elevated : colors.background,
|
||||
borderBottomColor: isDrawerList ? colors.elevated : colors.background,
|
||||
maxHeight: 44,
|
||||
height: 44,
|
||||
paddingTop: 0,
|
||||
marginBottom: 8,
|
||||
}}
|
||||
bottomDivider={false}
|
||||
topDivider={false}
|
||||
containerStyle={{
|
||||
height: 44,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: colors.elevatated,
|
||||
borderTopColor: colors.elevatated,
|
||||
borderBottomColor: colors.elevatated,
|
||||
borderBottomWidth: 0,
|
||||
}}
|
||||
rightComponent={
|
||||
props.onNewWalletPress && (
|
||||
<TouchableOpacity
|
||||
onPress={props.onNewWalletPress}
|
||||
style={{
|
||||
height: 100,
|
||||
}}
|
||||
>
|
||||
<BluePlusIcon />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
backgroundColor={isDrawerList ? colors.elevated : colors.background}
|
||||
rightComponent={<BluePlusIcon onPress={props.onNewWalletPress} Component={TouchableOpacity} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export class BlueHeaderDefaultMain extends Component {
|
||||
render() {
|
||||
return (
|
||||
<SafeAreaView style={{ paddingVertical: 8, paddingHorizontal: 4, backgroundColor: BlueCurrentTheme.colors.background }}>
|
||||
<Header
|
||||
{...this.props}
|
||||
leftComponent={{
|
||||
text: this.props.leftText,
|
||||
style: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 34,
|
||||
color: BlueCurrentTheme.colors.foregroundColor,
|
||||
},
|
||||
}}
|
||||
leftContainerStyle={{
|
||||
minWidth: '70%',
|
||||
height: 80,
|
||||
}}
|
||||
bottomDivider={false}
|
||||
topDivider={false}
|
||||
containerStyle={{
|
||||
height: 44,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: BlueCurrentTheme.colors.background,
|
||||
borderTopColor: BlueCurrentTheme.colors.background,
|
||||
borderBottomColor: BlueCurrentTheme.colors.background,
|
||||
borderBottomWidth: 0,
|
||||
}}
|
||||
rightComponent={
|
||||
this.props.onNewWalletPress && (
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onNewWalletPress}
|
||||
style={{
|
||||
height: 100,
|
||||
}}
|
||||
>
|
||||
<BluePlusIcon />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class BlueSpacing extends Component {
|
||||
render() {
|
||||
return <View {...this.props} style={{ height: 60 }} />;
|
||||
|
@ -1268,23 +1214,12 @@ export const BluePlusIcon = props => {
|
|||
},
|
||||
});
|
||||
return (
|
||||
<View {...props} style={stylesBlueIcon.container}>
|
||||
<View style={stylesBlueIcon.box1}>
|
||||
<View style={[stylesBlueIcon.ball, stylesBlueIconHooks.ball]}>
|
||||
<Ionicons
|
||||
{...props}
|
||||
name="ios-add"
|
||||
size={26}
|
||||
style={{
|
||||
color: colors.foregroundColor,
|
||||
backgroundColor: 'transparent',
|
||||
left: 8,
|
||||
top: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Avatar
|
||||
rounded
|
||||
containerStyle={[stylesBlueIcon.ball, stylesBlueIconHooks.ball]}
|
||||
icon={{ name: 'add', size: 22, type: 'ionicons', color: colors.foregroundColor }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1632,7 +1567,7 @@ export const NewWalletPanel = props => {
|
|||
minHeight: Platform.OS === 'ios' ? 164 : 181,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-start',
|
||||
backgroundColor: WalletGradient.createWallet,
|
||||
backgroundColor: WalletGradient.createWallet(),
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
|
@ -1673,15 +1608,27 @@ export const NewWalletPanel = props => {
|
|||
export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => {
|
||||
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
|
||||
const { colors } = useTheme();
|
||||
const containerStyle = useMemo(
|
||||
() => ({
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: colors.lightBorder,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 16,
|
||||
}),
|
||||
[colors.lightBorder],
|
||||
);
|
||||
|
||||
const txMemo = () => {
|
||||
if (BlueApp.tx_metadata[item.hash] && BlueApp.tx_metadata[item.hash].memo) {
|
||||
return BlueApp.tx_metadata[item.hash].memo;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const title = useMemo(() => transactionTimeToReadable(item.received), [item.received]);
|
||||
const txMemo = BlueApp.tx_metadata[item.hash]?.memo ?? '';
|
||||
const subtitle = useMemo(() => {
|
||||
let sub = item.confirmations < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : '';
|
||||
if (sub !== '') sub += ' ';
|
||||
sub += txMemo;
|
||||
if (item.memo) sub += item.memo;
|
||||
return sub || null;
|
||||
}, [txMemo, item.confirmations, item.memo]);
|
||||
|
||||
const rowTitle = () => {
|
||||
const rowTitle = useMemo(() => {
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
if (isNaN(item.value)) {
|
||||
item.value = '0';
|
||||
|
@ -1702,9 +1649,9 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
|
|||
} else {
|
||||
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
|
||||
}
|
||||
};
|
||||
}, [item, itemPriceUnit]);
|
||||
|
||||
const rowTitleStyle = () => {
|
||||
const rowTitleStyle = useMemo(() => {
|
||||
let color = colors.successColor;
|
||||
|
||||
if (item.type === 'user_invoice' || item.type === 'payment_request') {
|
||||
|
@ -1726,15 +1673,15 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
|
|||
}
|
||||
|
||||
return {
|
||||
fontWeight: '600',
|
||||
color,
|
||||
fontSize: 14,
|
||||
color: color,
|
||||
fontWeight: '600',
|
||||
textAlign: 'right',
|
||||
width: 96,
|
||||
};
|
||||
};
|
||||
}, [item, colors.foregroundColor, colors.successColor]);
|
||||
|
||||
const avatar = () => {
|
||||
const avatar = useMemo(() => {
|
||||
// is it lightning refill tx?
|
||||
if (item.category === 'receive' && item.confirmations < 3) {
|
||||
return (
|
||||
|
@ -1800,13 +1747,9 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
|
|||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [item]);
|
||||
|
||||
const subtitle = () => {
|
||||
return (item.confirmations < 7 ? loc.transactions.list_conf + ': ' + item.confirmations + ' ' : '') + txMemo() + (item.memo || '');
|
||||
};
|
||||
|
||||
const onPress = async () => {
|
||||
const onPress = useCallback(async () => {
|
||||
if (item.hash) {
|
||||
NavigationService.navigate('TransactionStatus', { hash: item.hash });
|
||||
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
|
||||
|
@ -1829,7 +1772,7 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
|
|||
NavigationService.navigate('ScanLndInvoiceRoot', {
|
||||
screen: 'LnurlPaySuccess',
|
||||
params: {
|
||||
paymentHash: paymentHash,
|
||||
paymentHash,
|
||||
justPaid: false,
|
||||
fromWalletID: lightningWallet[0].getID(),
|
||||
},
|
||||
|
@ -1844,28 +1787,31 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
|
|||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [item]);
|
||||
|
||||
const onLongPress = () => {
|
||||
const onLongPress = useCallback(() => {
|
||||
if (subtitleNumberOfLines === 1) {
|
||||
setSubtitleNumberOfLines(0);
|
||||
}
|
||||
};
|
||||
}, [subtitleNumberOfLines]);
|
||||
|
||||
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
|
||||
|
||||
return (
|
||||
<View style={{ marginHorizontal: 4 }}>
|
||||
<BlueListItem
|
||||
leftAvatar={avatar()}
|
||||
title={transactionTimeToReadable(item.received)}
|
||||
leftAvatar={avatar}
|
||||
title={title}
|
||||
titleNumberOfLines={subtitleNumberOfLines}
|
||||
subtitle={subtitle()}
|
||||
subtitleProps={{ numberOfLines: subtitleNumberOfLines }}
|
||||
subtitle={subtitle}
|
||||
subtitleProps={subtitleProps}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
chevron={false}
|
||||
Component={TouchableOpacity}
|
||||
rightTitle={rowTitle()}
|
||||
rightTitleStyle={rowTitleStyle()}
|
||||
rightTitle={rowTitle}
|
||||
rightTitleStyle={rowTitleStyle}
|
||||
containerStyle={containerStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
@ -2005,7 +1951,16 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
|
|||
}}
|
||||
>
|
||||
<Image
|
||||
source={(LightningCustodianWallet.type === item.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png')}
|
||||
source={(() => {
|
||||
switch (item.type) {
|
||||
case LightningCustodianWallet.type:
|
||||
return require('./img/lnd-shape.png');
|
||||
case MultisigHDWallet.type:
|
||||
return require('./img/vault-shape.png');
|
||||
default:
|
||||
return require('./img/btc-shape.png');
|
||||
}
|
||||
})()}
|
||||
style={{
|
||||
width: 99,
|
||||
height: 94,
|
||||
|
@ -2093,7 +2048,8 @@ export class WalletsCarousel extends Component {
|
|||
};
|
||||
|
||||
snapToItem = item => {
|
||||
this.walletsCarousel.current.snapToItem(item);
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.walletsCarousel?.current?.snapToItem(item);
|
||||
};
|
||||
|
||||
onLayout = () => {
|
||||
|
@ -2250,6 +2206,7 @@ export class BlueAddressInput extends Component {
|
|||
{...this.props}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
testID="BlueAddressInputScanQrButton"
|
||||
disabled={this.props.isLoading}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss();
|
||||
|
@ -2745,6 +2702,7 @@ const tabsStyles = StyleSheet.create({
|
|||
borderBottomWidth: 1,
|
||||
},
|
||||
tabRoot: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderColor: 'white',
|
||||
|
@ -2753,7 +2711,7 @@ const tabsStyles = StyleSheet.create({
|
|||
});
|
||||
|
||||
export const BlueTabs = ({ active, onSwitch, tabs }) => (
|
||||
<View style={tabsStyles.root}>
|
||||
<View style={[tabsStyles.root, isIpad && { marginBottom: 30 }]}>
|
||||
{tabs.map((Tab, i) => (
|
||||
<TouchableOpacity
|
||||
key={i}
|
||||
|
@ -2764,7 +2722,6 @@ export const BlueTabs = ({ active, onSwitch, tabs }) => (
|
|||
borderColor: BlueCurrentTheme.colors.buttonAlternativeTextColor,
|
||||
borderBottomWidth: 2,
|
||||
},
|
||||
{ width: width / tabs.length },
|
||||
]}
|
||||
>
|
||||
<Tab active={active === i} />
|
||||
|
|
|
@ -27,6 +27,7 @@ import PleaseBackupLNDHub from './screen/wallets/pleaseBackupLNDHub';
|
|||
import ImportWallet from './screen/wallets/import';
|
||||
import WalletDetails from './screen/wallets/details';
|
||||
import WalletExport from './screen/wallets/export';
|
||||
import ExportMultisigCoordinationSetup from './screen/wallets/exportMultisigCoordinationSetup';
|
||||
import WalletXpub from './screen/wallets/xpub';
|
||||
import BuyBitcoin from './screen/wallets/buyBitcoin';
|
||||
import HodlHodl from './screen/wallets/hodlHodl';
|
||||
|
@ -53,6 +54,7 @@ import ScanQRCode from './screen/send/ScanQRCode';
|
|||
import SendCreate from './screen/send/create';
|
||||
import Confirm from './screen/send/confirm';
|
||||
import PsbtWithHardwareWallet from './screen/send/psbtWithHardwareWallet';
|
||||
import PsbtMultisig from './screen/send/psbtMultisig';
|
||||
import Success from './screen/send/success';
|
||||
import Broadcast from './screen/send/broadcast';
|
||||
|
||||
|
@ -79,7 +81,11 @@ const defaultScreenOptions =
|
|||
...TransitionPresets.ModalPresentationIOS,
|
||||
gestureResponseDistance: { vertical: Dimensions.get('window').height, horizontal: 50 },
|
||||
})
|
||||
: undefined;
|
||||
: {
|
||||
gestureEnabled: true,
|
||||
cardOverlayEnabled: true,
|
||||
...TransitionPresets.ScaleFromCenterAndroid,
|
||||
};
|
||||
const defaultStackScreenOptions =
|
||||
Platform.OS === 'ios'
|
||||
? {
|
||||
|
@ -88,11 +94,15 @@ const defaultStackScreenOptions =
|
|||
cardStyle: { backgroundColor: '#FFFFFF' },
|
||||
headerStatusBarHeight: 10,
|
||||
}
|
||||
: undefined;
|
||||
: {
|
||||
gestureEnabled: true,
|
||||
cardOverlayEnabled: true,
|
||||
...TransitionPresets.ScaleFromCenterAndroid,
|
||||
};
|
||||
|
||||
const WalletsStack = createStackNavigator();
|
||||
const WalletsRoot = () => (
|
||||
<WalletsStack.Navigator>
|
||||
<WalletsStack.Navigator {...(Platform.OS === 'android' ? { screenOptions: defaultScreenOptions } : null)}>
|
||||
<WalletsStack.Screen name="WalletsList" component={WalletsList} options={WalletsList.navigationOptions} />
|
||||
<WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions} />
|
||||
<WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions} />
|
||||
|
@ -167,17 +177,8 @@ const SendDetailsRoot = () => (
|
|||
component={PsbtWithHardwareWallet}
|
||||
options={PsbtWithHardwareWallet.navigationOptions}
|
||||
/>
|
||||
<SendDetailsStack.Screen
|
||||
name="CreateTransaction"
|
||||
component={SendCreate}
|
||||
options={{
|
||||
headerStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
headerTintColor: '#0c2550',
|
||||
}}
|
||||
/>
|
||||
<SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions} />
|
||||
<SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions} />
|
||||
<SendDetailsStack.Screen name="Success" component={Success} options={Success.navigationOptions} />
|
||||
<SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
|
||||
</SendDetailsStack.Navigator>
|
||||
|
@ -263,11 +264,12 @@ function DrawerRoot() {
|
|||
const dimensions = useWindowDimensions();
|
||||
const isLargeScreen = Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 3 && isTablet();
|
||||
const drawerStyle = { width: '0%' };
|
||||
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
drawerStyle={isLargeScreen ? null : drawerStyle}
|
||||
drawerType={isLargeScreen ? 'permanent' : null}
|
||||
drawerContent={props => <DrawerList {...props} />}
|
||||
drawerContent={props => (isLargeScreen ? <DrawerList {...props} /> : null)}
|
||||
>
|
||||
<Drawer.Screen name="Navigation" component={Navigation} options={{ headerShown: false, gestureEnabled: false }} />
|
||||
</Drawer.Navigator>
|
||||
|
@ -304,6 +306,11 @@ const Navigation = () => (
|
|||
|
||||
{/* screens */}
|
||||
<RootStack.Screen name="WalletExport" component={WalletExport} options={WalletExport.navigationOptions} />
|
||||
<RootStack.Screen
|
||||
name="ExportMultisigCoordinationSetup"
|
||||
component={ExportMultisigCoordinationSetup}
|
||||
options={ExportMultisigCoordinationSetup.navigationOptions}
|
||||
/>
|
||||
<RootStack.Screen name="WalletXpub" component={WalletXpub} options={WalletXpub.navigationOptions} />
|
||||
<RootStack.Screen name="BuyBitcoin" component={BuyBitcoin} options={BuyBitcoin.navigationOptions} />
|
||||
<RootStack.Screen name="Marketplace" component={Marketplace} options={Marketplace.navigationOptions} />
|
||||
|
@ -315,7 +322,7 @@ const Navigation = () => (
|
|||
name="ScanQRCodeRoot"
|
||||
component={ScanQRCodeRoot}
|
||||
options={{
|
||||
...TransitionPresets.ModalTransition,
|
||||
...(Platform.OS === 'ios' ? TransitionPresets.ModalTransition : TransitionPresets.ScaleFromCenterAndroid),
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -69,7 +69,7 @@ The above command will build the app and install it. Once you launch the app it
|
|||
* To run on iOS:
|
||||
|
||||
```
|
||||
npx podinstall
|
||||
npx pod-install
|
||||
npm start
|
||||
```
|
||||
|
||||
|
|
|
@ -127,12 +127,16 @@ android {
|
|||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.bluewallet.bluewallet"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "5.6.0"
|
||||
versionName "5.6.2"
|
||||
multiDexEnabled true
|
||||
missingDimensionStrategy 'react-native-camera', 'general'
|
||||
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type
|
||||
|
@ -155,13 +159,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst "lib/armeabi-v7a/libc++_shared.so"
|
||||
pickFirst "lib/arm64-v8a/libc++_shared.so"
|
||||
pickFirst "lib/x86/libc++_shared.so"
|
||||
pickFirst "lib/x86_64/libc++_shared.so"
|
||||
}
|
||||
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "28.0.3"
|
||||
minSdkVersion = 18
|
||||
compileSdkVersion = 28
|
||||
targetSdkVersion = 28
|
||||
supportLibVersion = "28.0.0"
|
||||
buildToolsVersion = "29.0.2"
|
||||
compileSdkVersion = 29
|
||||
targetSdkVersion = 29
|
||||
googlePlayServicesVersion = "16.+"
|
||||
firebaseVersion = "17.3.4"
|
||||
firebaseMessagingVersion = "20.2.1"
|
||||
|
|
|
@ -31,4 +31,4 @@ org.gradle.configureondemand=true
|
|||
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.33.1
|
||||
FLIPPER_VERSION=0.54.0
|
30
android/gradlew
vendored
30
android/gradlew
vendored
|
@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
@ -175,15 +175,9 @@ save () {
|
|||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
@ -521,15 +521,27 @@ module.exports.calculateBlockTime = function (height) {
|
|||
*/
|
||||
module.exports.testConnection = async function (host, tcpPort, sslPort) {
|
||||
const client = new ElectrumClient(sslPort || tcpPort, host, sslPort ? 'tls' : 'tcp');
|
||||
client.onError = () => {}; // mute
|
||||
let timeoutId = false;
|
||||
try {
|
||||
await client.connect();
|
||||
const rez = await Promise.race([
|
||||
new Promise(resolve => {
|
||||
timeoutId = setTimeout(() => resolve('timeout'), 3000);
|
||||
}),
|
||||
client.connect(),
|
||||
]);
|
||||
if (rez === 'timeout') return false;
|
||||
|
||||
await client.server_version('2.7.11', '1.4');
|
||||
await client.server_ping();
|
||||
client.close();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
} finally {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
client.close();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports.forceDisconnect = () => {
|
||||
|
|
109
blue_modules/fs.js
Normal file
109
blue_modules/fs.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
/* global alert */
|
||||
import { PermissionsAndroid, Platform } from 'react-native';
|
||||
import RNFS from 'react-native-fs';
|
||||
import Share from 'react-native-share';
|
||||
import loc from '../loc';
|
||||
import { getSystemName } from 'react-native-device-info';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
|
||||
const isDesktop = getSystemName() === 'Mac OS X';
|
||||
|
||||
const writeFileAndExport = async function (filename, contents) {
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
Share.open({
|
||||
url: 'file://' + filePath,
|
||||
saveToFiles: isDesktop,
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
// alert(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: loc.send.permission_storage_title,
|
||||
message: loc.send.permission_storage_message,
|
||||
buttonNeutral: loc.send.permission_storage_later,
|
||||
buttonNegative: loc._.cancel,
|
||||
buttonPositive: loc._.ok,
|
||||
});
|
||||
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||
console.log('Storage Permission: Granted');
|
||||
const filePath = RNFS.DownloadDirectoryPath + `/${filename}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
alert(loc.formatString(loc._.file_saved, { filePath: filename }));
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens & reads *.psbt files, and returns base64 psbt. FALSE if something went wrong (wont throw).
|
||||
*
|
||||
* @returns {Promise<string|boolean>} Base64 PSBT
|
||||
*/
|
||||
const openSignedTransaction = async function () {
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallt.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
});
|
||||
|
||||
return await _readPsbtFileIntoBase64(res.uri);
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert(loc.send.details_no_signed_tx);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const _readPsbtFileIntoBase64 = async function (uri) {
|
||||
const base64 = await RNFS.readFile(uri, 'base64');
|
||||
const stringData = Buffer.from(base64, 'base64').toString(); // decode from base64
|
||||
if (stringData.startsWith('psbt')) {
|
||||
// file was binary, but outer code expects base64 psbt, so we return base64 we got from rn-fs;
|
||||
// most likely produced by Electrum-desktop
|
||||
return base64;
|
||||
} else {
|
||||
// file was a text file, having base64 psbt in there. so we basically have double base64encoded string
|
||||
// thats why we are returning string that was decoded once;
|
||||
// most likely produced by Coldcard
|
||||
return stringData;
|
||||
}
|
||||
};
|
||||
|
||||
const showFilePickerAndReadFile = async function () {
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type:
|
||||
Platform.OS === 'ios'
|
||||
? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn', DocumentPicker.types.plainText, 'public.json']
|
||||
: [DocumentPicker.types.allFiles],
|
||||
});
|
||||
|
||||
let file = false;
|
||||
if (res.uri.toLowerCase().endsWith('.psbt')) {
|
||||
// this is either binary file from ElectrumDesktop OR string file with base64 string in there
|
||||
file = await _readPsbtFileIntoBase64(res.uri);
|
||||
} else {
|
||||
file = await RNFS.readFile(res.uri);
|
||||
}
|
||||
|
||||
return { data: file, uri: res.uri };
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
return { data: false, uri: false };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.writeFileAndExport = writeFileAndExport;
|
||||
module.exports.openSignedTransaction = openSignedTransaction;
|
||||
module.exports.showFilePickerAndReadFile = showFilePickerAndReadFile;
|
|
@ -14,6 +14,7 @@ import {
|
|||
LightningCustodianWallet,
|
||||
HDLegacyElectrumSeedP2PKHWallet,
|
||||
HDSegwitElectrumSeedP2WPKHWallet,
|
||||
MultisigHDWallet,
|
||||
} from './';
|
||||
import DeviceQuickActions from './quick-actions';
|
||||
import { AbstractHDElectrumWallet } from './wallets/abstract-hd-electrum-wallet';
|
||||
|
@ -297,6 +298,9 @@ export class AppStorage {
|
|||
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
||||
unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key);
|
||||
break;
|
||||
case MultisigHDWallet.type:
|
||||
unserializedWallet = MultisigHDWallet.fromJson(key);
|
||||
break;
|
||||
case LightningCustodianWallet.type: {
|
||||
/** @type {LightningCustodianWallet} */
|
||||
unserializedWallet = LightningCustodianWallet.fromJson(key);
|
||||
|
@ -433,7 +437,7 @@ export class AppStorage {
|
|||
const realm = await this.getRealm();
|
||||
for (const key of this.wallets) {
|
||||
if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue;
|
||||
if (key.prepareForSerialization) key.prepareForSerialization();
|
||||
key.prepareForSerialization();
|
||||
const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore
|
||||
if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance);
|
||||
this.offloadWalletToRealm(realm, key);
|
||||
|
|
|
@ -59,12 +59,12 @@ export default class Biometric {
|
|||
static async unlockWithBiometrics() {
|
||||
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
|
||||
if (isDeviceBiometricCapable) {
|
||||
try {
|
||||
const isConfirmed = await FingerprintScanner.authenticate({ description: 'Please confirm your identity.', fallbackEnabled: true });
|
||||
return isConfirmed;
|
||||
} catch (_e) {
|
||||
return false;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
FingerprintScanner.authenticate({ description: 'Please confirm your identity.', fallbackEnabled: true })
|
||||
.then(() => resolve(true))
|
||||
.catch(() => resolve(false))
|
||||
.finally(() => FingerprintScanner.release());
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -40,9 +40,8 @@ class DeeplinkSchemaMatch {
|
|||
if (event.url.toLowerCase().startsWith('bluewallet:bitcoin:') || event.url.toLowerCase().startsWith('bluewallet:lightning:')) {
|
||||
event.url = event.url.substring(11);
|
||||
}
|
||||
|
||||
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) {
|
||||
RNFS.readFile(event.url)
|
||||
if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(event.url)) {
|
||||
RNFS.readFile(decodeURI(event.url))
|
||||
.then(file => {
|
||||
if (file) {
|
||||
completionHandler([
|
||||
|
@ -203,13 +202,23 @@ class DeeplinkSchemaMatch {
|
|||
}
|
||||
|
||||
static isTXNFile(filePath) {
|
||||
return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('.txn');
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('.txn')
|
||||
);
|
||||
}
|
||||
|
||||
static isPossiblySignedPSBTFile(filePath) {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('-signed.psbt')
|
||||
);
|
||||
}
|
||||
|
||||
static isPossiblyPSBTFile(filePath) {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('-signed.psbt')
|
||||
filePath.toLowerCase().endsWith('.psbt')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -314,6 +323,31 @@ class DeeplinkSchemaMatch {
|
|||
static bip21encode() {
|
||||
return bip21.encode.apply(bip21, arguments);
|
||||
}
|
||||
|
||||
static decodeBitcoinUri(uri) {
|
||||
let amount = '';
|
||||
let parsedBitcoinUri = null;
|
||||
let address = uri || '';
|
||||
let memo = '';
|
||||
let payjoinUrl = '';
|
||||
try {
|
||||
parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri);
|
||||
address = 'address' in parsedBitcoinUri ? parsedBitcoinUri.address : address;
|
||||
if ('options' in parsedBitcoinUri) {
|
||||
if ('amount' in parsedBitcoinUri.options) {
|
||||
amount = parsedBitcoinUri.options.amount.toString();
|
||||
amount = parsedBitcoinUri.options.amount;
|
||||
}
|
||||
if ('label' in parsedBitcoinUri.options) {
|
||||
memo = parsedBitcoinUri.options.label || memo;
|
||||
}
|
||||
if ('pj' in parsedBitcoinUri.options) {
|
||||
payjoinUrl = parsedBitcoinUri.options.pj;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
return { address, amount, memo, payjoinUrl };
|
||||
}
|
||||
}
|
||||
|
||||
export default DeeplinkSchemaMatch;
|
||||
|
|
|
@ -214,6 +214,32 @@ export class HDSegwitBech32Transaction {
|
|||
return { fee, feeRate, targets, changeAmount, utxos, unconfirmedUtxos };
|
||||
}
|
||||
|
||||
/**
|
||||
* We get _all_ our UTXOs (even spent kek),
|
||||
* and see if each input in this transaction's UTXO is in there. If its not there - its an unknown
|
||||
* input, we dont own it (possibly a payjoin transaction), and we cant do RBF
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async thereAreUnknownInputsInTx() {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._txDecoded) await this._fetchTxhexAndDecode();
|
||||
|
||||
const spentUtxos = this._wallet.getDerivedUtxoFromOurTransaction(true);
|
||||
for (const inp of this._txDecoded.ins) {
|
||||
const txidInUtxo = reverse(inp.hash).toString('hex');
|
||||
|
||||
let found = false;
|
||||
for (const spentU of spentUtxos) {
|
||||
if (spentU.txid === txidInUtxo && spentU.vout === inp.index) found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all outputs belong to us, that
|
||||
* means we already canceled this tx and we can only bump fees
|
||||
|
@ -224,6 +250,8 @@ export class HDSegwitBech32Transaction {
|
|||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._txDecoded) await this._fetchTxhexAndDecode();
|
||||
|
||||
if (await this.thereAreUnknownInputsInTx()) return false;
|
||||
|
||||
// if theres at least one output we dont own - we can cancel this transaction!
|
||||
for (const outp of this._txDecoded.outs) {
|
||||
if (!this._wallet.weOwnAddress(SegwitBech32Wallet.scriptPubKeyToAddress(outp.script))) return true;
|
||||
|
@ -232,6 +260,15 @@ export class HDSegwitBech32Transaction {
|
|||
return false;
|
||||
}
|
||||
|
||||
async canBumpTx() {
|
||||
if (!this._wallet) throw new Error('Wallet required for this method');
|
||||
if (!this._txDecoded) await this._fetchTxhexAndDecode();
|
||||
|
||||
if (await this.thereAreUnknownInputsInTx()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RBF transaction that can replace previous one and basically cancel it (rewrite
|
||||
* output to the one our wallet controls). Note, this cannot add more utxo in RBF transaction if
|
||||
|
|
|
@ -14,3 +14,4 @@ export * from './hd-segwit-bech32-transaction';
|
|||
export * from './wallets/placeholder-wallet';
|
||||
export * from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
|
||||
export * from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
|
||||
export * from './wallets/multisig-hd-wallet';
|
||||
|
|
85
class/payjoin-transaction.js
Normal file
85
class/payjoin-transaction.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
/* global alert */
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
|
||||
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
|
||||
// Implements IPayjoinClientWallet
|
||||
// https://github.com/bitcoinjs/payjoin-client/blob/master/ts_src/wallet.ts
|
||||
export default class PayjoinTransaction {
|
||||
constructor(psbt, broadcast, wallet) {
|
||||
this._psbt = psbt;
|
||||
this._broadcast = broadcast;
|
||||
this._wallet = wallet;
|
||||
this._payjoinPsbt = false;
|
||||
}
|
||||
|
||||
async getPsbt() {
|
||||
// Nasty hack to get this working for now
|
||||
const unfinalized = this._psbt.clone();
|
||||
unfinalized.data.inputs.forEach((input, index) => {
|
||||
delete input.finalScriptWitness;
|
||||
|
||||
const address = bitcoin.address.fromOutputScript(input.witnessUtxo.script);
|
||||
const wif = this._wallet._getWifForAddress(address);
|
||||
const keyPair = bitcoin.ECPair.fromWIF(wif);
|
||||
|
||||
unfinalized.signInput(index, keyPair);
|
||||
});
|
||||
|
||||
return unfinalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesnt conform to spec but needed for user-facing wallet software to find out txid of payjoined transaction
|
||||
*
|
||||
* @returns {boolean|Psbt}
|
||||
*/
|
||||
getPayjoinPsbt() {
|
||||
return this._payjoinPsbt;
|
||||
}
|
||||
|
||||
async signPsbt(payjoinPsbt) {
|
||||
// Do this without relying on private methods
|
||||
payjoinPsbt.data.inputs.forEach((input, index) => {
|
||||
const address = bitcoin.address.fromOutputScript(input.witnessUtxo.script);
|
||||
try {
|
||||
const wif = this._wallet._getWifForAddress(address);
|
||||
const keyPair = bitcoin.ECPair.fromWIF(wif);
|
||||
payjoinPsbt.signInput(index, keyPair).finalizeInput(index);
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
this._payjoinPsbt = payjoinPsbt;
|
||||
return this._payjoinPsbt;
|
||||
}
|
||||
|
||||
async broadcastTx(txHex) {
|
||||
try {
|
||||
const result = await this._broadcast(txHex);
|
||||
if (!result) {
|
||||
throw new Error(`Broadcast failed`);
|
||||
}
|
||||
return '';
|
||||
} catch (e) {
|
||||
return 'Error: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async scheduleBroadcastTx(txHex, milliseconds) {
|
||||
delay(milliseconds).then(async () => {
|
||||
const result = await this.broadcastTx(txHex);
|
||||
if (result === '') {
|
||||
// TODO: Improve the wording of this error message
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
alert('Something was wrong with the payjoin transaction, the original transaction sucessfully broadcast.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async isOwnOutputScript(outputScript) {
|
||||
const address = bitcoin.address.fromOutputScript(outputScript);
|
||||
|
||||
return this._wallet.weOwnAddress(address);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,8 @@ import { PlaceholderWallet } from './wallets/placeholder-wallet';
|
|||
import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet';
|
||||
import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
|
||||
import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
|
||||
import { BlueCurrentTheme } from '../components/themes';
|
||||
import { MultisigHDWallet } from './wallets/multisig-hd-wallet';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
|
||||
export default class WalletGradient {
|
||||
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
|
||||
|
@ -19,9 +20,15 @@ export default class WalletGradient {
|
|||
static legacyWallet = ['#40fad1', '#15be98'];
|
||||
static hdLegacyP2PKHWallet = ['#e36dfa', '#bd10e0'];
|
||||
static hdLegacyBreadWallet = ['#fe6381', '#f99c42'];
|
||||
static multisigHdWallet = ['#1ce6eb', '#296fc5', '#3500A2'];
|
||||
static defaultGradients = ['#c65afb', '#9053fe'];
|
||||
static lightningCustodianWallet = ['#f1be07', '#f79056'];
|
||||
static createWallet = BlueCurrentTheme.colors.lightButton;
|
||||
|
||||
static createWallet = () => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const { colors } = useTheme();
|
||||
return colors.lightButton;
|
||||
};
|
||||
|
||||
static gradientsFor(type) {
|
||||
let gradient;
|
||||
|
@ -55,6 +62,9 @@ export default class WalletGradient {
|
|||
case SegwitBech32Wallet.type:
|
||||
gradient = WalletGradient.segwitBech32Wallet;
|
||||
break;
|
||||
case MultisigHDWallet.type:
|
||||
gradient = WalletGradient.multisigHdWallet;
|
||||
break;
|
||||
default:
|
||||
gradient = WalletGradient.defaultGradients;
|
||||
break;
|
||||
|
@ -88,6 +98,9 @@ export default class WalletGradient {
|
|||
case SegwitBech32Wallet.type:
|
||||
gradient = WalletGradient.segwitBech32Wallet;
|
||||
break;
|
||||
case MultisigHDWallet.type:
|
||||
gradient = WalletGradient.multisigHdWallet;
|
||||
break;
|
||||
case LightningCustodianWallet.type:
|
||||
gradient = WalletGradient.lightningCustodianWallet;
|
||||
break;
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
SegwitBech32Wallet,
|
||||
HDLegacyElectrumSeedP2PKHWallet,
|
||||
HDSegwitElectrumSeedP2WPKHWallet,
|
||||
MultisigHDWallet,
|
||||
} from '.';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import loc from '../loc';
|
||||
|
@ -34,7 +35,9 @@ export default class WalletImport {
|
|||
*/
|
||||
static async _saveWallet(w, additionalProperties) {
|
||||
try {
|
||||
const wallet = BlueApp.getWallets().some(wallet => wallet.getSecret() === w.secret && wallet.type !== PlaceholderWallet.type);
|
||||
const wallet = BlueApp.getWallets().some(
|
||||
wallet => (wallet.getSecret() === w.secret || wallet.getID() === w.getID()) && wallet.type !== PlaceholderWallet.type,
|
||||
);
|
||||
if (wallet) {
|
||||
alert('This wallet has been previously imported.');
|
||||
WalletImport.removePlaceholderWallet();
|
||||
|
@ -97,6 +100,7 @@ export default class WalletImport {
|
|||
const placeholderWallet = WalletImport.addPlaceholderWallet(importText);
|
||||
// Plan:
|
||||
// -2. check if BIP38 encrypted
|
||||
// -1a. check if multisig
|
||||
// -1. check lightning custodian
|
||||
// 0. check if its HDSegwitBech32Wallet (BIP84)
|
||||
// 1. check if its HDSegwitP2SHWallet (BIP49)
|
||||
|
@ -125,6 +129,18 @@ export default class WalletImport {
|
|||
}
|
||||
}
|
||||
|
||||
// is it multisig?
|
||||
try {
|
||||
const ms = new MultisigHDWallet();
|
||||
ms.setSecret(importText);
|
||||
if (ms.getN() > 0 && ms.getM() > 0) {
|
||||
await ms.fetchBalance();
|
||||
return WalletImport._saveWallet(ms);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
// is it lightning custodian?
|
||||
if (importText.indexOf('blitzhub://') !== -1 || importText.indexOf('lndhub://') !== -1) {
|
||||
const lnd = new LightningCustodianWallet();
|
||||
|
|
|
@ -553,27 +553,33 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
async _fetchBalance() {
|
||||
// probing future addressess in hierarchy whether they have any transactions, in case
|
||||
// our 'next free addr' pointers are lagging behind
|
||||
let tryAgain = false;
|
||||
let txs = await BlueElectrum.getTransactionsByAddress(
|
||||
this._getExternalAddressByIndex(this.next_free_address_index + this.gap_limit - 1),
|
||||
);
|
||||
if (txs.length > 0) {
|
||||
// whoa, someone uses our wallet outside! better catch up
|
||||
this.next_free_address_index += this.gap_limit;
|
||||
tryAgain = true;
|
||||
// for that we are gona batch fetch history for all addresses between last used and last used + gap_limit
|
||||
|
||||
const lagAddressesToFetch = [];
|
||||
for (let c = this.next_free_address_index; c < this.next_free_address_index + this.gap_limit; c++) {
|
||||
lagAddressesToFetch.push(this._getExternalAddressByIndex(c));
|
||||
}
|
||||
for (let c = this.next_free_change_address_index; c < this.next_free_change_address_index + this.gap_limit; c++) {
|
||||
lagAddressesToFetch.push(this._getInternalAddressByIndex(c));
|
||||
}
|
||||
|
||||
txs = await BlueElectrum.getTransactionsByAddress(
|
||||
this._getInternalAddressByIndex(this.next_free_change_address_index + this.gap_limit - 1),
|
||||
);
|
||||
if (txs.length > 0) {
|
||||
this.next_free_change_address_index += this.gap_limit;
|
||||
tryAgain = true;
|
||||
const txs = await BlueElectrum.multiGetHistoryByAddress(lagAddressesToFetch); // <------ electrum call
|
||||
|
||||
for (let c = this.next_free_address_index; c < this.next_free_address_index + this.gap_limit; c++) {
|
||||
const address = this._getExternalAddressByIndex(c);
|
||||
if (txs[address] && Array.isArray(txs[address]) && txs[address].length > 0) {
|
||||
// whoa, someone uses our wallet outside! better catch up
|
||||
this.next_free_address_index = c + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: refactor me ^^^ can be batched in single call. plus not just couple of addresses, but all between [ next_free .. (next_free + gap_limit) ]
|
||||
|
||||
if (tryAgain) return this._fetchBalance();
|
||||
for (let c = this.next_free_change_address_index; c < this.next_free_change_address_index + this.gap_limit; c++) {
|
||||
const address = this._getInternalAddressByIndex(c);
|
||||
if (txs[address] && Array.isArray(txs[address]) && txs[address].length > 0) {
|
||||
// whoa, someone uses our wallet outside! better catch up
|
||||
this.next_free_change_address_index = c + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// next, business as usuall. fetch balances
|
||||
|
||||
|
@ -674,8 +680,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
|
||||
addressess = [...new Set(addressess)]; // deduplicate just for any case
|
||||
|
||||
const fetchedUtxo = await BlueElectrum.multiGetUtxoByAddress(addressess);
|
||||
this._utxo = [];
|
||||
for (const arr of Object.values(await BlueElectrum.multiGetUtxoByAddress(addressess))) {
|
||||
for (const arr of Object.values(fetchedUtxo)) {
|
||||
this._utxo = this._utxo.concat(arr);
|
||||
}
|
||||
|
||||
|
@ -712,15 +719,26 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
return this._utxo;
|
||||
}
|
||||
|
||||
getDerivedUtxoFromOurTransaction() {
|
||||
getDerivedUtxoFromOurTransaction(returnSpentUtxoAsWell = false) {
|
||||
const utxos = [];
|
||||
|
||||
// its faster to pre-build hashmap of owned addresses than to query `this.weOwnAddress()`, which in turn
|
||||
// iterates over all addresses in hierarchy
|
||||
const ownedAddressesHashmap = {};
|
||||
for (let c = 0; c < this.next_free_address_index + 1; c++) {
|
||||
ownedAddressesHashmap[this._getExternalAddressByIndex(c)] = true;
|
||||
}
|
||||
for (let c = 0; c < this.next_free_change_address_index + 1; c++) {
|
||||
ownedAddressesHashmap[this._getInternalAddressByIndex(c)] = true;
|
||||
}
|
||||
|
||||
for (const tx of this.getTransactions()) {
|
||||
for (const output of tx.outputs) {
|
||||
let address = false;
|
||||
if (output.scriptPubKey && output.scriptPubKey.addresses && output.scriptPubKey.addresses[0]) {
|
||||
address = output.scriptPubKey.addresses[0];
|
||||
}
|
||||
if (this.weOwnAddress(address)) {
|
||||
if (ownedAddressesHashmap[address]) {
|
||||
const value = new BigNumber(output.value).multipliedBy(100000000).toNumber();
|
||||
utxos.push({
|
||||
txid: tx.txid,
|
||||
|
@ -730,13 +748,15 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
value,
|
||||
amount: value,
|
||||
confirmations: tx.confirmations,
|
||||
wif: this._getWifForAddress(address),
|
||||
wif: false,
|
||||
height: BlueElectrum.estimateCurrentBlockheight() - tx.confirmations,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (returnSpentUtxoAsWell) return utxos;
|
||||
|
||||
// got all utxos we ever had. lets filter out the ones that are spent:
|
||||
const ret = [];
|
||||
for (const utxo of utxos) {
|
||||
|
@ -748,7 +768,11 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
}
|
||||
}
|
||||
|
||||
if (!spent) ret.push(utxo);
|
||||
if (!spent) {
|
||||
// filling WIFs only for legit unspent UTXO, as it is a slow operation
|
||||
utxo.wif = this._getWifForAddress(utxo.address);
|
||||
ret.push(utxo);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -26,6 +26,10 @@ export class AbstractHDWallet extends LegacyWallet {
|
|||
return this.next_free_address_index;
|
||||
}
|
||||
|
||||
getNextFreeChangeAddressIndex() {
|
||||
return this.next_free_change_address_index;
|
||||
}
|
||||
|
||||
prepareForSerialization() {
|
||||
// deleting structures that cant be serialized
|
||||
delete this._node0;
|
||||
|
@ -93,7 +97,7 @@ export class AbstractHDWallet extends LegacyWallet {
|
|||
if (!freeAddress) {
|
||||
// could not find in cycle above, give up
|
||||
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
|
||||
this.next_free_address_index += c + 1; // now points to the one _after_
|
||||
this.next_free_address_index += c; // now points to this one
|
||||
}
|
||||
this._address = freeAddress;
|
||||
return freeAddress;
|
||||
|
@ -130,8 +134,8 @@ export class AbstractHDWallet extends LegacyWallet {
|
|||
|
||||
if (!freeAddress) {
|
||||
// could not find in cycle above, give up
|
||||
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
|
||||
this.next_free_address_index += c + 1; // now points to the one _after_
|
||||
freeAddress = this._getInternalAddressByIndex(this.next_free_change_address_index + c); // we didnt check this one, maybe its free
|
||||
this.next_free_change_address_index += c; // now points to this one
|
||||
}
|
||||
this._address = freeAddress;
|
||||
return freeAddress;
|
||||
|
|
|
@ -115,6 +115,10 @@ export class AbstractWallet {
|
|||
return false;
|
||||
}
|
||||
|
||||
allowPayJoin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
weOwnAddress(address) {
|
||||
throw Error('not implemented');
|
||||
}
|
||||
|
@ -215,6 +219,10 @@ export class AbstractWallet {
|
|||
return new Promise(resolve => resolve(this.getAddress()));
|
||||
}
|
||||
|
||||
async getChangeAddressAsync() {
|
||||
return new Promise(resolve => resolve(this.getAddress()));
|
||||
}
|
||||
|
||||
useWithHardwareWalletEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
@ -260,4 +268,6 @@ export class AbstractWallet {
|
|||
|
||||
return b58.encode(data);
|
||||
}
|
||||
|
||||
prepareForSerialization() {}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet {
|
|||
* @private
|
||||
*/
|
||||
_getWIFByIndex(internal, index) {
|
||||
if (!this.secret) return false;
|
||||
const mnemonic = this.secret;
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bitcoinjs.bip32.fromSeed(seed);
|
||||
|
|
|
@ -61,6 +61,7 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
|
|||
}
|
||||
|
||||
_getWIFByIndex(internal, index) {
|
||||
if (!this.secret) return false;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
|
||||
const path = `m/${internal ? 1 : 0}/${index}`;
|
||||
const child = root.derivePath(path);
|
||||
|
|
|
@ -48,6 +48,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
|
|||
* @private
|
||||
*/
|
||||
_getWIFByIndex(internal, index) {
|
||||
if (!this.secret) return false;
|
||||
const mnemonic = this.secret;
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
|
||||
|
|
|
@ -28,4 +28,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
|
|||
allowRBF() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowPayJoin() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
|
|||
}
|
||||
|
||||
_getWIFByIndex(internal, index) {
|
||||
if (!this.secret) return false;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
|
||||
const path = `m/0'/${internal ? 1 : 0}/${index}`;
|
||||
const child = root.derivePath(path);
|
||||
|
|
|
@ -29,6 +29,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
|||
* @private
|
||||
*/
|
||||
_getWIFByIndex(internal, index) {
|
||||
if (!this.secret) return false;
|
||||
const mnemonic = this.secret;
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bitcoin.bip32.fromSeed(seed);
|
||||
|
|
|
@ -401,10 +401,4 @@ export class LegacyWallet extends AbstractWallet {
|
|||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getChangeAddressAsync() {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.getAddress());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
897
class/wallets/multisig-hd-wallet.js
Normal file
897
class/wallets/multisig-hd-wallet.js
Normal file
|
@ -0,0 +1,897 @@
|
|||
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
||||
import bip39 from 'bip39';
|
||||
import b58 from 'bs58check';
|
||||
import { decodeUR } from 'bc-ur';
|
||||
const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
const coinSelectAccumulative = require('coinselect/accumulative');
|
||||
const coinSelectSplit = require('coinselect/split');
|
||||
const HDNode = require('bip32');
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const createHash = require('create-hash');
|
||||
const reverse = require('buffer-reverse');
|
||||
|
||||
export class MultisigHDWallet extends AbstractHDElectrumWallet {
|
||||
static type = 'HDmultisig';
|
||||
static typeReadable = 'Multisig Vault';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._m = 0; // minimum required signatures so spend (m out of n)
|
||||
this._cosigners = []; // array of xpubs or mnemonic seeds
|
||||
this._cosignersFingerprints = []; // array of according fingerprints (if any provided)
|
||||
this._cosignersCustomPaths = []; // array of according paths (if any provided)
|
||||
this._derivationPath = '';
|
||||
this._isNativeSegwit = false;
|
||||
this._isWrappedSegwit = false;
|
||||
this._isLegacy = false;
|
||||
this.gap_limit = 10;
|
||||
}
|
||||
|
||||
isLegacy() {
|
||||
return this._isLegacy;
|
||||
}
|
||||
|
||||
isNativeSegwit() {
|
||||
return this._isNativeSegwit;
|
||||
}
|
||||
|
||||
isWrappedSegwit() {
|
||||
return this._isWrappedSegwit;
|
||||
}
|
||||
|
||||
setWrappedSegwit() {
|
||||
this._isWrappedSegwit = true;
|
||||
}
|
||||
|
||||
setNativeSegwit() {
|
||||
this._isNativeSegwit = true;
|
||||
}
|
||||
|
||||
setLegacy() {
|
||||
this._isLegacy = true;
|
||||
}
|
||||
|
||||
setM(m) {
|
||||
this._m = m;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} How many minumim signatures required to authorize a spend
|
||||
*/
|
||||
getM() {
|
||||
return this._m;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} Total count of cosigners
|
||||
*/
|
||||
getN() {
|
||||
return this._cosigners.length;
|
||||
}
|
||||
|
||||
setDerivationPath(path) {
|
||||
this._derivationPath = path;
|
||||
switch (this._derivationPath) {
|
||||
case "m/48'/0'/0'/2'":
|
||||
this._isNativeSegwit = true;
|
||||
break;
|
||||
case "m/48'/0'/0'/1'":
|
||||
this._isWrappedSegwit = true;
|
||||
break;
|
||||
case "m/45'":
|
||||
this._isLegacy = true;
|
||||
break;
|
||||
case "m/44'":
|
||||
this._isLegacy = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getDerivationPath() {
|
||||
return this._derivationPath;
|
||||
}
|
||||
|
||||
getCustomDerivationPathForCosigner(index) {
|
||||
if (index === 0) throw new Error('cosigners indexation starts from 1');
|
||||
return this._cosignersCustomPaths[index - 1] || this.getDerivationPath();
|
||||
}
|
||||
|
||||
getCosigner(index) {
|
||||
if (index === 0) throw new Error('cosigners indexation starts from 1');
|
||||
return this._cosigners[index - 1];
|
||||
}
|
||||
|
||||
getFingerprint(index) {
|
||||
if (index === 0) throw new Error('cosigners fingerprints indexation starts from 1');
|
||||
return this._cosignersFingerprints[index - 1];
|
||||
}
|
||||
|
||||
getCosignerForFingerprint(fp) {
|
||||
const index = this._cosignersFingerprints.indexOf(fp);
|
||||
return this._cosigners[index];
|
||||
}
|
||||
|
||||
static isXpubValid(key) {
|
||||
let xpub;
|
||||
|
||||
try {
|
||||
xpub = super._zpubToXpub(key);
|
||||
HDNode.fromBase58(xpub);
|
||||
return true;
|
||||
} catch (_) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key {string} Either xpub or mnemonic phrase
|
||||
* @param fingerprint {string} Fingerprint for cosigner that is added as xpub
|
||||
* @param path {string} Custom path (if any) for cosigner that is added as mnemonics
|
||||
*/
|
||||
addCosigner(key, fingerprint, path) {
|
||||
if (MultisigHDWallet.isXpubString(key) && !fingerprint) {
|
||||
throw new Error('fingerprint is required when adding cosigner as xpub (watch-only)');
|
||||
}
|
||||
|
||||
if (path && !this.constructor.isPathValid(path)) {
|
||||
throw new Error('path is not valid');
|
||||
}
|
||||
|
||||
if (!MultisigHDWallet.isXpubString(key)) {
|
||||
// mnemonics. lets derive fingerprint
|
||||
if (!bip39.validateMnemonic(key)) throw new Error('Not a valid mnemonic phrase');
|
||||
fingerprint = MultisigHDWallet.seedToFingerprint(key);
|
||||
} else {
|
||||
if (!MultisigHDWallet.isXpubValid(key)) throw new Error('Not a valid xpub: ' + key);
|
||||
}
|
||||
|
||||
const index = this._cosigners.length;
|
||||
this._cosigners[index] = key;
|
||||
if (fingerprint) this._cosignersFingerprints[index] = fingerprint.toUpperCase();
|
||||
if (path) this._cosignersCustomPaths[index] = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stored cosigner can be EITHER xpub (or Zpub or smth), OR mnemonic phrase. This method converts it to xpub
|
||||
*
|
||||
* @param cosigner {string} Zpub (or similar) or mnemonic seed
|
||||
* @returns {string} xpub
|
||||
* @private
|
||||
*/
|
||||
_getXpubFromCosigner(cosigner) {
|
||||
let xpub = cosigner;
|
||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
||||
const index = this._cosigners.indexOf(cosigner);
|
||||
xpub = MultisigHDWallet.seedToXpub(cosigner, this._cosignersCustomPaths[index] || this._derivationPath);
|
||||
}
|
||||
return this.constructor._zpubToXpub(xpub);
|
||||
}
|
||||
|
||||
_getExternalAddressByIndex(index) {
|
||||
if (!this._m) throw new Error('m is not set');
|
||||
index = +index;
|
||||
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
|
||||
|
||||
const address = this._getAddressFromNode(0, index);
|
||||
this.external_addresses_cache[index] = address;
|
||||
return address;
|
||||
}
|
||||
|
||||
_getAddressFromNode(nodeIndex, index) {
|
||||
const pubkeys = [];
|
||||
let cosignerIndex = 0;
|
||||
for (const cosigner of this._cosigners) {
|
||||
this._nodes = this._nodes || [];
|
||||
this._nodes[nodeIndex] = this._nodes[nodeIndex] || [];
|
||||
let _node;
|
||||
|
||||
if (!this._nodes[nodeIndex][cosignerIndex]) {
|
||||
const xpub = this._getXpubFromCosigner(cosigner);
|
||||
const hdNode = HDNode.fromBase58(xpub);
|
||||
_node = hdNode.derive(nodeIndex);
|
||||
} else {
|
||||
_node = this._nodes[nodeIndex][cosignerIndex];
|
||||
}
|
||||
|
||||
pubkeys.push(_node.derive(index).publicKey);
|
||||
cosignerIndex++;
|
||||
}
|
||||
|
||||
if (this.isWrappedSegwit()) {
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
}),
|
||||
});
|
||||
|
||||
return address;
|
||||
} else if (this.isNativeSegwit()) {
|
||||
const { address } = bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
});
|
||||
|
||||
return address;
|
||||
} else if (this.isLegacy()) {
|
||||
const { address } = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
});
|
||||
|
||||
return address;
|
||||
} else {
|
||||
throw new Error('Dont know how to make address');
|
||||
}
|
||||
}
|
||||
|
||||
_getInternalAddressByIndex(index) {
|
||||
if (!this._m) throw new Error('m is not set');
|
||||
index = +index;
|
||||
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
|
||||
|
||||
const address = this._getAddressFromNode(1, index);
|
||||
this.internal_addresses_cache[index] = address;
|
||||
return address;
|
||||
}
|
||||
|
||||
static seedToXpub(mnemonic, path) {
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bitcoin.bip32.fromSeed(seed);
|
||||
|
||||
const child = root.derivePath(path).neutered();
|
||||
this._xpub = child.toBase58();
|
||||
|
||||
return this._xpub;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mnemonic {string} Mnemonic seed phrase
|
||||
* @returns {string} Hex string of fingerprint derived from mnemonics. Always has lenght of 8 chars and correct leading zeroes
|
||||
*/
|
||||
static seedToFingerprint(mnemonic) {
|
||||
const seed = bip39.mnemonicToSeed(mnemonic);
|
||||
const root = bitcoin.bip32.fromSeed(seed);
|
||||
let hex = root.fingerprint.toString('hex');
|
||||
while (hex.length < 8) hex = '0' + hex; // leading zeroes
|
||||
return hex.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns xpub with correct prefix accodting to this objects set derivation path, for example 'Zpub' (with
|
||||
* capital Z) for bech32 multisig
|
||||
* @see https://github.com/satoshilabs/slips/blob/master/slip-0132.md
|
||||
*
|
||||
* @param xpub {string} Any kind of xpub, including zpub etc since we are only swapping the prefix bytes
|
||||
* @returns {string}
|
||||
*/
|
||||
convertXpubToMultisignatureXpub(xpub) {
|
||||
let data = b58.decode(xpub);
|
||||
data = data.slice(4);
|
||||
if (this.isNativeSegwit()) {
|
||||
return b58.encode(Buffer.concat([Buffer.from('02aa7ed3', 'hex'), data]));
|
||||
} else if (this.isWrappedSegwit()) {
|
||||
return b58.encode(Buffer.concat([Buffer.from('0295b43f', 'hex'), data]));
|
||||
}
|
||||
|
||||
return xpub;
|
||||
}
|
||||
|
||||
static isXpubString(xpub) {
|
||||
return ['xpub', 'ypub', 'zpub', 'Ypub', 'Zpub'].includes(xpub.substring(0, 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts fingerprint that is stored as a deciman number to hex string (all caps)
|
||||
*
|
||||
* @param xfp {number} For example 64392470
|
||||
* @returns {string} For example 168DD603
|
||||
*/
|
||||
static ckccXfp2fingerprint(xfp) {
|
||||
let masterFingerprintHex = Number(xfp).toString(16);
|
||||
while (masterFingerprintHex.length < 8) masterFingerprintHex = '0' + masterFingerprintHex; // conversion without explicit zero might result in lost byte
|
||||
|
||||
// poor man's little-endian conversion:
|
||||
// ¯\_(ツ)_/¯
|
||||
return (
|
||||
masterFingerprintHex[6] +
|
||||
masterFingerprintHex[7] +
|
||||
masterFingerprintHex[4] +
|
||||
masterFingerprintHex[5] +
|
||||
masterFingerprintHex[2] +
|
||||
masterFingerprintHex[3] +
|
||||
masterFingerprintHex[0] +
|
||||
masterFingerprintHex[1]
|
||||
).toUpperCase();
|
||||
}
|
||||
|
||||
getXpub() {
|
||||
return this.getSecret(true);
|
||||
}
|
||||
|
||||
getSecret(coordinationSetup = false) {
|
||||
let ret = '# BlueWallet Multisig setup file\n';
|
||||
if (coordinationSetup) ret += '# this file contains only public keys and is safe to\n# distribute among cosigners\n';
|
||||
if (!coordinationSetup) ret += '# this file may contain private information\n';
|
||||
ret += '#\n';
|
||||
ret += 'Name: ' + this.getLabel() + '\n';
|
||||
ret += 'Policy: ' + this.getM() + ' of ' + this.getN() + '\n';
|
||||
|
||||
let hasCustomPaths = 0;
|
||||
for (let index = 0; index < this.getN(); index++) {
|
||||
if (this._cosignersCustomPaths[index]) hasCustomPaths++;
|
||||
}
|
||||
|
||||
let printedGlobalDerivation = false;
|
||||
if (hasCustomPaths !== this.getN()) {
|
||||
printedGlobalDerivation = true;
|
||||
ret += 'Derivation: ' + this.getDerivationPath() + '\n';
|
||||
}
|
||||
|
||||
if (this.isNativeSegwit()) {
|
||||
ret += 'Format: P2WSH\n';
|
||||
} else if (this.isWrappedSegwit()) {
|
||||
ret += 'Format: P2WSH-P2SH\n';
|
||||
} else if (this.isLegacy()) {
|
||||
ret += 'Format: P2SH\n';
|
||||
} else {
|
||||
ret += 'Format: unknown\n';
|
||||
}
|
||||
ret += '\n';
|
||||
|
||||
for (let index = 0; index < this.getN(); index++) {
|
||||
if (
|
||||
this._cosignersCustomPaths[index] &&
|
||||
((printedGlobalDerivation && this._cosignersCustomPaths[index] !== this.getDerivationPath()) || !printedGlobalDerivation)
|
||||
) {
|
||||
ret += '# derivation: ' + this._cosignersCustomPaths[index] + '\n';
|
||||
// if we printed global derivation and this cosigned _has_ derivation and its different from global - we print it ;
|
||||
// or we print it if cosigner _has_ some derivation set and we did not print global
|
||||
}
|
||||
if (this.constructor.isXpubString(this._cosigners[index])) {
|
||||
ret += this._cosignersFingerprints[index] + ': ' + this._cosigners[index] + '\n';
|
||||
} else {
|
||||
if (coordinationSetup) {
|
||||
const xpub = this.convertXpubToMultisignatureXpub(
|
||||
MultisigHDWallet.seedToXpub(this._cosigners[index], this._cosignersCustomPaths[index] || this._derivationPath),
|
||||
);
|
||||
const fingerprint = MultisigHDWallet.seedToFingerprint(this._cosigners[index]);
|
||||
ret += fingerprint + ': ' + xpub + '\n';
|
||||
} else {
|
||||
ret += 'seed: ' + this._cosigners[index] + '\n';
|
||||
ret += '# warning! sensitive information, do not disclose ^^^ \n';
|
||||
}
|
||||
}
|
||||
|
||||
ret += '\n';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
setSecret(secret) {
|
||||
if (secret.toUpperCase().startsWith('UR:BYTES')) {
|
||||
const decoded = decodeUR([secret]);
|
||||
const b = Buffer.from(decoded, 'hex');
|
||||
secret = b.toString();
|
||||
}
|
||||
|
||||
// is it Coldcard json file?
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(secret);
|
||||
} catch (_) {}
|
||||
if (json && json.xfp && json.p2wsh_deriv && json.p2wsh) {
|
||||
this.addCosigner(json.p2wsh, json.xfp); // technically we dont need deriv (json.p2wsh_deriv), since cosigner is already an xpub
|
||||
return;
|
||||
}
|
||||
|
||||
// is it electrum json?
|
||||
if (json && json.wallet_type) {
|
||||
const mofn = json.wallet_type.split('of');
|
||||
this.setM(parseInt(mofn[0].trim()));
|
||||
const n = parseInt(mofn[1].trim());
|
||||
for (let c = 1; c <= n; c++) {
|
||||
const cosignerData = json['x' + c + '/'];
|
||||
if (cosignerData) {
|
||||
const fingerprint =
|
||||
(cosignerData.ckcc_xfp
|
||||
? MultisigHDWallet.ckccXfp2fingerprint(cosignerData.ckcc_xfp)
|
||||
: cosignerData.root_fingerprint?.toUpperCase()) || '00000000';
|
||||
if (cosignerData.seed) {
|
||||
// TODO: support electrum's bip32
|
||||
}
|
||||
this.addCosigner(cosignerData.xpub, fingerprint, cosignerData.derivation);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.getCosigner(1).startsWith('Zpub')) this.setNativeSegwit();
|
||||
if (this.getCosigner(1).startsWith('Ypub')) this.setWrappedSegwit();
|
||||
if (this.getCosigner(1).startsWith('xpub')) this.setLegacy();
|
||||
}
|
||||
|
||||
// coldcard & cobo txt format:
|
||||
let customPathForCurrentCosigner = false;
|
||||
for (const line of secret.split('\n')) {
|
||||
const [key, value] = line.split(':');
|
||||
|
||||
switch (key) {
|
||||
case 'Name':
|
||||
this.setLabel(value.trim());
|
||||
break;
|
||||
|
||||
case 'Policy':
|
||||
this.setM(parseInt(value.trim().split('of')[0].trim()));
|
||||
break;
|
||||
|
||||
case 'Derivation':
|
||||
this.setDerivationPath(value.trim());
|
||||
break;
|
||||
|
||||
case 'Format':
|
||||
switch (value.trim()) {
|
||||
case 'P2WSH':
|
||||
this.setNativeSegwit();
|
||||
break;
|
||||
case 'P2WSH-P2SH':
|
||||
this.setWrappedSegwit();
|
||||
break;
|
||||
case 'P2SH':
|
||||
this.setLegacy();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (key && value && MultisigHDWallet.isXpubString(value.trim())) {
|
||||
this.addCosigner(value.trim(), key, customPathForCurrentCosigner);
|
||||
} else if (key.replace('#', '').trim() === 'derivation') {
|
||||
customPathForCurrentCosigner = value.trim();
|
||||
} else if (key === 'seed') {
|
||||
this.addCosigner(value.trim(), false, customPathForCurrentCosigner);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// is it wallet descriptor?
|
||||
// @see https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
|
||||
// @see https://github.com/Fonta1n3/FullyNoded/blob/master/Docs/Wallets/Wallet-Export-Spec.md
|
||||
if (secret.indexOf('sortedmulti(') !== -1 && json.descriptor) {
|
||||
if (json.label) this.setLabel(json.label);
|
||||
if (json.descriptor.startsWith('wsh(')) {
|
||||
this.setNativeSegwit();
|
||||
}
|
||||
if (json.descriptor.startsWith('sh(')) {
|
||||
this.setLegacy();
|
||||
}
|
||||
if (json.descriptor.startsWith('sh(wsh(')) {
|
||||
this.setLegacy();
|
||||
}
|
||||
|
||||
const s2 = json.descriptor.substr(json.descriptor.indexOf('sortedmulti(') + 12);
|
||||
const s3 = s2.split(',');
|
||||
const m = parseInt(s3[0]);
|
||||
if (m) this.setM(m);
|
||||
|
||||
for (let c = 1; c < s3.length; c++) {
|
||||
const re = /\[([^\]]+)\](.*)/;
|
||||
const m = s3[c].match(re);
|
||||
if (m && m.length === 3) {
|
||||
let hexFingerprint = m[1].split('/')[0];
|
||||
if (hexFingerprint.length === 8) {
|
||||
hexFingerprint = Buffer.from(hexFingerprint, 'hex').reverse().toString('hex');
|
||||
}
|
||||
|
||||
const path = 'm/' + m[1].split('/').slice(1).join('/').replace(/[h]/g, "'");
|
||||
let xpub = m[2];
|
||||
if (xpub.indexOf('/') !== -1) {
|
||||
xpub = xpub.substr(0, xpub.indexOf('/'));
|
||||
}
|
||||
|
||||
// console.warn('m[2] = ', m[2], {hexFingerprint, path, xpub});
|
||||
this.addCosigner(xpub, hexFingerprint.toUpperCase(), path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// is it caravan?
|
||||
if (json && json.network === 'mainnet' && json.quorum) {
|
||||
this.setM(+json.quorum.requiredSigners);
|
||||
if (json.name) this.setLabel(json.name);
|
||||
|
||||
switch (json.addressType.toLowerCase()) {
|
||||
case 'P2SH':
|
||||
this.setLegacy();
|
||||
break;
|
||||
case 'P2SH-P2WSH':
|
||||
this.setWrappedSegwit();
|
||||
break;
|
||||
default:
|
||||
case 'P2WSH':
|
||||
this.setNativeSegwit();
|
||||
break;
|
||||
}
|
||||
|
||||
for (const pk of json.extendedPublicKeys) {
|
||||
const path = this.constructor.isPathValid(json.bip32Path) ? json.bip32Path : "m/1'";
|
||||
// wtf, where caravan stores fingerprints..?
|
||||
this.addCosigner(pk.xpub, '00000000', path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.getLabel()) this.setLabel('Multisig vault');
|
||||
}
|
||||
|
||||
_getDerivationPathByAddressWithCustomPath(address, customPathPrefix) {
|
||||
const path = customPathPrefix || this._derivationPath;
|
||||
for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) {
|
||||
if (this._getExternalAddressByIndex(c) === address) return path + '/0/' + c;
|
||||
}
|
||||
for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) {
|
||||
if (this._getInternalAddressByIndex(c) === address) return path + '/1/' + c;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_getWifForAddress(address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_getPubkeyByAddress(address) {
|
||||
throw new Error('Not applicable in multisig');
|
||||
}
|
||||
|
||||
_getDerivationPathByAddress(address) {
|
||||
throw new Error('Not applicable in multisig');
|
||||
}
|
||||
|
||||
_addPsbtInput(psbt, input, sequence, masterFingerprintBuffer) {
|
||||
const bip32Derivation = []; // array per each pubkey thats gona be used
|
||||
const pubkeys = [];
|
||||
for (let c = 0; c < this._cosigners.length; c++) {
|
||||
const cosigner = this._cosigners[c];
|
||||
const path = this._getDerivationPathByAddressWithCustomPath(input.address, this._cosignersCustomPaths[c] || this._derivationPath);
|
||||
// ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used
|
||||
const masterFingerprint = Buffer.from(this._cosignersFingerprints[c], 'hex');
|
||||
|
||||
const xpub = this._getXpubFromCosigner(cosigner);
|
||||
const hdNode0 = HDNode.fromBase58(xpub);
|
||||
const splt = path.split('/');
|
||||
const internal = +splt[splt.length - 2];
|
||||
const index = +splt[splt.length - 1];
|
||||
const _node0 = hdNode0.derive(internal);
|
||||
const pubkey = _node0.derive(index).publicKey;
|
||||
pubkeys.push(pubkey);
|
||||
|
||||
bip32Derivation.push({
|
||||
masterFingerprint,
|
||||
path,
|
||||
pubkey,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isNativeSegwit()) {
|
||||
const p2wsh = bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
});
|
||||
const witnessScript = p2wsh.redeem.output;
|
||||
|
||||
psbt.addInput({
|
||||
hash: input.txId,
|
||||
index: input.vout,
|
||||
sequence,
|
||||
bip32Derivation,
|
||||
witnessUtxo: {
|
||||
script: p2wsh.output,
|
||||
value: input.value,
|
||||
},
|
||||
witnessScript,
|
||||
// hw wallets now require passing the whole previous tx as Buffer, as if it was non-segwit input, to mitigate
|
||||
// some hw wallets attack vector
|
||||
nonWitnessUtxo: Buffer.from(input.txhex, 'hex'),
|
||||
});
|
||||
} else if (this.isWrappedSegwit()) {
|
||||
const p2shP2wsh = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
}),
|
||||
});
|
||||
const witnessScript = p2shP2wsh.redeem.redeem.output;
|
||||
const redeemScript = p2shP2wsh.redeem.output;
|
||||
|
||||
psbt.addInput({
|
||||
hash: input.txId,
|
||||
index: input.vout,
|
||||
sequence,
|
||||
bip32Derivation,
|
||||
witnessUtxo: {
|
||||
script: p2shP2wsh.output,
|
||||
value: input.value,
|
||||
},
|
||||
witnessScript,
|
||||
redeemScript,
|
||||
// hw wallets now require passing the whole previous tx as Buffer, as if it was non-segwit input, to mitigate
|
||||
// some hw wallets attack vector
|
||||
nonWitnessUtxo: Buffer.from(input.txhex, 'hex'),
|
||||
});
|
||||
} else if (this.isLegacy()) {
|
||||
const p2sh = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
});
|
||||
const redeemScript = p2sh.redeem.output;
|
||||
psbt.addInput({
|
||||
hash: input.txId,
|
||||
index: input.vout,
|
||||
sequence,
|
||||
bip32Derivation,
|
||||
redeemScript,
|
||||
nonWitnessUtxo: Buffer.from(input.txhex, 'hex'),
|
||||
});
|
||||
} else {
|
||||
throw new Error('Dont know how to add input');
|
||||
}
|
||||
|
||||
return psbt;
|
||||
}
|
||||
|
||||
_getOutputDataForChange(outputData) {
|
||||
const bip32Derivation = []; // array per each pubkey thats gona be used
|
||||
const pubkeys = [];
|
||||
for (let c = 0; c < this._cosigners.length; c++) {
|
||||
const cosigner = this._cosigners[c];
|
||||
const path = this._getDerivationPathByAddressWithCustomPath(
|
||||
outputData.address,
|
||||
this._cosignersCustomPaths[c] || this._derivationPath,
|
||||
);
|
||||
// ^^ path resembles _custom path_, if provided by user during setup, otherwise default path for wallet type gona be used
|
||||
const masterFingerprint = Buffer.from(this._cosignersFingerprints[c], 'hex');
|
||||
|
||||
const xpub = this._getXpubFromCosigner(cosigner);
|
||||
const hdNode0 = HDNode.fromBase58(xpub);
|
||||
const splt = path.split('/');
|
||||
const internal = +splt[splt.length - 2];
|
||||
const index = +splt[splt.length - 1];
|
||||
const _node0 = hdNode0.derive(internal);
|
||||
const pubkey = _node0.derive(index).publicKey;
|
||||
pubkeys.push(pubkey);
|
||||
|
||||
bip32Derivation.push({
|
||||
masterFingerprint,
|
||||
path,
|
||||
pubkey,
|
||||
});
|
||||
}
|
||||
|
||||
outputData.bip32Derivation = bip32Derivation;
|
||||
|
||||
if (this.isLegacy()) {
|
||||
const p2sh = bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) });
|
||||
outputData.redeemScript = p2sh.output;
|
||||
} else if (this.isWrappedSegwit()) {
|
||||
const p2shP2wsh = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
}),
|
||||
});
|
||||
outputData.witnessScript = p2shP2wsh.redeem.redeem.output;
|
||||
outputData.redeemScript = p2shP2wsh.redeem.output;
|
||||
} else if (this.isNativeSegwit()) {
|
||||
// not needed by coldcard, apparently..?
|
||||
const p2wsh = bitcoin.payments.p2wsh({
|
||||
redeem: bitcoin.payments.p2ms({ m: this._m, pubkeys: MultisigHDWallet.sortBuffers(pubkeys) }),
|
||||
});
|
||||
outputData.witnessScript = p2wsh.redeem.output;
|
||||
} else {
|
||||
throw new Error('dont know how to add change output');
|
||||
}
|
||||
|
||||
return outputData;
|
||||
}
|
||||
|
||||
howManySignaturesCanWeMake() {
|
||||
let howManyPrivKeysWeGot = 0;
|
||||
for (const cosigner of this._cosigners) {
|
||||
if (!MultisigHDWallet.isXpubString(cosigner)) howManyPrivKeysWeGot++;
|
||||
}
|
||||
|
||||
return howManyPrivKeysWeGot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) {
|
||||
if (targets.length === 0) throw new Error('No destination provided');
|
||||
if (this.howManySignaturesCanWeMake() === 0) skipSigning = true;
|
||||
|
||||
if (!changeAddress) throw new Error('No change address provided');
|
||||
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
|
||||
|
||||
let algo = coinSelectAccumulative;
|
||||
if (targets.length === 1 && targets[0] && !targets[0].value) {
|
||||
// we want to send MAX
|
||||
algo = coinSelectSplit;
|
||||
}
|
||||
|
||||
const { inputs, outputs, fee } = algo(utxos, targets, feeRate);
|
||||
|
||||
// .inputs and .outputs will be undefined if no solution was found
|
||||
if (!inputs || !outputs) {
|
||||
throw new Error('Not enough balance. Try sending smaller amount');
|
||||
}
|
||||
|
||||
let psbt = new bitcoin.Psbt();
|
||||
|
||||
let c = 0;
|
||||
inputs.forEach(input => {
|
||||
c++;
|
||||
psbt = this._addPsbtInput(psbt, input, sequence);
|
||||
});
|
||||
|
||||
outputs.forEach(output => {
|
||||
// if output has no address - this is change output
|
||||
let change = false;
|
||||
if (!output.address) {
|
||||
change = true;
|
||||
output.address = changeAddress;
|
||||
}
|
||||
|
||||
let outputData = {
|
||||
address: output.address,
|
||||
value: output.value,
|
||||
};
|
||||
|
||||
if (change) {
|
||||
outputData = this._getOutputDataForChange(outputData);
|
||||
}
|
||||
|
||||
psbt.addOutput(outputData);
|
||||
});
|
||||
|
||||
if (!skipSigning) {
|
||||
for (let cc = 0; cc < c; cc++) {
|
||||
for (const cosigner of this._cosigners) {
|
||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
||||
// ok this is a mnemonic, lets try to sign
|
||||
const seed = bip39.mnemonicToSeed(cosigner);
|
||||
const hdRoot = bitcoin.bip32.fromSeed(seed);
|
||||
psbt.signInputHD(cc, hdRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tx;
|
||||
if (!skipSigning && this.howManySignaturesCanWeMake() >= this.getM()) {
|
||||
tx = psbt.finalizeAllInputs().extractTransaction();
|
||||
}
|
||||
return { tx, inputs, outputs, fee, psbt };
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki
|
||||
*
|
||||
* @param bufArr {Array.<Buffer>}
|
||||
* @returns {Array.<Buffer>}
|
||||
*/
|
||||
static sortBuffers(bufArr) {
|
||||
return bufArr.sort(Buffer.compare);
|
||||
}
|
||||
|
||||
prepareForSerialization() {
|
||||
// deleting structures that cant be serialized
|
||||
delete this._nodes;
|
||||
}
|
||||
|
||||
static isPathValid(path) {
|
||||
const root = bitcoin.bip32.fromSeed(Buffer.alloc(32));
|
||||
try {
|
||||
root.derivePath(path);
|
||||
return true;
|
||||
} catch (_) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
allowSend() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async fetchUtxo() {
|
||||
await super.fetchUtxo();
|
||||
// now we need to fetch txhash for each input as required by PSBT
|
||||
const txhexes = await BlueElectrum.multiGetTransactionByTxid(
|
||||
this.getUtxo().map(x => x.txid),
|
||||
50,
|
||||
false,
|
||||
);
|
||||
|
||||
const newUtxos = [];
|
||||
for (const u of this.getUtxo()) {
|
||||
if (txhexes[u.txid]) u.txhex = txhexes[u.txid];
|
||||
newUtxos.push(u);
|
||||
}
|
||||
|
||||
return newUtxos;
|
||||
}
|
||||
|
||||
getID() {
|
||||
const string2hash = [...this._cosigners].sort().join(',') + ';' + [...this._cosignersFingerprints].sort().join(',');
|
||||
return createHash('sha256').update(string2hash).digest().toString('hex');
|
||||
}
|
||||
|
||||
calculateFeeFromPsbt(psbt) {
|
||||
let goesIn = 0;
|
||||
const cacheUtxoAmounts = {};
|
||||
for (const inp of psbt.data.inputs) {
|
||||
if (inp.witnessUtxo && inp.witnessUtxo.value) {
|
||||
// segwit input
|
||||
goesIn += inp.witnessUtxo.value;
|
||||
} else if (inp.nonWitnessUtxo) {
|
||||
// non-segwit input
|
||||
// lets parse this transaction and cache how much each input was worth
|
||||
const inputTx = bitcoin.Transaction.fromHex(inp.nonWitnessUtxo);
|
||||
let index = 0;
|
||||
for (const out of inputTx.outs) {
|
||||
cacheUtxoAmounts[inputTx.getId() + ':' + index] = out.value;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (goesIn === 0) {
|
||||
// means we failed to get amounts that go in previously, so lets use utxo amounts cache we've build
|
||||
// from non-segwit inputs
|
||||
for (const inp of psbt.txInputs) {
|
||||
const cacheKey = reverse(inp.hash).toString('hex') + ':' + inp.index;
|
||||
if (cacheUtxoAmounts[cacheKey]) goesIn += cacheUtxoAmounts[cacheKey];
|
||||
}
|
||||
}
|
||||
|
||||
let goesOut = 0;
|
||||
for (const output of psbt.txOutputs) {
|
||||
goesOut += output.value;
|
||||
}
|
||||
|
||||
return goesIn - goesOut;
|
||||
}
|
||||
|
||||
calculateHowManySignaturesWeHaveFromPsbt(psbt) {
|
||||
let sigsHave = 0;
|
||||
for (const inp of psbt.data.inputs) {
|
||||
sigsHave = Math.max(sigsHave, inp.partialSig?.length || 0);
|
||||
if (inp.finalScriptSig || inp.finalScriptWitness) sigsHave = this.getM(); // hacky, but it means we have enough
|
||||
// He who knows that enough is enough will always have enough. Lao Tzu
|
||||
}
|
||||
|
||||
return sigsHave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to signs passed psbt object (by reference). If there are enough signatures - tries to finalize psbt
|
||||
* and returns Transaction (ready to extract hex)
|
||||
*
|
||||
* @param psbt {Psbt}
|
||||
* @returns {{ tx: Transaction }}
|
||||
*/
|
||||
cosignPsbt(psbt) {
|
||||
for (let cc = 0; cc < psbt.inputCount; cc++) {
|
||||
for (const cosigner of this._cosigners) {
|
||||
if (!MultisigHDWallet.isXpubString(cosigner)) {
|
||||
// ok this is a mnemonic, lets try to sign
|
||||
const seed = bip39.mnemonicToSeed(cosigner);
|
||||
const hdRoot = bitcoin.bip32.fromSeed(seed);
|
||||
try {
|
||||
psbt.signInputHD(cc, hdRoot);
|
||||
} catch (_) {} // protects agains duplicate cosignings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tx = false;
|
||||
if (this.calculateHowManySignaturesWeHaveFromPsbt(psbt) >= this.getM()) {
|
||||
tx = psbt.finalizeAllInputs().extractTransaction();
|
||||
}
|
||||
|
||||
return { tx };
|
||||
}
|
||||
}
|
|
@ -9,6 +9,11 @@ export class PlaceholderWallet extends AbstractWallet {
|
|||
this._isFailure = false;
|
||||
}
|
||||
|
||||
setSecret(newSecret) {
|
||||
// so TRY AGAIN when something goes wrong during import has more consistent prefilled text
|
||||
this.secret = newSecret;
|
||||
}
|
||||
|
||||
allowSend() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -120,16 +120,26 @@ export class WatchOnlyWallet extends LegacyWallet {
|
|||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
async _getExternalAddressByIndex(index) {
|
||||
_getExternalAddressByIndex(index) {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index);
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
_getInternalAddressByIndex(index) {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance._getInternalAddressByIndex(index);
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
getNextFreeAddressIndex() {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.next_free_address_index;
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
getNextFreeChangeAddressIndex() {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.next_free_change_address_index;
|
||||
throw new Error('Not initialized');
|
||||
}
|
||||
|
||||
async getChangeAddressAsync() {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync();
|
||||
throw new Error('Not initialized');
|
||||
|
|
180
components/DynamicQRCode.js
Normal file
180
components/DynamicQRCode.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
|
||||
import React, { Component } from 'react';
|
||||
import { Text } from 'react-native-elements';
|
||||
import { Dimensions, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import { encodeUR } from 'bc-ur/dist';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import { BlueCurrentTheme } from '../components/themes';
|
||||
import { BlueSpacing20 } from '../BlueComponents';
|
||||
import loc from '../loc';
|
||||
|
||||
const { height, width } = Dimensions.get('window');
|
||||
|
||||
export class DynamicQRCode extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
const qrCodeHeight = height > width ? width - 40 : width / 3;
|
||||
const qrCodeMaxHeight = 370;
|
||||
this.state = {
|
||||
index: 0,
|
||||
total: 0,
|
||||
qrCodeHeight: Math.min(qrCodeHeight, qrCodeMaxHeight),
|
||||
intervalHandler: null,
|
||||
};
|
||||
}
|
||||
|
||||
fragments = [];
|
||||
|
||||
componentDidMount() {
|
||||
const { value, capacity = 800, hideControls = true } = this.props;
|
||||
this.fragments = encodeUR(value, capacity);
|
||||
this.setState(
|
||||
{
|
||||
total: this.fragments.length,
|
||||
hideControls,
|
||||
},
|
||||
() => {
|
||||
this.startAutoMove();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
moveToNextFragment = () => {
|
||||
const { index, total } = this.state;
|
||||
if (index === total - 1) {
|
||||
this.setState({
|
||||
index: 0,
|
||||
});
|
||||
} else {
|
||||
this.setState(state => ({
|
||||
index: state.index + 1,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
startAutoMove = () => {
|
||||
if (!this.state.intervalHandler)
|
||||
this.setState(() => ({
|
||||
intervalHandler: setInterval(this.moveToNextFragment, 500),
|
||||
}));
|
||||
};
|
||||
|
||||
stopAutoMove = () => {
|
||||
clearInterval(this.state.intervalHandler);
|
||||
this.setState(() => ({
|
||||
intervalHandler: null,
|
||||
}));
|
||||
};
|
||||
|
||||
moveToPreviousFragment = () => {
|
||||
const { index, total } = this.state;
|
||||
if (index > 0) {
|
||||
this.setState(state => ({
|
||||
index: state.index - 1,
|
||||
}));
|
||||
} else {
|
||||
this.setState(state => ({
|
||||
index: total - 1,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const currentFragment = this.fragments[this.state.index];
|
||||
|
||||
if (!currentFragment) {
|
||||
return (
|
||||
<View>
|
||||
<Text>{loc.send.dynamic_init}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={animatedQRCodeStyle.container}>
|
||||
<TouchableOpacity
|
||||
style={animatedQRCodeStyle.qrcodeContainer}
|
||||
onPress={() => {
|
||||
this.setState(prevState => ({ hideControls: !prevState.hideControls }));
|
||||
}}
|
||||
>
|
||||
<QRCode
|
||||
value={currentFragment.toUpperCase()}
|
||||
size={this.state.qrCodeHeight}
|
||||
color="#000000"
|
||||
logoBackgroundColor={BlueCurrentTheme.colors.brandingColor}
|
||||
backgroundColor="#FFFFFF"
|
||||
ecl="L"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{!this.state.hideControls && (
|
||||
<View style={animatedQRCodeStyle.container}>
|
||||
<BlueSpacing20 />
|
||||
<View>
|
||||
<Text style={animatedQRCodeStyle.text}>
|
||||
{loc.formatString(loc._.of, { number: this.state.index + 1, total: this.state.total })}
|
||||
</Text>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<View style={animatedQRCodeStyle.controller}>
|
||||
<TouchableOpacity
|
||||
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-start' }]}
|
||||
onPress={this.moveToPreviousFragment}
|
||||
>
|
||||
<Text style={animatedQRCodeStyle.text}>{loc.send.dynamic_prev}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[animatedQRCodeStyle.button, { width: '50%' }]}
|
||||
onPress={this.state.intervalHandler ? this.stopAutoMove : this.startAutoMove}
|
||||
>
|
||||
<Text style={animatedQRCodeStyle.text}>{this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[animatedQRCodeStyle.button, { width: '25%', alignItems: 'flex-end' }]}
|
||||
onPress={this.moveToNextFragment}
|
||||
>
|
||||
<Text style={animatedQRCodeStyle.text}>{loc.send.dynamic_next}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const animatedQRCodeStyle = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
qrcodeContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderWidth: 6,
|
||||
borderRadius: 8,
|
||||
borderColor: '#FFFFFF',
|
||||
margin: 6,
|
||||
},
|
||||
controller: {
|
||||
width: '90%',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderRadius: 25,
|
||||
height: 45,
|
||||
paddingHorizontal: 18,
|
||||
},
|
||||
button: {
|
||||
alignItems: 'center',
|
||||
height: 45,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
text: {
|
||||
fontSize: 14,
|
||||
color: BlueCurrentTheme.colors.foregroundColor,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
118
components/FloatButtons.js
Normal file
118
components/FloatButtons.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
import React, { useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, TouchableOpacity, StyleSheet, Dimensions, PixelRatio } from 'react-native';
|
||||
|
||||
import { BlueCurrentTheme } from './themes';
|
||||
|
||||
const BORDER_RADIUS = 30;
|
||||
const PADDINGS = 8;
|
||||
const ICON_MARGIN = 7;
|
||||
|
||||
const cStyles = StyleSheet.create({
|
||||
root: {
|
||||
position: 'absolute',
|
||||
alignSelf: 'center',
|
||||
height: '6.3%',
|
||||
minHeight: 44,
|
||||
},
|
||||
rootPre: {
|
||||
bottom: -1000,
|
||||
},
|
||||
rootPost: {
|
||||
bottom: 30,
|
||||
borderRadius: BORDER_RADIUS,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
});
|
||||
|
||||
export const FContainer = ({ children }) => {
|
||||
const [newWidth, setNewWidth] = useState();
|
||||
const layoutCalculated = useRef(false);
|
||||
|
||||
const onLayout = event => {
|
||||
if (layoutCalculated.current) return;
|
||||
const maxWidth = Dimensions.get('window').width - BORDER_RADIUS - 20;
|
||||
const { width } = event.nativeEvent.layout;
|
||||
const withPaddings = Math.ceil(width + PADDINGS * 2);
|
||||
const len = React.Children.toArray(children).filter(Boolean).length;
|
||||
let newWidth = withPaddings * len > maxWidth ? Math.floor(maxWidth / len) : withPaddings;
|
||||
if (len === 1 && newWidth < 90) newWidth = 90; // to add Paddings for lonely small button, like Scan on main screen
|
||||
setNewWidth(newWidth);
|
||||
layoutCalculated.current = true;
|
||||
};
|
||||
|
||||
return (
|
||||
<View onLayout={onLayout} style={[cStyles.root, newWidth ? cStyles.rootPost : cStyles.rootPre]}>
|
||||
{newWidth
|
||||
? React.Children.toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((c, index, array) =>
|
||||
React.cloneElement(c, {
|
||||
width: newWidth,
|
||||
key: index,
|
||||
first: index === 0,
|
||||
last: index === array.length - 1,
|
||||
}),
|
||||
)
|
||||
: children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
FContainer.propTypes = {
|
||||
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.element), PropTypes.element]),
|
||||
};
|
||||
|
||||
const buttonFontSize =
|
||||
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
|
||||
? 22
|
||||
: PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26);
|
||||
|
||||
const bStyles = StyleSheet.create({
|
||||
root: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
|
||||
},
|
||||
icon: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
text: {
|
||||
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
|
||||
fontSize: buttonFontSize,
|
||||
fontWeight: '600',
|
||||
marginLeft: ICON_MARGIN,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
export const FButton = ({ text, icon, width, first, last, ...props }) => {
|
||||
const style = {};
|
||||
if (width) {
|
||||
const paddingLeft = first ? BORDER_RADIUS / 2 : PADDINGS;
|
||||
const paddingRight = last ? BORDER_RADIUS / 2 : PADDINGS;
|
||||
style.paddingRight = paddingRight;
|
||||
style.paddingLeft = paddingLeft;
|
||||
style.width = width + paddingRight + paddingLeft;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity style={[bStyles.root, style]} {...props}>
|
||||
<View style={bStyles.icon}>{icon}</View>
|
||||
<Text numberOfLines={1} style={bStyles.text}>
|
||||
{text}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
FButton.propTypes = {
|
||||
text: PropTypes.string,
|
||||
icon: PropTypes.element,
|
||||
width: PropTypes.number,
|
||||
first: PropTypes.bool,
|
||||
last: PropTypes.bool,
|
||||
};
|
38
components/SquareButton.js
Normal file
38
components/SquareButton.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
|
||||
import React from 'react';
|
||||
import { TouchableOpacity, View, Text } from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
|
||||
export const SquareButton = props => {
|
||||
const { colors } = useTheme();
|
||||
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonBlueBackgroundColor;
|
||||
let fontColor = colors.buttonTextColor;
|
||||
if (props.disabled === true) {
|
||||
backgroundColor = colors.buttonDisabledBackgroundColor;
|
||||
fontColor = colors.buttonDisabledTextColor;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flex: 1,
|
||||
borderWidth: 0.7,
|
||||
borderColor: 'transparent',
|
||||
backgroundColor: backgroundColor,
|
||||
minHeight: 50,
|
||||
height: 50,
|
||||
maxHeight: 50,
|
||||
borderRadius: 10,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
|
||||
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
|
||||
{props.title && <Text style={{ marginHorizontal: 8, fontSize: 16, color: fontColor }}>{props.title}</Text>}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
|
@ -54,6 +54,8 @@ export const BlueDefaultTheme = {
|
|||
mainColor: '#CFDCF6',
|
||||
success: '#ccddf9',
|
||||
successCheck: '#0f5cc0',
|
||||
msSuccessBG: '#37c0a1',
|
||||
msSuccessCheck: '#ffffff',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -96,6 +98,8 @@ export const BlueDarkTheme = {
|
|||
buttonBlueBackgroundColor: '#202020',
|
||||
scanLabel: 'rgba(255,255,255,.2)',
|
||||
labelText: '#ffffff',
|
||||
msSuccessBG: '#8EFFE5',
|
||||
msSuccessCheck: '#000000',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
BIN
img/vault-shape.png
Normal file
BIN
img/vault-shape.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
10
index.js
10
index.js
|
@ -1,15 +1,7 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import './shim.js';
|
||||
import { AppRegistry, YellowBox } from 'react-native';
|
||||
import { AppRegistry } from 'react-native';
|
||||
import App from './App';
|
||||
YellowBox.ignoreWarnings([
|
||||
'Require cycle',
|
||||
'Non-serializable values were',
|
||||
"Can't perform a React state update",
|
||||
'{"code":404',
|
||||
'React has detected a change in the order of Hooks',
|
||||
]);
|
||||
|
||||
const A = require('./blue_modules/analytics');
|
||||
|
||||
if (!Error.captureStackTrace) {
|
||||
|
|
|
@ -939,43 +939,12 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources.sh",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
@ -1281,6 +1250,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 5.6.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
@ -1320,6 +1290,7 @@
|
|||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 5.6.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
@ -1575,6 +1546,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 239;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
@ -1611,6 +1583,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 239;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = A7W54YZ4WU;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#import "RNQuickActionManager.h"
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#import <RNCPushNotificationIOS.h>
|
||||
#ifdef FB_SONARKIT_ENABLED
|
||||
#if DEBUG
|
||||
#import <FlipperKit/FlipperClient.h>
|
||||
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
|
||||
|
@ -31,13 +32,13 @@ static void InitializeFlipper(UIApplication *application) {
|
|||
[client start];
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
#if DEBUG
|
||||
#ifdef FB_SONARKIT_ENABLED
|
||||
InitializeFlipper(application);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -36,6 +36,34 @@
|
|||
<string>io.bluewallet.psbt.txn</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>TXT</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.txt</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>JSON</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>io.bluewallet.json</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
@ -101,7 +129,7 @@
|
|||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>This alert should not show up as we do not require this data</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<string>In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code.</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>In order to use FaceID please confirm your permission.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
|
@ -241,6 +269,44 @@
|
|||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.plain-text</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Text File</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.txt</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>txt</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.json</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>JSON File</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.bluewallet.json</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>json</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.6.0</string>
|
||||
<string>5.6.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>239</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
|
|
@ -14,6 +14,7 @@ enum WalletGradient: String {
|
|||
case LightningCustodial = "lightningCustodianWallet"
|
||||
case SegwitNative = "HDsegwitBech32"
|
||||
case WatchOnly = "watchOnly"
|
||||
case MultiSig = "HDmultisig"
|
||||
|
||||
var imageString: String{
|
||||
switch self {
|
||||
|
@ -27,6 +28,8 @@ enum WalletGradient: String {
|
|||
return "walletWatchOnly"
|
||||
case .LightningCustodial:
|
||||
return "walletLightningCustodial"
|
||||
case .MultiSig:
|
||||
return "watchMultisig"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/Contents.json
vendored
Normal file
23
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "multisig-watch.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "multisig-watch@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "multisig-watch@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/multisig-watch.png
vendored
Normal file
BIN
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/multisig-watch.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/multisig-watch@2x.png
vendored
Normal file
BIN
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/multisig-watch@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/multisig-watch@3x.png
vendored
Normal file
BIN
ios/BlueWalletWatch/Assets.xcassets/watchMultisig.imageset/multisig-watch@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.6.0</string>
|
||||
<string>5.6.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>239</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
|
83
ios/Podfile
83
ios/Podfile
|
@ -1,91 +1,18 @@
|
|||
platform :ios, '10.0'
|
||||
workspace 'BlueWallet'
|
||||
require_relative '../node_modules/react-native/scripts/react_native_pods'
|
||||
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
|
||||
|
||||
def add_flipper_pods!(versions = {})
|
||||
versions['Flipper'] ||= '~> 0.37.0'
|
||||
versions['DoubleConversion'] ||= '1.1.7'
|
||||
versions['Flipper-Folly'] ||= '~> 2.1'
|
||||
versions['Flipper-Glog'] ||= '0.3.6'
|
||||
versions['Flipper-PeerTalk'] ||= '~> 0.0.4'
|
||||
versions['Flipper-RSocket'] ||= '~> 1.0'
|
||||
|
||||
pod 'FlipperKit', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitLayoutPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/SKIOSNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitReactPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
|
||||
# List all transitive dependencies for FlipperKit pods
|
||||
# to avoid them being linked in Release builds
|
||||
pod 'Flipper', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'Flipper-DoubleConversion', versions['DoubleConversion'], :configuration => 'Debug'
|
||||
pod 'Flipper-Folly', versions['Flipper-Folly'], :configuration => 'Debug'
|
||||
pod 'Flipper-Glog', versions['Flipper-Glog'], :configuration => 'Debug'
|
||||
pod 'Flipper-PeerTalk', versions['Flipper-PeerTalk'], :configuration => 'Debug'
|
||||
pod 'Flipper-RSocket', versions['Flipper-RSocket'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/Core', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/CppBridge', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FBCxxFollyDynamicConvert', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FBDefines', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FKPortForwarding', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitHighlightOverlay', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitLayoutTextSearchable', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
end
|
||||
|
||||
# Post Install processing for Flipper
|
||||
def flipper_post_install(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
if target.name == 'YogaKit'
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['SWIFT_VERSION'] = '4.1'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
target 'BlueWallet' do
|
||||
# Pods for RnDiffApp
|
||||
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
|
||||
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
|
||||
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
|
||||
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
|
||||
pod 'React', :path => '../node_modules/react-native/'
|
||||
pod 'React-Core', :path => '../node_modules/react-native/'
|
||||
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
|
||||
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
|
||||
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
|
||||
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
|
||||
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
|
||||
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
|
||||
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
|
||||
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
|
||||
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
|
||||
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
|
||||
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
|
||||
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
|
||||
config = use_native_modules!
|
||||
|
||||
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
|
||||
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
|
||||
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
|
||||
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
|
||||
pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
|
||||
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
|
||||
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
|
||||
|
||||
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
||||
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
||||
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
||||
|
||||
|
||||
use_native_modules!
|
||||
use_react_native!(:path => config["reactNativePath"])
|
||||
|
||||
# Enables Flipper.
|
||||
#
|
||||
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
||||
# you should disable these next few lines.
|
||||
add_flipper_pods!
|
||||
use_flipper!
|
||||
post_install do |installer|
|
||||
flipper_post_install(installer)
|
||||
end
|
||||
|
|
545
ios/Podfile.lock
545
ios/Podfile.lock
|
@ -5,15 +5,15 @@ PODS:
|
|||
- CocoaAsyncSocket (7.6.4)
|
||||
- CocoaLibEvent (1.0.0)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.62.2)
|
||||
- FBReactNativeSpec (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTRequired (= 0.62.2)
|
||||
- RCTTypeSafety (= 0.62.2)
|
||||
- React-Core (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- Flipper (0.37.0):
|
||||
- FBLazyVector (0.63.3)
|
||||
- FBReactNativeSpec (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- RCTRequired (= 0.63.3)
|
||||
- RCTTypeSafety (= 0.63.3)
|
||||
- React-Core (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- Flipper (0.54.0):
|
||||
- Flipper-Folly (~> 2.2)
|
||||
- Flipper-RSocket (~> 1.1)
|
||||
- Flipper-DoubleConversion (1.1.7)
|
||||
|
@ -27,44 +27,44 @@ PODS:
|
|||
- Flipper-PeerTalk (0.0.4)
|
||||
- Flipper-RSocket (1.1.0):
|
||||
- Flipper-Folly (~> 2.2)
|
||||
- FlipperKit (0.37.0):
|
||||
- FlipperKit/Core (= 0.37.0)
|
||||
- FlipperKit/Core (0.37.0):
|
||||
- Flipper (~> 0.37.0)
|
||||
- FlipperKit (0.54.0):
|
||||
- FlipperKit/Core (= 0.54.0)
|
||||
- FlipperKit/Core (0.54.0):
|
||||
- Flipper (~> 0.54.0)
|
||||
- FlipperKit/CppBridge
|
||||
- FlipperKit/FBCxxFollyDynamicConvert
|
||||
- FlipperKit/FBDefines
|
||||
- FlipperKit/FKPortForwarding
|
||||
- FlipperKit/CppBridge (0.37.0):
|
||||
- Flipper (~> 0.37.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.37.0):
|
||||
- FlipperKit/CppBridge (0.54.0):
|
||||
- Flipper (~> 0.54.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.54.0):
|
||||
- Flipper-Folly (~> 2.2)
|
||||
- FlipperKit/FBDefines (0.37.0)
|
||||
- FlipperKit/FKPortForwarding (0.37.0):
|
||||
- FlipperKit/FBDefines (0.54.0)
|
||||
- FlipperKit/FKPortForwarding (0.54.0):
|
||||
- CocoaAsyncSocket (~> 7.6)
|
||||
- Flipper-PeerTalk (~> 0.0.4)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.37.0)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.37.0):
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.54.0)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.54.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- YogaKit (~> 1.18)
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.37.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.37.0):
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.54.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.54.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitReactPlugin (0.37.0):
|
||||
- FlipperKit/FlipperKitReactPlugin (0.54.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.37.0):
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.54.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.37.0):
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.54.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitNetworkPlugin
|
||||
- Folly (2018.10.22.00):
|
||||
- Folly (2020.01.13.00):
|
||||
- boost-for-react-native
|
||||
- DoubleConversion
|
||||
- Folly/Default (= 2018.10.22.00)
|
||||
- Folly/Default (= 2020.01.13.00)
|
||||
- glog
|
||||
- Folly/Default (2018.10.22.00):
|
||||
- Folly/Default (2020.01.13.00):
|
||||
- boost-for-react-native
|
||||
- DoubleConversion
|
||||
- glog
|
||||
|
@ -81,169 +81,172 @@ PODS:
|
|||
- OpenSSL-Universal/Static (1.0.2.19)
|
||||
- PasscodeAuth (1.0.0):
|
||||
- React
|
||||
- RCTRequired (0.62.2)
|
||||
- RCTTypeSafety (0.62.2):
|
||||
- FBLazyVector (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTRequired (= 0.62.2)
|
||||
- React-Core (= 0.62.2)
|
||||
- React (0.62.2):
|
||||
- React-Core (= 0.62.2)
|
||||
- React-Core/DevSupport (= 0.62.2)
|
||||
- React-Core/RCTWebSocket (= 0.62.2)
|
||||
- React-RCTActionSheet (= 0.62.2)
|
||||
- React-RCTAnimation (= 0.62.2)
|
||||
- React-RCTBlob (= 0.62.2)
|
||||
- React-RCTImage (= 0.62.2)
|
||||
- React-RCTLinking (= 0.62.2)
|
||||
- React-RCTNetwork (= 0.62.2)
|
||||
- React-RCTSettings (= 0.62.2)
|
||||
- React-RCTText (= 0.62.2)
|
||||
- React-RCTVibration (= 0.62.2)
|
||||
- React-Core (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTRequired (0.63.3)
|
||||
- RCTTypeSafety (0.63.3):
|
||||
- FBLazyVector (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- RCTRequired (= 0.63.3)
|
||||
- React-Core (= 0.63.3)
|
||||
- React (0.63.3):
|
||||
- React-Core (= 0.63.3)
|
||||
- React-Core/DevSupport (= 0.63.3)
|
||||
- React-Core/RCTWebSocket (= 0.63.3)
|
||||
- React-RCTActionSheet (= 0.63.3)
|
||||
- React-RCTAnimation (= 0.63.3)
|
||||
- React-RCTBlob (= 0.63.3)
|
||||
- React-RCTImage (= 0.63.3)
|
||||
- React-RCTLinking (= 0.63.3)
|
||||
- React-RCTNetwork (= 0.63.3)
|
||||
- React-RCTSettings (= 0.63.3)
|
||||
- React-RCTText (= 0.63.3)
|
||||
- React-RCTVibration (= 0.63.3)
|
||||
- React-callinvoker (0.63.3)
|
||||
- React-Core (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default (= 0.62.2)
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-Core/Default (= 0.63.3)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/CoreModulesHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/CoreModulesHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/Default (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/Default (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/DevSupport (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/DevSupport (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default (= 0.62.2)
|
||||
- React-Core/RCTWebSocket (= 0.62.2)
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-jsinspector (= 0.62.2)
|
||||
- React-Core/Default (= 0.63.3)
|
||||
- React-Core/RCTWebSocket (= 0.63.3)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- React-jsinspector (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTActionSheetHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTActionSheetHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTAnimationHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTAnimationHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTBlobHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTBlobHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTImageHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTImageHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTLinkingHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTLinkingHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTNetworkHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTNetworkHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTSettingsHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTSettingsHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTTextHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTTextHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTVibrationHeaders (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTVibrationHeaders (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-Core/RCTWebSocket (0.62.2):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTWebSocket (0.63.3):
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-Core/Default (= 0.62.2)
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsiexecutor (= 0.62.2)
|
||||
- React-Core/Default (= 0.63.3)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsiexecutor (= 0.63.3)
|
||||
- Yoga
|
||||
- React-CoreModules (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTTypeSafety (= 0.62.2)
|
||||
- React-Core/CoreModulesHeaders (= 0.62.2)
|
||||
- React-RCTImage (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- React-cxxreact (0.62.2):
|
||||
- React-CoreModules (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.63.3)
|
||||
- React-Core/CoreModulesHeaders (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-RCTImage (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- React-cxxreact (0.63.3):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-jsinspector (= 0.62.2)
|
||||
- React-jsi (0.62.2):
|
||||
- React-callinvoker (= 0.63.3)
|
||||
- React-jsinspector (= 0.63.3)
|
||||
- React-jsi (0.63.3):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-jsi/Default (= 0.62.2)
|
||||
- React-jsi/Default (0.62.2):
|
||||
- React-jsi/Default (= 0.63.3)
|
||||
- React-jsi/Default (0.63.3):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-jsiexecutor (0.62.2):
|
||||
- React-jsiexecutor (0.63.3):
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-jsinspector (0.62.2)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-jsinspector (0.63.3)
|
||||
- react-native-blue-crypto (1.0.0):
|
||||
- React
|
||||
- react-native-blur (0.8.0):
|
||||
|
@ -262,8 +265,8 @@ PODS:
|
|||
- React
|
||||
- react-native-geolocation (2.0.2):
|
||||
- React
|
||||
- react-native-image-picker (2.3.3):
|
||||
- React
|
||||
- react-native-image-picker (2.3.4):
|
||||
- React-Core
|
||||
- react-native-randombytes (3.5.3):
|
||||
- React
|
||||
- react-native-safe-area-context (3.1.8):
|
||||
|
@ -273,67 +276,68 @@ PODS:
|
|||
- react-native-tcp-socket (3.7.1):
|
||||
- CocoaAsyncSocket
|
||||
- React
|
||||
- react-native-webview (10.8.3):
|
||||
- React
|
||||
- React-RCTActionSheet (0.62.2):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.62.2)
|
||||
- React-RCTAnimation (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTTypeSafety (= 0.62.2)
|
||||
- React-Core/RCTAnimationHeaders (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- React-RCTBlob (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTBlobHeaders (= 0.62.2)
|
||||
- React-Core/RCTWebSocket (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- React-RCTNetwork (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- React-RCTImage (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTTypeSafety (= 0.62.2)
|
||||
- React-Core/RCTImageHeaders (= 0.62.2)
|
||||
- React-RCTNetwork (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- React-RCTLinking (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- React-Core/RCTLinkingHeaders (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- React-RCTNetwork (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTTypeSafety (= 0.62.2)
|
||||
- React-Core/RCTNetworkHeaders (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- React-RCTSettings (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- RCTTypeSafety (= 0.62.2)
|
||||
- React-Core/RCTSettingsHeaders (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- React-RCTText (0.62.2):
|
||||
- React-Core/RCTTextHeaders (= 0.62.2)
|
||||
- React-RCTVibration (0.62.2):
|
||||
- FBReactNativeSpec (= 0.62.2)
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React-Core/RCTVibrationHeaders (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (= 0.62.2)
|
||||
- ReactCommon/callinvoker (0.62.2):
|
||||
- react-native-webview (10.9.2):
|
||||
- React-Core
|
||||
- React-RCTActionSheet (0.63.3):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.63.3)
|
||||
- React-RCTAnimation (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.63.3)
|
||||
- React-Core/RCTAnimationHeaders (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- React-RCTBlob (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- React-Core/RCTBlobHeaders (= 0.63.3)
|
||||
- React-Core/RCTWebSocket (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-RCTNetwork (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- React-RCTImage (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.63.3)
|
||||
- React-Core/RCTImageHeaders (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- React-RCTNetwork (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- React-RCTLinking (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- React-Core/RCTLinkingHeaders (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- React-RCTNetwork (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.63.3)
|
||||
- React-Core/RCTNetworkHeaders (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- React-RCTSettings (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.63.3)
|
||||
- React-Core/RCTSettingsHeaders (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- React-RCTText (0.63.3):
|
||||
- React-Core/RCTTextHeaders (= 0.63.3)
|
||||
- React-RCTVibration (0.63.3):
|
||||
- FBReactNativeSpec (= 0.63.3)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- React-Core/RCTVibrationHeaders (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (= 0.63.3)
|
||||
- ReactCommon/turbomodule/core (0.63.3):
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- Folly (= 2020.01.13.00)
|
||||
- glog
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- ReactCommon/turbomodule/core (0.62.2):
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- glog
|
||||
- React-Core (= 0.62.2)
|
||||
- React-cxxreact (= 0.62.2)
|
||||
- React-jsi (= 0.62.2)
|
||||
- ReactCommon/callinvoker (= 0.62.2)
|
||||
- React-callinvoker (= 0.63.3)
|
||||
- React-Core (= 0.63.3)
|
||||
- React-cxxreact (= 0.63.3)
|
||||
- React-jsi (= 0.63.3)
|
||||
- ReactNativePrivacySnapshot (1.0.0):
|
||||
- React
|
||||
- RealmJS (6.1.0):
|
||||
|
@ -341,28 +345,28 @@ PODS:
|
|||
- React
|
||||
- RemobileReactNativeQrcodeLocalImage (1.0.4):
|
||||
- React
|
||||
- RNCAsyncStorage (1.12.0):
|
||||
- React
|
||||
- RNCClipboard (1.2.3):
|
||||
- React
|
||||
- RNCAsyncStorage (1.12.1):
|
||||
- React-Core
|
||||
- RNCClipboard (1.4.0):
|
||||
- React-Core
|
||||
- RNCMaskedView (0.1.10):
|
||||
- React
|
||||
- RNCPushNotificationIOS (1.5.0):
|
||||
- React
|
||||
- RNDefaultPreference (1.4.3):
|
||||
- React
|
||||
- RNDeviceInfo (6.0.3):
|
||||
- RNDeviceInfo (6.2.0):
|
||||
- React-Core
|
||||
- RNFS (2.16.6):
|
||||
- React
|
||||
- RNGestureHandler (1.7.0):
|
||||
- RNGestureHandler (1.8.0):
|
||||
- React
|
||||
- RNHandoff (0.0.3):
|
||||
- React
|
||||
- RNInAppBrowser (3.4.0):
|
||||
- React
|
||||
- RNLocalize (1.4.0):
|
||||
- React
|
||||
- RNLocalize (1.4.2):
|
||||
- React-Core
|
||||
- RNQuickAction (0.3.13):
|
||||
- React
|
||||
- RNRate (1.2.4):
|
||||
|
@ -375,11 +379,11 @@ PODS:
|
|||
- React
|
||||
- RNSecureKeyStore (1.0.0):
|
||||
- React
|
||||
- RNSentry (1.8.0):
|
||||
- RNSentry (1.8.2):
|
||||
- React
|
||||
- Sentry (~> 5.2.0)
|
||||
- RNShare (3.7.0):
|
||||
- React
|
||||
- RNShare (4.0.2):
|
||||
- React-Core
|
||||
- RNSVG (12.1.0):
|
||||
- React
|
||||
- RNVectorIcons (6.6.0):
|
||||
|
@ -400,25 +404,25 @@ DEPENDENCIES:
|
|||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
|
||||
- Flipper (~> 0.37.0)
|
||||
- Flipper (~> 0.54.0)
|
||||
- Flipper-DoubleConversion (= 1.1.7)
|
||||
- Flipper-Folly (~> 2.1)
|
||||
- Flipper-Folly (~> 2.2)
|
||||
- Flipper-Glog (= 0.3.6)
|
||||
- Flipper-PeerTalk (~> 0.0.4)
|
||||
- Flipper-RSocket (~> 1.0)
|
||||
- FlipperKit (~> 0.37.0)
|
||||
- FlipperKit/Core (~> 0.37.0)
|
||||
- FlipperKit/CppBridge (~> 0.37.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.37.0)
|
||||
- FlipperKit/FBDefines (~> 0.37.0)
|
||||
- FlipperKit/FKPortForwarding (~> 0.37.0)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (~> 0.37.0)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (~> 0.37.0)
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.37.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (~> 0.37.0)
|
||||
- FlipperKit/FlipperKitReactPlugin (~> 0.37.0)
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.37.0)
|
||||
- FlipperKit/SKIOSNetworkPlugin (~> 0.37.0)
|
||||
- Flipper-RSocket (~> 1.1)
|
||||
- FlipperKit (~> 0.54.0)
|
||||
- FlipperKit/Core (~> 0.54.0)
|
||||
- FlipperKit/CppBridge (~> 0.54.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.54.0)
|
||||
- FlipperKit/FBDefines (~> 0.54.0)
|
||||
- FlipperKit/FKPortForwarding (~> 0.54.0)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (~> 0.54.0)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (~> 0.54.0)
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.54.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (~> 0.54.0)
|
||||
- FlipperKit/FlipperKitReactPlugin (~> 0.54.0)
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.54.0)
|
||||
- FlipperKit/SKIOSNetworkPlugin (~> 0.54.0)
|
||||
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- lottie-ios (from `../node_modules/lottie-ios`)
|
||||
|
@ -427,6 +431,7 @@ DEPENDENCIES:
|
|||
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
||||
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
|
||||
- React (from `../node_modules/react-native/`)
|
||||
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
|
||||
- React-Core (from `../node_modules/react-native/`)
|
||||
- React-Core/DevSupport (from `../node_modules/react-native/`)
|
||||
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
|
||||
|
@ -456,7 +461,6 @@ DEPENDENCIES:
|
|||
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
|
||||
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
|
||||
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
|
||||
- ReactCommon/callinvoker (from `../node_modules/react-native/ReactCommon`)
|
||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||
- ReactNativePrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
|
||||
- RealmJS (from `../node_modules/realm`)
|
||||
|
@ -528,6 +532,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native/Libraries/TypeSafety"
|
||||
React:
|
||||
:path: "../node_modules/react-native/"
|
||||
React-callinvoker:
|
||||
:path: "../node_modules/react-native/ReactCommon/callinvoker"
|
||||
React-Core:
|
||||
:path: "../node_modules/react-native/"
|
||||
React-CoreModules:
|
||||
|
@ -644,84 +650,85 @@ SPEC CHECKSUMS:
|
|||
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
|
||||
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
|
||||
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
|
||||
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
|
||||
FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245
|
||||
FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e
|
||||
Flipper: 1670db365568191bd123a0c905b834e77ba9e3d3
|
||||
DoubleConversion: cde416483dac037923206447da6e1454df403714
|
||||
FBLazyVector: 878b59e31113e289e275165efbe4b54fa614d43d
|
||||
FBReactNativeSpec: 7da9338acfb98d4ef9e5536805a0704572d33c2f
|
||||
Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365
|
||||
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
|
||||
Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
|
||||
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
|
||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
|
||||
FlipperKit: afd4259ef9eadeeb2d30250b37d95cb3b6b97a69
|
||||
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
|
||||
FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d
|
||||
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
|
||||
GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
|
||||
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
|
||||
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
|
||||
lottie-ios: 48fac6be217c76937e36e340e2d09cf7b10b7f5f
|
||||
lottie-react-native: 1fb4ce21d6ad37dab8343eaff8719df76035bd93
|
||||
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
|
||||
PasscodeAuth: 1cc99b13d8e4de4716d7e2b4069af2f1a9de30b2
|
||||
RCTRequired: cec6a34b3ac8a9915c37e7e4ad3aa74726ce4035
|
||||
RCTTypeSafety: 93006131180074cffa227a1075802c89a49dd4ce
|
||||
React: 29a8b1a02bd764fb7644ef04019270849b9a7ac3
|
||||
React-Core: b12bffb3f567fdf99510acb716ef1abd426e0e05
|
||||
React-CoreModules: 4a9b87bbe669d6c3173c0132c3328e3b000783d0
|
||||
React-cxxreact: e65f9c2ba0ac5be946f53548c1aaaee5873a8103
|
||||
React-jsi: b6dc94a6a12ff98e8877287a0b7620d365201161
|
||||
React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da
|
||||
React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493
|
||||
RCTRequired: 48884c74035a0b5b76dbb7a998bd93bcfc5f2047
|
||||
RCTTypeSafety: edf4b618033c2f1c5b7bc3d90d8e085ed95ba2ab
|
||||
React: f36e90f3ceb976546e97df3403e37d226f79d0e3
|
||||
React-callinvoker: 18874f621eb96625df7a24a7dc8d6e07391affcd
|
||||
React-Core: ac3d816b8e3493970153f4aaf0cff18af0bb95e6
|
||||
React-CoreModules: 4016d3a4e518bcfc4f5a51252b5a05692ca6f0e1
|
||||
React-cxxreact: ffc9129013b87cb36cf3f30a86695a3c397b0f99
|
||||
React-jsi: df07aa95b39c5be3e41199921509bfa929ed2b9d
|
||||
React-jsiexecutor: b56c03e61c0dd5f5801255f2160a815f4a53d451
|
||||
React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2
|
||||
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
|
||||
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
|
||||
react-native-camera: fc1296181b2d393ea698164869070c96d8625129
|
||||
react-native-document-picker: c5752781fbc0c126c627c1549b037c139444a4cf
|
||||
react-native-fingerprint-scanner: c68136ca57e3704d7bdf5faa554ea535ce15b1d0
|
||||
react-native-geolocation: cbd9d6bd06bac411eed2671810f454d4908484a8
|
||||
react-native-image-picker: a6c3d644751a388b0fc8b56822ff7cbd398a3008
|
||||
react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11
|
||||
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
|
||||
react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6
|
||||
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
|
||||
react-native-tcp-socket: 96a4f104cdcc9c6621aafe92937f163d88447c5b
|
||||
react-native-webview: 162c2f2b14555cb524ac0e3b422a9b66ebceefee
|
||||
React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c
|
||||
React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0
|
||||
React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71
|
||||
React-RCTImage: e70be9b9c74fe4e42d0005f42cace7981c994ac3
|
||||
React-RCTLinking: c1b9739a88d56ecbec23b7f63650e44672ab2ad2
|
||||
React-RCTNetwork: 73138b6f45e5a2768ad93f3d57873c2a18d14b44
|
||||
React-RCTSettings: 6e3738a87e21b39a8cb08d627e68c44acf1e325a
|
||||
React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d
|
||||
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
|
||||
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
|
||||
react-native-webview: c51f73be304c61d359ec3e7c5e4e8f2c977fd360
|
||||
React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa
|
||||
React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2
|
||||
React-RCTBlob: 0b284339cbe4b15705a05e2313a51c6d8b51fa40
|
||||
React-RCTImage: d1756599ebd4dc2cb19d1682fe67c6b976658387
|
||||
React-RCTLinking: 9af0a51c6d6a4dd1674daadafffc6d03033a6d18
|
||||
React-RCTNetwork: 332c83929cc5eae0b3bbca4add1d668e1fc18bda
|
||||
React-RCTSettings: d6953772cfd55f2c68ad72b7ef29efc7ec49f773
|
||||
React-RCTText: 65a6de06a7389098ce24340d1d3556015c38f746
|
||||
React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454
|
||||
ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3
|
||||
ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015
|
||||
RealmJS: c8645e0d65b676780f7e6c393d327527a2eb15e8
|
||||
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
|
||||
RNCAsyncStorage: 3eea36d9460c5159b592f9ecbe5a77f8aca98006
|
||||
RNCClipboard: 5f3218dcdc28405aa2ae72b78e388f150b826dd3
|
||||
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
|
||||
RNCClipboard: ce9b77f2881948e9e04af84bd70262ab37adb1c1
|
||||
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
|
||||
RNCPushNotificationIOS: 8025ff0b610d7b28d29ddc1b619cd55814362e4c
|
||||
RNDefaultPreference: 21816c0a6f61a2829ccc0cef034392e9b509ee5f
|
||||
RNDeviceInfo: 910ef2129aff229d8ebd1214bc5f0148017b51d1
|
||||
RNDeviceInfo: 980848feea8d74412b16f2e3e8758c8294d63ca2
|
||||
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
|
||||
RNGestureHandler: b6b359bb800ae399a9c8b27032bdbf7c18f08a08
|
||||
RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39
|
||||
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
|
||||
RNInAppBrowser: 6097fbc6b09051b40a6a9ec22caf7af40b115ec0
|
||||
RNLocalize: fc27ee5878ce5a3af73873fb2d8e866e0d1e6d84
|
||||
RNLocalize: 4071198b59b461f3b74eebc5fee8c50f13e39e79
|
||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||
RNRate: 2b31dad120cd1b78e33c6034808561c386a3dddf
|
||||
RNReactNativeHapticFeedback: 22c5ecf474428766c6b148f96f2ff6155cd7225e
|
||||
RNReanimated: 89f5e0a04d1dd52fbf27e7e7030d8f80a646a3fc
|
||||
RNScreens: 0e91da98ab26d5d04c7b59a9b6bd694124caf88c
|
||||
RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8
|
||||
RNSentry: 1e27aeee260eaa833d5c22a4401be47d7b15c4b0
|
||||
RNShare: a1d5064df7a0ebe778d001869b3f0a124bf0a491
|
||||
RNSentry: cff88174a0b2e0289b2efe5be1abba1f6c54269d
|
||||
RNShare: 7a7277f3c313652422d9de072ac50714dff5e8a4
|
||||
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
||||
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
|
||||
RNWatch: d56d00be49131ee454bb5a4a574f18506c8949e4
|
||||
Sentry: 8fa58a051237554f22507fb483b9a1de0171a2dc
|
||||
ToolTipMenu: 4d89d95ddffd7539230bdbe02ee51bbde362e37e
|
||||
Yoga: 3ebccbdd559724312790e7742142d062476b698e
|
||||
Yoga: 7d13633d129fd179e01b8953d38d47be90db185a
|
||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||
|
||||
PODFILE CHECKSUM: e9c5efd531ca5ac67a4b743a179eeefb322cf387
|
||||
PODFILE CHECKSUM: 845139ceee01fe141fab93749d578abee11b11c9
|
||||
|
||||
COCOAPODS: 1.10.0.beta.2
|
||||
COCOAPODS: 1.9.3
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="M4Y-Lb-cyx">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.3" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="M4Y-Lb-cyx">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
|
@ -15,90 +17,80 @@
|
|||
<rect key="frame" x="0.0" y="0.0" width="320" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bitcoin Price" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aaf-Pc-Y9i">
|
||||
<rect key="frame" x="16" y="8" width="288" height="21"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="21" id="fON-Nf-oBQ"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bcB-MD-aJf">
|
||||
<rect key="frame" x="104" y="73" width="200" height="15"/>
|
||||
<rect key="frame" x="104" y="73.5" width="200" height="14.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Last Updated:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vU4-uK-6ow">
|
||||
<rect key="frame" x="16" y="73" width="80" height="15"/>
|
||||
<rect key="frame" x="16" y="73.5" width="80" height="14.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="USD" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lkL-gv-1a1">
|
||||
<rect key="frame" x="16" y="40" width="35" height="33"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="35" id="4Nq-zK-gi3"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bEQ-e6-Puo">
|
||||
<rect key="frame" x="59" y="46.5" width="14" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrow.up" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="eST-DU-WIK">
|
||||
<rect key="frame" x="221" y="47.5" width="17" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="17" id="gkK-pz-TDJ"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="..." textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gm7-vT-KrH" userLabel="...">
|
||||
<rect key="frame" x="290" y="40" width="14" height="33"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="14" id="KoT-51-551"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="from" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aqr-Mt-cor">
|
||||
<rect key="frame" x="246" y="40" width="36" height="33"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="33" id="u9N-WC-Os2"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="U8g-sL-Cl4">
|
||||
<rect key="frame" x="16" y="5.5" width="83" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="USD" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lkL-gv-1a1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="61" height="20.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="35" id="4Nq-zK-gi3"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bEQ-e6-Puo">
|
||||
<rect key="frame" x="69" y="0.0" width="14" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="2BS-g5-Fog">
|
||||
<rect key="frame" x="16" y="34" width="83" height="33"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrow.up" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="eST-DU-WIK">
|
||||
<rect key="frame" x="0.0" y="1" width="17" height="31"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="17" id="gkK-pz-TDJ"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="from" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aqr-Mt-cor">
|
||||
<rect key="frame" x="25" y="0.0" width="36" height="33"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="33" id="u9N-WC-Os2"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="..." textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gm7-vT-KrH" userLabel="...">
|
||||
<rect key="frame" x="69" y="0.0" width="14" height="33"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="14" id="KoT-51-551"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="aqr-Mt-cor" firstAttribute="top" secondItem="aaf-Pc-Y9i" secondAttribute="bottom" constant="11" id="0ca-1C-JqG"/>
|
||||
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="aaf-Pc-Y9i" secondAttribute="trailing" constant="16" id="197-jr-Kn5"/>
|
||||
<constraint firstItem="eST-DU-WIK" firstAttribute="centerY" secondItem="aqr-Mt-cor" secondAttribute="centerY" id="2yd-pY-y1Y"/>
|
||||
<constraint firstItem="gm7-vT-KrH" firstAttribute="leading" secondItem="bEQ-e6-Puo" secondAttribute="leading" id="1nN-8G-TB7"/>
|
||||
<constraint firstItem="bcB-MD-aJf" firstAttribute="leading" secondItem="vU4-uK-6ow" secondAttribute="trailing" constant="8" id="5bB-Zv-Yeq"/>
|
||||
<constraint firstItem="lkL-gv-1a1" firstAttribute="leading" secondItem="ssy-KU-ocm" secondAttribute="leading" constant="16" id="Bgx-xM-CSS"/>
|
||||
<constraint firstItem="ssy-KU-ocm" firstAttribute="bottom" secondItem="bcB-MD-aJf" secondAttribute="bottom" constant="12" id="EPP-OS-3b6"/>
|
||||
<constraint firstItem="vU4-uK-6ow" firstAttribute="leading" secondItem="ssy-KU-ocm" secondAttribute="leading" constant="16" id="EkD-jp-arv"/>
|
||||
<constraint firstItem="gm7-vT-KrH" firstAttribute="centerY" secondItem="aqr-Mt-cor" secondAttribute="centerY" id="Eo2-n1-zbi"/>
|
||||
<constraint firstItem="U8g-sL-Cl4" firstAttribute="leading" secondItem="vU4-uK-6ow" secondAttribute="leading" id="INV-Y4-WjP"/>
|
||||
<constraint firstItem="2BS-g5-Fog" firstAttribute="top" secondItem="U8g-sL-Cl4" secondAttribute="bottom" constant="8" id="JRm-Qe-7EW"/>
|
||||
<constraint firstItem="ssy-KU-ocm" firstAttribute="bottom" secondItem="vU4-uK-6ow" secondAttribute="bottom" constant="12" id="JSh-ZE-k1H"/>
|
||||
<constraint firstItem="bcB-MD-aJf" firstAttribute="centerY" secondItem="vU4-uK-6ow" secondAttribute="centerY" id="MUL-tE-LmX"/>
|
||||
<constraint firstItem="bEQ-e6-Puo" firstAttribute="leading" secondItem="lkL-gv-1a1" secondAttribute="trailing" constant="8" id="Ml2-4o-Yqk"/>
|
||||
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="gm7-vT-KrH" secondAttribute="trailing" constant="16" id="OLV-lQ-T8a"/>
|
||||
<constraint firstItem="gm7-vT-KrH" firstAttribute="centerY" secondItem="bEQ-e6-Puo" secondAttribute="centerY" id="Rle-PT-j9m"/>
|
||||
<constraint firstItem="gm7-vT-KrH" firstAttribute="leading" secondItem="aqr-Mt-cor" secondAttribute="trailing" constant="8" id="Rtu-ah-AvP"/>
|
||||
<constraint firstItem="gm7-vT-KrH" firstAttribute="firstBaseline" secondItem="aqr-Mt-cor" secondAttribute="firstBaseline" id="YIV-xq-qlw"/>
|
||||
<constraint firstItem="bEQ-e6-Puo" firstAttribute="centerY" secondItem="lkL-gv-1a1" secondAttribute="centerY" id="Ys3-7f-RIc"/>
|
||||
<constraint firstItem="aaf-Pc-Y9i" firstAttribute="leading" secondItem="S3S-Oj-5AN" secondAttribute="leading" constant="16" id="a1b-Yq-aZb"/>
|
||||
<constraint firstItem="aaf-Pc-Y9i" firstAttribute="top" secondItem="S3S-Oj-5AN" secondAttribute="top" constant="8" id="aIo-h1-w4F"/>
|
||||
<constraint firstItem="lkL-gv-1a1" firstAttribute="top" secondItem="aaf-Pc-Y9i" secondAttribute="bottom" constant="11" id="dET-8J-W4K"/>
|
||||
<constraint firstItem="2BS-g5-Fog" firstAttribute="leading" secondItem="vU4-uK-6ow" secondAttribute="leading" id="ThK-uE-6nD"/>
|
||||
<constraint firstItem="vU4-uK-6ow" firstAttribute="top" secondItem="2BS-g5-Fog" secondAttribute="bottom" constant="6.5" id="Wib-ev-GFn"/>
|
||||
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="bcB-MD-aJf" secondAttribute="trailing" constant="16" id="kkD-VZ-BAt"/>
|
||||
<constraint firstItem="vU4-uK-6ow" firstAttribute="firstBaseline" secondItem="lkL-gv-1a1" secondAttribute="baseline" constant="16" symbolType="layoutAnchor" id="lml-Hc-8Sv"/>
|
||||
<constraint firstItem="aqr-Mt-cor" firstAttribute="leading" secondItem="eST-DU-WIK" secondAttribute="trailing" constant="8" id="t4j-U9-sOm"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="ssy-KU-ocm"/>
|
||||
</view>
|
||||
|
@ -120,6 +112,6 @@
|
|||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="arrow.up" catalog="system" width="60" height="64"/>
|
||||
<image name="arrow.up" catalog="system" width="120" height="128"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.6.0</string>
|
||||
<string>5.6.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
|
|
|
@ -20,23 +20,28 @@ class TodayViewController: UIViewController, NCWidgetProviding {
|
|||
@IBOutlet weak var lastPrice: UILabel!
|
||||
@IBOutlet weak var lastPriceFromLabel: UILabel!
|
||||
private var lastPriceNumber: NSNumber?
|
||||
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
||||
setLastPriceOutletsHidden(isHidden: true)
|
||||
if let lastStoredTodayStore = TodayData.getPriceRateAndLastUpdate() {
|
||||
processRateAndLastUpdate(todayStore: lastStoredTodayStore)
|
||||
} else {
|
||||
setLastPriceOutletsHidden(isHidden: true)
|
||||
}
|
||||
|
||||
if #available(iOSApplicationExtension 13.0, *) {
|
||||
} else{
|
||||
self.lastPriceArrowImage.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
func setLastPriceOutletsHidden(isHidden: Bool) {
|
||||
lastPrice.isHidden = isHidden
|
||||
lastPriceFromLabel.isHidden = isHidden
|
||||
lastPriceArrowImage.isHidden = isHidden
|
||||
lastPriceArrowImage?.isHidden = isHidden
|
||||
}
|
||||
|
||||
func processRateAndLastUpdate(todayStore: TodayDataStore) {
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
A Bitcoin wallet that allows you to store, send Bitcoin, receive Bitcoin and buy Bitcoin with focus on security and simplicity.
|
||||
Eine Bitcoin-Wallet, die das Speichern, Überweisen, Empfangen und Kaufen von Bitcoin erlaubt und dabei den Fokus auf Sicherheit und Einfachheit legt.
|
||||
|
||||
On BlueWallet, a bitcoin wallet you own your private keys. A Bitcoin wallet made by Bitcoin users for the community.
|
||||
Auf BlueWallet bist Du im Besitz der privaten Schlüssel. Eine Bitcoin-Wallet, die von Bitcoin-Benutzern für die Gemeinschaft erstellt wurde.
|
||||
|
||||
You can instantly transact with anyone in the world and transform the financial system right from your pocket.
|
||||
Du kannst sofort mit jedem in der Welt Geschäfte machen und das Finanzsystem direkt aus der Tasche heraus reformieren.
|
||||
|
||||
Create for free unlimited number of bitcoin wallets or import your existing wallet. It's simple and fast.
|
||||
Erstelle kostenlos eine unbegrenzte Anzahl von Bitcoin-Wallets oder importiere Deine vorhandene Wallet. Es ist einfach und schnell.
|
||||
|
||||
_____
|
||||
|
||||
Folgendes ist enthalten:
|
||||
|
||||
|
||||
1 - Security by design
|
||||
1 - Konzeptionsintegrierte Sicherheit
|
||||
|
||||
Free/Libre Open Source Software
|
||||
MIT licensed, you can build it and run it on your own! Made with ReactNative
|
||||
Quelloffene Software
|
||||
MIT lizenziert. Sie können es selbst kompilieren und betreiben! Erstellt mit ReactNative.
|
||||
|
||||
Plausible deniability
|
||||
Password which decrypts fake bitcoin wallets if you are forced to disclose your access
|
||||
Plausible Bestreitbarkeit
|
||||
Passwort, das falsche Bitcoin-Wallet entschlüsselt, falls Du gezwungen bist, Deinen Zugang preiszugeben
|
||||
|
||||
Full encryption
|
||||
On top of the iOS multi-layer encryption, we encrypt everything with added passwords
|
||||
Vollständige Verschlüsselung
|
||||
Zusätzlich zur mehrschichtigen iOS-Verschlüsselung verschlüsseln wir alles mit zusätzlichen Passwörtern
|
||||
|
||||
Full node
|
||||
Connect to your Bitcoin full node through Electrum
|
||||
Full Node
|
||||
Verbindung zu Deinem Bitcoin Full Node über Electrum
|
||||
|
||||
Cold Storage
|
||||
Connect to your hardware wallet and keep your coins in Cold storage
|
||||
Verbinde Dich mit Deiner Hardware-Wallet und verwahre Deine Coins sicher im Cold Storage
|
||||
|
||||
2 - Focused on your experience
|
||||
2 - Fokus auf einfacher Bedienbarkeit
|
||||
|
||||
Be in control
|
||||
Private keys never leave your device.
|
||||
You control your private keys
|
||||
Behalte die Kontrolle
|
||||
Private Schlüssel verlassen niemals Dein Gerät.
|
||||
Du kontrollierst Deine privaten Schlüssel
|
||||
|
||||
Flexible fees
|
||||
Definierbare Transaktionsgebühren
|
||||
Starting from 1 Satoshi. Defined by you, the user
|
||||
|
||||
Replace-By-Fee
|
||||
|
@ -50,4 +50,4 @@ Bitcoin kaufen
|
|||
Enter in the open financial revolution with the ability to buy Bitcoin directly in your wallet.
|
||||
|
||||
Local Trader
|
||||
A p2p Bitcoin Trading platform, that allows you to buy and sell bitcoin directly to other users without 3rd parties.
|
||||
A p2p Bitcoin Trading platform, that allows you to buy and sell bitcoin directly to other users without 3rd parties.
|
|
@ -1,3 +1,24 @@
|
|||
v5.6.1
|
||||
======
|
||||
|
||||
* ADD: payjoin support
|
||||
* FIX: rare crash on startup (electrum server malformed response)
|
||||
* FIX: rare freezes on send screen
|
||||
* FIX: bitcoin price widget content overlap
|
||||
* FIX: biometrics listener release for some devices
|
||||
* FIX: locales pt_BR, pt_PT, ru, sl_SI, ja_JP
|
||||
* FIX: add margin for RTL languages
|
||||
* FIX: Missing (NT) before $ sign
|
||||
|
||||
v.5.6.0
|
||||
=======
|
||||
|
||||
* FIX: some transactions displayed with 0 value
|
||||
* FIX: PSBT with HW wallets flow
|
||||
* FIX: rare crash on watch-only receive button
|
||||
* FIX: RBF cancel style
|
||||
* REF: updated languages sl_SI, de_DE, fi_FI, es_ES
|
||||
|
||||
v5.5.9
|
||||
=======
|
||||
|
||||
|
@ -67,33 +88,3 @@ v5.5.7
|
|||
* FIX: Background had wrong color during loading phase
|
||||
* REF: speeded up large wallets (>3k txs)
|
||||
* REF: speedup onchain wallet creation
|
||||
|
||||
v5.5.6
|
||||
======
|
||||
|
||||
* ADD: Camera Permission authorization view
|
||||
* FIX: recieve button for watch-only wallets
|
||||
* FIX: could not scan animated QR signed psbt
|
||||
* FIX: updated 'fi_FI' language.
|
||||
|
||||
v5.5.5
|
||||
======
|
||||
|
||||
* FIX: scan Cobo vault signed transaction QR
|
||||
|
||||
v5.5.4
|
||||
======
|
||||
|
||||
* ADD: handling push notification open
|
||||
* ADD: View Wallet xPub (Apple Watch)
|
||||
* ADD: COP Fiat
|
||||
* FIX: Invoice were not being sent (Apple Watch)
|
||||
* FIX: Disable some Watch app elements when app is not reachable
|
||||
* FIX: Show loading indicator when processing file or qrcode image
|
||||
* FIX: Button size for large devices
|
||||
* FIX: better handling of electrum disconnect
|
||||
* FIX: disable push notifications in settings
|
||||
* FIX: Font-Color in Bump-Fee Input Field "Custom" is not adapted for dark mode
|
||||
* FIX: QRCode border in LND Backup screen
|
||||
* FIX: Animated QRCode border. Change save path to Downloads folder
|
||||
* FIX: sk_SK language updates
|
||||
|
|
|
@ -293,7 +293,7 @@
|
|||
"details_to": "Ausgehend",
|
||||
"details_transaction_details": "Transaktionsdetails",
|
||||
"enable_hw": "Dieses Wallet nutzt keine Hardware-Wallet. Möchtest Du die Verwendung einer Hardware-Wallet aktivieren?",
|
||||
"list_conf": "Konf",
|
||||
"list_conf": "Bestätigungen: {number}",
|
||||
"list_title": "Transaktionen",
|
||||
"rbf_explain": "BlueWallet ersetzt diese Transaktion zur Verringerung der Transaktionszeit durch eine mit höherer Gebühr. (RBF - Replace By Fee)",
|
||||
"rbf_title": "TRX-Gebühr erhöhen (RBF)",
|
||||
|
|
28
loc/en.json
28
loc/en.json
|
@ -8,7 +8,10 @@
|
|||
"of": "{number} of {total}",
|
||||
"ok": "OK",
|
||||
"storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it",
|
||||
"yes": "Yes"
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"invalid_animated_qr_code_fragment" : "Invalid animated QRCode fragment, please try again",
|
||||
"file_saved": "File ({filePath}) has been saved in your Downloads folder ."
|
||||
},
|
||||
"azteco": {
|
||||
"codeIs": "Your voucher code is",
|
||||
|
@ -170,13 +173,13 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "note to self",
|
||||
"details_scan": "Scan",
|
||||
"details_total_exceeds_balance": "The sending amount exceeds the available balance.",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_selection": "Wallet Selection",
|
||||
"dynamic_init": "Initialing",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Next",
|
||||
"dynamic_prev": "Previous",
|
||||
"dynamic_start": "Start",
|
||||
|
@ -209,7 +212,8 @@
|
|||
"qr_error_no_qrcode": "The selected image does not contain a QR Code.",
|
||||
"qr_error_no_wallet": "The selected file does not contain a wallet that can be imported.",
|
||||
"success_done": "Done",
|
||||
"txSaved": "The transaction file ({filePath}) has been saved in your Downloads folder ."
|
||||
"txSaved": "The transaction file ({filePath}) has been saved in your Downloads folder .",
|
||||
"problem_with_psbt": "Problem with PSBT"
|
||||
},
|
||||
"settings": {
|
||||
"about": "About",
|
||||
|
@ -293,7 +297,7 @@
|
|||
"details_to": "Output",
|
||||
"details_transaction_details": "Transaction details",
|
||||
"enable_hw": "This wallet is not being used in conjunction with a hardwarde wallet. Would you like to enable hardware wallet use?",
|
||||
"list_conf": "conf",
|
||||
"list_conf": "conf: {number}",
|
||||
"list_title": "transactions",
|
||||
"rbf_explain": "We will replace this transaction with the one with a higher fee, so it should be mined faster. This is called RBF - Replace By Fee.",
|
||||
"rbf_title": "Bump fee (RBF)",
|
||||
|
@ -371,5 +375,19 @@
|
|||
"select_wallet": "Select Wallet",
|
||||
"xpub_copiedToClipboard": "Copied to clipboard.",
|
||||
"xpub_title": "wallet XPUB"
|
||||
},
|
||||
"multisig": {
|
||||
"provide_signature": "Provide signature",
|
||||
"vault_key": "Vault key {number}",
|
||||
"fee": "Fee: {number}",
|
||||
"fee_btc": "{number} BTC",
|
||||
"confirm": "Confirm",
|
||||
"header": "Send",
|
||||
"share": "Share",
|
||||
"how_many_signatures_can_bluewallet_make": "how many signatures can bluewallet make",
|
||||
"scan_or_import_file": "Scan or import file",
|
||||
"export_coordination_setup": "export coordination setup",
|
||||
"cosign_this_transaction": "Co-sign this transaction?",
|
||||
"co_sign_transaction": "Co-sign transaction"
|
||||
}
|
||||
}
|
||||
|
|
24
loc/es.json
24
loc/es.json
|
@ -94,7 +94,7 @@
|
|||
"refill_external": "Recargar con una cartera externa",
|
||||
"refill_lnd_balance": "Rellenar la cartera de Lightning",
|
||||
"sameWalletAsInvoiceError": "No puedes pagar una factura con la misma cartera que usaste para crearla.",
|
||||
"title": "manejar fondos"
|
||||
"title": "Administrar fondos"
|
||||
},
|
||||
"lndViewInvoice": {
|
||||
"additional_info": "Información adicional",
|
||||
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Siguiente",
|
||||
"details_no_maximum": "La cartera seleccionada no permite el cálculo automático del saldo máximo. ¿Estás seguro de querer seleccionar esta cartera?",
|
||||
"details_no_multiple": "La cartera seleccionada no admite el envío de bitcoin a varios destinatarios. ¿Estás seguro de querer seleccionar esta cartera?",
|
||||
"details_no_signed_tx": "El archivo seleccionado no contiene una transacción firmada que se pueda importar.",
|
||||
"details_no_signed_tx": "El archivo seleccionado no contiene una transacción que se pueda importar.",
|
||||
"details_note_placeholder": "nota personal",
|
||||
"details_scan": "Escanear",
|
||||
"details_total_exceeds_balance": "El monto excede el balance disponible.",
|
||||
|
@ -184,20 +184,20 @@
|
|||
"fee_10m": "10m",
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Custom",
|
||||
"fee_fast": "Fast",
|
||||
"fee_medium": "Medium",
|
||||
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte",
|
||||
"fee_satbyte": "in sat/byte",
|
||||
"fee_slow": "Slow",
|
||||
"fee_custom": "Personalizada",
|
||||
"fee_fast": "Rápida",
|
||||
"fee_medium": "Media",
|
||||
"fee_replace_min": "La comisión (en satoshis por byte) tiene que ser mayor que {min} sat/byte",
|
||||
"fee_satbyte": "en sat/bye",
|
||||
"fee_slow": "Lenta",
|
||||
"header": "Enviar",
|
||||
"input_clear": "Borrar",
|
||||
"input_done": "Completado",
|
||||
"input_paste": "Pegar",
|
||||
"input_total": "Total:",
|
||||
"open_settings": "Abrir configuración",
|
||||
"permission_camera_message": "Necesitamos permiso para usar la cámara",
|
||||
"permission_camera_title": "Permiso para usar la cámara",
|
||||
"open_settings": "Abrir configuración",
|
||||
"permission_storage_later": "Pregúntame luego",
|
||||
"permission_storage_message": "BlueWallet necesita permiso para acceder a su almacenamiento para poder guardar esta transacción.",
|
||||
"permission_storage_title": "Permiso para acceder al almacenamiento",
|
||||
|
@ -308,6 +308,7 @@
|
|||
"add_entropy_provide": "Entropía mediante el lanzamiento de dados",
|
||||
"add_entropy_remain": "{gen} bytes of entropía generada. Los {rem} bytes restantes serán obtenidos del generador de números aleatorios.",
|
||||
"add_import_wallet": "Importar cartera",
|
||||
"import_file": "Importar archivo",
|
||||
"add_lightning": "Lightning",
|
||||
"add_lndhub": "Conecta a tu LDNHub",
|
||||
"add_lndhub_error": "La dirección proporcionada no es válida para un nodo LNDHub.",
|
||||
|
@ -341,7 +342,6 @@
|
|||
"import_do_import": "Importar",
|
||||
"import_error": "Error al importar. Por favor, asegúrate de que los datos introducidos son correctos.",
|
||||
"import_explanation": "Escriba aquí mnemotécnica, clave privada, WIF o cualquier cosa que tenga. BlueWallet hará todo lo posible para adivinar el formato correcto e importar su billetera.",
|
||||
"import_file": "Importar archivo",
|
||||
"import_imported": "Importado",
|
||||
"import_scan_qr": "Escanear o importar un archivo",
|
||||
"import_success": "Tu cartera ha sido importada.",
|
||||
|
@ -353,7 +353,7 @@
|
|||
"list_empty_txs1": "Tus transacciones aparecerán aquí",
|
||||
"list_empty_txs1_lightning": "Usa carteras Lighting para tus transacciones diarias. Tienen tasas muy bajas y una velocidad de vértigo.",
|
||||
"list_empty_txs2": "Empieza con tu cartera",
|
||||
"list_empty_txs2_lightning": "\nPara comenzar a usarlo, toque \"manejar fondos\" y recargue su saldo.",
|
||||
"list_empty_txs2_lightning": "\nPara comenzar a usarlo, toca en \"Administrar fondos\" y añade algo de salgo.",
|
||||
"list_header": "Una cartera representa un par de llaves, una privada y una que puedes compartir para recibir fondos.",
|
||||
"list_import_error": "Error al intentar importar esta cartera.",
|
||||
"list_import_problem": "Ha ocurrido un problema al importar esta cartera",
|
||||
|
@ -361,6 +361,7 @@
|
|||
"list_long_choose": "Elegir foto",
|
||||
"list_long_clipboard": "Copiar del portapapeles",
|
||||
"list_long_scan": "Escanear código QR",
|
||||
"take_photo": "Hacer una foto",
|
||||
"list_tap_here_to_buy": "Tap here to buy Bitcoin",
|
||||
"list_title": "Carteras",
|
||||
"list_tryagain": "Inténtalo otra vez",
|
||||
|
@ -368,7 +369,6 @@
|
|||
"select_no_bitcoin": "No hay carteras de Bitcoin disponibles.",
|
||||
"select_no_bitcoin_exp": "Es necesaria una cartera de Bitcoin para recargar una Cartera de Lighting. Por favor, cree o importe una.",
|
||||
"select_wallet": "Selecciona una cartera",
|
||||
"take_photo": "Hacer una foto",
|
||||
"xpub_copiedToClipboard": "Copiado a portapapeles.",
|
||||
"xpub_title": "XPUB de la cartera"
|
||||
}
|
||||
|
|
187
loc/fi_fi.json
187
loc/fi_fi.json
|
@ -7,7 +7,7 @@
|
|||
"never": "ei koskaan",
|
||||
"of": "{number} / {total}",
|
||||
"ok": "OK",
|
||||
"storage_is_encrypted": "Tallennustilasi on salattu. Salasana vaaditaan sen purkamiseksi",
|
||||
"storage_is_encrypted": "Tallennustilasi on salattu. Sen purkamiseksi vaaditaan salasana",
|
||||
"yes": "Kyllä"
|
||||
},
|
||||
"azteco": {
|
||||
|
@ -16,12 +16,12 @@
|
|||
"errorSomething": "Jotain meni pieleen. Onko tämä kuponki edelleen voimassa?",
|
||||
"redeem": "Lunastus lompakkoon",
|
||||
"redeemButton": "Lunastus",
|
||||
"success": "Onnistunut",
|
||||
"success": "Onnistui",
|
||||
"title": "Lunasta Azte.co kuponki"
|
||||
},
|
||||
"entropy": {
|
||||
"save": "Tallenna",
|
||||
"title": "Satunnaisuus",
|
||||
"title": "Entropia",
|
||||
"undo": "Kumoa"
|
||||
},
|
||||
"errors": {
|
||||
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"hodl": {
|
||||
"are_you_sure_you_want_to_logout": "Haluatko varmasti kirjautua ulos HodlHodl-palvelusta?",
|
||||
"cont_address_escrow": "Sulkutili",
|
||||
"cont_address_escrow": "Escrow",
|
||||
"cont_address_to": "Vastaanottaja",
|
||||
"cont_buying": "ostaminen",
|
||||
"cont_cancel": "Peruuta sopimus",
|
||||
|
@ -40,19 +40,19 @@
|
|||
"cont_chat": "Avaa keskustelu vastapuolen kanssa",
|
||||
"cont_how": "Kuinka maksaa",
|
||||
"cont_no": "Sinulla ei ole meneillään sopimuksia",
|
||||
"cont_paid": "Merkitse sopimus maksetuksi",
|
||||
"cont_paid": "Merkitse sopimus Maksettu",
|
||||
"cont_paid_e": "Tee tämä vain, jos lähetit varoja myyjälle sovitulla maksutavalla",
|
||||
"cont_paid_q": "Haluatko varmasti merkitä tämän sopimuksen maksettuksi?",
|
||||
"cont_paid_q": "Haluatko varmasti merkitä tämän sopimuksen maksetuksi?",
|
||||
"cont_selling": "myynti",
|
||||
"cont_st_completed": "Valmista!",
|
||||
"cont_st_in_progress_buyer": "Kolikot ovat sulkutilillä, ole hyvä ja maksa myyjälle",
|
||||
"cont_st_paid_enought": "Bitcoinit ovat sulkutilillä! Ole hyvä ja maksa myyjälle\nsovitun maksutavan kautta",
|
||||
"cont_st_paid_waiting": "Odotetaan myyjän vapauttavan kolikot sulkutilistä",
|
||||
"cont_st_waiting": "Odotetaan myyjän tallettavan bitcoineja sulkutiliin...",
|
||||
"cont_title": "Minun sopimukset",
|
||||
"cont_st_in_progress_buyer": "Kolikot ovat escrow:ssa, ole hyvä ja maksa myyjälle",
|
||||
"cont_st_paid_enought": "Bitcoinit ovat escrow:ssa! Ole hyvä ja maksa myyjälle\nsovitun maksutavan kautta",
|
||||
"cont_st_paid_waiting": "Odotetaan myyjän vapauttavan kolikot escrow:sta",
|
||||
"cont_st_waiting": "Odotetaan myyjän tallettavan bitcoineja escrow:iin...",
|
||||
"cont_title": "Sopimukseni",
|
||||
"filter_any": "Mikä tahansa",
|
||||
"filter_buying": "Ostaminen",
|
||||
"filter_country_global": "Globaalit tarjoukset",
|
||||
"filter_country_global": "Maailmanlaajuiset tarjoukset",
|
||||
"filter_country_near": "Lähellä minua",
|
||||
"filter_currency": "Valuutta",
|
||||
"filter_detail": "Tiedot",
|
||||
|
@ -63,17 +63,17 @@
|
|||
"filter_search": "Etsi",
|
||||
"filter_selling": "Myynti",
|
||||
"item_minmax": "Minimi/Maximi",
|
||||
"item_nooffers": "Ei tarjouksia. Yritä muuttaa \"Lähellä minua\" globaaleiksi tarjouksiksi!",
|
||||
"item_rating": "{rating} kaupat",
|
||||
"item_rating_no": "Ei arvosanaa",
|
||||
"item_nooffers": "Ei tarjouksia. Yritä muuttaa \"Lähellä minua\" kansainvälisiksi tarjouksiksi!",
|
||||
"item_rating": "{rating} vaihdot",
|
||||
"item_rating_no": "Ei luokitusta",
|
||||
"login": "Kirjaudu sisään",
|
||||
"mycont": "Sopimukseni",
|
||||
"offer_accept": "Hyväksy tarjous",
|
||||
"offer_account_finish": "Näyttää siltä, että et vienyt loppuun tilin luomista HodlHodl-palvelussa, haluatko lopettaa asennuksen nyt?",
|
||||
"offer_account_finish": "Näyttää siltä, että et vienyt loppuun tilin luomista HodlHodl-palvelussa, haluatko päättää asennuksen nyt?",
|
||||
"offer_choosemethod": "Valitse maksutapa",
|
||||
"offer_confirmations": "vahvistukset",
|
||||
"offer_minmax": "minimi/maximi",
|
||||
"offer_minutes": "minimi",
|
||||
"offer_minmax": "min / max",
|
||||
"offer_minutes": "min",
|
||||
"offer_promt_fiat": "Kuinka paljon {currency} haluat ostaa?",
|
||||
"offer_promt_fiat_e": "Esimerkiksi 100",
|
||||
"offer_window": "ikkuna",
|
||||
|
@ -92,7 +92,7 @@
|
|||
"refill_card": "Täytä pankkikortilla",
|
||||
"refill_create": "Jatka luomalla Bitcoin-lompakko, jolla voit täyttää sen.",
|
||||
"refill_external": "Täytä ulkoisella lompakolla",
|
||||
"refill_lnd_balance": "Täytä Lightning lompakon saldoa",
|
||||
"refill_lnd_balance": "Täytä Lightning-lompakon saldoa",
|
||||
"sameWalletAsInvoiceError": "Et voi maksaa laskua samalla lompakolla, jolla se on luotu.",
|
||||
"title": "hallinnoi varoja"
|
||||
},
|
||||
|
@ -107,12 +107,12 @@
|
|||
"wasnt_paid_and_expired": "Tätä laskua ei maksettu, ja se on vanhentunut"
|
||||
},
|
||||
"plausibledeniability": {
|
||||
"create_fake_storage": "Luo väärennetty tallennustila",
|
||||
"create_fake_storage": "Luo Salattu tallennustila",
|
||||
"create_password": "Luo salasana",
|
||||
"create_password_explanation": "Väärennetyn tallennustilan salasanan ei tule täsmätä oikean tallennustilan salasanan kanssa",
|
||||
"help": "Joissain tilanteissa, saatat olla pakotettu kertomaan salasanasi. Pitääksesi kolikkosi turvassa, BlueWallet voi luoda toisen salatun tallennustilan, toisella salasanalla. Paineen alla, voit kertoa tämän salasanan kolmannelle osapuolelle. Annettaessa BlueWalletiin, se avaa uuden väärennetyn tallennustilan. Se näyttää oikealta kolmannelle osapuolelle, mutta pitää oikean tallennustilasi kolikkoineen turvassa.",
|
||||
"create_password_explanation": "Väärennetyn tallennustilan salasana ei tule täsmätä oikean tallennustilan salasanan kanssa",
|
||||
"help": "Joissain tilanteissa, saatat olla pakotettu kertomaan salasanasi. Pitääksesi kolikkosi turvassa, BlueWallet voi luoda toisen salatun tallennustilan, toisella salasanalla. Paineen alla, voit kertoa tämän salasanan kolmannelle osapuolelle. Jos se tulee sisään BlueWallet:iin, se avaa uuden \"väärennetyn\" tallennustilan. Se näyttää oikealta kolmannelle osapuolelle, mutta pitää oikean tallennustilasi kolikkoineen turvassa.",
|
||||
"help2": "Uusi tallennustila näyttää täysin toimivalta, ja voit säilyttää pieniä summia siellä, jotta se näyttää uskottavalta.",
|
||||
"password_should_not_match": "Väärennetyn tallennustilan salasanan ei tule täsmätä oikean tallennustilan salasanan kanssa",
|
||||
"password_should_not_match": "Salasana on käytössä. Ole hyvä, ja kokeile toista salasanaa.",
|
||||
"passwords_do_not_match": "Salasanat eivät täsmää, yritä uudelleen",
|
||||
"retype_password": "Salasana uudelleen",
|
||||
"success": "Onnistui",
|
||||
|
@ -136,41 +136,41 @@
|
|||
"header": "Vastaanota"
|
||||
},
|
||||
"send": {
|
||||
"broadcastButton": "LÄHETTÄÄ",
|
||||
"broadcastButton": "LÄHETÄ",
|
||||
"broadcastError": "virhe",
|
||||
"broadcastNone": "Syötä siirtotapahtuman tiiviste",
|
||||
"broadcastPending": "odottaa",
|
||||
"broadcastSuccess": "onnistunut",
|
||||
"broadcastSuccess": "onnistui",
|
||||
"confirm_header": "Vahvista",
|
||||
"confirm_sendNow": "Lähetä nyt",
|
||||
"create_amount": "Summa",
|
||||
"create_broadcast": "Kuuluta",
|
||||
"create_broadcast": "Lähetä",
|
||||
"create_copy": "Kopioi ja lähetä myöhemmin",
|
||||
"create_details": "Tiedot",
|
||||
"create_details": "Tarkemmat tiedot",
|
||||
"create_fee": "Siirtokulu",
|
||||
"create_memo": "Muistio",
|
||||
"create_satoshi_per_byte": "Satoshia per tavu",
|
||||
"create_this_is_hex": "Tämä on siirron hex, allekirjoitettu ja valmis lähetettävksi verkkoon.",
|
||||
"create_this_is_hex": "Tämä on siirtotapahtuman hex, allekirjoitettu ja valmis lähetettävksi verkkoon.",
|
||||
"create_to": "Vastaanottaja",
|
||||
"create_tx_size": "TX koko",
|
||||
"create_verify": "Varmenna coinb.in:ssä",
|
||||
"create_verify": "Varmenna coinb.in :ssä",
|
||||
"details_add_rec_add": "Lisää Vastaanottaja",
|
||||
"details_add_rec_rem": "Poista Vastaanottaja",
|
||||
"details_address": "osoite",
|
||||
"details_address_field_is_not_valid": "Osoite ei kelpaa",
|
||||
"details_adv_fee_bump": "Salli Siirtomaksun Nosto",
|
||||
"details_adv_fee_bump": "Salli Siirtokulun Nosto",
|
||||
"details_adv_full": "Käytä Koko Saldo",
|
||||
"details_adv_full_remove": "Muut vastaanottajat poistetaan tästä siirtotapahtumasta.",
|
||||
"details_adv_full_sure": "Haluatko varmasti käyttää lompakon koko saldoa tähän siirtotapahtumaan?",
|
||||
"details_adv_full_sure": "Haluatko varmasti käyttää lompakon koko saldon tähän siirtotapahtumaan?",
|
||||
"details_adv_import": "Tuo Siirtotapahtuma",
|
||||
"details_amount_field_is_not_valid": "Määrä ei kelpaa",
|
||||
"details_create": "Luo Lasku",
|
||||
"details_error_decode": "Virhe: Bitcoin-osoitetta ei voi muuntaa",
|
||||
"details_fee_field_is_not_valid": "Siirtomaksu ei kelpaa",
|
||||
"details_error_decode": "Virhe: Bitcoin-osoitetta ei voida dekoodata",
|
||||
"details_fee_field_is_not_valid": "Siirtokulukenttä ei ole pätevä",
|
||||
"details_next": "Seuraava",
|
||||
"details_no_maximum": "Valittu lompakko ei tue automaattista enimmäis-saldolaskelmaa. Haluatko varmasti valita tämän lompakon?",
|
||||
"details_no_multiple": "Valittu lompakko ei tue Bitcoinin lähettämistä useille vastaanottajille. Haluatko varmasti valita tämän lompakon?",
|
||||
"details_no_signed_tx": "Valittu tiedosto ei sisällä allekirjoitettua siirtotapahtumaa, joka voidaan tuoda.",
|
||||
"details_no_signed_tx": "Valittu tiedosto ei sisällä tuotavaa siirtotapahtumaa.",
|
||||
"details_note_placeholder": "muistiinpano itselle",
|
||||
"details_scan": "Skannaa",
|
||||
"details_total_exceeds_balance": "Lähetettävä summa ylittää katteen",
|
||||
|
@ -181,31 +181,41 @@
|
|||
"dynamic_prev": "Edellinen",
|
||||
"dynamic_start": "Aloita",
|
||||
"dynamic_stop": "Lopeta",
|
||||
"fee_10m": "10 m",
|
||||
"fee_1d": "1 p",
|
||||
"fee_3h": "3 t",
|
||||
"fee_custom": "Mukautettu",
|
||||
"fee_fast": "Nopea",
|
||||
"fee_medium": "Keskitaso",
|
||||
"fee_replace_min": "Maksettavan kokonaiskulun (satoshia tavua kohti) tulisi olla korkeampi kuin {min} sat/tavu",
|
||||
"fee_satbyte": "sat/tavu",
|
||||
"fee_slow": "Hidas",
|
||||
"header": "Lähetä",
|
||||
"input_clear": "Tyhjää",
|
||||
"input_done": "Valmis",
|
||||
"input_paste": "Liitä",
|
||||
"input_total": "Loppusumma:",
|
||||
"permission_camera_message": "Tarvitsemme lupaasi käyttämään kameraasi",
|
||||
"input_total": "Yhteensä:",
|
||||
"permission_camera_message": "Tarvitsemme lupasi kameran käyttöön",
|
||||
"permission_camera_title": "Kameran käyttölupa",
|
||||
"open_settings": "Avaa Asetukset",
|
||||
"permission_storage_later": "Kysy Minulta Myöhemmin",
|
||||
"permission_storage_message": "BlueWallet tarvitsee luvan käyttää tallennustilaasi tämän siirtotapahtuman tallentamiseksi.",
|
||||
"permission_storage_title": "BlueWallet-Tallennustilan Käyttöoikeus",
|
||||
"psbt_clipboard": "Kopioi leikepöydälle",
|
||||
"psbt_this_is_psbt": "Tämä on osittain allekirjoitettu bitcoin-tapahtuma (PSBT). Ole hyvä ja allekirjoittakaa se hardware lompakolla.",
|
||||
"permission_storage_title": "BlueWallet Tallennustilan Käyttöoikeus",
|
||||
"psbt_clipboard": "Kopioi Leikepöydälle",
|
||||
"psbt_this_is_psbt": "Tämä on osittain allekirjoitettu bitcoin-siirtotapahtuma (PSBT). Ole hyvä ja allekirjoita se hardware-lompakolla.",
|
||||
"psbt_tx_export": "Vie tiedostoon",
|
||||
"psbt_tx_open": "Avaa allekirjoitettu siirtotapahtuma",
|
||||
"psbt_tx_scan": "Skannaa allekirjoitettu siirtotapahtuma",
|
||||
"psbt_tx_open": "Avaa Allekirjoitettu Siirtotapahtuma",
|
||||
"psbt_tx_scan": "Skannaa Allekirjoitettu Siirtotapahtuma",
|
||||
"qr_error_no_qrcode": "Valittu kuva ei sisällä QR-koodia.",
|
||||
"qr_error_no_wallet": "Valittu tiedosto ei sisällä tuotavaa lompakkoa.",
|
||||
"success_done": "Valmis",
|
||||
"txSaved": "Siirtotapahtuma tiedosto ({filePath}) on tallennettu Lataukset-kansioon."
|
||||
"txSaved": "Siirtotapahtumatiedosto ({filePath}) on tallennettu Lataukset-kansioon."
|
||||
},
|
||||
"settings": {
|
||||
"about": "Tietoa",
|
||||
"about_awesome": "Rakennettu mahtavasti",
|
||||
"about_backup": "Varmuuskopioi avaimesi aina!",
|
||||
"about_free": "BlueWallet on ilmainen ja avoimen lähdekoodin projekti. Bitcoin-käyttäjien tekemä.",
|
||||
"about_awesome": "Mahtavasti rakennettu",
|
||||
"about_backup": "Varmuuskopioi aina avaimesi!",
|
||||
"about_free": "BlueWallet on ilmainen ja avoimen lähdekoodin projekti. Bitcoin käyttäjien tekemä.",
|
||||
"about_release_notes": "Julkaisutiedot",
|
||||
"about_review": "Jätä meille arvostelu",
|
||||
"about_selftest": "Suorita itsetestaus",
|
||||
|
@ -215,61 +225,61 @@
|
|||
"advanced_options": "Lisäasetukset",
|
||||
"currency": "Valuutta",
|
||||
"currency_source": "Hinnat saadaan CoinDeskistä",
|
||||
"default_desc": "Kun toiminto on poistettu käytöstä, BlueWallet avaa valitun lompakon heti käynnistettäessä.",
|
||||
"default_info": "Oletus kohtaan",
|
||||
"default_title": "Käynnistyksessä",
|
||||
"default_desc": "Kun on pois käytöstä, BlueWallet avaa valitun lompakon heti käynnistettäessä.",
|
||||
"default_info": "Oletustiedot",
|
||||
"default_title": "Käynnistettäessä",
|
||||
"default_wallets": "Näytä Kaikki Lompakot",
|
||||
"electrum_connected": "Yhdistetty",
|
||||
"electrum_connected_not": "Ei yhteyttä",
|
||||
"electrum_error_connect": "Ei voi muodostaa yhteyttä toimitettuun Electrum-palvelimeen",
|
||||
"electrum_error_connect": "Ei voida yhdistää tarjottuun Electrum-palvelimeen",
|
||||
"electrum_host": "ylläpitäjä, esimerkiksi {example}",
|
||||
"electrum_port": "TCP-portti, yleensä {example}",
|
||||
"electrum_port_ssl": "SSL-portti, yleensä {example}",
|
||||
"electrum_saved": "Muutoksesi on tallennettu onnistuneesti. Uudelleenkäynnistys voi olla tarpeen, jotta muutokset tulevat voimaan.",
|
||||
"electrum_settings": "Electrum-asetukset",
|
||||
"electrum_settings_explain": "Aseta tyhjäksi käyttääksesi oletusasetusta",
|
||||
"electrum_status": "Status",
|
||||
"electrum_settings": "Electrum-Asetukset",
|
||||
"electrum_settings_explain": "Jätä tyhjäksi käyttääksesi oletusasetusta",
|
||||
"electrum_status": "Tila",
|
||||
"encrypt_decrypt": "Pura tallennustilan salaus",
|
||||
"encrypt_decrypt_q": "Haluatko varmasti purkaa tallennustilan salauksen? Tämän avulla lompakoihisi pääsee käsiksi ilman salasanaa.",
|
||||
"encrypt_decrypt_q": "Haluatko varmasti purkaa tallennustilan salauksen? Tämä mahdollistaa lompakkoihisi pääsyn ilman salasanaa.",
|
||||
"encrypt_del_uninstall": "Poista, jos BlueWallet poistetaan",
|
||||
"encrypt_enc_and_pass": "Salattu ja Salasanalla suojattu",
|
||||
"encrypt_title": "Tietoturva",
|
||||
"encrypt_tstorage": "tallennustila",
|
||||
"encrypt_use": "Käytä {type}",
|
||||
"encrypt_use_expl": "{type} -toimintoa käytetään henkilöllisyytesi vahvistamiseen ennen siirtotapahtuman tekemistä, lompakon lukituksen avaamista, vientiä tai poistamista. {type} ei käytetä salatun tallennustilan lukituksen avaamiseen.",
|
||||
"encrypt_use_expl": "{type} käytetään henkilöllisyytesi vahvistamiseen ennen siirtotapahtuman tekemistä, lompakon lukituksen avaamista, vientiä tai poistamista. {type} ei käytetä salatun tallennustilan lukituksen avaamiseen.",
|
||||
"general": "Yleinen",
|
||||
"general_adv_mode": "Kehittynyt tila",
|
||||
"general_adv_mode_e": "Kun tämä asetus on käytössä, näet lisäasetukset, kuten erilaiset lompakkotyypit, kyvyn määrittää LNDHub-ilmentymän, johon haluat muodostaa yhteyden, ja mukautetun entropian lompakon luomisen aikana.",
|
||||
"general_adv_mode_e": "Kun tämä asetus on käytössä, näet lisäasetukset, kuten erilaiset lompakkotyypit, kyvyn määrittää LNDHub-instanssi, johon haluat muodostaa yhteyden, ja mukautetun entropian lompakon luomisen aikana.",
|
||||
"general_continuity": "Jatkuvuus",
|
||||
"general_continuity_e": "Kun tämä asetus on käytössä, voit tarkastella valittuja lompakoita ja siirtotapahtumia muilla Apple iCloud -laitteilla.",
|
||||
"groundcontrol_explanation": "GroundControl on ilmainen avoimen lähdekoodin push-ilmoituspalvelin bitcoin-lompakoille. Voit asentaa oman GroundControl-palvelimen ja laittaa sen URL-osoitteen tähän, jotta et luota BlueWallet-infrastruktuuriin. Jätä tyhjäksi käyttääksesi oletusasetusta",
|
||||
"header": "asetukset",
|
||||
"language": "Kieli",
|
||||
"language_restart": "Kun valitset uuden kielen, muutoksen voimaantulo edellyttää, että BlueWallet käynnistetään uudelleen.",
|
||||
"lightning_error_lndhub_uri": "Ei kelvollinen LndHub-URI",
|
||||
"lightning_error_lndhub_uri": "LndHub-URI ei kelpaa",
|
||||
"lightning_saved": "Muutoksesi on tallennettu onnistuneesti",
|
||||
"lightning_settings": "Lightning asetukset",
|
||||
"lightning_settings": "Lightning-Asetukset",
|
||||
"lightning_settings_explain": "Yhdistääksesi omaan LND-solmuun, asenna LndHub ja laita sen URL tänne. Jätä tyhjäksi käyttääksesi BlueWalletin LNDHubia (lndhub.io). Muutosten tallentamisen jälkeen luodut lompakot yhdistävät annettuun LNDHubiin.",
|
||||
"network": "Verkko",
|
||||
"network_broadcast": "Lähetä siirtotapahtuma",
|
||||
"network_electrum": "Electrum-palvelin",
|
||||
"not_a_valid_uri": "URI ei kelpaa",
|
||||
"notifications": "Ilmoitukset",
|
||||
"password": "Salasana",
|
||||
"password_explain": "Luo salasana, jota käytät tallennustilan salauksen purkamiseen",
|
||||
"passwords_do_not_match": "Salasanat eivät täsmää",
|
||||
"plausible_deniability": "Uskottava kiistettävyys...",
|
||||
"retype_password": "Salasana uudelleen",
|
||||
"notifications": "Ilmoitukset",
|
||||
"save": "Tallenna",
|
||||
"saved": "Tallennettu",
|
||||
"not_a_valid_uri": "Ei kelvollinen URI",
|
||||
"plausible_deniability": "Uskottava kiistettävyys",
|
||||
"push_notifications": "Push-ilmoitukset",
|
||||
"groundcontrol_explanation": "GroundControl on ilmainen avoimen lähdekoodin push-ilmoituspalvelin bitcoin-lompakoille. Voit asentaa oman GroundControl-palvelimen ja laittaa sen URL-osoitteen tähän, jotta et luota BlueWallet-infrastruktuuriin. Jätä tyhjäksi käyttääksesi oletusasetusta"
|
||||
"retype_password": "Salasana uudelleen",
|
||||
"save": "Tallenna",
|
||||
"saved": "Tallennettu"
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_explain": "Korvaamme tämän siirtotapahtuman sillä, joka maksaa sinulle ja jolla on korkeammat siirtokulut. Tämä peruuttaa siirtotapahtuman tehokkaasti. Tätä kutsutaan RBF - Korvattavissa korkeammalla kululla.",
|
||||
"cancel_explain": "Korvaamme tämän siirtotapahtuman sillä, joka maksaa sinulle ja on korkeammat siirtokulut. Tämä peruuttaa siirtotapahtuman tehokkaasti. Tätä kutsutaan RBF - Replace By Fee - Korvaa korkeammilla kuluilla.",
|
||||
"cancel_no": "Tämä siirtotapahtuma ei ole vaihdettavissa",
|
||||
"cancel_title": "Peruuta tämä siirtotapahtuma (RBF)",
|
||||
"cpfp_create": "Luo",
|
||||
"cpfp_exp": "Luomme toisen siirtotapahtuman, joka kuluttaa vahvistamattoman siirtotapahtuman. Kokonaiskulu on suurempi kuin alkuperäinen siirtotapahtumakulu, joten sen pitäisi olla louhittu nopeammin. Tätä kutsutaan CPFP - lapsi maksaa vanhemmalle.",
|
||||
"cpfp_exp": "Luomme toisen siirtotapahtuman, joka kuluttaa vahvistamattoman siirtotapahtuman. Kokonaiskulu on suurempi kuin alkuperäinen siirtokulu, joten sen pitäisi olla louhittu nopeammin. Tätä kutsutaan CPFP - Child Pays For Parent - Lapsi Maksaa Vanhemmalle.",
|
||||
"cpfp_no_bump": "Tämä siirtotapahtuma ei ole nostettavissa",
|
||||
"cpfp_title": "Nosta siirtokuluja (CPFP)",
|
||||
"details_block": "Lohkon järjestysnumero",
|
||||
|
@ -279,17 +289,17 @@
|
|||
"details_outputs": "Ulostulot",
|
||||
"details_received": "Vastaanotettu",
|
||||
"details_show_in_block_explorer": "Näytä lohkoketjuselaimessa",
|
||||
"details_title": "Siirto",
|
||||
"details_title": "Siirtotapahtuma",
|
||||
"details_to": "Ulostulo",
|
||||
"details_transaction_details": "Siirtotapahtuman tiedot",
|
||||
"enable_hw": "Tätä lompakkoa ei käytetä yhdessä hardware-lompakon kanssa. Haluatko ottaa hardware-lompakon käyttöön?",
|
||||
"list_conf": "conf",
|
||||
"list_title": "siirrot",
|
||||
"transactions_count": "siirtotapahtumien määrä",
|
||||
"rbf_explain": "Korvaamme tämän siirtotapahtuman toisella jossa on korkeammat siirtokulut, joten se pitäisi olla louhittu nopeammin. Tätä kutsutaan RBF - Korvattavissa korkeammalla kululla.",
|
||||
"enable_hw": "Tätä lompakkoa ei ole käytetty yhdessä hardware-lompakon kanssa. Haluatko ottaa hardware-lompakon käyttöön?",
|
||||
"list_conf": "conf: {number}",
|
||||
"list_title": "siirtotapahtumat",
|
||||
"rbf_explain": "Korvaamme tämän siirtotapahtuman toisella jossa on korkeammat siirtokulut, joten se pitäisi olla louhittu nopeammin. Tätä kutsutaan RBF - Replace By Fee - Korvattavissa korkeammilla kuluilla.",
|
||||
"rbf_title": "Nosta siirtokuluja (RBF)",
|
||||
"status_bump": "Nosta siirtokuluja",
|
||||
"status_cancel": "Peruuta siirtotapahtuma"
|
||||
"status_cancel": "Peruuta Siirtotapahtuma",
|
||||
"transactions_count": "siirtotapahtumien määrä"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "Bitcoin",
|
||||
|
@ -298,8 +308,9 @@
|
|||
"add_entropy_provide": "Hanki entropia nopanheiton kautta",
|
||||
"add_entropy_remain": "{gen} tavua luotua entropiaa. Jäljellä olevat {rem} tavut saadaan Järjestelmän satunnaislukugeneraattorilta.",
|
||||
"add_import_wallet": "Tuo lompakko",
|
||||
"add_lightning": "Salama",
|
||||
"add_lndhub": "Yhdistä LNDHubiisi",
|
||||
"import_file": "Tuo tiedosto",
|
||||
"add_lightning": "Lightning",
|
||||
"add_lndhub": "Yhdistä LNDHub:iisi",
|
||||
"add_lndhub_error": "Annettu solmun osoite ei ole kelvollinen LNDHub-solmu.",
|
||||
"add_lndhub_placeholder": "solmusi osoite",
|
||||
"add_or": "tai",
|
||||
|
@ -312,14 +323,14 @@
|
|||
"details_connected_to": "Yhdistetty",
|
||||
"details_del_wb": "Lompakon saldo",
|
||||
"details_del_wb_err": "Annettu saldo ei vastaa tämän lompakon saldoa. Yritä uudelleen",
|
||||
"details_del_wb_q": "Tällä lompakolla on saldo. Ennen kuin jatkat, huomaa, että et voi palauttaa varoja ilman tämän lompakon siemenlauseketta. Syötä lompakkosi saldo {saldo} satoshia välttääksesi tämän lompakon vahingossa tapahtuvan poistamisen.",
|
||||
"details_del_wb_q": "Tällä lompakolla on saldoa. Ennen kuin jatkat, huomaa, että et voi palauttaa varoja ilman tämän lompakon siemenlauseketta. Syötä lompakkosi saldo {balance} satoshia välttääksesi vahingossa tämän lompakon poistamisen.",
|
||||
"details_delete": "Poista",
|
||||
"details_delete_wallet": "Poista lompakko",
|
||||
"details_display": "näkyy lompakkolistassa",
|
||||
"details_display": "näkyy lompakkojen listassa",
|
||||
"details_export_backup": "Vie / varmuuskopioi",
|
||||
"details_marketplace": "Kauppapaikka",
|
||||
"details_master_fingerprint": "Isäntä sormenjälki",
|
||||
"details_no_cancel": "En, peruuta",
|
||||
"details_master_fingerprint": "Pää sormenjälki",
|
||||
"details_no_cancel": "Ei, peruuta",
|
||||
"details_save": "Tallenna",
|
||||
"details_show_xpub": "Näytä lompakon XPUB",
|
||||
"details_title": "Lompakko",
|
||||
|
@ -332,26 +343,26 @@
|
|||
"import_error": "Tuonti epäonnistui. Varmista, että annettu tieto on oikein",
|
||||
"import_explanation": "Kirjoita tähän muistisanasi, yksityinen avain, WIF tai jotain mitä sinulla on. BlueWallet tekee parhaansa arvatakseen oikean muodon ja tuo lompakkosi",
|
||||
"import_imported": "Tuotu",
|
||||
"import_scan_qr": "tai skannaa QR-koodi?",
|
||||
"import_success": "Onnistui",
|
||||
"import_scan_qr": "Skannaa tai tuo tiedosto",
|
||||
"import_success": "Lompakkosi tuonti onnistui.",
|
||||
"import_title": "tuo",
|
||||
"list_create_a_button": "Lisää nyt",
|
||||
"list_create_a_wallet": "Lisää lompakko",
|
||||
"list_create_a_wallet1": "Se on ilmaista ja voit luoda",
|
||||
"list_create_a_wallet2": "niin monta kuin haluat",
|
||||
"list_empty_txs1": "Siirtosi näkyvät tässä,",
|
||||
"list_empty_txs1_lightning": "Salama-lompakkoa tulisi käyttää päivittäisiin siirtotapahtumiin. Siirtokulut ovat kohtuuttoman halpoja ja nopeus on liekehtivän nopea.",
|
||||
"list_empty_txs1": "Siirtotapahtumasi näkyvät tässä,",
|
||||
"list_empty_txs1_lightning": "Lightning-lompakkoa tulisi käyttää päivittäisiin siirtotapahtumiin. Siirtokulut ovat kohtuuttoman halpoja ja nopeus on liekehtivän kova.",
|
||||
"list_empty_txs2": "Aloita lompakostasi",
|
||||
"list_empty_txs2_lightning": "Aloita sen käyttäminen napsauttamalla \"hallinnoi varoja\" ja lisää saldoasi.",
|
||||
"list_header": "Lompakko edustaa salaista paria (yksityinen avain) ja osoitetta, jonka voit jakaa vastaanottaaksesi kolikoita.",
|
||||
"list_empty_txs2_lightning": "Aloita sen käyttäminen napsauttamalla \"hallinnoi varoja\" ja lisää saldoasi.\n",
|
||||
"list_header": "Lompakko edustaa avainparia, yhtä yksityistä ja yhtä, jonka voit jakaa vastaanottaaksesi kolikoita.",
|
||||
"list_import_error": "Tämän lompakon tuomisessa tapahtui virhe.",
|
||||
"list_import_problem": "Tämän lompakon tuonnissa oli ongelma",
|
||||
"list_import_problem": "Tämän lompakon tuomisessa oli ongelma",
|
||||
"list_latest_transaction": "viimeisin siirto",
|
||||
"list_long_choose": "Valitse Kuva",
|
||||
"list_long_clipboard": "Kopio leikepöydältä",
|
||||
"list_long_clipboard": "Kopioi Leikepöydältä",
|
||||
"list_long_scan": "Skannaa QR-koodi",
|
||||
"take_photo": "Ota Kuva",
|
||||
"list_tap_here_to_buy": "Napsauta tästä ostaaksesi Bitcoinia",
|
||||
"list_tap_here_to_buy": "Osta Bitcoinia",
|
||||
"list_title": "lompakot",
|
||||
"list_tryagain": "Yritä uudelleen",
|
||||
"reorder_title": "Järjestele Lompakot",
|
||||
|
|
100
loc/he.json
100
loc/he.json
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"_": {
|
||||
"bad_password": "סיסמה שגויה, נסו שוב.",
|
||||
"bad_password": "סיסמה שגויה, אנא נסו שוב.",
|
||||
"cancel": "ביטול",
|
||||
"continue": "המשך",
|
||||
"enter_password": "הזינו סיסמה",
|
||||
"enter_password": "הכניסו סיסמה",
|
||||
"never": "אף פעם",
|
||||
"of": "{number} של {total}",
|
||||
"ok": "אוקיי",
|
||||
"of": "{number} מתוך {total}",
|
||||
"ok": "אישור",
|
||||
"storage_is_encrypted": "האחסון שלך מוצפן, נדרשת סיסמה לפתיחה",
|
||||
"yes": "כן"
|
||||
},
|
||||
|
@ -33,35 +33,35 @@
|
|||
"are_you_sure_you_want_to_logout": "האם אתם בטוחים שאתם רוצים להתנתק מ- HodlHodl?",
|
||||
"cont_address_escrow": "פקדון",
|
||||
"cont_address_to": "עבור",
|
||||
"cont_buying": "קונה",
|
||||
"cont_buying": "קנייה",
|
||||
"cont_cancel": "ביטול חוזה",
|
||||
"cont_cancel_q": "האם אתם בטוחים שאתם רוצים לבטל חוזה זה?",
|
||||
"cont_cancel_y": "כן, בטל חוזה.",
|
||||
"cont_chat": "פתחו שיחה עם הצד השני",
|
||||
"cont_how": "איך לשלם",
|
||||
"cont_no": "אין לך שום חוזה פעיל.",
|
||||
"cont_paid": "סמן חוזה כשולם",
|
||||
"cont_paid_e": "עשה זאת רק אם שילמת למוכר עם אמצאי התשלום המוסכם",
|
||||
"cont_paid": "סימון חוזה כשולם",
|
||||
"cont_paid_e": "עשו זאת רק אם שילמתם למוכר עם אמצעי התשלום המוסכם",
|
||||
"cont_paid_q": "האם אתם בטוחים שאתם רוצים לסמן חוזה זה כשולם?",
|
||||
"cont_selling": "מוכר",
|
||||
"cont_st_completed": "הכל בוצע",
|
||||
"cont_selling": "מכירה",
|
||||
"cont_st_completed": "הכל בוצע!",
|
||||
"cont_st_in_progress_buyer": "המטבעות נעולים בפיקדון, אנא שלמו למוכר",
|
||||
"cont_st_paid_enought": "הביטקוין נעול בפיקדון! אנא שלמו למוכר\nבאמצעי התשלום המוסכם",
|
||||
"cont_st_paid_waiting": "מחכה לשחרור המטבעות מהפיקדון על-ידי המוכר ",
|
||||
"cont_st_waiting": "מחכה להפקדת המטבעות בפיקדון על-ידי המוכר ...",
|
||||
"cont_title": "החוזים שלי",
|
||||
"filter_any": "הכל",
|
||||
"filter_buying": "קונה",
|
||||
"filter_buying": "קנייה",
|
||||
"filter_country_global": "הצעות גלובליות",
|
||||
"filter_country_near": "לידי",
|
||||
"filter_currency": "מטבע",
|
||||
"filter_detail": "פרטים",
|
||||
"filter_filters": "מסננים",
|
||||
"filter_iambuying": "אני רוצה לקנות ביטקוין",
|
||||
"filter_iamselling": "רוצה למכור ביטקוין",
|
||||
"filter_iamselling": "אני רוצה למכור ביטקוין",
|
||||
"filter_method": "אמצעי תשלום",
|
||||
"filter_search": "חיפוש",
|
||||
"filter_selling": "מוכר",
|
||||
"filter_selling": "מכירה",
|
||||
"item_minmax": "מינימום/מקסימום",
|
||||
"item_nooffers": "אין הצעות. נסו לשנות \"לידי\" להצעות גלובליות!",
|
||||
"item_rating": "{rating} החלפות",
|
||||
|
@ -113,8 +113,8 @@
|
|||
"help": "בנסיבות מסוימות, יתכן ותאולצו לחשוף את סיסמת הארנק. כדי לשמור על המטבעות בטוחים, BlueWallet מאפשר ליצור אחסון מוצפן נוסף, עם סיסמה שונה. תחת לחץ, תוכלו לחשוף את סיסמה זו לצד שלישי. אם הסיסמה תוכנס ל- BlueWallet, אחסון 'מזויף' חדש יפתח. מצב זה יראה לגיטימי לצד השלישי, בזמן שהאחסון הראשי ישמר בסודיות עם כשהמטבעות מוגנים.",
|
||||
"help2": "האחסון החדש יתפקד באופן מלא, ותוכלו לאחסן בו סכומים מינימליים כך שיראה יותר מהימן.",
|
||||
"password_should_not_match": "הסיסמה כבר בשימוש. אנא נסו סיסמה אחרת.",
|
||||
"passwords_do_not_match": "סיסמה אינה תואמת, נסו שוב",
|
||||
"retype_password": "סיסמה בשנית",
|
||||
"passwords_do_not_match": "סיסמאות אינן תואמות, נסו שוב",
|
||||
"retype_password": "הכניסו שוב סיסמה",
|
||||
"success": "הצלחה",
|
||||
"title": "הכחשה סבירה"
|
||||
},
|
||||
|
@ -129,16 +129,16 @@
|
|||
"title": "ארנקך נוצר..."
|
||||
},
|
||||
"receive": {
|
||||
"details_create": "צרו",
|
||||
"details_create": "יצירה",
|
||||
"details_label": "תיאור",
|
||||
"details_setAmount": "בחר כמות",
|
||||
"details_setAmount": "קבלה עם סכום",
|
||||
"details_share": "שיתוף",
|
||||
"header": "קבלה"
|
||||
},
|
||||
"send": {
|
||||
"broadcastButton": "שדר",
|
||||
"broadcastError": "שגיאה",
|
||||
"broadcastNone": "גיבוב קלט העסקה",
|
||||
"broadcastNone": "קלט גיבוב העברה",
|
||||
"broadcastPending": "ממתין",
|
||||
"broadcastSuccess": "הצלחה",
|
||||
"confirm_header": "אישור",
|
||||
|
@ -150,7 +150,7 @@
|
|||
"create_fee": "עמלה",
|
||||
"create_memo": "תזכיר",
|
||||
"create_satoshi_per_byte": "סאטושי לבייט",
|
||||
"create_this_is_hex": "זוהי העסקה החתומה שלך, מוכנה לשידור לרשת.",
|
||||
"create_this_is_hex": "זוהי ההעברה שלך, חתומה ומוכנה לשידור לרשת.",
|
||||
"create_to": "עבור",
|
||||
"create_tx_size": "גודל ההעברה",
|
||||
"create_verify": "אמתו ב- coinb.in",
|
||||
|
@ -158,11 +158,11 @@
|
|||
"details_add_rec_rem": "הסרת נמען",
|
||||
"details_address": "כתובת",
|
||||
"details_address_field_is_not_valid": "שדה כתובת לא תקין",
|
||||
"details_adv_fee_bump": "אפשר העלאת עמלה",
|
||||
"details_adv_fee_bump": "אפשר הקפצת עמלה",
|
||||
"details_adv_full": "שימוש בכל היתרה",
|
||||
"details_adv_full_remove": "שאר הנמענים ימחקו מהעברה זו.",
|
||||
"details_adv_full_sure": "האם אתם בטוחים שתרצו להשתמש בכל יתרת הארנק בשביל עסקה זאת?",
|
||||
"details_adv_import": "יבוא עסקה",
|
||||
"details_adv_full_sure": "האם אתם בטוחים שתרצו להשתמש בכל יתרת הארנק בשביל העברה זאת?",
|
||||
"details_adv_import": "יבוא העברה",
|
||||
"details_amount_field_is_not_valid": "שדה סכום אינו תקין",
|
||||
"details_create": "יצירת קבלה",
|
||||
"details_error_decode": "שגיאה: לא ניתן לפענח כתובת ביטקוין",
|
||||
|
@ -170,8 +170,8 @@
|
|||
"details_next": "הבא",
|
||||
"details_no_maximum": "הארנק הנבחר אינו תומך בחישוב יתרה מקסימלית אוטומטי. האם לבחור בארנק זה בכל זאת?",
|
||||
"details_no_multiple": "הארנק הנבחר אינו תומך בשליחת ביטקוין לנמענים מרובים. האם לבחור בארנק זה בכל זאת?",
|
||||
"details_no_signed_tx": "הקובץ הנבחר אינו מכיל העברה חתומה שניתן לייבא.",
|
||||
"details_note_placeholder": "פתק לעצמך",
|
||||
"details_no_signed_tx": "הקובץ הנבחר אינו מכיל העברה שניתן לייבא.",
|
||||
"details_note_placeholder": "הערה לעצמך",
|
||||
"details_scan": "סריקה",
|
||||
"details_total_exceeds_balance": "הסכום לשליחה חורג מהיתרה הזמינה.",
|
||||
"details_wallet_before_tx": "לפני יצירת העברה, עליך להוסיף ארנק ביטקוין.",
|
||||
|
@ -181,16 +181,16 @@
|
|||
"dynamic_prev": "הקודם",
|
||||
"dynamic_start": "התחל",
|
||||
"dynamic_stop": "עצור",
|
||||
"fee_10m": "10m",
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Custom",
|
||||
"fee_fast": "Fast",
|
||||
"fee_medium": "Medium",
|
||||
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte",
|
||||
"fee_satbyte": "in sat/byte",
|
||||
"fee_slow": "Slow",
|
||||
"header": "שלח",
|
||||
"fee_10m": "10 ד'",
|
||||
"fee_1d": "1 י'",
|
||||
"fee_3h": "3 ש'",
|
||||
"fee_custom": "אחר",
|
||||
"fee_fast": "מהיר",
|
||||
"fee_medium": "בינוני",
|
||||
"fee_replace_min": "סך כל העמלה (סאטושי לבייט) שתרצו לשלם צריך להיות גבוה מ- {min} סאט/בייט",
|
||||
"fee_satbyte": "בסאטושי/בייט",
|
||||
"fee_slow": "איטי",
|
||||
"header": "שליחה",
|
||||
"input_clear": "נקה",
|
||||
"input_done": "בוצע",
|
||||
"input_paste": "הדבק",
|
||||
|
@ -201,11 +201,11 @@
|
|||
"permission_storage_later": "שאל אותי מאוחר יותר",
|
||||
"permission_storage_message": "BlueWallet צריך את הרשאתך לגשת לאחסון שלך כדי לשמור את ההעברה.",
|
||||
"permission_storage_title": "הרשאת גישה לאחסון BlueWallet",
|
||||
"psbt_clipboard": "העתק ללוח",
|
||||
"psbt_clipboard": "העתקה ללוח",
|
||||
"psbt_this_is_psbt": "זוהי העברת ביטקוין חתומה חלקית (PSBT). אנא סיימו את תהליך החתימה בארנק החומרה שלכם.",
|
||||
"psbt_tx_export": "יצא לקובץ",
|
||||
"psbt_tx_open": "פתחו עסקה חתומה",
|
||||
"psbt_tx_scan": "סרקו עסקה חתומה",
|
||||
"psbt_tx_open": "פתחו העברה חתומה",
|
||||
"psbt_tx_scan": "סרקו העברה חתומה",
|
||||
"qr_error_no_qrcode": "התמונה אינה מכילה קוד QR.",
|
||||
"qr_error_no_wallet": "הקובץ הנבחר אינו מכיל ארנק שניתן לייבא.",
|
||||
"success_done": "בוצע",
|
||||
|
@ -215,7 +215,7 @@
|
|||
"about": "אודות",
|
||||
"about_awesome": "נבנה בעזרת",
|
||||
"about_backup": "תמיד גבו את המפתחות שלכם!",
|
||||
"about_free": "BlueWallet הינו פרויקט חופשי בקוד פתוח. נוצר על ידי קהילת ביטקוין.",
|
||||
"about_free": "פרויקט BlueWallet הינו פרויקט חופשי בקוד פתוח. נוצר על ידי קהילת ביטקוין.",
|
||||
"about_release_notes": "הערות שחרור",
|
||||
"about_review": "השאירו לנו ביקורת",
|
||||
"about_selftest": "הרץ בדיקה עצמית",
|
||||
|
@ -252,7 +252,7 @@
|
|||
"general_adv_mode_e": "כאשר מופעל, אפשרויות מתקדמות יוצגו כגון סוגי ארנק שונים, אפשרות להתחבר לצומת LNDHub לפי רצונך ואנטרופיה מותאמת בתהליך יצירת ארנק.",
|
||||
"general_continuity": "המשכיות",
|
||||
"general_continuity_e": "כאשר מופעל, תוכלו לצפות בארנקים והעברות נבחרים, באמצעות מכשירי Apple iCloud מחוברים אחרים.",
|
||||
"groundcontrol_explanation": "GroundControl הינו שרת התראות חופשי בקוד פתוח בשביל ארנקי ביטקוין. באפשרותך להתקין שרת GroundControl אישי ולהכניס את ה- URL שלו כאן, כדי לא להסתמך על התשתית של BlueWallet. השאירו ריק כדי להשתמש בברירת המחדל",
|
||||
"groundcontrol_explanation": "שרת GroundControl הינו שרת התראות חופשי בקוד פתוח בשביל ארנקי ביטקוין. באפשרותך להתקין שרת GroundControl אישי ולהכניס את ה- URL שלו כאן, כדי לא להסתמך על התשתית של BlueWallet. השאירו ריק כדי להשתמש בברירת המחדל",
|
||||
"header": "הגדרות",
|
||||
"language": "שפה",
|
||||
"language_restart": "כאשר בוחרים שפה חדשה, יתכן ותדרש הפעלה מחדש של BlueWallet כדי שהשינוי ייכנס לתוקף.",
|
||||
|
@ -261,7 +261,7 @@
|
|||
"lightning_settings": "הגדרות ברק",
|
||||
"lightning_settings_explain": "כדי להתחבר לצומת LND אישי, אנא התקינו LndHub והכניסו את כתובת ה- URL שלו כאן בהגדרות. השאירו ריק כדי להשתמש ב- LNDHub של BlueWallet (lndhub.io). ארנקים שנוצרו אחרי שמירת השינויים יתחברו ל- LNDHub שהוגדר.",
|
||||
"network": "רשת",
|
||||
"network_broadcast": "שידור עסקה",
|
||||
"network_broadcast": "שידור העברה",
|
||||
"network_electrum": "שרת אלקטרום",
|
||||
"not_a_valid_uri": "URI לא תקני",
|
||||
"notifications": "התראות",
|
||||
|
@ -270,16 +270,16 @@
|
|||
"passwords_do_not_match": "סיסמאות לא תואמות",
|
||||
"plausible_deniability": "הכחשה סבירה",
|
||||
"push_notifications": "התראות",
|
||||
"retype_password": "סיסמה בשנית",
|
||||
"retype_password": "הכניסו שוב סיסמה",
|
||||
"save": "שמירה",
|
||||
"saved": "נשמר"
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_explain": "אנו נחליף את ההעברה הזאת באחת עם עמלה גבוהה יותר. פעולה זאת למעשה מבטלת את העברה. פעולה זאת נקראת RBF - Replace By Fee.",
|
||||
"cancel_no": "העברה זאת אינה ניתנת להחלפה",
|
||||
"cancel_title": "בטל עסקה זאת (RBF)",
|
||||
"cancel_title": "בטל העברה זאת (RBF)",
|
||||
"cpfp_create": "צור",
|
||||
"cpfp_exp": "אנו ניצור העברה נוספת שתשתמש בעודף שנשאר מההעברה הקודמת שבוצעה. סך כל העמלה יהיה גבוה יותר מעמלת העסקה המקורית, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.",
|
||||
"cpfp_exp": "אנו ניצור העברה נוספת שתשתמש בעודף שנשאר מההעברה הקודמת שבוצעה. סך כל העמלה יהיה גבוה יותר מעמלת ההעברה המקורית, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.",
|
||||
"cpfp_no_bump": "עמלת העברה זו אינה ניתנת להעלאה",
|
||||
"cpfp_title": "הקפץ עמלה (CPFP)",
|
||||
"details_block": "גובה הבלוק",
|
||||
|
@ -289,23 +289,23 @@
|
|||
"details_outputs": "פלטים",
|
||||
"details_received": "התקבל",
|
||||
"details_show_in_block_explorer": "צפייה בסייר בלוקים",
|
||||
"details_title": "עסקה",
|
||||
"details_title": "העברה",
|
||||
"details_to": "פלט",
|
||||
"details_transaction_details": "פרטי עסקה",
|
||||
"details_transaction_details": "פרטי העברה",
|
||||
"enable_hw": "ארנק זה אינו נמצא בשימוש בצירוף ארנק חומרה. האם תרצו לאפשר שימוש בארנק חומרה?",
|
||||
"list_conf": "אישורים",
|
||||
"list_title": "תנועות",
|
||||
"rbf_explain": "אנו נחליף את העברה זו בהעברה עם עמלה גבוהה יותר, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.",
|
||||
"rbf_title": "העלאת עמלה (RBF)",
|
||||
"status_bump": "העלאת עמלה",
|
||||
"status_cancel": "ביטול עסקה",
|
||||
"status_cancel": "ביטול העברה",
|
||||
"transactions_count": "מספר תנועות"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "ביטקוין",
|
||||
"add_create": "יצירה",
|
||||
"add_entropy_generated": "{gen} בייטים של אנתרופיה",
|
||||
"add_entropy_provide": "ספקו אנטרופיה בעזרת קוביות ",
|
||||
"add_entropy_provide": "ספקו אנטרופיה על ידי הטלת קוביות ",
|
||||
"add_entropy_remain": "{gen} בייטים של אנתרופיה. שאר {rem} בייטים יתקבלו ממחולל מספרים רנדומליים של המערכת.",
|
||||
"add_import_wallet": "יבוא ארנק",
|
||||
"import_file": "יבוא קובץ",
|
||||
|
@ -341,7 +341,7 @@
|
|||
"export_title": "יצוא ארנק",
|
||||
"import_do_import": "יבוא",
|
||||
"import_error": "היבוא כשל. אנא וודאו שהמידע שסופק תקין.",
|
||||
"import_explanation": "כתבו כאן את מילות הגיבוי, המפתח הפרטי, WIF או כל דבר אחר שברשותך. BlueWallet ישתדל לנחש את הפורמט הנכון וייבא את ארנק.",
|
||||
"import_explanation": "כתבו כאן את מילות הגיבוי, המפתח הפרטי, WIF או כל דבר אחר שברשותכם. BlueWallet ישתדל לנחש את הפורמט הנכון וייבא את ארנק.",
|
||||
"import_imported": "יובא",
|
||||
"import_scan_qr": "סריקה או יבוא קובץ",
|
||||
"import_success": "ארנקך יובא בהצלחה.",
|
||||
|
@ -352,14 +352,14 @@
|
|||
"list_create_a_wallet2": "כמה שתרצו",
|
||||
"list_empty_txs1": "התנועות שלך יופיעו פה",
|
||||
"list_empty_txs1_lightning": "ארנק הברק משמש לתשלומים יומיומיים. העברות זולות בצורה לא הגיונית המתבצעות במהירות הבזק.",
|
||||
"list_empty_txs2": "התחילו שימוש בארנק",
|
||||
"list_empty_txs2": "עם תחילת השימוש בארנק",
|
||||
"list_empty_txs2_lightning": "\nלהתחלת שימוש לחצו על \"ניהול כספים\" ומלאו את היתרה",
|
||||
"list_header": "ארנק מייצר צמד מפתחות, מפתח פרטי וכתובת שאותה ניתן לשתף כדי לקבל מטבעות.",
|
||||
"list_import_error": "התרחשה שגיאה בניסיון לייבא ארנק זה.",
|
||||
"list_import_problem": "לא ניתן לייבא ארנק הזה",
|
||||
"list_latest_transaction": "עסקה אחרונה",
|
||||
"list_latest_transaction": "העברה אחרונה",
|
||||
"list_long_choose": "בחר תמונה",
|
||||
"list_long_clipboard": "העתק מלוח",
|
||||
"list_long_clipboard": "העתקה מלוח",
|
||||
"list_long_scan": "סריקת קוד QR",
|
||||
"take_photo": "צילום תמונה",
|
||||
"list_tap_here_to_buy": "קנו ביטקוין",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Következő",
|
||||
"details_no_maximum": "A kiválasztott tárca nem támogatja az automatikus teljes egyenleg számítást. Biztosan ezt a tárcát választod?",
|
||||
"details_no_multiple": "A kiválasztott tárca nem támogatja a Bitcoin küldést több kedvezményezettnek. Biztosan ezt a tárcát választod?",
|
||||
"details_no_signed_tx": "A kiválasztott fájl nem tartalmaz importálható aláírt tranzakciót.",
|
||||
"details_no_signed_tx": "A kiválasztott fájl nem tartalmaz importálható tranzakciót.",
|
||||
"details_note_placeholder": "saját megjegyzés",
|
||||
"details_scan": "Szkennelés",
|
||||
"details_total_exceeds_balance": "A megadott összeg nagyobb, mint a tárca elérhető egyenlege",
|
||||
|
@ -184,20 +184,20 @@
|
|||
"fee_10m": "10m",
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Custom",
|
||||
"fee_fast": "Fast",
|
||||
"fee_medium": "Medium",
|
||||
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte",
|
||||
"fee_satbyte": "in sat/byte",
|
||||
"fee_slow": "Slow",
|
||||
"fee_custom": "beállított",
|
||||
"fee_fast": "Gyors",
|
||||
"fee_medium": "Közepes",
|
||||
"fee_replace_min": "A teljes tranzakciós díj (satoshi / byte) amit fizetsz, magasabb lesz, mint a minimum díj.",
|
||||
"fee_satbyte": "satoshi/byte",
|
||||
"fee_slow": "Lassú",
|
||||
"header": "Küldés",
|
||||
"input_clear": "Törlés",
|
||||
"input_done": "Kész",
|
||||
"input_paste": "beillesztés",
|
||||
"input_total": "Összesen:",
|
||||
"open_settings": "Beállítások megnyitása",
|
||||
"permission_camera_message": "Kamera használat engedélyezése",
|
||||
"permission_camera_title": "Kamera használatának engedélyezése",
|
||||
"open_settings": "Beállítások megnyitása",
|
||||
"permission_storage_later": "Később",
|
||||
"permission_storage_message": "A tranzakció elmentéséhez engedélyezned kell a BlueWallet hozzáférését a háttértárhoz.",
|
||||
"permission_storage_title": "BlueWallet Tárhely Hozzáférés Engedélyezés",
|
||||
|
@ -252,6 +252,7 @@
|
|||
"general_adv_mode_e": "Ha engedélyezve van, további opciók is elérhetőek, mint különböző tárca típusok, Lightning LNDHub beállítások és entrópia beállítások tárca készítésénél. ",
|
||||
"general_continuity": "Folytonosság",
|
||||
"general_continuity_e": "Ha engedélyezve van, láthatod a kiválasztott tárcákat és tranzakciókat más, csatlakoztatott Apple iCloud eszközökön.",
|
||||
"groundcontrol_explanation": "A GroundControl egy ingyenes, nyílt forráskodú, push üzenetküldő szerver Bitcoin tárcákhoz. Telepítheted a saját GroundControl szerveredet webcímed megadásával, ami függetlet lesz a BlueWallet infrastuktúrától. Hagyd üresen alapértelmezésben.",
|
||||
"header": "beállítások",
|
||||
"language": "Nyelv",
|
||||
"language_restart": "Új nyelv kiválasztásánal, szügség lehet a BlueWallet újraindítására. ",
|
||||
|
@ -307,6 +308,7 @@
|
|||
"add_entropy_provide": "Entrópia megadása véletlenszerűen ",
|
||||
"add_entropy_remain": "{gen} byte generálva entrópiával. A megmaradt {rem} byte a rendszer véletlenszám generátorával készül.",
|
||||
"add_import_wallet": "Tárca importálása",
|
||||
"import_file": "fájl importálása",
|
||||
"add_lightning": "Lightning",
|
||||
"add_lndhub": "Kapcsolódj az LNDHub-hoz",
|
||||
"add_lndhub_error": "A megadott node cím hibás LBDHub node.",
|
||||
|
@ -340,7 +342,6 @@
|
|||
"import_do_import": "Importálás",
|
||||
"import_error": "Importálás sikertelen. Ellenőrizd, hogy helyes adatokat adtál-e meg.",
|
||||
"import_explanation": "Írd be a kulcsszavaidat, a titkos kulcsodat, WIF-et, vagy bármi mást. A BlueWallet megpróbálja kitalálni a helyes formátumot, és importálja a tárcádat",
|
||||
"import_file": "fájl importálása",
|
||||
"import_imported": "Importálva",
|
||||
"import_scan_qr": "vagy QR-kód szkennelése?",
|
||||
"import_success": "Sikeres importálás!",
|
||||
|
@ -360,6 +361,7 @@
|
|||
"list_long_choose": "Válassz fényképet",
|
||||
"list_long_clipboard": "Másolás vágólapról",
|
||||
"list_long_scan": "QR kód szkennelése",
|
||||
"take_photo": "Fénykép készítése",
|
||||
"list_tap_here_to_buy": "Bitcoin vásárláshoz kattints ide",
|
||||
"list_title": "tárcák",
|
||||
"list_tryagain": "Próbáld újra",
|
||||
|
@ -367,7 +369,6 @@
|
|||
"select_no_bitcoin": "Jelenleg nincs elérhető Bitcoin tárca.",
|
||||
"select_no_bitcoin_exp": "A Lightning tárca feltöltéséhez Bitcoin tárcára van szükség. Készíts vagy importálj egy Bitcoin tárcát.",
|
||||
"select_wallet": "Válassz tárcát",
|
||||
"take_photo": "Fénykép készítése",
|
||||
"xpub_copiedToClipboard": "Vágólapra másolva",
|
||||
"xpub_title": "a tárca XPUB kulcsa"
|
||||
}
|
||||
|
|
|
@ -170,13 +170,13 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "catatan pribadi",
|
||||
"details_scan": "Pindai",
|
||||
"details_total_exceeds_balance": "Jumlah yang dikirim melebihi saldo.",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_selection": "Wallet Selection",
|
||||
"dynamic_init": "Initialing",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Next",
|
||||
"dynamic_prev": "Previous",
|
||||
"dynamic_start": "Start",
|
||||
|
|
262
loc/it.json
262
loc/it.json
|
@ -5,17 +5,107 @@
|
|||
"continue": "Continua",
|
||||
"enter_password": "Inserisci password",
|
||||
"never": "mai",
|
||||
"of": "{number} su {total}",
|
||||
"ok": "OK",
|
||||
"storage_is_encrypted": "Il tuo archivio è criptato. È necessaria una password per decriptarlo"
|
||||
"storage_is_encrypted": "Il tuo archivio è criptato. È necessaria una password per decriptarlo",
|
||||
"yes": "Sì"
|
||||
},
|
||||
"azteco": {
|
||||
"codeIs": "Your voucher code is",
|
||||
"errorBeforeRefeem": "Before redeeming you must first add a Bitcoin wallet.",
|
||||
"errorSomething": "Something went wrong. Is this voucher still valid?",
|
||||
"redeem": "Redeem to wallet",
|
||||
"redeemButton": "Redeem",
|
||||
"success": "Fatto",
|
||||
"title": "Redeem Azte.co voucher"
|
||||
},
|
||||
"entropy": {
|
||||
"save": "Salva",
|
||||
"title": "Entropy",
|
||||
"undo": "Annulla"
|
||||
},
|
||||
"errors": {
|
||||
"broadcast": "Broadcast failed",
|
||||
"error": "Errore",
|
||||
"network": "Errore di rete"
|
||||
},
|
||||
"hodl": {
|
||||
"are_you_sure_you_want_to_logout": "Are you sure you want to logout from HodlHodl?",
|
||||
"cont_address_escrow": "Escrow",
|
||||
"cont_address_to": "A",
|
||||
"cont_buying": "buying",
|
||||
"cont_cancel": "Cancella contratto",
|
||||
"cont_cancel_q": "Sei sicuro di voler cancellare questo contratto?",
|
||||
"cont_cancel_y": "Sì, cancella il contratto",
|
||||
"cont_chat": "Open chat with counterparty",
|
||||
"cont_how": "Come pagare",
|
||||
"cont_no": "You don't have any contracts in progress",
|
||||
"cont_paid": "Mark contract as Paid",
|
||||
"cont_paid_e": "Do this only if you sent funds to the seller via agreed payment method",
|
||||
"cont_paid_q": "Are you sure you want to mark this contract as paid?",
|
||||
"cont_selling": "selling",
|
||||
"cont_st_completed": "All done!",
|
||||
"cont_st_in_progress_buyer": "Coins are in escrow, please pay seller",
|
||||
"cont_st_paid_enought": "Bitcoins are in escrow! Please pay seller\nvia agreed payment method",
|
||||
"cont_st_paid_waiting": "Waiting for seller to release coins from escrow",
|
||||
"cont_st_waiting": "Waiting for seller to deposit bitcoins to escrow...",
|
||||
"cont_title": "I miei contratti",
|
||||
"filter_any": "Any",
|
||||
"filter_buying": "Buying",
|
||||
"filter_country_global": "Global offers",
|
||||
"filter_country_near": "Vicino a me",
|
||||
"filter_currency": "Valuta",
|
||||
"filter_detail": "Dettaglio",
|
||||
"filter_filters": "Filtri",
|
||||
"filter_iambuying": "Sto comprando bitcoin",
|
||||
"filter_iamselling": "Sto vendendo bitcoin",
|
||||
"filter_method": "Metodo di pagamento",
|
||||
"filter_search": "Cerca",
|
||||
"filter_selling": "Selling",
|
||||
"item_minmax": "Min/Max",
|
||||
"item_nooffers": "No offers. Try to change \"Near me\" to Global offers!",
|
||||
"item_rating": "{rating} trades",
|
||||
"item_rating_no": "Nessuna recensione",
|
||||
"login": "Login",
|
||||
"mycont": "I miei contratti",
|
||||
"offer_accept": "Accept offer",
|
||||
"offer_account_finish": "Looks like you didn't finish setting up account on HodlHodl, would you like to finish setup now?",
|
||||
"offer_choosemethod": "Scegli il metodo di pagamento",
|
||||
"offer_confirmations": "Conferme",
|
||||
"offer_minmax": "min / max",
|
||||
"offer_minutes": "min",
|
||||
"offer_promt_fiat": "How much {currency} do you want to buy?",
|
||||
"offer_promt_fiat_e": "For example 100",
|
||||
"offer_window": "window",
|
||||
"p2p": "A p2p exchange"
|
||||
},
|
||||
"lnd": {
|
||||
"errorInvoiceExpired": "Invoice expired",
|
||||
"exchange": "Exchange",
|
||||
"expired": "Scaduto",
|
||||
"expiredLow": "expired",
|
||||
"expiresIn": "Expires: {time}",
|
||||
"payButton": "Pay",
|
||||
"placeholder": "Fattura",
|
||||
"potentialFee": "Commissioni potenziali: {fee}",
|
||||
"refill": "Ricarica",
|
||||
"refill_card": "Ricarica con una carta",
|
||||
"refill_create": "In order to proceed, please create a Bitcoin wallet to refill with.",
|
||||
"refill_external": "Refill with External Wallet",
|
||||
"refill_lnd_balance": "Ricarica saldo del portafoglio Lightning",
|
||||
"sameWalletAsInvoiceError": "Non puoi pagare una fattura con lo stesso portafoglio utilizzato per crearla.",
|
||||
"title": "Gestisci fondi"
|
||||
},
|
||||
"lndViewInvoice": {
|
||||
"additional_info": "Additional Information",
|
||||
"for": "For:",
|
||||
"has_been_paid": "This invoice has been paid for",
|
||||
"open_direct_channel": "Open direct channel with this node:",
|
||||
"please_pay": "Please pay",
|
||||
"preimage": "Preimage",
|
||||
"sats": "sats",
|
||||
"wasnt_paid_and_expired": "This invoice was not paid for and has expired"
|
||||
},
|
||||
"plausibledeniability": {
|
||||
"create_fake_storage": "Crea archivio falso criptato",
|
||||
"create_password": "Crea una password",
|
||||
|
@ -28,6 +118,16 @@
|
|||
"success": "Fatto",
|
||||
"title": "Negazione Plausibile"
|
||||
},
|
||||
"pleasebackup": {
|
||||
"ask": "Have you saved your wallet's backup phrase? This backup phrase is required to access your funds in case you lose this device. Without the backup phrase, your funds will be permanently lost.",
|
||||
"ask_no": "No, I have not",
|
||||
"ask_yes": "Yes, I have",
|
||||
"ok": "OK, I wrote this down!",
|
||||
"ok_lnd": "OK, I have saved it.",
|
||||
"text": "Please take a moment to write down this mnemonic phrase on a piece of paper. It's your backup you can use to restore the wallet on other device.",
|
||||
"text_lnd": "Please take a moment to save this LNDHub authentication. It's your backup you can use to restore the wallet on other device.",
|
||||
"title": "Your wallet is created..."
|
||||
},
|
||||
"receive": {
|
||||
"details_create": "Crea",
|
||||
"details_label": "Descrizione",
|
||||
|
@ -36,65 +136,207 @@
|
|||
"header": "Ricevi"
|
||||
},
|
||||
"send": {
|
||||
"broadcastButton": "BROADCAST",
|
||||
"broadcastError": "error",
|
||||
"broadcastNone": "Input transaction hash",
|
||||
"broadcastPending": "pending",
|
||||
"broadcastSuccess": "success",
|
||||
"confirm_header": "Conferma",
|
||||
"confirm_sendNow": "Invia ora",
|
||||
"create_amount": "Importo",
|
||||
"create_broadcast": "Trasmetti",
|
||||
"create_copy": "Copy and broadcast later",
|
||||
"create_details": "Dettagli",
|
||||
"create_fee": "Commissione",
|
||||
"create_memo": "Memo",
|
||||
"create_satoshi_per_byte": "Satoshi per byte",
|
||||
"create_this_is_hex": "Questo è l'hex della transazione, firmato e pronto per essere trasmesso sulla rete.",
|
||||
"create_to": "A",
|
||||
"create_tx_size": "Grandezza TX",
|
||||
"create_verify": "Verify on coinb.in",
|
||||
"details_add_rec_add": "Add Recipient",
|
||||
"details_add_rec_rem": "Remove Recipient",
|
||||
"details_address": "Indirizzo",
|
||||
"details_address_field_is_not_valid": "Indirizzo non valido",
|
||||
"details_adv_fee_bump": "Allow Fee Bump",
|
||||
"details_adv_full": "Use Full Balance",
|
||||
"details_adv_full_remove": "Your other recipients will be removed from this transaction.",
|
||||
"details_adv_full_sure": "Are you sure you want to use your wallet's full balance for this transaction?",
|
||||
"details_adv_import": "Import Transaction",
|
||||
"details_amount_field_is_not_valid": "Importo non valido",
|
||||
"details_create": "Crea",
|
||||
"details_error_decode": "Error: Unable to decode Bitcoin address",
|
||||
"details_fee_field_is_not_valid": "Commissione non valida",
|
||||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "Nota",
|
||||
"details_scan": "Scansiona",
|
||||
"details_total_exceeds_balance": "L'importo da inviare eccede i fondi disponibili.",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_selection": "Wallet Selection",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Next",
|
||||
"dynamic_prev": "Previous",
|
||||
"dynamic_start": "Start",
|
||||
"dynamic_stop": "Stop",
|
||||
"fee_10m": "10m",
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Custom",
|
||||
"fee_fast": "Fast",
|
||||
"fee_medium": "Medium",
|
||||
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte",
|
||||
"fee_satbyte": "in sat/byte",
|
||||
"fee_slow": "Slow",
|
||||
"header": "Invia",
|
||||
"success_done": "Fatto"
|
||||
"input_clear": "Clear",
|
||||
"input_done": "Done",
|
||||
"input_paste": "Paste",
|
||||
"input_total": "Total:",
|
||||
"permission_camera_message": "We need your permission to use your camera",
|
||||
"permission_camera_title": "Permission to use camera",
|
||||
"open_settings": "Open Settings",
|
||||
"permission_storage_later": "Ask Me Later",
|
||||
"permission_storage_message": "BlueWallet needs your permission to access your storage to save this transaction.",
|
||||
"permission_storage_title": "BlueWallet Storage Access Permission",
|
||||
"psbt_clipboard": "Copy to Clipboard",
|
||||
"psbt_this_is_psbt": "This is a partially signed bitcoin transaction (PSBT). Please finish signing it with your hardware wallet.",
|
||||
"psbt_tx_export": "Export to file",
|
||||
"psbt_tx_open": "Open Signed Transaction",
|
||||
"psbt_tx_scan": "Scan Signed Transaction",
|
||||
"qr_error_no_qrcode": "The selected image does not contain a QR Code.",
|
||||
"qr_error_no_wallet": "The selected file does not contain a wallet that can be imported.",
|
||||
"success_done": "Fatto",
|
||||
"txSaved": "The transaction file ({filePath}) has been saved in your Downloads folder ."
|
||||
},
|
||||
"settings": {
|
||||
"about": "Informazioni",
|
||||
"about_awesome": "Built with the awesome",
|
||||
"about_backup": "Always backup your keys!",
|
||||
"about_free": "BlueWallet is a free and open source project. Crafted by Bitcoin users.",
|
||||
"about_release_notes": "Release notes",
|
||||
"about_review": "Leave us a review",
|
||||
"about_selftest": "Run self test",
|
||||
"about_sm_github": "GitHub",
|
||||
"about_sm_telegram": "Telegram chat",
|
||||
"about_sm_twitter": "Follow us on Twitter",
|
||||
"advanced_options": "Advanced Options",
|
||||
"currency": "Valuta",
|
||||
"currency_source": "Prices are obtained from CoinDesk",
|
||||
"default_desc": "When disabled, BlueWallet will immediately open the selected wallet at launch.",
|
||||
"default_info": "Default info",
|
||||
"default_title": "On Launch",
|
||||
"default_wallets": "View All Wallets",
|
||||
"electrum_connected": "Connected",
|
||||
"electrum_connected_not": "Not Connected",
|
||||
"electrum_error_connect": "Can't connect to provided Electrum server",
|
||||
"electrum_host": "host, for example {example}",
|
||||
"electrum_port": "TCP port, usually {example}",
|
||||
"electrum_port_ssl": "SSL port, usually {example}",
|
||||
"electrum_saved": "Your changes have been saved successfully. Restart may be required for changes to take effect.",
|
||||
"electrum_settings": "Electrum Settings",
|
||||
"electrum_settings_explain": "Set to blank to use default",
|
||||
"electrum_status": "Status",
|
||||
"encrypt_decrypt": "Decrypt Storage",
|
||||
"encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.",
|
||||
"encrypt_del_uninstall": "Delete if BlueWallet is uninstalled",
|
||||
"encrypt_enc_and_pass": "Encrypted and Password protected",
|
||||
"encrypt_title": "Security",
|
||||
"encrypt_tstorage": "storage",
|
||||
"encrypt_use": "Use {type}",
|
||||
"encrypt_use_expl": "{type} will be used to confirm your identity prior to making a transaction, unlocking, exporting or deleting a wallet. {type} will not be used to unlock an encrypted storage.",
|
||||
"general": "General",
|
||||
"general_adv_mode": "Enable advanced mode",
|
||||
"general_adv_mode_e": "When enabled, you will see advanced options such as different wallet types, the ability to specify the LNDHub instance you wish to connect to and custom entropy during wallet creation.",
|
||||
"general_continuity": "Continuity",
|
||||
"general_continuity_e": "When enabled, you will be able to view selected wallets, and transactions, using your other Apple iCloud connected devices.",
|
||||
"groundcontrol_explanation": "GroundControl is a free opensource push notifications server for bitcoin wallets. You can install your own GroundControl server and put its URL here to not rely on BlueWallet's infrastructure. Leave blank to use default",
|
||||
"header": "Impostazioni",
|
||||
"language": "Lingua",
|
||||
"language_restart": "When selecting a new language, restarting BlueWallet may be required for the change to take effect.",
|
||||
"lightning_error_lndhub_uri": "Not a valid LndHub URI",
|
||||
"lightning_saved": "Your changes have been saved successfully",
|
||||
"lightning_settings": "Impostazioni Lightning",
|
||||
"lightning_settings_explain": "Per connetterti al tuo nodo LND personale installa LndHub e inserisci il suo URL qui nelle impostazioni. Lascialo vuoto per utilizzare il nodo LndHub di default (lndhub.io)",
|
||||
"network": "Network",
|
||||
"network_broadcast": "Broadcast transaction",
|
||||
"network_electrum": "Electrum server",
|
||||
"not_a_valid_uri": "Not a valid URI",
|
||||
"notifications": "Notifications",
|
||||
"password": "Password",
|
||||
"password_explain": "Crea la password che userai per decriptare l'archivio",
|
||||
"passwords_do_not_match": "Le password non corrispondono",
|
||||
"plausible_deniability": "Negazione plausibile...",
|
||||
"push_notifications": "Push notifications",
|
||||
"retype_password": "Reinserisci password",
|
||||
"save": "Salva"
|
||||
"save": "Salva",
|
||||
"saved": "Saved"
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_explain": "We will replace this transaction with the one that pays you and has higher fees. This effectively cancels transaction. This is called RBF - Replace By Fee.",
|
||||
"cancel_no": "This transaction is not replaceable",
|
||||
"cancel_title": "Cancel this transaction (RBF)",
|
||||
"cpfp_create": "Create",
|
||||
"cpfp_exp": "We will create another transaction that spends your unconfirmed transaction. The total fee will be higher than the original transaction fee, so it should be mined faster. This is called CPFP - Child Pays For Parent.",
|
||||
"cpfp_no_bump": "This transaction is not bumpable",
|
||||
"cpfp_title": "Bump fee (CPFP)",
|
||||
"details_block": "Block Height",
|
||||
"details_copy": "Copia",
|
||||
"details_from": "Da",
|
||||
"details_inputs": "Inputs",
|
||||
"details_outputs": "Outputs",
|
||||
"details_received": "Received",
|
||||
"details_show_in_block_explorer": "Mostra sul block explorer",
|
||||
"details_title": "Transazione",
|
||||
"details_to": "A",
|
||||
"details_transaction_details": "Dettagli transazione",
|
||||
"list_title": "Transazioni"
|
||||
"enable_hw": "This wallet is not being used in conjunction with a hardwarde wallet. Would you like to enable hardware wallet use?",
|
||||
"list_conf": "conf",
|
||||
"list_title": "Transazioni",
|
||||
"rbf_explain": "We will replace this transaction with the one with a higher fee, so it should be mined faster. This is called RBF - Replace By Fee.",
|
||||
"rbf_title": "Bump fee (RBF)",
|
||||
"status_bump": "Bump Fee",
|
||||
"status_cancel": "Cancel Transaction",
|
||||
"transactions_count": "transactions count"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "Bitcoin",
|
||||
"add_create": "Crea",
|
||||
"add_entropy_generated": "{gen} bytes of generated entropy",
|
||||
"add_entropy_provide": "Provide entropy via dice rolls",
|
||||
"add_entropy_remain": "{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.",
|
||||
"add_import_wallet": "Importa Portafoglio",
|
||||
"import_file": "Import File",
|
||||
"add_lightning": "Lightning",
|
||||
"add_lndhub": "Connect to your LNDHub",
|
||||
"add_lndhub_error": "The provided node address is not valid LNDHub node.",
|
||||
"add_lndhub_placeholder": "your node address",
|
||||
"add_or": "o",
|
||||
"add_title": "Aggiungi Portafoglio",
|
||||
"add_wallet_name": "Nome Portafoglio",
|
||||
"add_wallet_type": "Tipo",
|
||||
"details_address": "Indirizzo",
|
||||
"details_advanced": "Advanced",
|
||||
"details_are_you_sure": "Sei sicuro?",
|
||||
"details_connected_to": "Connected to",
|
||||
"details_del_wb": "Wallet Balance",
|
||||
"details_del_wb_err": "The provided balance amount does not match this wallet's balance. Please, try again",
|
||||
"details_del_wb_q": "This wallet has a balance. Before proceeding, please be aware that you will not be able to recover the funds without this wallet's seed phrase. In order to avoid accidental removal this wallet, please enter your wallet's balance of {balance} satoshis.",
|
||||
"details_delete": "Elimina",
|
||||
"details_delete_wallet": "Delete wallet",
|
||||
"details_display": "display in wallets list",
|
||||
"details_export_backup": "Esporta / Backup",
|
||||
"details_marketplace": "Marketplace",
|
||||
"details_master_fingerprint": "Master fingerprint",
|
||||
"details_no_cancel": "No, annulla",
|
||||
"details_save": "Salva",
|
||||
"details_show_xpub": "Mostra XPUB del portafoglio",
|
||||
"details_title": "Portafoglio",
|
||||
"details_type": "Tipo",
|
||||
"details_use_with_hardware_wallet": "Use with hardware wallet",
|
||||
"details_wallet_updated": "Wallet updated",
|
||||
"details_yes_delete": "Si, elimina",
|
||||
"export_title": "Esporta portafoglio",
|
||||
"import_do_import": "Importa",
|
||||
|
@ -109,11 +351,23 @@
|
|||
"list_create_a_wallet1": "È gratuito e puoi crearne",
|
||||
"list_create_a_wallet2": "quanti ne vuoi",
|
||||
"list_empty_txs1": "Le tue transazioni appariranno qui,",
|
||||
"list_empty_txs1_lightning": "Lightning wallet should be used for your daily transactions. Fees are unfairly cheap and speed is blazing fast.",
|
||||
"list_empty_txs2": "Nessuna transazione",
|
||||
"list_empty_txs2_lightning": "\nTo start using it tap on \"manage funds\" and topup your balance.",
|
||||
"list_header": "A wallet represents a pair of keys, one private and one you can share to receive coins.",
|
||||
"list_import_error": "An error was encountered when attempting to import this wallet.",
|
||||
"list_import_problem": "There was a problem importing this wallet",
|
||||
"list_latest_transaction": "Transazioni recenti",
|
||||
"list_long_choose": "Choose Photo",
|
||||
"list_long_clipboard": "Copy from Clipboard",
|
||||
"list_long_scan": "Scan QR Code",
|
||||
"take_photo": "Take Photo",
|
||||
"list_tap_here_to_buy": "Clicca qui per comprare Bitcoin",
|
||||
"list_title": "Portafogli",
|
||||
"list_tryagain": "Try Again",
|
||||
"reorder_title": "Riordina Portafogli",
|
||||
"select_no_bitcoin": "There are currently no Bitcoin wallets available.",
|
||||
"select_no_bitcoin_exp": "A Bitcoin wallet is required to refill Lightning wallets. Please, create or import one.",
|
||||
"select_wallet": "Seleziona Portafoglio",
|
||||
"xpub_copiedToClipboard": "Copiata negli appunti.",
|
||||
"xpub_title": "XPUB del Portafoglio"
|
||||
|
|
172
loc/jp_jp.json
172
loc/jp_jp.json
|
@ -32,7 +32,7 @@
|
|||
"hodl": {
|
||||
"are_you_sure_you_want_to_logout": "本当にHodlHodlからログアウトしますか?",
|
||||
"cont_address_escrow": "エスクロー",
|
||||
"cont_address_to": "To",
|
||||
"cont_address_to": "宛先",
|
||||
"cont_buying": "買う",
|
||||
"cont_cancel": "コントラクトをキャンセル",
|
||||
"cont_cancel_q": "本当にこのコントラクトをキャンセルしますか?",
|
||||
|
@ -50,8 +50,8 @@
|
|||
"cont_st_paid_waiting": "販売者がエスクローからコインをリリースするのを待っています。",
|
||||
"cont_st_waiting": "販売者がエスクローにビットコインを預けるのを待っています。",
|
||||
"cont_title": "マイコントラクト",
|
||||
"filter_any": "Any",
|
||||
"filter_buying": "Buying",
|
||||
"filter_any": "すべて",
|
||||
"filter_buying": "買う",
|
||||
"filter_country_global": "グローバル・オファー",
|
||||
"filter_country_near": "近くで探す",
|
||||
"filter_currency": "通貨",
|
||||
|
@ -74,7 +74,7 @@
|
|||
"offer_confirmations": "承認",
|
||||
"offer_minmax": "最小 / 最大",
|
||||
"offer_minutes": "分",
|
||||
"offer_promt_fiat": "How much {currency} do you want to buy?",
|
||||
"offer_promt_fiat": "いくら {currency} を購入しますか?",
|
||||
"offer_promt_fiat_e": "例 100",
|
||||
"offer_window": "ウィンドウ",
|
||||
"p2p": "P2P エクスチェンジ"
|
||||
|
@ -89,9 +89,9 @@
|
|||
"placeholder": "入金依頼",
|
||||
"potentialFee": "手数料推計: {fee}",
|
||||
"refill": "送金",
|
||||
"refill_card": "Refill with bank card",
|
||||
"refill_create": "In order to proceed, please create a Bitcoin wallet to refill with.",
|
||||
"refill_external": "Refill with External Wallet",
|
||||
"refill_card": "銀行カードで補充する",
|
||||
"refill_create": "先に進むためには、ビットコインウォレットを作成して補充してください。",
|
||||
"refill_external": "外部ウォレットで補充",
|
||||
"refill_lnd_balance": "Lightning ウォレットへ送金",
|
||||
"sameWalletAsInvoiceError": "以前作成したウォレットと同じウォレットへの支払いはできません。",
|
||||
"title": "資金の管理"
|
||||
|
@ -125,7 +125,7 @@
|
|||
"ok": "すべてのニモニックを書きとめました",
|
||||
"ok_lnd": "はい、書きとめました",
|
||||
"text": "すべてのニモニックを別紙に書きとめてください。他のデバイスへウォレットをリストアする際にニモニックが必要になります。デスクトップ用ウォレットの Electrum wallet (https://electrum.org/) へニモニックを使用してウォレットをリストアすることが可能です。",
|
||||
"text_lnd": "Please take a moment to save this LNDHub authentication. It's your backup you can use to restore the wallet on other device.",
|
||||
"text_lnd": "このLNDHub認証を保存しておいてください。これはあなたのバックアップであり、他のデバイス上でウォレットを復元するために使用できます。",
|
||||
"title": "ウォレットを作成しています..."
|
||||
},
|
||||
"receive": {
|
||||
|
@ -136,9 +136,9 @@
|
|||
"header": "入金"
|
||||
},
|
||||
"send": {
|
||||
"broadcastButton": "BROADCAST",
|
||||
"broadcastButton": "ブロードキャスト",
|
||||
"broadcastError": "エラー",
|
||||
"broadcastNone": "Input transaction hash",
|
||||
"broadcastNone": "インプットトランザクションハッシュ",
|
||||
"broadcastPending": "試行中",
|
||||
"broadcastSuccess": "成功",
|
||||
"confirm_header": "確認",
|
||||
|
@ -158,38 +158,38 @@
|
|||
"details_add_rec_rem": "宛先を削除",
|
||||
"details_address": "アドレス",
|
||||
"details_address_field_is_not_valid": "アドレス欄が正しくありません",
|
||||
"details_adv_fee_bump": "Allow Fee Bump",
|
||||
"details_adv_full": "Use Full Balance",
|
||||
"details_adv_full_remove": "Your other recipients will be removed from this transaction.",
|
||||
"details_adv_full_sure": "Are you sure you want to use your wallet's full balance for this transaction?",
|
||||
"details_adv_import": "Import Transaction",
|
||||
"details_adv_fee_bump": "費用のバンプ(増加)を許可",
|
||||
"details_adv_full": "全残高を使う",
|
||||
"details_adv_full_remove": "BlueWalletがアンインストールされたら消去",
|
||||
"details_adv_full_sure": "本当にこのウォレットの全残高をこのトランザクションに利用しますか?",
|
||||
"details_adv_import": "トランザクションをインポート",
|
||||
"details_amount_field_is_not_valid": "金額欄が正しくありません",
|
||||
"details_create": "作成",
|
||||
"details_error_decode": "Error: Unable to decode Bitcoin address",
|
||||
"details_error_decode": "エラー:ビットコインアドレスを復号できません",
|
||||
"details_fee_field_is_not_valid": "手数料欄が正しくありません",
|
||||
"details_next": "次",
|
||||
"details_no_maximum": "選択したウォレットは、最大残高の自動計算に対応していません。このウォレットを選択してもよろしいですか?",
|
||||
"details_no_multiple": "選択したウォレットは、複数の受信者へのビットコインの送信をサポートしていません。このウォレットを選択してもよろしいですか?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "選択したファイルには、インポート可能なトランザクションが含まれていません。",
|
||||
"details_note_placeholder": "ラベル",
|
||||
"details_scan": "読取り",
|
||||
"details_total_exceeds_balance": "送金額が利用可能残額を超えています。",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_before_tx": "トランザクションを作成する前に、まずはビットコインウォレットを追加する必要があります。",
|
||||
"details_wallet_selection": "ウォレット選択",
|
||||
"dynamic_init": "インストール中",
|
||||
"dynamic_next": "次",
|
||||
"dynamic_prev": "前",
|
||||
"dynamic_start": "スタート",
|
||||
"dynamic_stop": "ストップ",
|
||||
"fee_10m": "10m",
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Custom",
|
||||
"fee_fast": "Fast",
|
||||
"fee_medium": "Medium",
|
||||
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte",
|
||||
"fee_satbyte": "in sat/byte",
|
||||
"fee_slow": "Slow",
|
||||
"fee_10m": "10分",
|
||||
"fee_1d": "1日",
|
||||
"fee_3h": "3時間",
|
||||
"fee_custom": "カスタム",
|
||||
"fee_fast": "高速",
|
||||
"fee_medium": "中速",
|
||||
"fee_replace_min": "支払いたい手数料レート(1バイトあたりのsatoshi)は、{min} sat/byteよりも高い必要があります。",
|
||||
"fee_satbyte": "sat/バイト",
|
||||
"fee_slow": "低速",
|
||||
"header": "送金",
|
||||
"input_clear": "クリア",
|
||||
"input_done": "完了",
|
||||
|
@ -197,71 +197,71 @@
|
|||
"input_total": "合計:",
|
||||
"permission_camera_message": "カメラを使用するのに許可が必要です",
|
||||
"permission_camera_title": "カメラの使用許可",
|
||||
"open_settings": "Open Settings",
|
||||
"permission_storage_later": "Ask Me Later",
|
||||
"permission_storage_message": "BlueWallet needs your permission to access your storage to save this transaction.",
|
||||
"permission_storage_title": "BlueWallet Storage Access Permission",
|
||||
"open_settings": "設定を開く",
|
||||
"permission_storage_later": "後で聞く",
|
||||
"permission_storage_message": "BlueWalletはこのトランザクションを保存するためにストレージへのアクセス許可を必要としています。",
|
||||
"permission_storage_title": "BlueWalletストレージアクセス許可",
|
||||
"psbt_clipboard": "クリップボードにコピー",
|
||||
"psbt_this_is_psbt": "これは部分的に署名されたビットコイントランザクション(PSBT)です。ハードウェアウォレットで署名を完了させてください。",
|
||||
"psbt_tx_export": "ファイルにエクスポート",
|
||||
"psbt_tx_open": "署名トランザクションを開く",
|
||||
"psbt_tx_scan": "署名トランザクションをスキャン",
|
||||
"qr_error_no_qrcode": "選択された画像はQRコードを含んでいません。",
|
||||
"qr_error_no_wallet": "The selected file does not contain a wallet that can be imported.",
|
||||
"qr_error_no_wallet": "選択されたファイルにはインポートできるウォレットが含まれていません。",
|
||||
"success_done": "完了",
|
||||
"txSaved": "The transaction file ({filePath}) has been saved in your Downloads folder ."
|
||||
"txSaved": "トランザクションファイル ({filePath}) はダウンロードフォルダに保存されました。"
|
||||
},
|
||||
"settings": {
|
||||
"about": "BlueWallet について",
|
||||
"about_awesome": "Built with the awesome",
|
||||
"about_backup": "Always backup your keys!",
|
||||
"about_free": "BlueWallet is a free and open source project. Crafted by Bitcoin users.",
|
||||
"about_release_notes": "Release notes",
|
||||
"about_backup": "常に秘密鍵はバックアップしましょう!",
|
||||
"about_free": "BlueWalletはフリーでオープンソースのプロジェクトです。ビットコインユーザーによって作られています。",
|
||||
"about_release_notes": "リリースノート",
|
||||
"about_review": "レビューを書く",
|
||||
"about_selftest": "Run self test",
|
||||
"about_selftest": "セルフテストを実行",
|
||||
"about_sm_github": "GitHub",
|
||||
"about_sm_telegram": "テレグラムチャット",
|
||||
"about_sm_twitter": "Twitterでフォロー",
|
||||
"advanced_options": "上級設定",
|
||||
"currency": "通貨",
|
||||
"currency_source": "CoinDeskから取得された価格",
|
||||
"default_desc": "When disabled, BlueWallet will immediately open the selected wallet at launch.",
|
||||
"default_info": "Default info",
|
||||
"default_title": "On Launch",
|
||||
"default_wallets": "View All Wallets",
|
||||
"electrum_connected": "Connected",
|
||||
"electrum_connected_not": "Not Connected",
|
||||
"electrum_error_connect": "Can't connect to provided Electrum server",
|
||||
"electrum_host": "host, for example {example}",
|
||||
"electrum_port": "TCP port, usually {example}",
|
||||
"electrum_port_ssl": "SSL port, usually {example}",
|
||||
"electrum_saved": "Your changes have been saved successfully. Restart may be required for changes to take effect.",
|
||||
"electrum_settings": "Electrum Settings",
|
||||
"electrum_settings_explain": "Set to blank to use default",
|
||||
"default_desc": "無効にすれば、BlueWalletは起動時に選択したウォレットをすぐに開きます。",
|
||||
"default_info": "デフォルト情報",
|
||||
"default_title": "起動時",
|
||||
"default_wallets": "すべてのウォレットを確認",
|
||||
"electrum_connected": "接続済",
|
||||
"electrum_connected_not": "未接続",
|
||||
"electrum_error_connect": "指定されたElectrumサーバーに接続できません",
|
||||
"electrum_host": "ホスト 例 {example}",
|
||||
"electrum_port": "TCP ポート 通常 {example}",
|
||||
"electrum_port_ssl": "SSL ポート 通常 {example}",
|
||||
"electrum_saved": "変更は正常に保存されました。変更の適用には、リスタートが必要な場合があります。",
|
||||
"electrum_settings": "Electrum 設定",
|
||||
"electrum_settings_explain": "ブランクに設定してデフォルトを使用",
|
||||
"electrum_status": "ステータス",
|
||||
"encrypt_decrypt": "ストレージ復号化",
|
||||
"encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.",
|
||||
"encrypt_del_uninstall": "Delete if BlueWallet is uninstalled",
|
||||
"encrypt_enc_and_pass": "Encrypted and Password protected",
|
||||
"encrypt_decrypt_q": "本当にストレージを復号化しますか?これによりウォレットがパスワードなしでアクセス可能になります。",
|
||||
"encrypt_del_uninstall": "BlueWalletをアンインストールしたら削除",
|
||||
"encrypt_enc_and_pass": "暗号化されパスワードで保護されています",
|
||||
"encrypt_title": "セキュリティ",
|
||||
"encrypt_tstorage": "ストレージ",
|
||||
"encrypt_use": "{type} を使う",
|
||||
"encrypt_use_expl": "{type} は、トランザクションの実行、ウォレットのロック解除、エクスポート、または削除を行う前の本人確認に使用されます。{type} は暗号化されたストレージのロック解除には使用されません。",
|
||||
"general": "一般情報",
|
||||
"general_adv_mode": "Enable advanced mode",
|
||||
"general_adv_mode_e": "When enabled, you will see advanced options such as different wallet types, the ability to specify the LNDHub instance you wish to connect to and custom entropy during wallet creation.",
|
||||
"general_continuity": "Continuity",
|
||||
"general_continuity_e": "When enabled, you will be able to view selected wallets, and transactions, using your other Apple iCloud connected devices.",
|
||||
"groundcontrol_explanation": "GroundControl is a free opensource push notifications server for bitcoin wallets. You can install your own GroundControl server and put its URL here to not rely on BlueWallet's infrastructure. Leave blank to use default",
|
||||
"general_adv_mode_e": "この機能を有効にすると、異なるウォレットタイプ、接続先の LNDHub インスタンスの指定、ウォレット作成時のカスタムエントロピーなどの高度なオプションが表示されます。",
|
||||
"general_continuity": "継続性",
|
||||
"general_continuity_e": "この機能を有効にすると、Apple iCloudに接続している他のデバイスを使用して、選択したウォレットやトランザクションを表示できるようになります。",
|
||||
"groundcontrol_explanation": "GroundControlはビットコインウォレットのための無料のオープンソースのプッシュ通知サーバーです。独自のGroundControlサーバーをインストールし、BlueWalletのインフラに依存しないようにURLをここに入力することができます。デフォルトを使用するには空白のままにしてください。",
|
||||
"header": "設定",
|
||||
"language": "言語",
|
||||
"language_restart": "When selecting a new language, restarting BlueWallet may be required for the change to take effect.",
|
||||
"lightning_error_lndhub_uri": "Not a valid LndHub URI",
|
||||
"lightning_saved": "Your changes have been saved successfully",
|
||||
"language_restart": "新しい言語を選択した場合、変更を有効にするには BlueWallet の再起動が必要な場合があります。",
|
||||
"lightning_error_lndhub_uri": "有効なLndHub URIではありません",
|
||||
"lightning_saved": "変更は正常に保存されました",
|
||||
"lightning_settings": "Lightning 設定",
|
||||
"lightning_settings_explain": "他の LND ノードへ接続するには LndHub をインストール後、URL を入力してください。既定の設定を使用するには空欄にしますndHub\n (lndhub.io)",
|
||||
"network": "Network",
|
||||
"network_broadcast": "Broadcast transaction",
|
||||
"network": "ネットワーク",
|
||||
"network_broadcast": "ブロードキャストトランザクション",
|
||||
"network_electrum": "Electrum サーバー",
|
||||
"not_a_valid_uri": "有効なURIではありません",
|
||||
"notifications": "通知",
|
||||
|
@ -275,11 +275,11 @@
|
|||
"saved": "保存済"
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_explain": "We will replace this transaction with the one that pays you and has higher fees. This effectively cancels transaction. This is called RBF - Replace By Fee.",
|
||||
"cancel_explain": "このトランザクションを、最初の支払い時より高い手数料を持つものに置き換えます。これは事実上、最初のトランザクションをキャンセルします。これはReplace By Fee - RBFと呼ばれています。",
|
||||
"cancel_no": "このトランザクションは交換可能ではありません",
|
||||
"cancel_title": "このトランザクションをキャンセル (RBF)",
|
||||
"cpfp_create": "作成",
|
||||
"cpfp_exp": "We will create another transaction that spends your unconfirmed transaction. The total fee will be higher than the original transaction fee, so it should be mined faster. This is called CPFP - Child Pays For Parent.",
|
||||
"cpfp_exp": "あなたの未承認トランザクションを消費する別のトランザクションを作成します。元のトランザクションの手数料よりも合計金額が高くなるため、より早くマイニングされます。これはCPFP - Child Pays For Parentと呼ばれています。",
|
||||
"cpfp_no_bump": "このトランザクションはバンプ可能ではありません",
|
||||
"cpfp_title": "バンプ費用 (CPFP)",
|
||||
"details_block": "ブロック高",
|
||||
|
@ -292,44 +292,44 @@
|
|||
"details_title": "取引",
|
||||
"details_to": "送り先",
|
||||
"details_transaction_details": "取引詳細",
|
||||
"enable_hw": "This wallet is not being used in conjunction with a hardwarde wallet. Would you like to enable hardware wallet use?",
|
||||
"list_conf": "確認",
|
||||
"enable_hw": "このウォレットはハードウォレットとの併用はされていません。ハードウェアウォレットの使用を有効にしますか?",
|
||||
"list_conf": "コンファメーション: {number}",
|
||||
"list_title": "取引",
|
||||
"rbf_explain": "We will replace this transaction with the one with a higher fee, so it should be mined faster. This is called RBF - Replace By Fee.",
|
||||
"rbf_title": "Bump fee (RBF)",
|
||||
"status_bump": "Bump Fee",
|
||||
"status_cancel": "Cancel Transaction",
|
||||
"transactions_count": "transactions count"
|
||||
"rbf_explain": "このトランザクションを手数料の高いものに置き換えるので、マイニングが早くなるはずです。これをRBF - Replace By Feeといいます。",
|
||||
"rbf_title": "手数料をバンプ (RBF)",
|
||||
"status_bump": "手数料をバンプ",
|
||||
"status_cancel": "トランザクションをキャンセル",
|
||||
"transactions_count": "トランザクションカウント"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "ビットコイン",
|
||||
"add_create": "作成",
|
||||
"add_entropy_generated": "{gen} bytes of generated entropy",
|
||||
"add_entropy_generated": "生成されたエントロピーの {gen} バイト",
|
||||
"add_entropy_provide": "サイコロを振ってエントロピーを提供",
|
||||
"add_entropy_remain": "{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.",
|
||||
"add_entropy_remain": "生成されたエントロピーの{gen}バイト。残りの{rem}バイトはシステム乱数発生器から取得されます。",
|
||||
"add_import_wallet": "ウォレットをインポート",
|
||||
"import_file": "Import File",
|
||||
"add_lightning": "Lightning",
|
||||
"add_lndhub": "Connect to your LNDHub",
|
||||
"add_lndhub_error": "The provided node address is not valid LNDHub node.",
|
||||
"add_lndhub_placeholder": "your node address",
|
||||
"import_file": "インポートファイル",
|
||||
"add_lightning": "ライトニング",
|
||||
"add_lndhub": "あなたのLNDHubに接続",
|
||||
"add_lndhub_error": "指定されたノードアドレスは有効なLNDHubノードではありません。",
|
||||
"add_lndhub_placeholder": "あなたのノードアドレス",
|
||||
"add_or": "又は",
|
||||
"add_title": "ウォレットの追加",
|
||||
"add_wallet_name": "ウォレット名",
|
||||
"add_wallet_type": "タイプ",
|
||||
"details_address": "アドレス",
|
||||
"details_advanced": "Advanced",
|
||||
"details_advanced": "上級設定",
|
||||
"details_are_you_sure": "実行しますか?",
|
||||
"details_connected_to": "接続",
|
||||
"details_del_wb": "ウォレット残高",
|
||||
"details_del_wb_err": "The provided balance amount does not match this wallet's balance. Please, try again",
|
||||
"details_del_wb_q": "This wallet has a balance. Before proceeding, please be aware that you will not be able to recover the funds without this wallet's seed phrase. In order to avoid accidental removal this wallet, please enter your wallet's balance of {balance} satoshis.",
|
||||
"details_del_wb_err": "提供された残高は、このウォレットの残高と一致しません。もう一度お試しください。",
|
||||
"details_del_wb_q": "このウォレットには残高があります。先に進む前に、このウォレットのシードフレーズがないと資金を回収できない点に注意してください。誤ってこのウォレットを削除しないようにするために、このウォレットの残高 {balance} satoshisを入力してください。",
|
||||
"details_delete": "削除",
|
||||
"details_delete_wallet": "ウォレット削除",
|
||||
"details_display": "display in wallets list",
|
||||
"details_display": "ウォレットリストで表示",
|
||||
"details_export_backup": "エクスポート / バックアップ",
|
||||
"details_marketplace": "Marketplace",
|
||||
"details_master_fingerprint": "Master fingerprint",
|
||||
"details_marketplace": "マーケットプレイス",
|
||||
"details_master_fingerprint": "マスタフィンガープリント",
|
||||
"details_no_cancel": "いいえ、中止します",
|
||||
"details_save": "保存",
|
||||
"details_show_xpub": "ウォレット XPUB の表示",
|
||||
|
@ -354,8 +354,8 @@
|
|||
"list_empty_txs1_lightning": "Lightning ウォレットを日常の取引にご利用ください。手数料は安く、送金はあっという間に完了します。",
|
||||
"list_empty_txs2": "現在は何もありません",
|
||||
"list_empty_txs2_lightning": "\n利用を開始するには\"資金の管理\"をタップしてウォレットへ送金してください。",
|
||||
"list_header": "A wallet represents a pair of keys, one private and one you can share to receive coins.",
|
||||
"list_import_error": "An error was encountered when attempting to import this wallet.",
|
||||
"list_header": "ウォレットが表示する鍵のペアは、ひとつが秘密鍵、もうひとつはコインを受け取るために他人と共有することができる鍵です。",
|
||||
"list_import_error": "ウォレットのインポート時にエラーが起こりました。",
|
||||
"list_import_problem": "このウォレットのインポートに問題が生じました",
|
||||
"list_latest_transaction": "最新の取引",
|
||||
"list_long_choose": "写真選択",
|
||||
|
|
|
@ -170,13 +170,13 @@
|
|||
"details_next": "Volgende",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "notitie voor mezelf",
|
||||
"details_scan": "Scan",
|
||||
"details_total_exceeds_balance": "Het verzendingsbedrag overschrijdt het beschikbare saldo.",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_selection": "Wallet Selection",
|
||||
"dynamic_init": "Initialing",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Volgende",
|
||||
"dynamic_prev": "Vorige",
|
||||
"dynamic_start": "Start",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Próximo",
|
||||
"details_no_maximum": "A carteira selecionada não suporta cálculo automático de saldo máximo. Tem certeza que deseja selecionar esta carteira?",
|
||||
"details_no_multiple": "A carteira selecionada não suporta o envio de Bitcoins para vários destinatários. Tem certeza que deseja selecionar esta carteira?",
|
||||
"details_no_signed_tx": "O arquivo selecionado não contém uma transação assinada que possa ser importada.",
|
||||
"details_no_signed_tx": "O arquivo selecionado não contém uma transação que possa ser importada.",
|
||||
"details_note_placeholder": "Nota pessoal",
|
||||
"details_scan": "Ler",
|
||||
"details_total_exceeds_balance": "Valor total excede o saldo disponível",
|
||||
|
@ -181,6 +181,15 @@
|
|||
"dynamic_prev": "Anterior",
|
||||
"dynamic_start": "Começar",
|
||||
"dynamic_stop": "Parar",
|
||||
"fee_10m": "10m",
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Inserir",
|
||||
"fee_fast": "Veloz",
|
||||
"fee_medium": "Médio",
|
||||
"fee_replace_min": "A taxa total (satoshi por byte) que você deseja pagar deve ser superior a {min} sat/byte",
|
||||
"fee_satbyte": "em sat/byte",
|
||||
"fee_slow": "Lento",
|
||||
"header": "Enviar",
|
||||
"input_clear": "Limpar",
|
||||
"input_done": "Feito",
|
||||
|
@ -217,7 +226,7 @@
|
|||
"currency": "Moeda",
|
||||
"currency_source": "Os preços são obtidos no CoinDesk",
|
||||
"default_desc": "Quando desativado, o BlueWallet abrirá imediatamente a carteira selecionada no lançamento.",
|
||||
"default_info": "Padrão em",
|
||||
"default_info": "Carteira padrão",
|
||||
"default_title": "No lançamento",
|
||||
"default_wallets": "Ver todas as carteiras",
|
||||
"electrum_connected": "Conectado",
|
||||
|
@ -243,6 +252,7 @@
|
|||
"general_adv_mode_e": "Quando ativado, você verá opções avançadas, como diferentes tipos de carteira, a capacidade de especificar a instância do LNDHub à qual deseja se conectar e a entropia personalizada durante a criação da carteira.",
|
||||
"general_continuity": "Continuidade",
|
||||
"general_continuity_e": "Quando ativado, você poderá visualizar carteiras selecionadas e transações, usando seus outros dispositivos conectados ao Apple iCloud.",
|
||||
"groundcontrol_explanation": "GroundControl é um servidor de notificações push de código aberto gratuito para carteiras bitcoin. Você pode instalar seu próprio servidor GroundControl e colocar sua URL aqui para não depender da infraestrutura da BlueWallet. Deixe em branco para usar o padrão",
|
||||
"header": "definições",
|
||||
"language": "Idioma",
|
||||
"language_restart": "Ao selecionar um novo idioma, pode ser necessário reiniciar a BlueWallet para que a alteração tenha efeito.",
|
||||
|
@ -253,17 +263,16 @@
|
|||
"network": "Rede",
|
||||
"network_broadcast": "Divulgar transação",
|
||||
"network_electrum": "Servidor Electrum",
|
||||
"not_a_valid_uri": "Não é um URI válido",
|
||||
"notifications": "Notificações",
|
||||
"password": "Senha",
|
||||
"password_explain": "Definir a senha para descriptografar os arquivos",
|
||||
"passwords_do_not_match": "Senhas não conferem",
|
||||
"plausible_deniability": "Negação plausível...",
|
||||
"retype_password": "Inserir senha novamente",
|
||||
"notifications": "Notificações",
|
||||
"save": "Salvar",
|
||||
"saved": "Salvo",
|
||||
"not_a_valid_uri": "Não é um URI válido",
|
||||
"push_notifications": "Notificações via push",
|
||||
"groundcontrol_explanation": "GroundControl é um servidor de notificações push de código aberto gratuito para carteiras bitcoin. Você pode instalar seu próprio servidor GroundControl e colocar sua URL aqui para não depender da infraestrutura da BlueWallet. Deixe em branco para usar o padrão"
|
||||
"retype_password": "Inserir senha novamente",
|
||||
"save": "Salvar",
|
||||
"saved": "Salvo"
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_explain": "Substituiremos esta transação por aquela que lhe paga e tem taxas mais altas. Isso efetivamente cancela a transação. Isso é chamado de RBF - Substituir por Taxa.",
|
||||
|
@ -284,13 +293,13 @@
|
|||
"details_to": "Para",
|
||||
"details_transaction_details": "Detalhes",
|
||||
"enable_hw": "Esta carteira não está sendo usada em conjunto com uma carteira de hardware. Gostaria de habilitar o uso de carteira de hardware?",
|
||||
"list_conf": "conf",
|
||||
"list_conf": "conf: {number}",
|
||||
"list_title": "Transações",
|
||||
"transactions_count": "contagem de transações",
|
||||
"rbf_explain": "Substituiremos essa transação por outra com uma taxa mais alta, portanto, ela deve ser confirmada mais rapidamente. Isso é chamado de RBF - Substituir por Taxa.",
|
||||
"rbf_title": "Aumento de taxa (RBF)",
|
||||
"status_bump": "Aumento de taxa",
|
||||
"status_cancel": "Cancelar Transação"
|
||||
"status_cancel": "Cancelar Transação",
|
||||
"transactions_count": "contagem de transações"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "Bitcoin",
|
||||
|
@ -334,7 +343,7 @@
|
|||
"import_error": "Erro. Por favor, confira se o formato que você passou é válido.",
|
||||
"import_explanation": "Escreva aqui sua frase mnemônica, chave privada, WIF, ou o que você tiver. Faremos nosso melhor para adivinhar o formato e importat sua carteira",
|
||||
"import_imported": "Importada",
|
||||
"import_scan_qr": "ou ler um código QR?",
|
||||
"import_scan_qr": "Ler um código QR ou arquivo",
|
||||
"import_success": "Sucesso",
|
||||
"import_title": "importar",
|
||||
"list_create_a_button": "Criar agora",
|
||||
|
@ -353,7 +362,7 @@
|
|||
"list_long_clipboard": "Copiar da área de transferência",
|
||||
"list_long_scan": "Ler QR Code",
|
||||
"take_photo": "Registrar Foto",
|
||||
"list_tap_here_to_buy": "Toque aqui para comprar Bitcoin",
|
||||
"list_tap_here_to_buy": "Comprar Bitcoin",
|
||||
"list_title": "carteiras",
|
||||
"list_tryagain": "Tente Novamente",
|
||||
"reorder_title": "Reordenar carteiras",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Próximo",
|
||||
"details_no_maximum": "A carteira selecionada não suporta cálculo automático de saldo máximo. Tem a certeza que deseja selecionar esta carteira?",
|
||||
"details_no_multiple": "A carteira selecionada não suporta o envio de Bitcoins para vários destinatários. Tem a certeza que deseja selecionar esta carteira?",
|
||||
"details_no_signed_tx": "O ficheiro seleccionado não contém uma transacção assinada que possa ser importada.",
|
||||
"details_no_signed_tx": "O ficheiro seleccionado não contém uma transacção que possa ser importada.",
|
||||
"details_note_placeholder": "Nota pessoal",
|
||||
"details_scan": "Scan",
|
||||
"details_total_exceeds_balance": "O valor total excede o saldo disponível.",
|
||||
|
@ -181,6 +181,15 @@
|
|||
"dynamic_prev": "Anterior",
|
||||
"dynamic_start": "Começar",
|
||||
"dynamic_stop": "Parar",
|
||||
"fee_10m": "10m",
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Escolher",
|
||||
"fee_fast": "Rápido",
|
||||
"fee_medium": "Médio",
|
||||
"fee_replace_min": "A taxa total (satoshi/byte) que deseja pagar deve ser superior a {min} sat/byte",
|
||||
"fee_satbyte": "em sat/byte",
|
||||
"fee_slow": "Lento",
|
||||
"header": "Enviar",
|
||||
"input_clear": "Limpar",
|
||||
"input_done": "Feito",
|
||||
|
@ -217,7 +226,7 @@
|
|||
"currency": "Moeda",
|
||||
"currency_source": "Os preços são obtidos no CoinDesk",
|
||||
"default_desc": "Quando desactivado, a BlueWallet abrirá imediatamente a carteira seleccionada no lançamento.",
|
||||
"default_info": "Padrão em",
|
||||
"default_info": "Carteira padrão",
|
||||
"default_title": "A abrir",
|
||||
"default_wallets": "Ver todas as carteiras",
|
||||
"electrum_connected": "Conectado",
|
||||
|
@ -243,6 +252,7 @@
|
|||
"general_adv_mode_e": "Quando activado, verá opções avançadas, como diferentes tipos de carteira, a capacidade de especificar a instância do LNDHub à qual se deseja conectar e a entropia personalizada durante a criação da carteira.",
|
||||
"general_continuity": "Continuidade",
|
||||
"general_continuity_e": "Quando activado, poderá visualizar carteiras seleccionadas e transacções, usando os seus outros dispositivos conectados ao Apple iCloud.",
|
||||
"groundcontrol_explanation": "GroundControl é um servidor de notificações push de código aberto gratuito para carteiras bitcoin. Pode instalar seu próprio servidor GroundControl e colocar sua URL aqui para não depender da infraestrutura da BlueWallet. Deixe em branco para usar o padrão",
|
||||
"header": "definições",
|
||||
"language": "Idioma",
|
||||
"language_restart": "Ao selecionar um novo idioma, pode ser necessário reiniciar a BlueWallet para que a alteração tenha efeito.",
|
||||
|
@ -253,17 +263,16 @@
|
|||
"network": "Rede",
|
||||
"network_broadcast": "Transmitir transacção",
|
||||
"network_electrum": "Electrum server",
|
||||
"not_a_valid_uri": "Não é um URI válido",
|
||||
"notifications": "Notificações",
|
||||
"password": "Password",
|
||||
"password_explain": "Definir a password para desencriptar o armazenamento",
|
||||
"passwords_do_not_match": "Passwords não coincidem",
|
||||
"plausible_deniability": "Negação plausível...",
|
||||
"retype_password": "Inserir password novamente",
|
||||
"notifications": "Notificações",
|
||||
"save": "Guardar",
|
||||
"saved": "Guardado",
|
||||
"not_a_valid_uri": "Não é um URI válido",
|
||||
"push_notifications": "Notificações via push",
|
||||
"groundcontrol_explanation": "GroundControl é um servidor de notificações push de código aberto gratuito para carteiras bitcoin. Pode instalar seu próprio servidor GroundControl e colocar sua URL aqui para não depender da infraestrutura da BlueWallet. Deixe em branco para usar o padrão"
|
||||
"retype_password": "Inserir password novamente",
|
||||
"save": "Guardar",
|
||||
"saved": "Guardado"
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_explain": "Substituiremos esta transacção por aquela que lhe paga e tem taxas mais altas. Isso efectivamente cancela a transacção. Isto é chamado de RBF - Substituir por Taxa.",
|
||||
|
@ -286,11 +295,11 @@
|
|||
"enable_hw": "Esta carteira não está a ser usada em conjunto com uma carteira de hardware. Gostaria de habilitar o uso de carteira de hardware?",
|
||||
"list_conf": "conf",
|
||||
"list_title": "transacções",
|
||||
"transactions_count": "contagem de transações",
|
||||
"rbf_explain": "Substituiremos esta transacção por outra com uma taxa mais alta, portanto, ela deve ser confirmada mais rapidamente. Isto é chamado de RBF - Substituir por Taxa.",
|
||||
"rbf_title": "Aumento de taxa (RBF)",
|
||||
"status_bump": "Aumento de taxa",
|
||||
"status_cancel": "Cancelar transacção"
|
||||
"status_cancel": "Cancelar transacção",
|
||||
"transactions_count": "contagem de transações"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "Bitcoin",
|
||||
|
|
35
loc/ru.json
35
loc/ru.json
|
@ -170,9 +170,9 @@
|
|||
"details_next": "Дальше",
|
||||
"details_no_maximum": "Этот кошелёк не поддерживает отправку всех средств. Ты уверен что хочешь выбрать его?",
|
||||
"details_no_multiple": "Этот кошелёк не поддерживает отправку нескольким получателям. Ты уверен что хочешь выбрать его?",
|
||||
"details_no_signed_tx": "В файле нет подписаных транзакций которые можно импортировать",
|
||||
"details_no_signed_tx": "В файле нет транзакций которые можно импортировать.",
|
||||
"details_note_placeholder": "примечание платежа",
|
||||
"details_scan": "Отсканировать QR",
|
||||
"details_scan": "Скан",
|
||||
"details_total_exceeds_balance": "Общая сумма превышает баланс.",
|
||||
"details_wallet_before_tx": "Перед созданием транзакции нужно сделать Bitcoin кошелёк.",
|
||||
"details_wallet_selection": "Выбор Кошелька",
|
||||
|
@ -181,6 +181,15 @@
|
|||
"dynamic_prev": "Предыдущий",
|
||||
"dynamic_start": "Старт",
|
||||
"dynamic_stop": "Стоп",
|
||||
"fee_10m": "10м",
|
||||
"fee_1d": "1д",
|
||||
"fee_3h": "3ч",
|
||||
"fee_custom": "Другое",
|
||||
"fee_fast": "Быстро",
|
||||
"fee_medium": "Средне",
|
||||
"fee_replace_min": "Комиссия (сатоши за байт) которую нужно заплатить должна быть выше {min} сатоши/байт",
|
||||
"fee_satbyte": "сатоши/байт",
|
||||
"fee_slow": "Медленно",
|
||||
"header": "Отправить",
|
||||
"input_clear": "Очистить",
|
||||
"input_done": "Готово",
|
||||
|
@ -188,6 +197,7 @@
|
|||
"input_total": "Всего:",
|
||||
"permission_camera_message": "Нужно ваше разрешение на использование камеры",
|
||||
"permission_camera_title": "Разрешите пользоваться камерой",
|
||||
"open_settings": "Открыть настройки",
|
||||
"permission_storage_later": "Спроси меня позже",
|
||||
"permission_storage_message": "BlueWallet нужно ваше разрешение для доступа к хранилищу файлов, чтобы сохранить эту транзакцию.",
|
||||
"permission_storage_title": "Разрешите сохранять файлы",
|
||||
|
@ -242,6 +252,7 @@
|
|||
"general_adv_mode_e": "При включении вы увидите расширенные параметры, такие как разные типы кошельков, возможность указать экземпляр LNDHub, к которому вы хотите подключиться, и пользовательскую энтропию при создании кошелька.",
|
||||
"general_continuity": "Непрерывность",
|
||||
"general_continuity_e": "Когда эта функция включена, вы сможете просматривать выбранные кошельки и транзакции, используя другие устройства, подключенные к Apple iCloud.",
|
||||
"groundcontrol_explanation": "GroundControl - это бесплатный сервер push-уведомлений с открытым исходным кодом для биткойн-кошельков. Вы можете установить свой собственный сервер GroundControl и указать здесь его URL, чтобы не полагаться на инфраструктуру BlueWallet. Оставьте пустым, чтобы использовать по умолчанию",
|
||||
"header": "Настройки",
|
||||
"language": "Язык",
|
||||
"language_restart": "При выборе нового языка может потребоваться перезапуск BlueWallet, чтобы изменения вступили в силу.",
|
||||
|
@ -252,17 +263,16 @@
|
|||
"network": "Сеть",
|
||||
"network_broadcast": "Отправить транзакцию",
|
||||
"network_electrum": "Electrum сервер",
|
||||
"not_a_valid_uri": "Неверный URI",
|
||||
"notifications": "Уведомления",
|
||||
"password": "Пароль",
|
||||
"password_explain": "Придумай пароль для расшифровки хранилища",
|
||||
"passwords_do_not_match": "Пароли не совпадают",
|
||||
"plausible_deniability": "Правдоподобная имитация...",
|
||||
"retype_password": "Набери пароль повторно",
|
||||
"notifications": "Уведомления",
|
||||
"save": "Сохранить",
|
||||
"saved": "Сохранено",
|
||||
"not_a_valid_uri": "Неверный URI",
|
||||
"push_notifications": "Push-уведомления",
|
||||
"groundcontrol_explanation": "GroundControl - это бесплатный сервер push-уведомлений с открытым исходным кодом для биткойн-кошельков. Вы можете установить свой собственный сервер GroundControl и указать здесь его URL, чтобы не полагаться на инфраструктуру BlueWallet. Оставьте пустым, чтобы использовать по умолчанию"
|
||||
"retype_password": "Набери пароль повторно",
|
||||
"save": "Сохранить",
|
||||
"saved": "Сохранено"
|
||||
},
|
||||
"transactions": {
|
||||
"cancel_explain": "Мы заменим эту транзакцию другой, которая платит вам и имеет более высокую комиссию. Это отменит предыдущую транзакцию. Это называется RBF - Replace By Fee.",
|
||||
|
@ -283,13 +293,13 @@
|
|||
"details_to": "Кому",
|
||||
"details_transaction_details": "Детали транзакции",
|
||||
"enable_hw": "Кошелек не используется вместе с аппаратным. Вы хотите включить поддержку аппаратного кошелека?",
|
||||
"list_conf": "подтв.",
|
||||
"list_conf": "{number} подтв.",
|
||||
"list_title": "Мои транзакции",
|
||||
"transactions_count": "кол-во транзакций",
|
||||
"rbf_explain": "Мы заменим эту транзакцию другой с более высокой комиссией, поэтому будет обработана быстрее. Это называется RBF - Replace By Fee.",
|
||||
"rbf_title": "Повысить комиссию (RBF)",
|
||||
"status_bump": "Повысить комиссию",
|
||||
"status_cancel": "Отменить транзакцию"
|
||||
"status_cancel": "Отменить транзакцию",
|
||||
"transactions_count": "кол-во транзакций"
|
||||
},
|
||||
"wallets": {
|
||||
"add_bitcoin": "Bitcoin",
|
||||
|
@ -298,6 +308,7 @@
|
|||
"add_entropy_provide": "Сгенерировать энторию с помощью игральных костей",
|
||||
"add_entropy_remain": "{gen} байтов сгенерированной энтории. Оставшиеся {rem} байт будут получены из системного генератора случайных чисел.",
|
||||
"add_import_wallet": "Импортировать кошелек",
|
||||
"import_file": "Импортировать файл",
|
||||
"add_lightning": "Lightning",
|
||||
"add_lndhub": "Подключиться к своему LNDHub",
|
||||
"add_lndhub_error": "Не верный адрес LNDHub.",
|
||||
|
@ -343,7 +354,7 @@
|
|||
"list_empty_txs1_lightning": "Lightning кошелек отлично подходит для ежедневных транзакций. Комиссия несправедливо мала, а скорость невероятно высока.",
|
||||
"list_empty_txs2": "Пока транзакций нет ",
|
||||
"list_empty_txs2_lightning": "\nДля начала использования нажми \"Мои средства\" и пополни баланс.",
|
||||
"list_header": "Кошелек - это секретный (приватный) ключ и соответствующий ему адрес на который можно получать Bitcoin",
|
||||
"list_header": "Кошелек - пара ключей, один приватный, другой - публичный используется, чтобы получить Bitcoin.",
|
||||
"list_import_error": "При импорте этого кошелька возникла ошибка.",
|
||||
"list_import_problem": "Ошибка при импорте кошелька",
|
||||
"list_latest_transaction": "Последняя транзакция",
|
||||
|
|
|
@ -170,13 +170,13 @@
|
|||
"details_next": "Ďalej",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "poznámka pre seba",
|
||||
"details_scan": "Skenovať",
|
||||
"details_total_exceeds_balance": "Čiastka, ktorú chcete poslať, presahuje dostupný zostatok.",
|
||||
"details_wallet_before_tx": "Pred vytvorením transakcie potrebujete najprv pridať Bitcoinovú peňaženku.",
|
||||
"details_wallet_selection": "Výber peňaženky",
|
||||
"dynamic_init": "Initialing",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Next",
|
||||
"dynamic_prev": "Previous",
|
||||
"dynamic_start": "Start",
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
"of": "{number} od {total}",
|
||||
"ok": "OK",
|
||||
"storage_is_encrypted": "Shramba je šifrirana. Za dešifriranje je potrebno geslo",
|
||||
"yes": "Da"
|
||||
"yes": "Da",
|
||||
"invalid_animated_qr_code_fragment" : "Neveljaven del animirane QR kode, prosimo poskusite ponovno",
|
||||
"file_saved": "Datoteka ({filePath}) je bila shranjena v mapo Prenosi."
|
||||
},
|
||||
"azteco": {
|
||||
"codeIs": "Koda vašega bona je",
|
||||
|
@ -170,7 +172,7 @@
|
|||
"details_next": "Naprej",
|
||||
"details_no_maximum": "Izbrana denarnica ne podpira samodejnega izračuna največjega stanja. Ali ste prepričani, da želite izbrati to denarnico?",
|
||||
"details_no_multiple": "Izbrana denarnica ne podpira pošiljanja več prejemnikom. Ali ste prepričani, da želite izbrati to denarnico?",
|
||||
"details_no_signed_tx": "Izbrana datoteka ne vsebuje podpisane transakcije, ki jo je mogoče uvoziti.",
|
||||
"details_no_signed_tx": "Izbrana datoteka ne vsebuje transakcije, ki jo je mogoče uvoziti.",
|
||||
"details_note_placeholder": "lastna opomba",
|
||||
"details_scan": "Skeniraj",
|
||||
"details_total_exceeds_balance": "Znesek presega razpoložljivo stanje.",
|
||||
|
@ -209,7 +211,8 @@
|
|||
"qr_error_no_qrcode": "Izbrana slika ne vsebuje QR kode.",
|
||||
"qr_error_no_wallet": "Izbrana datoteka ne vsebuje denarnice, ki jo je mogoče uvoziti.",
|
||||
"success_done": "Končano",
|
||||
"txSaved": "Transakcijska datoteka ({filePath}) je bila shranjena v mapo Prenosi."
|
||||
"txSaved": "Transakcijska datoteka ({filePath}) je bila shranjena v mapo Prenosi.",
|
||||
"problem_with_psbt": "Težava s PSBT"
|
||||
},
|
||||
"settings": {
|
||||
"about": "O aplikaciji",
|
||||
|
@ -293,7 +296,7 @@
|
|||
"details_to": "Izhod",
|
||||
"details_transaction_details": "Podrobnosti transakcije",
|
||||
"enable_hw": "Ta denarnica se ne uporablja skupaj s strojno denarnico. Ali želite omogočiti uporabo strojne denarnice?",
|
||||
"list_conf": "potrd",
|
||||
"list_conf": "potrd: {number}",
|
||||
"list_title": "transakcije",
|
||||
"rbf_explain": "To transakcijo bomo nadomestili z novo, ki plača višjo omrežnino, zato bi morala biti potrditev hitrejša. To se imenuje RBF - Replace By Fee.",
|
||||
"rbf_title": "Povečaj omrežnino (RBF)",
|
||||
|
@ -371,5 +374,17 @@
|
|||
"select_wallet": "Izberite Denarnico",
|
||||
"xpub_copiedToClipboard": "Kopirano v odložišče.",
|
||||
"xpub_title": "XPUB denarnice"
|
||||
},
|
||||
"multisig": {
|
||||
"provide_signature": "Vnesite podpis",
|
||||
"vault_key": "Ključ trezorja {number}",
|
||||
"fee": "Omrežnina: {number}",
|
||||
"fee_btc": "{number} BTC",
|
||||
"confirm": "Potrditev",
|
||||
"header": "Pošlji",
|
||||
"share": "Deli",
|
||||
"how_many_signatures_can_bluewallet_make": "koliko podpisov lahko naredi bluewallet",
|
||||
"scan_or_import_file": "Skenirajte ali uvozite datoteko",
|
||||
"export_coordination_setup": "izvoz koordinacijskih nastavitev"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,13 +170,13 @@
|
|||
"details_next": "Nästa",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "egen notering",
|
||||
"details_scan": "Skanna",
|
||||
"details_total_exceeds_balance": "Beloppet överstiger plånbokens tillgängliga belopp",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_selection": "Wallet Selection",
|
||||
"dynamic_init": "Initialing",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Nästa",
|
||||
"dynamic_prev": "Föregående",
|
||||
"dynamic_start": "Starta",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "ถัดไป",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "หมายเหตุถึงตัวท่านเอง",
|
||||
"details_scan": "สแกน",
|
||||
"details_total_exceeds_balance": "จำนวนเงินที่จะส่งเกินเงินที่มี.",
|
||||
|
|
|
@ -170,13 +170,13 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "kendime not",
|
||||
"details_scan": "Tara",
|
||||
"details_total_exceeds_balance": "Gönderme miktarı mevcut bakiyeyi aşıyor.",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_selection": "Wallet Selection",
|
||||
"dynamic_init": "Initialing",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Next",
|
||||
"dynamic_prev": "Previous",
|
||||
"dynamic_start": "Start",
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
"azteco": {
|
||||
"codeIs": "Your voucher code is",
|
||||
"errorBeforeRefeem": "Before redeeming you must first add a Bitcoin wallet.",
|
||||
"errorBeforeRefeem": "你需先添加一个Bitcoin钱包",
|
||||
"errorSomething": "Something went wrong. Is this voucher still valid?",
|
||||
"redeem": "Redeem to wallet",
|
||||
"redeemButton": "Redeem",
|
||||
|
@ -30,10 +30,10 @@
|
|||
"network": "网络错误"
|
||||
},
|
||||
"hodl": {
|
||||
"are_you_sure_you_want_to_logout": "Are you sure you want to logout from HodlHodl?",
|
||||
"are_you_sure_you_want_to_logout": "您确定要注销HodlHodl吗?",
|
||||
"cont_address_escrow": "Escrow",
|
||||
"cont_address_to": "To",
|
||||
"cont_buying": "buying",
|
||||
"cont_buying": "购买",
|
||||
"cont_cancel": "Cancel contract",
|
||||
"cont_cancel_q": "Are you sure you want to cancel this contract?",
|
||||
"cont_cancel_y": "Yes, cancel contract",
|
||||
|
@ -170,13 +170,13 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "消息",
|
||||
"details_scan": "扫描",
|
||||
"details_total_exceeds_balance": "余额不足",
|
||||
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
|
||||
"details_wallet_selection": "Wallet Selection",
|
||||
"dynamic_init": "Initialing",
|
||||
"dynamic_init": "Initializing",
|
||||
"dynamic_next": "Next",
|
||||
"dynamic_prev": "Previous",
|
||||
"dynamic_start": "Start",
|
||||
|
|
|
@ -27,7 +27,7 @@ export const FiatUnit = Object.freeze({
|
|||
SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' },
|
||||
SEK: { endPointKey: 'SEK', symbol: 'kr', locale: 'sv-SE' },
|
||||
THB: { endPointKey: 'THB', symbol: '฿', locale: 'th-TH' },
|
||||
TWD: { endPointKey: 'TWD', symbol: '$', locale: 'zh-Hant-TW' },
|
||||
TWD: { endPointKey: 'TWD', symbol: 'NT$', locale: 'zh-Hant-TW' },
|
||||
UAH: { endPointKey: 'UAH', symbol: '₴', locale: 'uk-UA' },
|
||||
VEF: { endPointKey: 'VEF', symbol: 'Bs.', locale: 'es-VE' },
|
||||
ZAR: { endPointKey: 'ZAR', symbol: 'R', locale: 'en-ZA' },
|
||||
|
|
|
@ -20,7 +20,7 @@ export class NetworkTransactionFee {
|
|||
export default class NetworkTransactionFees {
|
||||
static recommendedFees() {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return new Promise(async resolve => {
|
||||
try {
|
||||
const response = await BlueElectrum.estimateFees();
|
||||
if (typeof response === 'object') {
|
||||
|
@ -28,12 +28,12 @@ export default class NetworkTransactionFees {
|
|||
resolve(networkFee);
|
||||
} else {
|
||||
const networkFee = new NetworkTransactionFee(1, 1, 1);
|
||||
reject(networkFee);
|
||||
resolve(networkFee);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err);
|
||||
const networkFee = new NetworkTransactionFee(1, 1, 1);
|
||||
reject(networkFee);
|
||||
resolve(networkFee);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
12081
package-lock.json
generated
12081
package-lock.json
generated
File diff suppressed because it is too large
Load diff
69
package.json
69
package.json
|
@ -1,25 +1,26 @@
|
|||
{
|
||||
"name": "bluewallet",
|
||||
"version": "5.6.0",
|
||||
"version": "5.6.2",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/runtime": "^7.9.6",
|
||||
"@react-native-community/eslint-config": "^1.1.0",
|
||||
"@babel/core": "^7.10.4",
|
||||
"@babel/runtime": "^7.10.4",
|
||||
"@jest/reporters": "^26.4.1",
|
||||
"@react-native-community/eslint-config": "^2.0.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.0.1",
|
||||
"babel-jest": "^26.1.0",
|
||||
"babel-preset-flow": "^6.23.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.20.0",
|
||||
"flow-bin": "^0.125.1",
|
||||
"jest": "^24.9.0",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"flow-bin": "^0.134.0",
|
||||
"jest": "^26.1.0",
|
||||
"jetifier": "^1.6.3",
|
||||
"react-test-renderer": "16.11.0"
|
||||
"react-test-renderer": "16.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0",
|
||||
|
@ -42,7 +43,7 @@
|
|||
"e2e:debug": "(test -f android/app/build/outputs/apk/debug/app-debug.apk && test -f android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk) || npm run e2e:debug-build; npm run e2e:debug-test",
|
||||
"e2e:release-build": "npx detox build -c android.emu.release",
|
||||
"e2e:release-test": "detox test -c android.emu.release --record-videos all --take-screenshots all --headless",
|
||||
"lint": "eslint *.js screen/**/*.js blue_modules/*.js class/**/*.js models/ loc/ tests/**/*.js",
|
||||
"lint": "eslint *.js screen/**/*.js blue_modules/*.js class/**/*.js models/ loc/ tests/**/*.js components/**/*.js",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep '\\.js' --color=never | awk '{print $2}' | xargs eslint --fix; exit 0",
|
||||
"unit": "jest tests/unit/*"
|
||||
|
@ -55,24 +56,27 @@
|
|||
"setupFiles": [
|
||||
"./tests/setup.js"
|
||||
],
|
||||
"watchPathIgnorePatterns": [
|
||||
"<rootDir>/node_modules"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
"./tests/setupAfterEnv.js"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-env": "7.11.0",
|
||||
"@react-native-community/async-storage": "1.12.0",
|
||||
"@babel/preset-env": "7.11.5",
|
||||
"@react-native-community/async-storage": "1.12.1",
|
||||
"@react-native-community/blur": "3.6.0",
|
||||
"@react-native-community/clipboard": "1.2.3",
|
||||
"@react-native-community/clipboard": "1.4.0",
|
||||
"@react-native-community/geolocation": "2.0.2",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-native-community/push-notification-ios": "1.5.0",
|
||||
"@react-native-community/slider": "3.0.3",
|
||||
"@react-navigation/drawer": "5.9.0",
|
||||
"@react-navigation/native": "5.7.3",
|
||||
"@react-navigation/stack": "5.9.0",
|
||||
"@react-navigation/drawer": "5.9.2",
|
||||
"@react-navigation/native": "5.7.5",
|
||||
"@react-navigation/stack": "5.9.2",
|
||||
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git",
|
||||
"@sentry/react-native": "1.8.0",
|
||||
"@sentry/react-native": "1.8.2",
|
||||
"amplitude-js": "5.11.0",
|
||||
"assert": "1.5.0",
|
||||
"bc-bech32": "file:blue_modules/bc-bech32",
|
||||
|
@ -82,16 +86,16 @@
|
|||
"bip21": "2.0.3",
|
||||
"bip32": "2.0.5",
|
||||
"bip39": "2.6.0",
|
||||
"bitcoinjs-lib": "5.1.10",
|
||||
"bitcoinjs-lib": "5.2.0",
|
||||
"bolt11": "1.2.7",
|
||||
"buffer": "5.6.0",
|
||||
"buffer-reverse": "1.0.1",
|
||||
"coinselect": "3.1.12",
|
||||
"crypto-js": "3.1.9-1",
|
||||
"dayjs": "1.8.33",
|
||||
"dayjs": "1.8.35",
|
||||
"detox": "17.5.6",
|
||||
"ecurve": "1.0.6",
|
||||
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git#99c75385f99e82b87fbfed2abfb2cccd322fe3e9",
|
||||
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git#f9a827e724a5a2e578fdfdb483f83793af55b030",
|
||||
"electrum-mnemonic": "2.0.0",
|
||||
"eslint-config-prettier": "6.11.0",
|
||||
"eslint-config-standard": "14.1.1",
|
||||
|
@ -104,29 +108,30 @@
|
|||
"lottie-react-native": "3.5.0",
|
||||
"metro-react-native-babel-preset": "0.63.0",
|
||||
"path-browserify": "1.0.1",
|
||||
"payjoin-client": "git+https://github.com/bitcoinjs/payjoin-client.git#31d2118a4c0d00192d975f3a6da2a96238f8f7a5",
|
||||
"pbkdf2": "3.1.1",
|
||||
"prettier": "2.1.1",
|
||||
"process": "0.11.10",
|
||||
"prop-types": "15.7.2",
|
||||
"react": "16.11.0",
|
||||
"react": "16.13.1",
|
||||
"react-localization": "1.0.15",
|
||||
"react-native": "0.62.2",
|
||||
"react-native": "0.63.3",
|
||||
"react-native-blue-crypto": "git+https://github.com/Overtorment/react-native-blue-crypto.git",
|
||||
"react-native-camera": "3.39.1",
|
||||
"react-native-default-preference": "1.4.3",
|
||||
"react-native-device-info": "6.0.3",
|
||||
"react-native-device-info": "6.2.0",
|
||||
"react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#3684d4fcc2bc0b47c32be39024e4796004c3e428",
|
||||
"react-native-elements": "2.2.1",
|
||||
"react-native-elements": "2.3.1",
|
||||
"react-native-fingerprint-scanner": "git+https://github.com/BlueWallet/react-native-fingerprint-scanner.git#ce644673681716335d786727bab998f7e632ab5e",
|
||||
"react-native-fs": "2.16.6",
|
||||
"react-native-gesture-handler": "1.7.0",
|
||||
"react-native-gesture-handler": "1.8.0",
|
||||
"react-native-handoff": "git+https://github.com/marcosrdz/react-native-handoff.git",
|
||||
"react-native-haptic-feedback": "1.10.0",
|
||||
"react-native-image-picker": "2.3.3",
|
||||
"react-native-image-picker": "2.3.4",
|
||||
"react-native-inappbrowser-reborn": "git+https://github.com/BlueWallet/react-native-inappbrowser.git#v3.4.0",
|
||||
"react-native-level-fs": "3.0.1",
|
||||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-localize": " 1.4.0",
|
||||
"react-native-localize": "1.4.2",
|
||||
"react-native-modal": "11.5.6",
|
||||
"react-native-obscure": "1.2.1",
|
||||
"react-native-passcode-auth": "git+https://github.com/BlueWallet/react-native-passcode-auth.git#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12",
|
||||
|
@ -142,7 +147,7 @@
|
|||
"react-native-safe-area-context": "3.1.8",
|
||||
"react-native-screens": "2.11.0",
|
||||
"react-native-secure-key-store": "git+https://github.com/BlueWallet/react-native-secure-key-store.git#4ba25dedb3d5ae15c22fd0ea0555116055630966",
|
||||
"react-native-share": "3.7.0",
|
||||
"react-native-share": "4.0.2",
|
||||
"react-native-snap-carousel": "3.9.1",
|
||||
"react-native-sortable-list": "0.0.24",
|
||||
"react-native-svg": "12.1.0",
|
||||
|
@ -150,7 +155,7 @@
|
|||
"react-native-tooltip": "git+https://github.com/BlueWallet/react-native-tooltip.git#d369e7ece09e4dec73873f1cfeac83e9d35294a6",
|
||||
"react-native-vector-icons": "6.6.0",
|
||||
"react-native-watch-connectivity": "1.0.2",
|
||||
"react-native-webview": "10.8.3",
|
||||
"react-native-webview": "10.9.2",
|
||||
"react-test-render": "1.1.2",
|
||||
"readable-stream": "3.6.0",
|
||||
"realm": "6.1.0",
|
||||
|
|
|
@ -125,9 +125,8 @@ const ReceiveDetails = () => {
|
|||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
share: {
|
||||
alignItems: 'center',
|
||||
alignContent: 'flex-end',
|
||||
marginBottom: 24,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
modalButton: {
|
||||
backgroundColor: BlueCurrentTheme.colors.modalButton,
|
||||
|
@ -193,7 +192,7 @@ const ReceiveDetails = () => {
|
|||
if (!address) {
|
||||
// either sleep expired or getAddressAsync threw an exception
|
||||
console.warn('either sleep expired or getAddressAsync threw an exception');
|
||||
address = wallet._getExternalAddressByIndex(wallet.next_free_address_index);
|
||||
address = wallet._getExternalAddressByIndex(wallet.getNextFreeAddressIndex());
|
||||
} else {
|
||||
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/* global alert */
|
||||
import React, { useState } from 'react';
|
||||
import { Image, View, TouchableOpacity, StatusBar, Platform, StyleSheet, Linking, Alert } from 'react-native';
|
||||
import { Image, View, TouchableOpacity, StatusBar, Platform, StyleSheet, Linking, Alert, TextInput } from 'react-native';
|
||||
import { RNCamera } from 'react-native-camera';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import { useNavigation, useRoute, useIsFocused, useTheme } from '@react-navigation/native';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import RNFS from 'react-native-fs';
|
||||
import loc from '../../loc';
|
||||
import { BlueLoadingHook, BlueTextHooks, BlueButtonHook, BlueSpacing40 } from '../../BlueComponents';
|
||||
import { getSystemName } from 'react-native-device-info';
|
||||
const prompt = require('../../blue_modules/prompt');
|
||||
import { BlueCurrentTheme } from '../../components/themes';
|
||||
import { decodeUR, extractSingleWorkload } from 'bc-ur';
|
||||
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
|
||||
const createHash = require('create-hash');
|
||||
const isDesktop = getSystemName() === 'Mac OS X';
|
||||
const fs = require('../../blue_modules/fs');
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
|
@ -65,12 +65,21 @@ const styles = StyleSheet.create({
|
|||
backdoorButton: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: 'rgba(0,0,0,0)',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 0,
|
||||
backgroundColor: 'rgba(0,0,0,0.1)',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
backdoorInputWrapper: { position: 'absolute', left: '5%', top: '0%', width: '90%', height: '70%', backgroundColor: 'white' },
|
||||
backdoorInput: {
|
||||
height: '50%',
|
||||
marginTop: 5,
|
||||
marginHorizontal: 20,
|
||||
borderColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderBottomColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
|
||||
color: BlueCurrentTheme.colors.foregroundColor,
|
||||
textAlignVertical: 'top',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -85,6 +94,9 @@ const ScanQRCode = () => {
|
|||
const isFocused = useIsFocused();
|
||||
const [cameraStatus, setCameraStatus] = useState(RNCamera.Constants.CameraStatus.PENDING_AUTHORIZATION);
|
||||
const [backdoorPressed, setBackdoorPressed] = useState(0);
|
||||
const [backdoorText, setBackdoorText] = useState('');
|
||||
const [backdoorVisible, setBackdoorVisible] = useState(false);
|
||||
const [animatedQRCodeData, setAnimatedQRCodeData] = useState({});
|
||||
const stylesHook = StyleSheet.create({
|
||||
openSettingsContainer: {
|
||||
backgroundColor: colors.brandingColor,
|
||||
|
@ -94,6 +106,34 @@ const ScanQRCode = () => {
|
|||
return createHash('sha256').update(s).digest().toString('hex');
|
||||
};
|
||||
|
||||
const _onReadUniformResource = ur => {
|
||||
try {
|
||||
const [index, total] = extractSingleWorkload(ur);
|
||||
animatedQRCodeData[index + 'of' + total] = ur;
|
||||
if (Object.values(animatedQRCodeData).length === total) {
|
||||
const payload = decodeUR(Object.values(animatedQRCodeData));
|
||||
// lets look inside that data
|
||||
let data = false;
|
||||
if (Buffer.from(payload, 'hex').toString().startsWith('psbt')) {
|
||||
// its a psbt, and whoever requested it expects it encoded in base64
|
||||
data = Buffer.from(payload, 'hex').toString('base64');
|
||||
} else {
|
||||
// its something else. probably plain text is expected
|
||||
data = Buffer.from(payload, 'hex').toString();
|
||||
}
|
||||
if (launchedBy) {
|
||||
navigation.navigate(launchedBy);
|
||||
}
|
||||
onBarScanned({ data });
|
||||
} else {
|
||||
setAnimatedQRCodeData(animatedQRCodeData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
alert(loc._.invalid_animated_qr_code_fragment);
|
||||
}
|
||||
};
|
||||
|
||||
const onBarCodeRead = ret => {
|
||||
const h = HashIt(ret.data);
|
||||
if (scannedCache[h]) {
|
||||
|
@ -102,6 +142,10 @@ const ScanQRCode = () => {
|
|||
}
|
||||
scannedCache[h] = +new Date();
|
||||
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
return _onReadUniformResource(ret.data);
|
||||
}
|
||||
|
||||
if (!isLoading) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
|
@ -121,26 +165,10 @@ const ScanQRCode = () => {
|
|||
};
|
||||
|
||||
const showFilePicker = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await DocumentPicker.pick();
|
||||
const file = await RNFS.readFile(res.uri);
|
||||
const fileParsed = JSON.parse(file);
|
||||
if (fileParsed.keystore.xpub) {
|
||||
let masterFingerprint;
|
||||
if (fileParsed.keystore.ckcc_xfp) {
|
||||
masterFingerprint = Number(fileParsed.keystore.ckcc_xfp);
|
||||
}
|
||||
onBarCodeRead({ data: fileParsed.keystore.xpub, additionalProperties: { masterFingerprint, label: fileParsed.keystore.label } });
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
alert(loc.send.qr_error_no_wallet);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
setIsLoading(true);
|
||||
const { data } = await fs.showFilePickerAndReadFile();
|
||||
if (data) onBarCodeRead({ data });
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const showImagePicker = () => {
|
||||
|
@ -218,6 +246,45 @@ const ScanQRCode = () => {
|
|||
<TouchableOpacity style={styles.imagePickerTouch} onPress={showImagePicker}>
|
||||
<Icon name="image" type="font-awesome" color="#ffffff" />
|
||||
</TouchableOpacity>
|
||||
{showFileImportButton && (
|
||||
<TouchableOpacity style={styles.filePickerTouch} onPress={showFilePicker}>
|
||||
<Icon name="file-import" type="material-community" color="#ffffff" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{backdoorVisible && (
|
||||
<View style={styles.backdoorInputWrapper}>
|
||||
<BlueTextHooks>Provide QR code contents manually:</BlueTextHooks>
|
||||
<TextInput
|
||||
testID="scanQrBackdoorInput"
|
||||
multiline
|
||||
underlineColorAndroid="transparent"
|
||||
style={styles.backdoorInput}
|
||||
autoCorrect={false}
|
||||
autoCapitalize="none"
|
||||
spellCheck={false}
|
||||
selectTextOnFocus={false}
|
||||
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
|
||||
value={backdoorText}
|
||||
onChangeText={setBackdoorText}
|
||||
/>
|
||||
<BlueButtonHook
|
||||
title="OK"
|
||||
testID="scanQrBackdoorOkButton"
|
||||
onPress={() => {
|
||||
setBackdoorVisible(false);
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(backdoorText);
|
||||
// this might be a json string (for convenience - in case there are "\n" in there)
|
||||
} catch (_) {
|
||||
data = backdoorText;
|
||||
}
|
||||
|
||||
if (data) onBarCodeRead({ data });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
testID="ScanQrBackdoorButton"
|
||||
style={styles.backdoorButton}
|
||||
|
@ -227,23 +294,10 @@ const ScanQRCode = () => {
|
|||
// this allows to mock and test QR scanning in e2e tests
|
||||
setBackdoorPressed(backdoorPressed + 1);
|
||||
if (backdoorPressed < 10) return;
|
||||
let data, userInput;
|
||||
try {
|
||||
userInput = await prompt('Provide QR code contents manually:', '', false, 'plain-text');
|
||||
data = JSON.parse(userInput);
|
||||
// this might be a json string (for convenience - in case there are "\n" in there)
|
||||
} catch (_) {
|
||||
data = userInput;
|
||||
}
|
||||
|
||||
if (data) onBarCodeRead({ data });
|
||||
setBackdoorPressed(0);
|
||||
setBackdoorVisible(true);
|
||||
}}
|
||||
/>
|
||||
{showFileImportButton && (
|
||||
<TouchableOpacity style={styles.filePickerTouch} onPress={showFilePicker}>
|
||||
<Icon name="file-import" type="material-community" color="#ffffff" />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
/* global alert */
|
||||
import React, { Component } from 'react';
|
||||
import { ActivityIndicator, FlatList, TouchableOpacity, StyleSheet, View } from 'react-native';
|
||||
import { ActivityIndicator, FlatList, TouchableOpacity, StyleSheet, Switch, View } from 'react-native';
|
||||
import { Text } from 'react-native-elements';
|
||||
import { PayjoinClient } from 'payjoin-client';
|
||||
import PayjoinTransaction from '../../class/payjoin-transaction';
|
||||
import { BlueButton, BlueText, SafeBlueArea, BlueCard, BlueSpacing40, BlueNavigationStyle } from '../../BlueComponents';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -32,7 +34,10 @@ export default class Confirm extends Component {
|
|||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
fee: props.route.params.fee,
|
||||
isPayjoinEnabled: false,
|
||||
payjoinUrl: props.route.params.fromWallet.allowPayJoin() ? props.route.params?.payjoinUrl : false,
|
||||
psbt: props.route.params?.psbt,
|
||||
fee: props.route.params?.fee,
|
||||
feeSatoshi: new Bignumber(props.route.params.fee).multipliedBy(100000000).toNumber(),
|
||||
memo: props.route.params.memo,
|
||||
recipients: props.route.params.recipients,
|
||||
|
@ -50,59 +55,63 @@ export default class Confirm extends Component {
|
|||
this.isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
}
|
||||
|
||||
broadcast() {
|
||||
send() {
|
||||
this.setState({ isLoading: true }, async () => {
|
||||
try {
|
||||
// await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
|
||||
if (this.isBiometricUseCapableAndEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.state.fromWallet.broadcastTx(this.state.tx);
|
||||
if (!result) {
|
||||
throw new Error(loc.errors.broadcast);
|
||||
const txids2watch = [];
|
||||
if (!this.state.isPayjoinEnabled) {
|
||||
await this.broadcast(this.state.tx);
|
||||
} else {
|
||||
const txid = bitcoin.Transaction.fromHex(this.state.tx).getId();
|
||||
notifications.majorTomToGroundControl([], [], [txid]);
|
||||
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
|
||||
let amount = 0;
|
||||
const recipients = this.state.recipients;
|
||||
if (recipients[0].amount === BitcoinUnit.MAX || (!recipients[0].amount && !recipients[0].value)) {
|
||||
amount = this.state.fromWallet.getBalance() - this.state.feeSatoshi;
|
||||
} else {
|
||||
for (const recipient of recipients) {
|
||||
amount += recipient.amount ? +recipient.amount : recipient.value;
|
||||
}
|
||||
}
|
||||
|
||||
// wallets that support new createTransaction() instead of deprecated createTx()
|
||||
if (
|
||||
[
|
||||
HDSegwitBech32Wallet.type,
|
||||
HDSegwitP2SHWallet.type,
|
||||
HDLegacyP2PKHWallet.type,
|
||||
HDLegacyBreadwalletWallet.type,
|
||||
HDLegacyElectrumSeedP2PKHWallet.type,
|
||||
LegacyWallet.type,
|
||||
SegwitP2SHWallet.type,
|
||||
SegwitBech32Wallet.type,
|
||||
].includes(this.state.fromWallet.type)
|
||||
) {
|
||||
amount = formatBalanceWithoutSuffix(amount, BitcoinUnit.BTC, false);
|
||||
}
|
||||
|
||||
this.props.navigation.navigate('Success', {
|
||||
fee: Number(this.state.fee),
|
||||
amount,
|
||||
dismissModal: () => this.props.navigation.dangerouslyGetParent().pop(),
|
||||
const wallet = new PayjoinTransaction(this.state.psbt, txHex => this.broadcast(txHex), this.state.fromWallet);
|
||||
const payjoinClient = new PayjoinClient({
|
||||
wallet,
|
||||
payjoinUrl: this.state.payjoinUrl,
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
await payjoinClient.run();
|
||||
const payjoinPsbt = wallet.getPayjoinPsbt();
|
||||
if (payjoinPsbt) {
|
||||
const tx = payjoinPsbt.extractTransaction();
|
||||
txids2watch.push(tx.getId());
|
||||
}
|
||||
}
|
||||
|
||||
const txid = bitcoin.Transaction.fromHex(this.state.tx).getId();
|
||||
txids2watch.push(txid);
|
||||
notifications.majorTomToGroundControl([], [], txids2watch);
|
||||
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
|
||||
let amount = 0;
|
||||
const recipients = this.state.recipients;
|
||||
if (recipients[0].amount === BitcoinUnit.MAX || (!recipients[0].amount && !recipients[0].value)) {
|
||||
amount = this.state.fromWallet.getBalance() - this.state.feeSatoshi;
|
||||
} else {
|
||||
for (const recipient of recipients) {
|
||||
amount += recipient.amount ? +recipient.amount : recipient.value;
|
||||
}
|
||||
}
|
||||
|
||||
// wallets that support new createTransaction() instead of deprecated createTx()
|
||||
if (
|
||||
[
|
||||
HDSegwitBech32Wallet.type,
|
||||
HDSegwitP2SHWallet.type,
|
||||
HDLegacyP2PKHWallet.type,
|
||||
HDLegacyBreadwalletWallet.type,
|
||||
HDLegacyElectrumSeedP2PKHWallet.type,
|
||||
LegacyWallet.type,
|
||||
SegwitP2SHWallet.type,
|
||||
SegwitBech32Wallet.type,
|
||||
].includes(this.state.fromWallet.type)
|
||||
) {
|
||||
amount = formatBalanceWithoutSuffix(amount, BitcoinUnit.BTC, false);
|
||||
}
|
||||
|
||||
this.props.navigation.navigate('Success', {
|
||||
fee: Number(this.state.fee),
|
||||
amount,
|
||||
dismissModal: () => this.props.navigation.dangerouslyGetParent().pop(),
|
||||
});
|
||||
|
||||
this.setState({ isLoading: false });
|
||||
} catch (error) {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', {
|
||||
ignoreAndroidSystemSettings: false,
|
||||
|
@ -113,6 +122,24 @@ export default class Confirm extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
async broadcast(tx) {
|
||||
await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
|
||||
if (this.isBiometricUseCapableAndEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.state.fromWallet.broadcastTx(tx);
|
||||
if (!result) {
|
||||
throw new Error(loc.errors.broadcast);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_renderItem = ({ index, item }) => {
|
||||
return (
|
||||
<>
|
||||
|
@ -168,11 +195,17 @@ export default class Confirm extends Component {
|
|||
{currency.satoshiToLocalCurrency(this.state.feeSatoshi)})
|
||||
</Text>
|
||||
<BlueSpacing40 />
|
||||
{this.state.isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<BlueButton onPress={() => this.broadcast()} title={loc.send.confirm_sendNow} />
|
||||
{!!this.state.payjoinUrl && (
|
||||
<View style={styles.payjoinWrapper}>
|
||||
<Text style={styles.payjoinText}>Payjoin</Text>
|
||||
<Switch
|
||||
testID="PayjoinSwitch"
|
||||
value={this.state.isPayjoinEnabled}
|
||||
onValueChange={isPayjoinEnabled => this.setState({ isPayjoinEnabled })}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{this.state.isLoading ? <ActivityIndicator /> : <BlueButton onPress={() => this.send()} title={loc.send.confirm_sendNow} />}
|
||||
|
||||
<TouchableOpacity
|
||||
testID="TransactionDetailsButton"
|
||||
|
@ -287,11 +320,20 @@ const styles = StyleSheet.create({
|
|||
fontWeight: '500',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
payjoinWrapper: {
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 20,
|
||||
marginBottom: 10,
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
payjoinText: { color: '#81868e', fontSize: 14 },
|
||||
});
|
||||
|
||||
Confirm.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
dismiss: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
dangerouslyGetParent: PropTypes.func,
|
||||
}),
|
||||
|
|
|
@ -253,8 +253,8 @@ SendCreate.navigationOptions = ({ navigation, route }) => {
|
|||
}
|
||||
|
||||
return {
|
||||
...BlueNavigationStyle,
|
||||
title: loc.send.create.details,
|
||||
...BlueNavigationStyle(),
|
||||
title: loc.send.create_details,
|
||||
headerRight,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -38,15 +38,17 @@ import * as bitcoin from 'bitcoinjs-lib';
|
|||
|
||||
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
import { AppStorage, HDSegwitBech32Wallet, LightningCustodianWallet, WatchOnlyWallet } from '../../class';
|
||||
import { AppStorage, HDSegwitBech32Wallet, LightningCustodianWallet, MultisigHDWallet, WatchOnlyWallet } from '../../class';
|
||||
import { BitcoinTransaction } from '../../models/bitcoinTransactionInfo';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
|
||||
import loc from '../../loc';
|
||||
import { BlueCurrentTheme } from '../../components/themes';
|
||||
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
|
||||
const currency = require('../../blue_modules/currency');
|
||||
const BlueApp: AppStorage = require('../../BlueApp');
|
||||
const prompt = require('../../blue_modules/prompt');
|
||||
const fs = require('../../blue_modules/fs');
|
||||
|
||||
const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/;
|
||||
|
||||
|
@ -131,7 +133,6 @@ const styles = StyleSheet.create({
|
|||
createButton: {
|
||||
marginHorizontal: 56,
|
||||
marginVertical: 16,
|
||||
alignItems: 'center',
|
||||
minHeight: 44,
|
||||
},
|
||||
select: {
|
||||
|
@ -309,12 +310,14 @@ export default class SendDetails extends Component {
|
|||
units[this.state.recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC
|
||||
recipients[[this.state.recipientsScrollIndex]].address = address;
|
||||
recipients[[this.state.recipientsScrollIndex]].amount = options.amount;
|
||||
recipients[[this.state.recipientsScrollIndex]].amountSats = new BigNumber(options.amount).multipliedBy(100000000).toNumber();
|
||||
this.setState({
|
||||
addresses: recipients,
|
||||
memo: options.label || options.message,
|
||||
isLoading: false,
|
||||
amountUnit: BitcoinUnit.BTC,
|
||||
units,
|
||||
payjoinUrl: options.pj || '',
|
||||
});
|
||||
} else {
|
||||
this.setState({ isLoading: false });
|
||||
|
@ -332,10 +335,10 @@ export default class SendDetails extends Component {
|
|||
if (this.props.route.params.uri) {
|
||||
const uri = this.props.route.params.uri;
|
||||
try {
|
||||
const { address, amount, memo } = this.decodeBitcoinUri(uri);
|
||||
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(uri);
|
||||
addresses.push(new BitcoinTransaction(address, amount, currency.btcToSatoshi(amount)));
|
||||
initialMemo = memo;
|
||||
this.setState({ addresses, memo: initialMemo, isLoading: false, amountUnit: BitcoinUnit.BTC });
|
||||
this.setState({ addresses, memo: initialMemo, isLoading: false, amountUnit: BitcoinUnit.BTC, payjoinUrl });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
alert(loc.send.details_error_decode);
|
||||
|
@ -359,10 +362,10 @@ export default class SendDetails extends Component {
|
|||
}
|
||||
} catch (_) {}
|
||||
|
||||
await this.reCalcTx();
|
||||
this.reCalcTx();
|
||||
|
||||
try {
|
||||
const recommendedFees = await NetworkTransactionFees.recommendedFees();
|
||||
const recommendedFees = await Promise.race([NetworkTransactionFees.recommendedFees(), BlueApp.sleep(2000)]);
|
||||
if (recommendedFees && 'fastestFee' in recommendedFees) {
|
||||
await AsyncStorage.setItem(NetworkTransactionFee.StorageKey, JSON.stringify(recommendedFees));
|
||||
this.setState({
|
||||
|
@ -370,21 +373,28 @@ export default class SendDetails extends Component {
|
|||
networkTransactionFees: recommendedFees,
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
} catch (_) {} // either sleep expired or recommendedFees threw an exception
|
||||
|
||||
if (this.props.route.params.uri) {
|
||||
try {
|
||||
const { address, amount, memo } = this.decodeBitcoinUri(this.props.route.params.uri);
|
||||
this.setState({ address, amount, memo });
|
||||
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(this.props.route.params.uri);
|
||||
this.setState({ address, amount, memo, isLoading: false, payjoinUrl });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.setState({ isLoading: false });
|
||||
alert(loc.send.details_error_decode);
|
||||
}
|
||||
}
|
||||
|
||||
await this.state.fromWallet.fetchUtxo();
|
||||
try {
|
||||
await Promise.race([this.state.fromWallet.fetchUtxo(), BlueApp.sleep(6000)]);
|
||||
} catch (_) {
|
||||
// either sleep expired or fetchUtxo threw an exception
|
||||
}
|
||||
|
||||
this.setState({ isLoading: false });
|
||||
await this.reCalcTx();
|
||||
|
||||
this.reCalcTx();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -400,27 +410,6 @@ export default class SendDetails extends Component {
|
|||
this.setState({ renderWalletSelectionButtonHidden: false, isAmountToolbarVisibleForAndroid: false });
|
||||
};
|
||||
|
||||
decodeBitcoinUri(uri) {
|
||||
let amount = '';
|
||||
let parsedBitcoinUri = null;
|
||||
let address = uri || '';
|
||||
let memo = '';
|
||||
try {
|
||||
parsedBitcoinUri = DeeplinkSchemaMatch.bip21decode(uri);
|
||||
address = 'address' in parsedBitcoinUri ? parsedBitcoinUri.address : address;
|
||||
if ('options' in parsedBitcoinUri) {
|
||||
if ('amount' in parsedBitcoinUri.options) {
|
||||
amount = parsedBitcoinUri.options.amount.toString();
|
||||
amount = parsedBitcoinUri.options.amount;
|
||||
}
|
||||
if ('label' in parsedBitcoinUri.options) {
|
||||
memo = parsedBitcoinUri.options.label || memo;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
return { address, amount, memo };
|
||||
}
|
||||
|
||||
async createTransaction() {
|
||||
Keyboard.dismiss();
|
||||
this.setState({ isLoading: true });
|
||||
|
@ -484,13 +473,63 @@ export default class SendDetails extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
getChangeAddressFast() {
|
||||
if (this.state.changeAddress) return this.state.changeAddress; // cache
|
||||
|
||||
/** @type {AbstractHDElectrumWallet|WatchOnlyWallet} */
|
||||
const wallet = this.state.fromWallet;
|
||||
let changeAddress;
|
||||
if (WatchOnlyWallet.type === wallet.type && !wallet.isHd()) {
|
||||
// plain watchonly - just get the address
|
||||
changeAddress = wallet.getAddress();
|
||||
} else if (WatchOnlyWallet.type === wallet.type || wallet instanceof AbstractHDElectrumWallet) {
|
||||
changeAddress = wallet._getInternalAddressByIndex(wallet.getNextFreeChangeAddressIndex());
|
||||
} else {
|
||||
// legacy wallets
|
||||
changeAddress = wallet.getAddress();
|
||||
}
|
||||
|
||||
return changeAddress;
|
||||
}
|
||||
|
||||
async getChangeAddressAsync() {
|
||||
if (this.state.changeAddress) return this.state.changeAddress; // cache
|
||||
|
||||
/** @type {AbstractHDElectrumWallet|WatchOnlyWallet} */
|
||||
const wallet = this.state.fromWallet;
|
||||
let changeAddress;
|
||||
if (WatchOnlyWallet.type === wallet.type && !wallet.isHd()) {
|
||||
// plain watchonly - just get the address
|
||||
changeAddress = wallet.getAddress();
|
||||
} else {
|
||||
// otherwise, lets call widely-used getChangeAddressAsync()
|
||||
try {
|
||||
changeAddress = await Promise.race([BlueApp.sleep(2000), wallet.getChangeAddressAsync()]);
|
||||
} catch (_) {}
|
||||
|
||||
if (!changeAddress) {
|
||||
// either sleep expired or getChangeAddressAsync threw an exception
|
||||
if (wallet instanceof AbstractHDElectrumWallet) {
|
||||
changeAddress = wallet._getInternalAddressByIndex(wallet.getNextFreeChangeAddressIndex());
|
||||
} else {
|
||||
// legacy wallets
|
||||
changeAddress = wallet.getAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changeAddress) this.setState({ changeAddress }); // cache
|
||||
|
||||
return changeAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculating fee options by creating skeleton of future tx.
|
||||
*/
|
||||
reCalcTx = async (all = false) => {
|
||||
reCalcTx = (all = false) => {
|
||||
const wallet = this.state.fromWallet;
|
||||
const fees = this.state.networkTransactionFees;
|
||||
const changeAddress = await wallet.getChangeAddressAsync();
|
||||
const changeAddress = this.getChangeAddressFast();
|
||||
const requestedSatPerByte = Number(this.state.fee);
|
||||
const feePrecalc = { ...this.state.feePrecalc };
|
||||
|
||||
|
@ -564,7 +603,7 @@ export default class SendDetails extends Component {
|
|||
async createPsbtTransaction() {
|
||||
/** @type {HDSegwitBech32Wallet} */
|
||||
const wallet = this.state.fromWallet;
|
||||
const changeAddress = await wallet.getChangeAddressAsync();
|
||||
const changeAddress = await this.getChangeAddressAsync();
|
||||
const requestedSatPerByte = Number(this.state.fee);
|
||||
console.log({ requestedSatPerByte, utxo: wallet.getUtxo() });
|
||||
|
||||
|
@ -606,6 +645,16 @@ export default class SendDetails extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
if (wallet.type === MultisigHDWallet.type) {
|
||||
this.props.navigation.navigate('PsbtMultisig', {
|
||||
memo: this.state.memo,
|
||||
psbtBase64: psbt.toBase64(),
|
||||
walletId: wallet.getID(),
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
BlueApp.tx_metadata = BlueApp.tx_metadata || {};
|
||||
BlueApp.tx_metadata[tx.getId()] = {
|
||||
txhex: tx.toHex(),
|
||||
|
@ -619,6 +668,8 @@ export default class SendDetails extends Component {
|
|||
tx: tx.toHex(),
|
||||
recipients: targets,
|
||||
satoshiPerByte: requestedSatPerByte,
|
||||
payjoinUrl: this.state.payjoinUrl,
|
||||
psbt,
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
|
@ -772,32 +823,54 @@ export default class SendDetails extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
||||
* so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
|
||||
* user whether he wants to broadcast it.
|
||||
* alternatively, user can export psbt file, sign it externally and then import it
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
importTransaction = async () => {
|
||||
if (this.state.fromWallet.type !== WatchOnlyWallet.type) {
|
||||
alert('Error: importing transaction in non-watchonly wallet (this should never happen)');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
type:
|
||||
Platform.OS === 'ios'
|
||||
? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn', DocumentPicker.types.plainText, 'public.json']
|
||||
: [DocumentPicker.types.allFiles],
|
||||
});
|
||||
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
|
||||
|
||||
if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(res.uri)) {
|
||||
// we assume that transaction is already signed, so all we have to do is get txhex and pass it to next screen
|
||||
// so user can broadcast:
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
const bufferDecoded = Buffer.from(file, 'ascii').toString('base64');
|
||||
if (bufferDecoded) {
|
||||
if (this.state.fromWallet.type === WatchOnlyWallet.type) {
|
||||
// watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
||||
// so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
|
||||
// user whether he wants to broadcast it.
|
||||
// alternatively, user can export psbt file, sign it externally and then import it
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
psbt: file,
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
const psbt = bitcoin.Psbt.fromBase64(file);
|
||||
const txhex = psbt.extractTransaction().toHex();
|
||||
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
txhex,
|
||||
});
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
} else if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
|
||||
// looks like transaction is UNsigned, so we construct PSBT object and pass to next screen
|
||||
// so user can do smth with it:
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
const psbt = bitcoin.Psbt.fromBase64(file);
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
psbt,
|
||||
});
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
} else if (DeeplinkSchemaMatch.isTXNFile(res.uri)) {
|
||||
// plain text file with txhex ready to broadcast
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
|
@ -805,7 +878,8 @@ export default class SendDetails extends Component {
|
|||
txhex: file,
|
||||
});
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
return;
|
||||
} else {
|
||||
alert('Unrecognized file format');
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
|
@ -814,6 +888,112 @@ export default class SendDetails extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
askCosignThisTransaction = async () => {
|
||||
return new Promise(resolve => {
|
||||
Alert.alert(
|
||||
loc.multisig.cosign_this_transaction,
|
||||
'',
|
||||
[
|
||||
{
|
||||
text: loc._.no,
|
||||
style: 'cancel',
|
||||
onPress: () => resolve(false),
|
||||
},
|
||||
{
|
||||
text: loc._.yes,
|
||||
onPress: () => resolve(true),
|
||||
},
|
||||
],
|
||||
{ cancelable: false },
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
_importTransactionMultisig = async base64arg => {
|
||||
try {
|
||||
/** @type MultisigHDWallet */
|
||||
const fromWallet = this.state.fromWallet;
|
||||
const base64 = base64arg || (await fs.openSignedTransaction());
|
||||
if (!base64) return;
|
||||
const psbt = bitcoin.Psbt.fromBase64(base64); // if it doesnt throw - all good, its valid
|
||||
|
||||
if (fromWallet.howManySignaturesCanWeMake() > 0 && (await this.askCosignThisTransaction())) {
|
||||
fromWallet.cosignPsbt(psbt);
|
||||
}
|
||||
|
||||
this.props.navigation.navigate('PsbtMultisig', {
|
||||
memo: this.state.memo,
|
||||
psbtBase64: psbt.toBase64(),
|
||||
walletId: fromWallet.getID(),
|
||||
});
|
||||
} catch (error) {
|
||||
alert(loc.send.problem_with_psbt + ': ' + error.message);
|
||||
}
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
};
|
||||
|
||||
importTransactionMultisig = async () => {
|
||||
return this._importTransactionMultisig();
|
||||
};
|
||||
|
||||
onBarScanned = ret => {
|
||||
this.props.navigation.dangerouslyGetParent().pop();
|
||||
if (!ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
alert('BC-UR not decoded. This should never happen');
|
||||
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
// we dont support it in this flow
|
||||
} else {
|
||||
// psbt base64?
|
||||
return this._importTransactionMultisig(ret.data);
|
||||
}
|
||||
};
|
||||
|
||||
importTransactionMultisigScanQr = async () => {
|
||||
this.setState({ isAdvancedTransactionOptionsVisible: false });
|
||||
this.props.navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
onBarScanned: this.onBarScanned,
|
||||
showFileImportButton: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleAddRecipient = () => {
|
||||
const { addresses } = this.state;
|
||||
addresses.push(new BitcoinTransaction());
|
||||
this.setState(
|
||||
{
|
||||
addresses,
|
||||
isAdvancedTransactionOptionsVisible: false,
|
||||
},
|
||||
() => {
|
||||
this.scrollView.scrollToEnd();
|
||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
||||
// after adding recipient it automatically scrolls to the last one
|
||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
handleRemoveRecipient = () => {
|
||||
const { addresses } = this.state;
|
||||
addresses.splice(this.state.recipientsScrollIndex, 1);
|
||||
this.setState(
|
||||
{
|
||||
addresses,
|
||||
isAdvancedTransactionOptionsVisible: false,
|
||||
},
|
||||
() => {
|
||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
||||
// after deletion it automatically scrolls to the last one
|
||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
renderAdvancedTransactionOptionsModal = () => {
|
||||
const isSendMaxUsed = this.state.addresses.some(element => element.amount === BitcoinUnit.MAX);
|
||||
return (
|
||||
|
@ -856,6 +1036,22 @@ export default class SendDetails extends Component {
|
|||
onPress={this.importTransaction}
|
||||
/>
|
||||
)}
|
||||
{this.state.fromWallet.type === MultisigHDWallet.type && (
|
||||
<BlueListItem
|
||||
title={loc.send.details_adv_import}
|
||||
hideChevron
|
||||
component={TouchableOpacity}
|
||||
onPress={this.importTransactionMultisig}
|
||||
/>
|
||||
)}
|
||||
{this.state.fromWallet.type === MultisigHDWallet.type && this.state.fromWallet.howManySignaturesCanWeMake() > 0 && (
|
||||
<BlueListItem
|
||||
title={loc.multisig.co_sign_transaction}
|
||||
hideChevron
|
||||
component={TouchableOpacity}
|
||||
onPress={this.importTransactionMultisigScanQr}
|
||||
/>
|
||||
)}
|
||||
{this.state.fromWallet.allowBatchSend() && (
|
||||
<>
|
||||
<BlueListItem
|
||||
|
@ -863,43 +1059,14 @@ export default class SendDetails extends Component {
|
|||
title={loc.send.details_add_rec_add}
|
||||
hideChevron
|
||||
component={TouchableOpacity}
|
||||
onPress={() => {
|
||||
const addresses = this.state.addresses;
|
||||
addresses.push(new BitcoinTransaction());
|
||||
this.setState(
|
||||
{
|
||||
addresses,
|
||||
isAdvancedTransactionOptionsVisible: false,
|
||||
},
|
||||
() => {
|
||||
this.scrollView.scrollToEnd();
|
||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
||||
// after adding recipient it automatically scrolls to the last one
|
||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||
},
|
||||
);
|
||||
}}
|
||||
onPress={this.handleAddRecipient}
|
||||
/>
|
||||
<BlueListItem
|
||||
title={loc.send.details_add_rec_rem}
|
||||
hideChevron
|
||||
disabled={this.state.addresses.length < 2}
|
||||
component={TouchableOpacity}
|
||||
onPress={() => {
|
||||
const addresses = this.state.addresses;
|
||||
addresses.splice(this.state.recipientsScrollIndex, 1);
|
||||
this.setState(
|
||||
{
|
||||
addresses,
|
||||
isAdvancedTransactionOptionsVisible: false,
|
||||
},
|
||||
() => {
|
||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
||||
// after deletion it automatically scrolls to the last one
|
||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||
},
|
||||
);
|
||||
}}
|
||||
onPress={this.handleRemoveRecipient}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -1033,7 +1200,7 @@ export default class SendDetails extends Component {
|
|||
onChangeText={async text => {
|
||||
text = text.trim();
|
||||
const transactions = this.state.addresses;
|
||||
const { address, amount, memo } = this.decodeBitcoinUri(text);
|
||||
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(text);
|
||||
item.address = address || text;
|
||||
item.amount = amount || item.amount;
|
||||
transactions[index] = item;
|
||||
|
@ -1041,6 +1208,7 @@ export default class SendDetails extends Component {
|
|||
addresses: transactions,
|
||||
memo: memo || this.state.memo,
|
||||
isLoading: false,
|
||||
payjoinUrl,
|
||||
});
|
||||
this.reCalcTx();
|
||||
}}
|
||||
|
@ -1185,6 +1353,7 @@ SendDetails.propTypes = {
|
|||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
setParams: PropTypes.func,
|
||||
dangerouslyGetParent: PropTypes.func,
|
||||
}),
|
||||
route: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
|
|
491
screen/send/psbtMultisig.js
Normal file
491
screen/send/psbtMultisig.js
Normal file
|
@ -0,0 +1,491 @@
|
|||
/* global alert */
|
||||
import React, { useState } from 'react';
|
||||
import { FlatList, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { BlueButton, BlueButtonLink, BlueCard, BlueNavigationStyle, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
|
||||
import { DynamicQRCode } from '../../components/DynamicQRCode';
|
||||
import { SquareButton } from '../../components/SquareButton';
|
||||
import { getSystemName } from 'react-native-device-info';
|
||||
import loc from '../../loc';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import ScanQRCode from './ScanQRCode';
|
||||
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
|
||||
const BlueApp = require('../../BlueApp');
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const currency = require('../../blue_modules/currency');
|
||||
const fs = require('../../blue_modules/fs');
|
||||
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
|
||||
const isDesktop = getSystemName() === 'Mac OS X';
|
||||
const BigNumber = require('bignumber.js');
|
||||
|
||||
const shortenAddress = addr => {
|
||||
return addr.substr(0, Math.floor(addr.length / 2) - 1) + '\n' + addr.substr(Math.floor(addr.length / 2) - 1, addr.length);
|
||||
};
|
||||
|
||||
const PsbtMultisig = () => {
|
||||
const navigation = useNavigation();
|
||||
const route = useRoute();
|
||||
const { colors } = useTheme();
|
||||
const [flatListHeight, setFlatListHeight] = useState(0);
|
||||
|
||||
const walletId = route.params.walletId;
|
||||
const psbtBase64 = route.params.psbtBase64;
|
||||
const memo = route.params.memo;
|
||||
|
||||
const [psbt, setPsbt] = useState(bitcoin.Psbt.fromBase64(psbtBase64));
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const stylesHook = StyleSheet.create({
|
||||
root: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
textBtc: {
|
||||
color: colors.buttonAlternativeTextColor,
|
||||
},
|
||||
textDestinationFirstFour: {
|
||||
color: colors.buttonAlternativeTextColor,
|
||||
},
|
||||
textBtcUnitValue: {
|
||||
color: colors.buttonAlternativeTextColor,
|
||||
},
|
||||
textDestination: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
modalContentShort: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
textFiat: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
provideSignatureButton: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
exportButton: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
provideSignatureButtonText: {
|
||||
color: colors.buttonTextColor,
|
||||
},
|
||||
vaultKeyCircle: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
vaultKeyText: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
feeFiatText: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
vaultKeyCircleSuccess: {
|
||||
backgroundColor: colors.msSuccessBG,
|
||||
},
|
||||
vaultKeyTextSigned: {
|
||||
color: colors.msSuccessBG,
|
||||
},
|
||||
});
|
||||
/** @type MultisigHDWallet */
|
||||
const wallet = BlueApp.getWallets().find(w => w.getID() === walletId);
|
||||
let destination = [];
|
||||
let totalSat = 0;
|
||||
const targets = [];
|
||||
for (const output of psbt.txOutputs) {
|
||||
if (output.address && !wallet.weOwnAddress(output.address)) {
|
||||
totalSat += output.value;
|
||||
destination.push(output.address);
|
||||
targets.push({ address: output.address, value: output.value });
|
||||
}
|
||||
}
|
||||
destination = shortenAddress(destination.join(', '));
|
||||
const totalBtc = new BigNumber(totalSat).dividedBy(100000000).toNumber();
|
||||
const totalFiat = currency.satoshiToLocalCurrency(totalSat);
|
||||
const fileName = `${Date.now()}.psbt`;
|
||||
|
||||
const howManySignaturesWeHave = () => {
|
||||
return wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt);
|
||||
};
|
||||
|
||||
const getFee = () => {
|
||||
return wallet.calculateFeeFromPsbt(psbt);
|
||||
};
|
||||
|
||||
const _renderItem = el => {
|
||||
if (el.index >= howManySignaturesWeHave()) return _renderItemUnsigned(el);
|
||||
else return _renderItemSigned(el);
|
||||
};
|
||||
|
||||
const _renderItemUnsigned = el => {
|
||||
const renderProvideSignature = el.index === howManySignaturesWeHave();
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.itemUnsignedWrapper}>
|
||||
<View style={[styles.vaultKeyCircle, stylesHook.vaultKeyCircle]}>
|
||||
<Text style={[styles.vaultKeyText, stylesHook.vaultKeyText]}>{el.index + 1}</Text>
|
||||
</View>
|
||||
<View style={styles.vaultKeyTextWrapper}>
|
||||
<Text style={[styles.vaultKeyText, stylesHook.vaultKeyText]}>
|
||||
{loc.formatString(loc.multisig.vault_key, { number: el.index + 1 })}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{renderProvideSignature && (
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
|
||||
onPress={() => {
|
||||
setIsModalVisible(true);
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
|
||||
{loc.multisig.provide_signature}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderItemSigned = el => {
|
||||
return (
|
||||
<View style={styles.flexDirectionRow}>
|
||||
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
|
||||
<Icon size={24} name="check" type="ionicons" color={colors.msSuccessCheck} />
|
||||
</View>
|
||||
<View style={styles.vaultKeyTextSignedWrapper}>
|
||||
<Text style={[styles.vaultKeyTextSigned, stylesHook.vaultKeyTextSigned]}>
|
||||
{loc.formatString(loc.multisig.vault_key, { number: el.index + 1 })}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const _combinePSBT = receivedPSBTBase64 => {
|
||||
const receivedPSBT = bitcoin.Psbt.fromBase64(receivedPSBTBase64);
|
||||
try {
|
||||
const newPsbt = psbt.combine(receivedPSBT);
|
||||
navigation.dangerouslyGetParent().pop();
|
||||
setPsbt(newPsbt);
|
||||
setIsModalVisible(false);
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
};
|
||||
|
||||
const onBarScanned = ret => {
|
||||
if (!ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
alert('BC-UR not decoded. This should never happen');
|
||||
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
// we dont support it in this flow
|
||||
} else {
|
||||
// psbt base64?
|
||||
_combinePSBT(ret.data);
|
||||
}
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
try {
|
||||
psbt.finalizeAllInputs();
|
||||
} catch (_) {} // ignore if it is already finalized
|
||||
|
||||
try {
|
||||
const tx = psbt.extractTransaction().toHex();
|
||||
const satoshiPerByte = Math.round(getFee() / (tx.length / 2));
|
||||
navigation.navigate('Confirm', {
|
||||
fee: new BigNumber(getFee()).dividedBy(100000000).toNumber(),
|
||||
memo: memo,
|
||||
fromWallet: wallet,
|
||||
tx,
|
||||
recipients: targets,
|
||||
satoshiPerByte,
|
||||
});
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
};
|
||||
|
||||
const openScanner = () => {
|
||||
if (isDesktop) {
|
||||
ImagePicker.launchCamera(
|
||||
{
|
||||
title: null,
|
||||
mediaType: 'photo',
|
||||
takePhotoButtonTitle: null,
|
||||
},
|
||||
response => {
|
||||
if (response.uri) {
|
||||
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
|
||||
LocalQRCode.decode(uri, (error, result) => {
|
||||
if (!error) {
|
||||
onBarScanned(result);
|
||||
} else {
|
||||
alert(loc.send.qr_error_no_qrcode);
|
||||
}
|
||||
});
|
||||
} else if (response.error) {
|
||||
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
navigation.navigate('ScanQRCodeRoot', {
|
||||
screen: 'ScanQRCode',
|
||||
params: {
|
||||
onBarScanned: onBarScanned,
|
||||
showFileImportButton: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const exportPSBT = async () => {
|
||||
await fs.writeFileAndExport(fileName, psbt.toBase64());
|
||||
};
|
||||
|
||||
const isConfirmEnabled = () => {
|
||||
return howManySignaturesWeHave() >= wallet.getM();
|
||||
};
|
||||
|
||||
const renderDynamicQrCode = () => {
|
||||
return (
|
||||
<SafeBlueArea style={[styles.root, stylesHook.root]}>
|
||||
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent}>
|
||||
<View style={[styles.modalContentShort, stylesHook.modalContentShort]}>
|
||||
<DynamicQRCode value={psbt.toHex()} capacity={666} />
|
||||
<BlueSpacing20 />
|
||||
<SquareButton
|
||||
style={[styles.exportButton, stylesHook.exportButton]}
|
||||
onPress={openScanner}
|
||||
title={loc.multisig.scan_or_import_file}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportPSBT} title={loc.multisig.share} />
|
||||
<BlueSpacing20 />
|
||||
<BlueButtonLink title={loc._.cancel} onPress={() => setIsModalVisible(false)} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
};
|
||||
|
||||
const destinationAddress = () => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let destinationAddressView = [];
|
||||
const destinations = Object.entries(destination.split(','));
|
||||
for (const [index, address] of destinations) {
|
||||
if (index > 1) {
|
||||
destinationAddressView.push(
|
||||
<View style={styles.destionationTextContainer} key={`end-${index}`}>
|
||||
<Text numberOfLines={0} style={[styles.textDestinationFirstFour, stylesHook.textFiat]}>
|
||||
and {destinations.length - 2} more...
|
||||
</Text>
|
||||
</View>,
|
||||
);
|
||||
break;
|
||||
} else {
|
||||
const currentAddress = address;
|
||||
const firstFour = currentAddress.substring(0, 5);
|
||||
const lastFour = currentAddress.substring(currentAddress.length - 5, currentAddress.length);
|
||||
const middle = currentAddress.split(firstFour)[1].split(lastFour)[0];
|
||||
destinationAddressView.push(
|
||||
<View style={styles.destionationTextContainer} key={`${currentAddress}-${index}`}>
|
||||
<Text numberOfLines={2} style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>
|
||||
{firstFour}
|
||||
<Text> </Text>
|
||||
<Text style={[styles.textDestination, stylesHook.textFiat]}>{middle}</Text>
|
||||
<Text> </Text>
|
||||
<Text style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>{lastFour}</Text>
|
||||
</Text>
|
||||
</View>,
|
||||
);
|
||||
}
|
||||
}
|
||||
return destinationAddressView;
|
||||
};
|
||||
|
||||
const header = (
|
||||
<View style={stylesHook.root}>
|
||||
<View style={styles.containerText}>
|
||||
<BlueText style={[styles.textBtc, stylesHook.textBtc]}>{totalBtc}</BlueText>
|
||||
<View style={styles.textBtcUnit}>
|
||||
<BlueText style={[styles.textBtcUnitValue, stylesHook.textBtcUnitValue]}> {BitcoinUnit.BTC}</BlueText>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.containerText}>
|
||||
<BlueText style={[styles.textFiat, stylesHook.textFiat]}>{totalFiat}</BlueText>
|
||||
</View>
|
||||
<View>{destinationAddress()}</View>
|
||||
</View>
|
||||
);
|
||||
const footer = (
|
||||
<View style={styles.bottomWrapper}>
|
||||
<View style={styles.bottomFeesWrapper}>
|
||||
<BlueText style={[styles.feeFiatText, stylesHook.feeFiatText]}>
|
||||
{loc.formatString(loc.multisig.fee, { number: currency.satoshiToLocalCurrency(getFee()) })} -{' '}
|
||||
</BlueText>
|
||||
<BlueText>{loc.formatString(loc.multisig.fee_btc, { number: currency.satoshiToBTC(getFee()) })}</BlueText>
|
||||
</View>
|
||||
<BlueButton disabled={!isConfirmEnabled()} title={loc.multisig.confirm} onPress={onConfirm} />
|
||||
</View>
|
||||
);
|
||||
|
||||
if (isModalVisible) return renderDynamicQrCode();
|
||||
|
||||
const onLayout = e => {
|
||||
setFlatListHeight(e.nativeEvent.layout.height);
|
||||
};
|
||||
|
||||
const data = new Array(wallet.getM());
|
||||
return (
|
||||
<SafeBlueArea style={[styles.root, stylesHook.root]}>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.mstopcontainer}>
|
||||
<View style={styles.mscontainer}>
|
||||
<View style={[styles.msleft, { height: flatListHeight - 200 }]} />
|
||||
</View>
|
||||
<View style={styles.msright}>
|
||||
<BlueCard>
|
||||
<FlatList
|
||||
data={data}
|
||||
onLayout={onLayout}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(_item, index) => `${index}`}
|
||||
ListHeaderComponent={header}
|
||||
scrollEnabled={false}
|
||||
/>
|
||||
</BlueCard>
|
||||
</View>
|
||||
</View>
|
||||
{footer}
|
||||
</View>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
},
|
||||
mstopcontainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
mscontainer: {
|
||||
flex: 10,
|
||||
},
|
||||
msleft: {
|
||||
width: 1,
|
||||
borderStyle: 'dashed',
|
||||
borderWidth: 0.8,
|
||||
borderColor: '#c4c4c4',
|
||||
marginLeft: 40,
|
||||
marginTop: 185,
|
||||
},
|
||||
msright: {
|
||||
flex: 90,
|
||||
marginLeft: '-11%',
|
||||
},
|
||||
scrollViewContent: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'column',
|
||||
paddingTop: 24,
|
||||
flex: 1,
|
||||
},
|
||||
containerText: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
destionationTextContainer: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 4,
|
||||
paddingHorizontal: 60,
|
||||
fontSize: 14,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
textFiat: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
marginBottom: 30,
|
||||
},
|
||||
textBtc: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 30,
|
||||
},
|
||||
textDestinationFirstFour: {
|
||||
fontSize: 14,
|
||||
},
|
||||
textDestination: {
|
||||
paddingTop: 10,
|
||||
paddingBottom: 40,
|
||||
fontSize: 14,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
bottomModal: {
|
||||
justifyContent: 'flex-end',
|
||||
margin: 0,
|
||||
},
|
||||
modalContentShort: {
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
},
|
||||
copyToClipboard: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
exportButton: {
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
provideSignatureButton: {
|
||||
marginTop: 24,
|
||||
marginLeft: 40,
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
provideSignatureButtonText: { fontWeight: '600', fontSize: 15 },
|
||||
vaultKeyText: { fontSize: 18, fontWeight: 'bold' },
|
||||
vaultKeyTextWrapper: { justifyContent: 'center', alignItems: 'center', paddingLeft: 16 },
|
||||
vaultKeyCircle: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 25,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
vaultKeyCircleSuccess: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 25,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
itemUnsignedWrapper: { flexDirection: 'row', paddingTop: 16 },
|
||||
textDestinationSpacingRight: { marginRight: 4 },
|
||||
textDestinationSpacingLeft: { marginLeft: 4 },
|
||||
vaultKeyTextSigned: { fontSize: 18, fontWeight: 'bold' },
|
||||
vaultKeyTextSignedWrapper: { justifyContent: 'center', alignItems: 'center', paddingLeft: 16 },
|
||||
flexDirectionRow: { flexDirection: 'row', paddingVertical: 12 },
|
||||
textBtcUnit: { justifyContent: 'flex-end', bottom: 8 },
|
||||
bottomFeesWrapper: { flexDirection: 'row', paddingBottom: 20 },
|
||||
bottomWrapper: { justifyContent: 'center', alignItems: 'center', paddingVertical: 20 },
|
||||
});
|
||||
|
||||
PsbtMultisig.navigationOptions = () => ({
|
||||
...BlueNavigationStyle(null, false),
|
||||
title: loc.multisig.header,
|
||||
});
|
||||
|
||||
export default PsbtMultisig;
|
|
@ -33,7 +33,6 @@ import { getSystemName } from 'react-native-device-info';
|
|||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import RNFS from 'react-native-fs';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import { decodeUR, extractSingleWorkload } from 'bc-ur/dist';
|
||||
import loc from '../../loc';
|
||||
import { BlueCurrentTheme } from '../../components/themes';
|
||||
import ScanQRCode from './ScanQRCode';
|
||||
|
@ -68,6 +67,7 @@ const styles = StyleSheet.create({
|
|||
rootPadding: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
closeCamera: {
|
||||
width: 40,
|
||||
|
@ -89,6 +89,7 @@ const styles = StyleSheet.create({
|
|||
hexWrap: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
hexLabel: {
|
||||
color: BlueCurrentTheme.colors.foregroundColor,
|
||||
|
@ -124,44 +125,6 @@ const styles = StyleSheet.create({
|
|||
export default class PsbtWithHardwareWallet extends Component {
|
||||
cameraRef = null;
|
||||
|
||||
_onReadUniformResource = ur => {
|
||||
try {
|
||||
const [index, total] = extractSingleWorkload(ur);
|
||||
const { animatedQRCodeData } = this.state;
|
||||
if (animatedQRCodeData.length > 0) {
|
||||
const currentTotal = animatedQRCodeData[0].total;
|
||||
if (total !== currentTotal) {
|
||||
alert('invalid animated QRCode');
|
||||
}
|
||||
}
|
||||
if (!animatedQRCodeData.find(i => i.index === index)) {
|
||||
this.setState(
|
||||
state => ({
|
||||
animatedQRCodeData: [
|
||||
...state.animatedQRCodeData,
|
||||
{
|
||||
index,
|
||||
total,
|
||||
data: ur,
|
||||
},
|
||||
],
|
||||
}),
|
||||
() => {
|
||||
if (this.state.animatedQRCodeData.length === total) {
|
||||
const payload = decodeUR(this.state.animatedQRCodeData.map(i => i.data));
|
||||
const psbtB64 = Buffer.from(payload, 'hex').toString('base64');
|
||||
const Tx = this._combinePSBT(psbtB64);
|
||||
this.setState({ txhex: Tx.toHex() });
|
||||
this.props.navigation.dangerouslyGetParent().pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (Err) {
|
||||
alert('invalid animated QRCode fragment, please try again');
|
||||
}
|
||||
};
|
||||
|
||||
_combinePSBT = receivedPSBT => {
|
||||
return this.state.fromWallet.combinePsbt(this.state.psbt, receivedPSBT);
|
||||
};
|
||||
|
@ -169,11 +132,11 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
onBarScanned = ret => {
|
||||
if (ret && !ret.data) ret = { data: ret };
|
||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||
return this._onReadUniformResource(ret.data);
|
||||
alert('BC-UR not decoded. This should never happen');
|
||||
}
|
||||
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
this.setState({ txhex: ret.data }, () => this.props.navigation.dangerouslyGetParent().pop());
|
||||
this.setState({ txhex: ret.data });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
@ -201,11 +164,20 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (!prevState.psbt && !nextProps.route.params.txhex) {
|
||||
alert('There is no transaction signing in progress');
|
||||
return {
|
||||
...prevState,
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
const deepLinkPSBT = nextProps.route.params.deepLinkPSBT;
|
||||
const txhex = nextProps.route.params.txhex;
|
||||
if (deepLinkPSBT) {
|
||||
const psbt = bitcoin.Psbt.fromBase64(deepLinkPSBT);
|
||||
try {
|
||||
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, deepLinkPSBT);
|
||||
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, psbt);
|
||||
return {
|
||||
...prevState,
|
||||
txhex: Tx.toHex(),
|
||||
|
|
|
@ -1,19 +1,104 @@
|
|||
import React, { Component } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import LottieView from 'lottie-react-native';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { Text } from 'react-native-elements';
|
||||
import { BlueButton, SafeBlueArea, BlueCard } from '../../BlueComponents';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import PropTypes from 'prop-types';
|
||||
import loc from '../../loc';
|
||||
import { BlueCurrentTheme } from '../../components/themes';
|
||||
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
||||
|
||||
const Success = () => {
|
||||
const { colors } = useTheme();
|
||||
const { dangerouslyGetParent } = useNavigation();
|
||||
const { amount, fee = 0, amountUnit = BitcoinUnit.BTC, invoiceDescription = '' } = useRoute().params;
|
||||
const animationRef = useRef();
|
||||
const stylesHook = StyleSheet.create({
|
||||
root: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
amountValue: {
|
||||
color: colors.alternativeTextColor2,
|
||||
},
|
||||
amountUnit: {
|
||||
color: colors.alternativeTextColor2,
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
console.log('send/success - useEffect');
|
||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const pop = () => {
|
||||
dangerouslyGetParent().pop();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
animationRef.current.reset();
|
||||
animationRef.current.resume();
|
||||
}, [colors]);
|
||||
|
||||
return (
|
||||
<SafeBlueArea style={[styles.root, stylesHook.root]}>
|
||||
<BlueCard style={styles.amout}>
|
||||
{amount > 0 && (
|
||||
<View style={styles.view}>
|
||||
<Text style={[styles.amountValue, stylesHook.amountValue]}>{amount}</Text>
|
||||
<Text style={[styles.amountUnit, stylesHook.amountUnit]}>{' ' + amountUnit}</Text>
|
||||
</View>
|
||||
)}
|
||||
{fee > 0 && (
|
||||
<Text style={styles.feeText}>
|
||||
{loc.send.create_fee}: {fee} {BitcoinUnit.BTC}
|
||||
</Text>
|
||||
)}
|
||||
{fee <= 0 && (
|
||||
<Text numberOfLines={0} style={styles.feeText}>
|
||||
{invoiceDescription}
|
||||
</Text>
|
||||
)}
|
||||
</BlueCard>
|
||||
<View style={styles.ready}>
|
||||
<LottieView
|
||||
style={styles.lottie}
|
||||
source={require('../../img/bluenice.json')}
|
||||
autoPlay
|
||||
ref={animationRef}
|
||||
loop={false}
|
||||
colorFilters={[
|
||||
{
|
||||
keypath: 'spark',
|
||||
color: colors.success,
|
||||
},
|
||||
{
|
||||
keypath: 'circle',
|
||||
color: colors.success,
|
||||
},
|
||||
{
|
||||
keypath: 'Oval',
|
||||
color: colors.successCheck,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<BlueCard>
|
||||
<BlueButton onPress={pop} title={loc.send.success_done} />
|
||||
</BlueCard>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
};
|
||||
|
||||
Success.navigationOptions = {
|
||||
headerShown: false,
|
||||
gesturesEnabled: false,
|
||||
};
|
||||
|
||||
export default Success;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
paddingTop: 19,
|
||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||
},
|
||||
amout: {
|
||||
alignItems: 'center',
|
||||
|
@ -26,12 +111,10 @@ const styles = StyleSheet.create({
|
|||
paddingBottom: 16,
|
||||
},
|
||||
amountValue: {
|
||||
color: BlueCurrentTheme.colors.alternativeTextColor2,
|
||||
fontSize: 36,
|
||||
fontWeight: '600',
|
||||
},
|
||||
amountUnit: {
|
||||
color: BlueCurrentTheme.colors.alternativeTextColor2,
|
||||
fontSize: 16,
|
||||
marginHorizontal: 4,
|
||||
paddingBottom: 6,
|
||||
|
@ -61,92 +144,3 @@ const styles = StyleSheet.create({
|
|||
height: 400,
|
||||
},
|
||||
});
|
||||
|
||||
export default class Success extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log('send/success constructor');
|
||||
|
||||
this.state = {
|
||||
amount: props.route.params.amount,
|
||||
fee: props.route.params.fee || 0,
|
||||
amountUnit: props.route.params.amountUnit || BitcoinUnit.BTC,
|
||||
invoiceDescription: props.route.params.invoiceDescription || '',
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
console.log('send/success - componentDidMount');
|
||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SafeBlueArea style={styles.root}>
|
||||
<BlueCard style={styles.amout}>
|
||||
<View style={styles.view}>
|
||||
<Text style={styles.amountValue}>{this.state.amount}</Text>
|
||||
<Text style={styles.amountUnit}>{' ' + this.state.amountUnit}</Text>
|
||||
</View>
|
||||
{this.state.fee > 0 && (
|
||||
<Text style={styles.feeText}>
|
||||
{loc.send.create_fee}: {this.state.fee} {BitcoinUnit.BTC}
|
||||
</Text>
|
||||
)}
|
||||
{this.state.fee <= 0 && (
|
||||
<Text numberOfLines={0} style={styles.feeText}>
|
||||
{this.state.invoiceDescription}
|
||||
</Text>
|
||||
)}
|
||||
</BlueCard>
|
||||
<View style={styles.ready}>
|
||||
<LottieView
|
||||
style={styles.lottie}
|
||||
source={require('../../img/bluenice.json')}
|
||||
autoPlay
|
||||
loop={false}
|
||||
colorFilters={[
|
||||
{
|
||||
keypath: 'spark',
|
||||
color: BlueCurrentTheme.colors.success,
|
||||
},
|
||||
{
|
||||
keypath: 'circle',
|
||||
color: BlueCurrentTheme.colors.success,
|
||||
},
|
||||
{
|
||||
keypath: 'Oval',
|
||||
color: BlueCurrentTheme.colors.successCheck,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<BlueCard>
|
||||
<BlueButton onPress={() => this.props.navigation.dangerouslyGetParent().pop()} title={loc.send.success_done} />
|
||||
</BlueCard>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Success.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
goBack: PropTypes.func,
|
||||
navigate: PropTypes.func,
|
||||
dangerouslyGetParent: PropTypes.func,
|
||||
state: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
fee: PropTypes.number,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
route: PropTypes.shape({
|
||||
params: PropTypes.object,
|
||||
}),
|
||||
};
|
||||
|
||||
Success.navigationOptions = {
|
||||
headerShown: false,
|
||||
gesturesEnabled: false,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { ScrollView, Platform, TouchableWithoutFeedback, TouchableOpacity, StyleSheet } from 'react-native';
|
||||
import { BlueLoading, BlueTextHooks, BlueSpacing20, BlueListItemHooks, BlueNavigationStyle, BlueCard } from '../../BlueComponents';
|
||||
import { BlueLoading, BlueTextHooks, BlueSpacing20, BlueListItem, BlueNavigationStyle, BlueCard } from '../../BlueComponents';
|
||||
import { AppStorage } from '../../class';
|
||||
import { useNavigation, useTheme } from '@react-navigation/native';
|
||||
import HandoffSettings from '../../class/handoff';
|
||||
|
@ -58,17 +58,12 @@ const GeneralSettings = () => {
|
|||
<ScrollView style={stylesWithThemeHook.scroll}>
|
||||
{BlueApp.getWallets().length > 1 && (
|
||||
<>
|
||||
<BlueListItemHooks
|
||||
component={TouchableOpacity}
|
||||
onPress={() => navigate('DefaultView')}
|
||||
title={loc.settings.default_title}
|
||||
chevron
|
||||
/>
|
||||
<BlueListItem component={TouchableOpacity} onPress={() => navigate('DefaultView')} title={loc.settings.default_title} chevron />
|
||||
</>
|
||||
)}
|
||||
{Platform.OS === 'ios' ? (
|
||||
<>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
hideChevron
|
||||
title={loc.settings.general_continuity}
|
||||
Component={TouchableWithoutFeedback}
|
||||
|
@ -80,7 +75,7 @@ const GeneralSettings = () => {
|
|||
<BlueSpacing20 />
|
||||
</>
|
||||
) : null}
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
Component={TouchableWithoutFeedback}
|
||||
title={loc.settings.general_adv_mode}
|
||||
switch={{ onValueChange: onAdvancedModeSwitch, value: isAdancedModeEnabled }}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { ScrollView, StyleSheet } from 'react-native';
|
||||
import { SafeBlueArea, BlueListItemHooks, BlueNavigationStyle } from '../../BlueComponents';
|
||||
import { SafeBlueArea, BlueListItem, BlueNavigationStyle } from '../../BlueComponents';
|
||||
import { useNavigation, useTheme } from '@react-navigation/native';
|
||||
import loc from '../../loc';
|
||||
|
||||
|
@ -29,9 +29,9 @@ const NetworkSettings = () => {
|
|||
return (
|
||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
|
||||
<ScrollView>
|
||||
<BlueListItemHooks title={loc.settings.network_electrum} onPress={navigateToElectrumSettings} chevron />
|
||||
<BlueListItemHooks title={loc.settings.lightning_settings} onPress={navigateToLightningSettings} chevron />
|
||||
<BlueListItemHooks title={loc.settings.network_broadcast} onPress={navigateToBroadcast} chevron />
|
||||
<BlueListItem title={loc.settings.network_electrum} onPress={navigateToElectrumSettings} chevron />
|
||||
<BlueListItem title={loc.settings.lightning_settings} onPress={navigateToLightningSettings} chevron />
|
||||
<BlueListItem title={loc.settings.network_broadcast} onPress={navigateToBroadcast} chevron />
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { ScrollView, Linking, Dimensions, Image, View, Text, StyleSheet } from 'react-native';
|
||||
import React from 'react';
|
||||
import { ScrollView, Linking, Image, View, Text, StyleSheet, useWindowDimensions } from 'react-native';
|
||||
import { useNavigation, useTheme } from '@react-navigation/native';
|
||||
import {
|
||||
BlueTextCentered,
|
||||
|
@ -7,20 +7,17 @@ import {
|
|||
BlueButton,
|
||||
SafeBlueArea,
|
||||
BlueCard,
|
||||
BlueListItemHooks,
|
||||
BlueListItem,
|
||||
BlueNavigationStyle,
|
||||
BlueLoadingHook,
|
||||
} from '../../BlueComponents';
|
||||
import { getApplicationName, getVersion, getBundleId, getBuildNumber } from 'react-native-device-info';
|
||||
import Rate, { AndroidMarket } from 'react-native-rate';
|
||||
import loc from '../../loc';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
||||
const About = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { navigate } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { width, height } = useWindowDimensions();
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
|
@ -58,10 +55,6 @@ const About = () => {
|
|||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
const handleOnReleaseNotesPress = () => {
|
||||
navigate('ReleaseNotes');
|
||||
};
|
||||
|
@ -102,9 +95,7 @@ const About = () => {
|
|||
});
|
||||
};
|
||||
|
||||
return isLoading ? (
|
||||
<BlueLoadingHook />
|
||||
) : (
|
||||
return (
|
||||
<SafeBlueArea style={styles.root}>
|
||||
<ScrollView testID="AboutScrollView">
|
||||
<BlueCard>
|
||||
|
@ -115,7 +106,7 @@ const About = () => {
|
|||
<BlueButton onPress={handleOnRatePress} title={loc.settings.about_review + ' ⭐🙏'} />
|
||||
</View>
|
||||
</BlueCard>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
leftIcon={{
|
||||
name: 'twitter',
|
||||
type: 'font-awesome',
|
||||
|
@ -124,7 +115,7 @@ const About = () => {
|
|||
onPress={handleOnTwitterPress}
|
||||
title={loc.settings.about_sm_twitter}
|
||||
/>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
leftIcon={{
|
||||
name: 'telegram',
|
||||
type: 'font-awesome',
|
||||
|
@ -133,7 +124,7 @@ const About = () => {
|
|||
onPress={handleOnTelegramPress}
|
||||
title={loc.settings.about_sm_telegram}
|
||||
/>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
leftIcon={{
|
||||
name: 'github',
|
||||
type: 'font-awesome',
|
||||
|
@ -154,7 +145,7 @@ const About = () => {
|
|||
<BlueTextCentered>Electrum server</BlueTextCentered>
|
||||
</View>
|
||||
</BlueCard>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
leftIcon={{
|
||||
name: 'book',
|
||||
type: 'font-awesome',
|
||||
|
@ -164,7 +155,7 @@ const About = () => {
|
|||
onPress={handleOnReleaseNotesPress}
|
||||
title={loc.settings.about_release_notes}
|
||||
/>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
leftIcon={{
|
||||
name: 'law',
|
||||
type: 'octicon',
|
||||
|
@ -174,7 +165,7 @@ const About = () => {
|
|||
onPress={handleOnLicensingPress}
|
||||
title="MIT License"
|
||||
/>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
leftIcon={{
|
||||
name: 'flask',
|
||||
type: 'font-awesome',
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { FlatList, TouchableOpacity, ActivityIndicator, View, StyleSheet } from 'react-native';
|
||||
import { SafeBlueArea, BlueListItemHooks, BlueTextHooks, BlueCard, BlueNavigationStyle } from '../../BlueComponents';
|
||||
import { SafeBlueArea, BlueListItem, BlueTextHooks, BlueCard, BlueNavigationStyle } from '../../BlueComponents';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { FiatUnit } from '../../models/fiatUnit';
|
||||
import loc from '../../loc';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
|
@ -52,11 +51,11 @@ const Currency = () => {
|
|||
extraData={data}
|
||||
renderItem={({ item }) => {
|
||||
return (
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
disabled={isSavingNewPreferredCurrency}
|
||||
title={`${item.endPointKey} (${item.symbol})`}
|
||||
{...(selectedCurrency.endPointKey === item.endPointKey
|
||||
? { rightIcon: <Icon name="check" type="octaicon" color="#0070FF" /> }
|
||||
? { rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' } }
|
||||
: { hideChevron: true })}
|
||||
Component={TouchableOpacity}
|
||||
onPress={async () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { View, TouchableWithoutFeedback, StyleSheet } from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { SafeBlueArea, BlueCard, BlueNavigationStyle, BlueListItemHooks, BlueTextHooks } from '../../BlueComponents';
|
||||
import { SafeBlueArea, BlueCard, BlueNavigationStyle, BlueListItem, BlueTextHooks } from '../../BlueComponents';
|
||||
import OnAppLaunch from '../../class/on-app-launch';
|
||||
import loc from '../../loc';
|
||||
const BlueApp = require('../../BlueApp');
|
||||
|
@ -57,7 +57,7 @@ const DefaultView = () => {
|
|||
return (
|
||||
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.flex}>
|
||||
<View>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
title={loc.settings.default_wallets}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{
|
||||
|
@ -70,7 +70,7 @@ const DefaultView = () => {
|
|||
<BlueTextHooks>{loc.settings.default_desc}</BlueTextHooks>
|
||||
</BlueCard>
|
||||
{!viewAllWalletsEnabled && (
|
||||
<BlueListItemHooks title={loc.settings.default_info} onPress={selectWallet} rightTitle={defaultWalletLabel} chevron />
|
||||
<BlueListItem title={loc.settings.default_info} onPress={selectWallet} rightTitle={defaultWalletLabel} chevron />
|
||||
)}
|
||||
</View>
|
||||
</SafeBlueArea>
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
SafeBlueArea,
|
||||
BlueSpacing20,
|
||||
BlueCard,
|
||||
BlueListItemHooks,
|
||||
BlueListItem,
|
||||
BlueHeaderDefaultSubHooks,
|
||||
BlueTextHooks,
|
||||
BlueNavigationStyle,
|
||||
|
@ -150,7 +150,7 @@ const EncryptStorage = () => {
|
|||
{biometrics.isDeviceBiometricCapable && (
|
||||
<>
|
||||
<BlueHeaderDefaultSubHooks leftText="biometrics" rightComponent={null} />
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
title={loc.formatString(loc.settings.encrypt_use, { type: biometrics.biometricsType })}
|
||||
Component={TouchableWithoutFeedback}
|
||||
switch={{ value: biometrics.isBiometricsEnabled, onValueChange: onUseBiometricSwitch }}
|
||||
|
@ -162,7 +162,7 @@ const EncryptStorage = () => {
|
|||
</>
|
||||
)}
|
||||
<BlueHeaderDefaultSubHooks leftText={loc.settings.encrypt_tstorage} rightComponent={null} />
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
testID="EncyptedAndPasswordProtected"
|
||||
hideChevron
|
||||
title={loc.settings.encrypt_enc_and_pass}
|
||||
|
@ -170,7 +170,7 @@ const EncryptStorage = () => {
|
|||
switch={{ onValueChange: onEncryptStorageSwitch, value: storageIsEncrypted }}
|
||||
/>
|
||||
{Platform.OS === 'ios' && (
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
hideChevron
|
||||
title={loc.settings.encrypt_del_uninstall}
|
||||
Component={TouchableWithoutFeedback}
|
||||
|
@ -181,7 +181,7 @@ const EncryptStorage = () => {
|
|||
/>
|
||||
)}
|
||||
{storageIsEncrypted && (
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
onPress={navigateToPlausibleDeniability}
|
||||
title={loc.settings.plausible_deniability}
|
||||
chevron
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { FlatList, StyleSheet } from 'react-native';
|
||||
import { SafeBlueArea, BlueListItemHooks, BlueCard, BlueLoadingHook, BlueNavigationStyle, BlueTextHooks } from '../../BlueComponents';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { SafeBlueArea, BlueListItem, BlueCard, BlueLoadingHook, BlueNavigationStyle, BlueTextHooks } from '../../BlueComponents';
|
||||
import { AvailableLanguages } from '../../loc/languages';
|
||||
import loc from '../../loc';
|
||||
|
||||
|
@ -22,7 +21,7 @@ const Language = () => {
|
|||
const renderItem = useCallback(
|
||||
({ item }) => {
|
||||
return (
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
onPress={() => {
|
||||
console.log('setLanguage', item.value);
|
||||
loc.saveLanguage(item.value);
|
||||
|
@ -31,7 +30,7 @@ const Language = () => {
|
|||
title={item.label}
|
||||
{...(language === item.value
|
||||
? {
|
||||
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
|
||||
rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
|
||||
}
|
||||
: { hideChevron: true })}
|
||||
/>
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
BlueLoading,
|
||||
BlueTextHooks,
|
||||
BlueSpacing20,
|
||||
BlueListItemHooks,
|
||||
BlueListItem,
|
||||
BlueNavigationStyle,
|
||||
BlueCard,
|
||||
BlueButton,
|
||||
|
@ -101,7 +101,7 @@ const NotificationSettings = () => {
|
|||
<BlueLoading />
|
||||
) : (
|
||||
<ScrollView style={stylesWithThemeHook.scroll}>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
Component={TouchableWithoutFeedback}
|
||||
title={loc.settings.push_notifications}
|
||||
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled }}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { ScrollView, TouchableOpacity, StyleSheet, StatusBar } from 'react-native';
|
||||
import { BlueListItemHooks, BlueHeaderDefaultSubHooks } from '../../BlueComponents';
|
||||
import { BlueListItem, BlueHeaderDefaultSubHooks } from '../../BlueComponents';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import loc from '../../loc';
|
||||
|
||||
|
@ -17,24 +17,24 @@ const Settings = () => {
|
|||
<ScrollView style={styles.root}>
|
||||
<StatusBar barStyle="default" />
|
||||
<BlueHeaderDefaultSubHooks leftText={loc.settings.header} rightComponent={null} />
|
||||
<BlueListItemHooks title={loc.settings.general} component={TouchableOpacity} onPress={() => navigate('GeneralSettings')} chevron />
|
||||
<BlueListItemHooks title={loc.settings.currency} component={TouchableOpacity} onPress={() => navigate('Currency')} chevron />
|
||||
<BlueListItemHooks title={loc.settings.language} component={TouchableOpacity} onPress={() => navigate('Language')} chevron />
|
||||
<BlueListItemHooks
|
||||
<BlueListItem title={loc.settings.general} component={TouchableOpacity} onPress={() => navigate('GeneralSettings')} chevron />
|
||||
<BlueListItem title={loc.settings.currency} component={TouchableOpacity} onPress={() => navigate('Currency')} chevron />
|
||||
<BlueListItem title={loc.settings.language} component={TouchableOpacity} onPress={() => navigate('Language')} chevron />
|
||||
<BlueListItem
|
||||
title={loc.settings.encrypt_title}
|
||||
onPress={() => navigate('EncryptStorage')}
|
||||
component={TouchableOpacity}
|
||||
testID="SecurityButton"
|
||||
chevron
|
||||
/>
|
||||
<BlueListItemHooks title={loc.settings.network} component={TouchableOpacity} onPress={() => navigate('NetworkSettings')} chevron />
|
||||
<BlueListItemHooks
|
||||
<BlueListItem title={loc.settings.network} component={TouchableOpacity} onPress={() => navigate('NetworkSettings')} chevron />
|
||||
<BlueListItem
|
||||
title={loc.settings.notifications}
|
||||
component={TouchableOpacity}
|
||||
onPress={() => navigate('NotificationSettings')}
|
||||
chevron
|
||||
/>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
title={loc.settings.about}
|
||||
component={TouchableOpacity}
|
||||
onPress={() => navigate('About')}
|
||||
|
|
|
@ -215,7 +215,12 @@ export default class TransactionsStatus extends Component {
|
|||
}
|
||||
|
||||
const tx = new HDSegwitBech32Transaction(null, this.state.tx.hash, this.state.wallet);
|
||||
if ((await tx.isOurTransaction()) && (await tx.getRemoteConfirmationsNum()) === 0 && (await tx.isSequenceReplaceable())) {
|
||||
if (
|
||||
(await tx.isOurTransaction()) &&
|
||||
(await tx.getRemoteConfirmationsNum()) === 0 &&
|
||||
(await tx.isSequenceReplaceable()) &&
|
||||
(await tx.canBumpTx())
|
||||
) {
|
||||
return this.setState({ isRBFBumpFeePossible: buttonStatus.possible });
|
||||
} else {
|
||||
return this.setState({ isRBFBumpFeePossible: buttonStatus.notPossible });
|
||||
|
|
|
@ -16,7 +16,7 @@ import AsyncStorage from '@react-native-community/async-storage';
|
|||
import {
|
||||
BlueTextCenteredHooks,
|
||||
BlueTextHooks,
|
||||
BlueListItemHooks,
|
||||
BlueListItem,
|
||||
LightningButton,
|
||||
BitcoinButton,
|
||||
BlueFormLabel,
|
||||
|
@ -27,7 +27,6 @@ import {
|
|||
} from '../../BlueComponents';
|
||||
import { HDSegwitBech32Wallet, SegwitP2SHWallet, HDSegwitP2SHWallet, LightningCustodianWallet, AppStorage } from '../../class';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import { useTheme, useNavigation } from '@react-navigation/native';
|
||||
import { Chain } from '../../models/bitcoinUnits';
|
||||
import loc from '../../loc';
|
||||
|
@ -93,7 +92,6 @@ const styles = StyleSheet.create({
|
|||
borderRadius: 4,
|
||||
},
|
||||
createButton: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
marginTop: 32,
|
||||
},
|
||||
|
@ -312,36 +310,36 @@ const WalletsAdd = () => {
|
|||
<View>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.advancedText, stylesHook.advancedText]}>{loc.settings.advanced_options}</Text>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
containerStyle={[styles.noPadding, stylesHook.noPadding]}
|
||||
bottomDivider={false}
|
||||
onPress={() => setSelectedIndex(0)}
|
||||
title={HDSegwitBech32Wallet.typeReadable}
|
||||
{...(selectedIndex === 0
|
||||
? {
|
||||
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
|
||||
rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
|
||||
}
|
||||
: { hideChevron: true })}
|
||||
/>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
containerStyle={[styles.noPadding, stylesHook.noPadding]}
|
||||
bottomDivider={false}
|
||||
onPress={() => setSelectedIndex(1)}
|
||||
title={SegwitP2SHWallet.typeReadable}
|
||||
{...(selectedIndex === 1
|
||||
? {
|
||||
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
|
||||
rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
|
||||
}
|
||||
: { hideChevron: true })}
|
||||
/>
|
||||
<BlueListItemHooks
|
||||
<BlueListItem
|
||||
containerStyle={[styles.noPadding, stylesHook.noPadding]}
|
||||
bottomDivider={false}
|
||||
onPress={() => setSelectedIndex(2)}
|
||||
title={HDSegwitP2SHWallet.typeReadable}
|
||||
{...(selectedIndex === 2
|
||||
? {
|
||||
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
|
||||
rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
|
||||
}
|
||||
: { hideChevron: true })}
|
||||
/>
|
||||
|
@ -351,7 +349,7 @@ const WalletsAdd = () => {
|
|||
return (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<Text style={styles.advancedText}>{loc.settings.advanced_options}</Text>
|
||||
<Text style={[styles.advancedText, stylesHook.advancedText]}>{loc.settings.advanced_options}</Text>
|
||||
<BlueSpacing20 />
|
||||
<BlueTextHooks>Connect to your LNDHub</BlueTextHooks>
|
||||
<View style={[styles.lndUri, stylesHook.lndUri]}>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
Linking,
|
||||
StyleSheet,
|
||||
StatusBar,
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueNavigationStyle, BlueText, BlueLoadingHook } from '../../BlueComponents';
|
||||
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
|
||||
|
@ -22,14 +23,18 @@ import { HDLegacyP2PKHWallet } from '../../class/wallets/hd-legacy-p2pkh-wallet'
|
|||
import { HDSegwitP2SHWallet } from '../../class/wallets/hd-segwit-p2sh-wallet';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import { HDSegwitBech32Wallet, SegwitP2SHWallet, LegacyWallet, SegwitBech32Wallet, WatchOnlyWallet } from '../../class';
|
||||
import { HDSegwitBech32Wallet, SegwitP2SHWallet, LegacyWallet, SegwitBech32Wallet, WatchOnlyWallet, MultisigHDWallet } from '../../class';
|
||||
import { ScrollView } from 'react-native-gesture-handler';
|
||||
import loc from '../../loc';
|
||||
import { useTheme, useRoute, useNavigation } from '@react-navigation/native';
|
||||
import RNFS from 'react-native-fs';
|
||||
import Share from 'react-native-share';
|
||||
import { getSystemName } from 'react-native-device-info';
|
||||
const EV = require('../../blue_modules/events');
|
||||
const prompt = require('../../blue_modules/prompt');
|
||||
const BlueApp = require('../../BlueApp');
|
||||
const notifications = require('../../blue_modules/notifications');
|
||||
const isDesktop = getSystemName() === 'Mac OS X';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
|
@ -94,6 +99,7 @@ const styles = StyleSheet.create({
|
|||
const WalletDetails = () => {
|
||||
const { wallet } = useRoute().params;
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [backdoorPressed, setBackdoorPressed] = useState(0);
|
||||
const [walletName, setWalletName] = useState(wallet.getLabel());
|
||||
const [useWithHardwareWallet, setUseWithHardwareWallet] = useState(wallet.useWithHardwareWalletEnabled());
|
||||
const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList());
|
||||
|
@ -148,27 +154,29 @@ const WalletDetails = () => {
|
|||
|
||||
const presentWalletHasBalanceAlert = useCallback(async () => {
|
||||
ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false });
|
||||
const walletBalanceConfirmation = await prompt(
|
||||
loc.wallets.details_del_wb,
|
||||
loc.formatString(loc.wallets.details_del_wb_q, { balance: wallet.getBalance() }),
|
||||
true,
|
||||
'plain-text',
|
||||
);
|
||||
if (Number(walletBalanceConfirmation) === wallet.getBalance()) {
|
||||
setParams({ isLoading: true });
|
||||
setIsLoading(true);
|
||||
notifications.unsubscribe(wallet.getAllExternalAddresses(), [], []);
|
||||
BlueApp.deleteWallet(wallet);
|
||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
||||
await BlueApp.saveToDisk();
|
||||
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
|
||||
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||
popToTop();
|
||||
} else {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
setIsLoading(false);
|
||||
alert(loc.wallets.details_del_wb_err);
|
||||
}
|
||||
try {
|
||||
const walletBalanceConfirmation = await prompt(
|
||||
loc.wallets.details_del_wb,
|
||||
loc.formatString(loc.wallets.details_del_wb_q, { balance: wallet.getBalance() }),
|
||||
true,
|
||||
'plain-text',
|
||||
);
|
||||
if (Number(walletBalanceConfirmation) === wallet.getBalance()) {
|
||||
setParams({ isLoading: true });
|
||||
setIsLoading(true);
|
||||
notifications.unsubscribe(wallet.getAllExternalAddresses(), [], []);
|
||||
BlueApp.deleteWallet(wallet);
|
||||
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
|
||||
await BlueApp.saveToDisk();
|
||||
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
|
||||
EV(EV.enum.WALLETS_COUNT_CHANGED);
|
||||
popToTop();
|
||||
} else {
|
||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||
setIsLoading(false);
|
||||
alert(loc.wallets.details_del_wb_err);
|
||||
}
|
||||
} catch (_) {}
|
||||
}, [popToTop, setParams, wallet]);
|
||||
|
||||
const navigateToWalletExport = () => {
|
||||
|
@ -176,6 +184,11 @@ const WalletDetails = () => {
|
|||
wallet,
|
||||
});
|
||||
};
|
||||
const navigateToMultisigCoordinationSetup = () => {
|
||||
navigate('ExportMultisigCoordinationSetup', {
|
||||
walletId: wallet.getID(),
|
||||
});
|
||||
};
|
||||
const navigateToXPub = () =>
|
||||
navigate('WalletXpub', {
|
||||
secret: wallet.getSecret(),
|
||||
|
@ -204,6 +217,65 @@ const WalletDetails = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const exportInternals = async () => {
|
||||
if (backdoorPressed < 10) return setBackdoorPressed(backdoorPressed + 1);
|
||||
setBackdoorPressed(0);
|
||||
if (wallet.type !== HDSegwitBech32Wallet.type) return;
|
||||
const fileName = 'wallet-externals.json';
|
||||
const contents = JSON.stringify(
|
||||
{
|
||||
_balances_by_external_index: wallet._balances_by_external_index,
|
||||
_balances_by_internal_index: wallet._balances_by_internal_index,
|
||||
_txs_by_external_index: wallet._txs_by_external_index,
|
||||
_txs_by_internal_index: wallet._txs_by_internal_index,
|
||||
_utxo: wallet._utxo,
|
||||
next_free_address_index: wallet.next_free_address_index,
|
||||
next_free_change_address_index: wallet.next_free_change_address_index,
|
||||
internal_addresses_cache: wallet.internal_addresses_cache,
|
||||
external_addresses_cache: wallet.external_addresses_cache,
|
||||
_xpub: wallet._xpub,
|
||||
gap_limit: wallet.gap_limit,
|
||||
label: wallet.label,
|
||||
_lastTxFetch: wallet._lastTxFetch,
|
||||
_lastBalanceFetch: wallet._lastBalanceFetch,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
if (Platform.OS === 'ios') {
|
||||
const filePath = RNFS.TemporaryDirectoryPath + `/${fileName}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
Share.open({
|
||||
url: 'file://' + filePath,
|
||||
saveToFiles: isDesktop,
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
alert(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
RNFS.unlink(filePath);
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
||||
title: loc.send.permission_storage_title,
|
||||
message: loc.send.permission_storage_message,
|
||||
buttonNeutral: loc.send.permission_storage_later,
|
||||
buttonNegative: loc._.cancel,
|
||||
buttonPositive: loc._.ok,
|
||||
});
|
||||
|
||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
||||
console.log('Storage Permission: Granted');
|
||||
const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`;
|
||||
await RNFS.writeFile(filePath, contents);
|
||||
alert(loc.formatString(loc.send.txSaved, { filePath: fileName }));
|
||||
} else {
|
||||
console.log('Storage Permission: Denied');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToBroadcast = () => {
|
||||
navigate('Broadcast');
|
||||
};
|
||||
|
@ -295,6 +367,35 @@ const WalletDetails = () => {
|
|||
<BlueSpacing20 />
|
||||
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_type.toLowerCase()}</Text>
|
||||
<Text style={[styles.textValue, stylesHook.textValue]}>{wallet.typeReadable}</Text>
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>multisig</Text>
|
||||
<BlueText>
|
||||
{wallet.getM()} of {wallet.getN()}{' '}
|
||||
{wallet.isNativeSegwit()
|
||||
? 'native segwit (p2wsh)'
|
||||
: wallet.isWrappedSegwit()
|
||||
? 'wrapped segwit (p2sh-p2wsh)'
|
||||
: 'legacy (p2sh)'}
|
||||
</BlueText>
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && wallet.howManySignaturesCanWeMake() > 0 && (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.multisig.how_many_signatures_can_bluewallet_make}</Text>
|
||||
<BlueText>{wallet.howManySignaturesCanWeMake()}</BlueText>
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && !!wallet.getDerivationPath() && (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>derivation path</Text>
|
||||
<BlueText>{wallet.getDerivationPath()}</BlueText>
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.type === LightningCustodianWallet.type && (
|
||||
<>
|
||||
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_connected_to.toLowerCase()}</Text>
|
||||
|
@ -302,7 +403,9 @@ const WalletDetails = () => {
|
|||
</>
|
||||
)}
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.transactions.list_title.toLowerCase()}</Text>
|
||||
<Text onPress={exportInternals} style={[styles.textLabel2, stylesHook.textLabel2]}>
|
||||
{loc.transactions.list_title.toLowerCase()}
|
||||
</Text>
|
||||
<View style={styles.hardware}>
|
||||
<BlueText>{loc.wallets.details_display}</BlueText>
|
||||
<Switch value={hideTransactionsInWalletsList} onValueChange={setHideTransactionsInWalletsList} />
|
||||
|
@ -333,6 +436,15 @@ const WalletDetails = () => {
|
|||
|
||||
<BlueSpacing20 />
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<SecondButton
|
||||
onPress={navigateToMultisigCoordinationSetup}
|
||||
title={loc.multisig.export_coordination_setup.replace(/^\w/, c => c.toUpperCase())}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(wallet.type === HDLegacyBreadwalletWallet.type ||
|
||||
wallet.type === HDLegacyP2PKHWallet.type ||
|
||||
wallet.type === HDSegwitBech32Wallet.type ||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { StatusBar, View, TouchableOpacity, InteractionManager, StyleSheet, Alert, useWindowDimensions } from 'react-native';
|
||||
import React, { useRef } from 'react';
|
||||
import { StatusBar, View, TouchableOpacity, StyleSheet, Alert, useWindowDimensions } from 'react-native';
|
||||
import { DrawerContentScrollView } from '@react-navigation/drawer';
|
||||
import { WalletsCarousel, BlueNavigationStyle, BlueHeaderDefaultMainHooks } from '../../BlueComponents';
|
||||
import { WalletsCarousel, BlueNavigationStyle, BlueHeaderDefaultMain } from '../../BlueComponents';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -14,11 +14,11 @@ import { useTheme, useRoute } from '@react-navigation/native';
|
|||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
const EV = require('../../blue_modules/events');
|
||||
const BlueApp: AppStorage = require('../../BlueApp');
|
||||
const BlueElectrum = require('../../blue_modules/BlueElectrum');
|
||||
|
||||
const DrawerList = props => {
|
||||
console.log('drawerList rendering...');
|
||||
const walletsCarousel = useRef();
|
||||
const [wallets, setWallets] = useState(BlueApp.getWallets().concat(false));
|
||||
const wallets = useRoute().params?.wallets || BlueApp.getWallets() || [];
|
||||
const height = useWindowDimensions().height;
|
||||
const { colors } = useTheme();
|
||||
const { selectedWallet } = useRoute().params || '';
|
||||
|
@ -27,83 +27,6 @@ const DrawerList = props => {
|
|||
backgroundColor: colors.brandingColor,
|
||||
},
|
||||
});
|
||||
let lastSnappedTo = 0;
|
||||
|
||||
const refreshTransactions = () => {
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
let noErr = true;
|
||||
try {
|
||||
// await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
const balanceStart = +new Date();
|
||||
await BlueApp.fetchWalletBalances(lastSnappedTo || 0);
|
||||
const balanceEnd = +new Date();
|
||||
console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
const start = +new Date();
|
||||
await BlueApp.fetchWalletTransactions(lastSnappedTo || 0);
|
||||
const end = +new Date();
|
||||
console.log('fetch tx took', (end - start) / 1000, 'sec');
|
||||
} catch (err) {
|
||||
noErr = false;
|
||||
console.warn(err);
|
||||
}
|
||||
if (noErr) await BlueApp.saveToDisk(); // caching
|
||||
|
||||
redrawScreen();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED);
|
||||
console.log('drawerList wallets changed');
|
||||
}, [wallets]);
|
||||
|
||||
const redrawScreen = (scrollToEnd = false) => {
|
||||
console.log('drawerList redrawScreen()');
|
||||
|
||||
const newWallets = BlueApp.getWallets().concat(false);
|
||||
if (scrollToEnd) {
|
||||
scrollToEnd = newWallets.length > wallets.length;
|
||||
}
|
||||
|
||||
setWallets(newWallets);
|
||||
if (scrollToEnd) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
walletsCarousel.current?.snapToItem(wallets.length - 2);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// here, when we receive REMOTE_TRANSACTIONS_COUNT_CHANGED we fetch TXs and balance for current wallet.
|
||||
// placing event subscription here so it gets exclusively re-subscribed more often. otherwise we would
|
||||
// have to unsubscribe on unmount and resubscribe again on mount.
|
||||
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED, refreshTransactions, true);
|
||||
|
||||
EV(EV.enum.WALLETS_COUNT_CHANGED, () => redrawScreen(true));
|
||||
|
||||
console.log('drawerList useEffect');
|
||||
// the idea is that upon wallet launch we will refresh
|
||||
// all balances and all transactions here:
|
||||
redrawScreen();
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
try {
|
||||
await BlueElectrum.waitTillConnected();
|
||||
const balanceStart = +new Date();
|
||||
await BlueApp.fetchWalletBalances();
|
||||
const balanceEnd = +new Date();
|
||||
console.log('fetch all wallet balances took', (balanceEnd - balanceStart) / 1000, 'sec');
|
||||
const start = +new Date();
|
||||
await BlueApp.fetchWalletTransactions();
|
||||
const end = +new Date();
|
||||
console.log('fetch all wallet txs took', (end - start) / 1000, 'sec');
|
||||
redrawScreen();
|
||||
await BlueApp.saveToDisk();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleClick = index => {
|
||||
console.log('click', index);
|
||||
|
@ -156,82 +79,13 @@ const DrawerList = props => {
|
|||
}
|
||||
};
|
||||
|
||||
const onSnapToItem = index => {
|
||||
console.log('onSnapToItem', index);
|
||||
lastSnappedTo = index;
|
||||
if (index < BlueApp.getWallets().length) {
|
||||
// not the last
|
||||
}
|
||||
|
||||
if (wallets[index].type === PlaceholderWallet.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
// now, lets try to fetch balance and txs for this wallet in case it has changed
|
||||
lazyRefreshWallet(index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decides whether wallet with such index shoud be refreshed,
|
||||
* refreshes if yes and redraws the screen
|
||||
* @param index {Integer} Index of the wallet.
|
||||
* @return {Promise.<void>}
|
||||
*/
|
||||
const lazyRefreshWallet = async index => {
|
||||
/** @type {Array.<AbstractWallet>} wallets */
|
||||
const wallets = BlueApp.getWallets();
|
||||
if (!wallets[index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldBalance = wallets[index].getBalance();
|
||||
let noErr = true;
|
||||
let didRefresh = false;
|
||||
|
||||
try {
|
||||
if (wallets[index] && wallets[index].type !== PlaceholderWallet.type && wallets[index].timeToRefreshBalance()) {
|
||||
console.log('snapped to, and now its time to refresh wallet #', index);
|
||||
await wallets[index].fetchBalance();
|
||||
if (oldBalance !== wallets[index].getBalance() || wallets[index].getUnconfirmedBalance() !== 0) {
|
||||
console.log('balance changed, thus txs too');
|
||||
// balance changed, thus txs too
|
||||
await wallets[index].fetchTransactions();
|
||||
redrawScreen();
|
||||
didRefresh = true;
|
||||
} else if (wallets[index].timeToRefreshTransaction()) {
|
||||
console.log(wallets[index].getLabel(), 'thinks its time to refresh TXs');
|
||||
await wallets[index].fetchTransactions();
|
||||
if (wallets[index].fetchPendingTransactions) {
|
||||
await wallets[index].fetchPendingTransactions();
|
||||
}
|
||||
if (wallets[index].fetchUserInvoices) {
|
||||
await wallets[index].fetchUserInvoices();
|
||||
await wallets[index].fetchBalance(); // chances are, paid ln invoice was processed during `fetchUserInvoices()` call and altered user's balance, so its worth fetching balance again
|
||||
}
|
||||
redrawScreen();
|
||||
didRefresh = true;
|
||||
} else {
|
||||
console.log('balance not changed');
|
||||
}
|
||||
}
|
||||
} catch (Err) {
|
||||
noErr = false;
|
||||
console.warn(Err);
|
||||
}
|
||||
|
||||
if (noErr && didRefresh) {
|
||||
await BlueApp.saveToDisk(); // caching
|
||||
}
|
||||
};
|
||||
|
||||
const renderWalletsCarousel = () => {
|
||||
return (
|
||||
<WalletsCarousel
|
||||
removeClippedSubviews={false}
|
||||
data={wallets}
|
||||
data={wallets.concat(false)}
|
||||
onPress={handleClick}
|
||||
handleLongPress={handleLongPress}
|
||||
onSnapToItem={onSnapToItem}
|
||||
ref={walletsCarousel}
|
||||
testID="WalletsList"
|
||||
vertical
|
||||
|
@ -244,19 +98,16 @@ const DrawerList = props => {
|
|||
);
|
||||
};
|
||||
|
||||
const onNewWalletPress = () => {
|
||||
return !BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type) ? props.navigation.navigate('AddWalletRoot') : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerContentScrollView {...props} scrollEnabled={false}>
|
||||
<View styles={[styles.root, stylesHook.root]}>
|
||||
<StatusBar barStyle="default" />
|
||||
<SafeAreaView style={styles.root}>
|
||||
<BlueHeaderDefaultMainHooks
|
||||
leftText={loc.wallets.list_title}
|
||||
onNewWalletPress={
|
||||
!BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type)
|
||||
? () => props.navigation.navigate('AddWalletRoot')
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<BlueHeaderDefaultMain leftText={loc.wallets.list_title} onNewWalletPress={onNewWalletPress} isDrawerList />
|
||||
</SafeAreaView>
|
||||
{renderWalletsCarousel()}
|
||||
</View>
|
||||
|
|
128
screen/wallets/exportMultisigCoordinationSetup.js
Normal file
128
screen/wallets/exportMultisigCoordinationSetup.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { ActivityIndicator, InteractionManager, ScrollView, StatusBar, StyleSheet, useWindowDimensions, View } from 'react-native';
|
||||
import QRCode from 'react-native-qrcode-svg';
|
||||
import { BlueNavigationStyle, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
|
||||
import Privacy from '../../Privacy';
|
||||
import Biometric from '../../class/biometrics';
|
||||
import loc from '../../loc';
|
||||
import { encodeUR } from '../../blue_modules/bc-ur/dist';
|
||||
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
|
||||
import { SquareButton } from '../../components/SquareButton';
|
||||
|
||||
const BlueApp = require('../../BlueApp');
|
||||
const fs = require('../../blue_modules/fs');
|
||||
|
||||
const ExportMultisigCoordinationSetup = () => {
|
||||
const walletId = useRoute().params.walletId;
|
||||
const wallet = BlueApp.getWallets().find(w => w.getID() === walletId);
|
||||
const qrCodeContents = encodeUR(Buffer.from(wallet.getXpub(), 'ascii').toString('hex'), 77777)[0];
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { goBack } = useNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { width, height } = useWindowDimensions();
|
||||
const stylesHook = {
|
||||
...styles,
|
||||
loading: {
|
||||
...styles.loading,
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
root: {
|
||||
...styles.root,
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
type: { ...styles.type, color: colors.foregroundColor },
|
||||
secret: { ...styles.secret, color: colors.foregroundColor },
|
||||
};
|
||||
|
||||
const exportTxtFile = async () => {
|
||||
await fs.writeFileAndExport(wallet.getLabel() + '.txt', wallet.getXpub());
|
||||
};
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
Privacy.enableBlur();
|
||||
const task = InteractionManager.runAfterInteractions(async () => {
|
||||
if (wallet) {
|
||||
const isBiometricsEnabled = await Biometric.isBiometricUseCapableAndEnabled();
|
||||
|
||||
if (isBiometricsEnabled) {
|
||||
if (!(await Biometric.unlockWithBiometrics())) {
|
||||
return goBack();
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
task.cancel();
|
||||
Privacy.disableBlur();
|
||||
};
|
||||
}, [goBack, wallet]),
|
||||
);
|
||||
|
||||
return isLoading ? (
|
||||
<View style={stylesHook.loading}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : (
|
||||
<SafeBlueArea style={stylesHook.root}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
<ScrollView contentContainerStyle={styles.scrollViewContent}>
|
||||
<View>
|
||||
<BlueText style={stylesHook.type}>{wallet.getLabel()}</BlueText>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<View style={styles.activeQrcode}>
|
||||
<QRCode
|
||||
value={qrCodeContents}
|
||||
size={height > width ? width - 40 : width / 2}
|
||||
logoSize={70}
|
||||
color="#000000"
|
||||
logoBackgroundColor={colors.brandingColor}
|
||||
backgroundColor="#FFFFFF"
|
||||
ecl="H"
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<SquareButton backgroundColor="#EEF0F4" onPress={exportTxtFile} title={loc.multisig.share} />
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<BlueText style={stylesHook.secret}>{wallet.getXpub()}</BlueText>
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
},
|
||||
root: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollViewContent: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexGrow: 1,
|
||||
},
|
||||
activeQrcode: { borderWidth: 6, borderRadius: 8, borderColor: '#FFFFFF' },
|
||||
type: {
|
||||
fontSize: 17,
|
||||
fontWeight: '700',
|
||||
},
|
||||
secret: {
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
},
|
||||
});
|
||||
|
||||
ExportMultisigCoordinationSetup.navigationOptions = ({ navigation }) => ({
|
||||
...BlueNavigationStyle(navigation, true),
|
||||
title: loc.multisig.export_coordination_setup,
|
||||
headerLeft: null,
|
||||
});
|
||||
|
||||
export default ExportMultisigCoordinationSetup;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue