mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 23:08:07 +01:00
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:
commit
3fab5ad390
17 changed files with 70 additions and 204 deletions
|
@ -101,18 +101,10 @@ export class AbstractWallet {
|
|||
return true;
|
||||
}
|
||||
|
||||
allowSendMax(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
allowRBF() {
|
||||
return false;
|
||||
}
|
||||
|
||||
allowBatchSend() {
|
||||
return false;
|
||||
}
|
||||
|
||||
allowHodlHodlTrading() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -143,14 +143,6 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet {
|
|||
return true;
|
||||
}
|
||||
|
||||
allowBatchSend() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowHodlHodlTrading() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -70,10 +70,6 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
|
|||
return child.toWIF();
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
_getNodePubkeyByIndex(node, index) {
|
||||
index = index * 1; // cast to int
|
||||
|
||||
|
|
|
@ -18,10 +18,6 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet {
|
|||
return true;
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowCosignPsbt() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -15,14 +15,6 @@ export class HDSegwitBech32Wallet extends AbstractHDElectrumWallet {
|
|||
return true;
|
||||
}
|
||||
|
||||
allowBatchSend() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowHodlHodlTrading() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -70,10 +70,6 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
|
|||
return child.toWIF();
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
_getNodePubkeyByIndex(node, index) {
|
||||
index = index * 1; // cast to int
|
||||
|
||||
|
|
|
@ -19,10 +19,6 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
|
|||
return true;
|
||||
}
|
||||
|
||||
allowSendMax(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowCosignPsbt() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -476,10 +476,6 @@ export class LegacyWallet extends AbstractWallet {
|
|||
return false;
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowSignVerifyMessage() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -130,10 +130,6 @@ export class SegwitBech32Wallet extends LegacyWallet {
|
|||
return true;
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowSignVerifyMessage() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -139,10 +139,6 @@ export class SegwitP2SHWallet extends LegacyWallet {
|
|||
return { tx, inputs, outputs, fee, psbt };
|
||||
}
|
||||
|
||||
allowSendMax() {
|
||||
return true;
|
||||
}
|
||||
|
||||
allowSignVerifyMessage() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 doesn’t support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet doesn’t support sending bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file doesn’t 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",
|
||||
|
|
|
@ -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,
|
||||
}));
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue