This commit is contained in:
Marcos Rodriguez Velez 2025-02-07 13:16:41 -04:00
parent a8858833ef
commit 4849042dc6
7 changed files with 345 additions and 221 deletions

View file

@ -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} />;
};

View file

@ -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}",

View file

@ -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;

View file

@ -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,
],
);

View file

@ -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;

View file

@ -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;

View file

@ -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;