Merge remote-tracking branch 'origin/master' into buttonsMultisig

This commit is contained in:
Overtorment 2020-11-05 17:35:58 +00:00
commit ad1eaec4df
8 changed files with 118 additions and 40 deletions

View file

@ -12,6 +12,9 @@
"dont_allow": "Ne dovoli", "dont_allow": "Ne dovoli",
"yes": "Da", "yes": "Da",
"no": "Ne", "no": "Ne",
"save": "Shrani",
"seed": "Seme",
"wallet_key": "Ključ denarnice",
"invalid_animated_qr_code_fragment" : "Neveljaven del animirane QR kode, prosimo poskusite ponovno", "invalid_animated_qr_code_fragment" : "Neveljaven del animirane QR kode, prosimo poskusite ponovno",
"file_saved": "Datoteka ({filePath}) je bila shranjena v mapo Prenosi." "file_saved": "Datoteka ({filePath}) je bila shranjena v mapo Prenosi."
}, },
@ -317,7 +320,7 @@
"transactions_count": "število transakcij" "transactions_count": "število transakcij"
}, },
"wallets": { "wallets": {
"add_bitcoin_explain": "Simple and powerful Bitcoin wallet", "add_bitcoin_explain": "Preprosta in zmogljiva Bitcoin denarnica",
"add_bitcoin": "Bitcoin", "add_bitcoin": "Bitcoin",
"add_create": "Ustvari", "add_create": "Ustvari",
"add_entropy_generated": "{gen} bajtov ustvarjene entropije", "add_entropy_generated": "{gen} bajtov ustvarjene entropije",
@ -326,7 +329,7 @@
"add_import_wallet": "Uvozi denarnico", "add_import_wallet": "Uvozi denarnico",
"import_file": "Uvozi datoteko", "import_file": "Uvozi datoteko",
"add_lightning": "Lightning", "add_lightning": "Lightning",
"add_lightning_explain": "For spending with instant transactions", "add_lightning_explain": "Za hitre vsakodnevne transakcije",
"add_lndhub": "Povežite se s svojim LNDHub-om", "add_lndhub": "Povežite se s svojim LNDHub-om",
"add_lndhub_error": "Podan naslov vozlišča ni veljavno vozlišče LNDHub.", "add_lndhub_error": "Podan naslov vozlišča ni veljavno vozlišče LNDHub.",
"add_lndhub_placeholder": "naslov vašega vozlišča", "add_lndhub_placeholder": "naslov vašega vozlišča",
@ -391,10 +394,11 @@
"xpub_title": "XPUB denarnice" "xpub_title": "XPUB denarnice"
}, },
"multisig": { "multisig": {
"multisig_vault": "Vault", "multisig_vault": "Trezor",
"multisig_vault_explain": "Best security for large amounts", "multisig_vault_explain": "Največja varnost za višje zneske",
"provide_signature": "Vnesite podpis", "provide_signature": "Vnesite podpis",
"vault_key": "Ključ trezorja {number}", "vault_key": "Ključ trezorja {number}",
"required_keys_out_of_total": "Zahtevani ključi od vseh",
"fee": "Omrežnina: {number}", "fee": "Omrežnina: {number}",
"fee_btc": "{number} BTC", "fee_btc": "{number} BTC",
"confirm": "Potrditev", "confirm": "Potrditev",
@ -404,6 +408,41 @@
"scan_or_import_file": "Skenirajte ali uvozite datoteko", "scan_or_import_file": "Skenirajte ali uvozite datoteko",
"export_coordination_setup": "izvoz koordinacijskih nastavitev", "export_coordination_setup": "izvoz koordinacijskih nastavitev",
"cosign_this_transaction": "Sopodpis te transakcije?", "cosign_this_transaction": "Sopodpis te transakcije?",
"co_sign_transaction": "Sopodpis QR-airgapped transakcije" "lets_start": "Začnimo",
"create": "Ustvari",
"provide_key": "Vnesite ključ",
"native_segwit_title": "Najb. praksa",
"wrapped_segwit_title": "Najb. združljivost",
"legacy_title": "Zastarelo",
"co_sign_transaction": "Sopodpis QR-airgapped transakcije",
"what_is_vault": "Trezor je",
"what_is_vault_numberOfWallets": " {m}-od-{n} multisig ",
"what_is_vault_wallet": "denarnica",
"vault_advanced_customize": "Nastavitve trezorja...",
"needs": "Zahtevana sta",
"what_is_vault_description_number_of_vault_keys": " {m} ključa trezorja, ",
"what_is_vault_description_to_spend": "tretji pa\npredstavlja rezervo.",
"quorum": "{m} od {n} kvorum",
"quorum_header": "Kvorum",
"of": "od",
"wallet_type": "Tip denarnice",
"view_key": "prikaži",
"invalid_mnemonics": "Zdi se, da to mnemonično seme ni veljavno",
"invalid_cosigner": "Neveljavni podatki sopodpisnika",
"invalid_cosigner_format": "Nepravilen sopodpisnik: to ni sopodpisnik za {format} obliko",
"create_new_key": "Ustvari novega",
"scan_or_open_file": "Skenirajte ali odprite datoteko",
"i_have_mnemonics": "Za ta ključ imam seme...",
"please_write_down_mnemonics": "Prosimo, zapišite si seznam besed (mnemonično seme) na list papirja. Lahko si zapišete tudi pozneje.",
"i_wrote_it_down": "V redu, sem si zapisal",
"type_your_mnemonics": "Vnesite seme za uvoz obstoječega ključa trezorja",
"this_is_cosigners_xpub": "To je xpub sopodpisnika, pripravljen za uvoz v drugo denarnico. Varno ga lahko delite.",
"wallet_key_created": "Ključ trezorja je bil ustvarjen. Vzemite si trenutek, ter zapišite seznam besed (mnemonično seme) na list papirja.",
"are_you_sure_seed_will_be_lost": "Ali ste prepričani? Če nimate varnostne kopije, bo vaše mnemonično seme izgubljeno",
"forget_this_seed": "Pozabi to seme in uporabi xpub",
"invalid_fingerprint": "Prstni odtis (fingerprint) tega semena se ne ujema s sopodpisnikovim",
"view_edit_cosigners": "Prikaži/uredi sopodpisnike",
"this_cosigner_is_already_imported": "Ta sopodpisnik je že uvožen",
"view_edit_cosigners_title": "Urejanje sopodpisnikov"
} }
} }

View file

@ -70,6 +70,7 @@ const styles = StyleSheet.create({
position: 'absolute', position: 'absolute',
}, },
backdoorInputWrapper: { position: 'absolute', left: '5%', top: '0%', width: '90%', height: '70%', backgroundColor: 'white' }, backdoorInputWrapper: { position: 'absolute', left: '5%', top: '0%', width: '90%', height: '70%', backgroundColor: 'white' },
progressWrapper: { position: 'absolute', right: '0%', top: '0%', backgroundColor: 'white' },
backdoorInput: { backdoorInput: {
height: '50%', height: '50%',
marginTop: 5, marginTop: 5,
@ -95,6 +96,8 @@ 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 [urTotal, setUrTotal] = useState(0);
const [urHave, setUrHave] = useState(0);
const [backdoorText, setBackdoorText] = useState(''); const [backdoorText, setBackdoorText] = useState('');
const [backdoorVisible, setBackdoorVisible] = useState(false); const [backdoorVisible, setBackdoorVisible] = useState(false);
const [animatedQRCodeData, setAnimatedQRCodeData] = useState({}); const [animatedQRCodeData, setAnimatedQRCodeData] = useState({});
@ -111,6 +114,8 @@ const ScanQRCode = () => {
try { try {
const [index, total] = extractSingleWorkload(ur); const [index, total] = extractSingleWorkload(ur);
animatedQRCodeData[index + 'of' + total] = ur; animatedQRCodeData[index + 'of' + total] = ur;
setUrTotal(total);
setUrHave(Object.values(animatedQRCodeData).length);
if (Object.values(animatedQRCodeData).length === total) { if (Object.values(animatedQRCodeData).length === total) {
const payload = decodeUR(Object.values(animatedQRCodeData)); const payload = decodeUR(Object.values(animatedQRCodeData));
// lets look inside that data // lets look inside that data
@ -264,6 +269,14 @@ const ScanQRCode = () => {
<Icon name="file-import" type="material-community" color="#ffffff" /> <Icon name="file-import" type="material-community" color="#ffffff" />
</TouchableOpacity> </TouchableOpacity>
)} )}
{urTotal > 0 && (
<View style={styles.progressWrapper} testID="UrProgressBar">
<BlueTextHooks>
{urHave} / {urTotal}
</BlueTextHooks>
</View>
)}
{backdoorVisible && ( {backdoorVisible && (
<View style={styles.backdoorInputWrapper}> <View style={styles.backdoorInputWrapper}>
<BlueTextHooks>Provide QR code contents manually:</BlueTextHooks> <BlueTextHooks>Provide QR code contents manually:</BlueTextHooks>
@ -291,6 +304,8 @@ const ScanQRCode = () => {
// this might be a json string (for convenience - in case there are "\n" in there) // this might be a json string (for convenience - in case there are "\n" in there)
} catch (_) { } catch (_) {
data = backdoorText; data = backdoorText;
} finally {
setBackdoorText('');
} }
if (data) onBarCodeRead({ data }); if (data) onBarCodeRead({ data });

View file

@ -9,16 +9,6 @@ import { BitcoinUnit } from '../../models/bitcoinUnits';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
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 {
HDLegacyElectrumSeedP2PKHWallet,
HDLegacyP2PKHWallet,
HDSegwitBech32Wallet,
HDSegwitP2SHWallet,
HDLegacyBreadwalletWallet,
LegacyWallet,
SegwitP2SHWallet,
SegwitBech32Wallet,
} from '../../class';
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc'; import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc';
import { BlueCurrentTheme } from '../../components/themes'; import { BlueCurrentTheme } from '../../components/themes';
import Notifications from '../../blue_modules/notifications'; import Notifications from '../../blue_modules/notifications';
@ -89,21 +79,7 @@ export default class Confirm extends Component {
} }
} }
// wallets that support new createTransaction() instead of deprecated createTx() amount = formatBalanceWithoutSuffix(amount, BitcoinUnit.BTC, false);
if (
[
HDSegwitBech32Wallet.type,
HDSegwitP2SHWallet.type,
HDLegacyP2PKHWallet.type,
HDLegacyBreadwalletWallet.type,
HDLegacyElectrumSeedP2PKHWallet.type,
LegacyWallet.type,
SegwitP2SHWallet.type,
SegwitBech32Wallet.type,
].includes(this.state.fromWallet.type)
) {
amount = formatBalanceWithoutSuffix(amount, BitcoinUnit.BTC, false);
}
this.context.fetchAndSaveWalletTransactions(this.state.fromWallet.getID()); this.context.fetchAndSaveWalletTransactions(this.state.fromWallet.getID());
this.props.navigation.navigate('Success', { this.props.navigation.navigate('Success', {

View file

@ -42,7 +42,7 @@ const WalletsAddMultisig = () => {
color: colors.alternativeTextColor, color: colors.alternativeTextColor,
}, },
selectedItem: { selectedItem: {
backgroundColor: colors.buttonDisabledTextColor, backgroundColor: colors.elevated,
}, },
deSelectedItem: { deSelectedItem: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
@ -146,21 +146,21 @@ const WalletsAddMultisig = () => {
onPress={setFormatP2wsh} onPress={setFormatP2wsh}
title={`${loc.multisig.native_segwit_title} (${MultisigHDWallet.FORMAT_P2WSH})`} title={`${loc.multisig.native_segwit_title} (${MultisigHDWallet.FORMAT_P2WSH})`}
checkmark={isP2wsh()} checkmark={isP2wsh()}
containerStyle={[styles.borderRadius6, isP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]} containerStyle={[styles.borderRadius6, styles.item, isP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/> />
<BlueListItem <BlueListItem
bottomDivider={false} bottomDivider={false}
onPress={setFormatP2shP2wsh} onPress={setFormatP2shP2wsh}
title={`${loc.multisig.wrapped_segwit_title} (${MultisigHDWallet.FORMAT_P2SH_P2WSH})`} title={`${loc.multisig.wrapped_segwit_title} (${MultisigHDWallet.FORMAT_P2SH_P2WSH})`}
checkmark={isP2shP2wsh()} checkmark={isP2shP2wsh()}
containerStyle={[styles.borderRadius6, isP2shP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]} containerStyle={[styles.borderRadius6, styles.item, isP2shP2wsh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/> />
<BlueListItem <BlueListItem
bottomDivider={false} bottomDivider={false}
onPress={setFormatP2sh} onPress={setFormatP2sh}
title={`${loc.multisig.legacy_title} (${MultisigHDWallet.FORMAT_P2SH})`} title={`${loc.multisig.legacy_title} (${MultisigHDWallet.FORMAT_P2SH})`}
checkmark={isP2sh()} checkmark={isP2sh()}
containerStyle={[styles.borderRadius6, isP2sh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]} containerStyle={[styles.borderRadius6, styles.item, isP2sh() ? stylesHook.selectedItem : stylesHook.deSelectedItem]}
/> />
</View> </View>
</KeyboardAvoidingView> </KeyboardAvoidingView>
@ -239,6 +239,9 @@ const styles = StyleSheet.create({
justifyContent: 'flex-end', justifyContent: 'flex-end',
margin: 0, margin: 0,
}, },
item: {
paddingHorizontal: 0,
},
descriptionContainer: { descriptionContainer: {
alignContent: 'center', alignContent: 'center',
justifyContent: 'center', justifyContent: 'center',

View file

@ -1,5 +1,5 @@
/* global alert */ /* global alert */
import React, { useRef, useState } from 'react'; import React, { useContext, useRef, useState } from 'react';
import { import {
ActivityIndicator, ActivityIndicator,
FlatList, FlatList,
@ -32,7 +32,6 @@ import Modal from 'react-native-modal';
import { getSystemName } from 'react-native-device-info'; import { getSystemName } from 'react-native-device-info';
import ImagePicker from 'react-native-image-picker'; import ImagePicker from 'react-native-image-picker';
import ScanQRCode from '../send/ScanQRCode'; import ScanQRCode from '../send/ScanQRCode';
import WalletImport from '../../class/wallet-import';
import QRCode from 'react-native-qrcode-svg'; import QRCode from 'react-native-qrcode-svg';
import { SquareButton } from '../../components/SquareButton'; import { SquareButton } from '../../components/SquareButton';
import { SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaView } from 'react-native-safe-area-context';
@ -43,13 +42,17 @@ import MultipleStepsListItem, {
import Clipboard from '@react-native-community/clipboard'; import Clipboard from '@react-native-community/clipboard';
import showPopupMenu from 'react-native-popup-menu-android'; import showPopupMenu from 'react-native-popup-menu-android';
import ToolTip from 'react-native-tooltip'; import ToolTip from 'react-native-tooltip';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const A = require('../../blue_modules/analytics');
const fs = require('../../blue_modules/fs'); const fs = require('../../blue_modules/fs');
const isDesktop = getSystemName() === 'Mac OS X'; const isDesktop = getSystemName() === 'Mac OS X';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image'); const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const staticCache = {}; const staticCache = {};
const WalletsAddMultisigStep2 = () => { const WalletsAddMultisigStep2 = () => {
const { addWallet, saveToDisk, setNewWalletAdded } = useContext(BlueStorageContext);
const { colors } = useTheme(); const { colors } = useTheme();
const navigation = useNavigation(); const navigation = useNavigation();
@ -125,7 +128,7 @@ const WalletsAddMultisigStep2 = () => {
}, },
}); });
const onCreate = () => { const onCreate = async () => {
setIsLoading(true); setIsLoading(true);
const w = new MultisigHDWallet(); const w = new MultisigHDWallet();
w.setM(m); w.setM(m);
@ -149,7 +152,14 @@ const WalletsAddMultisigStep2 = () => {
w.addCosigner(cc[0], cc[1], cc[2]); w.addCosigner(cc[0], cc[1], cc[2]);
} }
w.setLabel('Multisig Vault'); w.setLabel('Multisig Vault');
WalletImport._saveWallet(w); await w.fetchBalance();
addWallet(w);
await saveToDisk();
setNewWalletAdded(true);
A(A.ENUM.CREATED_WALLET);
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
navigation.dangerouslyGetParent().pop(); navigation.dangerouslyGetParent().pop();
}; };

View file

@ -234,7 +234,7 @@ const WalletsImport = () => {
onPress={importButtonPressed} onPress={importButtonPressed}
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={importScan} /> <BlueButtonLink title={loc.wallets.import_scan_qr} onPress={importScan} testID="ScanImport" />
</> </>
</View> </View>
{Platform.select({ {Platform.select({

View file

@ -551,6 +551,41 @@ describe('BlueWallet UI Tests', () => {
expect(element(by.id('TransactionValue'))).toHaveText('0.0001'); expect(element(by.id('TransactionValue'))).toHaveText('0.0001');
expect(element(by.id('TransactionAddress'))).toHaveText('BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7'); expect(element(by.id('TransactionAddress'))).toHaveText('BC1QH6TF004TY7Z7UN2V5NTU4MKF630545GVHS45U7');
}); });
it('can import multisig setup from UR (ver1) QRs 2 frames', async () => {
await yo('WalletsList');
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
// going to Import Wallet screen and importing mnemonic
await element(by.id('CreateAWallet')).tap();
await element(by.id('ImportWallet')).tap();
await element(by.id('ScanImport')).tap();
const urs = [
'UR:BYTES/1OF2/J8RX04F2WJ9SSY577U30R55ELM4LUCJCXJVJTD60SYV9A286Q0AQH7QXL6/TYQMJGEQGFK82E2HV9KXCET5YPXH2MR5D9EKJEEQWDJHGATSYPNXJMR9PG3JQARGD9EJQENFD3JJQCM0DE6XZ6TWWVSX7MNV0YS8QATZD35KXGRTV4UHXGRPDEJZQ6TNYPEKZEN9YP6X7Z3RYPJXJUM5WF5KYAT5V5SXZMT0DENJQCM0WD5KWMN9WFES5GC2FESK6EF6YPXH2MR5D9EKJEEQ2ESH2MR5PFGX7MRFVDUN5GPJYPHKVGPJPFZX2UNFWESHG6T0DCAZQMF0XSUZWTESYUHNQFE0XGNS53N0WFKKZAP6YPGRY46NFQ9Q53PNXAZ5Z3PC8QAZQKNSW43RWDRFDFCXV6Z92F9YU6NGGD94S5NNWP2XGNZ22C6K2M69D4F4YKNYFPC5GANS',
'UR:BYTES/2OF2/J8RX04F2WJ9SSY577U30R55ELM4LUCJCXJVJTD60SYV9A286Q0AQH7QXL6/8944VARY2EZHJ62CDVMHQKRC2F3XVKN629M8X3ZXWPNYGJZ9FPT8G4NS0Q6YG73EG3R42468DCE9S6E40FRN2AF5X4G4GNTNT9FNYAN2DA5YU5G2PGCNVWZYGSMRQVE6YPD8QATZXU6K6S298PZK57TC2DAX772SD4RKUEP4G5MY672YXAQ5C36WDEJ8YA2HWC6NY7RS0F5K6KJ3FD6KKAMKG4N9S4ZGW9K5SWRWVF3XXDNRVDGR2APJV9XNXMTHWVEHQJ6E2DHYKUZTF4XHJARYVF8Y2KJX24UYK7N6W3V5VNFC2PHQ5ZSJDYL5T',
];
await waitFor(element(by.id('UrProgressBar'))).toBeNotVisible();
for (const ur of urs) {
// tapping 10 times invisible button is a backdoor:
for (let c = 0; c <= 10; c++) {
await element(by.id('ScanQrBackdoorButton')).tap();
}
await element(by.id('scanQrBackdoorInput')).replaceText(ur);
await element(by.id('scanQrBackdoorOkButton')).tap();
await waitFor(element(by.id('UrProgressBar'))).toBeVisible();
}
if (process.env.TRAVIS) await sleep(60000);
await sup('OK', 3 * 61000); // waiting for wallet import
await element(by.text('OK')).tap();
// ok, wallet imported
// lets go inside wallet
const expectedWalletLabel = 'Multisig Vault';
await element(by.text(expectedWalletLabel)).tap();
});
}); });
async function sleep(ms) { async function sleep(ms) {

View file

@ -46,6 +46,6 @@ describe('multisig-hd-wallet', () => {
await w.fetchBalance(); await w.fetchBalance();
await w.fetchTransactions(); await w.fetchTransactions();
assert.strictEqual(w.getTransactions().length, 5); assert.ok(w.getTransactions().length >= 6);
}); });
}); });