wallet, rpc: Add listrawtransactions RPC #35813

pull pablomartin4btc wants to merge 5 commits into bitcoin:master from pablomartin4btc:wallet/listrawtransactions changing 6 files +334 −27
  1. pablomartin4btc commented at 5:08 PM on July 26, 2026: member

    Add a new listrawtransactions RPC that returns wallet transactions without logical interpretation. Unlike listtransactions, which only lists transactions with an economic category (sends to external addresses, receives from outside), this RPC returns every transaction the wallet knows about — including consolidations and self-transfers that would otherwise be invisible.

    Each transaction appears exactly once with its net wallet balance change (amount) and, when the wallet funded the transaction, the fee paid (fee). Pagination is supported via count and skip parameters, matching the interface of listtransactions.

    Next possible follow-ups:

    • A filter parameter ("all" / "received" / "sent" / "self") once per-tx semantics are agreed on
    • Some fields inherited from TransactionDescriptionString() carry references to category (e.g. replaced_by_txid, parent_descs) which don't apply here — could be cleaned up in a separate pass

    Closes #34632.

    Previous attempt: #35009 (by alfonsoromanz), closed in favour of a separate RPC as suggested by achow101.

  2. DrahtBot added the label Wallet on Jul 26, 2026
  3. DrahtBot commented at 5:08 PM on July 26, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35813.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK polespinasa
    Stale ACK achow101, Bicaru20, BrandonOdiwuor, w0xlt

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35513 (rpc: help metadata fixes by RuslanProgrammer)
    • #35511 (RFC: consensus: Make CAmount a class by hodlinator)
    • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • self.nodes[0].listrawtransactions(10, 0, True) in test/functional/wallet_listrawtransactions.py

    <sup>2026-08-28 14:05:39</sup>

  4. pablomartin4btc referenced this in commit 2ca8449550 on Jul 26, 2026
  5. DrahtBot added the label CI failed on Jul 26, 2026
  6. DrahtBot commented at 5:12 PM on July 26, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task lint: https://github.com/bitcoin/bitcoin/actions/runs/30211957111/job/89819583780</sub> <sub>LLM reason (✨ experimental): CI failed because the lint test lint-files.py reported an executable-permission mismatch: test/functional/wallet_listrawtransactions.py has shebang but mode 644 instead of required 755.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  7. pablomartin4btc referenced this in commit 1555198853 on Jul 26, 2026
  8. pablomartin4btc force-pushed on Jul 26, 2026
  9. pablomartin4btc referenced this in commit f592d25fc7 on Jul 26, 2026
  10. pablomartin4btc force-pushed on Jul 26, 2026
  11. DrahtBot removed the label CI failed on Jul 26, 2026
  12. pablomartin4btc renamed this:
    wallet: Add listrawtransactions RPC
    wallet, rpc: Add listrawtransactions RPC
    on Jul 26, 2026
  13. pablomartin4btc commented at 11:38 PM on July 26, 2026: member

    -<ins>Updates</ins>:

    • Added release notes.
  14. w0xlt commented at 9:21 AM on July 27, 2026: contributor

    Concept ACK.

  15. in src/wallet/rpc/transactions.cpp:391 in f592d25fc7 outdated
     386 | + * @param  wtx            The wallet transaction.
     387 | + * @return                A JSON object with the net wallet balance change and
     388 | + *                        transaction metadata, without logical interpretation
     389 | + *                        (no category assignment, no change suppression).
     390 | + */
     391 | +static UniValue ListRawTransaction(const CWallet& wallet, const CWalletTx& wtx)
    


    vicjuma commented at 10:08 PM on July 27, 2026:

    without logical interpretation

    tACK. I think coinbase tx category may apply here.


    pablomartin4btc commented at 12:34 AM on July 28, 2026:

    A coinbase tx will be flagged via the "generated" field, not "category". Added a test for this in the first commit.

  16. pablomartin4btc force-pushed on Jul 28, 2026
  17. pablomartin4btc force-pushed on Jul 28, 2026
  18. DrahtBot added the label CI failed on Jul 28, 2026
  19. pablomartin4btc commented at 12:43 AM on July 28, 2026: member

    -<ins>Updates</ins>:

    • Added a test for a coinbase tx.
  20. DrahtBot removed the label CI failed on Jul 28, 2026
  21. in test/functional/wallet_listrawtransactions.py:47 in 142e1ac0f7
      42 | +        addr = self.nodes[0].getnewaddress()
      43 | +        txid = self.nodes[0].sendtoaddress(addr, 0.5)
      44 | +
      45 | +        lt_matching = [tx for tx in self.nodes[0].listtransactions("*", 20) if tx["txid"] == txid]
      46 | +        assert_equal(len(lt_matching), 2)
      47 | +        assert_equal({tx["category"] for tx in lt_matching}, {"send", "receive"})
    


    Bicaru20 commented at 11:06 AM on August 5, 2026:

    If this test is for listrawtransaction why do this check on listtransaction? Shouldn't this be made in the listtransaction test?


    pablomartin4btc commented at 4:57 AM on August 11, 2026:

    You're right, done in 061f0824f5cde46614427c0f2c3a1b9171d5fd8c, thanks! — removing the listtransactions assertions, that behaviour is already covered by its own test. Keeping the comments for context.

  22. in test/functional/wallet_listrawtransactions.py:108 in 142e1ac0f7
     103 | +
     104 | +        # listtransactions omits it — no logical category (neither send to an
     105 | +        # external address nor receive from outside).
     106 | +        lt_txids = [tx["txid"] for tx in self.nodes[0].listtransactions("*", 100)]
     107 | +        assert consolidation_txid not in lt_txids, \
     108 | +            f"consolidation txid {consolidation_txid} unexpectedly appeared in listtransactions"
    


    Bicaru20 commented at 11:14 AM on August 5, 2026:

    Again, I do not think it is necessary to demostrate the behaviour of listtransaction in this test as it has its own test to test its behaviour.


    pablomartin4btc commented at 4:58 AM on August 11, 2026:

    Same as above, removed in 061f0824f5cde46614427c0f2c3a1b9171d5fd8c, thanks!

  23. in test/functional/wallet_listrawtransactions.py:127 in 142e1ac0f7
     122 | +        self.log.info("Test count and skip")
     123 | +        node = self.nodes[0]
     124 | +
     125 | +        all_txs = node.listrawtransactions(9999)
     126 | +        total = len(all_txs)
     127 | +        assert total >= 3, f"Need at least 3 txs for pagination test, got {total}"
    


    Bicaru20 commented at 11:16 AM on August 5, 2026:

    Maybe it is better to use assert_greater_than_or_equal?

            assert_greater_than_or_equal(total, 3) # Need at least 3 txs for pagination test
    

    pablomartin4btc commented at 4:55 AM on August 11, 2026:

    Done in 061f0824f5cde46614427c0f2c3a1b9171d5fd8c, thanks!

  24. in test/functional/wallet_listrawtransactions.py:72 in 142e1ac0f7
      67 | +        # listrawtransactions intentionally omits category assignment.
      68 | +        assert_equal(entry["generated"], True)
      69 | +        assert "category" not in entry
      70 | +
      71 | +        # Wallet received the block reward (positive amount, no fee).
      72 | +        assert entry["amount"] > 0
    


    Bicaru20 commented at 8:57 AM on August 6, 2026:

    Maybe is better to use assert_greater_than

            assert_greater_than(entry["amount"],0)
    

    pablomartin4btc commented at 4:54 AM on August 11, 2026:

    Done in 061f0824f5cde46614427c0f2c3a1b9171d5fd8c, thanks!

  25. in src/wallet/rpc/transactions.cpp:410 in 142e1ac0f7
     405 | +
     406 | +    WalletTxToJSON(wallet, wtx, entry);
     407 | +    entry.pushKV("abandoned", wtx.isAbandoned());
     408 | +
     409 | +    return entry;
     410 | +}
    


    Bicaru20 commented at 10:06 AM on August 6, 2026:

    In order to keep the logic the same as ListTransactions, maybe it would be better to modify the vector ret inside the function instead of returning the entry and then adding it to the vector. That way when we call it, it would be just ListRawTransaction(*pwallet, *pwtx, ret); and the function would take care of modifying ret. Added the suggestion of how I think it should be:

    template <class Vec>
    static void ListRawTransaction(const CWallet& wallet, const CWalletTx& wtx, Vec& ret)
        EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
    {
        UniValue entry(UniValue::VOBJ);
    
        CAmount nCredit = CachedTxGetCredit(wallet, wtx, /*avoid_reuse=*/false);
        CAmount nDebit = CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/false);
        CAmount nNet = nCredit - nDebit;
        bool is_from_me = CachedTxIsFromMe(wallet, wtx);
        CAmount nFee = (is_from_me ? wtx.tx->GetValueOut() - nDebit : 0);
    
        entry.pushKV("amount", ValueFromAmount(nNet - nFee));
        if (is_from_me)
            entry.pushKV("fee", ValueFromAmount(nFee));
    
        WalletTxToJSON(wallet, wtx, entry);
        entry.pushKV("abandoned", wtx.isAbandoned());
        ret.push_back(entry);
    }
    

    pablomartin4btc commented at 4:53 AM on August 11, 2026:

    Done in 42b0de1da0dfe0b94bc8011c1ac57b3afca4d1b5, thanks!

  26. Bicaru20 commented at 10:15 AM on August 6, 2026: contributor

    Concept ACK. Left some comments that I think will improve the code. Also, maybe it would be better for reviewers if you could split the first commit into two: One for the new rpc and the other for the test.

    While reviewing, I noticed a lot of the code of listrawtransaction is from listtransaction. I though that it would be better if we created a helper function so we don't have duplicated code.

    <details> <summary>Something like this:</summary>

    
    diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
    index d20bec0ab4..5526165997 100644
    --- a/src/wallet/rpc/transactions.cpp
    +++ b/src/wallet/rpc/transactions.cpp
    @@ -409,6 +409,78 @@ static UniValue ListRawTransaction(const CWallet& wallet, const CWalletTx& wtx)
         return entry;
     }
     
    +static UniValue ListTransactionHelper(const JSONRPCRequest& request, bool raw){
    +
    +    LogDebug(BCLog::RPC, "\nlistrawtransactions 123\n");
    +    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
    +    if (!pwallet) return UniValue::VNULL;
    +
    +    // Make sure the results are valid at least up to the most recent block
    +    // the user could have gotten from another RPC command prior to now
    +    pwallet->BlockUntilSyncedToCurrentChain();
    +    std::optional<std::string> filter_label;
    +    // Listtransaction has an extra element than listrawtrnasaction
    +    // Depending on which rpc we are dealing with we substract value
    +    // to the index to get the parameter we want.
    +    int index = 1;
    +    if (!raw){
    +        index = 0;
    +        if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
    +            filter_label.emplace(LabelFromValue(request.params[0]));
    +            if (filter_label.value().empty()) {
    +                throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
    +            }
    +        }
    +    }
    +
    +    int nCount = 10;
    +    if (!request.params[1-index].isNull())
    +        nCount = request.params[1-index].getInt<int>();
    +    int nFrom = 0;
    +    if (!request.params[2-index].isNull())
    +        nFrom = request.params[2-index].getInt<int>();
    +
    +    if (nCount < 0)
    +        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
    +    if (nFrom < 0)
    +        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
    +
    +    std::vector<UniValue> ret;
    +    {
    +        LOCK(pwallet->cs_wallet);
    +
    +        const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
    +
    +        // iterate backwards until we have nCount items to return:
    +        if (!raw){
    +            for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
    +            {
    +                CWalletTx *const pwtx = (*it).second;
    +                ListTransactions(*pwallet, *pwtx, 0, true, ret, filter_label);
    +                if ((int)ret.size() >= (nCount+nFrom)) break;
    +            }
    +        } else {
    +            for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
    +                CWalletTx* const pwtx = (*it).second;
    +                ret.push_back(ListRawTransaction(*pwallet, *pwtx));
    +                if ((int)ret.size() >= (nCount + nFrom)) break;
    +            }
    +        }
    +    }
    +
    +    // ret is newest to oldest
    +
    +    if (nFrom > (int)ret.size())
    +        nFrom = ret.size();
    +    if ((nFrom + nCount) > (int)ret.size())
    +        nCount = ret.size() - nFrom;
    +
    +    auto txs_rev_it{std::make_move_iterator(ret.rend())};
    +    UniValue result{UniValue::VARR};
    +    result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest
    +    return result;
    +}
    +
     static std::vector<RPCResult> TransactionDescriptionString()
     {
         return{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n"
    @@ -496,58 +568,7 @@ RPCMethod listtransactions()
                     },
             [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
     {
    -    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
    -    if (!pwallet) return UniValue::VNULL;
    -
    -    // Make sure the results are valid at least up to the most recent block
    -    // the user could have gotten from another RPC command prior to now
    -    pwallet->BlockUntilSyncedToCurrentChain();
    -
    -    std::optional<std::string> filter_label;
    -    if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
    -        filter_label.emplace(LabelFromValue(request.params[0]));
    -        if (filter_label.value().empty()) {
    -            throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
    -        }
    -    }
    -    int nCount = 10;
    -    if (!request.params[1].isNull())
    -        nCount = request.params[1].getInt<int>();
    -    int nFrom = 0;
    -    if (!request.params[2].isNull())
    -        nFrom = request.params[2].getInt<int>();
    -
    -    if (nCount < 0)
    -        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
    -    if (nFrom < 0)
    -        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
    -
    -    std::vector<UniValue> ret;
    -    {
    -        LOCK(pwallet->cs_wallet);
    -
    -        const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
    -
    -        // iterate backwards until we have nCount items to return:
    -        for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
    -        {
    -            CWalletTx *const pwtx = (*it).second;
    -            ListTransactions(*pwallet, *pwtx, 0, true, ret, filter_label);
    -            if ((int)ret.size() >= (nCount+nFrom)) break;
    -        }
    -    }
    -
    -    // ret is newest to oldest
    -
    -    if (nFrom > (int)ret.size())
    -        nFrom = ret.size();
    -    if ((nFrom + nCount) > (int)ret.size())
    -        nCount = ret.size() - nFrom;
    -
    -    auto txs_rev_it{std::make_move_iterator(ret.rend())};
    -    UniValue result{UniValue::VARR};
    -    result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest
    -    return result;
    +    return ListTransactionHelper(request, false);
     },
         };
     }
    @@ -592,45 +613,8 @@ RPCMethod listrawtransactions()
             },
             [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
     {
    -    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
    -    if (!pwallet) return UniValue::VNULL;
    -
    -    pwallet->BlockUntilSyncedToCurrentChain();
    -
    -    int nCount = 10;
    -    if (!request.params[0].isNull())
    -        nCount = request.params[0].getInt<int>();
    -    int nFrom = 0;
    -    if (!request.params[1].isNull())
    -        nFrom = request.params[1].getInt<int>();
    -
    -    if (nCount < 0)
    -        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
    -    if (nFrom < 0)
    -        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
    -
    -    std::vector<UniValue> ret;
    -    {
    -        LOCK(pwallet->cs_wallet);
    -
    -        const CWallet::TxItems& txOrdered = pwallet->wtxOrdered;
    -
    -        for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
    -            CWalletTx* const pwtx = (*it).second;
    -            ret.push_back(ListRawTransaction(*pwallet, *pwtx));
    -            if ((int)ret.size() >= (nCount + nFrom)) break;
    -        }
    -    }
    -
    -    if (nFrom > (int)ret.size())
    -        nFrom = ret.size();
    -    if ((nFrom + nCount) > (int)ret.size())
    -        nCount = ret.size() - nFrom;
    -
    -    auto txs_rev_it{std::make_move_iterator(ret.rend())};
    -    UniValue result{UniValue::VARR};
    -    result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom);
    -    return result;
    +    return ListTransactionHelper(request, true);
     },
         };
     }
    

    </details>

    I am not supper convinced about this, as this requiers to also change the rpc listtransaction and may difficult things in the future if some new parameters are introduced in one of the rpcs. Just wanted to comment the idea to see what others think.

  27. in src/wallet/rpc/transactions.cpp:600 in ad67c8a67a
     595 | +    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
     596 | +    if (!pwallet) return UniValue::VNULL;
     597 | +
     598 | +    pwallet->BlockUntilSyncedToCurrentChain();
     599 | +
     600 | +    int nCount = 10;
    


    achow101 commented at 8:53 PM on August 10, 2026:

    In ad67c8a67aa9a0c148c88cae2062d0c43ab2b448 "wallet, test: Add listrawtransactions RPC"

    nit: New code should be snake_case.

        int count = 10;
    

    pablomartin4btc commented at 4:49 AM on August 11, 2026:

    Done in 061f0824f5cde46614427c0f2c3a1b9171d5fd8c, thanks!

  28. in src/wallet/rpc/transactions.cpp:602 in ad67c8a67a
     597 | +
     598 | +    pwallet->BlockUntilSyncedToCurrentChain();
     599 | +
     600 | +    int nCount = 10;
     601 | +    if (!request.params[0].isNull())
     602 | +        nCount = request.params[0].getInt<int>();
    


    achow101 commented at 8:55 PM on August 10, 2026:

    In ad67c8a67aa9a0c148c88cae2062d0c43ab2b448 "wallet, test: Add listrawtransactions RPC"

    nit: Use Arg instead

        int count = request.params.Arg<int>("count");
    

    pablomartin4btc commented at 4:46 AM on August 11, 2026:

    Done in 061f0824f5cde46614427c0f2c3a1b9171d5fd8c, thanks!

  29. in src/wallet/rpc/transactions.cpp:632 in ad67c8a67a
     627 | +    if ((nFrom + nCount) > (int)ret.size())
     628 | +        nCount = ret.size() - nFrom;
     629 | +
     630 | +    auto txs_rev_it{std::make_move_iterator(ret.rend())};
     631 | +    UniValue result{UniValue::VARR};
     632 | +    result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom);
    


    achow101 commented at 9:12 PM on August 10, 2026:

    In ad67c8a67aa9a0c148c88cae2062d0c43ab2b448 "wallet, test: Add listrawtransactions RPC"

    Instead of this insane iterator arithmetic, I think it would be better if the loop above that filled out ret just skipped the skip transactions in the first place. Then this can become

        result.push_backV(ret.rbegin(), ret.rend());
    

    pablomartin4btc commented at 4:46 AM on August 11, 2026:

    Done in 061f0824f5cde46614427c0f2c3a1b9171d5fd8c, thanks!

  30. achow101 commented at 9:15 PM on August 10, 2026: member

    This should take a verbose or verbosity parameter that would let the user optionally have the full transaction be decoded. The raw transaction hex should be in the default output as well.

    The help text should not include any mention of fields that will not be output, such as category.

  31. pablomartin4btc force-pushed on Aug 11, 2026
  32. pablomartin4btc force-pushed on Aug 11, 2026
  33. DrahtBot added the label CI failed on Aug 11, 2026
  34. DrahtBot commented at 3:18 AM on August 11, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task test ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/31454485563/job/93665364162</sub> <sub>LLM reason (✨ experimental): CI failed due to a build compilation error: wallet::rpc/transactions.cpp referenced a non-existent member CWalletTx::tx (3 clang errors).</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  35. pablomartin4btc force-pushed on Aug 11, 2026
  36. pablomartin4btc commented at 4:43 AM on August 11, 2026: member

    maybe it would be better for reviewers if you could split the first commit into two: One for the new rpc and the other for the test.

    I have done that, thanks.

    While reviewing, I noticed a lot of the code of listrawtransaction is from listtransaction. I thought that it would be better if we created a helper function so we don't have duplicated code.

    Thanks for the suggestion and for thinking about DRY here. listtransactions and listrawtransactions have fundamentally different semantics. listtransactions does economic interpretation: category assignment, change suppression, multiple entries per tx. listrawtransactions deliberately avoids all of that. A shared helper would entangle two RPCs that are designed to diverge, making it harder to evolve either one independently in the future.

  37. pablomartin4btc commented at 4:45 AM on August 11, 2026: member

    This should take a verbose or verbosity parameter that would let the user optionally have the full transaction be decoded. The raw transaction hex should be in the default output as well.

    The help text should not include any mention of fields that will not be output, such as category.

    Both done in 42b0de1da0dfe0b94bc8011c1ac57b3afca4d1b5, thanks.

  38. DrahtBot removed the label CI failed on Aug 11, 2026
  39. pablomartin4btc commented at 5:12 AM on August 11, 2026: member

    -<ins>Updates</ins>:

    • Addressed @Bicaru20's feedback:
      • Split the first commit into two — now 3 commits: RPC implementation, functional test, and release notes;
      • ListRawTransaction now pushes to ret directly via template <class Vec> (matching ListTransactions style), with a verbose param added for the decoded output;
      • Removed listtransactions API calls from test_one_entry_per_tx and test_consolidation_tx_visible; kept explanatory comments documenting why the comparison matters conceptually;
      • Replaced bare assert comparisons with assert_greater_than and assert_greater_than_or_equal;
      • On the shared ListTransactionHelper suggestion: listtransactions and listrawtransactions have fundamentally different semantics — the former works per-output with category assignment, label filtering, and change suppression; the latter works per-transaction with no categorisation. A shared raw flag would couple two RPCs that are likely to diverge further (e.g. the new verbose param only applies to listrawtransactions). Keeping them independent is cleaner.
    • Addressed @achow101's feedback:
      • Added verbose (bool, default false); hex is always included in the output; decoded is added when verbose=true (matches the gettransaction pattern);
      • Removed category field references from TransactionDescriptionString() help strings — descriptions no longer reference the category field;
      • Variables are now snake_case;
      • Using self.Arg<int>("count"), self.Arg<int>("skip"), self.Arg<bool>("verbose");
      • Pagination now skips in the loop directly; result assembled with result.push_backV(ret.rbegin(), ret.rend()).
  40. in src/wallet/rpc/transactions.cpp:637 in 42b0de1da0
     632 | +    if (!pwallet) return UniValue::VNULL;
     633 | +
     634 | +    pwallet->BlockUntilSyncedToCurrentChain();
     635 | +
     636 | +    int count   = self.Arg<int>("count");
     637 | +    int skip    = self.Arg<int>("skip");
    


    achow101 commented at 7:20 PM on August 11, 2026:

    In 42b0de1da0dfe0b94bc8011c1ac57b3afca4d1b5 "wallet, rpc: Add listrawtransactions RPC"

    nit: Whitespace


    pablomartin4btc commented at 9:05 PM on August 11, 2026:

    Done in 4791ec9727db6e169f4b130ad8ac50faf97c26dc, thanks!

  41. in src/wallet/rpc/transactions.cpp:653 in 42b0de1da0
     648 | +
     649 | +        const CWallet::TxItems& txOrdered = pwallet->wtxOrdered;
     650 | +
     651 | +        int skipped = 0;
     652 | +        for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
     653 | +            if (skipped < skip) { ++skipped; continue; }
    


    achow101 commented at 7:21 PM on August 11, 2026:

    In 42b0de1da0dfe0b94bc8011c1ac57b3afca4d1b5 "wallet, rpc: Add listrawtransactions RPC"

                if (skipped++ < skip) continue
    

    pablomartin4btc commented at 9:05 PM on August 11, 2026:

    Done in 4791ec9727db6e169f4b130ad8ac50faf97c26dc, thanks!

  42. in src/wallet/rpc/transactions.cpp:649 in 42b0de1da0
     644 | +
     645 | +    std::vector<UniValue> ret;
     646 | +    {
     647 | +        LOCK(pwallet->cs_wallet);
     648 | +
     649 | +        const CWallet::TxItems& txOrdered = pwallet->wtxOrdered;
    


    achow101 commented at 7:21 PM on August 11, 2026:

    In 42b0de1da0dfe0b94bc8011c1ac57b3afca4d1b5 "wallet, rpc: Add listrawtransactions RPC"

    nit: snake_case


    pablomartin4btc commented at 9:05 PM on August 11, 2026:

    Done in 4791ec9727db6e169f4b130ad8ac50faf97c26dc, thanks!

  43. in src/wallet/rpc/transactions.cpp:404 in 42b0de1da0
     399 | +static void ListRawTransaction(const CWallet& wallet, const CWalletTx& wtx, Vec& ret, bool verbose)
     400 | +    EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
     401 | +{
     402 | +    UniValue entry(UniValue::VOBJ);
     403 | +
     404 | +    CAmount nCredit = CachedTxGetCredit(wallet, wtx, /*avoid_reuse=*/false);
    


    achow101 commented at 7:22 PM on August 11, 2026:

    In 42b0de1da0dfe0b94bc8011c1ac57b3afca4d1b5 "wallet, rpc: Add listrawtransactions RPC"

    nit: Naming, in this function in general.


    pablomartin4btc commented at 9:05 PM on August 11, 2026:

    Done in 4791ec9727db6e169f4b130ad8ac50faf97c26dc, thanks!

  44. pablomartin4btc force-pushed on Aug 11, 2026
  45. pablomartin4btc commented at 9:15 PM on August 11, 2026: member

    -<ins>Updates</ins>:

    • Addressed @achow101's feedback: corrected naming convention for new code (snake_case) — missed from previous feedback (sorry!), removed extra alignment spaces and suggestion taken for if (skipped < skip) { ++skipped; continue; }if (skipped++ < skip) continue.
  46. achow101 commented at 10:05 PM on August 11, 2026: member

    ACK 7940906b3fdcbe8defdd78dcc153da4ec0bda1b6

  47. DrahtBot requested review from Bicaru20 on Aug 11, 2026
  48. Bicaru20 commented at 6:09 PM on August 19, 2026: contributor

    ACK 7940906b3f

    I really like the verbose parameter to ge the decoded transaction, I think it is very usefull.

    Thanks for the suggestion and for thinking about DRY here. listtransactions and listrawtransactions have fundamentally different semantics. listtransactions does economic interpretation: category assignment, change suppression, multiple entries per tx. listrawtransactions deliberately avoids all of that. A shared helper would entangle two RPCs that are designed to diverge, making it harder to evolve either one independently in the future.

    Agreed, it make sense to leave it separately.

  49. in src/wallet/rpc/transactions.cpp:656 in 7940906b3f
     651 | +        int skipped = 0;
     652 | +        for (CWallet::TxItems::const_reverse_iterator it = tx_ordered.rbegin(); it != tx_ordered.rend(); ++it) {
     653 | +            if (skipped++ < skip) continue;
     654 | +            CWalletTx* const pwtx = (*it).second;
     655 | +            ListRawTransaction(*pwallet, *pwtx, ret, verbose);
     656 | +            if ((int)ret.size() >= count) break;
    


    w0xlt commented at 8:12 PM on August 20, 2026:

    Why is count=0 allowed ? This will always return no data.

    Maybe can the validation be changed ?

    if (count <= 0)
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative or zero count");
    

    But even we want to support zero count for any reason, the code needs to be fixed since it will return the newest eligible wallet transaction:

    diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
    index a754aa3942..d95e8b5bb2 100644
    --- a/src/wallet/rpc/transactions.cpp
    +++ b/src/wallet/rpc/transactions.cpp
    @@ -650,10 +650,10 @@ RPCMethod listrawtransactions()
     
             int skipped = 0;
             for (CWallet::TxItems::const_reverse_iterator it = tx_ordered.rbegin(); it != tx_ordered.rend(); ++it) {
    +            if ((int)ret.size() >= count) break;
                 if (skipped++ < skip) continue;
                 CWalletTx* const pwtx = (*it).second;
                 ListRawTransaction(*pwallet, *pwtx, ret, verbose);
    -            if ((int)ret.size() >= count) break;
             }
         }
     
    diff --git a/test/functional/wallet_listrawtransactions.py b/test/functional/wallet_listrawtransactions.py
    index 549d920f92..f2ced75141 100755
    --- a/test/functional/wallet_listrawtransactions.py
    +++ b/test/functional/wallet_listrawtransactions.py
    @@ -121,6 +121,9 @@ class ListRawTransactionsTest(BitcoinTestFramework):
             total = len(all_txs)
             assert_greater_than_or_equal(total, 3)  # Need at least 3 txs for pagination test
     
    +        # count=0 returns an empty page.
    +        assert_equal(node.listrawtransactions(0), [])
    +
             # count=2 returns the 2 newest txs ordered oldest-first within the result.
             page = node.listrawtransactions(2)
             assert_equal(len(page), 2)
    

    pablomartin4btc commented at 11:48 PM on August 20, 2026:

    Thanks for catching this!

    Why is count=0 allowed ? This will always return no data.

    The reasoning is that listrawtransactions is meant to mimic listtransactions' behaviour, and rejecting count=0 here would create a divergence — listtransactions already accepts it silently and returns [].

    I've taken your fix — as you spotted, count=0 was incorrectly returning the newest record instead of an empty list.

  50. DrahtBot requested review from w0xlt on Aug 20, 2026
  51. pablomartin4btc force-pushed on Aug 20, 2026
  52. pablomartin4btc commented at 11:54 PM on August 20, 2026: member

    -<ins>Updates</ins>:

    • Addressed @w0xlt's feedback: fixed count=0 incorrectly returning the newest record; now correctly returns [], matching listtransactions' behaviour.
  53. BrandonOdiwuor commented at 11:52 PM on August 21, 2026: contributor

    ACK f7f4bf67f7ffa66e05c142257fd281baf57b4ff2

    Tested the new listrawtransactions RPC on regtest (Ubuntu 24.04.4 LTS)

    Verified:

    • Every wallet transaction appears exactly once, with no category field
    • Self-sends are shown as a single entry with amount: 0 and the correct fee
    • Pagination via count / skip works as documented (including edge cases)

    listrawtransactions vs listtransactions

    <details> <summary>bitcoin-cli -regtest listtransactions</summary>

    ./build_dev_mode/bin/bitcoin-cli -regtest -rpcwallet=my_wallet listtransactions
    [
      {
        "address": "bcrt1qygq445lj6r9qx9q0mss8w32u2c63fydlh52l8h",
        "parent_descs": [
          "wpkh([3abfaf54/84h/1h/0h]tpubDC4R87NimtK5PaN6WYxrayGo5LiZVfcjt3Jm58h4xuR24MyrJkiWWu3bDj4d9AKJmhRLEN1tTUm78Lo9CixG2gTPT6iN7GfjEVJtnSwv2PL/0/*)#vpvgr0z6"
        ],
        "category": "receive",
        "amount": 5.00000000,
        "label": "",
        "vout": 0,
        "abandoned": false,
        "confirmations": 1,
        "blockhash": "38d06e1c5bde99df396bd60c4b54287e994aaa0cbd0c50061a8d92401e48f096",
        "blockheight": 102,
        "blockindex": 4,
        "blocktime": 1787331199,
        "txid": "3021a78312359fb6557ff2337404e9983420825cd3e6900b5ec8201a68d5d4de",
        "wtxid": "139dd42c8088e50f3916bb1b93c4a84ac9f852a727c3aa70f7a9b7d818609552",
        "alternate_wtxids": [
        ],
        "walletconflicts": [
        ],
        "mempoolconflicts": [
        ],
        "time": 1787331105,
        "timereceived": 1787331105
      },
      {
        "address": "bcrt1qzc5v2u04ey742g8csxfqm95qaee7u92qrhkfxr",
        "parent_descs": [
          "wpkh([3abfaf54/84h/1h/0h]tpubDC4R87NimtK5PaN6WYxrayGo5LiZVfcjt3Jm58h4xuR24MyrJkiWWu3bDj4d9AKJmhRLEN1tTUm78Lo9CixG2gTPT6iN7GfjEVJtnSwv2PL/0/*)#vpvgr0z6"
        ],
        "category": "receive",
        "amount": 10.00000000,
        "label": "",
        "vout": 1,
        "abandoned": false,
        "confirmations": 1,
        "blockhash": "38d06e1c5bde99df396bd60c4b54287e994aaa0cbd0c50061a8d92401e48f096",
        "blockheight": 102,
        "blockindex": 5,
        "blocktime": 1787331199,
        "txid": "097ca684069f02817c36ea61bbd155782293802764d6505a991b0c9dd408fc74",
        "wtxid": "39276f39bbe1e1e20d88e53cfe577e5a2c875faf965a30b3999327e9b32380e3",
        "alternate_wtxids": [
        ],
        "walletconflicts": [
        ],
        "mempoolconflicts": [
        ],
        "time": 1787331105,
        "timereceived": 1787331105
      },
      {
        "address": "bcrt1q0vwvq26wqsff6fca92scc3355562l6l9ru0vwy",
        "parent_descs": [
          "wpkh([3abfaf54/84h/1h/0h]tpubDC4R87NimtK5PaN6WYxrayGo5LiZVfcjt3Jm58h4xuR24MyrJkiWWu3bDj4d9AKJmhRLEN1tTUm78Lo9CixG2gTPT6iN7GfjEVJtnSwv2PL/0/*)#vpvgr0z6"
        ],
        "category": "receive",
        "amount": 2.00000000,
        "label": "",
        "vout": 0,
        "abandoned": false,
        "confirmations": 0,
        "trusted": true,
        "txid": "aa3acf06a2b474ed3675775f51de695b779ffcbe59c44056c97bf43c9249cc8d",
        "wtxid": "65ea0b1a4e47a4f1e53c4c76f5e18fbe6b61fa32a654b1f016b76c20b3dc8cb3",
        "alternate_wtxids": [
        ],
        "walletconflicts": [
        ],
        "mempoolconflicts": [
        ],
        "time": 1787331293,
        "timereceived": 1787331293
      },
      {
        "address": "bcrt1q0vwvq26wqsff6fca92scc3355562l6l9ru0vwy",
        "category": "send",
        "amount": -2.00000000,
        "label": "",
        "vout": 0,
        "fee": -0.00002820,
        "confirmations": 0,
        "trusted": true,
        "txid": "aa3acf06a2b474ed3675775f51de695b779ffcbe59c44056c97bf43c9249cc8d",
        "wtxid": "65ea0b1a4e47a4f1e53c4c76f5e18fbe6b61fa32a654b1f016b76c20b3dc8cb3",
        "alternate_wtxids": [
        ],
        "walletconflicts": [
        ],
        "mempoolconflicts": [
        ],
        "time": 1787331293,
        "timereceived": 1787331293,
        "abandoned": false
      }
    ]
    

    </details>

    <details> <summary>bitcoin-cli -regtest listrawtransactions</summary>

    ./build_dev_mode/bin/bitcoin-cli -regtest -rpcwallet=my_wallet listrawtransactions
    [
      {
        "amount": 5.00000000,
        "confirmations": 1,
        "blockhash": "38d06e1c5bde99df396bd60c4b54287e994aaa0cbd0c50061a8d92401e48f096",
        "blockheight": 102,
        "blockindex": 4,
        "blocktime": 1787331199,
        "txid": "3021a78312359fb6557ff2337404e9983420825cd3e6900b5ec8201a68d5d4de",
        "wtxid": "139dd42c8088e50f3916bb1b93c4a84ac9f852a727c3aa70f7a9b7d818609552",
        "alternate_wtxids": [
        ],
        "walletconflicts": [
        ],
        "mempoolconflicts": [
        ],
        "time": 1787331105,
        "timereceived": 1787331105,
        "abandoned": false,
        "hex": "020000000001018ae67ec082ae5b4bfdd0a55eb30430835a4cd93077328bc69ad780d8e77b55c90100000000fdffffff020065cd1d0000000016001422015ad3f2d0ca03140fdc2077455c56351491bfac110295000000001600147aa249ffdc64983416f76a5c5d25f886639030f10247304402202998e2ab4d0ca0d1662a5765afac075971fc70a2b209ecbf9136268396f66b01022017427b04efdfb22ec0b06e306fa6537683043e2355686d30cfc423935f92c20c012102eb5f7f220b26583a40475a46682a589e3ae3274d4ad09debaabf61d08f8ca92500000000"
      },
      {
        "amount": 10.00000000,
        "confirmations": 1,
        "blockhash": "38d06e1c5bde99df396bd60c4b54287e994aaa0cbd0c50061a8d92401e48f096",
        "blockheight": 102,
        "blockindex": 5,
        "blocktime": 1787331199,
        "txid": "097ca684069f02817c36ea61bbd155782293802764d6505a991b0c9dd408fc74",
        "wtxid": "39276f39bbe1e1e20d88e53cfe577e5a2c875faf965a30b3999327e9b32380e3",
        "alternate_wtxids": [
        ],
        "walletconflicts": [
        ],
        "mempoolconflicts": [
        ],
        "time": 1787331105,
        "timereceived": 1787331105,
        "abandoned": false,
        "hex": "0200000000010275faff94cf8bdd5e593af300a9cc554a03b7c7c04ec8dd2337eb7982113eaf450000000000fdffffff631c2e7eeea13598244721c1767be13a4afe739dabde0a6ee767311b38ca3c820100000000fdffffff02209ccb1d0000000016001487afa811c32f2aabf8cdd6c96cf1c8daaae2d12e00ca9a3b000000001600141628c571f5c93d5520f881920d9680ee73ee154002473044022033c6120fc7a5a223a5c2c4482e28fdfad0a259f8d9a555a14189a041b1190942022067047e825bac1e2878dedd4b2729e9e7cfc0e8a17608519bd3725aa34948e8e001210241e7b7b792252c35d40d131fb1d3aecf3196095d6f1a4df696525c1ce14412f302473044022008622e5c03f0deb65388faefa4418aefdbaa64972d1cc67965d3d807e72eeefb022014e42955657f929142cd0a2ef0d3ceb4d34afddcea35e21f26c579a2c87fdeeb01210381f2fa33f5a95ff2cc944b89747a8578f72119f2311c6ea26b154d2c578f717e00000000"
      },
      {
        "amount": 0.00000000,
        "fee": -0.00002820,
        "confirmations": 0,
        "trusted": true,
        "txid": "aa3acf06a2b474ed3675775f51de695b779ffcbe59c44056c97bf43c9249cc8d",
        "wtxid": "65ea0b1a4e47a4f1e53c4c76f5e18fbe6b61fa32a654b1f016b76c20b3dc8cb3",
        "alternate_wtxids": [
        ],
        "walletconflicts": [
        ],
        "mempoolconflicts": [
        ],
        "time": 1787331293,
        "timereceived": 1787331293,
        "abandoned": false,
        "hex": "02000000000101ded4d5681a20c85e0b90e6d35c82203498e9047433f27f55b69f351283a721300000000000fdffffff0200c2eb0b000000001600147b1cc02b4e04129d271d2aa18c4634a534afebe5fc97e1110000000016001417e7b4daa467249ad1db13c1022d9079e5abb0e302473044022057c38c69acb5c1ff73f89842c6f5f46459069f2d26b29c4fc4defc6d8f47a277022041f8331d2c442ec6b7b431c15ea6d7621bfceae231821a59bbade8778f67b3460121036b245d774b4e83f854f8684cf839574358174d4d3a3232b59f4e794f2da4c06266000000"
      }
    ]
    

    </details>

    Self-send (single entry with amount: 0)

    <img width="1907" height="404" alt="Image" src="https://github.com/user-attachments/assets/be1c60d2-4719-4d45-af5a-38ee16ba9502" />

    Pagination

    <img width="1908" height="957" alt="Image" src="https://github.com/user-attachments/assets/c8bcb160-d237-4a2b-b768-3fdd8bca0404" />

    <img width="1468" height="140" alt="Image" src="https://github.com/user-attachments/assets/7f17f8d6-1dc4-4bfe-8d14-93a0d5571aa6" />

    The RPC behaves as described and makes internal transfers / consolidations visible.

  54. DrahtBot requested review from achow101 on Aug 21, 2026
  55. w0xlt commented at 11:36 PM on August 25, 2026: contributor

    ACK f7f4bf67f7ffa66e05c142257fd281baf57b4ff2

  56. in src/wallet/rpc/transactions.cpp:595 in 1fa34d1057 outdated
     590 | +        "(no category assignment, no change suppression). This means consolidation and self-transfer "
     591 | +        "transactions that are invisible in `listtransactions` are included here.\n",
     592 | +        {
     593 | +            {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return."},
     594 | +            {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip."},
     595 | +            {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
    


    polespinasa commented at 3:43 PM on August 26, 2026:

    in 1fa34d1057843854f20ad35f7250ab2f8a7cb575 wallet, rpc: Add listrawtransactions RPC

    I would use verbosity to be consistent with other RPC calls with the same arg (see getrawtransaction). Also, we normally have multiple levels of verbosity. I would suggest use a numeric type here with 0, 1 or 2, and give different type of information depending on that level. This again being consistent with what getrawtransaction does.


    pablomartin4btc commented at 3:35 AM on August 27, 2026:

    I think getrawtransaction's verbosity=2 exists mainly because fee and prevout there both need the same block-undo-data read — verbosity=1 never reads it, so bundling them at one tier avoids doing that read twice.

    listrawtransactions doesn't have that constraint: its fee already comes from the wallet's own accounting (CachedTxGetCredit/CachedTxGetDebit), no prevout lookup needed. A prevout tier here would only help with a niche case (seeing each input's source address/ amount), and unlike getrawtransaction (one tx per call), this RPC can decode up to count transactions per call, so that lookup cost multiplies.

    I'd keep verbose as a bool, matching gettransactionlistrawtransactions is already following the right sibling.

  57. in src/wallet/rpc/transactions.cpp:605 in 1fa34d1057 outdated
     600 | +                {RPCResult::Type::OBJ, "", "", Cat<std::vector<RPCResult>>(
     601 | +                {
     602 | +                    {RPCResult::Type::STR_AMOUNT, "amount", "The net change to the wallet balance caused by this transaction "
     603 | +                        "(excluding fee). Positive means the wallet gained funds, negative means it lost funds, "
     604 | +                        "zero means a pure self-transfer (e.g. consolidation)."},
     605 | +                    {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The fee paid in " + CURRENCY_UNIT + ". "
    


    polespinasa commented at 4:31 PM on August 26, 2026:

    in 1fa34d1057843854f20ad35f7250ab2f8a7cb575 wallet, rpc: Add listrawtransactions RPC

    Worth adding that it is a negative value. See listtransactions fee description:

        "fee" : n,                       (numeric, optional) The amount of the fee in BTC. This is negative and only available for the
                                         'send' category of transactions.
    
    

    pablomartin4btc commented at 3:37 AM on August 27, 2026:

    Done in 304596b3083b100df34f53ae543be50c70135ca5, thanks!

  58. in doc/release-notes-35813.md:4 in f7f4bf67f7 outdated
       0 | @@ -0,0 +1,12 @@
       1 | +New RPCs
       2 | +--------
       3 | +
       4 | +- A new `listrawtransactions` RPC has been added to the wallet. Unlike
    


    polespinasa commented at 4:38 PM on August 26, 2026:

    in f7f4bf67f7ffa66e05c142257fd281baf57b4ff2 doc: Add release notes for listrawtransactions

    The release note misdescribe amount and fee.

    For amount should mention that it excludes the fee. And should mention that the fee is optional and only appears if the wallet funded the transaction.


    pablomartin4btc commented at 3:37 AM on August 27, 2026:

    Done in 1a03da3ade34455818c0da09bacf337edba8f921, thanks!

  59. in src/wallet/rpc/transactions.cpp:589 in 1fa34d1057
     584 | +{
     585 | +    return RPCMethod{
     586 | +        "listrawtransactions",
     587 | +        "Returns up to 'count' most recent wallet transactions ordered from oldest to newest, "
     588 | +        "skipping the first 'skip' transactions. Unlike `listtransactions`, each wallet transaction "
     589 | +        "appears exactly once with its net wallet balance change, without logical interpretation "
    


    polespinasa commented at 4:40 PM on August 26, 2026:

    in 1fa34d1057843854f20ad35f7250ab2f8a7cb575 wallet, rpc: Add listrawtransactions RPC

    Should mention "excluding fee".


    pablomartin4btc commented at 3:38 AM on August 27, 2026:

    Done in 304596b3083b100df34f53ae543be50c70135ca5, thanks!

  60. in test/functional/wallet_listrawtransactions.py:95 in e6a746af1a
      90 | +        # omits it (outputs on the receiving keychain would appear as "receive").
      91 | +        inputs = [{"txid": utxo1["txid"], "vout": utxo1["vout"]},
      92 | +                  {"txid": utxo2["txid"], "vout": utxo2["vout"]}]
      93 | +        total = utxo1["amount"] + utxo2["amount"]
      94 | +        fee = Decimal("0.0001")
      95 | +        outputs = {self.nodes[0].getrawchangeaddress(): float(total - fee)}
    


    polespinasa commented at 4:41 PM on August 26, 2026:

    in e6a746af1a00400595caa2e816b617ea619d0ba1 test: Add functional test for listrawtransactions

    IIRC we use Decimal not Float for bitcoin amounts.


    pablomartin4btc commented at 3:38 AM on August 27, 2026:

    Done in 5617c172a47f4c969c9834d97020fcb5f40710e0, thanks!

  61. polespinasa commented at 4:43 PM on August 26, 2026: member

    concept ACK

  62. polespinasa commented at 5:28 PM on August 26, 2026: member

    There is some code duplications between listtransactions and listrawtransaction some static helper function would probably make it more clean and readable.

    <details> <summary>diff</summary>

    $ git diff
    diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
    index d95e8b5bb2..a84632ba87 100644
    --- a/src/wallet/rpc/transactions.cpp
    +++ b/src/wallet/rpc/transactions.cpp
    @@ -387,6 +387,47 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
         }
     }
     
    +static void PushTxAmountAndFee(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry)
    +    EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
    +{
    +    CAmount credit = CachedTxGetCredit(wallet, wtx, /*avoid_reuse=*/false);
    +    CAmount debit = CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/false);
    +    CAmount net = credit - debit;
    +    bool is_from_me = CachedTxIsFromMe(wallet, wtx);
    +    CAmount fee = (is_from_me ? wtx.GetTx()->GetValueOut() - debit : 0);
    +
    +    entry.pushKV("amount", ValueFromAmount(net - fee));
    +    if (is_from_me)
    +        entry.pushKV("fee", ValueFromAmount(fee));
    +}
    +
    +static void PushTxDecoded(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry)
    +    EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
    +{
    +    UniValue decoded(UniValue::VOBJ);
    +    TxToUniv(*wtx.GetTx(),
    +            /*block_hash=*/uint256(),
    +            /*entry=*/decoded,
    +            /*include_hex=*/false,
    +            /*txundo=*/nullptr,
    +            /*verbosity=*/TxVerbosity::SHOW_DETAILS,
    +            /*is_change_func=*/[&wallet](const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) {
    +                                    AssertLockHeld(wallet.cs_wallet);
    +                                    return OutputIsChange(wallet, txout);
    +                                });
    +    entry.pushKV("decoded", std::move(decoded));
    +}
    +
     /**
      * Append a raw transaction entry for the given wallet transaction to ret.
      *
    @@ -401,33 +442,14 @@ static void ListRawTransaction(const CWallet& wallet, const CWalletTx& wtx, Vec&
     {
         UniValue entry(UniValue::VOBJ);
     
    -    CAmount credit = CachedTxGetCredit(wallet, wtx, /*avoid_reuse=*/false);
    -    CAmount debit = CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/false);
    -    CAmount net = credit - debit;
    -    bool is_from_me = CachedTxIsFromMe(wallet, wtx);
    -    CAmount fee = (is_from_me ? wtx.GetTx()->GetValueOut() - debit : 0);
    -
    -    entry.pushKV("amount", ValueFromAmount(net - fee));
    -    if (is_from_me)
    -        entry.pushKV("fee", ValueFromAmount(fee));
    +    PushTxAmountAndFee(wallet, wtx, entry);
     
         WalletTxToJSON(wallet, wtx, entry);
         entry.pushKV("abandoned", wtx.isAbandoned());
         entry.pushKV("hex", EncodeHexTx(*wtx.GetTx()));
     
         if (verbose) {
    -        UniValue decoded(UniValue::VOBJ);
    -        TxToUniv(*wtx.GetTx(),
    -                /*block_hash=*/uint256(),
    -                /*entry=*/decoded,
    -                /*include_hex=*/false,
    -                /*txundo=*/nullptr,
    -                /*verbosity=*/TxVerbosity::SHOW_DETAILS,
    -                /*is_change_func=*/[&wallet](const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) {
    -                                        AssertLockHeld(wallet.cs_wallet);
    -                                        return OutputIsChange(wallet, txout);
    -                                    });
    -        entry.pushKV("decoded", std::move(decoded));
    +        PushTxDecoded(wallet, wtx, entry);
         }
     
         ret.push_back(std::move(entry));
    @@ -891,14 +913,7 @@ RPCMethod gettransaction()
         }
         const CWalletTx& wtx = it->second;
     
    -    CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, /*avoid_reuse=*/false);
    -    CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, /*avoid_reuse=*/false);
    -    CAmount nNet = nCredit - nDebit;
    -    CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx) ? wtx.GetTx()->GetValueOut() - nDebit : 0);
    -
    -    entry.pushKV("amount", ValueFromAmount(nNet - nFee));
    -    if (CachedTxIsFromMe(*pwallet, wtx))
    -        entry.pushKV("fee", ValueFromAmount(nFee));
    +    PushTxAmountAndFee(*pwallet, wtx, entry);
     
         WalletTxToJSON(*pwallet, wtx, entry);
     
    @@ -909,18 +924,7 @@ RPCMethod gettransaction()
         entry.pushKV("hex", EncodeHexTx(*wtx.GetTx()));
     
         if (verbose) {
    -        UniValue decoded(UniValue::VOBJ);
    -        TxToUniv(*wtx.GetTx(),
    -                /*block_hash=*/uint256(),
    -                /*entry=*/decoded,
    -                /*include_hex=*/false,
    -                /*txundo=*/nullptr,
    -                /*verbosity=*/TxVerbosity::SHOW_DETAILS,
    -                /*is_change_func=*/[&pwallet](const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
    -                                        AssertLockHeld(pwallet->cs_wallet);
    -                                        return OutputIsChange(*pwallet, txout);
    -                                    });
    -        entry.pushKV("decoded", std::move(decoded));
    +        PushTxDecoded(*pwallet, wtx, entry);
         }
     
         AppendLastProcessedBlock(entry, *pwallet);
    
    
  63. pablomartin4btc commented at 3:00 AM on August 27, 2026: member

    There is some code duplications between listtransactions and listrawtransaction some static helper function would probably make it more clean and readable.

    Thanks for looking at that with detail... I'll make this refactoring... small nit on the prose: this is dedup between gettransaction and ListRawTransaction, not listtransactions — the diff itself is exactly right though — mostly so nobody skimming the thread later comes away thinking ListTransactions's own logic was in scope, since it genuinely does something different (per-output category splitting via CachedTxGetAmounts, not the net credit/debit approach the other two share).

    I'll land this as a prep commit before the rest, so gettransaction gets the helpers extracted first and listrawtransactions is introduced already using them (via ListRawTransaction), rather than duplicating the logic and cleaning it up after.

  64. wallet, rpc: extract PushTxAmountAndFee/PushTxDecoded helpers
    gettransaction's amount/fee and decoded-transaction logic is duplicated
    inline. Extract it into two static helpers, PushTxAmountAndFee and
    PushTxDecoded, and have gettransaction call them instead.
    
    This is in preparation for the upcoming listrawtransactions RPC, which
    needs the exact same amount/fee and decoded-transaction logic per
    transaction entry and will reuse these helpers rather than duplicate
    them again.
    
    Co-authored-by: polespinasa <polespinasa@users.noreply.github.com>
    8c9d360058
  65. pablomartin4btc force-pushed on Aug 27, 2026
  66. pablomartin4btc commented at 3:47 AM on August 27, 2026: member

    -<ins>Updates</ins>:

    • Addressed @polespinasa's feedback here and regarding a refactoring here:
      • fee doc: now notes it's negative, matching gettransaction's wording.
      • amount's help text and the release note now both mention it excludes the fee.
      • Release note: also clarifies fee is only present (and negative) when the wallet funded the transaction.
      • Test: dropped an unnecessary float() cast on total - fee — kept as Decimal, per convention.
      • verbose/ verbosity: kept verbose as a bool, matching gettransaction rather than getrawtransaction — explained the reasoning in the thread (this RPC's fee doesn't depend on prevout data the way getrawtransaction's does, so the usual reason for a numeric tier doesn't transfer here).
      • Refactoring: extracted PushTxAmountAndFee/ PushTxDecoded static helpers shared between gettransaction and ListRawTransactions, landed as a prep commit ahead of the RPC commit.
  67. w0xlt commented at 5:19 AM on August 27, 2026: contributor

    reACK 1a03da3ade34455818c0da09bacf337edba8f921

  68. DrahtBot requested review from BrandonOdiwuor on Aug 27, 2026
  69. DrahtBot requested review from polespinasa on Aug 27, 2026
  70. in src/wallet/rpc/transactions.cpp:430 in 304596b308
     425 | + * @param  wtx            The wallet transaction.
     426 | + * @param  ret            Output vector to append the entry to.
     427 | + * @param  verbose        If true, include a decoded transaction object.
     428 | + */
     429 | +template <class Vec>
     430 | +static void ListRawTransaction(const CWallet& wallet, const CWalletTx& wtx, Vec& ret, bool verbose)
    


    polespinasa commented at 7:24 AM on August 27, 2026:

    in 304596b3083b100df34f53ae543be50c70135ca5 wallet, rpc: Add listrawtransactions RPC

    Given how small this function is and given that it is only used inside RPCMethod listrawtransactions() it can probably be a lamda function inside it.

    <details> <summary>suggested diff</summary>

    $ git diff
    diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
    index 0602ede8e2..cdde1774f0 100644
    --- a/src/wallet/rpc/transactions.cpp
    +++ b/src/wallet/rpc/transactions.cpp
    @@ -418,33 +418,6 @@ static void PushTxDecoded(const CWallet& wallet, const CWalletTx& wtx, UniValue&
         entry.pushKV("decoded", std::move(decoded));
     }
     
    -/**
    - * Append a raw transaction entry for the given wallet transaction to ret.
    - *
    - * [@param](/github-metadata-backup-bitcoin-bitcoin/contributor/param/)  wallet         The wallet.
    - * [@param](/github-metadata-backup-bitcoin-bitcoin/contributor/param/)  wtx            The wallet transaction.
    - * [@param](/github-metadata-backup-bitcoin-bitcoin/contributor/param/)  ret            Output vector to append the entry to.
    - * [@param](/github-metadata-backup-bitcoin-bitcoin/contributor/param/)  verbose        If true, include a decoded transaction object.
    - */
    -template <class Vec>
    -static void ListRawTransaction(const CWallet& wallet, const CWalletTx& wtx, Vec& ret, bool verbose)
    -    EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
    -{
    -    UniValue entry(UniValue::VOBJ);
    -
    -    PushTxAmountAndFee(wallet, wtx, entry);
    -
    -    WalletTxToJSON(wallet, wtx, entry);
    -    entry.pushKV("abandoned", wtx.isAbandoned());
    -    entry.pushKV("hex", EncodeHexTx(*wtx.GetTx()));
    -
    -    if (verbose) {
    -        PushTxDecoded(wallet, wtx, entry);
    -    }
    -
    -    ret.push_back(std::move(entry));
    -}
    -
     static std::vector<RPCResult> TransactionDescriptionString()
     {
         return{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n"
    @@ -660,12 +633,23 @@ RPCMethod listrawtransactions()
     
             const CWallet::TxItems& tx_ordered = pwallet->wtxOrdered;
     
    +        auto list_raw_tx = [&](const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
    +            UniValue entry(UniValue::VOBJ);
    +            PushTxAmountAndFee(*pwallet, wtx, entry);
    +            WalletTxToJSON(*pwallet, wtx, entry);
    +            entry.pushKV("abandoned", wtx.isAbandoned());
    +            entry.pushKV("hex", EncodeHexTx(*wtx.GetTx()));
    +            if (verbose) {
    +                PushTxDecoded(*pwallet, wtx, entry);
    +            }
    +            ret.push_back(std::move(entry));
    +        };
    +
             int skipped = 0;
             for (CWallet::TxItems::const_reverse_iterator it = tx_ordered.rbegin(); it != tx_ordered.rend(); ++it) {
                 if ((int)ret.size() >= count) break;
                 if (skipped++ < skip) continue;
    -            CWalletTx* const pwtx = (*it).second;
    -            ListRawTransaction(*pwallet, *pwtx, ret, verbose);
    +            list_raw_tx(*(*it).second);
             }
         }
    
    

    </details>


    pablomartin4btc commented at 6:44 PM on August 27, 2026:

    Thanks for the suggestion — I'd like to keep it as a named function rather than inline it, for a few reasons:

    • Every other per-entry builder in this file (ListTransactions, ListReceived, and the PushTxAmountAndFee/PushTxDecoded helpers just extracted from gettransaction) is a free static function, not a lambda. listrawtransactions deliberately mirrors listtransactions's shape (same iteration/pagination structure, just one entry per tx instead of one per category-split), so keeping ListRawTransaction named keeps that parallel intact.
    • The doc comment above it (@param wallet, @param verbose, etc.) reads naturally on a named function; it doesn't attach as cleanly to a lambda assignment.
    • Keeping the per-entry formatting logic out of the RPC handler's body keeps that lambda focused purely on iteration/pagination (skip/count/wtxOrdered traversal) — inlining would mix those two concerns back together.
    • ListRawTransaction could be a natural candidate to gain a second caller later; keeping it named avoids repeating that extraction.

    I did take the related point about the Vec template though — since it only has one caller today (unlike ListTransactions, which genuinely needs the template because listsinceblock passes a plain UniValue instead of a std::vector<UniValue>), I dropped it in favour of a concrete std::vector<UniValue>& parameter.

  71. in src/wallet/rpc/transactions.cpp:620 in 304596b308
     615 | +                        "(excluding fee). Positive means the wallet gained funds, negative means it lost funds, "
     616 | +                        "zero means a pure self-transfer (e.g. consolidation)."},
     617 | +                    {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The fee paid in " + CURRENCY_UNIT + ". "
     618 | +                        "This is negative and only present when the wallet funded the transaction."},
     619 | +                },
     620 | +                Cat(TransactionDescriptionString(),
    


    polespinasa commented at 7:30 AM on August 27, 2026:

    in 304596b wallet, rpc: Add listrawtransactions RPC

    parent_descs field is included in the documentation but it never can appear. TransactionDescriptionString adds parent_descs field, but as we are using WalletTxToJSON() (doesn't call PushParentDescriptors()) it never can have parent_descs field.

    known.
        "parent_descs" : [                (json array, optional) List of parent descriptors for the output script of this coin.
          "str",                          (string) The descriptor string.
          ...
        ],
    
    

    pablomartin4btc commented at 6:36 PM on August 27, 2026:

    Done in 978fac7a586395b770f609756ef591c758b76254, as a prep commit since I had to touch TransactionDescriptionString, thanks!

  72. in src/wallet/rpc/transactions.cpp:429 in 304596b308
     424 | + * @param  wallet         The wallet.
     425 | + * @param  wtx            The wallet transaction.
     426 | + * @param  ret            Output vector to append the entry to.
     427 | + * @param  verbose        If true, include a decoded transaction object.
     428 | + */
     429 | +template <class Vec>
    


    polespinasa commented at 7:32 AM on August 27, 2026:

    in 304596b wallet, rpc: Add listrawtransactions RPC

    This template is not necessary as the function is only called with vector<UniValue>. Fixed also in this suggestion https://github.com/bitcoin/bitcoin/pull/35813/changes/304596b3083b100df34f53ae543be50c70135ca5#r3869588711


    pablomartin4btc commented at 6:32 PM on August 27, 2026:

    Good catch! Originally matching listtransactions RPC , but ListTransactions is called from several places with different types: only listtransactions passes std::vector<UniValue>listsinceblock (transactions/ removed) and gettransaction (details) all pass a plain UniValue (UniValue::VARR).

    Done in 2f302a90f2acd8c8a166dcca6e446ff810569970, thanks!

  73. DrahtBot requested review from polespinasa on Aug 27, 2026
  74. wallet, rpc: allow omitting parent_descs from TransactionDescriptionString
    TransactionDescriptionString() unconditionally documents a parent_descs
    field, which is only ever populated by ListTransactions()'s per-output
    "received" branch via PushParentDescriptors(). Add an
    include_parent_descs parameter (default true, preserving current
    behavior for listtransactions/listsinceblock/gettransaction) so a
    caller whose entries don't go through that code path can correctly
    omit it instead of documenting a field it can never produce.
    
    This is in preparation for the upcoming listrawtransactions RPC, whose
    entries are built via WalletTxToJSON rather than ListTransactions and
    so can never populate parent_descs.
    978fac7a58
  75. pablomartin4btc force-pushed on Aug 27, 2026
  76. pablomartin4btc commented at 6:50 PM on August 27, 2026: member

    <ins>Updates</ins>:

    • Addressed @polespinasa's latest feedback:
      • Vec template on ListRawTransaction: dropped it — only ever instantiated with std::vector<UniValue>, unlike ListTransactions which genuinely needs it (listsinceblock/ gettransaction pass a plain UniValue). Done in 2f302a9.
      • parent_descs: confirmed a real doc bug — it was documented as a possible field on listrawtransactions but can never appear, since it's only populated by ListTransactions's per-output "received" branch, which this RPC never goes through. Added an include_parent_descs param to TransactionDescriptionString (default true, preserving current behaviour elsewhere), landed as its own prep commit (978fac7a5) since it touches pre-existing shared code.
      • Lambda suggestion for ListRawTransaction: kept it as a named free function instead — explained the reasoning in the thread (consistency with ListTransactions/ PushTxAmountAndFee/ PushTxDecoded, the doc comment attaching more naturally to a named function, and keeping the RPC lambda's body focused purely on pagination).
  77. in src/wallet/rpc/transactions.cpp:675 in 2f302a90f2
     670 | +            ListRawTransaction(*pwallet, *pwtx, ret, verbose);
     671 | +        }
     672 | +    }
     673 | +
     674 | +    UniValue result{UniValue::VARR};
     675 | +    result.push_backV(ret.rbegin(), ret.rend());
    


    polespinasa commented at 9:50 AM on August 28, 2026:

    in 2f302a90f2acd8c8a166dcca6e446ff810569970 wallet, rpc: Add listrawtransactions RPC

    nit: this could avoid an unnecessary copy by using std::make_move_iterator, similar to what listtransactions uses:

    $ git diff
    diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
    index 3ffc51bc5a..21fed23bbc 100644
    --- a/src/wallet/rpc/transactions.cpp
    +++ b/src/wallet/rpc/transactions.cpp
    @@ -672,7 +672,7 @@ RPCMethod listrawtransactions()
         }
     
         UniValue result{UniValue::VARR};
    -    result.push_backV(ret.rbegin(), ret.rend());
    +    result.push_backV(std::make_move_iterator(ret.rbegin()), std::make_move_iterator(ret.rend()));
         return result;
     },
         };
    

    pablomartin4btc commented at 2:02 PM on August 28, 2026:

    Thanks, I'll take it — I think it's worth fixing now, especially since listtransactions already uses the same mechanism.


    pablomartin4btc commented at 2:07 PM on August 28, 2026:

    Done, thanks!

  78. polespinasa approved
  79. polespinasa commented at 9:52 AM on August 28, 2026: member

    ACK c08e90f07a033827f75274e4252f9f392aeb7697

    Thanks for addressing feedback, lgtm now :)

    Just left a small nit, feel free to ignore.

  80. DrahtBot requested review from w0xlt on Aug 28, 2026
  81. wallet, rpc: Add listrawtransactions RPC
    Add a new `listrawtransactions` RPC that returns up to `count` most
    recent wallet transactions (newest-first, then reversed to oldest-first
    in the result), skipping the first `skip` entries.
    
    Unlike `listtransactions`, each wallet transaction appears exactly once
    with its net balance change, with no category assignment and no change
    suppression. This makes consolidation and self-transfer transactions
    that are invisible in `listtransactions` visible here.
    
    Params: count (default 10), skip (default 0), verbose (default false).
    When verbose=true, a `decoded` field is included (equivalent to
    decoderawtransaction).
    024f8eae00
  82. test: Add functional test for listrawtransactions
    Covers:
    - Each transaction appears exactly once (unlike listtransactions which
      shows one entry per output)
    - Coinbase transactions appear with generated=true and no category field
    - Consolidation transactions (all inputs/outputs wallet-owned) are
      visible, unlike in listtransactions
    - count and skip pagination
    - verbose=true includes hex always and a decoded field; verbose=false
      omits decoded
    4cc29809df
  83. doc: Add release notes for listrawtransactions 0e539c51e0
  84. pablomartin4btc force-pushed on Aug 28, 2026
  85. pablomartin4btc commented at 2:08 PM on August 28, 2026: member

    -<ins>Updates</ins>:

    • Addressed @polespinasa's nit:
      • listrawtransactions: use std::make_move_iterator when pushing the result vector, avoiding an unnecessary copy — matching the same pattern already used by listtransactions.
  86. polespinasa commented at 2:10 PM on August 28, 2026: member

    ACK 0e539c51e096db9c6787f00957b20d418f46bb04


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-09 07:56 UTC