Merge pull request #2810 from BlueWallet/limpbrains-bach-max-all-wallets

ADD: allow send MAX and BATCH for all wallet types
This commit is contained in:
GLaDOS 2021-03-30 11:06:09 +01:00 committed by GitHub
commit 3fab5ad390
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 70 additions and 204 deletions

View file

@ -101,18 +101,10 @@ export class AbstractWallet {
return true;
}
allowSendMax(): boolean {
return false;
}
allowRBF() {
return false;
}
allowBatchSend() {
return false;
}
allowHodlHodlTrading() {
return false;
}

View file

@ -143,14 +143,6 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet {
return true;
}
allowBatchSend() {
return true;
}
allowSendMax() {
return true;
}
allowHodlHodlTrading() {
return true;
}

View file

@ -18,10 +18,6 @@ export class HDLegacyBreadwalletWallet extends HDLegacyP2PKHWallet {
_external_segwit_index = null; // eslint-disable-line camelcase
_internal_segwit_index = null; // eslint-disable-line camelcase
allowSendMax() {
return true;
}
/**
* @see https://github.com/bitcoinjs/bitcoinjs-lib/issues/584
* @see https://github.com/bitcoinjs/bitcoinjs-lib/issues/914

View file

@ -70,10 +70,6 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
return child.toWIF();
}
allowSendMax() {
return true;
}
_getNodePubkeyByIndex(node, index) {
index = index * 1; // cast to int

View file

@ -18,10 +18,6 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
return true;
}
allowSendMax() {
return true;
}
allowCosignPsbt() {
return true;
}

View file

@ -15,14 +15,6 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
return true;
}
allowBatchSend() {
return true;
}
allowSendMax() {
return true;
}
allowHodlHodlTrading() {
return true;
}

View file

@ -70,10 +70,6 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
return child.toWIF();
}
allowSendMax() {
return true;
}
_getNodePubkeyByIndex(node, index) {
index = index * 1; // cast to int

View file

@ -19,10 +19,6 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
return true;
}
allowSendMax(): boolean {
return true;
}
allowCosignPsbt() {
return true;
}

View file

@ -476,10 +476,6 @@ export class LegacyWallet extends AbstractWallet {
return false;
}
allowSendMax() {
return true;
}
allowSignVerifyMessage() {
return true;
}

View file

@ -1092,10 +1092,6 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
return /^[0-9A-F]{8}$/i.test(fp);
}
allowBatchSend() {
return true;
}
/**
* Returns TRUE only for _multisignature_ xpubs as per SLIP-0132
* (capital Z, capital Y, or just xpub)

View file

@ -130,10 +130,6 @@ export class SegwitBech32Wallet extends LegacyWallet {
return true;
}
allowSendMax() {
return true;
}
allowSignVerifyMessage() {
return true;
}

View file

@ -139,10 +139,6 @@ export class SegwitP2SHWallet extends LegacyWallet {
return { tx, inputs, outputs, fee, psbt };
}
allowSendMax() {
return true;
}
allowSignVerifyMessage() {
return true;
}

View file

@ -21,20 +21,6 @@ export class WatchOnlyWallet extends LegacyWallet {
);
}
allowBatchSend() {
return (
this.useWithHardwareWalletEnabled() &&
this._hdWalletInstance instanceof HDSegwitBech32Wallet &&
this._hdWalletInstance.allowBatchSend()
);
}
allowSendMax() {
return (
this.useWithHardwareWalletEnabled() && this._hdWalletInstance instanceof HDSegwitBech32Wallet && this._hdWalletInstance.allowSendMax()
);
}
allowSignVerifyMessage() {
return false;
}

View file

@ -183,15 +183,12 @@
"details_error_decode": "Unable to decode Bitcoin address",
"details_fee_field_is_not_valid": "The fee is not valid.",
"details_next": "Next",
"details_no_maximum": "The selected wallet doesnt support automatic maximum balance calculation. Are you sure to want to select this wallet?",
"details_no_multiple": "The selected wallet doesnt support sending bitcoin to multiple recipients. Are you sure to want to select this wallet?",
"details_no_signed_tx": "The selected file doesnt contain a transaction that can be imported.",
"details_note_placeholder": "Note to Self",
"details_scan": "Scan",
"details_total_exceeds_balance": "The sending amount exceeds the available balance.",
"details_unrecognized_file_format": "Unrecognized file format",
"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",

View file

@ -186,7 +186,6 @@ const SendDetails = () => {
if (!wallet) return;
setSelectedWallet(wallet.getID());
navigation.setParams({
withAdvancedOptionsMenuButton: wallet.allowBatchSend() || wallet.allowSendMax(),
advancedOptionsMenuButtonAction: () => {
Keyboard.dismiss();
setOptionsVisible(true);
@ -404,7 +403,7 @@ const SendDetails = () => {
} else if (!transaction.address) {
error = loc.send.details_address_field_is_not_valid;
console.log('validation error');
} else if (wallet.getBalance() - transaction.amountSats < 0) {
} else if (balance - transaction.amountSats < 0) {
// first sanity check is that sending amount is not bigger than available balance
error = loc.send.details_total_exceeds_balance;
console.log('validation error');
@ -521,67 +520,8 @@ const SendDetails = () => {
};
const onWalletSelect = wallet => {
const changeWallet = () => {
setWallet(wallet);
navigation.pop();
};
if (addresses.length > 1 && !wallet.allowBatchSend()) {
ReactNativeHapticFeedback.trigger('notificationWarning');
Alert.alert(
loc.send.details_wallet_selection,
loc.send.details_no_multiple,
[
{
text: loc._.ok,
onPress: () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setAddresses(addresses => {
const firstTransaction =
addresses.find(element => {
const feeSatoshi = new BigNumber(element.amount).multipliedBy(100000000);
return element.address.length > 0 && feeSatoshi > 0;
}) || addresses[0];
return [firstTransaction];
});
changeWallet();
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
return;
}
if (addresses.some(element => element.amount === BitcoinUnit.MAX) && !wallet.allowSendMax()) {
ReactNativeHapticFeedback.trigger('notificationWarning');
Alert.alert(
loc.send.details_wallet_selection,
loc.send.details_no_maximum,
[
{
text: loc._.ok,
onPress: () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setAddresses(addresses => {
const firstTransaction = addresses.find(element => element.amount === BitcoinUnit.MAX) || addresses[0];
firstTransaction.amount = 0;
return [firstTransaction];
});
changeWallet();
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
return;
}
changeWallet();
setWallet(wallet);
navigation.pop();
};
/**
@ -1049,20 +989,20 @@ const SendDetails = () => {
};
const renderOptionsModal = () => {
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
return (
<BottomModal deviceWidth={width + width / 2} isVisible={optionsVisible} onClose={hideOptions}>
<KeyboardAvoidingView enabled={!Platform.isPad} behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={[styles.optionsContent, stylesHook.optionsContent]}>
{wallet.allowSendMax() && (
<BlueListItem
testID="sendMaxButton"
disabled={!(wallet.getBalance() > 0) || isSendMaxUsed}
title={loc.send.details_adv_full}
hideChevron
component={TouchableOpacity}
onPress={onUseAllPressed}
/>
)}
<BlueListItem
testID="sendMaxButton"
disabled={balance === 0 || isSendMaxUsed}
title={loc.send.details_adv_full}
hideChevron
component={TouchableOpacity}
onPress={onUseAllPressed}
/>
{wallet.type === HDSegwitBech32Wallet.type && (
<BlueListItem
title={loc.send.details_adv_fee_bump}
@ -1098,25 +1038,21 @@ const SendDetails = () => {
onPress={importTransactionMultisigScanQr}
/>
)}
{wallet.allowBatchSend() && (
<>
<BlueListItem
testID="AddRecipient"
title={loc.send.details_add_rec_add}
hideChevron
component={TouchableOpacity}
onPress={handleAddRecipient}
/>
<BlueListItem
testID="RemoveRecipient"
title={loc.send.details_add_rec_rem}
hideChevron
disabled={addresses.length < 2}
component={TouchableOpacity}
onPress={handleRemoveRecipient}
/>
</>
)}
<BlueListItem
testID="AddRecipient"
title={loc.send.details_add_rec_add}
hideChevron
component={TouchableOpacity}
onPress={handleAddRecipient}
/>
<BlueListItem
testID="RemoveRecipient"
title={loc.send.details_add_rec_rem}
hideChevron
disabled={addresses.length < 2}
component={TouchableOpacity}
onPress={handleRemoveRecipient}
/>
<BlueListItem testID="CoinControl" title={loc.cc.header} hideChevron component={TouchableOpacity} onPress={handleCoinControl} />
{wallet.allowCosignPsbt() && (
<BlueListItem
@ -1238,7 +1174,7 @@ const SendDetails = () => {
});
}}
unit={units[index] || amountUnit}
inputAccessoryViewID={wallet.allowSendMax() ? BlueUseAllFundsButton.InputAccessoryViewID : null}
inputAccessoryViewID={BlueUseAllFundsButton.InputAccessoryViewID}
/>
<AddressInput
onChangeText={text => {
@ -1280,7 +1216,6 @@ const SendDetails = () => {
// if utxo is limited we use it to calculate available balance
const balance = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : wallet.getBalance();
const allBalance = formatBalanceWithoutSuffix(balance, BitcoinUnit.BTC, true);
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
@ -1342,19 +1277,9 @@ const SendDetails = () => {
</View>
<BlueDismissKeyboardInputAccessory />
{Platform.select({
ios: (
<BlueUseAllFundsButton
canUseAll={wallet.allowSendMax() && allBalance > 0 && !isSendMaxUsed}
onUseAllPressed={onUseAllPressed}
balance={allBalance}
/>
),
ios: <BlueUseAllFundsButton canUseAll={balance > 0} onUseAllPressed={onUseAllPressed} balance={allBalance} />,
android: isAmountToolbarVisibleForAndroid && (
<BlueUseAllFundsButton
canUseAll={wallet.allowSendMax() && allBalance > 0 && !isSendMaxUsed}
onUseAllPressed={onUseAllPressed}
balance={allBalance}
/>
<BlueUseAllFundsButton canUseAll={balance > 0} onUseAllPressed={onUseAllPressed} balance={allBalance} />
),
})}
@ -1502,24 +1427,16 @@ const styles = StyleSheet.create({
},
});
SendDetails.navigationOptions = navigationStyleTx({}, (options, { theme, navigation, route }) => {
let headerRight;
if (route.params.withAdvancedOptionsMenuButton) {
headerRight = () => (
<TouchableOpacity
style={styles.advancedOptions}
onPress={route.params.advancedOptionsMenuButtonAction}
testID="advancedOptionsMenuButton"
>
<Icon size={22} name="kebab-horizontal" type="octicon" color={theme.colors.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
...options,
headerRight,
title: loc.send.header,
};
});
SendDetails.navigationOptions = navigationStyleTx({}, (options, { theme, navigation, route }) => ({
...options,
headerRight: () => (
<TouchableOpacity
style={styles.advancedOptions}
onPress={route.params.advancedOptionsMenuButtonAction}
testID="advancedOptionsMenuButton"
>
<Icon size={22} name="kebab-horizontal" type="octicon" color={theme.colors.foregroundColor} />
</TouchableOpacity>
),
title: loc.send.header,
}));

View file

@ -38,6 +38,19 @@ describe('Legacy wallet', () => {
assert.strictEqual(tx.ins.length, 1);
assert.strictEqual(tx.outs.length, 1);
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
// batch send + send max
txNew = l.createTransaction(
utxos,
[{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }, { address: 'bc1q3rl0mkyk0zrtxfmqn9wpcd3gnaz00yv9yp0hxe', value: 10000 }],
1,
l.getAddress(),
);
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex());
assert.strictEqual(tx.ins.length, 1);
assert.strictEqual(tx.outs.length, 2);
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
assert.strictEqual('bc1q3rl0mkyk0zrtxfmqn9wpcd3gnaz00yv9yp0hxe', bitcoin.address.fromOutputScript(tx.outs[1].script)); // to address
});
it("throws error if you can't create wallet from this entropy", async () => {

View file

@ -35,6 +35,19 @@ describe('Segwit P2SH wallet', () => {
assert.strictEqual(tx.ins.length, 1);
assert.strictEqual(tx.outs.length, 1);
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
// batch send + send max
txNew = wallet.createTransaction(
utxos,
[{ address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }, { address: '14YZ6iymQtBVQJk6gKnLCk49UScJK7SH4M', value: 10000 }],
1,
wallet.getAddress(),
);
tx = bitcoin.Transaction.fromHex(txNew.tx.toHex());
assert.strictEqual(tx.ins.length, 1);
assert.strictEqual(tx.outs.length, 2);
assert.strictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
assert.strictEqual('14YZ6iymQtBVQJk6gKnLCk49UScJK7SH4M', bitcoin.address.fromOutputScript(tx.outs[1].script)); // to address
});
it('can sign and verify messages', async () => {