wallet, rpc: Add listrawtransactions RPC #35813

pull pablomartin4btc wants to merge 3 commits into bitcoin:master from pablomartin4btc:wallet/listrawtransactions changing 6 files +314 −3
  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
    Concept ACK w0xlt
    Stale ACK achow101, Bicaru20

    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)

    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(1, 0, True) in test/functional/wallet_listrawtransactions.py

    <sup>2026-08-20 23:45:06</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. 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).
    1fa34d1057
  52. 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
    e6a746af1a
  53. doc: Add release notes for listrawtransactions f7f4bf67f7
  54. pablomartin4btc force-pushed on Aug 20, 2026
  55. 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.

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-08-21 17:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me