Merge branch 'master' into ops

This commit is contained in:
marcosrdz 2020-10-08 18:38:29 -04:00
commit aa8234b1d6
118 changed files with 14305 additions and 6735 deletions

View file

@ -71,5 +71,5 @@ untyped-import
untyped-type-import untyped-type-import
[version] [version]
^0.113.0 ^0.122.0

View file

@ -89,15 +89,3 @@ script:
- npm run e2e:release-test || npm run e2e:release-test - npm run e2e:release-test || npm run e2e:release-test
after_failure: ./tests/e2e/upload-artifacts.sh 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/

View file

@ -1,33 +1,35 @@
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */ /* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
/* global alert */ /* global alert */
import React, { Component, useState } from 'react'; import React, { Component, useState, useMemo, useCallback } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';
import PropTypes from 'prop-types'; 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 { import {
ActivityIndicator,
Alert,
Animated,
Dimensions,
FlatList,
Image,
InputAccessoryView,
Keyboard,
KeyboardAvoidingView,
PixelRatio,
Platform,
PlatformColor,
SafeAreaView,
StyleSheet,
Switch,
TextInput,
TouchableOpacity, TouchableOpacity,
TouchableWithoutFeedback, TouchableWithoutFeedback,
Animated,
Alert,
ActivityIndicator,
View,
KeyboardAvoidingView,
UIManager, UIManager,
StyleSheet, useWindowDimensions,
Dimensions, View,
Image,
Keyboard,
SafeAreaView,
InputAccessoryView,
Platform,
FlatList,
TextInput,
PixelRatio,
} from 'react-native'; } from 'react-native';
import Clipboard from '@react-native-community/clipboard'; import Clipboard from '@react-native-community/clipboard';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import ActionSheet from './screen/ActionSheet'; import ActionSheet from './screen/ActionSheet';
import { LightningCustodianWallet, PlaceholderWallet } from './class'; import { LightningCustodianWallet, MultisigHDWallet, PlaceholderWallet } from './class';
import Carousel from 'react-native-snap-carousel'; import Carousel from 'react-native-snap-carousel';
import { BitcoinUnit } from './models/bitcoinUnits'; import { BitcoinUnit } from './models/bitcoinUnits';
import * as NavigationService from './NavigationService'; import * as NavigationService from './NavigationService';
@ -60,57 +62,25 @@ if (aspectRatio > 1.6) {
} else { } else {
isIpad = true; isIpad = true;
} }
// eslint-disable-next-line no-unused-expressions
Platform.OS === 'android' ? (ActivityIndicator.defaultProps.color = PlatformColor('?attr/colorControlActivated')) : null;
export class BlueButton extends Component { export const BlueButton = props => {
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 => {
const { colors } = useTheme(); const { colors } = useTheme();
const { width } = useWindowDimensions();
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor; let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor;
let fontColor = colors.buttonTextColor; let fontColor = colors.buttonTextColor;
if (props.disabled === true) { if (props.disabled === true) {
backgroundColor = colors.buttonDisabledBackgroundColor; backgroundColor = colors.buttonDisabledBackgroundColor;
fontColor = colors.buttonDisabledTextColor; fontColor = colors.buttonDisabledTextColor;
} }
let buttonWidth = props.width ? props.width : width / 1.5; let buttonWidth = props.width ? props.width : width / 1.5;
if ('noMinWidth' in props) { if ('noMinWidth' in props) {
buttonWidth = 0; buttonWidth = 0;
} }
return ( return (
<TouchableOpacity <TouchableOpacity
style={{ style={{
@ -136,17 +106,13 @@ export const BlueButtonHook = props => {
); );
}; };
export class SecondButton extends Component { export const BlueButtonHook = props => {
render() { const { colors } = useTheme();
let backgroundColor = this.props.backgroundColor ? this.props.backgroundColor : BlueCurrentTheme.colors.buttonBlueBackgroundColor; let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.mainColor;
let fontColor = BlueCurrentTheme.colors.buttonTextColor; let fontColor = colors.buttonTextColor;
if (this.props.disabled === true) { if (props.disabled === true) {
backgroundColor = BlueCurrentTheme.colors.buttonDisabledBackgroundColor; backgroundColor = colors.buttonDisabledBackgroundColor;
fontColor = BlueCurrentTheme.colors.buttonDisabledTextColor; fontColor = colors.buttonDisabledTextColor;
}
let buttonWidth = this.props.width ? this.props.width : width / 1.5;
if ('noMinWidth' in this.props) {
buttonWidth = 0;
} }
return ( return (
<TouchableOpacity <TouchableOpacity
@ -159,7 +125,42 @@ export class SecondButton extends Component {
height: 45, height: 45,
maxHeight: 45, maxHeight: 45,
borderRadius: 25, borderRadius: 25,
minWidth: buttonWidth, 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;
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,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}} }}
@ -190,7 +191,7 @@ export const BitcoinButton = props => {
flex: 1, 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> <Text style={{ color: colors.hdborderColor, fontWeight: 'bold' }}>{loc.wallets.add_bitcoin}</Text>
</View> </View>
<Image <Image
@ -218,7 +219,7 @@ export const LightningButton = props => {
flex: 1, 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> <Text style={{ color: colors.lnborderColor, fontWeight: 'bold' }}>{loc.wallets.add_lightning}</Text>
</View> </View>
<Image <Image
@ -339,9 +340,16 @@ export class BlueWalletNavigationHeader extends Component {
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }} style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
> >
<Image <Image
source={ source={(() => {
(LightningCustodianWallet.type === this.state.wallet.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png') 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={{ style={{
width: 99, width: 99,
height: 94, height: 94,
@ -406,9 +414,9 @@ export class BlueWalletNavigationHeader extends Component {
marginBottom: 10, marginBottom: 10,
backgroundColor: 'rgba(255,255,255,0.2)', backgroundColor: 'rgba(255,255,255,0.2)',
borderRadius: 9, borderRadius: 9,
minWidth: 119,
minHeight: 39, minHeight: 39,
width: 119, alignSelf: 'flex-start',
paddingHorizontal: 12,
height: 39, height: 39,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
@ -699,60 +707,56 @@ export const BlueTextCenteredHooks = props => {
return <Text {...props} style={{ color: colors.foregroundColor, textAlign: 'center' }} />; return <Text {...props} style={{ color: colors.foregroundColor, textAlign: 'center' }} />;
}; };
export const BlueListItem = React.memo(props => ( 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 => {
const { colors } = useTheme(); const { colors } = useTheme();
return ( return (
<ListItem <ListItem
testID={props.testID} containerStyle={props.containerStyle ?? { backgroundColor: 'transparent' }}
Component={props.Component ?? TouchableOpacity}
bottomDivider bottomDivider
containerStyle={{ testID={props.testID}
backgroundColor: 'transparent', onPress={props.onPress}
borderBottomColor: colors.lightBorder, >
paddingTop: 16, {props.leftAvatar && <Avatar>{props.leftAvatar}</Avatar>}
paddingBottom: 16, {props.leftIcon && <Avatar icon={props.leftIcon} />}
}} <ListItem.Content>
titleStyle={{ <ListItem.Title
style={{
color: props.disabled ? colors.buttonDisabledTextColor : colors.foregroundColor, color: props.disabled ? colors.buttonDisabledTextColor : colors.foregroundColor,
fontSize: 16, fontSize: 16,
fontWeight: '500', fontWeight: '500',
}} }}
rightTitleStyle={{ flexWrap: 'wrap', color: colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }} numberOfLines={0}
subtitleStyle={{ flexWrap: 'wrap', color: colors.alternativeTextColor, fontWeight: '400', fontSize: 14 }} >
subtitleNumberOfLines={1} {props.title}
titleNumberOfLines={0} </ListItem.Title>
Component={TouchableOpacity} {props.subtitle && (
{...props} <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 => { export const BlueFormLabel = props => {
const { colors } = useTheme(); 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 { 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 { colors } = useTheme();
const { isDrawerList } = props;
return ( return (
<Header <Header
{...props}
leftComponent={{ leftComponent={{
text: props.leftText, text: props.leftText,
style: { style: {
fontWeight: 'bold', fontWeight: 'bold',
fontSize: 34, fontSize: 34,
color: colors.foregroundColor, color: colors.foregroundColor,
paddingHorizontal: 4,
}, },
}} }}
leftContainerStyle={{ placement="left"
minWidth: '70%', containerStyle={{
height: 80, borderTopColor: isDrawerList ? colors.elevated : colors.background,
borderBottomColor: isDrawerList ? colors.elevated : colors.background,
maxHeight: 44,
height: 44,
paddingTop: 0,
marginBottom: 8,
}} }}
bottomDivider={false} bottomDivider={false}
topDivider={false} topDivider={false}
containerStyle={{ backgroundColor={isDrawerList ? colors.elevated : colors.background}
height: 44, rightComponent={<BluePlusIcon onPress={props.onNewWalletPress} Component={TouchableOpacity} />}
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>
)
}
/> />
); );
}; };
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 { export class BlueSpacing extends Component {
render() { render() {
return <View {...this.props} style={{ height: 60 }} />; return <View {...this.props} style={{ height: 60 }} />;
@ -1268,23 +1214,12 @@ export const BluePlusIcon = props => {
}, },
}); });
return ( return (
<View {...props} style={stylesBlueIcon.container}> <Avatar
<View style={stylesBlueIcon.box1}> rounded
<View style={[stylesBlueIcon.ball, stylesBlueIconHooks.ball]}> containerStyle={[stylesBlueIcon.ball, stylesBlueIconHooks.ball]}
<Ionicons icon={{ name: 'add', size: 22, type: 'ionicons', color: colors.foregroundColor }}
{...props} {...props}
name="ios-add"
size={26}
style={{
color: colors.foregroundColor,
backgroundColor: 'transparent',
left: 8,
top: 1,
}}
/> />
</View>
</View>
</View>
); );
}; };
@ -1632,7 +1567,7 @@ export const NewWalletPanel = props => {
minHeight: Platform.OS === 'ios' ? 164 : 181, minHeight: Platform.OS === 'ios' ? 164 : 181,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'flex-start', alignItems: 'flex-start',
backgroundColor: WalletGradient.createWallet, backgroundColor: WalletGradient.createWallet(),
}} }}
> >
<Text <Text
@ -1673,15 +1608,27 @@ export const NewWalletPanel = props => {
export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => { export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1); const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme(); const { colors } = useTheme();
const containerStyle = useMemo(
() => ({
backgroundColor: 'transparent',
borderBottomColor: colors.lightBorder,
paddingTop: 16,
paddingBottom: 16,
}),
[colors.lightBorder],
);
const txMemo = () => { const title = useMemo(() => transactionTimeToReadable(item.received), [item.received]);
if (BlueApp.tx_metadata[item.hash] && BlueApp.tx_metadata[item.hash].memo) { const txMemo = BlueApp.tx_metadata[item.hash]?.memo ?? '';
return BlueApp.tx_metadata[item.hash].memo; const subtitle = useMemo(() => {
} let sub = item.confirmations < 7 ? loc.formatString(loc.transactions.list_conf, { number: item.confirmations }) : '';
return ''; 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 (item.type === 'user_invoice' || item.type === 'payment_request') {
if (isNaN(item.value)) { if (isNaN(item.value)) {
item.value = '0'; item.value = '0';
@ -1702,9 +1649,9 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
} else { } else {
return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString(); return formatBalanceWithoutSuffix(item.value && item.value, itemPriceUnit, true).toString();
} }
}; }, [item, itemPriceUnit]);
const rowTitleStyle = () => { const rowTitleStyle = useMemo(() => {
let color = colors.successColor; let color = colors.successColor;
if (item.type === 'user_invoice' || item.type === 'payment_request') { if (item.type === 'user_invoice' || item.type === 'payment_request') {
@ -1726,15 +1673,15 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
} }
return { return {
fontWeight: '600', color,
fontSize: 14, fontSize: 14,
color: color, fontWeight: '600',
textAlign: 'right', textAlign: 'right',
width: 96, width: 96,
}; };
}; }, [item, colors.foregroundColor, colors.successColor]);
const avatar = () => { const avatar = useMemo(() => {
// is it lightning refill tx? // is it lightning refill tx?
if (item.category === 'receive' && item.confirmations < 3) { if (item.category === 'receive' && item.confirmations < 3) {
return ( return (
@ -1800,13 +1747,9 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco
</View> </View>
); );
} }
}; }, [item]);
const subtitle = () => { const onPress = useCallback(async () => {
return (item.confirmations < 7 ? loc.transactions.list_conf + ': ' + item.confirmations + ' ' : '') + txMemo() + (item.memo || '');
};
const onPress = async () => {
if (item.hash) { if (item.hash) {
NavigationService.navigate('TransactionStatus', { hash: item.hash }); NavigationService.navigate('TransactionStatus', { hash: item.hash });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') { } 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', { NavigationService.navigate('ScanLndInvoiceRoot', {
screen: 'LnurlPaySuccess', screen: 'LnurlPaySuccess',
params: { params: {
paymentHash: paymentHash, paymentHash,
justPaid: false, justPaid: false,
fromWalletID: lightningWallet[0].getID(), 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) { if (subtitleNumberOfLines === 1) {
setSubtitleNumberOfLines(0); setSubtitleNumberOfLines(0);
} }
}; }, [subtitleNumberOfLines]);
const subtitleProps = useMemo(() => ({ numberOfLines: subtitleNumberOfLines }), [subtitleNumberOfLines]);
return ( return (
<View style={{ marginHorizontal: 4 }}> <View style={{ marginHorizontal: 4 }}>
<BlueListItem <BlueListItem
leftAvatar={avatar()} leftAvatar={avatar}
title={transactionTimeToReadable(item.received)} title={title}
titleNumberOfLines={subtitleNumberOfLines} titleNumberOfLines={subtitleNumberOfLines}
subtitle={subtitle()} subtitle={subtitle}
subtitleProps={{ numberOfLines: subtitleNumberOfLines }} subtitleProps={subtitleProps}
onPress={onPress} onPress={onPress}
onLongPress={onLongPress} onLongPress={onLongPress}
chevron={false} chevron={false}
Component={TouchableOpacity} Component={TouchableOpacity}
rightTitle={rowTitle()} rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle()} rightTitleStyle={rowTitleStyle}
containerStyle={containerStyle}
/> />
</View> </View>
); );
@ -2005,7 +1951,16 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW
}} }}
> >
<Image <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={{ style={{
width: 99, width: 99,
height: 94, height: 94,
@ -2093,7 +2048,8 @@ export class WalletsCarousel extends Component {
}; };
snapToItem = item => { snapToItem = item => {
this.walletsCarousel.current.snapToItem(item); // eslint-disable-next-line no-unused-expressions
this.walletsCarousel?.current?.snapToItem(item);
}; };
onLayout = () => { onLayout = () => {
@ -2250,6 +2206,7 @@ export class BlueAddressInput extends Component {
{...this.props} {...this.props}
/> />
<TouchableOpacity <TouchableOpacity
testID="BlueAddressInputScanQrButton"
disabled={this.props.isLoading} disabled={this.props.isLoading}
onPress={() => { onPress={() => {
Keyboard.dismiss(); Keyboard.dismiss();
@ -2745,6 +2702,7 @@ const tabsStyles = StyleSheet.create({
borderBottomWidth: 1, borderBottomWidth: 1,
}, },
tabRoot: { tabRoot: {
flex: 1,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
borderColor: 'white', borderColor: 'white',
@ -2753,7 +2711,7 @@ const tabsStyles = StyleSheet.create({
}); });
export const BlueTabs = ({ active, onSwitch, tabs }) => ( export const BlueTabs = ({ active, onSwitch, tabs }) => (
<View style={tabsStyles.root}> <View style={[tabsStyles.root, isIpad && { marginBottom: 30 }]}>
{tabs.map((Tab, i) => ( {tabs.map((Tab, i) => (
<TouchableOpacity <TouchableOpacity
key={i} key={i}
@ -2764,7 +2722,6 @@ export const BlueTabs = ({ active, onSwitch, tabs }) => (
borderColor: BlueCurrentTheme.colors.buttonAlternativeTextColor, borderColor: BlueCurrentTheme.colors.buttonAlternativeTextColor,
borderBottomWidth: 2, borderBottomWidth: 2,
}, },
{ width: width / tabs.length },
]} ]}
> >
<Tab active={active === i} /> <Tab active={active === i} />

View file

@ -27,6 +27,7 @@ import PleaseBackupLNDHub from './screen/wallets/pleaseBackupLNDHub';
import ImportWallet from './screen/wallets/import'; import ImportWallet from './screen/wallets/import';
import WalletDetails from './screen/wallets/details'; import WalletDetails from './screen/wallets/details';
import WalletExport from './screen/wallets/export'; import WalletExport from './screen/wallets/export';
import ExportMultisigCoordinationSetup from './screen/wallets/exportMultisigCoordinationSetup';
import WalletXpub from './screen/wallets/xpub'; import WalletXpub from './screen/wallets/xpub';
import BuyBitcoin from './screen/wallets/buyBitcoin'; import BuyBitcoin from './screen/wallets/buyBitcoin';
import HodlHodl from './screen/wallets/hodlHodl'; import HodlHodl from './screen/wallets/hodlHodl';
@ -53,6 +54,7 @@ import ScanQRCode from './screen/send/ScanQRCode';
import SendCreate from './screen/send/create'; import SendCreate from './screen/send/create';
import Confirm from './screen/send/confirm'; import Confirm from './screen/send/confirm';
import PsbtWithHardwareWallet from './screen/send/psbtWithHardwareWallet'; import PsbtWithHardwareWallet from './screen/send/psbtWithHardwareWallet';
import PsbtMultisig from './screen/send/psbtMultisig';
import Success from './screen/send/success'; import Success from './screen/send/success';
import Broadcast from './screen/send/broadcast'; import Broadcast from './screen/send/broadcast';
@ -79,7 +81,11 @@ const defaultScreenOptions =
...TransitionPresets.ModalPresentationIOS, ...TransitionPresets.ModalPresentationIOS,
gestureResponseDistance: { vertical: Dimensions.get('window').height, horizontal: 50 }, gestureResponseDistance: { vertical: Dimensions.get('window').height, horizontal: 50 },
}) })
: undefined; : {
gestureEnabled: true,
cardOverlayEnabled: true,
...TransitionPresets.ScaleFromCenterAndroid,
};
const defaultStackScreenOptions = const defaultStackScreenOptions =
Platform.OS === 'ios' Platform.OS === 'ios'
? { ? {
@ -88,11 +94,15 @@ const defaultStackScreenOptions =
cardStyle: { backgroundColor: '#FFFFFF' }, cardStyle: { backgroundColor: '#FFFFFF' },
headerStatusBarHeight: 10, headerStatusBarHeight: 10,
} }
: undefined; : {
gestureEnabled: true,
cardOverlayEnabled: true,
...TransitionPresets.ScaleFromCenterAndroid,
};
const WalletsStack = createStackNavigator(); const WalletsStack = createStackNavigator();
const WalletsRoot = () => ( const WalletsRoot = () => (
<WalletsStack.Navigator> <WalletsStack.Navigator {...(Platform.OS === 'android' ? { screenOptions: defaultScreenOptions } : null)}>
<WalletsStack.Screen name="WalletsList" component={WalletsList} options={WalletsList.navigationOptions} /> <WalletsStack.Screen name="WalletsList" component={WalletsList} options={WalletsList.navigationOptions} />
<WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions} /> <WalletsStack.Screen name="WalletTransactions" component={WalletTransactions} options={WalletTransactions.navigationOptions} />
<WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions} /> <WalletsStack.Screen name="WalletDetails" component={WalletDetails} options={WalletDetails.navigationOptions} />
@ -167,17 +177,8 @@ const SendDetailsRoot = () => (
component={PsbtWithHardwareWallet} component={PsbtWithHardwareWallet}
options={PsbtWithHardwareWallet.navigationOptions} options={PsbtWithHardwareWallet.navigationOptions}
/> />
<SendDetailsStack.Screen <SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions} />
name="CreateTransaction" <SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions} />
component={SendCreate}
options={{
headerStyle: {
backgroundColor: '#FFFFFF',
borderBottomWidth: 0,
},
headerTintColor: '#0c2550',
}}
/>
<SendDetailsStack.Screen name="Success" component={Success} options={Success.navigationOptions} /> <SendDetailsStack.Screen name="Success" component={Success} options={Success.navigationOptions} />
<SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} /> <SendDetailsStack.Screen name="SelectWallet" component={SelectWallet} options={SelectWallet.navigationOptions} />
</SendDetailsStack.Navigator> </SendDetailsStack.Navigator>
@ -263,11 +264,12 @@ function DrawerRoot() {
const dimensions = useWindowDimensions(); const dimensions = useWindowDimensions();
const isLargeScreen = Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 3 && isTablet(); const isLargeScreen = Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 3 && isTablet();
const drawerStyle = { width: '0%' }; const drawerStyle = { width: '0%' };
return ( return (
<Drawer.Navigator <Drawer.Navigator
drawerStyle={isLargeScreen ? null : drawerStyle} drawerStyle={isLargeScreen ? null : drawerStyle}
drawerType={isLargeScreen ? 'permanent' : null} 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.Screen name="Navigation" component={Navigation} options={{ headerShown: false, gestureEnabled: false }} />
</Drawer.Navigator> </Drawer.Navigator>
@ -304,6 +306,11 @@ const Navigation = () => (
{/* screens */} {/* screens */}
<RootStack.Screen name="WalletExport" component={WalletExport} options={WalletExport.navigationOptions} /> <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="WalletXpub" component={WalletXpub} options={WalletXpub.navigationOptions} />
<RootStack.Screen name="BuyBitcoin" component={BuyBitcoin} options={BuyBitcoin.navigationOptions} /> <RootStack.Screen name="BuyBitcoin" component={BuyBitcoin} options={BuyBitcoin.navigationOptions} />
<RootStack.Screen name="Marketplace" component={Marketplace} options={Marketplace.navigationOptions} /> <RootStack.Screen name="Marketplace" component={Marketplace} options={Marketplace.navigationOptions} />
@ -315,7 +322,7 @@ const Navigation = () => (
name="ScanQRCodeRoot" name="ScanQRCodeRoot"
component={ScanQRCodeRoot} component={ScanQRCodeRoot}
options={{ options={{
...TransitionPresets.ModalTransition, ...(Platform.OS === 'ios' ? TransitionPresets.ModalTransition : TransitionPresets.ScaleFromCenterAndroid),
headerShown: false, headerShown: false,
}} }}
/> />

View file

@ -69,7 +69,7 @@ The above command will build the app and install it. Once you launch the app it
* To run on iOS: * To run on iOS:
``` ```
npx podinstall npx pod-install
npm start npm start
``` ```

View file

@ -127,12 +127,16 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
lintOptions {
abortOnError false
}
defaultConfig { defaultConfig {
applicationId "io.bluewallet.bluewallet" applicationId "io.bluewallet.bluewallet"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "5.6.0" versionName "5.6.2"
multiDexEnabled true multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general' missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type 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 are e.g. debug, release
applicationVariants.all { variant -> applicationVariants.all { variant ->
variant.outputs.each { output -> variant.outputs.each { output ->

View file

@ -2,11 +2,11 @@
buildscript { buildscript {
ext { ext {
buildToolsVersion = "28.0.3"
minSdkVersion = 18 minSdkVersion = 18
compileSdkVersion = 28
targetSdkVersion = 28
supportLibVersion = "28.0.0" supportLibVersion = "28.0.0"
buildToolsVersion = "29.0.2"
compileSdkVersion = 29
targetSdkVersion = 29
googlePlayServicesVersion = "16.+" googlePlayServicesVersion = "16.+"
firebaseVersion = "17.3.4" firebaseVersion = "17.3.4"
firebaseMessagingVersion = "20.2.1" firebaseMessagingVersion = "20.2.1"

View file

@ -31,4 +31,4 @@ org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# Version of flipper SDK to use with React Native # Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.33.1 FLIPPER_VERSION=0.54.0

30
android/gradlew vendored
View file

@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else else
eval `echo args$i`="\"$arg\"" eval `echo args$i`="\"$arg\""
fi fi
i=$((i+1)) i=`expr $i + 1`
done done
case $i in case $i in
(0) set -- ;; 0) set -- ;;
(1) set -- "$args0" ;; 1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;; 2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;; 3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
@ -175,15 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }
APP_ARGS=$(save "$@") APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules # 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" 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" "$@" exec "$JAVACMD" "$@"

View file

@ -521,15 +521,27 @@ module.exports.calculateBlockTime = function (height) {
*/ */
module.exports.testConnection = async function (host, tcpPort, sslPort) { module.exports.testConnection = async function (host, tcpPort, sslPort) {
const client = new ElectrumClient(sslPort || tcpPort, host, sslPort ? 'tls' : 'tcp'); const client = new ElectrumClient(sslPort || tcpPort, host, sslPort ? 'tls' : 'tcp');
client.onError = () => {}; // mute
let timeoutId = false;
try { 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_version('2.7.11', '1.4');
await client.server_ping(); await client.server_ping();
client.close();
return true; return true;
} catch (_) { } catch (_) {
return false; } finally {
if (timeoutId) clearTimeout(timeoutId);
client.close();
} }
return false;
}; };
module.exports.forceDisconnect = () => { module.exports.forceDisconnect = () => {

109
blue_modules/fs.js Normal file
View 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;

View file

@ -14,6 +14,7 @@ import {
LightningCustodianWallet, LightningCustodianWallet,
HDLegacyElectrumSeedP2PKHWallet, HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet, HDSegwitElectrumSeedP2WPKHWallet,
MultisigHDWallet,
} from './'; } from './';
import DeviceQuickActions from './quick-actions'; import DeviceQuickActions from './quick-actions';
import { AbstractHDElectrumWallet } from './wallets/abstract-hd-electrum-wallet'; import { AbstractHDElectrumWallet } from './wallets/abstract-hd-electrum-wallet';
@ -297,6 +298,9 @@ export class AppStorage {
case HDSegwitElectrumSeedP2WPKHWallet.type: case HDSegwitElectrumSeedP2WPKHWallet.type:
unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key); unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key);
break; break;
case MultisigHDWallet.type:
unserializedWallet = MultisigHDWallet.fromJson(key);
break;
case LightningCustodianWallet.type: { case LightningCustodianWallet.type: {
/** @type {LightningCustodianWallet} */ /** @type {LightningCustodianWallet} */
unserializedWallet = LightningCustodianWallet.fromJson(key); unserializedWallet = LightningCustodianWallet.fromJson(key);
@ -433,7 +437,7 @@ export class AppStorage {
const realm = await this.getRealm(); const realm = await this.getRealm();
for (const key of this.wallets) { for (const key of this.wallets) {
if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue; 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 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); if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance);
this.offloadWalletToRealm(realm, key); this.offloadWalletToRealm(realm, key);

View file

@ -59,12 +59,12 @@ export default class Biometric {
static async unlockWithBiometrics() { static async unlockWithBiometrics() {
const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable(); const isDeviceBiometricCapable = await Biometric.isDeviceBiometricCapable();
if (isDeviceBiometricCapable) { if (isDeviceBiometricCapable) {
try { return new Promise(resolve => {
const isConfirmed = await FingerprintScanner.authenticate({ description: 'Please confirm your identity.', fallbackEnabled: true }); FingerprintScanner.authenticate({ description: 'Please confirm your identity.', fallbackEnabled: true })
return isConfirmed; .then(() => resolve(true))
} catch (_e) { .catch(() => resolve(false))
return false; .finally(() => FingerprintScanner.release());
} });
} }
return false; return false;
} }

View file

@ -40,9 +40,8 @@ class DeeplinkSchemaMatch {
if (event.url.toLowerCase().startsWith('bluewallet:bitcoin:') || event.url.toLowerCase().startsWith('bluewallet:lightning:')) { if (event.url.toLowerCase().startsWith('bluewallet:bitcoin:') || event.url.toLowerCase().startsWith('bluewallet:lightning:')) {
event.url = event.url.substring(11); event.url = event.url.substring(11);
} }
if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(event.url)) {
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) { RNFS.readFile(decodeURI(event.url))
RNFS.readFile(event.url)
.then(file => { .then(file => {
if (file) { if (file) {
completionHandler([ completionHandler([
@ -203,13 +202,23 @@ class DeeplinkSchemaMatch {
} }
static isTXNFile(filePath) { 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) { static isPossiblyPSBTFile(filePath) {
return ( return (
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) && (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() { static bip21encode() {
return bip21.encode.apply(bip21, arguments); 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; export default DeeplinkSchemaMatch;

View file

@ -214,6 +214,32 @@ export class HDSegwitBech32Transaction {
return { fee, feeRate, targets, changeAmount, utxos, unconfirmedUtxos }; 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 * Checks if all outputs belong to us, that
* means we already canceled this tx and we can only bump fees * 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._wallet) throw new Error('Wallet required for this method');
if (!this._txDecoded) await this._fetchTxhexAndDecode(); 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! // if theres at least one output we dont own - we can cancel this transaction!
for (const outp of this._txDecoded.outs) { for (const outp of this._txDecoded.outs) {
if (!this._wallet.weOwnAddress(SegwitBech32Wallet.scriptPubKeyToAddress(outp.script))) return true; if (!this._wallet.weOwnAddress(SegwitBech32Wallet.scriptPubKeyToAddress(outp.script))) return true;
@ -232,6 +260,15 @@ export class HDSegwitBech32Transaction {
return false; 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 * 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 * output to the one our wallet controls). Note, this cannot add more utxo in RBF transaction if

View file

@ -14,3 +14,4 @@ export * from './hd-segwit-bech32-transaction';
export * from './wallets/placeholder-wallet'; export * from './wallets/placeholder-wallet';
export * from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; export * from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
export * from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet'; export * from './wallets/hd-segwit-electrum-seed-p2wpkh-wallet';
export * from './wallets/multisig-hd-wallet';

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

View file

@ -9,7 +9,8 @@ import { PlaceholderWallet } from './wallets/placeholder-wallet';
import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet'; import { SegwitBech32Wallet } from './wallets/segwit-bech32-wallet';
import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet'; import { HDLegacyElectrumSeedP2PKHWallet } from './wallets/hd-legacy-electrum-seed-p2pkh-wallet';
import { HDSegwitElectrumSeedP2WPKHWallet } from './wallets/hd-segwit-electrum-seed-p2wpkh-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 { export default class WalletGradient {
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1']; static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
@ -19,9 +20,15 @@ export default class WalletGradient {
static legacyWallet = ['#40fad1', '#15be98']; static legacyWallet = ['#40fad1', '#15be98'];
static hdLegacyP2PKHWallet = ['#e36dfa', '#bd10e0']; static hdLegacyP2PKHWallet = ['#e36dfa', '#bd10e0'];
static hdLegacyBreadWallet = ['#fe6381', '#f99c42']; static hdLegacyBreadWallet = ['#fe6381', '#f99c42'];
static multisigHdWallet = ['#1ce6eb', '#296fc5', '#3500A2'];
static defaultGradients = ['#c65afb', '#9053fe']; static defaultGradients = ['#c65afb', '#9053fe'];
static lightningCustodianWallet = ['#f1be07', '#f79056']; 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) { static gradientsFor(type) {
let gradient; let gradient;
@ -55,6 +62,9 @@ export default class WalletGradient {
case SegwitBech32Wallet.type: case SegwitBech32Wallet.type:
gradient = WalletGradient.segwitBech32Wallet; gradient = WalletGradient.segwitBech32Wallet;
break; break;
case MultisigHDWallet.type:
gradient = WalletGradient.multisigHdWallet;
break;
default: default:
gradient = WalletGradient.defaultGradients; gradient = WalletGradient.defaultGradients;
break; break;
@ -88,6 +98,9 @@ export default class WalletGradient {
case SegwitBech32Wallet.type: case SegwitBech32Wallet.type:
gradient = WalletGradient.segwitBech32Wallet; gradient = WalletGradient.segwitBech32Wallet;
break; break;
case MultisigHDWallet.type:
gradient = WalletGradient.multisigHdWallet;
break;
case LightningCustodianWallet.type: case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet; gradient = WalletGradient.lightningCustodianWallet;
break; break;

View file

@ -13,6 +13,7 @@ import {
SegwitBech32Wallet, SegwitBech32Wallet,
HDLegacyElectrumSeedP2PKHWallet, HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet, HDSegwitElectrumSeedP2WPKHWallet,
MultisigHDWallet,
} from '.'; } from '.';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import loc from '../loc'; import loc from '../loc';
@ -34,7 +35,9 @@ export default class WalletImport {
*/ */
static async _saveWallet(w, additionalProperties) { static async _saveWallet(w, additionalProperties) {
try { 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) { if (wallet) {
alert('This wallet has been previously imported.'); alert('This wallet has been previously imported.');
WalletImport.removePlaceholderWallet(); WalletImport.removePlaceholderWallet();
@ -97,6 +100,7 @@ export default class WalletImport {
const placeholderWallet = WalletImport.addPlaceholderWallet(importText); const placeholderWallet = WalletImport.addPlaceholderWallet(importText);
// Plan: // Plan:
// -2. check if BIP38 encrypted // -2. check if BIP38 encrypted
// -1a. check if multisig
// -1. check lightning custodian // -1. check lightning custodian
// 0. check if its HDSegwitBech32Wallet (BIP84) // 0. check if its HDSegwitBech32Wallet (BIP84)
// 1. check if its HDSegwitP2SHWallet (BIP49) // 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? // is it lightning custodian?
if (importText.indexOf('blitzhub://') !== -1 || importText.indexOf('lndhub://') !== -1) { if (importText.indexOf('blitzhub://') !== -1 || importText.indexOf('lndhub://') !== -1) {
const lnd = new LightningCustodianWallet(); const lnd = new LightningCustodianWallet();

View file

@ -553,27 +553,33 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
async _fetchBalance() { async _fetchBalance() {
// probing future addressess in hierarchy whether they have any transactions, in case // probing future addressess in hierarchy whether they have any transactions, in case
// our 'next free addr' pointers are lagging behind // our 'next free addr' pointers are lagging behind
let tryAgain = false; // for that we are gona batch fetch history for all addresses between last used and last used + gap_limit
let txs = await BlueElectrum.getTransactionsByAddress(
this._getExternalAddressByIndex(this.next_free_address_index + this.gap_limit - 1), const lagAddressesToFetch = [];
); for (let c = this.next_free_address_index; c < this.next_free_address_index + this.gap_limit; c++) {
if (txs.length > 0) { 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));
}
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 // whoa, someone uses our wallet outside! better catch up
this.next_free_address_index += this.gap_limit; this.next_free_address_index = c + 1;
tryAgain = true; }
} }
txs = await BlueElectrum.getTransactionsByAddress( for (let c = this.next_free_change_address_index; c < this.next_free_change_address_index + this.gap_limit; c++) {
this._getInternalAddressByIndex(this.next_free_change_address_index + this.gap_limit - 1), const address = this._getInternalAddressByIndex(c);
); if (txs[address] && Array.isArray(txs[address]) && txs[address].length > 0) {
if (txs.length > 0) { // whoa, someone uses our wallet outside! better catch up
this.next_free_change_address_index += this.gap_limit; this.next_free_change_address_index = c + 1;
tryAgain = true; }
} }
// 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();
// next, business as usuall. fetch balances // next, business as usuall. fetch balances
@ -674,8 +680,9 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
addressess = [...new Set(addressess)]; // deduplicate just for any case addressess = [...new Set(addressess)]; // deduplicate just for any case
const fetchedUtxo = await BlueElectrum.multiGetUtxoByAddress(addressess);
this._utxo = []; 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); this._utxo = this._utxo.concat(arr);
} }
@ -712,15 +719,26 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
return this._utxo; return this._utxo;
} }
getDerivedUtxoFromOurTransaction() { getDerivedUtxoFromOurTransaction(returnSpentUtxoAsWell = false) {
const utxos = []; 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 tx of this.getTransactions()) {
for (const output of tx.outputs) { for (const output of tx.outputs) {
let address = false; let address = false;
if (output.scriptPubKey && output.scriptPubKey.addresses && output.scriptPubKey.addresses[0]) { if (output.scriptPubKey && output.scriptPubKey.addresses && output.scriptPubKey.addresses[0]) {
address = 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(); const value = new BigNumber(output.value).multipliedBy(100000000).toNumber();
utxos.push({ utxos.push({
txid: tx.txid, txid: tx.txid,
@ -730,13 +748,15 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
value, value,
amount: value, amount: value,
confirmations: tx.confirmations, confirmations: tx.confirmations,
wif: this._getWifForAddress(address), wif: false,
height: BlueElectrum.estimateCurrentBlockheight() - tx.confirmations, height: BlueElectrum.estimateCurrentBlockheight() - tx.confirmations,
}); });
} }
} }
} }
if (returnSpentUtxoAsWell) return utxos;
// got all utxos we ever had. lets filter out the ones that are spent: // got all utxos we ever had. lets filter out the ones that are spent:
const ret = []; const ret = [];
for (const utxo of utxos) { 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; return ret;

View file

@ -26,6 +26,10 @@ export class AbstractHDWallet extends LegacyWallet {
return this.next_free_address_index; return this.next_free_address_index;
} }
getNextFreeChangeAddressIndex() {
return this.next_free_change_address_index;
}
prepareForSerialization() { prepareForSerialization() {
// deleting structures that cant be serialized // deleting structures that cant be serialized
delete this._node0; delete this._node0;
@ -93,7 +97,7 @@ export class AbstractHDWallet extends LegacyWallet {
if (!freeAddress) { if (!freeAddress) {
// could not find in cycle above, give up // 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 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; this._address = freeAddress;
return freeAddress; return freeAddress;
@ -130,8 +134,8 @@ export class AbstractHDWallet extends LegacyWallet {
if (!freeAddress) { if (!freeAddress) {
// could not find in cycle above, give up // 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 freeAddress = this._getInternalAddressByIndex(this.next_free_change_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_change_address_index += c; // now points to this one
} }
this._address = freeAddress; this._address = freeAddress;
return freeAddress; return freeAddress;

View file

@ -115,6 +115,10 @@ export class AbstractWallet {
return false; return false;
} }
allowPayJoin() {
return false;
}
weOwnAddress(address) { weOwnAddress(address) {
throw Error('not implemented'); throw Error('not implemented');
} }
@ -215,6 +219,10 @@ export class AbstractWallet {
return new Promise(resolve => resolve(this.getAddress())); return new Promise(resolve => resolve(this.getAddress()));
} }
async getChangeAddressAsync() {
return new Promise(resolve => resolve(this.getAddress()));
}
useWithHardwareWalletEnabled() { useWithHardwareWalletEnabled() {
return false; return false;
} }
@ -260,4 +268,6 @@ export class AbstractWallet {
return b58.encode(data); return b58.encode(data);
} }
prepareForSerialization() {}
} }

View file

@ -71,6 +71,7 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet {
* @private * @private
*/ */
_getWIFByIndex(internal, index) { _getWIFByIndex(internal, index) {
if (!this.secret) return false;
const mnemonic = this.secret; const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic); const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoinjs.bip32.fromSeed(seed); const root = bitcoinjs.bip32.fromSeed(seed);

View file

@ -61,6 +61,7 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
} }
_getWIFByIndex(internal, index) { _getWIFByIndex(internal, index) {
if (!this.secret) return false;
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS)); const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
const path = `m/${internal ? 1 : 0}/${index}`; const path = `m/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path); const child = root.derivePath(path);

View file

@ -48,6 +48,7 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
* @private * @private
*/ */
_getWIFByIndex(internal, index) { _getWIFByIndex(internal, index) {
if (!this.secret) return false;
const mnemonic = this.secret; const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic); const seed = bip39.mnemonicToSeed(mnemonic);

View file

@ -28,4 +28,8 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
allowRBF() { allowRBF() {
return true; return true;
} }
allowPayJoin() {
return true;
}
} }

View file

@ -61,6 +61,7 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
} }
_getWIFByIndex(internal, index) { _getWIFByIndex(internal, index) {
if (!this.secret) return false;
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS)); const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
const path = `m/0'/${internal ? 1 : 0}/${index}`; const path = `m/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path); const child = root.derivePath(path);

View file

@ -29,6 +29,7 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
* @private * @private
*/ */
_getWIFByIndex(internal, index) { _getWIFByIndex(internal, index) {
if (!this.secret) return false;
const mnemonic = this.secret; const mnemonic = this.secret;
const seed = bip39.mnemonicToSeed(mnemonic); const seed = bip39.mnemonicToSeed(mnemonic);
const root = bitcoin.bip32.fromSeed(seed); const root = bitcoin.bip32.fromSeed(seed);

View file

@ -401,10 +401,4 @@ export class LegacyWallet extends AbstractWallet {
allowSendMax() { allowSendMax() {
return true; return true;
} }
async getChangeAddressAsync() {
return new Promise(resolve => {
resolve(this.getAddress());
});
}
} }

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

View file

@ -9,6 +9,11 @@ export class PlaceholderWallet extends AbstractWallet {
this._isFailure = false; this._isFailure = false;
} }
setSecret(newSecret) {
// so TRY AGAIN when something goes wrong during import has more consistent prefilled text
this.secret = newSecret;
}
allowSend() { allowSend() {
return false; return false;
} }

View file

@ -120,16 +120,26 @@ export class WatchOnlyWallet extends LegacyWallet {
throw new Error('Not initialized'); throw new Error('Not initialized');
} }
async _getExternalAddressByIndex(index) { _getExternalAddressByIndex(index) {
if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index); if (this._hdWalletInstance) return this._hdWalletInstance._getExternalAddressByIndex(index);
throw new Error('Not initialized'); throw new Error('Not initialized');
} }
_getInternalAddressByIndex(index) {
if (this._hdWalletInstance) return this._hdWalletInstance._getInternalAddressByIndex(index);
throw new Error('Not initialized');
}
getNextFreeAddressIndex() { getNextFreeAddressIndex() {
if (this._hdWalletInstance) return this._hdWalletInstance.next_free_address_index; if (this._hdWalletInstance) return this._hdWalletInstance.next_free_address_index;
throw new Error('Not initialized'); throw new Error('Not initialized');
} }
getNextFreeChangeAddressIndex() {
if (this._hdWalletInstance) return this._hdWalletInstance.next_free_change_address_index;
throw new Error('Not initialized');
}
async getChangeAddressAsync() { async getChangeAddressAsync() {
if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync(); if (this._hdWalletInstance) return this._hdWalletInstance.getChangeAddressAsync();
throw new Error('Not initialized'); throw new Error('Not initialized');

180
components/DynamicQRCode.js Normal file
View 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
View 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,
};

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

View file

@ -54,6 +54,8 @@ export const BlueDefaultTheme = {
mainColor: '#CFDCF6', mainColor: '#CFDCF6',
success: '#ccddf9', success: '#ccddf9',
successCheck: '#0f5cc0', successCheck: '#0f5cc0',
msSuccessBG: '#37c0a1',
msSuccessCheck: '#ffffff',
}, },
}; };
@ -96,6 +98,8 @@ export const BlueDarkTheme = {
buttonBlueBackgroundColor: '#202020', buttonBlueBackgroundColor: '#202020',
scanLabel: 'rgba(255,255,255,.2)', scanLabel: 'rgba(255,255,255,.2)',
labelText: '#ffffff', labelText: '#ffffff',
msSuccessBG: '#8EFFE5',
msSuccessCheck: '#000000',
}, },
}; };

BIN
img/vault-shape.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -1,15 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import './shim.js'; import './shim.js';
import { AppRegistry, YellowBox } from 'react-native'; import { AppRegistry } from 'react-native';
import App from './App'; 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'); const A = require('./blue_modules/analytics');
if (!Error.captureStackTrace) { if (!Error.captureStackTrace) {

View file

@ -939,43 +939,12 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources.sh", "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-input-files.xcfilelist",
"${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",
); );
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputPaths = ( outputFileListPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources-${CONFIGURATION}-output-files.xcfilelist",
"${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",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
@ -1281,6 +1250,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
MARKETING_VERSION = 5.6.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@ -1320,6 +1290,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
MARKETING_VERSION = 5.6.2;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
@ -1575,6 +1546,7 @@
CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements"; CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 239;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A7W54YZ4WU; DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
@ -1611,6 +1583,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 239;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A7W54YZ4WU; DEVELOPMENT_TEAM = A7W54YZ4WU;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;

View file

@ -13,6 +13,7 @@
#import "RNQuickActionManager.h" #import "RNQuickActionManager.h"
#import <UserNotifications/UserNotifications.h> #import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h> #import <RNCPushNotificationIOS.h>
#ifdef FB_SONARKIT_ENABLED
#if DEBUG #if DEBUG
#import <FlipperKit/FlipperClient.h> #import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h> #import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
@ -31,13 +32,13 @@ static void InitializeFlipper(UIApplication *application) {
[client start]; [client start];
} }
#endif #endif
#endif
@implementation AppDelegate @implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ {
#if DEBUG #ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application); InitializeFlipper(application);
#endif #endif

View file

@ -36,6 +36,34 @@
<string>io.bluewallet.psbt.txn</string> <string>io.bluewallet.psbt.txn</string>
</array> </array>
</dict> </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> </array>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
@ -101,7 +129,7 @@
<key>NSCalendarsUsageDescription</key> <key>NSCalendarsUsageDescription</key>
<string>This alert should not show up as we do not require this data</string> <string>This alert should not show up as we do not require this data</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>In order to quickly scan the recipient&apos;s address, we need your permission to use the camera to scan their QR Code.</string> <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> <key>NSFaceIDUsageDescription</key>
<string>In order to use FaceID please confirm your permission.</string> <string>In order to use FaceID please confirm your permission.</string>
<key>NSLocationAlwaysUsageDescription</key> <key>NSLocationAlwaysUsageDescription</key>
@ -241,6 +269,44 @@
</array> </array>
</dict> </dict>
</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> </array>
</dict> </dict>
</plist> </plist>

View file

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>XPC!</string> <string>XPC!</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.6.0</string> <string>5.6.2</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>239</string> <string>239</string>
<key>CLKComplicationPrincipalClass</key> <key>CLKComplicationPrincipalClass</key>

View file

@ -14,6 +14,7 @@ enum WalletGradient: String {
case LightningCustodial = "lightningCustodianWallet" case LightningCustodial = "lightningCustodianWallet"
case SegwitNative = "HDsegwitBech32" case SegwitNative = "HDsegwitBech32"
case WatchOnly = "watchOnly" case WatchOnly = "watchOnly"
case MultiSig = "HDmultisig"
var imageString: String{ var imageString: String{
switch self { switch self {
@ -27,6 +28,8 @@ enum WalletGradient: String {
return "walletWatchOnly" return "walletWatchOnly"
case .LightningCustodial: case .LightningCustodial:
return "walletLightningCustodial" return "walletLightningCustodial"
case .MultiSig:
return "watchMultisig"
} }
} }
} }

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

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

View file

@ -1,91 +1,18 @@
platform :ios, '10.0' platform :ios, '10.0'
workspace 'BlueWallet' workspace 'BlueWallet'
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 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 target 'BlueWallet' do
# Pods for RnDiffApp config = use_native_modules!
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/'
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' use_react_native!(:path => config["reactNativePath"])
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!
# Enables Flipper. # Enables Flipper.
# #
# Note that if you have use_frameworks! enabled, Flipper will not work and # Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines. # you should disable these next few lines.
add_flipper_pods! use_flipper!
post_install do |installer| post_install do |installer|
flipper_post_install(installer) flipper_post_install(installer)
end end

View file

@ -5,15 +5,15 @@ PODS:
- CocoaAsyncSocket (7.6.4) - CocoaAsyncSocket (7.6.4)
- CocoaLibEvent (1.0.0) - CocoaLibEvent (1.0.0)
- DoubleConversion (1.1.6) - DoubleConversion (1.1.6)
- FBLazyVector (0.62.2) - FBLazyVector (0.63.3)
- FBReactNativeSpec (0.62.2): - FBReactNativeSpec (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- RCTRequired (= 0.62.2) - RCTRequired (= 0.63.3)
- RCTTypeSafety (= 0.62.2) - RCTTypeSafety (= 0.63.3)
- React-Core (= 0.62.2) - React-Core (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- ReactCommon/turbomodule/core (= 0.62.2) - ReactCommon/turbomodule/core (= 0.63.3)
- Flipper (0.37.0): - Flipper (0.54.0):
- Flipper-Folly (~> 2.2) - Flipper-Folly (~> 2.2)
- Flipper-RSocket (~> 1.1) - Flipper-RSocket (~> 1.1)
- Flipper-DoubleConversion (1.1.7) - Flipper-DoubleConversion (1.1.7)
@ -27,44 +27,44 @@ PODS:
- Flipper-PeerTalk (0.0.4) - Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.1.0): - Flipper-RSocket (1.1.0):
- Flipper-Folly (~> 2.2) - Flipper-Folly (~> 2.2)
- FlipperKit (0.37.0): - FlipperKit (0.54.0):
- FlipperKit/Core (= 0.37.0) - FlipperKit/Core (= 0.54.0)
- FlipperKit/Core (0.37.0): - FlipperKit/Core (0.54.0):
- Flipper (~> 0.37.0) - Flipper (~> 0.54.0)
- FlipperKit/CppBridge - FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert - FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines - FlipperKit/FBDefines
- FlipperKit/FKPortForwarding - FlipperKit/FKPortForwarding
- FlipperKit/CppBridge (0.37.0): - FlipperKit/CppBridge (0.54.0):
- Flipper (~> 0.37.0) - Flipper (~> 0.54.0)
- FlipperKit/FBCxxFollyDynamicConvert (0.37.0): - FlipperKit/FBCxxFollyDynamicConvert (0.54.0):
- Flipper-Folly (~> 2.2) - Flipper-Folly (~> 2.2)
- FlipperKit/FBDefines (0.37.0) - FlipperKit/FBDefines (0.54.0)
- FlipperKit/FKPortForwarding (0.37.0): - FlipperKit/FKPortForwarding (0.54.0):
- CocoaAsyncSocket (~> 7.6) - CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4) - Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.37.0) - FlipperKit/FlipperKitHighlightOverlay (0.54.0)
- FlipperKit/FlipperKitLayoutPlugin (0.37.0): - FlipperKit/FlipperKitLayoutPlugin (0.54.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable - FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18) - YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.37.0) - FlipperKit/FlipperKitLayoutTextSearchable (0.54.0)
- FlipperKit/FlipperKitNetworkPlugin (0.37.0): - FlipperKit/FlipperKitNetworkPlugin (0.54.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.37.0): - FlipperKit/FlipperKitReactPlugin (0.54.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.37.0): - FlipperKit/FlipperKitUserDefaultsPlugin (0.54.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.37.0): - FlipperKit/SKIOSNetworkPlugin (0.54.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin - FlipperKit/FlipperKitNetworkPlugin
- Folly (2018.10.22.00): - Folly (2020.01.13.00):
- boost-for-react-native - boost-for-react-native
- DoubleConversion - DoubleConversion
- Folly/Default (= 2018.10.22.00) - Folly/Default (= 2020.01.13.00)
- glog - glog
- Folly/Default (2018.10.22.00): - Folly/Default (2020.01.13.00):
- boost-for-react-native - boost-for-react-native
- DoubleConversion - DoubleConversion
- glog - glog
@ -81,169 +81,172 @@ PODS:
- OpenSSL-Universal/Static (1.0.2.19) - OpenSSL-Universal/Static (1.0.2.19)
- PasscodeAuth (1.0.0): - PasscodeAuth (1.0.0):
- React - React
- RCTRequired (0.62.2) - RCTRequired (0.63.3)
- RCTTypeSafety (0.62.2): - RCTTypeSafety (0.63.3):
- FBLazyVector (= 0.62.2) - FBLazyVector (= 0.63.3)
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- RCTRequired (= 0.62.2) - RCTRequired (= 0.63.3)
- React-Core (= 0.62.2) - React-Core (= 0.63.3)
- React (0.62.2): - React (0.63.3):
- React-Core (= 0.62.2) - React-Core (= 0.63.3)
- React-Core/DevSupport (= 0.62.2) - React-Core/DevSupport (= 0.63.3)
- React-Core/RCTWebSocket (= 0.62.2) - React-Core/RCTWebSocket (= 0.63.3)
- React-RCTActionSheet (= 0.62.2) - React-RCTActionSheet (= 0.63.3)
- React-RCTAnimation (= 0.62.2) - React-RCTAnimation (= 0.63.3)
- React-RCTBlob (= 0.62.2) - React-RCTBlob (= 0.63.3)
- React-RCTImage (= 0.62.2) - React-RCTImage (= 0.63.3)
- React-RCTLinking (= 0.62.2) - React-RCTLinking (= 0.63.3)
- React-RCTNetwork (= 0.62.2) - React-RCTNetwork (= 0.63.3)
- React-RCTSettings (= 0.62.2) - React-RCTSettings (= 0.63.3)
- React-RCTText (= 0.62.2) - React-RCTText (= 0.63.3)
- React-RCTVibration (= 0.62.2) - React-RCTVibration (= 0.63.3)
- React-Core (0.62.2): - React-callinvoker (0.63.3)
- Folly (= 2018.10.22.00) - React-Core (0.63.3):
- Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default (= 0.62.2) - React-Core/Default (= 0.63.3)
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/CoreModulesHeaders (0.62.2): - React-Core/CoreModulesHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/Default (0.62.2): - React-Core/Default (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/DevSupport (0.62.2): - React-Core/DevSupport (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default (= 0.62.2) - React-Core/Default (= 0.63.3)
- React-Core/RCTWebSocket (= 0.62.2) - React-Core/RCTWebSocket (= 0.63.3)
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- React-jsinspector (= 0.62.2) - React-jsinspector (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTActionSheetHeaders (0.62.2): - React-Core/RCTActionSheetHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTAnimationHeaders (0.62.2): - React-Core/RCTAnimationHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTBlobHeaders (0.62.2): - React-Core/RCTBlobHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTImageHeaders (0.62.2): - React-Core/RCTImageHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTLinkingHeaders (0.62.2): - React-Core/RCTLinkingHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTNetworkHeaders (0.62.2): - React-Core/RCTNetworkHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTSettingsHeaders (0.62.2): - React-Core/RCTSettingsHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTTextHeaders (0.62.2): - React-Core/RCTTextHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTVibrationHeaders (0.62.2): - React-Core/RCTVibrationHeaders (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default - React-Core/Default
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-Core/RCTWebSocket (0.62.2): - React-Core/RCTWebSocket (0.63.3):
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-Core/Default (= 0.62.2) - React-Core/Default (= 0.63.3)
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsiexecutor (= 0.62.2) - React-jsiexecutor (= 0.63.3)
- Yoga - Yoga
- React-CoreModules (0.62.2): - React-CoreModules (0.63.3):
- FBReactNativeSpec (= 0.62.2) - FBReactNativeSpec (= 0.63.3)
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.62.2) - RCTTypeSafety (= 0.63.3)
- React-Core/CoreModulesHeaders (= 0.62.2) - React-Core/CoreModulesHeaders (= 0.63.3)
- React-RCTImage (= 0.62.2) - React-jsi (= 0.63.3)
- ReactCommon/turbomodule/core (= 0.62.2) - React-RCTImage (= 0.63.3)
- React-cxxreact (0.62.2): - ReactCommon/turbomodule/core (= 0.63.3)
- React-cxxreact (0.63.3):
- boost-for-react-native (= 1.63.0) - boost-for-react-native (= 1.63.0)
- DoubleConversion - DoubleConversion
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-jsinspector (= 0.62.2) - React-callinvoker (= 0.63.3)
- React-jsi (0.62.2): - React-jsinspector (= 0.63.3)
- React-jsi (0.63.3):
- boost-for-react-native (= 1.63.0) - boost-for-react-native (= 1.63.0)
- DoubleConversion - DoubleConversion
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-jsi/Default (= 0.62.2) - React-jsi/Default (= 0.63.3)
- React-jsi/Default (0.62.2): - React-jsi/Default (0.63.3):
- boost-for-react-native (= 1.63.0) - boost-for-react-native (= 1.63.0)
- DoubleConversion - DoubleConversion
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-jsiexecutor (0.62.2): - React-jsiexecutor (0.63.3):
- DoubleConversion - DoubleConversion
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-cxxreact (= 0.62.2) - React-cxxreact (= 0.63.3)
- React-jsi (= 0.62.2) - React-jsi (= 0.63.3)
- React-jsinspector (0.62.2) - React-jsinspector (0.63.3)
- react-native-blue-crypto (1.0.0): - react-native-blue-crypto (1.0.0):
- React - React
- react-native-blur (0.8.0): - react-native-blur (0.8.0):
@ -262,8 +265,8 @@ PODS:
- React - React
- react-native-geolocation (2.0.2): - react-native-geolocation (2.0.2):
- React - React
- react-native-image-picker (2.3.3): - react-native-image-picker (2.3.4):
- React - React-Core
- react-native-randombytes (3.5.3): - react-native-randombytes (3.5.3):
- React - React
- react-native-safe-area-context (3.1.8): - react-native-safe-area-context (3.1.8):
@ -273,67 +276,68 @@ PODS:
- react-native-tcp-socket (3.7.1): - react-native-tcp-socket (3.7.1):
- CocoaAsyncSocket - CocoaAsyncSocket
- React - React
- react-native-webview (10.8.3): - react-native-webview (10.9.2):
- React - React-Core
- React-RCTActionSheet (0.62.2): - React-RCTActionSheet (0.63.3):
- React-Core/RCTActionSheetHeaders (= 0.62.2) - React-Core/RCTActionSheetHeaders (= 0.63.3)
- React-RCTAnimation (0.62.2): - React-RCTAnimation (0.63.3):
- FBReactNativeSpec (= 0.62.2) - FBReactNativeSpec (= 0.63.3)
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.62.2) - RCTTypeSafety (= 0.63.3)
- React-Core/RCTAnimationHeaders (= 0.62.2) - React-Core/RCTAnimationHeaders (= 0.63.3)
- ReactCommon/turbomodule/core (= 0.62.2) - React-jsi (= 0.63.3)
- React-RCTBlob (0.62.2): - ReactCommon/turbomodule/core (= 0.63.3)
- FBReactNativeSpec (= 0.62.2) - React-RCTBlob (0.63.3):
- Folly (= 2018.10.22.00) - FBReactNativeSpec (= 0.63.3)
- React-Core/RCTBlobHeaders (= 0.62.2) - Folly (= 2020.01.13.00)
- React-Core/RCTWebSocket (= 0.62.2) - React-Core/RCTBlobHeaders (= 0.63.3)
- React-jsi (= 0.62.2) - React-Core/RCTWebSocket (= 0.63.3)
- React-RCTNetwork (= 0.62.2) - React-jsi (= 0.63.3)
- ReactCommon/turbomodule/core (= 0.62.2) - React-RCTNetwork (= 0.63.3)
- React-RCTImage (0.62.2): - ReactCommon/turbomodule/core (= 0.63.3)
- FBReactNativeSpec (= 0.62.2) - React-RCTImage (0.63.3):
- Folly (= 2018.10.22.00) - FBReactNativeSpec (= 0.63.3)
- RCTTypeSafety (= 0.62.2) - Folly (= 2020.01.13.00)
- React-Core/RCTImageHeaders (= 0.62.2) - RCTTypeSafety (= 0.63.3)
- React-RCTNetwork (= 0.62.2) - React-Core/RCTImageHeaders (= 0.63.3)
- ReactCommon/turbomodule/core (= 0.62.2) - React-jsi (= 0.63.3)
- React-RCTLinking (0.62.2): - React-RCTNetwork (= 0.63.3)
- FBReactNativeSpec (= 0.62.2) - ReactCommon/turbomodule/core (= 0.63.3)
- React-Core/RCTLinkingHeaders (= 0.62.2) - React-RCTLinking (0.63.3):
- ReactCommon/turbomodule/core (= 0.62.2) - FBReactNativeSpec (= 0.63.3)
- React-RCTNetwork (0.62.2): - React-Core/RCTLinkingHeaders (= 0.63.3)
- FBReactNativeSpec (= 0.62.2) - React-jsi (= 0.63.3)
- Folly (= 2018.10.22.00) - ReactCommon/turbomodule/core (= 0.63.3)
- RCTTypeSafety (= 0.62.2) - React-RCTNetwork (0.63.3):
- React-Core/RCTNetworkHeaders (= 0.62.2) - FBReactNativeSpec (= 0.63.3)
- ReactCommon/turbomodule/core (= 0.62.2) - Folly (= 2020.01.13.00)
- React-RCTSettings (0.62.2): - RCTTypeSafety (= 0.63.3)
- FBReactNativeSpec (= 0.62.2) - React-Core/RCTNetworkHeaders (= 0.63.3)
- Folly (= 2018.10.22.00) - React-jsi (= 0.63.3)
- RCTTypeSafety (= 0.62.2) - ReactCommon/turbomodule/core (= 0.63.3)
- React-Core/RCTSettingsHeaders (= 0.62.2) - React-RCTSettings (0.63.3):
- ReactCommon/turbomodule/core (= 0.62.2) - FBReactNativeSpec (= 0.63.3)
- React-RCTText (0.62.2): - Folly (= 2020.01.13.00)
- React-Core/RCTTextHeaders (= 0.62.2) - RCTTypeSafety (= 0.63.3)
- React-RCTVibration (0.62.2): - React-Core/RCTSettingsHeaders (= 0.63.3)
- FBReactNativeSpec (= 0.62.2) - React-jsi (= 0.63.3)
- Folly (= 2018.10.22.00) - ReactCommon/turbomodule/core (= 0.63.3)
- React-Core/RCTVibrationHeaders (= 0.62.2) - React-RCTText (0.63.3):
- ReactCommon/turbomodule/core (= 0.62.2) - React-Core/RCTTextHeaders (= 0.63.3)
- ReactCommon/callinvoker (0.62.2): - 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 - DoubleConversion
- Folly (= 2018.10.22.00) - Folly (= 2020.01.13.00)
- glog - glog
- React-cxxreact (= 0.62.2) - React-callinvoker (= 0.63.3)
- ReactCommon/turbomodule/core (0.62.2): - React-Core (= 0.63.3)
- DoubleConversion - React-cxxreact (= 0.63.3)
- Folly (= 2018.10.22.00) - React-jsi (= 0.63.3)
- glog
- React-Core (= 0.62.2)
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- ReactCommon/callinvoker (= 0.62.2)
- ReactNativePrivacySnapshot (1.0.0): - ReactNativePrivacySnapshot (1.0.0):
- React - React
- RealmJS (6.1.0): - RealmJS (6.1.0):
@ -341,28 +345,28 @@ PODS:
- React - React
- RemobileReactNativeQrcodeLocalImage (1.0.4): - RemobileReactNativeQrcodeLocalImage (1.0.4):
- React - React
- RNCAsyncStorage (1.12.0): - RNCAsyncStorage (1.12.1):
- React - React-Core
- RNCClipboard (1.2.3): - RNCClipboard (1.4.0):
- React - React-Core
- RNCMaskedView (0.1.10): - RNCMaskedView (0.1.10):
- React - React
- RNCPushNotificationIOS (1.5.0): - RNCPushNotificationIOS (1.5.0):
- React - React
- RNDefaultPreference (1.4.3): - RNDefaultPreference (1.4.3):
- React - React
- RNDeviceInfo (6.0.3): - RNDeviceInfo (6.2.0):
- React-Core - React-Core
- RNFS (2.16.6): - RNFS (2.16.6):
- React - React
- RNGestureHandler (1.7.0): - RNGestureHandler (1.8.0):
- React - React
- RNHandoff (0.0.3): - RNHandoff (0.0.3):
- React - React
- RNInAppBrowser (3.4.0): - RNInAppBrowser (3.4.0):
- React - React
- RNLocalize (1.4.0): - RNLocalize (1.4.2):
- React - React-Core
- RNQuickAction (0.3.13): - RNQuickAction (0.3.13):
- React - React
- RNRate (1.2.4): - RNRate (1.2.4):
@ -375,11 +379,11 @@ PODS:
- React - React
- RNSecureKeyStore (1.0.0): - RNSecureKeyStore (1.0.0):
- React - React
- RNSentry (1.8.0): - RNSentry (1.8.2):
- React - React
- Sentry (~> 5.2.0) - Sentry (~> 5.2.0)
- RNShare (3.7.0): - RNShare (4.0.2):
- React - React-Core
- RNSVG (12.1.0): - RNSVG (12.1.0):
- React - React
- RNVectorIcons (6.6.0): - RNVectorIcons (6.6.0):
@ -400,25 +404,25 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`) - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
- Flipper (~> 0.37.0) - Flipper (~> 0.54.0)
- Flipper-DoubleConversion (= 1.1.7) - Flipper-DoubleConversion (= 1.1.7)
- Flipper-Folly (~> 2.1) - Flipper-Folly (~> 2.2)
- Flipper-Glog (= 0.3.6) - Flipper-Glog (= 0.3.6)
- Flipper-PeerTalk (~> 0.0.4) - Flipper-PeerTalk (~> 0.0.4)
- Flipper-RSocket (~> 1.0) - Flipper-RSocket (~> 1.1)
- FlipperKit (~> 0.37.0) - FlipperKit (~> 0.54.0)
- FlipperKit/Core (~> 0.37.0) - FlipperKit/Core (~> 0.54.0)
- FlipperKit/CppBridge (~> 0.37.0) - FlipperKit/CppBridge (~> 0.54.0)
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.37.0) - FlipperKit/FBCxxFollyDynamicConvert (~> 0.54.0)
- FlipperKit/FBDefines (~> 0.37.0) - FlipperKit/FBDefines (~> 0.54.0)
- FlipperKit/FKPortForwarding (~> 0.37.0) - FlipperKit/FKPortForwarding (~> 0.54.0)
- FlipperKit/FlipperKitHighlightOverlay (~> 0.37.0) - FlipperKit/FlipperKitHighlightOverlay (~> 0.54.0)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.37.0) - FlipperKit/FlipperKitLayoutPlugin (~> 0.54.0)
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.37.0) - FlipperKit/FlipperKitLayoutTextSearchable (~> 0.54.0)
- FlipperKit/FlipperKitNetworkPlugin (~> 0.37.0) - FlipperKit/FlipperKitNetworkPlugin (~> 0.54.0)
- FlipperKit/FlipperKitReactPlugin (~> 0.37.0) - FlipperKit/FlipperKitReactPlugin (~> 0.54.0)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.37.0) - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.54.0)
- FlipperKit/SKIOSNetworkPlugin (~> 0.37.0) - FlipperKit/SKIOSNetworkPlugin (~> 0.54.0)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- lottie-ios (from `../node_modules/lottie-ios`) - lottie-ios (from `../node_modules/lottie-ios`)
@ -427,6 +431,7 @@ DEPENDENCIES:
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`) - React (from `../node_modules/react-native/`)
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Core (from `../node_modules/react-native/`) - React-Core (from `../node_modules/react-native/`)
- React-Core/DevSupport (from `../node_modules/react-native/`) - React-Core/DevSupport (from `../node_modules/react-native/`)
- React-Core/RCTWebSocket (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-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`) - React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - 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`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- ReactNativePrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`) - ReactNativePrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
- RealmJS (from `../node_modules/realm`) - RealmJS (from `../node_modules/realm`)
@ -528,6 +532,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/TypeSafety" :path: "../node_modules/react-native/Libraries/TypeSafety"
React: React:
:path: "../node_modules/react-native/" :path: "../node_modules/react-native/"
React-callinvoker:
:path: "../node_modules/react-native/ReactCommon/callinvoker"
React-Core: React-Core:
:path: "../node_modules/react-native/" :path: "../node_modules/react-native/"
React-CoreModules: React-CoreModules:
@ -644,84 +650,85 @@ SPEC CHECKSUMS:
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872 BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845 CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 DoubleConversion: cde416483dac037923206447da6e1454df403714
FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245 FBLazyVector: 878b59e31113e289e275165efbe4b54fa614d43d
FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e FBReactNativeSpec: 7da9338acfb98d4ef9e5536805a0704572d33c2f
Flipper: 1670db365568191bd123a0c905b834e77ba9e3d3 Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3 Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7 Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
FlipperKit: afd4259ef9eadeeb2d30250b37d95cb3b6b97a69 FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 Folly: b73c3869541e86821df3c387eb0af5f65addfab4
GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4 GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
glog: 1f3da668190260b06b429bb211bfbee5cd790c28 glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
lottie-ios: 48fac6be217c76937e36e340e2d09cf7b10b7f5f lottie-ios: 48fac6be217c76937e36e340e2d09cf7b10b7f5f
lottie-react-native: 1fb4ce21d6ad37dab8343eaff8719df76035bd93 lottie-react-native: 1fb4ce21d6ad37dab8343eaff8719df76035bd93
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355 OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
PasscodeAuth: 1cc99b13d8e4de4716d7e2b4069af2f1a9de30b2 PasscodeAuth: 1cc99b13d8e4de4716d7e2b4069af2f1a9de30b2
RCTRequired: cec6a34b3ac8a9915c37e7e4ad3aa74726ce4035 RCTRequired: 48884c74035a0b5b76dbb7a998bd93bcfc5f2047
RCTTypeSafety: 93006131180074cffa227a1075802c89a49dd4ce RCTTypeSafety: edf4b618033c2f1c5b7bc3d90d8e085ed95ba2ab
React: 29a8b1a02bd764fb7644ef04019270849b9a7ac3 React: f36e90f3ceb976546e97df3403e37d226f79d0e3
React-Core: b12bffb3f567fdf99510acb716ef1abd426e0e05 React-callinvoker: 18874f621eb96625df7a24a7dc8d6e07391affcd
React-CoreModules: 4a9b87bbe669d6c3173c0132c3328e3b000783d0 React-Core: ac3d816b8e3493970153f4aaf0cff18af0bb95e6
React-cxxreact: e65f9c2ba0ac5be946f53548c1aaaee5873a8103 React-CoreModules: 4016d3a4e518bcfc4f5a51252b5a05692ca6f0e1
React-jsi: b6dc94a6a12ff98e8877287a0b7620d365201161 React-cxxreact: ffc9129013b87cb36cf3f30a86695a3c397b0f99
React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da React-jsi: df07aa95b39c5be3e41199921509bfa929ed2b9d
React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493 React-jsiexecutor: b56c03e61c0dd5f5801255f2160a815f4a53d451
React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56 react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: fc1296181b2d393ea698164869070c96d8625129 react-native-camera: fc1296181b2d393ea698164869070c96d8625129
react-native-document-picker: c5752781fbc0c126c627c1549b037c139444a4cf react-native-document-picker: c5752781fbc0c126c627c1549b037c139444a4cf
react-native-fingerprint-scanner: c68136ca57e3704d7bdf5faa554ea535ce15b1d0 react-native-fingerprint-scanner: c68136ca57e3704d7bdf5faa554ea535ce15b1d0
react-native-geolocation: cbd9d6bd06bac411eed2671810f454d4908484a8 react-native-geolocation: cbd9d6bd06bac411eed2671810f454d4908484a8
react-native-image-picker: a6c3d644751a388b0fc8b56822ff7cbd398a3008 react-native-image-picker: 32d1ad2c0024ca36161ae0d5c2117e2d6c441f11
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8 react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6 react-native-safe-area-context: 01158a92c300895d79dee447e980672dc3fb85a6
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93 react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
react-native-tcp-socket: 96a4f104cdcc9c6621aafe92937f163d88447c5b react-native-tcp-socket: 96a4f104cdcc9c6621aafe92937f163d88447c5b
react-native-webview: 162c2f2b14555cb524ac0e3b422a9b66ebceefee react-native-webview: c51f73be304c61d359ec3e7c5e4e8f2c977fd360
React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c React-RCTActionSheet: 53ea72699698b0b47a6421cb1c8b4ab215a774aa
React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0 React-RCTAnimation: 1befece0b5183c22ae01b966f5583f42e69a83c2
React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71 React-RCTBlob: 0b284339cbe4b15705a05e2313a51c6d8b51fa40
React-RCTImage: e70be9b9c74fe4e42d0005f42cace7981c994ac3 React-RCTImage: d1756599ebd4dc2cb19d1682fe67c6b976658387
React-RCTLinking: c1b9739a88d56ecbec23b7f63650e44672ab2ad2 React-RCTLinking: 9af0a51c6d6a4dd1674daadafffc6d03033a6d18
React-RCTNetwork: 73138b6f45e5a2768ad93f3d57873c2a18d14b44 React-RCTNetwork: 332c83929cc5eae0b3bbca4add1d668e1fc18bda
React-RCTSettings: 6e3738a87e21b39a8cb08d627e68c44acf1e325a React-RCTSettings: d6953772cfd55f2c68ad72b7ef29efc7ec49f773
React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d React-RCTText: 65a6de06a7389098ce24340d1d3556015c38f746
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256 React-RCTVibration: 8e9fb25724a0805107fc1acc9075e26f814df454
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3 ReactCommon: 4167844018c9ed375cc01a843e9ee564399e53c3
ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015 ReactNativePrivacySnapshot: cc295e45dc22810e9ff2c93380d643de20a77015
RealmJS: c8645e0d65b676780f7e6c393d327527a2eb15e8 RealmJS: c8645e0d65b676780f7e6c393d327527a2eb15e8
RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa RemobileReactNativeQrcodeLocalImage: 57aadc12896b148fb5e04bc7c6805f3565f5c3fa
RNCAsyncStorage: 3eea36d9460c5159b592f9ecbe5a77f8aca98006 RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
RNCClipboard: 5f3218dcdc28405aa2ae72b78e388f150b826dd3 RNCClipboard: ce9b77f2881948e9e04af84bd70262ab37adb1c1
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459 RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNCPushNotificationIOS: 8025ff0b610d7b28d29ddc1b619cd55814362e4c RNCPushNotificationIOS: 8025ff0b610d7b28d29ddc1b619cd55814362e4c
RNDefaultPreference: 21816c0a6f61a2829ccc0cef034392e9b509ee5f RNDefaultPreference: 21816c0a6f61a2829ccc0cef034392e9b509ee5f
RNDeviceInfo: 910ef2129aff229d8ebd1214bc5f0148017b51d1 RNDeviceInfo: 980848feea8d74412b16f2e3e8758c8294d63ca2
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df
RNGestureHandler: b6b359bb800ae399a9c8b27032bdbf7c18f08a08 RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNInAppBrowser: 6097fbc6b09051b40a6a9ec22caf7af40b115ec0 RNInAppBrowser: 6097fbc6b09051b40a6a9ec22caf7af40b115ec0
RNLocalize: fc27ee5878ce5a3af73873fb2d8e866e0d1e6d84 RNLocalize: 4071198b59b461f3b74eebc5fee8c50f13e39e79
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: 2b31dad120cd1b78e33c6034808561c386a3dddf RNRate: 2b31dad120cd1b78e33c6034808561c386a3dddf
RNReactNativeHapticFeedback: 22c5ecf474428766c6b148f96f2ff6155cd7225e RNReactNativeHapticFeedback: 22c5ecf474428766c6b148f96f2ff6155cd7225e
RNReanimated: 89f5e0a04d1dd52fbf27e7e7030d8f80a646a3fc RNReanimated: 89f5e0a04d1dd52fbf27e7e7030d8f80a646a3fc
RNScreens: 0e91da98ab26d5d04c7b59a9b6bd694124caf88c RNScreens: 0e91da98ab26d5d04c7b59a9b6bd694124caf88c
RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8 RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8
RNSentry: 1e27aeee260eaa833d5c22a4401be47d7b15c4b0 RNSentry: cff88174a0b2e0289b2efe5be1abba1f6c54269d
RNShare: a1d5064df7a0ebe778d001869b3f0a124bf0a491 RNShare: 7a7277f3c313652422d9de072ac50714dff5e8a4
RNSVG: ce9d996113475209013317e48b05c21ee988d42e RNSVG: ce9d996113475209013317e48b05c21ee988d42e
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4 RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
RNWatch: d56d00be49131ee454bb5a4a574f18506c8949e4 RNWatch: d56d00be49131ee454bb5a4a574f18506c8949e4
Sentry: 8fa58a051237554f22507fb483b9a1de0171a2dc Sentry: 8fa58a051237554f22507fb483b9a1de0171a2dc
ToolTipMenu: 4d89d95ddffd7539230bdbe02ee51bbde362e37e ToolTipMenu: 4d89d95ddffd7539230bdbe02ee51bbde362e37e
Yoga: 3ebccbdd559724312790e7742142d062476b698e Yoga: 7d13633d129fd179e01b8953d38d47be90db185a
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: e9c5efd531ca5ac67a4b743a179eeefb322cf387 PODFILE CHECKSUM: 845139ceee01fe141fab93749d578abee11b11c9
COCOAPODS: 1.10.0.beta.2 COCOAPODS: 1.9.3

View file

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <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="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"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -15,59 +17,49 @@
<rect key="frame" x="0.0" y="0.0" width="320" height="100"/> <rect key="frame" x="0.0" y="0.0" width="320" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <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"> <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"/> <fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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"> <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"/> <fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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"> <stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="U8g-sL-Cl4">
<rect key="frame" x="16" y="40" width="35" height="33"/> <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> <constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="35" id="4Nq-zK-gi3"/> <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="35" id="4Nq-zK-gi3"/>
</constraints> </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"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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"> </subviews>
<rect key="frame" x="59" y="46.5" width="14" height="20.5"/> </stackView>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <stackView opaque="NO" contentMode="scaleToFill" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="2BS-g5-Fog">
<nil key="textColor"/> <rect key="frame" x="16" y="34" width="83" height="33"/>
<nil key="highlightedColor"/> <subviews>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrow.up" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="eST-DU-WIK"> <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"/> <rect key="frame" x="0.0" y="1" width="17" height="31"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="17" id="gkK-pz-TDJ"/> <constraint firstAttribute="width" relation="lessThanOrEqual" constant="17" id="gkK-pz-TDJ"/>
</constraints> </constraints>
</imageView> </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="..."> <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="290" y="40" width="14" height="33"/> <rect key="frame" x="25" y="0.0" width="36" 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> <constraints>
<constraint firstAttribute="height" constant="33" id="u9N-WC-Os2"/> <constraint firstAttribute="height" constant="33" id="u9N-WC-Os2"/>
</constraints> </constraints>
@ -75,30 +67,30 @@
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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> </subviews>
<constraints> <constraints>
<constraint firstItem="aqr-Mt-cor" firstAttribute="top" secondItem="aaf-Pc-Y9i" secondAttribute="bottom" constant="11" id="0ca-1C-JqG"/> <constraint firstItem="gm7-vT-KrH" firstAttribute="leading" secondItem="bEQ-e6-Puo" secondAttribute="leading" id="1nN-8G-TB7"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="aaf-Pc-Y9i" secondAttribute="trailing" constant="16" id="197-jr-Kn5"/>
<constraint firstItem="eST-DU-WIK" firstAttribute="centerY" secondItem="aqr-Mt-cor" secondAttribute="centerY" id="2yd-pY-y1Y"/>
<constraint firstItem="bcB-MD-aJf" firstAttribute="leading" secondItem="vU4-uK-6ow" secondAttribute="trailing" constant="8" id="5bB-Zv-Yeq"/> <constraint firstItem="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="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="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="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="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="2BS-g5-Fog" firstAttribute="leading" secondItem="vU4-uK-6ow" secondAttribute="leading" id="ThK-uE-6nD"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="gm7-vT-KrH" secondAttribute="trailing" constant="16" id="OLV-lQ-T8a"/> <constraint firstItem="vU4-uK-6ow" firstAttribute="top" secondItem="2BS-g5-Fog" secondAttribute="bottom" constant="6.5" id="Wib-ev-GFn"/>
<constraint firstItem="gm7-vT-KrH" firstAttribute="centerY" secondItem="bEQ-e6-Puo" secondAttribute="centerY" id="Rle-PT-j9m"/>
<constraint firstItem="gm7-vT-KrH" firstAttribute="leading" secondItem="aqr-Mt-cor" secondAttribute="trailing" constant="8" id="Rtu-ah-AvP"/>
<constraint firstItem="gm7-vT-KrH" firstAttribute="firstBaseline" secondItem="aqr-Mt-cor" secondAttribute="firstBaseline" id="YIV-xq-qlw"/>
<constraint firstItem="bEQ-e6-Puo" firstAttribute="centerY" secondItem="lkL-gv-1a1" secondAttribute="centerY" id="Ys3-7f-RIc"/>
<constraint firstItem="aaf-Pc-Y9i" firstAttribute="leading" secondItem="S3S-Oj-5AN" secondAttribute="leading" constant="16" id="a1b-Yq-aZb"/>
<constraint firstItem="aaf-Pc-Y9i" firstAttribute="top" secondItem="S3S-Oj-5AN" secondAttribute="top" constant="8" id="aIo-h1-w4F"/>
<constraint firstItem="lkL-gv-1a1" firstAttribute="top" secondItem="aaf-Pc-Y9i" secondAttribute="bottom" constant="11" id="dET-8J-W4K"/>
<constraint firstItem="ssy-KU-ocm" firstAttribute="trailing" secondItem="bcB-MD-aJf" secondAttribute="trailing" constant="16" id="kkD-VZ-BAt"/> <constraint firstItem="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> </constraints>
<viewLayoutGuide key="safeArea" id="ssy-KU-ocm"/> <viewLayoutGuide key="safeArea" id="ssy-KU-ocm"/>
</view> </view>
@ -120,6 +112,6 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="arrow.up" catalog="system" width="60" height="64"/> <image name="arrow.up" catalog="system" width="120" height="128"/>
</resources> </resources>
</document> </document>

View file

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.6.0</string> <string>5.6.2</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionMainStoryboard</key> <key>NSExtensionMainStoryboard</key>

View file

@ -31,12 +31,17 @@ class TodayViewController: UIViewController, NCWidgetProviding {
} else { } else {
setLastPriceOutletsHidden(isHidden: true) setLastPriceOutletsHidden(isHidden: true)
} }
if #available(iOSApplicationExtension 13.0, *) {
} else{
self.lastPriceArrowImage.removeFromSuperview()
}
} }
func setLastPriceOutletsHidden(isHidden: Bool) { func setLastPriceOutletsHidden(isHidden: Bool) {
lastPrice.isHidden = isHidden lastPrice.isHidden = isHidden
lastPriceFromLabel.isHidden = isHidden lastPriceFromLabel.isHidden = isHidden
lastPriceArrowImage.isHidden = isHidden lastPriceArrowImage?.isHidden = isHidden
} }
func processRateAndLastUpdate(todayStore: TodayDataStore) { func processRateAndLastUpdate(todayStore: TodayDataStore) {

View file

@ -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: Folgendes ist enthalten:
1 - Security by design 1 - Konzeptionsintegrierte Sicherheit
Free/Libre Open Source Software Quelloffene Software
MIT licensed, you can build it and run it on your own! Made with ReactNative MIT lizenziert. Sie können es selbst kompilieren und betreiben! Erstellt mit ReactNative.
Plausible deniability Plausible Bestreitbarkeit
Password which decrypts fake bitcoin wallets if you are forced to disclose your access Passwort, das falsche Bitcoin-Wallet entschlüsselt, falls Du gezwungen bist, Deinen Zugang preiszugeben
Full encryption Vollständige Verschlüsselung
On top of the iOS multi-layer encryption, we encrypt everything with added passwords Zusätzlich zur mehrschichtigen iOS-Verschlüsselung verschlüsseln wir alles mit zusätzlichen Passwörtern
Full node Full Node
Connect to your Bitcoin full node through Electrum Verbindung zu Deinem Bitcoin Full Node über Electrum
Cold Storage 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 Behalte die Kontrolle
Private keys never leave your device. Private Schlüssel verlassen niemals Dein Gerät.
You control your private keys Du kontrollierst Deine privaten Schlüssel
Flexible fees Definierbare Transaktionsgebühren
Starting from 1 Satoshi. Defined by you, the user Starting from 1 Satoshi. Defined by you, the user
Replace-By-Fee Replace-By-Fee

View file

@ -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 v5.5.9
======= =======
@ -67,33 +88,3 @@ v5.5.7
* FIX: Background had wrong color during loading phase * FIX: Background had wrong color during loading phase
* REF: speeded up large wallets (>3k txs) * REF: speeded up large wallets (>3k txs)
* REF: speedup onchain wallet creation * 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

View file

@ -293,7 +293,7 @@
"details_to": "Ausgehend", "details_to": "Ausgehend",
"details_transaction_details": "Transaktionsdetails", "details_transaction_details": "Transaktionsdetails",
"enable_hw": "Dieses Wallet nutzt keine Hardware-Wallet. Möchtest Du die Verwendung einer Hardware-Wallet aktivieren?", "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", "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_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)", "rbf_title": "TRX-Gebühr erhöhen (RBF)",

View file

@ -8,7 +8,10 @@
"of": "{number} of {total}", "of": "{number} of {total}",
"ok": "OK", "ok": "OK",
"storage_is_encrypted": "Your storage is encrypted. Password is required to decrypt it", "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": { "azteco": {
"codeIs": "Your voucher code is", "codeIs": "Your voucher code is",
@ -170,13 +173,13 @@
"details_next": "Next", "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_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_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_note_placeholder": "note to self",
"details_scan": "Scan", "details_scan": "Scan",
"details_total_exceeds_balance": "The sending amount exceeds the available balance.", "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_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
"details_wallet_selection": "Wallet Selection", "details_wallet_selection": "Wallet Selection",
"dynamic_init": "Initialing", "dynamic_init": "Initializing",
"dynamic_next": "Next", "dynamic_next": "Next",
"dynamic_prev": "Previous", "dynamic_prev": "Previous",
"dynamic_start": "Start", "dynamic_start": "Start",
@ -209,7 +212,8 @@
"qr_error_no_qrcode": "The selected image does not contain a QR Code.", "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.", "qr_error_no_wallet": "The selected file does not contain a wallet that can be imported.",
"success_done": "Done", "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": { "settings": {
"about": "About", "about": "About",
@ -293,7 +297,7 @@
"details_to": "Output", "details_to": "Output",
"details_transaction_details": "Transaction details", "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?", "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", "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_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)", "rbf_title": "Bump fee (RBF)",
@ -371,5 +375,19 @@
"select_wallet": "Select Wallet", "select_wallet": "Select Wallet",
"xpub_copiedToClipboard": "Copied to clipboard.", "xpub_copiedToClipboard": "Copied to clipboard.",
"xpub_title": "wallet XPUB" "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"
} }
} }

View file

@ -94,7 +94,7 @@
"refill_external": "Recargar con una cartera externa", "refill_external": "Recargar con una cartera externa",
"refill_lnd_balance": "Rellenar la cartera de Lightning", "refill_lnd_balance": "Rellenar la cartera de Lightning",
"sameWalletAsInvoiceError": "No puedes pagar una factura con la misma cartera que usaste para crearla.", "sameWalletAsInvoiceError": "No puedes pagar una factura con la misma cartera que usaste para crearla.",
"title": "manejar fondos" "title": "Administrar fondos"
}, },
"lndViewInvoice": { "lndViewInvoice": {
"additional_info": "Información adicional", "additional_info": "Información adicional",
@ -170,7 +170,7 @@
"details_next": "Siguiente", "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_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_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_note_placeholder": "nota personal",
"details_scan": "Escanear", "details_scan": "Escanear",
"details_total_exceeds_balance": "El monto excede el balance disponible.", "details_total_exceeds_balance": "El monto excede el balance disponible.",
@ -184,20 +184,20 @@
"fee_10m": "10m", "fee_10m": "10m",
"fee_1d": "1d", "fee_1d": "1d",
"fee_3h": "3h", "fee_3h": "3h",
"fee_custom": "Custom", "fee_custom": "Personalizada",
"fee_fast": "Fast", "fee_fast": "Rápida",
"fee_medium": "Medium", "fee_medium": "Media",
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte", "fee_replace_min": "La comisión (en satoshis por byte) tiene que ser mayor que {min} sat/byte",
"fee_satbyte": "in sat/byte", "fee_satbyte": "en sat/bye",
"fee_slow": "Slow", "fee_slow": "Lenta",
"header": "Enviar", "header": "Enviar",
"input_clear": "Borrar", "input_clear": "Borrar",
"input_done": "Completado", "input_done": "Completado",
"input_paste": "Pegar", "input_paste": "Pegar",
"input_total": "Total:", "input_total": "Total:",
"open_settings": "Abrir configuración",
"permission_camera_message": "Necesitamos permiso para usar la cámara", "permission_camera_message": "Necesitamos permiso para usar la cámara",
"permission_camera_title": "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_later": "Pregúntame luego",
"permission_storage_message": "BlueWallet necesita permiso para acceder a su almacenamiento para poder guardar esta transacción.", "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", "permission_storage_title": "Permiso para acceder al almacenamiento",
@ -308,6 +308,7 @@
"add_entropy_provide": "Entropía mediante el lanzamiento de dados", "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_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", "add_import_wallet": "Importar cartera",
"import_file": "Importar archivo",
"add_lightning": "Lightning", "add_lightning": "Lightning",
"add_lndhub": "Conecta a tu LDNHub", "add_lndhub": "Conecta a tu LDNHub",
"add_lndhub_error": "La dirección proporcionada no es válida para un nodo LNDHub.", "add_lndhub_error": "La dirección proporcionada no es válida para un nodo LNDHub.",
@ -341,7 +342,6 @@
"import_do_import": "Importar", "import_do_import": "Importar",
"import_error": "Error al importar. Por favor, asegúrate de que los datos introducidos son correctos.", "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_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_imported": "Importado",
"import_scan_qr": "Escanear o importar un archivo", "import_scan_qr": "Escanear o importar un archivo",
"import_success": "Tu cartera ha sido importada.", "import_success": "Tu cartera ha sido importada.",
@ -353,7 +353,7 @@
"list_empty_txs1": "Tus transacciones aparecerán aquí", "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_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": "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_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_error": "Error al intentar importar esta cartera.",
"list_import_problem": "Ha ocurrido un problema al 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_choose": "Elegir foto",
"list_long_clipboard": "Copiar del portapapeles", "list_long_clipboard": "Copiar del portapapeles",
"list_long_scan": "Escanear código QR", "list_long_scan": "Escanear código QR",
"take_photo": "Hacer una foto",
"list_tap_here_to_buy": "Tap here to buy Bitcoin", "list_tap_here_to_buy": "Tap here to buy Bitcoin",
"list_title": "Carteras", "list_title": "Carteras",
"list_tryagain": "Inténtalo otra vez", "list_tryagain": "Inténtalo otra vez",
@ -368,7 +369,6 @@
"select_no_bitcoin": "No hay carteras de Bitcoin disponibles.", "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_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", "select_wallet": "Selecciona una cartera",
"take_photo": "Hacer una foto",
"xpub_copiedToClipboard": "Copiado a portapapeles.", "xpub_copiedToClipboard": "Copiado a portapapeles.",
"xpub_title": "XPUB de la cartera" "xpub_title": "XPUB de la cartera"
} }

View file

@ -7,7 +7,7 @@
"never": "ei koskaan", "never": "ei koskaan",
"of": "{number} / {total}", "of": "{number} / {total}",
"ok": "OK", "ok": "OK",
"storage_is_encrypted": "Tallennustilasi on salattu. Salasana vaaditaan sen purkamiseksi", "storage_is_encrypted": "Tallennustilasi on salattu. Sen purkamiseksi vaaditaan salasana",
"yes": "Kyllä" "yes": "Kyllä"
}, },
"azteco": { "azteco": {
@ -16,12 +16,12 @@
"errorSomething": "Jotain meni pieleen. Onko tämä kuponki edelleen voimassa?", "errorSomething": "Jotain meni pieleen. Onko tämä kuponki edelleen voimassa?",
"redeem": "Lunastus lompakkoon", "redeem": "Lunastus lompakkoon",
"redeemButton": "Lunastus", "redeemButton": "Lunastus",
"success": "Onnistunut", "success": "Onnistui",
"title": "Lunasta Azte.co kuponki" "title": "Lunasta Azte.co kuponki"
}, },
"entropy": { "entropy": {
"save": "Tallenna", "save": "Tallenna",
"title": "Satunnaisuus", "title": "Entropia",
"undo": "Kumoa" "undo": "Kumoa"
}, },
"errors": { "errors": {
@ -31,7 +31,7 @@
}, },
"hodl": { "hodl": {
"are_you_sure_you_want_to_logout": "Haluatko varmasti kirjautua ulos HodlHodl-palvelusta?", "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_address_to": "Vastaanottaja",
"cont_buying": "ostaminen", "cont_buying": "ostaminen",
"cont_cancel": "Peruuta sopimus", "cont_cancel": "Peruuta sopimus",
@ -40,19 +40,19 @@
"cont_chat": "Avaa keskustelu vastapuolen kanssa", "cont_chat": "Avaa keskustelu vastapuolen kanssa",
"cont_how": "Kuinka maksaa", "cont_how": "Kuinka maksaa",
"cont_no": "Sinulla ei ole meneillään sopimuksia", "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_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_selling": "myynti",
"cont_st_completed": "Valmista!", "cont_st_completed": "Valmista!",
"cont_st_in_progress_buyer": "Kolikot ovat sulkutilillä, ole hyvä ja maksa myyjälle", "cont_st_in_progress_buyer": "Kolikot ovat escrow:ssa, 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_enought": "Bitcoinit ovat escrow:ssa! Ole hyvä ja maksa myyjälle\nsovitun maksutavan kautta",
"cont_st_paid_waiting": "Odotetaan myyjän vapauttavan kolikot sulkutilistä", "cont_st_paid_waiting": "Odotetaan myyjän vapauttavan kolikot escrow:sta",
"cont_st_waiting": "Odotetaan myyjän tallettavan bitcoineja sulkutiliin...", "cont_st_waiting": "Odotetaan myyjän tallettavan bitcoineja escrow:iin...",
"cont_title": "Minun sopimukset", "cont_title": "Sopimukseni",
"filter_any": "Mikä tahansa", "filter_any": "Mikä tahansa",
"filter_buying": "Ostaminen", "filter_buying": "Ostaminen",
"filter_country_global": "Globaalit tarjoukset", "filter_country_global": "Maailmanlaajuiset tarjoukset",
"filter_country_near": "Lähellä minua", "filter_country_near": "Lähellä minua",
"filter_currency": "Valuutta", "filter_currency": "Valuutta",
"filter_detail": "Tiedot", "filter_detail": "Tiedot",
@ -63,17 +63,17 @@
"filter_search": "Etsi", "filter_search": "Etsi",
"filter_selling": "Myynti", "filter_selling": "Myynti",
"item_minmax": "Minimi/Maximi", "item_minmax": "Minimi/Maximi",
"item_nooffers": "Ei tarjouksia. Yritä muuttaa \"Lähellä minua\" globaaleiksi tarjouksiksi!", "item_nooffers": "Ei tarjouksia. Yritä muuttaa \"Lähellä minua\" kansainvälisiksi tarjouksiksi!",
"item_rating": "{rating} kaupat", "item_rating": "{rating} vaihdot",
"item_rating_no": "Ei arvosanaa", "item_rating_no": "Ei luokitusta",
"login": "Kirjaudu sisään", "login": "Kirjaudu sisään",
"mycont": "Sopimukseni", "mycont": "Sopimukseni",
"offer_accept": "Hyväksy tarjous", "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_choosemethod": "Valitse maksutapa",
"offer_confirmations": "vahvistukset", "offer_confirmations": "vahvistukset",
"offer_minmax": "minimi/maximi", "offer_minmax": "min / max",
"offer_minutes": "minimi", "offer_minutes": "min",
"offer_promt_fiat": "Kuinka paljon {currency} haluat ostaa?", "offer_promt_fiat": "Kuinka paljon {currency} haluat ostaa?",
"offer_promt_fiat_e": "Esimerkiksi 100", "offer_promt_fiat_e": "Esimerkiksi 100",
"offer_window": "ikkuna", "offer_window": "ikkuna",
@ -92,7 +92,7 @@
"refill_card": "Täytä pankkikortilla", "refill_card": "Täytä pankkikortilla",
"refill_create": "Jatka luomalla Bitcoin-lompakko, jolla voit täyttää sen.", "refill_create": "Jatka luomalla Bitcoin-lompakko, jolla voit täyttää sen.",
"refill_external": "Täytä ulkoisella lompakolla", "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.", "sameWalletAsInvoiceError": "Et voi maksaa laskua samalla lompakolla, jolla se on luotu.",
"title": "hallinnoi varoja" "title": "hallinnoi varoja"
}, },
@ -107,12 +107,12 @@
"wasnt_paid_and_expired": "Tätä laskua ei maksettu, ja se on vanhentunut" "wasnt_paid_and_expired": "Tätä laskua ei maksettu, ja se on vanhentunut"
}, },
"plausibledeniability": { "plausibledeniability": {
"create_fake_storage": "Luo väärennetty tallennustila", "create_fake_storage": "Luo Salattu tallennustila",
"create_password": "Luo salasana", "create_password": "Luo salasana",
"create_password_explanation": "Väärennetyn tallennustilan salasanan ei tule täsmätä oikean tallennustilan salasanan kanssa", "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. Annettaessa BlueWalletiin, se avaa uuden väärennetyn tallennustilan. Se näyttää oikealta kolmannelle osapuolelle, mutta pitää oikean tallennustilasi kolikkoineen turvassa.", "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.", "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", "passwords_do_not_match": "Salasanat eivät täsmää, yritä uudelleen",
"retype_password": "Salasana uudelleen", "retype_password": "Salasana uudelleen",
"success": "Onnistui", "success": "Onnistui",
@ -136,21 +136,21 @@
"header": "Vastaanota" "header": "Vastaanota"
}, },
"send": { "send": {
"broadcastButton": "LÄHETÄ", "broadcastButton": "LÄHETÄ",
"broadcastError": "virhe", "broadcastError": "virhe",
"broadcastNone": "Syötä siirtotapahtuman tiiviste", "broadcastNone": "Syötä siirtotapahtuman tiiviste",
"broadcastPending": "odottaa", "broadcastPending": "odottaa",
"broadcastSuccess": "onnistunut", "broadcastSuccess": "onnistui",
"confirm_header": "Vahvista", "confirm_header": "Vahvista",
"confirm_sendNow": "Lähetä nyt", "confirm_sendNow": "Lähetä nyt",
"create_amount": "Summa", "create_amount": "Summa",
"create_broadcast": "Kuuluta", "create_broadcast": "Lähetä",
"create_copy": "Kopioi ja lähetä myöhemmin", "create_copy": "Kopioi ja lähetä myöhemmin",
"create_details": "Tiedot", "create_details": "Tarkemmat tiedot",
"create_fee": "Siirtokulu", "create_fee": "Siirtokulu",
"create_memo": "Muistio", "create_memo": "Muistio",
"create_satoshi_per_byte": "Satoshia per tavu", "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_to": "Vastaanottaja",
"create_tx_size": "TX koko", "create_tx_size": "TX koko",
"create_verify": "Varmenna coinb.in :ssä", "create_verify": "Varmenna coinb.in :ssä",
@ -158,19 +158,19 @@
"details_add_rec_rem": "Poista Vastaanottaja", "details_add_rec_rem": "Poista Vastaanottaja",
"details_address": "osoite", "details_address": "osoite",
"details_address_field_is_not_valid": "Osoite ei kelpaa", "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": "Käytä Koko Saldo",
"details_adv_full_remove": "Muut vastaanottajat poistetaan tästä siirtotapahtumasta.", "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_adv_import": "Tuo Siirtotapahtuma",
"details_amount_field_is_not_valid": "Määrä ei kelpaa", "details_amount_field_is_not_valid": "Määrä ei kelpaa",
"details_create": "Luo Lasku", "details_create": "Luo Lasku",
"details_error_decode": "Virhe: Bitcoin-osoitetta ei voi muuntaa", "details_error_decode": "Virhe: Bitcoin-osoitetta ei voida dekoodata",
"details_fee_field_is_not_valid": "Siirtomaksu ei kelpaa", "details_fee_field_is_not_valid": "Siirtokulukenttä ei ole pätevä",
"details_next": "Seuraava", "details_next": "Seuraava",
"details_no_maximum": "Valittu lompakko ei tue automaattista enimmäis-saldolaskelmaa. Haluatko varmasti valita tämän lompakon?", "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_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_note_placeholder": "muistiinpano itselle",
"details_scan": "Skannaa", "details_scan": "Skannaa",
"details_total_exceeds_balance": "Lähetettävä summa ylittää katteen", "details_total_exceeds_balance": "Lähetettävä summa ylittää katteen",
@ -181,21 +181,31 @@
"dynamic_prev": "Edellinen", "dynamic_prev": "Edellinen",
"dynamic_start": "Aloita", "dynamic_start": "Aloita",
"dynamic_stop": "Lopeta", "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ä", "header": "Lähetä",
"input_clear": "Tyhjää", "input_clear": "Tyhjää",
"input_done": "Valmis", "input_done": "Valmis",
"input_paste": "Liitä", "input_paste": "Liitä",
"input_total": "Loppusumma:", "input_total": "Yhteensä:",
"permission_camera_message": "Tarvitsemme lupaasi käyttämään kameraasi", "permission_camera_message": "Tarvitsemme lupasi kameran käyttöön",
"permission_camera_title": "Kameran käyttölupa", "permission_camera_title": "Kameran käyttölupa",
"open_settings": "Avaa Asetukset",
"permission_storage_later": "Kysy Minulta Myöhemmin", "permission_storage_later": "Kysy Minulta Myöhemmin",
"permission_storage_message": "BlueWallet tarvitsee luvan käyttää tallennustilaasi tämän siirtotapahtuman tallentamiseksi.", "permission_storage_message": "BlueWallet tarvitsee luvan käyttää tallennustilaasi tämän siirtotapahtuman tallentamiseksi.",
"permission_storage_title": "BlueWallet-Tallennustilan Käyttöoikeus", "permission_storage_title": "BlueWallet Tallennustilan Käyttöoikeus",
"psbt_clipboard": "Kopioi leikepöydälle", "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.", "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_export": "Vie tiedostoon",
"psbt_tx_open": "Avaa allekirjoitettu siirtotapahtuma", "psbt_tx_open": "Avaa Allekirjoitettu Siirtotapahtuma",
"psbt_tx_scan": "Skannaa allekirjoitettu siirtotapahtuma", "psbt_tx_scan": "Skannaa Allekirjoitettu Siirtotapahtuma",
"qr_error_no_qrcode": "Valittu kuva ei sisällä QR-koodia.", "qr_error_no_qrcode": "Valittu kuva ei sisällä QR-koodia.",
"qr_error_no_wallet": "Valittu tiedosto ei sisällä tuotavaa lompakkoa.", "qr_error_no_wallet": "Valittu tiedosto ei sisällä tuotavaa lompakkoa.",
"success_done": "Valmis", "success_done": "Valmis",
@ -203,9 +213,9 @@
}, },
"settings": { "settings": {
"about": "Tietoa", "about": "Tietoa",
"about_awesome": "Rakennettu mahtavasti", "about_awesome": "Mahtavasti rakennettu",
"about_backup": "Varmuuskopioi avaimesi aina!", "about_backup": "Varmuuskopioi aina avaimesi!",
"about_free": "BlueWallet on ilmainen ja avoimen lähdekoodin projekti. Bitcoin-käyttäjien tekemä.", "about_free": "BlueWallet on ilmainen ja avoimen lähdekoodin projekti. Bitcoin käyttäjien tekemä.",
"about_release_notes": "Julkaisutiedot", "about_release_notes": "Julkaisutiedot",
"about_review": "Jätä meille arvostelu", "about_review": "Jätä meille arvostelu",
"about_selftest": "Suorita itsetestaus", "about_selftest": "Suorita itsetestaus",
@ -215,61 +225,61 @@
"advanced_options": "Lisäasetukset", "advanced_options": "Lisäasetukset",
"currency": "Valuutta", "currency": "Valuutta",
"currency_source": "Hinnat saadaan CoinDeskistä", "currency_source": "Hinnat saadaan CoinDeskistä",
"default_desc": "Kun toiminto on poistettu käytöstä, BlueWallet avaa valitun lompakon heti käynnistettäessä.", "default_desc": "Kun on pois käytöstä, BlueWallet avaa valitun lompakon heti käynnistettäessä.",
"default_info": "Oletus kohtaan", "default_info": "Oletustiedot",
"default_title": "Käynnistyksessä", "default_title": "Käynnistettäessä",
"default_wallets": "Näytä Kaikki Lompakot", "default_wallets": "Näytä Kaikki Lompakot",
"electrum_connected": "Yhdistetty", "electrum_connected": "Yhdistetty",
"electrum_connected_not": "Ei yhteyttä", "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_host": "ylläpitäjä, esimerkiksi {example}",
"electrum_port": "TCP-portti, yleensä {example}", "electrum_port": "TCP-portti, yleensä {example}",
"electrum_port_ssl": "SSL-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_saved": "Muutoksesi on tallennettu onnistuneesti. Uudelleenkäynnistys voi olla tarpeen, jotta muutokset tulevat voimaan.",
"electrum_settings": "Electrum-asetukset", "electrum_settings": "Electrum-Asetukset",
"electrum_settings_explain": "Aseta tyhjäksi käyttääksesi oletusasetusta", "electrum_settings_explain": "Jätä tyhjäksi käyttääksesi oletusasetusta",
"electrum_status": "Status", "electrum_status": "Tila",
"encrypt_decrypt": "Pura tallennustilan salaus", "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_del_uninstall": "Poista, jos BlueWallet poistetaan",
"encrypt_enc_and_pass": "Salattu ja Salasanalla suojattu", "encrypt_enc_and_pass": "Salattu ja Salasanalla suojattu",
"encrypt_title": "Tietoturva", "encrypt_title": "Tietoturva",
"encrypt_tstorage": "tallennustila", "encrypt_tstorage": "tallennustila",
"encrypt_use": "Käytä {type}", "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": "Yleinen",
"general_adv_mode": "Kehittynyt tila", "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": "Jatkuvuus",
"general_continuity_e": "Kun tämä asetus on käytössä, voit tarkastella valittuja lompakoita ja siirtotapahtumia muilla Apple iCloud -laitteilla.", "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", "header": "asetukset",
"language": "Kieli", "language": "Kieli",
"language_restart": "Kun valitset uuden kielen, muutoksen voimaantulo edellyttää, että BlueWallet käynnistetään uudelleen.", "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_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.", "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": "Verkko",
"network_broadcast": "Lähetä siirtotapahtuma", "network_broadcast": "Lähetä siirtotapahtuma",
"network_electrum": "Electrum-palvelin", "network_electrum": "Electrum-palvelin",
"not_a_valid_uri": "URI ei kelpaa",
"notifications": "Ilmoitukset",
"password": "Salasana", "password": "Salasana",
"password_explain": "Luo salasana, jota käytät tallennustilan salauksen purkamiseen", "password_explain": "Luo salasana, jota käytät tallennustilan salauksen purkamiseen",
"passwords_do_not_match": "Salasanat eivät täsmää", "passwords_do_not_match": "Salasanat eivät täsmää",
"plausible_deniability": "Uskottava kiistettävyys...", "plausible_deniability": "Uskottava kiistettävyys",
"retype_password": "Salasana uudelleen",
"notifications": "Ilmoitukset",
"save": "Tallenna",
"saved": "Tallennettu",
"not_a_valid_uri": "Ei kelvollinen URI",
"push_notifications": "Push-ilmoitukset", "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": { "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_no": "Tämä siirtotapahtuma ei ole vaihdettavissa",
"cancel_title": "Peruuta tämä siirtotapahtuma (RBF)", "cancel_title": "Peruuta tämä siirtotapahtuma (RBF)",
"cpfp_create": "Luo", "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_no_bump": "Tämä siirtotapahtuma ei ole nostettavissa",
"cpfp_title": "Nosta siirtokuluja (CPFP)", "cpfp_title": "Nosta siirtokuluja (CPFP)",
"details_block": "Lohkon järjestysnumero", "details_block": "Lohkon järjestysnumero",
@ -279,17 +289,17 @@
"details_outputs": "Ulostulot", "details_outputs": "Ulostulot",
"details_received": "Vastaanotettu", "details_received": "Vastaanotettu",
"details_show_in_block_explorer": "Näytä lohkoketjuselaimessa", "details_show_in_block_explorer": "Näytä lohkoketjuselaimessa",
"details_title": "Siirto", "details_title": "Siirtotapahtuma",
"details_to": "Ulostulo", "details_to": "Ulostulo",
"details_transaction_details": "Siirtotapahtuman tiedot", "details_transaction_details": "Siirtotapahtuman tiedot",
"enable_hw": "Tätä lompakkoa ei käytetä yhdessä hardware-lompakon kanssa. Haluatko ottaa hardware-lompakon käyttöön?", "enable_hw": "Tätä lompakkoa ei ole käytetty yhdessä hardware-lompakon kanssa. Haluatko ottaa hardware-lompakon käyttöön?",
"list_conf": "conf", "list_conf": "conf: {number}",
"list_title": "siirrot", "list_title": "siirtotapahtumat",
"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 - Replace By Fee - Korvattavissa korkeammilla kuluilla.",
"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.",
"rbf_title": "Nosta siirtokuluja (RBF)", "rbf_title": "Nosta siirtokuluja (RBF)",
"status_bump": "Nosta siirtokuluja", "status_bump": "Nosta siirtokuluja",
"status_cancel": "Peruuta siirtotapahtuma" "status_cancel": "Peruuta Siirtotapahtuma",
"transactions_count": "siirtotapahtumien määrä"
}, },
"wallets": { "wallets": {
"add_bitcoin": "Bitcoin", "add_bitcoin": "Bitcoin",
@ -298,8 +308,9 @@
"add_entropy_provide": "Hanki entropia nopanheiton kautta", "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_entropy_remain": "{gen} tavua luotua entropiaa. Jäljellä olevat {rem} tavut saadaan Järjestelmän satunnaislukugeneraattorilta.",
"add_import_wallet": "Tuo lompakko", "add_import_wallet": "Tuo lompakko",
"add_lightning": "Salama", "import_file": "Tuo tiedosto",
"add_lndhub": "Yhdistä LNDHubiisi", "add_lightning": "Lightning",
"add_lndhub": "Yhdistä LNDHub:iisi",
"add_lndhub_error": "Annettu solmun osoite ei ole kelvollinen LNDHub-solmu.", "add_lndhub_error": "Annettu solmun osoite ei ole kelvollinen LNDHub-solmu.",
"add_lndhub_placeholder": "solmusi osoite", "add_lndhub_placeholder": "solmusi osoite",
"add_or": "tai", "add_or": "tai",
@ -312,14 +323,14 @@
"details_connected_to": "Yhdistetty", "details_connected_to": "Yhdistetty",
"details_del_wb": "Lompakon saldo", "details_del_wb": "Lompakon saldo",
"details_del_wb_err": "Annettu saldo ei vastaa tämän lompakon saldoa. Yritä uudelleen", "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": "Poista",
"details_delete_wallet": "Poista lompakko", "details_delete_wallet": "Poista lompakko",
"details_display": "näkyy lompakkolistassa", "details_display": "näkyy lompakkojen listassa",
"details_export_backup": "Vie / varmuuskopioi", "details_export_backup": "Vie / varmuuskopioi",
"details_marketplace": "Kauppapaikka", "details_marketplace": "Kauppapaikka",
"details_master_fingerprint": "Isäntä sormenjälki", "details_master_fingerprint": "ä sormenjälki",
"details_no_cancel": "En, peruuta", "details_no_cancel": "Ei, peruuta",
"details_save": "Tallenna", "details_save": "Tallenna",
"details_show_xpub": "Näytä lompakon XPUB", "details_show_xpub": "Näytä lompakon XPUB",
"details_title": "Lompakko", "details_title": "Lompakko",
@ -332,26 +343,26 @@
"import_error": "Tuonti epäonnistui. Varmista, että annettu tieto on oikein", "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_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_imported": "Tuotu",
"import_scan_qr": "tai skannaa QR-koodi?", "import_scan_qr": "Skannaa tai tuo tiedosto",
"import_success": "Onnistui", "import_success": "Lompakkosi tuonti onnistui.",
"import_title": "tuo", "import_title": "tuo",
"list_create_a_button": "Lisää nyt", "list_create_a_button": "Lisää nyt",
"list_create_a_wallet": "Lisää lompakko", "list_create_a_wallet": "Lisää lompakko",
"list_create_a_wallet1": "Se on ilmaista ja voit luoda", "list_create_a_wallet1": "Se on ilmaista ja voit luoda",
"list_create_a_wallet2": "niin monta kuin haluat", "list_create_a_wallet2": "niin monta kuin haluat",
"list_empty_txs1": "Siirtosi näkyvät tässä,", "list_empty_txs1": "Siirtotapahtumasi 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_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": "Aloita lompakostasi",
"list_empty_txs2_lightning": "Aloita sen käyttäminen napsauttamalla \"hallinnoi varoja\" ja lisää saldoasi.", "list_empty_txs2_lightning": "Aloita sen käyttäminen napsauttamalla \"hallinnoi varoja\" ja lisää saldoasi.\n",
"list_header": "Lompakko edustaa salaista paria (yksityinen avain) ja osoitetta, jonka voit jakaa vastaanottaaksesi kolikoita.", "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_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_latest_transaction": "viimeisin siirto",
"list_long_choose": "Valitse Kuva", "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", "list_long_scan": "Skannaa QR-koodi",
"take_photo": "Ota Kuva", "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_title": "lompakot",
"list_tryagain": "Yritä uudelleen", "list_tryagain": "Yritä uudelleen",
"reorder_title": "Järjestele Lompakot", "reorder_title": "Järjestele Lompakot",

View file

@ -1,12 +1,12 @@
{ {
"_": { "_": {
"bad_password": "סיסמה שגויה, נסו שוב.", "bad_password": "סיסמה שגויה, אנא נסו שוב.",
"cancel": "ביטול", "cancel": "ביטול",
"continue": "המשך", "continue": "המשך",
"enter_password": זינו סיסמה", "enter_password": כניסו סיסמה",
"never": "אף פעם", "never": "אף פעם",
"of": "{number} של {total}", "of": "{number} מתוך {total}",
"ok": וקיי", "ok": ישור",
"storage_is_encrypted": "האחסון שלך מוצפן, נדרשת סיסמה לפתיחה", "storage_is_encrypted": "האחסון שלך מוצפן, נדרשת סיסמה לפתיחה",
"yes": "כן" "yes": "כן"
}, },
@ -33,35 +33,35 @@
"are_you_sure_you_want_to_logout": "האם אתם בטוחים שאתם רוצים להתנתק מ- HodlHodl?", "are_you_sure_you_want_to_logout": "האם אתם בטוחים שאתם רוצים להתנתק מ- HodlHodl?",
"cont_address_escrow": "פקדון", "cont_address_escrow": "פקדון",
"cont_address_to": "עבור", "cont_address_to": "עבור",
"cont_buying": ונה", "cont_buying": נייה",
"cont_cancel": "ביטול חוזה", "cont_cancel": "ביטול חוזה",
"cont_cancel_q": "האם אתם בטוחים שאתם רוצים לבטל חוזה זה?", "cont_cancel_q": "האם אתם בטוחים שאתם רוצים לבטל חוזה זה?",
"cont_cancel_y": "כן, בטל חוזה.", "cont_cancel_y": "כן, בטל חוזה.",
"cont_chat": "פתחו שיחה עם הצד השני", "cont_chat": "פתחו שיחה עם הצד השני",
"cont_how": "איך לשלם", "cont_how": "איך לשלם",
"cont_no": "אין לך שום חוזה פעיל.", "cont_no": "אין לך שום חוזה פעיל.",
"cont_paid": "סמן חוזה כשולם", "cont_paid": "סימון חוזה כשולם",
"cont_paid_e": "עשה זאת רק אם שילמת למוכר עם אמצאי התשלום המוסכם", "cont_paid_e": "עשו זאת רק אם שילמתם למוכר עם אמצעי התשלום המוסכם",
"cont_paid_q": "האם אתם בטוחים שאתם רוצים לסמן חוזה זה כשולם?", "cont_paid_q": "האם אתם בטוחים שאתם רוצים לסמן חוזה זה כשולם?",
"cont_selling": וכר", "cont_selling": כירה",
"cont_st_completed": "הכל בוצע", "cont_st_completed": "הכל בוצע!",
"cont_st_in_progress_buyer": "המטבעות נעולים בפיקדון, אנא שלמו למוכר", "cont_st_in_progress_buyer": "המטבעות נעולים בפיקדון, אנא שלמו למוכר",
"cont_st_paid_enought": "הביטקוין נעול בפיקדון! אנא שלמו למוכר\nבאמצעי התשלום המוסכם", "cont_st_paid_enought": "הביטקוין נעול בפיקדון! אנא שלמו למוכר\nבאמצעי התשלום המוסכם",
"cont_st_paid_waiting": "מחכה לשחרור המטבעות מהפיקדון על-ידי המוכר ", "cont_st_paid_waiting": "מחכה לשחרור המטבעות מהפיקדון על-ידי המוכר ",
"cont_st_waiting": "מחכה להפקדת המטבעות בפיקדון על-ידי המוכר ...", "cont_st_waiting": "מחכה להפקדת המטבעות בפיקדון על-ידי המוכר ...",
"cont_title": "החוזים שלי", "cont_title": "החוזים שלי",
"filter_any": "הכל", "filter_any": "הכל",
"filter_buying": ונה", "filter_buying": נייה",
"filter_country_global": "הצעות גלובליות", "filter_country_global": "הצעות גלובליות",
"filter_country_near": "לידי", "filter_country_near": "לידי",
"filter_currency": "מטבע", "filter_currency": "מטבע",
"filter_detail": "פרטים", "filter_detail": "פרטים",
"filter_filters": "מסננים", "filter_filters": "מסננים",
"filter_iambuying": "אני רוצה לקנות ביטקוין", "filter_iambuying": "אני רוצה לקנות ביטקוין",
"filter_iamselling": "רוצה למכור ביטקוין", "filter_iamselling": "אני רוצה למכור ביטקוין",
"filter_method": "אמצעי תשלום", "filter_method": "אמצעי תשלום",
"filter_search": "חיפוש", "filter_search": "חיפוש",
"filter_selling": וכר", "filter_selling": כירה",
"item_minmax": "מינימום/מקסימום", "item_minmax": "מינימום/מקסימום",
"item_nooffers": "אין הצעות. נסו לשנות \"לידי\" להצעות גלובליות!", "item_nooffers": "אין הצעות. נסו לשנות \"לידי\" להצעות גלובליות!",
"item_rating": "{rating} החלפות", "item_rating": "{rating} החלפות",
@ -113,8 +113,8 @@
"help": "בנסיבות מסוימות, יתכן ותאולצו לחשוף את סיסמת הארנק. כדי לשמור על המטבעות בטוחים, BlueWallet מאפשר ליצור אחסון מוצפן נוסף, עם סיסמה שונה. תחת לחץ, תוכלו לחשוף את סיסמה זו לצד שלישי. אם הסיסמה תוכנס ל- BlueWallet, אחסון 'מזויף' חדש יפתח. מצב זה יראה לגיטימי לצד השלישי, בזמן שהאחסון הראשי ישמר בסודיות עם כשהמטבעות מוגנים.", "help": "בנסיבות מסוימות, יתכן ותאולצו לחשוף את סיסמת הארנק. כדי לשמור על המטבעות בטוחים, BlueWallet מאפשר ליצור אחסון מוצפן נוסף, עם סיסמה שונה. תחת לחץ, תוכלו לחשוף את סיסמה זו לצד שלישי. אם הסיסמה תוכנס ל- BlueWallet, אחסון 'מזויף' חדש יפתח. מצב זה יראה לגיטימי לצד השלישי, בזמן שהאחסון הראשי ישמר בסודיות עם כשהמטבעות מוגנים.",
"help2": "האחסון החדש יתפקד באופן מלא, ותוכלו לאחסן בו סכומים מינימליים כך שיראה יותר מהימן.", "help2": "האחסון החדש יתפקד באופן מלא, ותוכלו לאחסן בו סכומים מינימליים כך שיראה יותר מהימן.",
"password_should_not_match": "הסיסמה כבר בשימוש. אנא נסו סיסמה אחרת.", "password_should_not_match": "הסיסמה כבר בשימוש. אנא נסו סיסמה אחרת.",
"passwords_do_not_match": "סיסמה אינה תואמת, נסו שוב", "passwords_do_not_match": "סיסמאות אינן תואמות, נסו שוב",
"retype_password": "סיסמה בשנית", "retype_password": "הכניסו שוב סיסמה",
"success": "הצלחה", "success": "הצלחה",
"title": "הכחשה סבירה" "title": "הכחשה סבירה"
}, },
@ -129,16 +129,16 @@
"title": "ארנקך נוצר..." "title": "ארנקך נוצר..."
}, },
"receive": { "receive": {
"details_create": "צרו", "details_create": "יצירה",
"details_label": "תיאור", "details_label": "תיאור",
"details_setAmount": "בחר כמות", "details_setAmount": "קבלה עם סכום",
"details_share": "שיתוף", "details_share": "שיתוף",
"header": "קבלה" "header": "קבלה"
}, },
"send": { "send": {
"broadcastButton": "שדר", "broadcastButton": "שדר",
"broadcastError": "שגיאה", "broadcastError": "שגיאה",
"broadcastNone": "גיבוב קלט העסקה", "broadcastNone": "קלט גיבוב העברה",
"broadcastPending": "ממתין", "broadcastPending": "ממתין",
"broadcastSuccess": "הצלחה", "broadcastSuccess": "הצלחה",
"confirm_header": "אישור", "confirm_header": "אישור",
@ -150,7 +150,7 @@
"create_fee": "עמלה", "create_fee": "עמלה",
"create_memo": "תזכיר", "create_memo": "תזכיר",
"create_satoshi_per_byte": "סאטושי לבייט", "create_satoshi_per_byte": "סאטושי לבייט",
"create_this_is_hex": "זוהי העסקה החתומה שלך, מוכנה לשידור לרשת.", "create_this_is_hex": "זוהי ההעברה שלך, חתומה ומוכנה לשידור לרשת.",
"create_to": "עבור", "create_to": "עבור",
"create_tx_size": "גודל ההעברה", "create_tx_size": "גודל ההעברה",
"create_verify": "אמתו ב- coinb.in", "create_verify": "אמתו ב- coinb.in",
@ -158,11 +158,11 @@
"details_add_rec_rem": "הסרת נמען", "details_add_rec_rem": "הסרת נמען",
"details_address": "כתובת", "details_address": "כתובת",
"details_address_field_is_not_valid": "שדה כתובת לא תקין", "details_address_field_is_not_valid": "שדה כתובת לא תקין",
"details_adv_fee_bump": "אפשר העלאת עמלה", "details_adv_fee_bump": "אפשר הקפצת עמלה",
"details_adv_full": "שימוש בכל היתרה", "details_adv_full": "שימוש בכל היתרה",
"details_adv_full_remove": "שאר הנמענים ימחקו מהעברה זו.", "details_adv_full_remove": "שאר הנמענים ימחקו מהעברה זו.",
"details_adv_full_sure": "האם אתם בטוחים שתרצו להשתמש בכל יתרת הארנק בשביל עסקה זאת?", "details_adv_full_sure": "האם אתם בטוחים שתרצו להשתמש בכל יתרת הארנק בשביל העברה זאת?",
"details_adv_import": "יבוא עסקה", "details_adv_import": "יבוא העברה",
"details_amount_field_is_not_valid": "שדה סכום אינו תקין", "details_amount_field_is_not_valid": "שדה סכום אינו תקין",
"details_create": "יצירת קבלה", "details_create": "יצירת קבלה",
"details_error_decode": "שגיאה: לא ניתן לפענח כתובת ביטקוין", "details_error_decode": "שגיאה: לא ניתן לפענח כתובת ביטקוין",
@ -170,8 +170,8 @@
"details_next": "הבא", "details_next": "הבא",
"details_no_maximum": "הארנק הנבחר אינו תומך בחישוב יתרה מקסימלית אוטומטי. האם לבחור בארנק זה בכל זאת?", "details_no_maximum": "הארנק הנבחר אינו תומך בחישוב יתרה מקסימלית אוטומטי. האם לבחור בארנק זה בכל זאת?",
"details_no_multiple": "הארנק הנבחר אינו תומך בשליחת ביטקוין לנמענים מרובים. האם לבחור בארנק זה בכל זאת?", "details_no_multiple": "הארנק הנבחר אינו תומך בשליחת ביטקוין לנמענים מרובים. האם לבחור בארנק זה בכל זאת?",
"details_no_signed_tx": "הקובץ הנבחר אינו מכיל העברה חתומה שניתן לייבא.", "details_no_signed_tx": "הקובץ הנבחר אינו מכיל העברה שניתן לייבא.",
"details_note_placeholder": "פתק לעצמך", "details_note_placeholder": "הערה לעצמך",
"details_scan": "סריקה", "details_scan": "סריקה",
"details_total_exceeds_balance": "הסכום לשליחה חורג מהיתרה הזמינה.", "details_total_exceeds_balance": "הסכום לשליחה חורג מהיתרה הזמינה.",
"details_wallet_before_tx": "לפני יצירת העברה, עליך להוסיף ארנק ביטקוין.", "details_wallet_before_tx": "לפני יצירת העברה, עליך להוסיף ארנק ביטקוין.",
@ -181,16 +181,16 @@
"dynamic_prev": "הקודם", "dynamic_prev": "הקודם",
"dynamic_start": "התחל", "dynamic_start": "התחל",
"dynamic_stop": "עצור", "dynamic_stop": "עצור",
"fee_10m": "10m", "fee_10m": "10 ד'",
"fee_1d": "1d", "fee_1d": "1 י'",
"fee_3h": "3h", "fee_3h": "3 ש'",
"fee_custom": "Custom", "fee_custom": "אחר",
"fee_fast": "Fast", "fee_fast": "מהיר",
"fee_medium": "Medium", "fee_medium": "בינוני",
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte", "fee_replace_min": "סך כל העמלה (סאטושי לבייט) שתרצו לשלם צריך להיות גבוה מ- {min} סאט/בייט",
"fee_satbyte": "in sat/byte", "fee_satbyte": "בסאטושי/בייט",
"fee_slow": "Slow", "fee_slow": "איטי",
"header": "שלח", "header": "שליחה",
"input_clear": "נקה", "input_clear": "נקה",
"input_done": "בוצע", "input_done": "בוצע",
"input_paste": "הדבק", "input_paste": "הדבק",
@ -201,11 +201,11 @@
"permission_storage_later": "שאל אותי מאוחר יותר", "permission_storage_later": "שאל אותי מאוחר יותר",
"permission_storage_message": "BlueWallet צריך את הרשאתך לגשת לאחסון שלך כדי לשמור את ההעברה.", "permission_storage_message": "BlueWallet צריך את הרשאתך לגשת לאחסון שלך כדי לשמור את ההעברה.",
"permission_storage_title": "הרשאת גישה לאחסון BlueWallet", "permission_storage_title": "הרשאת גישה לאחסון BlueWallet",
"psbt_clipboard": "העתק ללוח", "psbt_clipboard": "העתקה ללוח",
"psbt_this_is_psbt": "זוהי העברת ביטקוין חתומה חלקית (PSBT). אנא סיימו את תהליך החתימה בארנק החומרה שלכם.", "psbt_this_is_psbt": "זוהי העברת ביטקוין חתומה חלקית (PSBT). אנא סיימו את תהליך החתימה בארנק החומרה שלכם.",
"psbt_tx_export": "יצא לקובץ", "psbt_tx_export": "יצא לקובץ",
"psbt_tx_open": "פתחו עסקה חתומה", "psbt_tx_open": "פתחו העברה חתומה",
"psbt_tx_scan": "סרקו עסקה חתומה", "psbt_tx_scan": "סרקו העברה חתומה",
"qr_error_no_qrcode": "התמונה אינה מכילה קוד QR.", "qr_error_no_qrcode": "התמונה אינה מכילה קוד QR.",
"qr_error_no_wallet": "הקובץ הנבחר אינו מכיל ארנק שניתן לייבא.", "qr_error_no_wallet": "הקובץ הנבחר אינו מכיל ארנק שניתן לייבא.",
"success_done": "בוצע", "success_done": "בוצע",
@ -215,7 +215,7 @@
"about": "אודות", "about": "אודות",
"about_awesome": "נבנה בעזרת", "about_awesome": "נבנה בעזרת",
"about_backup": "תמיד גבו את המפתחות שלכם!", "about_backup": "תמיד גבו את המפתחות שלכם!",
"about_free": "BlueWallet הינו פרויקט חופשי בקוד פתוח. נוצר על ידי קהילת ביטקוין.", "about_free": "פרויקט BlueWallet הינו פרויקט חופשי בקוד פתוח. נוצר על ידי קהילת ביטקוין.",
"about_release_notes": "הערות שחרור", "about_release_notes": "הערות שחרור",
"about_review": "השאירו לנו ביקורת", "about_review": "השאירו לנו ביקורת",
"about_selftest": "הרץ בדיקה עצמית", "about_selftest": "הרץ בדיקה עצמית",
@ -252,7 +252,7 @@
"general_adv_mode_e": "כאשר מופעל, אפשרויות מתקדמות יוצגו כגון סוגי ארנק שונים, אפשרות להתחבר לצומת LNDHub לפי רצונך ואנטרופיה מותאמת בתהליך יצירת ארנק.", "general_adv_mode_e": "כאשר מופעל, אפשרויות מתקדמות יוצגו כגון סוגי ארנק שונים, אפשרות להתחבר לצומת LNDHub לפי רצונך ואנטרופיה מותאמת בתהליך יצירת ארנק.",
"general_continuity": "המשכיות", "general_continuity": "המשכיות",
"general_continuity_e": "כאשר מופעל, תוכלו לצפות בארנקים והעברות נבחרים, באמצעות מכשירי Apple iCloud מחוברים אחרים.", "general_continuity_e": "כאשר מופעל, תוכלו לצפות בארנקים והעברות נבחרים, באמצעות מכשירי Apple iCloud מחוברים אחרים.",
"groundcontrol_explanation": "GroundControl הינו שרת התראות חופשי בקוד פתוח בשביל ארנקי ביטקוין. באפשרותך להתקין שרת GroundControl אישי ולהכניס את ה- URL שלו כאן, כדי לא להסתמך על התשתית של BlueWallet. השאירו ריק כדי להשתמש בברירת המחדל", "groundcontrol_explanation": "שרת GroundControl הינו שרת התראות חופשי בקוד פתוח בשביל ארנקי ביטקוין. באפשרותך להתקין שרת GroundControl אישי ולהכניס את ה- URL שלו כאן, כדי לא להסתמך על התשתית של BlueWallet. השאירו ריק כדי להשתמש בברירת המחדל",
"header": "הגדרות", "header": "הגדרות",
"language": "שפה", "language": "שפה",
"language_restart": "כאשר בוחרים שפה חדשה, יתכן ותדרש הפעלה מחדש של BlueWallet כדי שהשינוי ייכנס לתוקף.", "language_restart": "כאשר בוחרים שפה חדשה, יתכן ותדרש הפעלה מחדש של BlueWallet כדי שהשינוי ייכנס לתוקף.",
@ -261,7 +261,7 @@
"lightning_settings": "הגדרות ברק", "lightning_settings": "הגדרות ברק",
"lightning_settings_explain": "כדי להתחבר לצומת LND אישי, אנא התקינו LndHub והכניסו את כתובת ה- URL שלו כאן בהגדרות. השאירו ריק כדי להשתמש ב- LNDHub של BlueWallet (lndhub.io). ארנקים שנוצרו אחרי שמירת השינויים יתחברו ל- LNDHub שהוגדר.", "lightning_settings_explain": "כדי להתחבר לצומת LND אישי, אנא התקינו LndHub והכניסו את כתובת ה- URL שלו כאן בהגדרות. השאירו ריק כדי להשתמש ב- LNDHub של BlueWallet (lndhub.io). ארנקים שנוצרו אחרי שמירת השינויים יתחברו ל- LNDHub שהוגדר.",
"network": "רשת", "network": "רשת",
"network_broadcast": "שידור עסקה", "network_broadcast": "שידור העברה",
"network_electrum": "שרת אלקטרום", "network_electrum": "שרת אלקטרום",
"not_a_valid_uri": "URI לא תקני", "not_a_valid_uri": "URI לא תקני",
"notifications": "התראות", "notifications": "התראות",
@ -270,16 +270,16 @@
"passwords_do_not_match": "סיסמאות לא תואמות", "passwords_do_not_match": "סיסמאות לא תואמות",
"plausible_deniability": "הכחשה סבירה", "plausible_deniability": "הכחשה סבירה",
"push_notifications": "התראות", "push_notifications": "התראות",
"retype_password": "סיסמה בשנית", "retype_password": "הכניסו שוב סיסמה",
"save": "שמירה", "save": "שמירה",
"saved": "נשמר" "saved": "נשמר"
}, },
"transactions": { "transactions": {
"cancel_explain": "אנו נחליף את ההעברה הזאת באחת עם עמלה גבוהה יותר. פעולה זאת למעשה מבטלת את העברה. פעולה זאת נקראת RBF - Replace By Fee.", "cancel_explain": "אנו נחליף את ההעברה הזאת באחת עם עמלה גבוהה יותר. פעולה זאת למעשה מבטלת את העברה. פעולה זאת נקראת RBF - Replace By Fee.",
"cancel_no": "העברה זאת אינה ניתנת להחלפה", "cancel_no": "העברה זאת אינה ניתנת להחלפה",
"cancel_title": "בטל עסקה זאת (RBF)", "cancel_title": "בטל העברה זאת (RBF)",
"cpfp_create": "צור", "cpfp_create": "צור",
"cpfp_exp": "אנו ניצור העברה נוספת שתשתמש בעודף שנשאר מההעברה הקודמת שבוצעה. סך כל העמלה יהיה גבוה יותר מעמלת העסקה המקורית, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.", "cpfp_exp": "אנו ניצור העברה נוספת שתשתמש בעודף שנשאר מההעברה הקודמת שבוצעה. סך כל העמלה יהיה גבוה יותר מעמלת ההעברה המקורית, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.",
"cpfp_no_bump": "עמלת העברה זו אינה ניתנת להעלאה", "cpfp_no_bump": "עמלת העברה זו אינה ניתנת להעלאה",
"cpfp_title": "הקפץ עמלה (CPFP)", "cpfp_title": "הקפץ עמלה (CPFP)",
"details_block": "גובה הבלוק", "details_block": "גובה הבלוק",
@ -289,23 +289,23 @@
"details_outputs": "פלטים", "details_outputs": "פלטים",
"details_received": "התקבל", "details_received": "התקבל",
"details_show_in_block_explorer": "צפייה בסייר בלוקים", "details_show_in_block_explorer": "צפייה בסייר בלוקים",
"details_title": "עסקה", "details_title": "העברה",
"details_to": "פלט", "details_to": "פלט",
"details_transaction_details": "פרטי עסקה", "details_transaction_details": "פרטי העברה",
"enable_hw": "ארנק זה אינו נמצא בשימוש בצירוף ארנק חומרה. האם תרצו לאפשר שימוש בארנק חומרה?", "enable_hw": "ארנק זה אינו נמצא בשימוש בצירוף ארנק חומרה. האם תרצו לאפשר שימוש בארנק חומרה?",
"list_conf": "אישורים", "list_conf": "אישורים",
"list_title": "תנועות", "list_title": "תנועות",
"rbf_explain": "אנו נחליף את העברה זו בהעברה עם עמלה גבוהה יותר, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.", "rbf_explain": "אנו נחליף את העברה זו בהעברה עם עמלה גבוהה יותר, כך שמהירות קבלת האישור אמורה לעלות. פעולה זאת נקראת CPFP - Child Pays For Parent.",
"rbf_title": "העלאת עמלה (RBF)", "rbf_title": "העלאת עמלה (RBF)",
"status_bump": "העלאת עמלה", "status_bump": "העלאת עמלה",
"status_cancel": "ביטול עסקה", "status_cancel": "ביטול העברה",
"transactions_count": "מספר תנועות" "transactions_count": "מספר תנועות"
}, },
"wallets": { "wallets": {
"add_bitcoin": "ביטקוין", "add_bitcoin": "ביטקוין",
"add_create": "יצירה", "add_create": "יצירה",
"add_entropy_generated": "{gen} בייטים של אנתרופיה", "add_entropy_generated": "{gen} בייטים של אנתרופיה",
"add_entropy_provide": "ספקו אנטרופיה בעזרת קוביות ", "add_entropy_provide": "ספקו אנטרופיה על ידי הטלת קוביות ",
"add_entropy_remain": "{gen} בייטים של אנתרופיה. שאר {rem} בייטים יתקבלו ממחולל מספרים רנדומליים של המערכת.", "add_entropy_remain": "{gen} בייטים של אנתרופיה. שאר {rem} בייטים יתקבלו ממחולל מספרים רנדומליים של המערכת.",
"add_import_wallet": "יבוא ארנק", "add_import_wallet": "יבוא ארנק",
"import_file": "יבוא קובץ", "import_file": "יבוא קובץ",
@ -341,7 +341,7 @@
"export_title": "יצוא ארנק", "export_title": "יצוא ארנק",
"import_do_import": "יבוא", "import_do_import": "יבוא",
"import_error": "היבוא כשל. אנא וודאו שהמידע שסופק תקין.", "import_error": "היבוא כשל. אנא וודאו שהמידע שסופק תקין.",
"import_explanation": "כתבו כאן את מילות הגיבוי, המפתח הפרטי, WIF או כל דבר אחר שברשותך. BlueWallet ישתדל לנחש את הפורמט הנכון וייבא את ארנק.", "import_explanation": "כתבו כאן את מילות הגיבוי, המפתח הפרטי, WIF או כל דבר אחר שברשותכם. BlueWallet ישתדל לנחש את הפורמט הנכון וייבא את ארנק.",
"import_imported": "יובא", "import_imported": "יובא",
"import_scan_qr": "סריקה או יבוא קובץ", "import_scan_qr": "סריקה או יבוא קובץ",
"import_success": "ארנקך יובא בהצלחה.", "import_success": "ארנקך יובא בהצלחה.",
@ -352,14 +352,14 @@
"list_create_a_wallet2": "כמה שתרצו", "list_create_a_wallet2": "כמה שתרצו",
"list_empty_txs1": "התנועות שלך יופיעו פה", "list_empty_txs1": "התנועות שלך יופיעו פה",
"list_empty_txs1_lightning": "ארנק הברק משמש לתשלומים יומיומיים. העברות זולות בצורה לא הגיונית המתבצעות במהירות הבזק.", "list_empty_txs1_lightning": "ארנק הברק משמש לתשלומים יומיומיים. העברות זולות בצורה לא הגיונית המתבצעות במהירות הבזק.",
"list_empty_txs2": "התחילו שימוש בארנק", "list_empty_txs2": "עם תחילת השימוש בארנק",
"list_empty_txs2_lightning": "\nלהתחלת שימוש לחצו על \"ניהול כספים\" ומלאו את היתרה", "list_empty_txs2_lightning": "\nלהתחלת שימוש לחצו על \"ניהול כספים\" ומלאו את היתרה",
"list_header": "ארנק מייצר צמד מפתחות, מפתח פרטי וכתובת שאותה ניתן לשתף כדי לקבל מטבעות.", "list_header": "ארנק מייצר צמד מפתחות, מפתח פרטי וכתובת שאותה ניתן לשתף כדי לקבל מטבעות.",
"list_import_error": "התרחשה שגיאה בניסיון לייבא ארנק זה.", "list_import_error": "התרחשה שגיאה בניסיון לייבא ארנק זה.",
"list_import_problem": "לא ניתן לייבא ארנק הזה", "list_import_problem": "לא ניתן לייבא ארנק הזה",
"list_latest_transaction": "עסקה אחרונה", "list_latest_transaction": "העברה אחרונה",
"list_long_choose": "בחר תמונה", "list_long_choose": "בחר תמונה",
"list_long_clipboard": "העתק מלוח", "list_long_clipboard": "העתקה מלוח",
"list_long_scan": "סריקת קוד QR", "list_long_scan": "סריקת קוד QR",
"take_photo": "צילום תמונה", "take_photo": "צילום תמונה",
"list_tap_here_to_buy": "קנו ביטקוין", "list_tap_here_to_buy": "קנו ביטקוין",

View file

@ -170,7 +170,7 @@
"details_next": "Következő", "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_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_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_note_placeholder": "saját megjegyzés",
"details_scan": "Szkennelés", "details_scan": "Szkennelés",
"details_total_exceeds_balance": "A megadott összeg nagyobb, mint a tárca elérhető egyenlege", "details_total_exceeds_balance": "A megadott összeg nagyobb, mint a tárca elérhető egyenlege",
@ -184,20 +184,20 @@
"fee_10m": "10m", "fee_10m": "10m",
"fee_1d": "1d", "fee_1d": "1d",
"fee_3h": "3h", "fee_3h": "3h",
"fee_custom": "Custom", "fee_custom": "beállított",
"fee_fast": "Fast", "fee_fast": "Gyors",
"fee_medium": "Medium", "fee_medium": "Közepes",
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte", "fee_replace_min": "A teljes tranzakciós díj (satoshi / byte) amit fizetsz, magasabb lesz, mint a minimum díj.",
"fee_satbyte": "in sat/byte", "fee_satbyte": "satoshi/byte",
"fee_slow": "Slow", "fee_slow": "Lassú",
"header": "Küldés", "header": "Küldés",
"input_clear": "Törlés", "input_clear": "Törlés",
"input_done": "Kész", "input_done": "Kész",
"input_paste": "beillesztés", "input_paste": "beillesztés",
"input_total": "Összesen:", "input_total": "Összesen:",
"open_settings": "Beállítások megnyitása",
"permission_camera_message": "Kamera használat engedélyezése", "permission_camera_message": "Kamera használat engedélyezése",
"permission_camera_title": "Kamera használatának 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_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_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", "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_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": "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.", "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", "header": "beállítások",
"language": "Nyelv", "language": "Nyelv",
"language_restart": "Új nyelv kiválasztásánal, szügség lehet a BlueWallet újraindítására. ", "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_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_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", "add_import_wallet": "Tárca importálása",
"import_file": "fájl importálása",
"add_lightning": "Lightning", "add_lightning": "Lightning",
"add_lndhub": "Kapcsolódj az LNDHub-hoz", "add_lndhub": "Kapcsolódj az LNDHub-hoz",
"add_lndhub_error": "A megadott node cím hibás LBDHub node.", "add_lndhub_error": "A megadott node cím hibás LBDHub node.",
@ -340,7 +342,6 @@
"import_do_import": "Importálás", "import_do_import": "Importálás",
"import_error": "Importálás sikertelen. Ellenőrizd, hogy helyes adatokat adtál-e meg.", "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_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_imported": "Importálva",
"import_scan_qr": "vagy QR-kód szkennelése?", "import_scan_qr": "vagy QR-kód szkennelése?",
"import_success": "Sikeres importálás!", "import_success": "Sikeres importálás!",
@ -360,6 +361,7 @@
"list_long_choose": "Válassz fényképet", "list_long_choose": "Válassz fényképet",
"list_long_clipboard": "Másolás vágólapról", "list_long_clipboard": "Másolás vágólapról",
"list_long_scan": "QR kód szkennelése", "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_tap_here_to_buy": "Bitcoin vásárláshoz kattints ide",
"list_title": "tárcák", "list_title": "tárcák",
"list_tryagain": "Próbáld újra", "list_tryagain": "Próbáld újra",
@ -367,7 +369,6 @@
"select_no_bitcoin": "Jelenleg nincs elérhető Bitcoin tárca.", "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_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", "select_wallet": "Válassz tárcát",
"take_photo": "Fénykép készítése",
"xpub_copiedToClipboard": "Vágólapra másolva", "xpub_copiedToClipboard": "Vágólapra másolva",
"xpub_title": "a tárca XPUB kulcsa" "xpub_title": "a tárca XPUB kulcsa"
} }

View file

@ -170,13 +170,13 @@
"details_next": "Next", "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_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_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_note_placeholder": "catatan pribadi",
"details_scan": "Pindai", "details_scan": "Pindai",
"details_total_exceeds_balance": "Jumlah yang dikirim melebihi saldo.", "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_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
"details_wallet_selection": "Wallet Selection", "details_wallet_selection": "Wallet Selection",
"dynamic_init": "Initialing", "dynamic_init": "Initializing",
"dynamic_next": "Next", "dynamic_next": "Next",
"dynamic_prev": "Previous", "dynamic_prev": "Previous",
"dynamic_start": "Start", "dynamic_start": "Start",

View file

@ -5,17 +5,107 @@
"continue": "Continua", "continue": "Continua",
"enter_password": "Inserisci password", "enter_password": "Inserisci password",
"never": "mai", "never": "mai",
"of": "{number} su {total}",
"ok": "OK", "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": { "lnd": {
"errorInvoiceExpired": "Invoice expired",
"exchange": "Exchange",
"expired": "Scaduto", "expired": "Scaduto",
"expiredLow": "expired",
"expiresIn": "Expires: {time}",
"payButton": "Pay",
"placeholder": "Fattura", "placeholder": "Fattura",
"potentialFee": "Commissioni potenziali: {fee}",
"refill": "Ricarica", "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", "refill_lnd_balance": "Ricarica saldo del portafoglio Lightning",
"sameWalletAsInvoiceError": "Non puoi pagare una fattura con lo stesso portafoglio utilizzato per crearla.", "sameWalletAsInvoiceError": "Non puoi pagare una fattura con lo stesso portafoglio utilizzato per crearla.",
"title": "Gestisci fondi" "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": { "plausibledeniability": {
"create_fake_storage": "Crea archivio falso criptato", "create_fake_storage": "Crea archivio falso criptato",
"create_password": "Crea una password", "create_password": "Crea una password",
@ -28,6 +118,16 @@
"success": "Fatto", "success": "Fatto",
"title": "Negazione Plausibile" "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": { "receive": {
"details_create": "Crea", "details_create": "Crea",
"details_label": "Descrizione", "details_label": "Descrizione",
@ -36,65 +136,207 @@
"header": "Ricevi" "header": "Ricevi"
}, },
"send": { "send": {
"broadcastButton": "BROADCAST",
"broadcastError": "error",
"broadcastNone": "Input transaction hash",
"broadcastPending": "pending",
"broadcastSuccess": "success",
"confirm_header": "Conferma", "confirm_header": "Conferma",
"confirm_sendNow": "Invia ora", "confirm_sendNow": "Invia ora",
"create_amount": "Importo", "create_amount": "Importo",
"create_broadcast": "Trasmetti", "create_broadcast": "Trasmetti",
"create_copy": "Copy and broadcast later",
"create_details": "Dettagli", "create_details": "Dettagli",
"create_fee": "Commissione", "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_this_is_hex": "Questo è l'hex della transazione, firmato e pronto per essere trasmesso sulla rete.",
"create_to": "A", "create_to": "A",
"create_tx_size": "Grandezza TX", "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": "Indirizzo",
"details_address_field_is_not_valid": "Indirizzo non valido", "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_amount_field_is_not_valid": "Importo non valido",
"details_create": "Crea", "details_create": "Crea",
"details_error_decode": "Error: Unable to decode Bitcoin address",
"details_fee_field_is_not_valid": "Commissione non valida", "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_note_placeholder": "Nota",
"details_scan": "Scansiona", "details_scan": "Scansiona",
"details_total_exceeds_balance": "L'importo da inviare eccede i fondi disponibili.", "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", "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": { "settings": {
"about": "Informazioni", "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": "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": "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", "header": "Impostazioni",
"language": "Lingua", "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": "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)", "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", "password_explain": "Crea la password che userai per decriptare l'archivio",
"passwords_do_not_match": "Le password non corrispondono", "passwords_do_not_match": "Le password non corrispondono",
"plausible_deniability": "Negazione plausibile...", "plausible_deniability": "Negazione plausibile...",
"push_notifications": "Push notifications",
"retype_password": "Reinserisci password", "retype_password": "Reinserisci password",
"save": "Salva" "save": "Salva",
"saved": "Saved"
}, },
"transactions": { "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_copy": "Copia",
"details_from": "Da", "details_from": "Da",
"details_inputs": "Inputs",
"details_outputs": "Outputs",
"details_received": "Received",
"details_show_in_block_explorer": "Mostra sul block explorer", "details_show_in_block_explorer": "Mostra sul block explorer",
"details_title": "Transazione", "details_title": "Transazione",
"details_to": "A", "details_to": "A",
"details_transaction_details": "Dettagli transazione", "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": { "wallets": {
"add_bitcoin": "Bitcoin",
"add_create": "Crea", "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", "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_or": "o",
"add_title": "Aggiungi Portafoglio", "add_title": "Aggiungi Portafoglio",
"add_wallet_name": "Nome Portafoglio", "add_wallet_name": "Nome Portafoglio",
"add_wallet_type": "Tipo", "add_wallet_type": "Tipo",
"details_address": "Indirizzo", "details_address": "Indirizzo",
"details_advanced": "Advanced",
"details_are_you_sure": "Sei sicuro?", "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": "Elimina",
"details_delete_wallet": "Delete wallet",
"details_display": "display in wallets list",
"details_export_backup": "Esporta / Backup", "details_export_backup": "Esporta / Backup",
"details_marketplace": "Marketplace",
"details_master_fingerprint": "Master fingerprint",
"details_no_cancel": "No, annulla", "details_no_cancel": "No, annulla",
"details_save": "Salva", "details_save": "Salva",
"details_show_xpub": "Mostra XPUB del portafoglio", "details_show_xpub": "Mostra XPUB del portafoglio",
"details_title": "Portafoglio", "details_title": "Portafoglio",
"details_type": "Tipo", "details_type": "Tipo",
"details_use_with_hardware_wallet": "Use with hardware wallet",
"details_wallet_updated": "Wallet updated",
"details_yes_delete": "Si, elimina", "details_yes_delete": "Si, elimina",
"export_title": "Esporta portafoglio", "export_title": "Esporta portafoglio",
"import_do_import": "Importa", "import_do_import": "Importa",
@ -109,11 +351,23 @@
"list_create_a_wallet1": "È gratuito e puoi crearne", "list_create_a_wallet1": "È gratuito e puoi crearne",
"list_create_a_wallet2": "quanti ne vuoi", "list_create_a_wallet2": "quanti ne vuoi",
"list_empty_txs1": "Le tue transazioni appariranno qui,", "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": "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_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_tap_here_to_buy": "Clicca qui per comprare Bitcoin",
"list_title": "Portafogli", "list_title": "Portafogli",
"list_tryagain": "Try Again",
"reorder_title": "Riordina Portafogli", "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", "select_wallet": "Seleziona Portafoglio",
"xpub_copiedToClipboard": "Copiata negli appunti.", "xpub_copiedToClipboard": "Copiata negli appunti.",
"xpub_title": "XPUB del Portafoglio" "xpub_title": "XPUB del Portafoglio"

View file

@ -32,7 +32,7 @@
"hodl": { "hodl": {
"are_you_sure_you_want_to_logout": "本当にHodlHodlからログアウトしますか", "are_you_sure_you_want_to_logout": "本当にHodlHodlからログアウトしますか",
"cont_address_escrow": "エスクロー", "cont_address_escrow": "エスクロー",
"cont_address_to": "To", "cont_address_to": "宛先",
"cont_buying": "買う", "cont_buying": "買う",
"cont_cancel": "コントラクトをキャンセル", "cont_cancel": "コントラクトをキャンセル",
"cont_cancel_q": "本当にこのコントラクトをキャンセルしますか?", "cont_cancel_q": "本当にこのコントラクトをキャンセルしますか?",
@ -50,8 +50,8 @@
"cont_st_paid_waiting": "販売者がエスクローからコインをリリースするのを待っています。", "cont_st_paid_waiting": "販売者がエスクローからコインをリリースするのを待っています。",
"cont_st_waiting": "販売者がエスクローにビットコインを預けるのを待っています。", "cont_st_waiting": "販売者がエスクローにビットコインを預けるのを待っています。",
"cont_title": "マイコントラクト", "cont_title": "マイコントラクト",
"filter_any": "Any", "filter_any": "すべて",
"filter_buying": "Buying", "filter_buying": "買う",
"filter_country_global": "グローバル・オファー", "filter_country_global": "グローバル・オファー",
"filter_country_near": "近くで探す", "filter_country_near": "近くで探す",
"filter_currency": "通貨", "filter_currency": "通貨",
@ -74,7 +74,7 @@
"offer_confirmations": "承認", "offer_confirmations": "承認",
"offer_minmax": "最小 / 最大", "offer_minmax": "最小 / 最大",
"offer_minutes": "分", "offer_minutes": "分",
"offer_promt_fiat": "How much {currency} do you want to buy?", "offer_promt_fiat": "いくら {currency} を購入しますか?",
"offer_promt_fiat_e": "例 100", "offer_promt_fiat_e": "例 100",
"offer_window": "ウィンドウ", "offer_window": "ウィンドウ",
"p2p": "P2P エクスチェンジ" "p2p": "P2P エクスチェンジ"
@ -89,9 +89,9 @@
"placeholder": "入金依頼", "placeholder": "入金依頼",
"potentialFee": "手数料推計: {fee}", "potentialFee": "手数料推計: {fee}",
"refill": "送金", "refill": "送金",
"refill_card": "Refill with bank card", "refill_card": "銀行カードで補充する",
"refill_create": "In order to proceed, please create a Bitcoin wallet to refill with.", "refill_create": "先に進むためには、ビットコインウォレットを作成して補充してください。",
"refill_external": "Refill with External Wallet", "refill_external": "外部ウォレットで補充",
"refill_lnd_balance": "Lightning ウォレットへ送金", "refill_lnd_balance": "Lightning ウォレットへ送金",
"sameWalletAsInvoiceError": "以前作成したウォレットと同じウォレットへの支払いはできません。", "sameWalletAsInvoiceError": "以前作成したウォレットと同じウォレットへの支払いはできません。",
"title": "資金の管理" "title": "資金の管理"
@ -125,7 +125,7 @@
"ok": "すべてのニモニックを書きとめました", "ok": "すべてのニモニックを書きとめました",
"ok_lnd": "はい、書きとめました", "ok_lnd": "はい、書きとめました",
"text": "すべてのニモニックを別紙に書きとめてください。他のデバイスへウォレットをリストアする際にニモニックが必要になります。デスクトップ用ウォレットの Electrum wallet (https://electrum.org/) へニモニックを使用してウォレットをリストアすることが可能です。", "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": "ウォレットを作成しています..." "title": "ウォレットを作成しています..."
}, },
"receive": { "receive": {
@ -136,9 +136,9 @@
"header": "入金" "header": "入金"
}, },
"send": { "send": {
"broadcastButton": "BROADCAST", "broadcastButton": "ブロードキャスト",
"broadcastError": "エラー", "broadcastError": "エラー",
"broadcastNone": "Input transaction hash", "broadcastNone": "インプットトランザクションハッシュ",
"broadcastPending": "試行中", "broadcastPending": "試行中",
"broadcastSuccess": "成功", "broadcastSuccess": "成功",
"confirm_header": "確認", "confirm_header": "確認",
@ -158,38 +158,38 @@
"details_add_rec_rem": "宛先を削除", "details_add_rec_rem": "宛先を削除",
"details_address": "アドレス", "details_address": "アドレス",
"details_address_field_is_not_valid": "アドレス欄が正しくありません", "details_address_field_is_not_valid": "アドレス欄が正しくありません",
"details_adv_fee_bump": "Allow Fee Bump", "details_adv_fee_bump": "費用のバンプ(増加)を許可",
"details_adv_full": "Use Full Balance", "details_adv_full": "全残高を使う",
"details_adv_full_remove": "Your other recipients will be removed from this transaction.", "details_adv_full_remove": "BlueWalletがアンインストールされたら消去",
"details_adv_full_sure": "Are you sure you want to use your wallet's full balance for this transaction?", "details_adv_full_sure": "本当にこのウォレットの全残高をこのトランザクションに利用しますか?",
"details_adv_import": "Import Transaction", "details_adv_import": "トランザクションをインポート",
"details_amount_field_is_not_valid": "金額欄が正しくありません", "details_amount_field_is_not_valid": "金額欄が正しくありません",
"details_create": "作成", "details_create": "作成",
"details_error_decode": "Error: Unable to decode Bitcoin address", "details_error_decode": "エラー:ビットコインアドレスを復号できません",
"details_fee_field_is_not_valid": "手数料欄が正しくありません", "details_fee_field_is_not_valid": "手数料欄が正しくありません",
"details_next": "次", "details_next": "次",
"details_no_maximum": "選択したウォレットは、最大残高の自動計算に対応していません。このウォレットを選択してもよろしいですか?", "details_no_maximum": "選択したウォレットは、最大残高の自動計算に対応していません。このウォレットを選択してもよろしいですか?",
"details_no_multiple": "選択したウォレットは、複数の受信者へのビットコインの送信をサポートしていません。このウォレットを選択してもよろしいですか?", "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_note_placeholder": "ラベル",
"details_scan": "読取り", "details_scan": "読取り",
"details_total_exceeds_balance": "送金額が利用可能残額を超えています。", "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": "ウォレット選択", "details_wallet_selection": "ウォレット選択",
"dynamic_init": "インストール中", "dynamic_init": "インストール中",
"dynamic_next": "次", "dynamic_next": "次",
"dynamic_prev": "前", "dynamic_prev": "前",
"dynamic_start": "スタート", "dynamic_start": "スタート",
"dynamic_stop": "ストップ", "dynamic_stop": "ストップ",
"fee_10m": "10m", "fee_10m": "10",
"fee_1d": "1d", "fee_1d": "1",
"fee_3h": "3h", "fee_3h": "3時間",
"fee_custom": "Custom", "fee_custom": "カスタム",
"fee_fast": "Fast", "fee_fast": "高速",
"fee_medium": "Medium", "fee_medium": "中速",
"fee_replace_min": "The total fee rate (satoshi per byte) you want to pay should be higher than {min} sat/byte", "fee_replace_min": "支払いたい手数料レート(1バイトあたりのsatoshi)は、{min} sat/byteよりも高い必要があります。",
"fee_satbyte": "in sat/byte", "fee_satbyte": "sat/バイト",
"fee_slow": "Slow", "fee_slow": "低速",
"header": "送金", "header": "送金",
"input_clear": "クリア", "input_clear": "クリア",
"input_done": "完了", "input_done": "完了",
@ -197,71 +197,71 @@
"input_total": "合計:", "input_total": "合計:",
"permission_camera_message": "カメラを使用するのに許可が必要です", "permission_camera_message": "カメラを使用するのに許可が必要です",
"permission_camera_title": "カメラの使用許可", "permission_camera_title": "カメラの使用許可",
"open_settings": "Open Settings", "open_settings": "設定を開く",
"permission_storage_later": "Ask Me Later", "permission_storage_later": "後で聞く",
"permission_storage_message": "BlueWallet needs your permission to access your storage to save this transaction.", "permission_storage_message": "BlueWalletはこのトランザクションを保存するためにストレージへのアクセス許可を必要としています。",
"permission_storage_title": "BlueWallet Storage Access Permission", "permission_storage_title": "BlueWalletストレージアクセス許可",
"psbt_clipboard": "クリップボードにコピー", "psbt_clipboard": "クリップボードにコピー",
"psbt_this_is_psbt": "これは部分的に署名されたビットコイントランザクションPSBTです。ハードウェアウォレットで署名を完了させてください。", "psbt_this_is_psbt": "これは部分的に署名されたビットコイントランザクションPSBTです。ハードウェアウォレットで署名を完了させてください。",
"psbt_tx_export": "ファイルにエクスポート", "psbt_tx_export": "ファイルにエクスポート",
"psbt_tx_open": "署名トランザクションを開く", "psbt_tx_open": "署名トランザクションを開く",
"psbt_tx_scan": "署名トランザクションをスキャン", "psbt_tx_scan": "署名トランザクションをスキャン",
"qr_error_no_qrcode": "選択された画像はQRコードを含んでいません。", "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": "完了", "success_done": "完了",
"txSaved": "The transaction file ({filePath}) has been saved in your Downloads folder ." "txSaved": "トランザクションファイル ({filePath}) はダウンロードフォルダに保存されました。"
}, },
"settings": { "settings": {
"about": "BlueWallet について", "about": "BlueWallet について",
"about_awesome": "Built with the awesome", "about_awesome": "Built with the awesome",
"about_backup": "Always backup your keys!", "about_backup": "常に秘密鍵はバックアップしましょう!",
"about_free": "BlueWallet is a free and open source project. Crafted by Bitcoin users.", "about_free": "BlueWalletはフリーでオープンソースのプロジェクトです。ビットコインユーザーによって作られています。",
"about_release_notes": "Release notes", "about_release_notes": "リリースノート",
"about_review": "レビューを書く", "about_review": "レビューを書く",
"about_selftest": "Run self test", "about_selftest": "セルフテストを実行",
"about_sm_github": "GitHub", "about_sm_github": "GitHub",
"about_sm_telegram": "テレグラムチャット", "about_sm_telegram": "テレグラムチャット",
"about_sm_twitter": "Twitterでフォロー", "about_sm_twitter": "Twitterでフォロー",
"advanced_options": "上級設定", "advanced_options": "上級設定",
"currency": "通貨", "currency": "通貨",
"currency_source": "CoinDeskから取得された価格", "currency_source": "CoinDeskから取得された価格",
"default_desc": "When disabled, BlueWallet will immediately open the selected wallet at launch.", "default_desc": "無効にすれば、BlueWalletは起動時に選択したウォレットをすぐに開きます。",
"default_info": "Default info", "default_info": "デフォルト情報",
"default_title": "On Launch", "default_title": "起動時",
"default_wallets": "View All Wallets", "default_wallets": "すべてのウォレットを確認",
"electrum_connected": "Connected", "electrum_connected": "接続済",
"electrum_connected_not": "Not Connected", "electrum_connected_not": "未接続",
"electrum_error_connect": "Can't connect to provided Electrum server", "electrum_error_connect": "指定されたElectrumサーバーに接続できません",
"electrum_host": "host, for example {example}", "electrum_host": "ホスト 例 {example}",
"electrum_port": "TCP port, usually {example}", "electrum_port": "TCP ポート 通常 {example}",
"electrum_port_ssl": "SSL port, usually {example}", "electrum_port_ssl": "SSL ポート 通常 {example}",
"electrum_saved": "Your changes have been saved successfully. Restart may be required for changes to take effect.", "electrum_saved": "変更は正常に保存されました。変更の適用には、リスタートが必要な場合があります。",
"electrum_settings": "Electrum Settings", "electrum_settings": "Electrum 設定",
"electrum_settings_explain": "Set to blank to use default", "electrum_settings_explain": "ブランクに設定してデフォルトを使用",
"electrum_status": "ステータス", "electrum_status": "ステータス",
"encrypt_decrypt": "ストレージ復号化", "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_decrypt_q": "本当にストレージを復号化しますか?これによりウォレットがパスワードなしでアクセス可能になります。",
"encrypt_del_uninstall": "Delete if BlueWallet is uninstalled", "encrypt_del_uninstall": "BlueWalletをアンインストールしたら削除",
"encrypt_enc_and_pass": "Encrypted and Password protected", "encrypt_enc_and_pass": "暗号化されパスワードで保護されています",
"encrypt_title": "セキュリティ", "encrypt_title": "セキュリティ",
"encrypt_tstorage": "ストレージ", "encrypt_tstorage": "ストレージ",
"encrypt_use": "{type} を使う", "encrypt_use": "{type} を使う",
"encrypt_use_expl": "{type} は、トランザクションの実行、ウォレットのロック解除、エクスポート、または削除を行う前の本人確認に使用されます。{type} は暗号化されたストレージのロック解除には使用されません。", "encrypt_use_expl": "{type} は、トランザクションの実行、ウォレットのロック解除、エクスポート、または削除を行う前の本人確認に使用されます。{type} は暗号化されたストレージのロック解除には使用されません。",
"general": "一般情報", "general": "一般情報",
"general_adv_mode": "Enable advanced mode", "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_adv_mode_e": "この機能を有効にすると、異なるウォレットタイプ、接続先の LNDHub インスタンスの指定、ウォレット作成時のカスタムエントロピーなどの高度なオプションが表示されます。",
"general_continuity": "Continuity", "general_continuity": "継続性",
"general_continuity_e": "When enabled, you will be able to view selected wallets, and transactions, using your other Apple iCloud connected devices.", "general_continuity_e": "この機能を有効にすると、Apple iCloudに接続している他のデバイスを使用して、選択したウォレットやトランザクションを表示できるようになります。",
"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", "groundcontrol_explanation": "GroundControlはビットコインウォレットのための無料のオープンソースのプッシュ通知サーバーです。独自のGroundControlサーバーをインストールし、BlueWalletのインフラに依存しないようにURLをここに入力することができます。デフォルトを使用するには空白のままにしてください。",
"header": "設定", "header": "設定",
"language": "言語", "language": "言語",
"language_restart": "When selecting a new language, restarting BlueWallet may be required for the change to take effect.", "language_restart": "新しい言語を選択した場合、変更を有効にするには BlueWallet の再起動が必要な場合があります。",
"lightning_error_lndhub_uri": "Not a valid LndHub URI", "lightning_error_lndhub_uri": "有効なLndHub URIではありません",
"lightning_saved": "Your changes have been saved successfully", "lightning_saved": "変更は正常に保存されました",
"lightning_settings": "Lightning 設定", "lightning_settings": "Lightning 設定",
"lightning_settings_explain": "他の LND ノードへ接続するには LndHub をインストール後、URL を入力してください。既定の設定を使用するには空欄にしますndHub\n (lndhub.io)", "lightning_settings_explain": "他の LND ノードへ接続するには LndHub をインストール後、URL を入力してください。既定の設定を使用するには空欄にしますndHub\n (lndhub.io)",
"network": "Network", "network": "ネットワーク",
"network_broadcast": "Broadcast transaction", "network_broadcast": "ブロードキャストトランザクション",
"network_electrum": "Electrum サーバー", "network_electrum": "Electrum サーバー",
"not_a_valid_uri": "有効なURIではありません", "not_a_valid_uri": "有効なURIではありません",
"notifications": "通知", "notifications": "通知",
@ -275,11 +275,11 @@
"saved": "保存済" "saved": "保存済"
}, },
"transactions": { "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_no": "このトランザクションは交換可能ではありません",
"cancel_title": "このトランザクションをキャンセル (RBF)", "cancel_title": "このトランザクションをキャンセル (RBF)",
"cpfp_create": "作成", "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_no_bump": "このトランザクションはバンプ可能ではありません",
"cpfp_title": "バンプ費用 (CPFP)", "cpfp_title": "バンプ費用 (CPFP)",
"details_block": "ブロック高", "details_block": "ブロック高",
@ -292,44 +292,44 @@
"details_title": "取引", "details_title": "取引",
"details_to": "送り先", "details_to": "送り先",
"details_transaction_details": "取引詳細", "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?", "enable_hw": "このウォレットはハードウォレットとの併用はされていません。ハードウェアウォレットの使用を有効にしますか?",
"list_conf": "確認", "list_conf": "コンファメーション: {number}",
"list_title": "取引", "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_explain": "このトランザクションを手数料の高いものに置き換えるので、マイニングが早くなるはずです。これをRBF - Replace By Feeといいます。",
"rbf_title": "Bump fee (RBF)", "rbf_title": "手数料をバンプ (RBF)",
"status_bump": "Bump Fee", "status_bump": "手数料をバンプ",
"status_cancel": "Cancel Transaction", "status_cancel": "トランザクションをキャンセル",
"transactions_count": "transactions count" "transactions_count": "トランザクションカウント"
}, },
"wallets": { "wallets": {
"add_bitcoin": "ビットコイン", "add_bitcoin": "ビットコイン",
"add_create": "作成", "add_create": "作成",
"add_entropy_generated": "{gen} bytes of generated entropy", "add_entropy_generated": "生成されたエントロピーの {gen} バイト",
"add_entropy_provide": "サイコロを振ってエントロピーを提供", "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": "ウォレットをインポート", "add_import_wallet": "ウォレットをインポート",
"import_file": "Import File", "import_file": "インポートファイル",
"add_lightning": "Lightning", "add_lightning": "ライトニング",
"add_lndhub": "Connect to your LNDHub", "add_lndhub": "あなたのLNDHubに接続",
"add_lndhub_error": "The provided node address is not valid LNDHub node.", "add_lndhub_error": "指定されたードアドレスは有効なLNDHubードではありません。",
"add_lndhub_placeholder": "your node address", "add_lndhub_placeholder": "あなたのノードアドレス",
"add_or": "又は", "add_or": "又は",
"add_title": "ウォレットの追加", "add_title": "ウォレットの追加",
"add_wallet_name": "ウォレット名", "add_wallet_name": "ウォレット名",
"add_wallet_type": "タイプ", "add_wallet_type": "タイプ",
"details_address": "アドレス", "details_address": "アドレス",
"details_advanced": "Advanced", "details_advanced": "上級設定",
"details_are_you_sure": "実行しますか?", "details_are_you_sure": "実行しますか?",
"details_connected_to": "接続", "details_connected_to": "接続",
"details_del_wb": "ウォレット残高", "details_del_wb": "ウォレット残高",
"details_del_wb_err": "The provided balance amount does not match this wallet's balance. Please, try again", "details_del_wb_err": "提供された残高は、このウォレットの残高と一致しません。もう一度お試しください。",
"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_q": "このウォレットには残高があります。先に進む前に、このウォレットのシードフレーズがないと資金を回収できない点に注意してください。誤ってこのウォレットを削除しないようにするために、このウォレットの残高 {balance} satoshisを入力してください。",
"details_delete": "削除", "details_delete": "削除",
"details_delete_wallet": "ウォレット削除", "details_delete_wallet": "ウォレット削除",
"details_display": "display in wallets list", "details_display": "ウォレットリストで表示",
"details_export_backup": "エクスポート / バックアップ", "details_export_backup": "エクスポート / バックアップ",
"details_marketplace": "Marketplace", "details_marketplace": "マーケットプレイス",
"details_master_fingerprint": "Master fingerprint", "details_master_fingerprint": "マスタフィンガープリント",
"details_no_cancel": "いいえ、中止します", "details_no_cancel": "いいえ、中止します",
"details_save": "保存", "details_save": "保存",
"details_show_xpub": "ウォレット XPUB の表示", "details_show_xpub": "ウォレット XPUB の表示",
@ -354,8 +354,8 @@
"list_empty_txs1_lightning": "Lightning ウォレットを日常の取引にご利用ください。手数料は安く、送金はあっという間に完了します。", "list_empty_txs1_lightning": "Lightning ウォレットを日常の取引にご利用ください。手数料は安く、送金はあっという間に完了します。",
"list_empty_txs2": "現在は何もありません", "list_empty_txs2": "現在は何もありません",
"list_empty_txs2_lightning": "\n利用を開始するには\"資金の管理\"をタップしてウォレットへ送金してください。", "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_header": "ウォレットが表示する鍵のペアは、ひとつが秘密鍵、もうひとつはコインを受け取るために他人と共有することができる鍵です。",
"list_import_error": "An error was encountered when attempting to import this wallet.", "list_import_error": "ウォレットのインポート時にエラーが起こりました。",
"list_import_problem": "このウォレットのインポートに問題が生じました", "list_import_problem": "このウォレットのインポートに問題が生じました",
"list_latest_transaction": "最新の取引", "list_latest_transaction": "最新の取引",
"list_long_choose": "写真選択", "list_long_choose": "写真選択",

View file

@ -170,13 +170,13 @@
"details_next": "Volgende", "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_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_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_note_placeholder": "notitie voor mezelf",
"details_scan": "Scan", "details_scan": "Scan",
"details_total_exceeds_balance": "Het verzendingsbedrag overschrijdt het beschikbare saldo.", "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_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
"details_wallet_selection": "Wallet Selection", "details_wallet_selection": "Wallet Selection",
"dynamic_init": "Initialing", "dynamic_init": "Initializing",
"dynamic_next": "Volgende", "dynamic_next": "Volgende",
"dynamic_prev": "Vorige", "dynamic_prev": "Vorige",
"dynamic_start": "Start", "dynamic_start": "Start",

View file

@ -170,7 +170,7 @@
"details_next": "Próximo", "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_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_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_note_placeholder": "Nota pessoal",
"details_scan": "Ler", "details_scan": "Ler",
"details_total_exceeds_balance": "Valor total excede o saldo disponível", "details_total_exceeds_balance": "Valor total excede o saldo disponível",
@ -181,6 +181,15 @@
"dynamic_prev": "Anterior", "dynamic_prev": "Anterior",
"dynamic_start": "Começar", "dynamic_start": "Começar",
"dynamic_stop": "Parar", "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", "header": "Enviar",
"input_clear": "Limpar", "input_clear": "Limpar",
"input_done": "Feito", "input_done": "Feito",
@ -217,7 +226,7 @@
"currency": "Moeda", "currency": "Moeda",
"currency_source": "Os preços são obtidos no CoinDesk", "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_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_title": "No lançamento",
"default_wallets": "Ver todas as carteiras", "default_wallets": "Ver todas as carteiras",
"electrum_connected": "Conectado", "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_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": "Continuidade",
"general_continuity_e": "Quando ativado, você poderá visualizar carteiras selecionadas e transações, usando seus outros dispositivos conectados ao Apple iCloud.", "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", "header": "definições",
"language": "Idioma", "language": "Idioma",
"language_restart": "Ao selecionar um novo idioma, pode ser necessário reiniciar a BlueWallet para que a alteração tenha efeito.", "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": "Rede",
"network_broadcast": "Divulgar transação", "network_broadcast": "Divulgar transação",
"network_electrum": "Servidor Electrum", "network_electrum": "Servidor Electrum",
"not_a_valid_uri": "Não é um URI válido",
"notifications": "Notificações",
"password": "Senha", "password": "Senha",
"password_explain": "Definir a senha para descriptografar os arquivos", "password_explain": "Definir a senha para descriptografar os arquivos",
"passwords_do_not_match": "Senhas não conferem", "passwords_do_not_match": "Senhas não conferem",
"plausible_deniability": "Negação plausível...", "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", "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": { "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.", "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_to": "Para",
"details_transaction_details": "Detalhes", "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?", "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", "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_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)", "rbf_title": "Aumento de taxa (RBF)",
"status_bump": "Aumento de taxa", "status_bump": "Aumento de taxa",
"status_cancel": "Cancelar Transação" "status_cancel": "Cancelar Transação",
"transactions_count": "contagem de transações"
}, },
"wallets": { "wallets": {
"add_bitcoin": "Bitcoin", "add_bitcoin": "Bitcoin",
@ -334,7 +343,7 @@
"import_error": "Erro. Por favor, confira se o formato que você passou é válido.", "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_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_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_success": "Sucesso",
"import_title": "importar", "import_title": "importar",
"list_create_a_button": "Criar agora", "list_create_a_button": "Criar agora",
@ -353,7 +362,7 @@
"list_long_clipboard": "Copiar da área de transferência", "list_long_clipboard": "Copiar da área de transferência",
"list_long_scan": "Ler QR Code", "list_long_scan": "Ler QR Code",
"take_photo": "Registrar Foto", "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_title": "carteiras",
"list_tryagain": "Tente Novamente", "list_tryagain": "Tente Novamente",
"reorder_title": "Reordenar carteiras", "reorder_title": "Reordenar carteiras",

View file

@ -170,7 +170,7 @@
"details_next": "Próximo", "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_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_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_note_placeholder": "Nota pessoal",
"details_scan": "Scan", "details_scan": "Scan",
"details_total_exceeds_balance": "O valor total excede o saldo disponível.", "details_total_exceeds_balance": "O valor total excede o saldo disponível.",
@ -181,6 +181,15 @@
"dynamic_prev": "Anterior", "dynamic_prev": "Anterior",
"dynamic_start": "Começar", "dynamic_start": "Começar",
"dynamic_stop": "Parar", "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", "header": "Enviar",
"input_clear": "Limpar", "input_clear": "Limpar",
"input_done": "Feito", "input_done": "Feito",
@ -217,7 +226,7 @@
"currency": "Moeda", "currency": "Moeda",
"currency_source": "Os preços são obtidos no CoinDesk", "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_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_title": "A abrir",
"default_wallets": "Ver todas as carteiras", "default_wallets": "Ver todas as carteiras",
"electrum_connected": "Conectado", "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_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": "Continuidade",
"general_continuity_e": "Quando activado, poderá visualizar carteiras seleccionadas e transacções, usando os seus outros dispositivos conectados ao Apple iCloud.", "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", "header": "definições",
"language": "Idioma", "language": "Idioma",
"language_restart": "Ao selecionar um novo idioma, pode ser necessário reiniciar a BlueWallet para que a alteração tenha efeito.", "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": "Rede",
"network_broadcast": "Transmitir transacção", "network_broadcast": "Transmitir transacção",
"network_electrum": "Electrum server", "network_electrum": "Electrum server",
"not_a_valid_uri": "Não é um URI válido",
"notifications": "Notificações",
"password": "Password", "password": "Password",
"password_explain": "Definir a password para desencriptar o armazenamento", "password_explain": "Definir a password para desencriptar o armazenamento",
"passwords_do_not_match": "Passwords não coincidem", "passwords_do_not_match": "Passwords não coincidem",
"plausible_deniability": "Negação plausível...", "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", "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": { "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.", "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?", "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_conf": "conf",
"list_title": "transacções", "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_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)", "rbf_title": "Aumento de taxa (RBF)",
"status_bump": "Aumento de taxa", "status_bump": "Aumento de taxa",
"status_cancel": "Cancelar transacção" "status_cancel": "Cancelar transacção",
"transactions_count": "contagem de transações"
}, },
"wallets": { "wallets": {
"add_bitcoin": "Bitcoin", "add_bitcoin": "Bitcoin",

View file

@ -170,9 +170,9 @@
"details_next": "Дальше", "details_next": "Дальше",
"details_no_maximum": "Этот кошелёк не поддерживает отправку всех средств. Ты уверен что хочешь выбрать его?", "details_no_maximum": "Этот кошелёк не поддерживает отправку всех средств. Ты уверен что хочешь выбрать его?",
"details_no_multiple": "Этот кошелёк не поддерживает отправку нескольким получателям. Ты уверен что хочешь выбрать его?", "details_no_multiple": "Этот кошелёк не поддерживает отправку нескольким получателям. Ты уверен что хочешь выбрать его?",
"details_no_signed_tx": "В файле нет подписаных транзакций которые можно импортировать", "details_no_signed_tx": "В файле нет транзакций которые можно импортировать.",
"details_note_placeholder": "примечание платежа", "details_note_placeholder": "примечание платежа",
"details_scan": "Отсканировать QR", "details_scan": "Скан",
"details_total_exceeds_balance": "Общая сумма превышает баланс.", "details_total_exceeds_balance": "Общая сумма превышает баланс.",
"details_wallet_before_tx": "Перед созданием транзакции нужно сделать Bitcoin кошелёк.", "details_wallet_before_tx": "Перед созданием транзакции нужно сделать Bitcoin кошелёк.",
"details_wallet_selection": "Выбор Кошелька", "details_wallet_selection": "Выбор Кошелька",
@ -181,6 +181,15 @@
"dynamic_prev": "Предыдущий", "dynamic_prev": "Предыдущий",
"dynamic_start": "Старт", "dynamic_start": "Старт",
"dynamic_stop": "Стоп", "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": "Отправить", "header": "Отправить",
"input_clear": "Очистить", "input_clear": "Очистить",
"input_done": "Готово", "input_done": "Готово",
@ -188,6 +197,7 @@
"input_total": "Всего:", "input_total": "Всего:",
"permission_camera_message": "Нужно ваше разрешение на использование камеры", "permission_camera_message": "Нужно ваше разрешение на использование камеры",
"permission_camera_title": "Разрешите пользоваться камерой", "permission_camera_title": "Разрешите пользоваться камерой",
"open_settings": "Открыть настройки",
"permission_storage_later": "Спроси меня позже", "permission_storage_later": "Спроси меня позже",
"permission_storage_message": "BlueWallet нужно ваше разрешение для доступа к хранилищу файлов, чтобы сохранить эту транзакцию.", "permission_storage_message": "BlueWallet нужно ваше разрешение для доступа к хранилищу файлов, чтобы сохранить эту транзакцию.",
"permission_storage_title": "Разрешите сохранять файлы", "permission_storage_title": "Разрешите сохранять файлы",
@ -242,6 +252,7 @@
"general_adv_mode_e": "При включении вы увидите расширенные параметры, такие как разные типы кошельков, возможность указать экземпляр LNDHub, к которому вы хотите подключиться, и пользовательскую энтропию при создании кошелька.", "general_adv_mode_e": "При включении вы увидите расширенные параметры, такие как разные типы кошельков, возможность указать экземпляр LNDHub, к которому вы хотите подключиться, и пользовательскую энтропию при создании кошелька.",
"general_continuity": "Непрерывность", "general_continuity": "Непрерывность",
"general_continuity_e": "Когда эта функция включена, вы сможете просматривать выбранные кошельки и транзакции, используя другие устройства, подключенные к Apple iCloud.", "general_continuity_e": "Когда эта функция включена, вы сможете просматривать выбранные кошельки и транзакции, используя другие устройства, подключенные к Apple iCloud.",
"groundcontrol_explanation": "GroundControl - это бесплатный сервер push-уведомлений с открытым исходным кодом для биткойн-кошельков. Вы можете установить свой собственный сервер GroundControl и указать здесь его URL, чтобы не полагаться на инфраструктуру BlueWallet. Оставьте пустым, чтобы использовать по умолчанию",
"header": "Настройки", "header": "Настройки",
"language": "Язык", "language": "Язык",
"language_restart": "При выборе нового языка может потребоваться перезапуск BlueWallet, чтобы изменения вступили в силу.", "language_restart": "При выборе нового языка может потребоваться перезапуск BlueWallet, чтобы изменения вступили в силу.",
@ -252,17 +263,16 @@
"network": "Сеть", "network": "Сеть",
"network_broadcast": "Отправить транзакцию", "network_broadcast": "Отправить транзакцию",
"network_electrum": "Electrum сервер", "network_electrum": "Electrum сервер",
"not_a_valid_uri": "Неверный URI",
"notifications": "Уведомления",
"password": "Пароль", "password": "Пароль",
"password_explain": "Придумай пароль для расшифровки хранилища", "password_explain": "Придумай пароль для расшифровки хранилища",
"passwords_do_not_match": "Пароли не совпадают", "passwords_do_not_match": "Пароли не совпадают",
"plausible_deniability": "Правдоподобная имитация...", "plausible_deniability": "Правдоподобная имитация...",
"retype_password": "Набери пароль повторно",
"notifications": "Уведомления",
"save": "Сохранить",
"saved": "Сохранено",
"not_a_valid_uri": "Неверный URI",
"push_notifications": "Push-уведомления", "push_notifications": "Push-уведомления",
"groundcontrol_explanation": "GroundControl - это бесплатный сервер push-уведомлений с открытым исходным кодом для биткойн-кошельков. Вы можете установить свой собственный сервер GroundControl и указать здесь его URL, чтобы не полагаться на инфраструктуру BlueWallet. Оставьте пустым, чтобы использовать по умолчанию" "retype_password": "Набери пароль повторно",
"save": "Сохранить",
"saved": "Сохранено"
}, },
"transactions": { "transactions": {
"cancel_explain": "Мы заменим эту транзакцию другой, которая платит вам и имеет более высокую комиссию. Это отменит предыдущую транзакцию. Это называется RBF - Replace By Fee.", "cancel_explain": "Мы заменим эту транзакцию другой, которая платит вам и имеет более высокую комиссию. Это отменит предыдущую транзакцию. Это называется RBF - Replace By Fee.",
@ -283,13 +293,13 @@
"details_to": "Кому", "details_to": "Кому",
"details_transaction_details": "Детали транзакции", "details_transaction_details": "Детали транзакции",
"enable_hw": "Кошелек не используется вместе с аппаратным. Вы хотите включить поддержку аппаратного кошелека?", "enable_hw": "Кошелек не используется вместе с аппаратным. Вы хотите включить поддержку аппаратного кошелека?",
"list_conf": "подтв.", "list_conf": "{number} подтв.",
"list_title": "Мои транзакции", "list_title": "Мои транзакции",
"transactions_count": "кол-во транзакций",
"rbf_explain": "Мы заменим эту транзакцию другой с более высокой комиссией, поэтому будет обработана быстрее. Это называется RBF - Replace By Fee.", "rbf_explain": "Мы заменим эту транзакцию другой с более высокой комиссией, поэтому будет обработана быстрее. Это называется RBF - Replace By Fee.",
"rbf_title": "Повысить комиссию (RBF)", "rbf_title": "Повысить комиссию (RBF)",
"status_bump": "Повысить комиссию", "status_bump": "Повысить комиссию",
"status_cancel": "Отменить транзакцию" "status_cancel": "Отменить транзакцию",
"transactions_count": "кол-во транзакций"
}, },
"wallets": { "wallets": {
"add_bitcoin": "Bitcoin", "add_bitcoin": "Bitcoin",
@ -298,6 +308,7 @@
"add_entropy_provide": "Сгенерировать энторию с помощью игральных костей", "add_entropy_provide": "Сгенерировать энторию с помощью игральных костей",
"add_entropy_remain": "{gen} байтов сгенерированной энтории. Оставшиеся {rem} байт будут получены из системного генератора случайных чисел.", "add_entropy_remain": "{gen} байтов сгенерированной энтории. Оставшиеся {rem} байт будут получены из системного генератора случайных чисел.",
"add_import_wallet": "Импортировать кошелек", "add_import_wallet": "Импортировать кошелек",
"import_file": "Импортировать файл",
"add_lightning": "Lightning", "add_lightning": "Lightning",
"add_lndhub": "Подключиться к своему LNDHub", "add_lndhub": "Подключиться к своему LNDHub",
"add_lndhub_error": "Не верный адрес LNDHub.", "add_lndhub_error": "Не верный адрес LNDHub.",
@ -343,7 +354,7 @@
"list_empty_txs1_lightning": "Lightning кошелек отлично подходит для ежедневных транзакций. Комиссия несправедливо мала, а скорость невероятно высока.", "list_empty_txs1_lightning": "Lightning кошелек отлично подходит для ежедневных транзакций. Комиссия несправедливо мала, а скорость невероятно высока.",
"list_empty_txs2": "Пока транзакций нет ", "list_empty_txs2": "Пока транзакций нет ",
"list_empty_txs2_lightning": "\nДля начала использования нажми \"Мои средства\" и пополни баланс.", "list_empty_txs2_lightning": "\nДля начала использования нажми \"Мои средства\" и пополни баланс.",
"list_header": "Кошелек - это секретный (приватный) ключ и соответствующий ему адрес на который можно получать Bitcoin", "list_header": "Кошелек - пара ключей, один приватный, другой - публичный используется, чтобы получить Bitcoin.",
"list_import_error": "При импорте этого кошелька возникла ошибка.", "list_import_error": "При импорте этого кошелька возникла ошибка.",
"list_import_problem": "Ошибка при импорте кошелька", "list_import_problem": "Ошибка при импорте кошелька",
"list_latest_transaction": "Последняя транзакция", "list_latest_transaction": "Последняя транзакция",

View file

@ -170,13 +170,13 @@
"details_next": "Ďalej", "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_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_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_note_placeholder": "poznámka pre seba",
"details_scan": "Skenovať", "details_scan": "Skenovať",
"details_total_exceeds_balance": "Čiastka, ktorú chcete poslať, presahuje dostupný zostatok.", "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_before_tx": "Pred vytvorením transakcie potrebujete najprv pridať Bitcoinovú peňaženku.",
"details_wallet_selection": "Výber peňaženky", "details_wallet_selection": "Výber peňaženky",
"dynamic_init": "Initialing", "dynamic_init": "Initializing",
"dynamic_next": "Next", "dynamic_next": "Next",
"dynamic_prev": "Previous", "dynamic_prev": "Previous",
"dynamic_start": "Start", "dynamic_start": "Start",

View file

@ -8,7 +8,9 @@
"of": "{number} od {total}", "of": "{number} od {total}",
"ok": "OK", "ok": "OK",
"storage_is_encrypted": "Shramba je šifrirana. Za dešifriranje je potrebno geslo", "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": { "azteco": {
"codeIs": "Koda vašega bona je", "codeIs": "Koda vašega bona je",
@ -170,7 +172,7 @@
"details_next": "Naprej", "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_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_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_note_placeholder": "lastna opomba",
"details_scan": "Skeniraj", "details_scan": "Skeniraj",
"details_total_exceeds_balance": "Znesek presega razpoložljivo stanje.", "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_qrcode": "Izbrana slika ne vsebuje QR kode.",
"qr_error_no_wallet": "Izbrana datoteka ne vsebuje denarnice, ki jo je mogoče uvoziti.", "qr_error_no_wallet": "Izbrana datoteka ne vsebuje denarnice, ki jo je mogoče uvoziti.",
"success_done": "Končano", "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": { "settings": {
"about": "O aplikaciji", "about": "O aplikaciji",
@ -293,7 +296,7 @@
"details_to": "Izhod", "details_to": "Izhod",
"details_transaction_details": "Podrobnosti transakcije", "details_transaction_details": "Podrobnosti transakcije",
"enable_hw": "Ta denarnica se ne uporablja skupaj s strojno denarnico. Ali želite omogočiti uporabo strojne denarnice?", "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", "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_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)", "rbf_title": "Povečaj omrežnino (RBF)",
@ -371,5 +374,17 @@
"select_wallet": "Izberite Denarnico", "select_wallet": "Izberite Denarnico",
"xpub_copiedToClipboard": "Kopirano v odložišče.", "xpub_copiedToClipboard": "Kopirano v odložišče.",
"xpub_title": "XPUB denarnice" "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"
} }
} }

View file

@ -170,13 +170,13 @@
"details_next": "Nästa", "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_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_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_note_placeholder": "egen notering",
"details_scan": "Skanna", "details_scan": "Skanna",
"details_total_exceeds_balance": "Beloppet överstiger plånbokens tillgängliga belopp", "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_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
"details_wallet_selection": "Wallet Selection", "details_wallet_selection": "Wallet Selection",
"dynamic_init": "Initialing", "dynamic_init": "Initializing",
"dynamic_next": "Nästa", "dynamic_next": "Nästa",
"dynamic_prev": "Föregående", "dynamic_prev": "Föregående",
"dynamic_start": "Starta", "dynamic_start": "Starta",

View file

@ -170,7 +170,7 @@
"details_next": "ถัดไป", "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_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_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_note_placeholder": "หมายเหตุถึงตัวท่านเอง",
"details_scan": "สแกน", "details_scan": "สแกน",
"details_total_exceeds_balance": "จำนวนเงินที่จะส่งเกินเงินที่มี.", "details_total_exceeds_balance": "จำนวนเงินที่จะส่งเกินเงินที่มี.",

View file

@ -170,13 +170,13 @@
"details_next": "Next", "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_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_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_note_placeholder": "kendime not",
"details_scan": "Tara", "details_scan": "Tara",
"details_total_exceeds_balance": "Gönderme miktarı mevcut bakiyeyi aşıyor.", "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_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
"details_wallet_selection": "Wallet Selection", "details_wallet_selection": "Wallet Selection",
"dynamic_init": "Initialing", "dynamic_init": "Initializing",
"dynamic_next": "Next", "dynamic_next": "Next",
"dynamic_prev": "Previous", "dynamic_prev": "Previous",
"dynamic_start": "Start", "dynamic_start": "Start",

View file

@ -12,7 +12,7 @@
}, },
"azteco": { "azteco": {
"codeIs": "Your voucher code is", "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?", "errorSomething": "Something went wrong. Is this voucher still valid?",
"redeem": "Redeem to wallet", "redeem": "Redeem to wallet",
"redeemButton": "Redeem", "redeemButton": "Redeem",
@ -30,10 +30,10 @@
"network": "网络错误" "network": "网络错误"
}, },
"hodl": { "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_escrow": "Escrow",
"cont_address_to": "To", "cont_address_to": "To",
"cont_buying": "buying", "cont_buying": "购买",
"cont_cancel": "Cancel contract", "cont_cancel": "Cancel contract",
"cont_cancel_q": "Are you sure you want to cancel this contract?", "cont_cancel_q": "Are you sure you want to cancel this contract?",
"cont_cancel_y": "Yes, cancel contract", "cont_cancel_y": "Yes, cancel contract",
@ -170,13 +170,13 @@
"details_next": "Next", "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_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_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_note_placeholder": "消息",
"details_scan": "扫描", "details_scan": "扫描",
"details_total_exceeds_balance": "余额不足", "details_total_exceeds_balance": "余额不足",
"details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.", "details_wallet_before_tx": "Before creating a transaction, you must first add a Bitcoin wallet.",
"details_wallet_selection": "Wallet Selection", "details_wallet_selection": "Wallet Selection",
"dynamic_init": "Initialing", "dynamic_init": "Initializing",
"dynamic_next": "Next", "dynamic_next": "Next",
"dynamic_prev": "Previous", "dynamic_prev": "Previous",
"dynamic_start": "Start", "dynamic_start": "Start",

View file

@ -27,7 +27,7 @@ export const FiatUnit = Object.freeze({
SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' }, SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' },
SEK: { endPointKey: 'SEK', symbol: 'kr', locale: 'sv-SE' }, SEK: { endPointKey: 'SEK', symbol: 'kr', locale: 'sv-SE' },
THB: { endPointKey: 'THB', symbol: '฿', locale: 'th-TH' }, 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' }, UAH: { endPointKey: 'UAH', symbol: '₴', locale: 'uk-UA' },
VEF: { endPointKey: 'VEF', symbol: 'Bs.', locale: 'es-VE' }, VEF: { endPointKey: 'VEF', symbol: 'Bs.', locale: 'es-VE' },
ZAR: { endPointKey: 'ZAR', symbol: 'R', locale: 'en-ZA' }, ZAR: { endPointKey: 'ZAR', symbol: 'R', locale: 'en-ZA' },

View file

@ -20,7 +20,7 @@ export class NetworkTransactionFee {
export default class NetworkTransactionFees { export default class NetworkTransactionFees {
static recommendedFees() { static recommendedFees() {
// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => { return new Promise(async resolve => {
try { try {
const response = await BlueElectrum.estimateFees(); const response = await BlueElectrum.estimateFees();
if (typeof response === 'object') { if (typeof response === 'object') {
@ -28,12 +28,12 @@ export default class NetworkTransactionFees {
resolve(networkFee); resolve(networkFee);
} else { } else {
const networkFee = new NetworkTransactionFee(1, 1, 1); const networkFee = new NetworkTransactionFee(1, 1, 1);
reject(networkFee); resolve(networkFee);
} }
} catch (err) { } catch (err) {
console.warn(err); console.warn(err);
const networkFee = new NetworkTransactionFee(1, 1, 1); const networkFee = new NetworkTransactionFee(1, 1, 1);
reject(networkFee); resolve(networkFee);
} }
}); });
} }

12141
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,26 @@
{ {
"name": "bluewallet", "name": "bluewallet",
"version": "5.6.0", "version": "5.6.2",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.6", "@babel/core": "^7.10.4",
"@babel/runtime": "^7.9.6", "@babel/runtime": "^7.10.4",
"@react-native-community/eslint-config": "^1.1.0", "@jest/reporters": "^26.4.1",
"@react-native-community/eslint-config": "^2.0.0",
"babel-cli": "^6.26.0", "babel-cli": "^6.26.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-jest": "^26.0.1", "babel-jest": "^26.1.0",
"babel-preset-flow": "^6.23.0", "babel-preset-flow": "^6.23.0",
"eslint": "^6.8.0", "eslint": "^7.5.0",
"eslint-plugin-babel": "^5.3.0", "eslint-plugin-babel": "^5.3.1",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.20.0", "eslint-plugin-react": "^7.20.3",
"flow-bin": "^0.125.1", "flow-bin": "^0.134.0",
"jest": "^24.9.0", "jest": "^26.1.0",
"jetifier": "^1.6.3", "jetifier": "^1.6.3",
"react-test-renderer": "16.11.0" "react-test-renderer": "16.13.1"
}, },
"engines": { "engines": {
"node": ">=10.16.0", "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: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-build": "npx detox build -c android.emu.release",
"e2e:release-test": "detox test -c android.emu.release --record-videos all --take-screenshots all --headless", "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: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", "lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep '\\.js' --color=never | awk '{print $2}' | xargs eslint --fix; exit 0",
"unit": "jest tests/unit/*" "unit": "jest tests/unit/*"
@ -55,24 +56,27 @@
"setupFiles": [ "setupFiles": [
"./tests/setup.js" "./tests/setup.js"
], ],
"watchPathIgnorePatterns": [
"<rootDir>/node_modules"
],
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
"./tests/setupAfterEnv.js" "./tests/setupAfterEnv.js"
] ]
}, },
"dependencies": { "dependencies": {
"@babel/preset-env": "7.11.0", "@babel/preset-env": "7.11.5",
"@react-native-community/async-storage": "1.12.0", "@react-native-community/async-storage": "1.12.1",
"@react-native-community/blur": "3.6.0", "@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/geolocation": "2.0.2",
"@react-native-community/masked-view": "0.1.10", "@react-native-community/masked-view": "0.1.10",
"@react-native-community/push-notification-ios": "1.5.0", "@react-native-community/push-notification-ios": "1.5.0",
"@react-native-community/slider": "3.0.3", "@react-native-community/slider": "3.0.3",
"@react-navigation/drawer": "5.9.0", "@react-navigation/drawer": "5.9.2",
"@react-navigation/native": "5.7.3", "@react-navigation/native": "5.7.5",
"@react-navigation/stack": "5.9.0", "@react-navigation/stack": "5.9.2",
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git", "@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", "amplitude-js": "5.11.0",
"assert": "1.5.0", "assert": "1.5.0",
"bc-bech32": "file:blue_modules/bc-bech32", "bc-bech32": "file:blue_modules/bc-bech32",
@ -82,16 +86,16 @@
"bip21": "2.0.3", "bip21": "2.0.3",
"bip32": "2.0.5", "bip32": "2.0.5",
"bip39": "2.6.0", "bip39": "2.6.0",
"bitcoinjs-lib": "5.1.10", "bitcoinjs-lib": "5.2.0",
"bolt11": "1.2.7", "bolt11": "1.2.7",
"buffer": "5.6.0", "buffer": "5.6.0",
"buffer-reverse": "1.0.1", "buffer-reverse": "1.0.1",
"coinselect": "3.1.12", "coinselect": "3.1.12",
"crypto-js": "3.1.9-1", "crypto-js": "3.1.9-1",
"dayjs": "1.8.33", "dayjs": "1.8.35",
"detox": "17.5.6", "detox": "17.5.6",
"ecurve": "1.0.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", "electrum-mnemonic": "2.0.0",
"eslint-config-prettier": "6.11.0", "eslint-config-prettier": "6.11.0",
"eslint-config-standard": "14.1.1", "eslint-config-standard": "14.1.1",
@ -104,29 +108,30 @@
"lottie-react-native": "3.5.0", "lottie-react-native": "3.5.0",
"metro-react-native-babel-preset": "0.63.0", "metro-react-native-babel-preset": "0.63.0",
"path-browserify": "1.0.1", "path-browserify": "1.0.1",
"payjoin-client": "git+https://github.com/bitcoinjs/payjoin-client.git#31d2118a4c0d00192d975f3a6da2a96238f8f7a5",
"pbkdf2": "3.1.1", "pbkdf2": "3.1.1",
"prettier": "2.1.1", "prettier": "2.1.1",
"process": "0.11.10", "process": "0.11.10",
"prop-types": "15.7.2", "prop-types": "15.7.2",
"react": "16.11.0", "react": "16.13.1",
"react-localization": "1.0.15", "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-blue-crypto": "git+https://github.com/Overtorment/react-native-blue-crypto.git",
"react-native-camera": "3.39.1", "react-native-camera": "3.39.1",
"react-native-default-preference": "1.4.3", "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-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-fingerprint-scanner": "git+https://github.com/BlueWallet/react-native-fingerprint-scanner.git#ce644673681716335d786727bab998f7e632ab5e",
"react-native-fs": "2.16.6", "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-handoff": "git+https://github.com/marcosrdz/react-native-handoff.git",
"react-native-haptic-feedback": "1.10.0", "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-inappbrowser-reborn": "git+https://github.com/BlueWallet/react-native-inappbrowser.git#v3.4.0",
"react-native-level-fs": "3.0.1", "react-native-level-fs": "3.0.1",
"react-native-linear-gradient": "2.5.6", "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-modal": "11.5.6",
"react-native-obscure": "1.2.1", "react-native-obscure": "1.2.1",
"react-native-passcode-auth": "git+https://github.com/BlueWallet/react-native-passcode-auth.git#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", "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-safe-area-context": "3.1.8",
"react-native-screens": "2.11.0", "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-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-snap-carousel": "3.9.1",
"react-native-sortable-list": "0.0.24", "react-native-sortable-list": "0.0.24",
"react-native-svg": "12.1.0", "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-tooltip": "git+https://github.com/BlueWallet/react-native-tooltip.git#d369e7ece09e4dec73873f1cfeac83e9d35294a6",
"react-native-vector-icons": "6.6.0", "react-native-vector-icons": "6.6.0",
"react-native-watch-connectivity": "1.0.2", "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", "react-test-render": "1.1.2",
"readable-stream": "3.6.0", "readable-stream": "3.6.0",
"realm": "6.1.0", "realm": "6.1.0",

View file

@ -125,9 +125,8 @@ const ReceiveDetails = () => {
backgroundColor: BlueCurrentTheme.colors.elevated, backgroundColor: BlueCurrentTheme.colors.elevated,
}, },
share: { share: {
alignItems: 'center',
alignContent: 'flex-end',
marginBottom: 24, marginBottom: 24,
marginHorizontal: 16,
}, },
modalButton: { modalButton: {
backgroundColor: BlueCurrentTheme.colors.modalButton, backgroundColor: BlueCurrentTheme.colors.modalButton,
@ -193,7 +192,7 @@ const ReceiveDetails = () => {
if (!address) { if (!address) {
// either sleep expired or getAddressAsync threw an exception // either sleep expired or getAddressAsync threw an exception
console.warn('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 { } else {
BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally BlueApp.saveToDisk(); // caching whatever getAddressAsync() generated internally
} }

View file

@ -1,19 +1,19 @@
/* global alert */ /* global alert */
import React, { useState } from 'react'; 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 { RNCamera } from 'react-native-camera';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import ImagePicker from 'react-native-image-picker'; import ImagePicker from 'react-native-image-picker';
import { useNavigation, useRoute, useIsFocused, useTheme } from '@react-navigation/native'; 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 loc from '../../loc';
import { BlueLoadingHook, BlueTextHooks, BlueButtonHook, BlueSpacing40 } from '../../BlueComponents'; import { BlueLoadingHook, BlueTextHooks, BlueButtonHook, BlueSpacing40 } from '../../BlueComponents';
import { getSystemName } from 'react-native-device-info'; 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 LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const createHash = require('create-hash'); const createHash = require('create-hash');
const isDesktop = getSystemName() === 'Mac OS X'; const isDesktop = getSystemName() === 'Mac OS X';
const fs = require('../../blue_modules/fs');
const styles = StyleSheet.create({ const styles = StyleSheet.create({
root: { root: {
@ -65,12 +65,21 @@ const styles = StyleSheet.create({
backdoorButton: { backdoorButton: {
width: 40, width: 40,
height: 40, height: 40,
backgroundColor: 'rgba(0,0,0,0)', backgroundColor: 'rgba(0,0,0,0.1)',
justifyContent: 'center',
borderRadius: 0,
position: 'absolute', 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 isFocused = useIsFocused();
const [cameraStatus, setCameraStatus] = useState(RNCamera.Constants.CameraStatus.PENDING_AUTHORIZATION); const [cameraStatus, setCameraStatus] = useState(RNCamera.Constants.CameraStatus.PENDING_AUTHORIZATION);
const [backdoorPressed, setBackdoorPressed] = useState(0); const [backdoorPressed, setBackdoorPressed] = useState(0);
const [backdoorText, setBackdoorText] = useState('');
const [backdoorVisible, setBackdoorVisible] = useState(false);
const [animatedQRCodeData, setAnimatedQRCodeData] = useState({});
const stylesHook = StyleSheet.create({ const stylesHook = StyleSheet.create({
openSettingsContainer: { openSettingsContainer: {
backgroundColor: colors.brandingColor, backgroundColor: colors.brandingColor,
@ -94,6 +106,34 @@ const ScanQRCode = () => {
return createHash('sha256').update(s).digest().toString('hex'); 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 onBarCodeRead = ret => {
const h = HashIt(ret.data); const h = HashIt(ret.data);
if (scannedCache[h]) { if (scannedCache[h]) {
@ -102,6 +142,10 @@ const ScanQRCode = () => {
} }
scannedCache[h] = +new Date(); scannedCache[h] = +new Date();
if (ret.data.toUpperCase().startsWith('UR')) {
return _onReadUniformResource(ret.data);
}
if (!isLoading) { if (!isLoading) {
setIsLoading(true); setIsLoading(true);
try { try {
@ -121,26 +165,10 @@ const ScanQRCode = () => {
}; };
const showFilePicker = async () => { const showFilePicker = async () => {
try {
setIsLoading(true); setIsLoading(true);
const res = await DocumentPicker.pick(); const { data } = await fs.showFilePickerAndReadFile();
const file = await RNFS.readFile(res.uri); if (data) onBarCodeRead({ data });
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(false);
}
}; };
const showImagePicker = () => { const showImagePicker = () => {
@ -218,6 +246,45 @@ const ScanQRCode = () => {
<TouchableOpacity style={styles.imagePickerTouch} onPress={showImagePicker}> <TouchableOpacity style={styles.imagePickerTouch} onPress={showImagePicker}>
<Icon name="image" type="font-awesome" color="#ffffff" /> <Icon name="image" type="font-awesome" color="#ffffff" />
</TouchableOpacity> </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 <TouchableOpacity
testID="ScanQrBackdoorButton" testID="ScanQrBackdoorButton"
style={styles.backdoorButton} style={styles.backdoorButton}
@ -227,23 +294,10 @@ const ScanQRCode = () => {
// this allows to mock and test QR scanning in e2e tests // this allows to mock and test QR scanning in e2e tests
setBackdoorPressed(backdoorPressed + 1); setBackdoorPressed(backdoorPressed + 1);
if (backdoorPressed < 10) return; if (backdoorPressed < 10) return;
let data, userInput; setBackdoorPressed(0);
try { setBackdoorVisible(true);
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 });
}} }}
/> />
{showFileImportButton && (
<TouchableOpacity style={styles.filePickerTouch} onPress={showFilePicker}>
<Icon name="file-import" type="material-community" color="#ffffff" />
</TouchableOpacity>
)}
</View> </View>
); );
}; };

View file

@ -1,7 +1,9 @@
/* global alert */ /* global alert */
import React, { Component } from 'react'; 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 { 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 { BlueButton, BlueText, SafeBlueArea, BlueCard, BlueSpacing40, BlueNavigationStyle } from '../../BlueComponents';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -32,7 +34,10 @@ export default class Confirm extends Component {
this.state = { this.state = {
isLoading: false, 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(), feeSatoshi: new Bignumber(props.route.params.fee).multipliedBy(100000000).toNumber(),
memo: props.route.params.memo, memo: props.route.params.memo,
recipients: props.route.params.recipients, recipients: props.route.params.recipients,
@ -50,25 +55,29 @@ export default class Confirm extends Component {
this.isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled(); this.isBiometricUseCapableAndEnabled = await Biometric.isBiometricUseCapableAndEnabled();
} }
broadcast() { send() {
this.setState({ isLoading: true }, async () => { this.setState({ isLoading: true }, async () => {
try { try {
// await BlueElectrum.ping(); const txids2watch = [];
await BlueElectrum.waitTillConnected(); if (!this.state.isPayjoinEnabled) {
await this.broadcast(this.state.tx);
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);
} else { } else {
const wallet = new PayjoinTransaction(this.state.psbt, txHex => this.broadcast(txHex), this.state.fromWallet);
const payjoinClient = new PayjoinClient({
wallet,
payjoinUrl: this.state.payjoinUrl,
});
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(); const txid = bitcoin.Transaction.fromHex(this.state.tx).getId();
notifications.majorTomToGroundControl([], [], [txid]); txids2watch.push(txid);
notifications.majorTomToGroundControl([], [], txids2watch);
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs
let amount = 0; let amount = 0;
const recipients = this.state.recipients; const recipients = this.state.recipients;
@ -101,8 +110,8 @@ export default class Confirm extends Component {
amount, amount,
dismissModal: () => this.props.navigation.dangerouslyGetParent().pop(), dismissModal: () => this.props.navigation.dangerouslyGetParent().pop(),
}); });
this.setState({ isLoading: false }); this.setState({ isLoading: false });
}
} catch (error) { } catch (error) {
ReactNativeHapticFeedback.trigger('notificationError', { ReactNativeHapticFeedback.trigger('notificationError', {
ignoreAndroidSystemSettings: false, 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 }) => { _renderItem = ({ index, item }) => {
return ( return (
<> <>
@ -168,11 +195,17 @@ export default class Confirm extends Component {
{currency.satoshiToLocalCurrency(this.state.feeSatoshi)}) {currency.satoshiToLocalCurrency(this.state.feeSatoshi)})
</Text> </Text>
<BlueSpacing40 /> <BlueSpacing40 />
{this.state.isLoading ? ( {!!this.state.payjoinUrl && (
<ActivityIndicator /> <View style={styles.payjoinWrapper}>
) : ( <Text style={styles.payjoinText}>Payjoin</Text>
<BlueButton onPress={() => this.broadcast()} title={loc.send.confirm_sendNow} /> <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 <TouchableOpacity
testID="TransactionDetailsButton" testID="TransactionDetailsButton"
@ -287,11 +320,20 @@ const styles = StyleSheet.create({
fontWeight: '500', fontWeight: '500',
alignSelf: 'center', alignSelf: 'center',
}, },
payjoinWrapper: {
flexDirection: 'row',
marginHorizontal: 20,
marginBottom: 10,
justifyContent: 'space-between',
alignItems: 'center',
},
payjoinText: { color: '#81868e', fontSize: 14 },
}); });
Confirm.propTypes = { Confirm.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.func, goBack: PropTypes.func,
dismiss: PropTypes.func,
navigate: PropTypes.func, navigate: PropTypes.func,
dangerouslyGetParent: PropTypes.func, dangerouslyGetParent: PropTypes.func,
}), }),

View file

@ -253,8 +253,8 @@ SendCreate.navigationOptions = ({ navigation, route }) => {
} }
return { return {
...BlueNavigationStyle, ...BlueNavigationStyle(),
title: loc.send.create.details, title: loc.send.create_details,
headerRight, headerRight,
}; };
}; };

View file

@ -38,15 +38,17 @@ import * as bitcoin from 'bitcoinjs-lib';
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees'; import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; 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 { BitcoinTransaction } from '../../models/bitcoinTransactionInfo';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match'; import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import loc from '../../loc'; import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes'; import { BlueCurrentTheme } from '../../components/themes';
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
const currency = require('../../blue_modules/currency'); const currency = require('../../blue_modules/currency');
const BlueApp: AppStorage = require('../../BlueApp'); const BlueApp: AppStorage = require('../../BlueApp');
const prompt = require('../../blue_modules/prompt'); const prompt = require('../../blue_modules/prompt');
const fs = require('../../blue_modules/fs');
const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/; const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/;
@ -131,7 +133,6 @@ const styles = StyleSheet.create({
createButton: { createButton: {
marginHorizontal: 56, marginHorizontal: 56,
marginVertical: 16, marginVertical: 16,
alignItems: 'center',
minHeight: 44, minHeight: 44,
}, },
select: { select: {
@ -309,12 +310,14 @@ export default class SendDetails extends Component {
units[this.state.recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC units[this.state.recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC
recipients[[this.state.recipientsScrollIndex]].address = address; recipients[[this.state.recipientsScrollIndex]].address = address;
recipients[[this.state.recipientsScrollIndex]].amount = options.amount; recipients[[this.state.recipientsScrollIndex]].amount = options.amount;
recipients[[this.state.recipientsScrollIndex]].amountSats = new BigNumber(options.amount).multipliedBy(100000000).toNumber();
this.setState({ this.setState({
addresses: recipients, addresses: recipients,
memo: options.label || options.message, memo: options.label || options.message,
isLoading: false, isLoading: false,
amountUnit: BitcoinUnit.BTC, amountUnit: BitcoinUnit.BTC,
units, units,
payjoinUrl: options.pj || '',
}); });
} else { } else {
this.setState({ isLoading: false }); this.setState({ isLoading: false });
@ -332,10 +335,10 @@ export default class SendDetails extends Component {
if (this.props.route.params.uri) { if (this.props.route.params.uri) {
const uri = this.props.route.params.uri; const uri = this.props.route.params.uri;
try { 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))); addresses.push(new BitcoinTransaction(address, amount, currency.btcToSatoshi(amount)));
initialMemo = memo; 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) { } catch (error) {
console.log(error); console.log(error);
alert(loc.send.details_error_decode); alert(loc.send.details_error_decode);
@ -359,10 +362,10 @@ export default class SendDetails extends Component {
} }
} catch (_) {} } catch (_) {}
await this.reCalcTx(); this.reCalcTx();
try { try {
const recommendedFees = await NetworkTransactionFees.recommendedFees(); const recommendedFees = await Promise.race([NetworkTransactionFees.recommendedFees(), BlueApp.sleep(2000)]);
if (recommendedFees && 'fastestFee' in recommendedFees) { if (recommendedFees && 'fastestFee' in recommendedFees) {
await AsyncStorage.setItem(NetworkTransactionFee.StorageKey, JSON.stringify(recommendedFees)); await AsyncStorage.setItem(NetworkTransactionFee.StorageKey, JSON.stringify(recommendedFees));
this.setState({ this.setState({
@ -370,21 +373,28 @@ export default class SendDetails extends Component {
networkTransactionFees: recommendedFees, networkTransactionFees: recommendedFees,
}); });
} }
} catch (_) {} } catch (_) {} // either sleep expired or recommendedFees threw an exception
if (this.props.route.params.uri) { if (this.props.route.params.uri) {
try { try {
const { address, amount, memo } = this.decodeBitcoinUri(this.props.route.params.uri); const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(this.props.route.params.uri);
this.setState({ address, amount, memo }); this.setState({ address, amount, memo, isLoading: false, payjoinUrl });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
this.setState({ isLoading: false });
alert(loc.send.details_error_decode); 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 }); this.setState({ isLoading: false });
await this.reCalcTx();
this.reCalcTx();
} }
componentWillUnmount() { componentWillUnmount() {
@ -400,27 +410,6 @@ export default class SendDetails extends Component {
this.setState({ renderWalletSelectionButtonHidden: false, isAmountToolbarVisibleForAndroid: false }); 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() { async createTransaction() {
Keyboard.dismiss(); Keyboard.dismiss();
this.setState({ isLoading: true }); 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. * Recalculating fee options by creating skeleton of future tx.
*/ */
reCalcTx = async (all = false) => { reCalcTx = (all = false) => {
const wallet = this.state.fromWallet; const wallet = this.state.fromWallet;
const fees = this.state.networkTransactionFees; const fees = this.state.networkTransactionFees;
const changeAddress = await wallet.getChangeAddressAsync(); const changeAddress = this.getChangeAddressFast();
const requestedSatPerByte = Number(this.state.fee); const requestedSatPerByte = Number(this.state.fee);
const feePrecalc = { ...this.state.feePrecalc }; const feePrecalc = { ...this.state.feePrecalc };
@ -564,7 +603,7 @@ export default class SendDetails extends Component {
async createPsbtTransaction() { async createPsbtTransaction() {
/** @type {HDSegwitBech32Wallet} */ /** @type {HDSegwitBech32Wallet} */
const wallet = this.state.fromWallet; const wallet = this.state.fromWallet;
const changeAddress = await wallet.getChangeAddressAsync(); const changeAddress = await this.getChangeAddressAsync();
const requestedSatPerByte = Number(this.state.fee); const requestedSatPerByte = Number(this.state.fee);
console.log({ requestedSatPerByte, utxo: wallet.getUtxo() }); console.log({ requestedSatPerByte, utxo: wallet.getUtxo() });
@ -606,6 +645,16 @@ export default class SendDetails extends Component {
return; 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 = BlueApp.tx_metadata || {};
BlueApp.tx_metadata[tx.getId()] = { BlueApp.tx_metadata[tx.getId()] = {
txhex: tx.toHex(), txhex: tx.toHex(),
@ -619,6 +668,8 @@ export default class SendDetails extends Component {
tx: tx.toHex(), tx: tx.toHex(),
recipients: targets, recipients: targets,
satoshiPerByte: requestedSatPerByte, satoshiPerByte: requestedSatPerByte,
payjoinUrl: this.state.payjoinUrl,
psbt,
}); });
this.setState({ isLoading: false }); 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 () => { importTransaction = async () => {
if (this.state.fromWallet.type !== WatchOnlyWallet.type) {
alert('Error: importing transaction in non-watchonly wallet (this should never happen)');
return;
}
try { try {
const res = await DocumentPicker.pick({ 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 file = await RNFS.readFile(res.uri, 'ascii');
const bufferDecoded = Buffer.from(file, 'ascii').toString('base64'); const psbt = bitcoin.Psbt.fromBase64(file);
if (bufferDecoded) { const txhex = psbt.extractTransaction().toHex();
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', { this.props.navigation.navigate('PsbtWithHardwareWallet', {
memo: this.state.memo, memo: this.state.memo,
fromWallet: this.state.fromWallet, fromWallet: this.state.fromWallet,
psbt: file, txhex,
}); });
this.setState({ isLoading: false }); this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
return; } else if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
} // looks like transaction is UNsigned, so we construct PSBT object and pass to next screen
} else { // so user can do smth with it:
throw new Error(); 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)) { } else if (DeeplinkSchemaMatch.isTXNFile(res.uri)) {
// plain text file with txhex ready to broadcast
const file = await RNFS.readFile(res.uri, 'ascii'); const file = await RNFS.readFile(res.uri, 'ascii');
this.props.navigation.navigate('PsbtWithHardwareWallet', { this.props.navigation.navigate('PsbtWithHardwareWallet', {
memo: this.state.memo, memo: this.state.memo,
@ -805,7 +878,8 @@ export default class SendDetails extends Component {
txhex: file, txhex: file,
}); });
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false }); this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
return; } else {
alert('Unrecognized file format');
} }
} catch (err) { } catch (err) {
if (!DocumentPicker.isCancel(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 = () => { renderAdvancedTransactionOptionsModal = () => {
const isSendMaxUsed = this.state.addresses.some(element => element.amount === BitcoinUnit.MAX); const isSendMaxUsed = this.state.addresses.some(element => element.amount === BitcoinUnit.MAX);
return ( return (
@ -856,6 +1036,22 @@ export default class SendDetails extends Component {
onPress={this.importTransaction} 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() && ( {this.state.fromWallet.allowBatchSend() && (
<> <>
<BlueListItem <BlueListItem
@ -863,43 +1059,14 @@ export default class SendDetails extends Component {
title={loc.send.details_add_rec_add} title={loc.send.details_add_rec_add}
hideChevron hideChevron
component={TouchableOpacity} component={TouchableOpacity}
onPress={() => { onPress={this.handleAddRecipient}
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 });
},
);
}}
/> />
<BlueListItem <BlueListItem
title={loc.send.details_add_rec_rem} title={loc.send.details_add_rec_rem}
hideChevron hideChevron
disabled={this.state.addresses.length < 2} disabled={this.state.addresses.length < 2}
component={TouchableOpacity} component={TouchableOpacity}
onPress={() => { onPress={this.handleRemoveRecipient}
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 });
},
);
}}
/> />
</> </>
)} )}
@ -1033,7 +1200,7 @@ export default class SendDetails extends Component {
onChangeText={async text => { onChangeText={async text => {
text = text.trim(); text = text.trim();
const transactions = this.state.addresses; 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.address = address || text;
item.amount = amount || item.amount; item.amount = amount || item.amount;
transactions[index] = item; transactions[index] = item;
@ -1041,6 +1208,7 @@ export default class SendDetails extends Component {
addresses: transactions, addresses: transactions,
memo: memo || this.state.memo, memo: memo || this.state.memo,
isLoading: false, isLoading: false,
payjoinUrl,
}); });
this.reCalcTx(); this.reCalcTx();
}} }}
@ -1185,6 +1353,7 @@ SendDetails.propTypes = {
goBack: PropTypes.func, goBack: PropTypes.func,
navigate: PropTypes.func, navigate: PropTypes.func,
setParams: PropTypes.func, setParams: PropTypes.func,
dangerouslyGetParent: PropTypes.func,
}), }),
route: PropTypes.shape({ route: PropTypes.shape({
name: PropTypes.string, name: PropTypes.string,

491
screen/send/psbtMultisig.js Normal file
View 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;

View file

@ -33,7 +33,6 @@ import { getSystemName } from 'react-native-device-info';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import RNFS from 'react-native-fs'; import RNFS from 'react-native-fs';
import DocumentPicker from 'react-native-document-picker'; import DocumentPicker from 'react-native-document-picker';
import { decodeUR, extractSingleWorkload } from 'bc-ur/dist';
import loc from '../../loc'; import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes'; import { BlueCurrentTheme } from '../../components/themes';
import ScanQRCode from './ScanQRCode'; import ScanQRCode from './ScanQRCode';
@ -68,6 +67,7 @@ const styles = StyleSheet.create({
rootPadding: { rootPadding: {
flex: 1, flex: 1,
paddingTop: 20, paddingTop: 20,
backgroundColor: BlueCurrentTheme.colors.elevated,
}, },
closeCamera: { closeCamera: {
width: 40, width: 40,
@ -89,6 +89,7 @@ const styles = StyleSheet.create({
hexWrap: { hexWrap: {
alignItems: 'center', alignItems: 'center',
flex: 1, flex: 1,
backgroundColor: BlueCurrentTheme.colors.elevated,
}, },
hexLabel: { hexLabel: {
color: BlueCurrentTheme.colors.foregroundColor, color: BlueCurrentTheme.colors.foregroundColor,
@ -124,44 +125,6 @@ const styles = StyleSheet.create({
export default class PsbtWithHardwareWallet extends Component { export default class PsbtWithHardwareWallet extends Component {
cameraRef = null; 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 => { _combinePSBT = receivedPSBT => {
return this.state.fromWallet.combinePsbt(this.state.psbt, receivedPSBT); return this.state.fromWallet.combinePsbt(this.state.psbt, receivedPSBT);
}; };
@ -169,11 +132,11 @@ export default class PsbtWithHardwareWallet extends Component {
onBarScanned = ret => { onBarScanned = ret => {
if (ret && !ret.data) ret = { data: ret }; if (ret && !ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) { 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) { 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 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; return;
} }
try { try {
@ -201,11 +164,20 @@ export default class PsbtWithHardwareWallet extends Component {
} }
static getDerivedStateFromProps(nextProps, prevState) { 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 deepLinkPSBT = nextProps.route.params.deepLinkPSBT;
const txhex = nextProps.route.params.txhex; const txhex = nextProps.route.params.txhex;
if (deepLinkPSBT) { if (deepLinkPSBT) {
const psbt = bitcoin.Psbt.fromBase64(deepLinkPSBT);
try { try {
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, deepLinkPSBT); const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, psbt);
return { return {
...prevState, ...prevState,
txhex: Tx.toHex(), txhex: Tx.toHex(),

View file

@ -1,19 +1,104 @@
import React, { Component } from 'react'; import React, { useEffect, useRef } from 'react';
import LottieView from 'lottie-react-native'; import LottieView from 'lottie-react-native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { View, StyleSheet } from 'react-native'; import { View, StyleSheet } from 'react-native';
import { Text } from 'react-native-elements'; import { Text } from 'react-native-elements';
import { BlueButton, SafeBlueArea, BlueCard } from '../../BlueComponents'; import { BlueButton, SafeBlueArea, BlueCard } from '../../BlueComponents';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
import PropTypes from 'prop-types';
import loc from '../../loc'; 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({ const styles = StyleSheet.create({
root: { root: {
flex: 1, flex: 1,
paddingTop: 19, paddingTop: 19,
backgroundColor: BlueCurrentTheme.colors.elevated,
}, },
amout: { amout: {
alignItems: 'center', alignItems: 'center',
@ -26,12 +111,10 @@ const styles = StyleSheet.create({
paddingBottom: 16, paddingBottom: 16,
}, },
amountValue: { amountValue: {
color: BlueCurrentTheme.colors.alternativeTextColor2,
fontSize: 36, fontSize: 36,
fontWeight: '600', fontWeight: '600',
}, },
amountUnit: { amountUnit: {
color: BlueCurrentTheme.colors.alternativeTextColor2,
fontSize: 16, fontSize: 16,
marginHorizontal: 4, marginHorizontal: 4,
paddingBottom: 6, paddingBottom: 6,
@ -61,92 +144,3 @@ const styles = StyleSheet.create({
height: 400, 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,
};

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ScrollView, Platform, TouchableWithoutFeedback, TouchableOpacity, StyleSheet } from 'react-native'; 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 { AppStorage } from '../../class';
import { useNavigation, useTheme } from '@react-navigation/native'; import { useNavigation, useTheme } from '@react-navigation/native';
import HandoffSettings from '../../class/handoff'; import HandoffSettings from '../../class/handoff';
@ -58,17 +58,12 @@ const GeneralSettings = () => {
<ScrollView style={stylesWithThemeHook.scroll}> <ScrollView style={stylesWithThemeHook.scroll}>
{BlueApp.getWallets().length > 1 && ( {BlueApp.getWallets().length > 1 && (
<> <>
<BlueListItemHooks <BlueListItem component={TouchableOpacity} onPress={() => navigate('DefaultView')} title={loc.settings.default_title} chevron />
component={TouchableOpacity}
onPress={() => navigate('DefaultView')}
title={loc.settings.default_title}
chevron
/>
</> </>
)} )}
{Platform.OS === 'ios' ? ( {Platform.OS === 'ios' ? (
<> <>
<BlueListItemHooks <BlueListItem
hideChevron hideChevron
title={loc.settings.general_continuity} title={loc.settings.general_continuity}
Component={TouchableWithoutFeedback} Component={TouchableWithoutFeedback}
@ -80,7 +75,7 @@ const GeneralSettings = () => {
<BlueSpacing20 /> <BlueSpacing20 />
</> </>
) : null} ) : null}
<BlueListItemHooks <BlueListItem
Component={TouchableWithoutFeedback} Component={TouchableWithoutFeedback}
title={loc.settings.general_adv_mode} title={loc.settings.general_adv_mode}
switch={{ onValueChange: onAdvancedModeSwitch, value: isAdancedModeEnabled }} switch={{ onValueChange: onAdvancedModeSwitch, value: isAdancedModeEnabled }}

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ScrollView, StyleSheet } from 'react-native'; 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 { useNavigation, useTheme } from '@react-navigation/native';
import loc from '../../loc'; import loc from '../../loc';
@ -29,9 +29,9 @@ const NetworkSettings = () => {
return ( return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}> <SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
<ScrollView> <ScrollView>
<BlueListItemHooks title={loc.settings.network_electrum} onPress={navigateToElectrumSettings} chevron /> <BlueListItem title={loc.settings.network_electrum} onPress={navigateToElectrumSettings} chevron />
<BlueListItemHooks title={loc.settings.lightning_settings} onPress={navigateToLightningSettings} chevron /> <BlueListItem title={loc.settings.lightning_settings} onPress={navigateToLightningSettings} chevron />
<BlueListItemHooks title={loc.settings.network_broadcast} onPress={navigateToBroadcast} chevron /> <BlueListItem title={loc.settings.network_broadcast} onPress={navigateToBroadcast} chevron />
</ScrollView> </ScrollView>
</SafeBlueArea> </SafeBlueArea>
); );

View file

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React from 'react';
import { ScrollView, Linking, Dimensions, Image, View, Text, StyleSheet } from 'react-native'; import { ScrollView, Linking, Image, View, Text, StyleSheet, useWindowDimensions } from 'react-native';
import { useNavigation, useTheme } from '@react-navigation/native'; import { useNavigation, useTheme } from '@react-navigation/native';
import { import {
BlueTextCentered, BlueTextCentered,
@ -7,20 +7,17 @@ import {
BlueButton, BlueButton,
SafeBlueArea, SafeBlueArea,
BlueCard, BlueCard,
BlueListItemHooks, BlueListItem,
BlueNavigationStyle, BlueNavigationStyle,
BlueLoadingHook,
} from '../../BlueComponents'; } from '../../BlueComponents';
import { getApplicationName, getVersion, getBundleId, getBuildNumber } from 'react-native-device-info'; import { getApplicationName, getVersion, getBundleId, getBuildNumber } from 'react-native-device-info';
import Rate, { AndroidMarket } from 'react-native-rate'; import Rate, { AndroidMarket } from 'react-native-rate';
import loc from '../../loc'; import loc from '../../loc';
const { width, height } = Dimensions.get('window');
const About = () => { const About = () => {
const [isLoading, setIsLoading] = useState(true);
const { navigate } = useNavigation(); const { navigate } = useNavigation();
const { colors } = useTheme(); const { colors } = useTheme();
const { width, height } = useWindowDimensions();
const styles = StyleSheet.create({ const styles = StyleSheet.create({
root: { root: {
flex: 1, flex: 1,
@ -58,10 +55,6 @@ const About = () => {
}, },
}); });
useEffect(() => {
setIsLoading(false);
}, []);
const handleOnReleaseNotesPress = () => { const handleOnReleaseNotesPress = () => {
navigate('ReleaseNotes'); navigate('ReleaseNotes');
}; };
@ -102,9 +95,7 @@ const About = () => {
}); });
}; };
return isLoading ? ( return (
<BlueLoadingHook />
) : (
<SafeBlueArea style={styles.root}> <SafeBlueArea style={styles.root}>
<ScrollView testID="AboutScrollView"> <ScrollView testID="AboutScrollView">
<BlueCard> <BlueCard>
@ -115,7 +106,7 @@ const About = () => {
<BlueButton onPress={handleOnRatePress} title={loc.settings.about_review + ' ⭐🙏'} /> <BlueButton onPress={handleOnRatePress} title={loc.settings.about_review + ' ⭐🙏'} />
</View> </View>
</BlueCard> </BlueCard>
<BlueListItemHooks <BlueListItem
leftIcon={{ leftIcon={{
name: 'twitter', name: 'twitter',
type: 'font-awesome', type: 'font-awesome',
@ -124,7 +115,7 @@ const About = () => {
onPress={handleOnTwitterPress} onPress={handleOnTwitterPress}
title={loc.settings.about_sm_twitter} title={loc.settings.about_sm_twitter}
/> />
<BlueListItemHooks <BlueListItem
leftIcon={{ leftIcon={{
name: 'telegram', name: 'telegram',
type: 'font-awesome', type: 'font-awesome',
@ -133,7 +124,7 @@ const About = () => {
onPress={handleOnTelegramPress} onPress={handleOnTelegramPress}
title={loc.settings.about_sm_telegram} title={loc.settings.about_sm_telegram}
/> />
<BlueListItemHooks <BlueListItem
leftIcon={{ leftIcon={{
name: 'github', name: 'github',
type: 'font-awesome', type: 'font-awesome',
@ -154,7 +145,7 @@ const About = () => {
<BlueTextCentered>Electrum server</BlueTextCentered> <BlueTextCentered>Electrum server</BlueTextCentered>
</View> </View>
</BlueCard> </BlueCard>
<BlueListItemHooks <BlueListItem
leftIcon={{ leftIcon={{
name: 'book', name: 'book',
type: 'font-awesome', type: 'font-awesome',
@ -164,7 +155,7 @@ const About = () => {
onPress={handleOnReleaseNotesPress} onPress={handleOnReleaseNotesPress}
title={loc.settings.about_release_notes} title={loc.settings.about_release_notes}
/> />
<BlueListItemHooks <BlueListItem
leftIcon={{ leftIcon={{
name: 'law', name: 'law',
type: 'octicon', type: 'octicon',
@ -174,7 +165,7 @@ const About = () => {
onPress={handleOnLicensingPress} onPress={handleOnLicensingPress}
title="MIT License" title="MIT License"
/> />
<BlueListItemHooks <BlueListItem
leftIcon={{ leftIcon={{
name: 'flask', name: 'flask',
type: 'font-awesome', type: 'font-awesome',

View file

@ -1,8 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { FlatList, TouchableOpacity, ActivityIndicator, View, StyleSheet } from 'react-native'; 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 PropTypes from 'prop-types';
import { Icon } from 'react-native-elements';
import { FiatUnit } from '../../models/fiatUnit'; import { FiatUnit } from '../../models/fiatUnit';
import loc from '../../loc'; import loc from '../../loc';
import { useTheme } from '@react-navigation/native'; import { useTheme } from '@react-navigation/native';
@ -52,11 +51,11 @@ const Currency = () => {
extraData={data} extraData={data}
renderItem={({ item }) => { renderItem={({ item }) => {
return ( return (
<BlueListItemHooks <BlueListItem
disabled={isSavingNewPreferredCurrency} disabled={isSavingNewPreferredCurrency}
title={`${item.endPointKey} (${item.symbol})`} title={`${item.endPointKey} (${item.symbol})`}
{...(selectedCurrency.endPointKey === item.endPointKey {...(selectedCurrency.endPointKey === item.endPointKey
? { rightIcon: <Icon name="check" type="octaicon" color="#0070FF" /> } ? { rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' } }
: { hideChevron: true })} : { hideChevron: true })}
Component={TouchableOpacity} Component={TouchableOpacity}
onPress={async () => { onPress={async () => {

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { View, TouchableWithoutFeedback, StyleSheet } from 'react-native'; import { View, TouchableWithoutFeedback, StyleSheet } from 'react-native';
import { useNavigation } from '@react-navigation/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 OnAppLaunch from '../../class/on-app-launch';
import loc from '../../loc'; import loc from '../../loc';
const BlueApp = require('../../BlueApp'); const BlueApp = require('../../BlueApp');
@ -57,7 +57,7 @@ const DefaultView = () => {
return ( return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.flex}> <SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.flex}>
<View> <View>
<BlueListItemHooks <BlueListItem
title={loc.settings.default_wallets} title={loc.settings.default_wallets}
Component={TouchableWithoutFeedback} Component={TouchableWithoutFeedback}
switch={{ switch={{
@ -70,7 +70,7 @@ const DefaultView = () => {
<BlueTextHooks>{loc.settings.default_desc}</BlueTextHooks> <BlueTextHooks>{loc.settings.default_desc}</BlueTextHooks>
</BlueCard> </BlueCard>
{!viewAllWalletsEnabled && ( {!viewAllWalletsEnabled && (
<BlueListItemHooks title={loc.settings.default_info} onPress={selectWallet} rightTitle={defaultWalletLabel} chevron /> <BlueListItem title={loc.settings.default_info} onPress={selectWallet} rightTitle={defaultWalletLabel} chevron />
)} )}
</View> </View>
</SafeBlueArea> </SafeBlueArea>

View file

@ -8,7 +8,7 @@ import {
SafeBlueArea, SafeBlueArea,
BlueSpacing20, BlueSpacing20,
BlueCard, BlueCard,
BlueListItemHooks, BlueListItem,
BlueHeaderDefaultSubHooks, BlueHeaderDefaultSubHooks,
BlueTextHooks, BlueTextHooks,
BlueNavigationStyle, BlueNavigationStyle,
@ -150,7 +150,7 @@ const EncryptStorage = () => {
{biometrics.isDeviceBiometricCapable && ( {biometrics.isDeviceBiometricCapable && (
<> <>
<BlueHeaderDefaultSubHooks leftText="biometrics" rightComponent={null} /> <BlueHeaderDefaultSubHooks leftText="biometrics" rightComponent={null} />
<BlueListItemHooks <BlueListItem
title={loc.formatString(loc.settings.encrypt_use, { type: biometrics.biometricsType })} title={loc.formatString(loc.settings.encrypt_use, { type: biometrics.biometricsType })}
Component={TouchableWithoutFeedback} Component={TouchableWithoutFeedback}
switch={{ value: biometrics.isBiometricsEnabled, onValueChange: onUseBiometricSwitch }} switch={{ value: biometrics.isBiometricsEnabled, onValueChange: onUseBiometricSwitch }}
@ -162,7 +162,7 @@ const EncryptStorage = () => {
</> </>
)} )}
<BlueHeaderDefaultSubHooks leftText={loc.settings.encrypt_tstorage} rightComponent={null} /> <BlueHeaderDefaultSubHooks leftText={loc.settings.encrypt_tstorage} rightComponent={null} />
<BlueListItemHooks <BlueListItem
testID="EncyptedAndPasswordProtected" testID="EncyptedAndPasswordProtected"
hideChevron hideChevron
title={loc.settings.encrypt_enc_and_pass} title={loc.settings.encrypt_enc_and_pass}
@ -170,7 +170,7 @@ const EncryptStorage = () => {
switch={{ onValueChange: onEncryptStorageSwitch, value: storageIsEncrypted }} switch={{ onValueChange: onEncryptStorageSwitch, value: storageIsEncrypted }}
/> />
{Platform.OS === 'ios' && ( {Platform.OS === 'ios' && (
<BlueListItemHooks <BlueListItem
hideChevron hideChevron
title={loc.settings.encrypt_del_uninstall} title={loc.settings.encrypt_del_uninstall}
Component={TouchableWithoutFeedback} Component={TouchableWithoutFeedback}
@ -181,7 +181,7 @@ const EncryptStorage = () => {
/> />
)} )}
{storageIsEncrypted && ( {storageIsEncrypted && (
<BlueListItemHooks <BlueListItem
onPress={navigateToPlausibleDeniability} onPress={navigateToPlausibleDeniability}
title={loc.settings.plausible_deniability} title={loc.settings.plausible_deniability}
chevron chevron

View file

@ -1,7 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { FlatList, StyleSheet } from 'react-native'; import { FlatList, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueListItemHooks, BlueCard, BlueLoadingHook, BlueNavigationStyle, BlueTextHooks } from '../../BlueComponents'; import { SafeBlueArea, BlueListItem, BlueCard, BlueLoadingHook, BlueNavigationStyle, BlueTextHooks } from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import { AvailableLanguages } from '../../loc/languages'; import { AvailableLanguages } from '../../loc/languages';
import loc from '../../loc'; import loc from '../../loc';
@ -22,7 +21,7 @@ const Language = () => {
const renderItem = useCallback( const renderItem = useCallback(
({ item }) => { ({ item }) => {
return ( return (
<BlueListItemHooks <BlueListItem
onPress={() => { onPress={() => {
console.log('setLanguage', item.value); console.log('setLanguage', item.value);
loc.saveLanguage(item.value); loc.saveLanguage(item.value);
@ -31,7 +30,7 @@ const Language = () => {
title={item.label} title={item.label}
{...(language === item.value {...(language === item.value
? { ? {
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />, rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
} }
: { hideChevron: true })} : { hideChevron: true })}
/> />

View file

@ -5,7 +5,7 @@ import {
BlueLoading, BlueLoading,
BlueTextHooks, BlueTextHooks,
BlueSpacing20, BlueSpacing20,
BlueListItemHooks, BlueListItem,
BlueNavigationStyle, BlueNavigationStyle,
BlueCard, BlueCard,
BlueButton, BlueButton,
@ -101,7 +101,7 @@ const NotificationSettings = () => {
<BlueLoading /> <BlueLoading />
) : ( ) : (
<ScrollView style={stylesWithThemeHook.scroll}> <ScrollView style={stylesWithThemeHook.scroll}>
<BlueListItemHooks <BlueListItem
Component={TouchableWithoutFeedback} Component={TouchableWithoutFeedback}
title={loc.settings.push_notifications} title={loc.settings.push_notifications}
switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled }} switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled }}

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ScrollView, TouchableOpacity, StyleSheet, StatusBar } from 'react-native'; 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 { useNavigation } from '@react-navigation/native';
import loc from '../../loc'; import loc from '../../loc';
@ -17,24 +17,24 @@ const Settings = () => {
<ScrollView style={styles.root}> <ScrollView style={styles.root}>
<StatusBar barStyle="default" /> <StatusBar barStyle="default" />
<BlueHeaderDefaultSubHooks leftText={loc.settings.header} rightComponent={null} /> <BlueHeaderDefaultSubHooks leftText={loc.settings.header} rightComponent={null} />
<BlueListItemHooks title={loc.settings.general} component={TouchableOpacity} onPress={() => navigate('GeneralSettings')} chevron /> <BlueListItem title={loc.settings.general} component={TouchableOpacity} onPress={() => navigate('GeneralSettings')} chevron />
<BlueListItemHooks title={loc.settings.currency} component={TouchableOpacity} onPress={() => navigate('Currency')} chevron /> <BlueListItem title={loc.settings.currency} component={TouchableOpacity} onPress={() => navigate('Currency')} chevron />
<BlueListItemHooks title={loc.settings.language} component={TouchableOpacity} onPress={() => navigate('Language')} chevron /> <BlueListItem title={loc.settings.language} component={TouchableOpacity} onPress={() => navigate('Language')} chevron />
<BlueListItemHooks <BlueListItem
title={loc.settings.encrypt_title} title={loc.settings.encrypt_title}
onPress={() => navigate('EncryptStorage')} onPress={() => navigate('EncryptStorage')}
component={TouchableOpacity} component={TouchableOpacity}
testID="SecurityButton" testID="SecurityButton"
chevron chevron
/> />
<BlueListItemHooks title={loc.settings.network} component={TouchableOpacity} onPress={() => navigate('NetworkSettings')} chevron /> <BlueListItem title={loc.settings.network} component={TouchableOpacity} onPress={() => navigate('NetworkSettings')} chevron />
<BlueListItemHooks <BlueListItem
title={loc.settings.notifications} title={loc.settings.notifications}
component={TouchableOpacity} component={TouchableOpacity}
onPress={() => navigate('NotificationSettings')} onPress={() => navigate('NotificationSettings')}
chevron chevron
/> />
<BlueListItemHooks <BlueListItem
title={loc.settings.about} title={loc.settings.about}
component={TouchableOpacity} component={TouchableOpacity}
onPress={() => navigate('About')} onPress={() => navigate('About')}

View file

@ -215,7 +215,12 @@ export default class TransactionsStatus extends Component {
} }
const tx = new HDSegwitBech32Transaction(null, this.state.tx.hash, this.state.wallet); 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 }); return this.setState({ isRBFBumpFeePossible: buttonStatus.possible });
} else { } else {
return this.setState({ isRBFBumpFeePossible: buttonStatus.notPossible }); return this.setState({ isRBFBumpFeePossible: buttonStatus.notPossible });

View file

@ -16,7 +16,7 @@ import AsyncStorage from '@react-native-community/async-storage';
import { import {
BlueTextCenteredHooks, BlueTextCenteredHooks,
BlueTextHooks, BlueTextHooks,
BlueListItemHooks, BlueListItem,
LightningButton, LightningButton,
BitcoinButton, BitcoinButton,
BlueFormLabel, BlueFormLabel,
@ -27,7 +27,6 @@ import {
} from '../../BlueComponents'; } from '../../BlueComponents';
import { HDSegwitBech32Wallet, SegwitP2SHWallet, HDSegwitP2SHWallet, LightningCustodianWallet, AppStorage } from '../../class'; import { HDSegwitBech32Wallet, SegwitP2SHWallet, HDSegwitP2SHWallet, LightningCustodianWallet, AppStorage } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements';
import { useTheme, useNavigation } from '@react-navigation/native'; import { useTheme, useNavigation } from '@react-navigation/native';
import { Chain } from '../../models/bitcoinUnits'; import { Chain } from '../../models/bitcoinUnits';
import loc from '../../loc'; import loc from '../../loc';
@ -93,7 +92,6 @@ const styles = StyleSheet.create({
borderRadius: 4, borderRadius: 4,
}, },
createButton: { createButton: {
alignItems: 'center',
flex: 1, flex: 1,
marginTop: 32, marginTop: 32,
}, },
@ -312,36 +310,36 @@ const WalletsAdd = () => {
<View> <View>
<BlueSpacing20 /> <BlueSpacing20 />
<Text style={[styles.advancedText, stylesHook.advancedText]}>{loc.settings.advanced_options}</Text> <Text style={[styles.advancedText, stylesHook.advancedText]}>{loc.settings.advanced_options}</Text>
<BlueListItemHooks <BlueListItem
containerStyle={[styles.noPadding, stylesHook.noPadding]} containerStyle={[styles.noPadding, stylesHook.noPadding]}
bottomDivider={false} bottomDivider={false}
onPress={() => setSelectedIndex(0)} onPress={() => setSelectedIndex(0)}
title={HDSegwitBech32Wallet.typeReadable} title={HDSegwitBech32Wallet.typeReadable}
{...(selectedIndex === 0 {...(selectedIndex === 0
? { ? {
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />, rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
} }
: { hideChevron: true })} : { hideChevron: true })}
/> />
<BlueListItemHooks <BlueListItem
containerStyle={[styles.noPadding, stylesHook.noPadding]} containerStyle={[styles.noPadding, stylesHook.noPadding]}
bottomDivider={false} bottomDivider={false}
onPress={() => setSelectedIndex(1)} onPress={() => setSelectedIndex(1)}
title={SegwitP2SHWallet.typeReadable} title={SegwitP2SHWallet.typeReadable}
{...(selectedIndex === 1 {...(selectedIndex === 1
? { ? {
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />, rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
} }
: { hideChevron: true })} : { hideChevron: true })}
/> />
<BlueListItemHooks <BlueListItem
containerStyle={[styles.noPadding, stylesHook.noPadding]} containerStyle={[styles.noPadding, stylesHook.noPadding]}
bottomDivider={false} bottomDivider={false}
onPress={() => setSelectedIndex(2)} onPress={() => setSelectedIndex(2)}
title={HDSegwitP2SHWallet.typeReadable} title={HDSegwitP2SHWallet.typeReadable}
{...(selectedIndex === 2 {...(selectedIndex === 2
? { ? {
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />, rightIcon: { name: 'check', type: 'octaicon', color: '#0070FF' },
} }
: { hideChevron: true })} : { hideChevron: true })}
/> />
@ -351,7 +349,7 @@ const WalletsAdd = () => {
return ( return (
<> <>
<BlueSpacing20 /> <BlueSpacing20 />
<Text style={styles.advancedText}>{loc.settings.advanced_options}</Text> <Text style={[styles.advancedText, stylesHook.advancedText]}>{loc.settings.advanced_options}</Text>
<BlueSpacing20 /> <BlueSpacing20 />
<BlueTextHooks>Connect to your LNDHub</BlueTextHooks> <BlueTextHooks>Connect to your LNDHub</BlueTextHooks>
<View style={[styles.lndUri, stylesHook.lndUri]}> <View style={[styles.lndUri, stylesHook.lndUri]}>

View file

@ -14,6 +14,7 @@ import {
Linking, Linking,
StyleSheet, StyleSheet,
StatusBar, StatusBar,
PermissionsAndroid,
} from 'react-native'; } from 'react-native';
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueNavigationStyle, BlueText, BlueLoadingHook } from '../../BlueComponents'; import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueNavigationStyle, BlueText, BlueLoadingHook } from '../../BlueComponents';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet'; 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 { HDSegwitP2SHWallet } from '../../class/wallets/hd-segwit-p2sh-wallet';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import Biometric from '../../class/biometrics'; 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 { ScrollView } from 'react-native-gesture-handler';
import loc from '../../loc'; import loc from '../../loc';
import { useTheme, useRoute, useNavigation } from '@react-navigation/native'; 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 EV = require('../../blue_modules/events');
const prompt = require('../../blue_modules/prompt'); const prompt = require('../../blue_modules/prompt');
const BlueApp = require('../../BlueApp'); const BlueApp = require('../../BlueApp');
const notifications = require('../../blue_modules/notifications'); const notifications = require('../../blue_modules/notifications');
const isDesktop = getSystemName() === 'Mac OS X';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
root: { root: {
@ -94,6 +99,7 @@ const styles = StyleSheet.create({
const WalletDetails = () => { const WalletDetails = () => {
const { wallet } = useRoute().params; const { wallet } = useRoute().params;
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [backdoorPressed, setBackdoorPressed] = useState(0);
const [walletName, setWalletName] = useState(wallet.getLabel()); const [walletName, setWalletName] = useState(wallet.getLabel());
const [useWithHardwareWallet, setUseWithHardwareWallet] = useState(wallet.useWithHardwareWalletEnabled()); const [useWithHardwareWallet, setUseWithHardwareWallet] = useState(wallet.useWithHardwareWalletEnabled());
const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList()); const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList());
@ -148,6 +154,7 @@ const WalletDetails = () => {
const presentWalletHasBalanceAlert = useCallback(async () => { const presentWalletHasBalanceAlert = useCallback(async () => {
ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false }); ReactNativeHapticFeedback.trigger('notificationWarning', { ignoreAndroidSystemSettings: false });
try {
const walletBalanceConfirmation = await prompt( const walletBalanceConfirmation = await prompt(
loc.wallets.details_del_wb, loc.wallets.details_del_wb,
loc.formatString(loc.wallets.details_del_wb_q, { balance: wallet.getBalance() }), loc.formatString(loc.wallets.details_del_wb_q, { balance: wallet.getBalance() }),
@ -169,6 +176,7 @@ const WalletDetails = () => {
setIsLoading(false); setIsLoading(false);
alert(loc.wallets.details_del_wb_err); alert(loc.wallets.details_del_wb_err);
} }
} catch (_) {}
}, [popToTop, setParams, wallet]); }, [popToTop, setParams, wallet]);
const navigateToWalletExport = () => { const navigateToWalletExport = () => {
@ -176,6 +184,11 @@ const WalletDetails = () => {
wallet, wallet,
}); });
}; };
const navigateToMultisigCoordinationSetup = () => {
navigate('ExportMultisigCoordinationSetup', {
walletId: wallet.getID(),
});
};
const navigateToXPub = () => const navigateToXPub = () =>
navigate('WalletXpub', { navigate('WalletXpub', {
secret: wallet.getSecret(), 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 = () => { const navigateToBroadcast = () => {
navigate('Broadcast'); navigate('Broadcast');
}; };
@ -295,6 +367,35 @@ const WalletDetails = () => {
<BlueSpacing20 /> <BlueSpacing20 />
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_type.toLowerCase()}</Text> <Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_type.toLowerCase()}</Text>
<Text style={[styles.textValue, stylesHook.textValue]}>{wallet.typeReadable}</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 && ( {wallet.type === LightningCustodianWallet.type && (
<> <>
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_connected_to.toLowerCase()}</Text> <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}> <View style={styles.hardware}>
<BlueText>{loc.wallets.details_display}</BlueText> <BlueText>{loc.wallets.details_display}</BlueText>
<Switch value={hideTransactionsInWalletsList} onValueChange={setHideTransactionsInWalletsList} /> <Switch value={hideTransactionsInWalletsList} onValueChange={setHideTransactionsInWalletsList} />
@ -333,6 +436,15 @@ const WalletDetails = () => {
<BlueSpacing20 /> <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 === HDLegacyBreadwalletWallet.type ||
wallet.type === HDLegacyP2PKHWallet.type || wallet.type === HDLegacyP2PKHWallet.type ||
wallet.type === HDSegwitBech32Wallet.type || wallet.type === HDSegwitBech32Wallet.type ||

View file

@ -1,7 +1,7 @@
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef } from 'react';
import { StatusBar, View, TouchableOpacity, InteractionManager, StyleSheet, Alert, useWindowDimensions } from 'react-native'; import { StatusBar, View, TouchableOpacity, StyleSheet, Alert, useWindowDimensions } from 'react-native';
import { DrawerContentScrollView } from '@react-navigation/drawer'; 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 { Icon } from 'react-native-elements';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -14,11 +14,11 @@ import { useTheme, useRoute } from '@react-navigation/native';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
const EV = require('../../blue_modules/events'); const EV = require('../../blue_modules/events');
const BlueApp: AppStorage = require('../../BlueApp'); const BlueApp: AppStorage = require('../../BlueApp');
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const DrawerList = props => { const DrawerList = props => {
console.log('drawerList rendering...');
const walletsCarousel = useRef(); const walletsCarousel = useRef();
const [wallets, setWallets] = useState(BlueApp.getWallets().concat(false)); const wallets = useRoute().params?.wallets || BlueApp.getWallets() || [];
const height = useWindowDimensions().height; const height = useWindowDimensions().height;
const { colors } = useTheme(); const { colors } = useTheme();
const { selectedWallet } = useRoute().params || ''; const { selectedWallet } = useRoute().params || '';
@ -27,83 +27,6 @@ const DrawerList = props => {
backgroundColor: colors.brandingColor, 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 => { const handleClick = index => {
console.log('click', 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 = () => { const renderWalletsCarousel = () => {
return ( return (
<WalletsCarousel <WalletsCarousel
removeClippedSubviews={false} removeClippedSubviews={false}
data={wallets} data={wallets.concat(false)}
onPress={handleClick} onPress={handleClick}
handleLongPress={handleLongPress} handleLongPress={handleLongPress}
onSnapToItem={onSnapToItem}
ref={walletsCarousel} ref={walletsCarousel}
testID="WalletsList" testID="WalletsList"
vertical 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 ( return (
<DrawerContentScrollView {...props} scrollEnabled={false}> <DrawerContentScrollView {...props} scrollEnabled={false}>
<View styles={[styles.root, stylesHook.root]}> <View styles={[styles.root, stylesHook.root]}>
<StatusBar barStyle="default" /> <StatusBar barStyle="default" />
<SafeAreaView style={styles.root}> <SafeAreaView style={styles.root}>
<BlueHeaderDefaultMainHooks <BlueHeaderDefaultMain leftText={loc.wallets.list_title} onNewWalletPress={onNewWalletPress} isDrawerList />
leftText={loc.wallets.list_title}
onNewWalletPress={
!BlueApp.getWallets().some(wallet => wallet.type === PlaceholderWallet.type)
? () => props.navigation.navigate('AddWalletRoot')
: null
}
/>
</SafeAreaView> </SafeAreaView>
{renderWalletsCarousel()} {renderWalletsCarousel()}
</View> </View>

View 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