import React, { useEffect, useState } from 'react'; import { useRoute } from '@react-navigation/native'; import BigNumber from 'bignumber.js'; import * as bitcoin from 'bitcoinjs-lib'; import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { Icon } from '@rneui/themed'; import { satoshiToBTC, satoshiToLocalCurrency } from '../../blue_modules/currency'; import { BlueCard, BlueText } from '../../BlueComponents'; import presentAlert from '../../components/Alert'; import Button from '../../components/Button'; import SafeArea from '../../components/SafeArea'; import { useTheme } from '../../components/themes'; import loc from '../../loc'; import { BitcoinUnit } from '../../models/bitcoinUnits'; import { useStorage } from '../../hooks/context/useStorage'; import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; 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 { wallets } = useStorage(); const { navigate, setParams } = useExtendedNavigation(); const { colors } = useTheme(); const [flatListHeight, setFlatListHeight] = useState(0); const { walletID, psbtBase64, memo, receivedPSBTBase64, launchedBy } = useRoute().params; /** @type MultisigHDWallet */ const wallet = wallets.find(w => w.getID() === walletID); const [psbt, setPsbt] = useState(bitcoin.Psbt.fromBase64(psbtBase64)); const data = new Array(wallet.getM()); const stylesHook = StyleSheet.create({ root: { backgroundColor: colors.elevated, }, whitespace: { color: colors.elevated, }, textBtc: { color: colors.buttonAlternativeTextColor, }, textBtcUnitValue: { color: colors.buttonAlternativeTextColor, }, textFiat: { color: colors.alternativeTextColor, }, provideSignatureButton: { 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, }, }); 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 = satoshiToLocalCurrency(totalSat); const getFee = () => { return wallet.calculateFeeFromPsbt(psbt); }; const _renderItem = el => { if (el.index >= howManySignaturesWeHave) return _renderItemUnsigned(el); else return _renderItemSigned(el); }; const navigateToPSBTMultisigQRCode = () => { navigate('PsbtMultisigQRCode', { walletID, psbtBase64: psbt.toBase64(), isShowOpenScanner: isConfirmEnabled() }); }; const _renderItemUnsigned = el => { const renderProvideSignature = el.index === howManySignaturesWeHave; return ( {el.index + 1} {loc.formatString(loc.multisig.vault_key, { number: el.index + 1 })} {renderProvideSignature && ( {loc.multisig.provide_signature} )} ); }; const _renderItemSigned = el => { return ( {loc.formatString(loc.multisig.vault_key, { number: el.index + 1 })} ); }; useEffect(() => { if (receivedPSBTBase64) { _combinePSBT(); setParams({ receivedPSBTBase64: undefined }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [receivedPSBTBase64]); const _combinePSBT = () => { try { const receivedPSBT = bitcoin.Psbt.fromBase64(receivedPSBTBase64); const newPsbt = psbt.combine(receivedPSBT); setPsbt(newPsbt); } catch (error) { presentAlert({ message: error }); } }; const onConfirm = () => { try { psbt.finalizeAllInputs(); } catch (_) {} // ignore if it is already finalized if (launchedBy) { // we must navigate back to the screen who requested psbt (instead of broadcasting it ourselves) // most likely for LN channel opening navigate(launchedBy, { psbt }); return; } try { const tx = psbt.extractTransaction().toHex(); const satoshiPerByte = Math.round(getFee() / psbt.extractTransaction().virtualSize()); navigate('Confirm', { fee: new BigNumber(getFee()).dividedBy(100000000).toNumber(), memo, walletID, tx, recipients: targets, satoshiPerByte, }); } catch (error) { presentAlert({ message: error }); } }; const howManySignaturesWeHave = wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt); const isConfirmEnabled = () => { return howManySignaturesWeHave >= wallet.getM(); }; const destinationAddress = () => { // eslint-disable-next-line prefer-const let destinationAddressView = []; const whitespace = '_'; const destinations = Object.entries(destination.split(',')); for (const [index, address] of destinations) { if (index > 1) { destinationAddressView.push( and {destinations.length - 2} more... , ); 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( {firstFour} {whitespace} {middle} {whitespace} {lastFour} , ); } } return destinationAddressView; }; const header = ( {totalBtc} {BitcoinUnit.BTC} {totalFiat} {destinationAddress()} ); const footer = ( <> {loc.formatString(loc.multisig.fee, { number: satoshiToLocalCurrency(getFee()) })} -{' '} {loc.formatString(loc.multisig.fee_btc, { number: satoshiToBTC(getFee()) })}