|
9 | 9 | #include <qml/models/paymentrequest.h> |
10 | 10 | #include <qml/models/sendrecipient.h> |
11 | 11 | #include <qml/models/sendrecipientslistmodel.h> |
| 12 | +#include <qml/models/transactionrbfadapter.h> |
12 | 13 | #include <qml/models/walletqmlmodeltransaction.h> |
13 | 14 |
|
14 | 15 | #include <consensus/amount.h> |
15 | 16 | #include <interfaces/wallet.h> |
16 | 17 | #include <key_io.h> |
17 | 18 | #include <addresstype.h> |
18 | 19 | #include <outputtype.h> |
| 20 | +#include <policy/feerate.h> |
19 | 21 | #include <qml/bitcoinunits.h> |
20 | 22 | #include <support/allocators/secure.h> |
21 | 23 | #include <wallet/coincontrol.h> |
|
29 | 31 |
|
30 | 32 | #include <limits> |
31 | 33 |
|
| 34 | +namespace { |
| 35 | +class WalletTransactionRbfBackend final : public TransactionRbfBackend |
| 36 | +{ |
| 37 | +public: |
| 38 | + explicit WalletTransactionRbfBackend(interfaces::Wallet& wallet) |
| 39 | + : m_wallet(wallet) |
| 40 | + { |
| 41 | + } |
| 42 | + |
| 43 | + bool transactionCanBeBumped(const Txid& txid) const override |
| 44 | + { |
| 45 | + return m_wallet.transactionCanBeBumped(txid); |
| 46 | + } |
| 47 | + |
| 48 | + bool createBumpTransaction(const Txid& txid, |
| 49 | + const unsigned int target_blocks, |
| 50 | + const std::optional<CAmount> custom_fee_sat_per_kvb, |
| 51 | + std::vector<bilingual_str>& errors, |
| 52 | + CAmount& old_fee, |
| 53 | + CAmount& new_fee, |
| 54 | + CMutableTransaction& mtx) override |
| 55 | + { |
| 56 | + wallet::CCoinControl coin_control; |
| 57 | + coin_control.m_confirm_target = target_blocks; |
| 58 | + if (custom_fee_sat_per_kvb.has_value()) { |
| 59 | + coin_control.m_feerate = CFeeRate(*custom_fee_sat_per_kvb); |
| 60 | + } |
| 61 | + |
| 62 | + return m_wallet.createBumpTransaction(txid, |
| 63 | + coin_control, |
| 64 | + errors, |
| 65 | + old_fee, |
| 66 | + new_fee, |
| 67 | + mtx); |
| 68 | + } |
| 69 | + |
| 70 | + bool signBumpTransaction(CMutableTransaction& mtx) override |
| 71 | + { |
| 72 | + return m_wallet.signBumpTransaction(mtx); |
| 73 | + } |
| 74 | + |
| 75 | + bool commitBumpTransaction(const Txid& txid, |
| 76 | + CMutableTransaction&& mtx, |
| 77 | + std::vector<bilingual_str>& errors, |
| 78 | + Txid& bumped_txid) override |
| 79 | + { |
| 80 | + return m_wallet.commitBumpTransaction(txid, |
| 81 | + std::move(mtx), |
| 82 | + errors, |
| 83 | + bumped_txid); |
| 84 | + } |
| 85 | + |
| 86 | + bool transactionCanBeAbandoned(const Txid& txid) const override |
| 87 | + { |
| 88 | + return m_wallet.transactionCanBeAbandoned(txid); |
| 89 | + } |
| 90 | + |
| 91 | + bool abandonTransaction(const Txid& txid) override |
| 92 | + { |
| 93 | + return m_wallet.abandonTransaction(txid); |
| 94 | + } |
| 95 | + |
| 96 | +private: |
| 97 | + interfaces::Wallet& m_wallet; |
| 98 | +}; |
| 99 | + |
| 100 | +QVariantMap BuildRbfResultMap(const TransactionRbfActionResult& result) |
| 101 | +{ |
| 102 | + QVariantMap payload; |
| 103 | + payload[QStringLiteral("success")] = result.success; |
| 104 | + payload[QStringLiteral("message")] = result.message; |
| 105 | + payload[QStringLiteral("replacementTxid")] = result.replacement_txid; |
| 106 | + return payload; |
| 107 | +} |
| 108 | +} // namespace |
| 109 | + |
32 | 110 | WalletQmlModel::WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObject *parent) |
33 | 111 | : QObject(parent) |
34 | 112 | { |
@@ -537,6 +615,120 @@ void WalletQmlModel::setCustomFeeRateSatPerKvB(const qint64 custom_fee_rate_sat_ |
537 | 615 | Q_EMIT customFeeRateSatPerKvBChanged(); |
538 | 616 | } |
539 | 617 |
|
| 618 | +bool WalletQmlModel::canBumpTransaction(const QString& txid_text) const |
| 619 | +{ |
| 620 | + if (!m_wallet) { |
| 621 | + return false; |
| 622 | + } |
| 623 | + |
| 624 | + WalletTransactionRbfBackend backend(*m_wallet); |
| 625 | + return TransactionRbfAdapter::CanBump(backend, txid_text); |
| 626 | +} |
| 627 | + |
| 628 | +bool WalletQmlModel::canAbandonTransaction(const QString& txid_text) const |
| 629 | +{ |
| 630 | + if (!m_wallet) { |
| 631 | + return false; |
| 632 | + } |
| 633 | + |
| 634 | + WalletTransactionRbfBackend backend(*m_wallet); |
| 635 | + return TransactionRbfAdapter::CanAbandon(backend, txid_text); |
| 636 | +} |
| 637 | + |
| 638 | +QString WalletQmlModel::bumpIneligibleReason() const |
| 639 | +{ |
| 640 | + return TransactionRbfAdapter::BumpIneligibleReason(); |
| 641 | +} |
| 642 | + |
| 643 | +QString WalletQmlModel::abandonIneligibleReason() const |
| 644 | +{ |
| 645 | + return TransactionRbfAdapter::AbandonIneligibleReason(); |
| 646 | +} |
| 647 | + |
| 648 | +QVariantMap WalletQmlModel::prepareBumpTransaction(const QString& txid_text, |
| 649 | + const unsigned int target_blocks, |
| 650 | + const bool custom_fee_enabled, |
| 651 | + const qint64 custom_fee_rate_sat_per_kvb) |
| 652 | +{ |
| 653 | + QVariantMap payload; |
| 654 | + if (!m_wallet) { |
| 655 | + payload[QStringLiteral("success")] = false; |
| 656 | + payload[QStringLiteral("message")] = tr("No wallet is loaded."); |
| 657 | + return payload; |
| 658 | + } |
| 659 | + |
| 660 | + WalletTransactionRbfBackend backend(*m_wallet); |
| 661 | + const TransactionRbfPreview preview = TransactionRbfAdapter::PrepareBump(backend, |
| 662 | + txid_text, |
| 663 | + target_blocks, |
| 664 | + custom_fee_enabled, |
| 665 | + custom_fee_rate_sat_per_kvb); |
| 666 | + |
| 667 | + payload[QStringLiteral("success")] = preview.success; |
| 668 | + payload[QStringLiteral("message")] = preview.message; |
| 669 | + payload[QStringLiteral("oldFeeDisplay")] = QmlBitcoinUnits::formatWithSettingsUnit(preview.old_fee); |
| 670 | + payload[QStringLiteral("newFeeDisplay")] = QmlBitcoinUnits::formatWithSettingsUnit(preview.new_fee); |
| 671 | + |
| 672 | + if (preview.success) { |
| 673 | + m_prepared_bump_transaction = preview.replacement; |
| 674 | + m_prepared_bump_txid = txid_text.trimmed(); |
| 675 | + } else { |
| 676 | + clearPreparedBump(); |
| 677 | + } |
| 678 | + |
| 679 | + return payload; |
| 680 | +} |
| 681 | + |
| 682 | +QVariantMap WalletQmlModel::commitPreparedBumpTransaction(const QString& txid_text) |
| 683 | +{ |
| 684 | + if (!m_wallet) { |
| 685 | + QVariantMap payload; |
| 686 | + payload[QStringLiteral("success")] = false; |
| 687 | + payload[QStringLiteral("message")] = tr("No wallet is loaded."); |
| 688 | + return payload; |
| 689 | + } |
| 690 | + |
| 691 | + if (!m_prepared_bump_transaction.has_value()) { |
| 692 | + QVariantMap payload; |
| 693 | + payload[QStringLiteral("success")] = false; |
| 694 | + payload[QStringLiteral("message")] = tr("Prepare a replacement transaction first."); |
| 695 | + return payload; |
| 696 | + } |
| 697 | + |
| 698 | + if (!m_prepared_bump_txid.isEmpty() && m_prepared_bump_txid != txid_text.trimmed()) { |
| 699 | + QVariantMap payload; |
| 700 | + payload[QStringLiteral("success")] = false; |
| 701 | + payload[QStringLiteral("message")] = tr("Prepared replacement does not match the selected transaction."); |
| 702 | + return payload; |
| 703 | + } |
| 704 | + |
| 705 | + WalletTransactionRbfBackend backend(*m_wallet); |
| 706 | + const TransactionRbfActionResult result = TransactionRbfAdapter::CommitBump(backend, |
| 707 | + txid_text, |
| 708 | + std::move(*m_prepared_bump_transaction)); |
| 709 | + clearPreparedBump(); |
| 710 | + return BuildRbfResultMap(result); |
| 711 | +} |
| 712 | + |
| 713 | +QVariantMap WalletQmlModel::abandonTransactionWithResult(const QString& txid_text) |
| 714 | +{ |
| 715 | + if (!m_wallet) { |
| 716 | + QVariantMap payload; |
| 717 | + payload[QStringLiteral("success")] = false; |
| 718 | + payload[QStringLiteral("message")] = tr("No wallet is loaded."); |
| 719 | + return payload; |
| 720 | + } |
| 721 | + |
| 722 | + WalletTransactionRbfBackend backend(*m_wallet); |
| 723 | + return BuildRbfResultMap(TransactionRbfAdapter::Abandon(backend, txid_text)); |
| 724 | +} |
| 725 | + |
| 726 | +void WalletQmlModel::clearPreparedBump() |
| 727 | +{ |
| 728 | + m_prepared_bump_transaction.reset(); |
| 729 | + m_prepared_bump_txid.clear(); |
| 730 | +} |
| 731 | + |
540 | 732 | void WalletQmlModel::setLastPrepareError(const QString& error_message) |
541 | 733 | { |
542 | 734 | if (m_last_prepare_error == error_message) { |
|
0 commit comments