mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 23:08:07 +01:00
wip
This commit is contained in:
parent
a8858833ef
commit
4849042dc6
7 changed files with 345 additions and 221 deletions
|
@ -40,9 +40,16 @@ export const BlueCard = props => {
|
|||
return <View {...props} style={{ padding: 20 }} />;
|
||||
};
|
||||
|
||||
export const BlueText = props => {
|
||||
export const BlueText = ({ bold = false, ...props }) => {
|
||||
const { colors } = useTheme();
|
||||
const style = StyleSheet.compose({ color: colors.foregroundColor, writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr' }, props.style);
|
||||
const style = StyleSheet.compose(
|
||||
{
|
||||
color: colors.foregroundColor,
|
||||
writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr',
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
},
|
||||
props.style,
|
||||
);
|
||||
return <Text {...props} style={style} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -188,6 +188,7 @@
|
|||
"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",
|
||||
|
||||
"no_tx_signing_in_progress": "There is no transaction signing in progress.",
|
||||
"outdated_rate": "Rate was last updated: {date}",
|
||||
"psbt_tx_open": "Open Signed Transaction",
|
||||
|
@ -508,6 +509,8 @@
|
|||
"default_label": "Multisig Vault",
|
||||
"multisig_vault_explain": "Best security for large amounts",
|
||||
"provide_signature": "Provide signature",
|
||||
"provide_signature_details": "Use your device and wallet where the key resides to sign this transaction",
|
||||
"provide_signature_details_bluewallet": "In BlueWallet, go to the Send screen menu and select 'Sign Transaction'",
|
||||
"vault_key": "Vault Key {number}",
|
||||
"required_keys_out_of_total": "Required keys out of the total",
|
||||
"fee": "Fee: {number}",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Psbt } from 'bitcoinjs-lib';
|
||||
import { CreateTransactionTarget, CreateTransactionUtxo, TWallet } from '../class/wallets/types';
|
||||
import { CreateTransactionTarget, CreateTransactionUtxo } from '../class/wallets/types';
|
||||
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
|
||||
import { ScanQRCodeParamList } from './DetailViewStackParamList';
|
||||
|
||||
|
@ -49,7 +49,6 @@ export type SendDetailsStackParamList = {
|
|||
txhex?: string;
|
||||
};
|
||||
CreateTransaction: {
|
||||
wallet: TWallet;
|
||||
memo?: string;
|
||||
psbt?: Psbt;
|
||||
txhex?: string;
|
||||
|
|
|
@ -131,7 +131,6 @@ const Confirm: React.FC = () => {
|
|||
memo,
|
||||
tx,
|
||||
satoshiPerByte,
|
||||
wallet,
|
||||
feeSatoshi,
|
||||
});
|
||||
}}
|
||||
|
@ -149,7 +148,6 @@ const Confirm: React.FC = () => {
|
|||
memo,
|
||||
tx,
|
||||
satoshiPerByte,
|
||||
wallet,
|
||||
feeSatoshi,
|
||||
],
|
||||
);
|
||||
|
|
|
@ -857,7 +857,6 @@ const SendDetails = () => {
|
|||
navigation.navigate('CreateTransaction', {
|
||||
fee: new BigNumber(psbt.getFee()).dividedBy(100000000).toNumber(),
|
||||
feeSatoshi: psbt.getFee(),
|
||||
wallet,
|
||||
tx: tx.toHex(),
|
||||
recipients,
|
||||
satoshiPerByte: psbt.getFeeRate(),
|
||||
|
@ -869,23 +868,28 @@ const SendDetails = () => {
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('SendDetails - onBarScanned hook triggered');
|
||||
const data = routeParams.onBarScanned;
|
||||
console.log('SendDetails - data:', data);
|
||||
if (data) {
|
||||
if (selectedDataProcessor.current) {
|
||||
console.log('SendDetails - selectedDataProcessor:', selectedDataProcessor.current);
|
||||
switch (selectedDataProcessor.current) {
|
||||
case CommonToolTipActions.ImportTransactionQR:
|
||||
importQrTransactionOnBarScanned(data);
|
||||
break;
|
||||
case CommonToolTipActions.CoSignTransaction:
|
||||
case CommonToolTipActions.SignPSBT:
|
||||
handlePsbtSign(data);
|
||||
break;
|
||||
case CommonToolTipActions.SignPSBT:
|
||||
case CommonToolTipActions.ImportTransactionMultsig:
|
||||
_importTransactionMultisig(data);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown selectedDataProcessor:', selectedDataProcessor.current);
|
||||
}
|
||||
} else {
|
||||
onBarScanned(data);
|
||||
}
|
||||
}
|
||||
setParams({ onBarScanned: undefined });
|
||||
|
@ -987,11 +991,13 @@ const SendDetails = () => {
|
|||
} else if (id === CommonToolTipActions.AllowRBF.id) {
|
||||
onReplaceableFeeSwitchValueChanged(!isTransactionReplaceable);
|
||||
} else if (id === CommonToolTipActions.ImportTransaction.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransaction;
|
||||
importTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionQR.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransactionQR;
|
||||
importQrTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionMultsig.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransactionMultsig;
|
||||
importTransactionMultisig();
|
||||
} else if (id === CommonToolTipActions.CoSignTransaction.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.CoSignTransaction;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useRoute } from '@react-navigation/native';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
|
@ -16,8 +16,9 @@ import { BitcoinUnit } from '../../models/bitcoinUnits';
|
|||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
const shortenAddress = addr =>
|
||||
addr.substr(0, Math.floor(addr.length / 2) - 1) + '\n' + addr.substr(Math.floor(addr.length / 2) - 1, addr.length);
|
||||
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();
|
||||
|
@ -27,93 +28,109 @@ const PsbtMultisig = () => {
|
|||
const { walletID, psbtBase64, memo, receivedPSBTBase64, launchedBy } = useRoute().params;
|
||||
/** @type MultisigHDWallet */
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
const psbt = bitcoin.Psbt.fromBase64(psbtBase64);
|
||||
|
||||
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 },
|
||||
feeFiatText: { color: colors.alternativeTextColor },
|
||||
vaultKeyTextSigned: { color: colors.msSuccessBG },
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
const { destinationStr, totalBtc, totalFiat, targets } = useMemo(() => {
|
||||
let totalSat = 0;
|
||||
const addresses = [];
|
||||
const targetList = [];
|
||||
psbt.txOutputs.forEach(output => {
|
||||
if (output.address && !wallet.weOwnAddress(output.address)) {
|
||||
totalSat += output.value;
|
||||
addresses.push(output.address);
|
||||
targetList.push({ address: output.address, value: output.value });
|
||||
}
|
||||
});
|
||||
return {
|
||||
destinationStr: shortenAddress(addresses.join(', ')),
|
||||
totalBtc: new BigNumber(totalSat).dividedBy(100000000).toNumber(),
|
||||
totalFiat: satoshiToLocalCurrency(totalSat),
|
||||
targets: targetList,
|
||||
};
|
||||
}, [psbt.txOutputs, wallet]);
|
||||
|
||||
const getFee = useCallback(() => wallet.calculateFeeFromPsbt(psbt), [wallet, psbt]);
|
||||
|
||||
const howManySignaturesWeHave = useMemo(() => wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt), [wallet, psbt]);
|
||||
const isConfirmEnabled = useCallback(() => howManySignaturesWeHave >= wallet.getM(), [howManySignaturesWeHave, wallet]);
|
||||
|
||||
const navigateToPSBTMultisigQRCode = useCallback(() => {
|
||||
navigate('PsbtMultisigQRCode', { walletID, psbtBase64: psbt.toBase64(), isShowOpenScanner: isConfirmEnabled() });
|
||||
}, [navigate, walletID, psbt, isConfirmEnabled]);
|
||||
|
||||
const _renderItemUnsigned = useCallback(
|
||||
el => {
|
||||
const renderProvideSignature = el.index === howManySignaturesWeHave;
|
||||
return (
|
||||
<View testID="ItemUnsigned">
|
||||
<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
|
||||
accessibilityRole="button"
|
||||
testID="ProvideSignature"
|
||||
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
|
||||
onPress={navigateToPSBTMultisigQRCode}
|
||||
>
|
||||
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
|
||||
{loc.multisig.provide_signature}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
vaultKeyTextSigned: {
|
||||
color: colors.msSuccessBG,
|
||||
},
|
||||
[howManySignaturesWeHave, navigateToPSBTMultisigQRCode, stylesHook],
|
||||
);
|
||||
});
|
||||
|
||||
const _renderItemSigned = useCallback(
|
||||
el => (
|
||||
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 (
|
||||
<View testID="ItemUnsigned">
|
||||
<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
|
||||
accessibilityRole="button"
|
||||
testID="ProvideSignature"
|
||||
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
|
||||
onPress={navigateToPSBTMultisigQRCode}
|
||||
>
|
||||
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
|
||||
{loc.multisig.provide_signature}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const _renderItemSigned = el => {
|
||||
return (
|
||||
<View style={styles.flexDirectionRow} testID="ItemSigned">
|
||||
<View style={[styles.vaultKeyCircleSuccess, stylesHook.vaultKeyCircleSuccess]}>
|
||||
<Icon size={24} name="check" type="ionicons" color={colors.msSuccessCheck} />
|
||||
|
@ -124,32 +141,28 @@ const PsbtMultisig = () => {
|
|||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
),
|
||||
[colors, stylesHook],
|
||||
);
|
||||
|
||||
const _renderItem = useCallback(
|
||||
el => {
|
||||
return el.index >= howManySignaturesWeHave ? _renderItemUnsigned(el) : _renderItemSigned(el);
|
||||
},
|
||||
[howManySignaturesWeHave, _renderItemSigned, _renderItemUnsigned],
|
||||
);
|
||||
|
||||
const _combinePSBT = useCallback(() => {
|
||||
try {
|
||||
const receivedPsbt = bitcoin.Psbt.fromBase64(receivedPSBTBase64);
|
||||
const newPsbt = psbt.combine(receivedPsbt);
|
||||
setParams({ psbtBase64: newPsbt.toBase64(), receivedPSBTBase64: undefined });
|
||||
} catch (error) {
|
||||
presentAlert({ message: error.message });
|
||||
}
|
||||
}, [psbt, receivedPSBTBase64, setParams]);
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (receivedPSBTBase64) _combinePSBT();
|
||||
}, [receivedPSBTBase64, _combinePSBT]);
|
||||
if (receivedPSBTBase64) {
|
||||
_combinePSBT();
|
||||
setParams({ receivedPSBTBase64: undefined });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [receivedPSBTBase64]);
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
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
|
||||
|
@ -160,6 +173,7 @@ const PsbtMultisig = () => {
|
|||
navigate(launchedBy, { psbt });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const tx = psbt.extractTransaction().toHex();
|
||||
const satoshiPerByte = Math.round(getFee() / psbt.extractTransaction().virtualSize());
|
||||
|
@ -174,115 +188,122 @@ const PsbtMultisig = () => {
|
|||
} catch (error) {
|
||||
presentAlert({ message: error });
|
||||
}
|
||||
}, [psbt, launchedBy, navigate, getFee, memo, walletID, targets]);
|
||||
};
|
||||
|
||||
const destinationAddress = useCallback(() => {
|
||||
const howManySignaturesWeHave = wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt);
|
||||
const isConfirmEnabled = () => {
|
||||
return howManySignaturesWeHave >= wallet.getM();
|
||||
};
|
||||
|
||||
const destinationAddress = () => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let destinationAddressView = [];
|
||||
const whitespace = '_';
|
||||
const addressList = destinationStr.split(',');
|
||||
return addressList.map((addr, index) => {
|
||||
const destinations = Object.entries(destination.split(','));
|
||||
for (const [index, address] of destinations) {
|
||||
if (index > 1) {
|
||||
return (
|
||||
destinationAddressView.push(
|
||||
<View style={styles.destinationTextContainer} key={`end-${index}`}>
|
||||
<Text numberOfLines={0} style={[styles.textDestinationFirstFour, stylesHook.textFiat]}>
|
||||
and {addressList.length - 2} more...
|
||||
and {destinations.length - 2} more...
|
||||
</Text>
|
||||
</View>
|
||||
</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.destinationTextContainer} key={`${currentAddress}-${index}`}>
|
||||
<Text style={styles.textAlignCenter}>
|
||||
<Text numberOfLines={2} style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>
|
||||
{firstFour}
|
||||
<Text style={stylesHook.whitespace}>{whitespace}</Text>
|
||||
<Text style={[styles.textDestination, stylesHook.textFiat]}>{middle}</Text>
|
||||
<Text style={stylesHook.whitespace}>{whitespace}</Text>
|
||||
<Text style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>{lastFour}</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</View>,
|
||||
);
|
||||
}
|
||||
const firstFour = addr.substring(0, 5);
|
||||
const lastFour = addr.substring(addr.length - 5);
|
||||
const middle = addr.split(firstFour)[1]?.split(lastFour)[0] || '';
|
||||
return (
|
||||
<View style={styles.destinationTextContainer} key={`${addr}-${index}`}>
|
||||
<Text style={styles.textAlignCenter}>
|
||||
<Text numberOfLines={2} style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>
|
||||
{firstFour}
|
||||
<Text style={stylesHook.whitespace}>{whitespace}</Text>
|
||||
<Text style={[styles.textDestination, stylesHook.textFiat]}>{middle}</Text>
|
||||
<Text style={stylesHook.whitespace}>{whitespace}</Text>
|
||||
<Text style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>{lastFour}</Text>
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}, [destinationStr, stylesHook]);
|
||||
}
|
||||
return destinationAddressView;
|
||||
};
|
||||
|
||||
const handleLayout = useCallback(e => {
|
||||
setFlatListHeight(e.nativeEvent.layout.height);
|
||||
}, []);
|
||||
|
||||
const header = useMemo(
|
||||
() => (
|
||||
<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>
|
||||
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 style={styles.containerText}>
|
||||
<BlueText style={[styles.textFiat, stylesHook.textFiat]}>{totalFiat}</BlueText>
|
||||
</View>
|
||||
<View>{destinationAddress()}</View>
|
||||
</View>
|
||||
),
|
||||
[totalBtc, totalFiat, destinationAddress, stylesHook],
|
||||
<View style={styles.containerText}>
|
||||
<BlueText style={[styles.textFiat, stylesHook.textFiat]}>{totalFiat}</BlueText>
|
||||
</View>
|
||||
<View>{destinationAddress()}</View>
|
||||
</View>
|
||||
);
|
||||
const footer = null;
|
||||
|
||||
const footer = useMemo(
|
||||
() => (
|
||||
<>
|
||||
<View style={styles.bottomWrapper}>
|
||||
<View style={styles.bottomFeesWrapper}>
|
||||
<BlueText style={[styles.feeFiatText, stylesHook.feeFiatText]}>
|
||||
{loc.formatString(loc.multisig.fee, { number: satoshiToLocalCurrency(getFee()) })} -{' '}
|
||||
</BlueText>
|
||||
|
||||
<BlueText style={styles.feeFiatText} />
|
||||
<BlueText>{loc.formatString(loc.multisig.fee_btc, { number: satoshiToBTC(getFee()) })}</BlueText>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.marginConfirmButton}>
|
||||
<Button disabled={!isConfirmEnabled()} title={loc.multisig.confirm} onPress={onConfirm} testID="PsbtMultisigConfirmButton" />
|
||||
</View>
|
||||
</>
|
||||
),
|
||||
[getFee, isConfirmEnabled, onConfirm, stylesHook],
|
||||
);
|
||||
const onLayout = event => {
|
||||
setFlatListHeight(event.nativeEvent.layout.height);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeArea style={stylesHook.root}>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.mstopcontainer}>
|
||||
<View style={styles.mscontainer}>
|
||||
<View style={[styles.msleft, { height: flatListHeight - 260 }]} />
|
||||
<View style={styles.flexColumnSpaceBetween}>
|
||||
<View style={styles.flexOne}>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.mstopcontainer}>
|
||||
<View style={styles.mscontainer}>
|
||||
<View style={[styles.msleft, { height: flatListHeight - 260 }]} />
|
||||
</View>
|
||||
<View style={styles.msright}>
|
||||
<BlueCard>
|
||||
<FlatList
|
||||
data={data}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(_item, index) => `${index}`}
|
||||
ListHeaderComponent={header}
|
||||
ListFooterComponent={footer}
|
||||
onLayout={onLayout}
|
||||
/>
|
||||
{isConfirmEnabled() && (
|
||||
<View style={styles.height80}>
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
testID="ExportSignedPsbt"
|
||||
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
|
||||
onPress={navigateToPSBTMultisigQRCode}
|
||||
>
|
||||
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
|
||||
{loc.multisig.export_signed_psbt}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</BlueCard>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.msright}>
|
||||
<BlueCard>
|
||||
<FlatList
|
||||
data={new Array(wallet.getM())}
|
||||
onLayout={handleLayout}
|
||||
renderItem={_renderItem}
|
||||
keyExtractor={(_item, index) => `${index}`}
|
||||
ListHeaderComponent={header}
|
||||
ListFooterComponent={footer}
|
||||
/>
|
||||
{isConfirmEnabled() && (
|
||||
<View style={styles.height80}>
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
testID="ExportSignedPsbt"
|
||||
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
|
||||
onPress={navigateToPSBTMultisigQRCode}
|
||||
>
|
||||
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
|
||||
{loc.multisig.export_signed_psbt}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</BlueCard>
|
||||
</View>
|
||||
<View style={styles.feeConfirmContainer}>
|
||||
<View style={styles.feeContainer}>
|
||||
<View style={styles.bottomWrapper}>
|
||||
<View style={styles.bottomFeesWrapper}>
|
||||
<BlueText style={[styles.feeFiatText, stylesHook.feeFiatText]}>
|
||||
{loc.formatString(loc.multisig.fee, { number: satoshiToLocalCurrency(getFee()) })} -{' '}
|
||||
</BlueText>
|
||||
<BlueText>{loc.formatString(loc.multisig.fee_btc, { number: satoshiToBTC(getFee()) })}</BlueText>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.flexConfirm}>
|
||||
<Button disabled={!isConfirmEnabled()} title={loc.multisig.confirm} onPress={onConfirm} testID="PsbtMultisigConfirmButton" />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
@ -291,18 +312,64 @@ const PsbtMultisig = () => {
|
|||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
mstopcontainer: { flex: 1, flexDirection: 'row' },
|
||||
mscontainer: { flex: 10 },
|
||||
msleft: { width: 1, borderStyle: 'dashed', borderWidth: 0.8, borderColor: '#c4c4c4', marginLeft: 40, marginTop: 130 },
|
||||
msright: { flex: 90, marginLeft: '-11%' },
|
||||
container: { flexDirection: 'column', paddingTop: 24, flex: 1 },
|
||||
containerText: { flexDirection: 'row', justifyContent: 'center' },
|
||||
destinationTextContainer: { flexDirection: 'row', marginBottom: 4, paddingHorizontal: 60, fontSize: 14, justifyContent: 'center' },
|
||||
textFiat: { fontSize: 16, fontWeight: '500', marginBottom: 30 },
|
||||
textBtc: { fontWeight: 'bold', fontSize: 30 },
|
||||
textAlignCenter: { textAlign: 'center' },
|
||||
textDestinationFirstFour: { fontSize: 14 },
|
||||
textDestination: { paddingTop: 10, paddingBottom: 40, fontSize: 14, flexWrap: 'wrap' },
|
||||
mstopcontainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
mscontainer: {
|
||||
flex: 10,
|
||||
},
|
||||
flexOne: {
|
||||
flex: 1,
|
||||
},
|
||||
msleft: {
|
||||
width: 1,
|
||||
borderStyle: 'dashed',
|
||||
borderWidth: 0.8,
|
||||
borderColor: '#c4c4c4',
|
||||
marginLeft: 40,
|
||||
marginTop: 160,
|
||||
},
|
||||
msright: {
|
||||
flex: 90,
|
||||
marginLeft: '-11%',
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
},
|
||||
containerText: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
destinationTextContainer: {
|
||||
flexDirection: 'row',
|
||||
marginBottom: 4,
|
||||
paddingHorizontal: 60,
|
||||
fontSize: 14,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
textFiat: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
marginBottom: 30,
|
||||
},
|
||||
textBtc: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: 30,
|
||||
},
|
||||
textAlignCenter: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
textDestinationFirstFour: {
|
||||
fontSize: 14,
|
||||
},
|
||||
textDestination: {
|
||||
paddingTop: 10,
|
||||
paddingBottom: 40,
|
||||
fontSize: 14,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
provideSignatureButton: {
|
||||
marginTop: 24,
|
||||
marginLeft: 40,
|
||||
|
@ -316,8 +383,20 @@ const styles = StyleSheet.create({
|
|||
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' },
|
||||
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 },
|
||||
vaultKeyTextSigned: { fontSize: 18, fontWeight: 'bold' },
|
||||
vaultKeyTextSignedWrapper: { justifyContent: 'center', alignItems: 'center', paddingLeft: 16 },
|
||||
|
@ -325,8 +404,25 @@ const styles = StyleSheet.create({
|
|||
textBtcUnit: { justifyContent: 'flex-end' },
|
||||
bottomFeesWrapper: { justifyContent: 'center', alignItems: 'center', flexDirection: 'row' },
|
||||
bottomWrapper: { marginTop: 16 },
|
||||
marginConfirmButton: { marginTop: 16, marginHorizontal: 32, marginBottom: 48 },
|
||||
height80: { height: 80 },
|
||||
height80: {
|
||||
height: 80,
|
||||
},
|
||||
flexColumnSpaceBetween: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
flexConfirm: {
|
||||
paddingHorizontal: 32,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
feeConfirmContainer: {
|
||||
paddingHorizontal: 32,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
feeContainer: {
|
||||
marginBottom: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default PsbtMultisig;
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as bitcoin from 'bitcoinjs-lib';
|
|||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ActivityIndicator, ScrollView, StyleSheet, View } from 'react-native';
|
||||
|
||||
import { BlueSpacing20 } from '../../BlueComponents';
|
||||
import { BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { DynamicQRCode } from '../../components/DynamicQRCode';
|
||||
import SafeArea from '../../components/SafeArea';
|
||||
|
@ -33,6 +33,9 @@ const PsbtMultisigQRCode = () => {
|
|||
exportButton: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
tipBox: {
|
||||
backgroundColor: colors.ballOutgoingExpired,
|
||||
},
|
||||
});
|
||||
const fileName = `${Date.now()}.psbt`;
|
||||
|
||||
|
@ -89,6 +92,13 @@ const PsbtMultisigQRCode = () => {
|
|||
<SafeArea style={stylesHook.root}>
|
||||
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent}>
|
||||
<View style={[styles.modalContentShort, stylesHook.modalContentShort]}>
|
||||
<View style={[styles.tipBox, stylesHook.tipBox]}>
|
||||
<BlueText bold>{loc.multisig.provide_signature}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<BlueText>{loc.multisig.provide_signature_details}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<BlueText>{loc.multisig.provide_signature_details_bluewallet}</BlueText>
|
||||
</View>
|
||||
<DynamicQRCode value={psbt.toHex()} ref={dynamicQRCode} />
|
||||
{!isShowOpenScanner && (
|
||||
<>
|
||||
|
@ -138,6 +148,11 @@ const styles = StyleSheet.create({
|
|||
justifyContent: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
tipBox: {
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
marginBottom: 24,
|
||||
},
|
||||
});
|
||||
|
||||
export default PsbtMultisigQRCode;
|
||||
|
|
Loading…
Add table
Reference in a new issue