Skip to content

btcwbackend: add neutrino+btcwallet wallet mode - #228

Merged
Roasbeef merged 11 commits into
mainfrom
btwwallet-neutrino
Apr 1, 2026
Merged

btcwbackend: add neutrino+btcwallet wallet mode#228
Roasbeef merged 11 commits into
mainfrom
btwwallet-neutrino

Conversation

@Roasbeef

Copy link
Copy Markdown
Member

This PR introduces a third wallet backend mode (wallet.type=btcwallet) that
uses btcwallet with neutrino (BIP 157/158 compact block filters) as the chain
source. This provides a fully self-contained, SPV-based wallet that connects
directly to the Bitcoin P2P network—no external Esplora server or lnd node
required.

Motivation

The existing wallet backends each carry an external dependency: lnd mode
requires a full lnd node, and lwwallet mode requires a running Esplora/
mempool.space instance. For users who want a lightweight, zero-dependency
setup, neutrino gives us BIP 157/158 compact block filter support over the raw
P2P network, which is exactly what lnd itself uses in its lightest
configuration.

Design

The new btcwbackend/ package implements the same three interfaces as the
existing backends:

  • chainsource.ChainBackend — Uses lnd's NeutrinoNotifier for
    event-driven confirmation/spend/block notifications (no polling). Fee
    estimation is handled by chainfee.WebAPIEstimator with a configurable fee
    URL, since neutrino has no mempool visibility.

  • wallet.BoardingBackend — Wraps btcwallet for key derivation and
    taproot script import. Unlike the lwwallet adapter (which bypasses btcwallet
    and queries Esplora directly for non-default scope UTXOs), this adapter uses
    btcwallet's native ListUnspent—after ImportTaprootScript, neutrino's
    compact block filter matching naturally detects matching outputs during sync.

  • round.ClientWallet — The Wallet struct embeds input.Signer from
    btcwallet and exposes DeriveNextKey, satisfying the interface directly
    (same pattern as lwwallet).

The daemon wiring in darepod/ adds WalletTypeBtcwallet arms to all six
wallet-type switch sites, plus a tryAutoUnlockBtcwallet function that reuses
the existing seed management and wallet unlock flow.

Configuration

--wallet.type=btcwallet
--wallet.btcwallet_peers=host:port      # exclusive peers (no DNS seeding)
--wallet.btcwallet_addpeers=host:port   # additional persistent peers
--wallet.btcwallet_datadir=/path        # neutrino data dir
--wallet.feeurl=https://...             # fee estimation endpoint (required on mainnet)
--wallet.persist_filters=true           # write cfilters to disk

Closes #149.

Test plan

  • go build ./btcwbackend/... ./darepod/... compiles cleanly
  • make lint-changed-local passes with zero issues
  • Unit tests for btcwbackend (TODO: follow-up PR)
  • Integration test on regtest with bitcoind peer (TODO: follow-up PR)
  • Manual smoke test: start darepod with --wallet.type=btcwallet on
    signet, verify chain sync and wallet operations

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new wallet backend, btcwbackend, which integrates btcwallet with neutrino to provide a self-contained SPV wallet implementation. The new backend includes a boarding adapter for Ark protocol participation and a chain backend for event-driven notifications. Key feedback points out a potential nil pointer dereference in the server initialization when the wallet is not auto-unlocked, a logic inconsistency in GetTransaction where the implemented fallback behavior does not match the documentation, and a resource lifecycle issue in RegisterBlocks caused by using a potentially short-lived caller context for a background subscription.

Comment thread darepod/server.go Outdated
Comment on lines +1017 to +1031
case WalletTypeBtcwallet:
// If the btcwallet is already started (auto-unlock
// succeeded), use its chain backend.
if s.btcwWallet.IsSome() {
w := s.btcwWallet.UnsafeFromSome()
s.chainBackend = w.ChainBackend()
alreadyStarted = true
}

// Unlike lwwallet, we cannot create a standalone
// neutrino chain backend without the wallet since
// neutrino requires the full service to be running.
// If the wallet is not yet started, the chain backend
// will be initialized when startBtcwallet is called.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This case fails to handle the scenario where the wallet is not yet started (e.g., when auto-unlock fails). Unlike the WalletTypeLwwallet case, s.chainBackend is not assigned a value here if s.btcwWallet.IsNone(). This will cause a nil pointer dereference and panic later in the function when s.chainBackend.Start() is called (on line 1038). Since the daemon is designed to support a locked wallet state for this backend, this function must ensure s.chainBackend is initialized or handle the deferred initialization correctly to avoid crashing the daemon on startup.

Comment thread btcwbackend/boarding_backend.go
Comment on lines +384 to +429
epochChan := make(chan *chainsource.BlockEpoch, 10)

go func() {
defer close(epochChan)
defer event.Cancel()

for {
select {
case lndEpoch, ok := <-event.Epochs:
if !ok {
return
}

if lndEpoch.Hash == nil {
continue
}

var timestamp int64
if lndEpoch.BlockHeader != nil {
ts := lndEpoch.BlockHeader.Timestamp
timestamp = ts.Unix()
}

epoch := &chainsource.BlockEpoch{
Hash: *lndEpoch.Hash,
Height: lndEpoch.Height,
Timestamp: timestamp,
}

select {
case epochChan <- epoch:

case <-ctx.Done():
return
}

case <-ctx.Done():
return
}
}
}()

return &chainsource.BlockRegistration{
Epochs: epochChan,
Cancel: event.Cancel,
}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The background goroutine in RegisterBlocks incorrectly uses the input ctx to control its lifetime. This is inconsistent with RegisterConf and RegisterSpend, which use a separate long-lived context. If the caller passes a short-lived context (e.g., from a gRPC request), the block subscription will be terminated prematurely when that context is cancelled, even if the registration is intended to remain active until the returned Cancel function is called.

References
  1. Follow consistent patterns for resource lifecycle management across similar methods in the same package. (link)
  2. Long-lived background tasks should use a dedicated context linked to their specific lifecycle rather than inheriting a potentially short-lived caller context.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7809b4db98

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread darepod/server.go
Comment on lines +1020 to +1024
if s.btcwWallet.IsSome() {
w := s.btcwWallet.UnsafeFromSome()
s.chainBackend = w.ChainBackend()
alreadyStarted = true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Initialize chain backend for locked btcwallet startup

When wallet.type=btcwallet starts without an already-open wallet (no env seed or no auto-unlock password), this branch leaves s.chainBackend unset, but initChainBackend still reaches the existing s.chainBackend.Start() call below. That causes a nil-interface dereference during daemon startup, so the process dies before InitWallet/UnlockWallet RPC can ever be used. Please either create a usable backend in this path or explicitly defer chain-source initialization until startBtcwallet has populated s.chainBackend.

Useful? React with 👍 / 👎.

Comment thread btcwbackend/boarding_backend.go Outdated
maxConfs = math.MaxInt32
}

addrs := b.SnapshotAddrs()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed, instead returning all the addresses. On restart, we don't re-populate this, so it can cause us to miss outputs.

@Roasbeef

Copy link
Copy Markdown
Member Author

@claude review this

@claude

claude Bot commented Mar 31, 2026

Copy link
Copy Markdown

Claude finished @Roasbeef's task in 14m 51s —— View job


PR Review: btcwbackend — neutrino+btcwallet wallet mode

  • Gather context and understand request
  • Read new btcwbackend package files
  • Read modified darepod files
  • Read walletcore and lwwallet changes
  • Compile findings and post review

Overall the design is solid — the three-interface pattern is clean, the walletcore extraction is well done, and the lifecycle management (startOnce/stopOnce guards) is careful. There is one structural bug that will cause a panic in the locked-wallet startup path, plus a few medium issues.


Critical

1. Nil chain backend panics when wallet is locked at startup
darepod/server.go:450–456

When WalletTypeBtcwallet starts without an auto-unlocked wallet (no env seed / no auto-unlock password), initChainBackend returns nil (early return at line 1054) but leaves s.chainBackend == nil. The very next lines then do:

chainActor := chainsource.NewChainSourceActor(
    chainsource.ChainSourceConfig{
        Backend: s.chainBackend, // nil here!
        ...
    },
)
chainSourceRef := actor.RegisterWithSystem(...)

The actor captures the nil Backend value at construction time. Later, when InitWallet/UnlockWallet calls startBtcwallet and sets s.chainBackend, the already-registered actor still holds nil. When markWalletReady fires and startWalletDependentActors passes chainSourceRef to the wallet/round actors, the first chain notification registration will dereference nil and panic.

The lwwallet case avoids this by creating a standalone lwwallet.NewChainBackend (Esplora only, no wallet needed). Neutrino doesn't have that option since the chain service requires the wallet.

The fix needs to either:

  • Defer chain actor registration until startBtcwallet runs (pass a chainSourceRef creator into the deferred goroutine), or
  • Use a lazy/gatekeeping wrapper backend that blocks until the real backend is injected

Fix this →


Medium

2. Address filter in ListUnspent causes UTXO loss after restart
btcwbackend/boarding_backend.go:72–92

This is also flagged by @Roasbeef. SnapshotAddrs() returns only addresses that have been imported into the in-memory map during the current process lifetime. On daemon restart, ImportedAddrs starts empty — the wallet actor re-imports addresses via handleStartupRecovery, but until that completes, ListUnspent will silently return nothing, potentially causing the wallet actor to think there are no UTXOs.

Removing the filter (return all UTXOs from btcwallet's ListUnspent) is the safe path — btcwallet already scopes to watched scripts, so the address filter is redundant.

3. GetTransaction docstring says "falls back to neutrino P2P fetch" but doesn't
btcwbackend/boarding_backend.go:146–163

The function comment says it fetches from the containing block via neutrino if the tx isn't in the wallet store, but the implementation returns an error immediately:

tx, err := b.BtcWallet.FetchTx(txid)
if err != nil || tx == nil {
    return nil, nil, fmt.Errorf("transaction %s not found in wallet", txid)
}

Either implement the neutrino P2P fallback (using b.neutrinoCS.GetBlock after scanning for the block containing the txid) or update the docstring to reflect the actual behaviour.

Fix this →

4. No validation that FeeURL is set on mainnet
btcwbackend/config.go / darepod/config.go

The PR description notes that a fee URL is "required on mainnet". There is no config validation enforcing this. Starting on mainnet without --wallet.feeurl will spin up successfully but then fail at runtime when any fee estimation is requested. A startup check in Config.Validate() (or in startBtcwallet) would give a clear error early.


Low / Nits

5. Gemini's RegisterBlocks context concern is already addressed

The comment at chain_backend.go:427 already uses context.Background() for notifyCtx, consistent with RegisterConf and RegisterSpend. The Gemini reviewer's concern doesn't apply to the current code.

6. btcw.Stop() called before btcw.Start() in New() error path
btcwbackend/wallet.go:109

In the NewChainBackend error branch, btcw.Stop() is called on a btcwallet that was never started. lnd's BtcWallet.Stop() is safe to call in this state (idempotent), so this is unlikely to cause issues in practice, but it's confusing.

7. WalletPassphrase name is misleading post-extraction
walletcore/config.go:16

var WalletPassphrase = []byte("lwwallet")

Now shared between lwwallet and btcwbackend, but still named/valued "lwwallet". Changing the value would break existing wallets, but a constant rename (DefaultInternalPassphrase or similar) would clarify intent without a breaking change.

8. NeutrinoService.Stop() silently drops cs.Stop() errors
btcwbackend/neutrino.go:124–129

Neutrino stop errors are logged at Warn but not returned, while the DB close error is returned. This asymmetry is acceptable (neutrino stop is often called in already-stopped state), but a comment explaining why would help future readers.

9. No unit or integration tests

PR acknowledges this. Follow-up PRs should include at least:

  • A unit test for BoardingBackendAdapter.ListUnspent with a mock btcwallet
  • A unit test for ChainBackend lifecycle (start/stop idempotency)
  • Integration test against regtest per the test plan

Summary

Severity Issue File
🔴 Critical Nil chain backend → panic when wallet locked at startup darepod/server.go:454
🟠 Medium Address filter drops UTXOs on restart btcwbackend/boarding_backend.go:72
🟠 Medium GetTransaction docstring misleads btcwbackend/boarding_backend.go:146
🟠 Medium No fee URL validation on mainnet btcwbackend/config.go
🟡 Low btcw.Stop() before btcw.Start() btcwbackend/wallet.go:109
🟡 Low WalletPassphrase naming walletcore/config.go:16

@Roasbeef

Copy link
Copy Markdown
Member Author

@claude Re the critical finding: the nil s.chainBackend passed to ChainSourceActor is not actually reachable in practice. The walletReady channel gates all wallet-dependent actors (startWalletDependentActors is deferred until markWalletReady), so no messages reach the chain source actor before the wallet is unlocked and startBtcwallet sets s.chainBackend. This mirrors how lwwallet handles the locked case. Even though lwwallet creates a standalone chain backend, nothing queries it before wallet-dependent actors start.

The address filter removal in ListUnspent (item #2) is addressed in the latest fixup. We now return all watched UTXOs from btcwallet directly without filtering by the in-memory importedAddrs map, since btcwallet already scopes to watched scripts.

GetTransaction docstring is also cleaned up in the same fixup.

FeeURL validation was already required unconditionally for btcwallet mode in an earlier commit.

@claude

claude Bot commented Mar 31, 2026

Copy link
Copy Markdown

Claude finished @Roasbeef's task in 2m 9s —— View job


Claude finished @Roasbeef's task in 14m 51s —— View job


Thanks for the follow-up. Verified each point against the current code:

  • Verify walletReady gating claim (critical finding)
  • Verify address filter removal fix
  • Verify GetTransaction docstring fix
  • Verify FeeURL validation

Critical finding — still a real bug

The gating argument is correct about message timing: nothing sends messages to the chain source actor before walletReady fires. But the panic isn't about early message arrival — it's about what the actor's internal Backend field holds when those messages do arrive.

chainsource/chainsource.go:54ChainSourceActor stores cfg ChainSourceConfig by value:

type ChainSourceActor struct {
    cfg ChainSourceConfig  // Backend field captured at construction
}

When NewChainSourceActor is called at darepod/server.go:454, Backend: s.chainBackend is nil for the non-auto-unlocked btcwallet path. That nil value is copied into the actor's cfg and stays nil forever — startBtcwallet updates s.chainBackend (the server field) but the actor's cfg.Backend is a separate copy.

So after unlock:

  1. startBtcwallet sets s.chainBackend = w.ChainBackend()
  2. markWalletReady() fires, goroutine wakes, calls startWalletDependentActors
  3. Wallet actors send messages to chainSourceRef
  4. Actor calls a.cfg.Backend.EstimateFee(...) on a nil interface → panic

The lwwallet comparison doesn't apply here: for lwwallet, s.chainBackend is always set to a real backend (either the wallet's or the standalone Esplora one) before the actor is constructed. For btcwallet, the actor is constructed with nil and has no mechanism to pick up the later assignment.

The minimal fix is to defer both construction and registration of the chain source actor into the goroutine that waits on walletReady, passing the already-set s.chainBackend at that point. Or alternatively, a thin lazyBackend wrapper that holds a *chainsource.ChainBackend pointer could be constructed up front and populated in startBtcwallet.


Items confirmed fixed in the latest fixup

Address filter removal (btcwbackend/boarding_backend.go:77): confirmed. ListUnspent now calls b.BtcWallet.InternalWallet().ListUnspent(minConfs, maxConfs, "") with empty account string — no importedAddrs filtering.

GetTransaction docstring (btcwbackend/boarding_backend.go:145): confirmed clean. The comment now just says "fetches from btcwallet's transaction store, which is populated by neutrino's chain sync" — no phantom P2P fallback language.

FeeURL validation (darepod/config.go:324–330): confirmed unconditional for WalletTypeBtcwallet:

case WalletTypeBtcwallet:
    if c.Wallet.FeeURL == "" {
        return fmt.Errorf("wallet.feeurl is required " +
            "when wallet.type is btcwallet")
    }

No mainnet-only condition — required in all networks. Good.


@Roasbeef
Roasbeef requested a review from sputn1ck March 31, 2026 23:46

@sputn1ck sputn1ck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Roasbeef and others added 10 commits April 1, 2026 14:09
In this commit, we introduce the btcwbackend package which provides a
fully self-contained, in-process Bitcoin wallet backed by btcwallet
with neutrino (BIP 157/158 compact block filters) as the chain
backend. This gives users a lightweight SPV option that connects
directly to the Bitcoin P2P network without requiring an external
Esplora server or LND node.

The package implements all three core interfaces needed by the daemon:

  - chainsource.ChainBackend via NeutrinoNotifier for event-driven
    confirmation, spend, and block notifications, with
    chainfee.WebAPIEstimator for fee estimation (since neutrino has
    no mempool visibility).

  - wallet.BoardingBackend via BoardingBackendAdapter, which uses
    btcwallet's native ListUnspent for UTXO tracking after importing
    taproot scripts (unlike lwwallet which bypasses btcwallet and
    queries Esplora directly for non-default scope UTXOs).

  - round.ClientWallet via the Wallet struct which embeds
    input.Signer from btcwallet and exposes DeriveNextKey, mirroring
    the lwwallet approach.

The chain backend translates between lnd's chainntnfs event types
and the chainsource registration types using the same goroutine
forwarding pattern as chainbackends.LNDBackend.
Add WalletTypeBtcwallet constant ("btcwallet") as a third wallet
backend option alongside "lnd" and "lwwallet". The WalletConfig
struct gains fields for neutrino peer configuration (ConnectPeers,
AddPeers), data directory, fee estimation URL, and filter
persistence.

Config validation enforces that wallet.feeurl is set on mainnet
when using the btcwallet backend, since neutrino has no mempool
visibility for fee estimation.
Wire the new neutrino+btcwallet backend into all daemon switch sites
that branch on wallet type. The Server struct gains a btcwWallet
field alongside the existing lwWallet, and six switch sites now
handle WalletTypeBtcwallet:

  - Start(): calls tryAutoUnlockBtcwallet (same seed/password flow
    as lwwallet)
  - initChainBackend(): uses btcwbackend.ChainBackend
  - initWalletActor(): uses btcwbackend.BoardingBackend
  - initRoundActor(): uses btcwbackend.Wallet as ClientWallet
  - initVTXOManager(): uses btcwbackend.Wallet as VTXOWallet
  - initOORActor(): uses btcwbackend.Wallet as input.Signer

The GenSeed, InitWallet, and UnlockWallet RPCs now accept both
lwwallet and btcwallet modes via a new isSelfManagedWallet() helper
and startSelfManagedWallet() dispatcher that routes to the correct
wallet start function. The deriveIdentityPubkey helper is similarly
updated to derive from whichever self-managed wallet is active.
The btcwbackend package imports neutrino, walletdb, and kvdb
directly, so these move from indirect to direct in go.mod.
Fix all critical and high-severity findings identified during code
review:

  - C1: Fix nil chainBackend panic when btcwallet is not auto-unlocked
    at startup. The initChainBackend btcwallet case now returns early,
    deferring chain backend init to startBtcwallet.

  - H1: startBtcwallet now assigns s.chainBackend and starts it when
    the chain backend was deferred at startup.

  - H3: Add missing btcwallet case to oorReceiveKeyOps so OOR receive
    works with the neutrino backend.

  - H4: Require wallet.feeurl unconditionally for btcwallet mode
    (not just mainnet), since neutrino never has mempool visibility.

Medium and low fixes:

  - M1: Fix data race on importedAddrs length in log statement by
    capturing the count while holding the lock.
  - M3: Add WarnS log messages for silently skipped UTXOs in
    ListUnspent.
  - M5: Add btcwWallet.Stop() to server shutdown path to prevent
    neutrino resource leaks.
  - M6: Fix inaccurate GetTransaction GoDoc that described an
    unimplemented neutrino fallback.
  - M9: Clean up btcwallet in constructor error path when
    NewChainBackend fails.
  - L4: Change BestBlock log level from InfoS to DebugS.
  - L5: Wire logger through NewChainBackend constructor.
  - L12: Cache KeyRing on BoardingBackendAdapter construction
    instead of allocating per DeriveNextKey call.
  - H2: Document that importedAddrs is repopulated on restart by
    the wallet actor's handleStartupRecovery.
Fix critical lifecycle bugs and goroutine safety issues identified
in the second review pass:

  - C1/C2: Make ChainBackend.Start()/Stop() idempotent using
    sync.Once, eliminating double-start and double-stop panics
    when both Wallet and daemon call lifecycle methods.

  - H1: Use walletdb.Open with Create fallback for neutrino DB
    so daemon restart doesn't fail on existing database files.

  - H2: Fix RegisterBlocks to use independent notifyCtx instead
    of caller's context for goroutine lifecycle, matching the
    pattern already used by RegisterConf and RegisterSpend.

  - H3: Wrap channel sends in RegisterConf and RegisterSpend
    goroutines with select on notifyCtx.Done() to prevent
    blocking when callers abandon registrations.

  - H7: Document correct shutdown ordering (btcwallet before
    neutrino service) in Wallet.Stop().
Refactor the duplicated btcwallet wrapping code into a new
walletcore package. Both lwwallet (Esplora-backed) and btcwbackend
(neutrino-backed) now embed walletcore.Wallet and
walletcore.BoardingBackendBase instead of duplicating HD key
management, signing, address generation, balance queries, and
boarding address tracking.

The walletcore package provides:

  - Wallet: embeds input.Signer, holds BtcWallet/KeyRing/ChainParams,
    and exposes DeriveNextKey, DeriveKey, NewAddress, Balance,
    ListUnspentWitness, ConfirmedBalance, InternalWallet.

  - BoardingBackendBase: holds BtcWallet/KeyRing/ChainKeyScope/
    ImportedAddrs and provides DeriveNextKey, ImportTaprootScript,
    SnapshotAddrs. Chain-specific adapters embed this and implement
    ListUnspent, GetTransaction, GetBlock.

  - Config: base fields (Seed, ChainParams, RecoveryWindow, DBDir,
    Log) that btcwbackend.Config embeds.

  - CoinTypeForNet, WalletPassphrase, DefaultBlockCacheSize:
    shared constants/helpers formerly duplicated in both packages.

This eliminates ~300 lines of duplicated wallet methods and ensures
both backends stay in sync for shared functionality.
Fix walletdb.Open and walletdb.Create calls that were missing the
required readOnly bool parameter added in a recent btcwallet update.
Add open-then-create fallback for the height hint cache DB so the
chain backend works on first run.

Expose bitcoind's P2P port (18444) from the Docker harness so
neutrino-backed test clients can sync headers and compact block
filters directly from the regtest node.
@sputn1ck
sputn1ck force-pushed the btwwallet-neutrino branch from da6da82 to 63a5f57 Compare April 1, 2026 12:10
Enable compact block filter serving on the bitcoind harness
container (-blockfilterindex=1, -peerblockfilters=1, -listen=1,
-bind=0.0.0.0:18444) so neutrino clients can sync headers and
cfilters via the P2P protocol.

Add a background sync wait in startBtcwallet that polls
BestBlock until neutrino reports a non-zero height before marking
the wallet ready. This prevents the daemon from accepting
boarding requests before the chain backend can detect
confirmations.
@Roasbeef
Roasbeef merged commit 6a5317f into main Apr 1, 2026
16 checks passed
ellemouton pushed a commit that referenced this pull request May 22, 2026
db: preserve context cancellation in mailbox pull
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

darepod: add neutrino+btcwallet mode (wallet.type=btcwallet)

2 participants