Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2026-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or https://www.opensource.org/licenses/mit-license.php.5#include <wallet/export.h>7#include <key_io.h>8#include <util/fs.h>9#include <util/expected.h>10#include <wallet/scriptpubkeyman.h>11#include <wallet/context.h>12#include <wallet/sqlite.h>13#include <wallet/wallet.h>15#include <fstream>17namespace wallet {18util::Expected<std::vector<WalletDescInfo>, std::string> ExportDescriptors(const CWallet& wallet, bool export_private)19{20AssertLockHeld(wallet.cs_wallet);21std::vector<WalletDescInfo> wallet_descriptors;22for (const auto& spk_man : wallet.GetAllScriptPubKeyMans()) {23const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);24if (!desc_spk_man) {25return util::Unexpected{"Unexpected ScriptPubKey manager type."};26}27LOCK(desc_spk_man->cs_desc_man);28const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();29std::string descriptor;30if (!Assume(desc_spk_man->GetDescriptorString(descriptor, export_private))) {31return util::Unexpected{"Can't get descriptor string."};32}33const bool is_range = wallet_descriptor.descriptor->IsRange();34wallet_descriptors.emplace_back(35descriptor,36wallet_descriptor.creation_time,37wallet.IsActiveScriptPubKeyMan(*desc_spk_man),38wallet.IsInternalScriptPubKeyMan(desc_spk_man),39is_range ? std::optional(std::make_pair(wallet_descriptor.GetStart(), wallet_descriptor.GetEnd())) : std::nullopt,40wallet_descriptor.GetNext(),41wallet_descriptor.cache42);43}44return wallet_descriptors;45}47util::Result<std::string> ExportWatchOnlyWallet(const CWallet& wallet, const fs::path& destination, WalletContext& context)48{49AssertLockHeld(wallet.cs_wallet);51if (destination.empty()) {52return util::Error{_("Error: Export destination cannot be empty")};53}54if (fs::exists(destination)) {55return util::Error{strprintf(_("Error: Export destination '%s' already exists"), fs::PathToString(destination))};56}57if (!std::ofstream{fs::PathToString(destination)}) {58return util::Error{strprintf(_("Error: Could not create file '%s'"), fs::PathToString(destination))};59}60bool success = false;61auto cleanup_destination = interfaces::MakeCleanupHandler([&success, &destination] {62if (!success) fs::remove(destination);63});65// Get the descriptors from this wallet66util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(wallet, /*export_private=*/false);67if (!exported) {68return util::Error{Untranslated(exported.error())};69}70if (exported->empty()) {71return util::Error{_("Error: Wallet has no descriptors to export")};72}74// Make the wallet with the same flags as this wallet, but without private keys75const uint64_t create_flags = wallet.GetWalletFlags() | WALLET_FLAG_DISABLE_PRIVATE_KEYS;77// Create the temporary watchonly wallet in memory to avoid leaving files on disk78std::vector<bilingual_str> warnings;79bilingual_str error;80WalletContext empty_context;81empty_context.args = context.args;82std::shared_ptr<CWallet> watchonly_wallet = CWallet::CreateNew(empty_context, /*name=*/wallet.GetName() + "_watchonly_temp", MakeInMemoryWalletDatabase(), create_flags, /*born_encrypted=*/false, error, warnings);83if (!watchonly_wallet) {84return util::Error{strprintf(_("Error: Failed to create new watchonly wallet. %s"), error)};85}87{88LOCK(watchonly_wallet->cs_wallet);90// Parse the descriptors and add them to the new wallet91for (const WalletDescInfo& desc_info : *Assert(exported)) {92// Parse the descriptor93FlatSigningProvider dummy_keys;94std::string dummy_err;95std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_info.descriptor, dummy_keys, dummy_err, /*require_checksum=*/true);96CHECK_NONFATAL(descs.size() == 1); // All of our descriptors should be valid, and not multipath97CHECK_NONFATAL(dummy_keys.keys.size() == 0); // No private keys should be present in our exported descriptors99// Get the range if there is one100int32_t range_start = 0;101int32_t range_end = 0;102if (desc_info.range) {103range_start = desc_info.range->first;104range_end = desc_info.range->second;105}107WalletDescriptor w_desc(std::move(descs.at(0)), desc_info.creation_time, range_start, range_end, desc_info.next_index);109// For descriptors that cannot self expand (i.e. needs private keys or cache), set the cache110if (!w_desc.descriptor->CanSelfExpand()) {111w_desc.cache = desc_info.cache;112}114// Add to the watchonly wallet115auto spkm_res = watchonly_wallet->AddWalletDescriptor(w_desc, dummy_keys, /*label=*/"", /*internal=*/false);116if (!spkm_res) {117return util::Error{util::ErrorString(spkm_res)};118}120// Set active spkms as active121if (desc_info.active) {122// Determine whether this descriptor is internal123// This is only set for active spkms124bool internal = false;125if (desc_info.internal) {126internal = *desc_info.internal;127}128watchonly_wallet->AddActiveScriptPubKeyMan(spkm_res->get().GetID(), *Assert(w_desc.descriptor->GetOutputType()), internal);129}130}132// Copy locked coins that are persisted133for (const auto& [coin, persisted] : wallet.m_locked_coins) {134if (!persisted) continue;135watchonly_wallet->LockCoin(coin, persisted);136}138{139// Make a WalletBatch for the watchonly wallet so that everything else can be written atomically140WalletBatch watchonly_batch(watchonly_wallet->GetDatabase());141if (!watchonly_batch.TxnBegin()) {142return util::Error{strprintf(_("Error: database transaction cannot be executed for new watchonly wallet %s"), watchonly_wallet->GetName())};143}145// Copy orderPosNext146watchonly_batch.WriteOrderPosNext(wallet.nOrderPosNext);148// Write the best block locator to avoid rescanning on reload149CBlockLocator best_block_locator;150{151WalletBatch local_wallet_batch(wallet.GetDatabase());152if (!local_wallet_batch.ReadBestBlock(best_block_locator)) {153return util::Error{_("Error: Unable to read wallet's best block locator record")};154}155}156if (!watchonly_batch.WriteBestBlock(best_block_locator)) {157return util::Error{_("Error: Unable to write watchonly wallet best block locator record")};158}160// Copy the transactions161for (const auto& [txid, wtx] : wallet.mapWallet) {162DataStream wtx_ser;163wtx_ser << wtx;164CWalletTx copy_wtx(deserialize, wtx_ser, wtx.GetTxs());165if (!watchonly_wallet->LoadToWallet(std::move(copy_wtx))) {166return util::Error{strprintf(_("Error: Could not add tx %s to watchonly wallet"), txid.GetHex())};167}168watchonly_batch.WriteFullTx(watchonly_wallet->mapWallet.at(txid));169}171// Copy address book172for (const auto& [dest, entry] : wallet.m_address_book) {173auto address{EncodeDestination(dest)};174if (entry.purpose) watchonly_batch.WritePurpose(address, PurposeToString(*entry.purpose));175if (entry.label) watchonly_batch.WriteName(address, *entry.label);176for (const auto& [id, request] : entry.receive_requests) {177watchonly_batch.WriteAddressReceiveRequest(dest, id, request);178}179if (entry.previously_spent) watchonly_batch.WriteAddressPreviouslySpent(dest, true);180}182if (!watchonly_batch.TxnCommit()) {183return util::Error{_("Error: cannot commit db transaction for watchonly wallet export")};184}185}187// Make a backup of this wallet at the specified destination directory188if (!watchonly_wallet->BackupWallet(fs::PathToString(destination))) {189return util::Error{_("Error: Unable to write the exported wallet")};190}191success = true;192}194return fs::PathToString(destination);195}196} // namespace wallet