feat: bdk rust bindings#566
Conversation
WalkthroughAdds a new Rust "bdk" crate with UniFFI exports and generated C/Go bindings, integrates a BDK-backed Bitcoin wallet into the Go server and tests, expands Makefile build targets to build BDK and generate bindings, exposes FullScan across wallets and mocks, and excludes generated bindings from golangci-lint. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Server
participant GoBDK as "Go bdk binding"
participant RustBDK as "Rust bdk crate"
participant DB as "SQLite"
participant Electrum as "Electrum"
Server->>GoBDK: NewBackend(network, electrumOpts, dbPath)
GoBDK->>RustBDK: FFI call -> construct Backend
RustBDK->>DB: open/create DB
RustBDK->>Electrum: configure Electrum client(s)
RustBDK-->>GoBDK: Backend handle
GoBDK-->>Server: Backend object
sequenceDiagram
autonumber
actor App
participant WalletGo as "Go Wallet wrapper"
participant GoBDK as "Go bdk binding"
participant RustBDK as "Rust bdk crate"
participant DB as "SQLite"
participant Electrum as "Electrum"
App->>WalletGo: SendToAddress(addr, amt, feeRate, sendAll)
WalletGo->>GoBDK: SendToAddress FFI
GoBDK->>RustBDK: invoke Rust wallet send
RustBDK->>DB: persist PSBT / wallet state
RustBDK->>Electrum: broadcast / sync
RustBDK-->>GoBDK: WalletSendResult(tx_hex, fee, send_amount)
GoBDK-->>WalletGo: result
WalletGo-->>App: tx info
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Files/areas to inspect closely:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
bf9f869 to
024c055
Compare
70ad6a8 to
a599213
Compare
ab40d08 to
150c9c1
Compare
150c9c1 to
bfecbf2
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
bdk/src/wallet.rs (2)
224-230: Use real timestamp for unconfirmed tx applicationPassing 0 loses useful recency info used by policies/UX. Use current UNIX time.
pub fn apply_transaction(&self, tx_hex: String) -> Result<(), Error> { let mut wallet = self.get_wallet()?; let tx: Transaction = deserialize_hex(tx_hex.as_str()).context("deserialize tx")?; - wallet.apply_unconfirmed_txs(vec![(Arc::new(tx), 0)]); + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + wallet.apply_unconfirmed_txs(vec![(Arc::new(tx), now)]); self.persist(&mut wallet)?; Ok(()) }Please confirm apply_unconfirmed_txs expects seconds as u64 in your bdk_wallet version. Based on learnings
211-222: Use incremental sync in normal operationfull_scan() every sync is expensive; prefer sync() for steady-state and full_scan() only on restore/import.
If bdk_electrum::BdkElectrumClient::sync() is available, consider:
let request = { let wallet = self.get_wallet()?; wallet.start_sync() }; let update = self.backend.electrum.sync(request).context("sync")?;Based on learnings
bdk/src/types.rs (2)
38-68: Consider deriving Clone for FFI recordsCloning these lightweight records often simplifies UniFFI consumers.
-#[derive(uniffi::Record)] +#[derive(uniffi::Record, Clone)] pub struct Balance { ... } -#[derive(uniffi::Record)] +#[derive(uniffi::Record, Clone)] pub struct WalletTransaction { ... } -#[derive(uniffi::Record)] +#[derive(uniffi::Record, Clone)] pub struct WalletTransactionOutput { ... } -#[derive(uniffi::Record)] +#[derive(uniffi::Record, Clone)] pub struct WalletSendResult { ... }
3-19: Error wrapping is fine; optional: add From<PoisonError<_>>You already map poisoned mutexes at call sites; adding a blanket From improves consistency.
impl<T> From<std::sync::PoisonError<T>> for Error { fn from(e: std::sync::PoisonError<T>) -> Self { Error::Generic(e.to_string()) } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bdk/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
.golangci.yml(1 hunks)Makefile(2 hunks)bdk/Cargo.toml(1 hunks)bdk/src/lib.rs(1 hunks)bdk/src/types.rs(1 hunks)bdk/src/wallet.rs(1 hunks)bdk/uniffi-bindgen.rs(1 hunks)
🔇 Additional comments (10)
.golangci.yml (1)
6-6: LGTM!The exclusion path for BDK bindings mirrors the existing pattern for LWK and appropriately excludes generated bindings from linting.
Makefile (3)
57-58: LGTM!The
bindgen-gotarget correctly installs the uniffi-bindgen-go tool with the appropriate version tag matching the uniffi dependency version (0.28.3) used in the BDK crate.
64-66: LGTM!The
bdk-bindingstarget follows the same pattern aslwk-bindingsand correctly generates Go bindings for the BDK library.
113-113: LGTM!The BDK_BINDINGS_PATH variable is well-defined and aligns with the repository structure.
bdk/Cargo.toml (4)
1-5: LGTM!Package metadata is well-defined with a standard edition (2021) and appropriate versioning for an initial release.
6-12: LGTM!Dependency versions align with the latest stable releases documented in the retrieved learnings. The
bdk_walletfeatures (keys-bip39, file_store, std, rusqlite) provide a comprehensive wallet implementation with key management, persistent storage, and database support. The uniffi dependency with "cli" feature is appropriate for the FFI bindings workflow.Based on learnings.
14-15: LGTM!The uniffi build-dependency with the "build" feature is correctly configured for the build-time scaffolding generation required by uniffi's binding workflow.
17-19: LGTM!The library configuration correctly specifies multiple crate types (staticlib for C FFI, cdylib for dynamic linking, rlib for Rust) and sets the library name to "bdk", which cargo will compile to
libbdk.{a,so,dylib}.bdk/src/lib.rs (1)
1-4: LGTM!The crate root is cleanly structured with public module declarations for the wallet API surface and uniffi scaffolding setup. This minimal integration follows uniffi best practices for FFI binding generation.
Based on learnings.
bdk/uniffi-bindgen.rs (1)
1-3: LGTM!This minimal binary entry point correctly delegates to uniffi's binding generation workflow. The uniffi_bindgen_main() function handles all command-line parsing and error handling internally, making this simple wrapper appropriate for the build pipeline.
Based on learnings.
bfecbf2 to
36807d7
Compare
041d716 to
08242ec
Compare
52f3bbe to
51a1abf
Compare
08242ec to
667843d
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (7)
internal/onchain/wallet_test.go (1)
290-301: Remove or document commented-out test case.The commented-out Xpub test case should either be removed if it's no longer needed, or documented with a comment explaining why it's disabled and when it might be re-enabled.
internal/onchain/bitcoin-wallet/wallet.go (5)
47-55: Ensure DB path portability and directory creationUse filepath.Join and create DataDir before opening the SQLite file to avoid cross‑platform and runtime issues.
Apply this diff:
- backend, err := bdk.NewBackend( - convertNetwork(cfg.Network), - url, - cfg.DataDir+"/bdk.sqlite", - ) + // Ensure data dir exists and build a portable path + if err := os.MkdirAll(cfg.DataDir, 0o700); err != nil { + return nil, fmt.Errorf("ensure data dir: %w", err) + } + dbPath := filepath.Join(cfg.DataDir, "bdk.sqlite") + backend, err := bdk.NewBackend( + convertNetwork(cfg.Network), + url, + dbPath, + )Add imports:
import ( "errors" "fmt" "time" "github.com/BoltzExchange/boltz-client/v2/internal/onchain" "github.com/BoltzExchange/boltz-client/v2/internal/onchain/bitcoin-wallet/bdk" "github.com/BoltzExchange/boltz-client/v2/pkg/boltz" + "os" + "path/filepath" )
71-88: Make read‑only explicit when no mnemonicCurrently sets Readonly=false when mnemonic provided, but not explicitly true when absent. Clarify state to avoid ambiguity.
if credentials.Mnemonic != "" { creds.Mnemonic = &credentials.Mnemonic info.Readonly = false + } else { + info.Readonly = true }
90-92: Naming mismatch: returns an xpub, not a descriptorMethod is named DeriveDefaultDescriptor but calls DeriveDefaultXpub and returns an xpub. Consider renaming to DeriveDefaultXpub (or return a descriptor).
114-124: Apply bumped transaction to keep wallet state consistentAfter broadcasting the bumped tx, also apply it locally (mirrors SendToAddress behavior).
newTxId, err := w.txProvider.BroadcastTransaction(txHex) if err != nil { return "", err } - return newTxId, nil + if err := w.Wallet.ApplyTransaction(txHex); err != nil { + return "", err + } + return newTxId, nil
167-169: Free FFI resources on DisconnectInvoke Destroy on the underlying BDK wallet for deterministic release instead of relying solely on finalizers.
func (w *Wallet) Disconnect() error { - return nil + w.Wallet.Destroy() + return nil }internal/onchain/bitcoin-wallet/bdk/bdk.go (1)
582-593: Optional: prefer io.ReadFull for exact readsreader.Read is not guaranteed to fill the buffer. io.ReadFull avoids partial reads.
- read_length, err := reader.Read(buffer) - if err != nil && err != io.EOF { - panic(err) - } - if read_length != int(length) { - panic(fmt.Errorf("bad read length when reading string, expected %d, read %d", length, read_length)) - } + if _, err := io.ReadFull(reader, buffer); err != nil { + panic(err) + }Note: This file is generated; consider updating the generator template instead.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bdk/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
.golangci.yml(1 hunks)Makefile(2 hunks)bdk/Cargo.toml(1 hunks)bdk/src/lib.rs(1 hunks)bdk/src/types.rs(1 hunks)bdk/src/wallet.rs(1 hunks)bdk/uniffi-bindgen.rs(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.go(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.h(1 hunks)internal/onchain/bitcoin-wallet/bdk/dynamic.go(1 hunks)internal/onchain/bitcoin-wallet/bdk/static.go(1 hunks)internal/onchain/bitcoin-wallet/wallet.go(1 hunks)internal/onchain/wallet_test.go(2 hunks)internal/rpcserver/server.go(2 hunks)internal/test/test.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- bdk/src/lib.rs
- bdk/src/wallet.rs
- bdk/src/types.rs
- bdk/Cargo.toml
- Makefile
🧰 Additional context used
🧬 Code graph analysis (5)
internal/test/test.go (5)
pkg/boltz/currency.go (1)
CurrencyBtc(11-11)internal/onchain/bitcoin-wallet/bdk/bdk.go (4)
DeriveDefaultXpub(1410-1422)NetworkRegtest(1259-1259)NewBackend(687-697)Network(1254-1254)internal/electrum/electrum.go (1)
NewClient(20-52)internal/onchain/onchain.go (3)
RegtestElectrumConfig(71-74)TxProvider(51-55)FeeProvider(39-41)internal/onchain/bitcoin-wallet/wallet.go (2)
NewBackend(35-56)Config(26-33)
internal/onchain/wallet_test.go (3)
internal/test/test.go (7)
WalletBackend(186-208)WalletCredentials(65-79)FundWallet(81-123)GetCli(218-224)GetNewAddress(258-260)SendToAddress(262-264)MineBlock(244-248)pkg/boltz/currency.go (2)
CurrencyBtc(11-11)CurrencyLiquid(12-12)internal/onchain/wallet.go (1)
WalletSendArgs(42-47)
internal/rpcserver/server.go (3)
internal/onchain/bitcoin-wallet/wallet.go (2)
Config(26-33)NewBackend(35-56)internal/onchain/onchain.go (2)
TxProvider(51-55)FeeProvider(39-41)pkg/boltz/currency.go (1)
CurrencyBtc(11-11)
internal/onchain/bitcoin-wallet/wallet.go (4)
internal/onchain/bitcoin-wallet/bdk/bdk.go (11)
Backend(683-685)Wallet(757-759)Network(1254-1254)NewBackend(687-697)NetworkBitcoin(1257-1257)NetworkTestnet(1258-1258)NetworkRegtest(1259-1259)NewWallet(761-771)WalletCredentials(968-971)Balance(928-931)WalletTransaction(1052-1059)internal/onchain/onchain.go (3)
TxProvider(51-55)FeeProvider(39-41)ElectrumOptions(61-64)pkg/boltz/currency.go (1)
CurrencyBtc(11-11)internal/onchain/wallet.go (1)
WalletSendArgs(42-47)
internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
internal/onchain/bitcoin-wallet/wallet.go (3)
NewBackend(35-56)Backend(13-18)Wallet(20-24)internal/test/test.go (1)
WalletCredentials(65-79)
🪛 GitHub Actions: CI
internal/onchain/wallet_test.go
[warning] 1-1: Multiple test runs surfaced consistent DB-open failures; likely environmental/file-system permissions or temp data cleanup issues affecting test wallets.
🔇 Additional comments (10)
.golangci.yml (1)
6-6: LGTM!Excluding the BDK bindings directory from linting is appropriate, as it contains generated FFI code that doesn't need to conform to Go linting standards.
bdk/uniffi-bindgen.rs (1)
1-3: LGTM!Standard UniFFI binding generator entry point. The implementation correctly delegates to the UniFFI library's main function.
internal/rpcserver/server.go (1)
251-264: LGTM!The Bitcoin wallet backend initialization follows the same pattern as the Liquid wallet backend above it, with proper configuration assembly and error handling.
internal/onchain/bitcoin-wallet/bdk/static.go (1)
1-8: LGTM!The static build configuration is correct, with appropriate linker flags (
--no-as-neededand-ldl) for static linking withlibbdk.a.internal/test/test.go (2)
72-74: LGTM!The BTC credentials derivation using
bdk.DeriveDefaultXpubis appropriate and follows the pattern established for Liquid wallets.
190-200: LGTM!The BTC backend test wiring correctly mirrors the Liquid backend pattern, with proper electrum client initialization and configuration.
internal/onchain/bitcoin-wallet/bdk/bdk.h (1)
1-797: Verify if auto-generated file should be committed.This is an auto-generated C header file for UniFFI bindings. While committing generated FFI bindings can be intentional for reproducible builds, verify this aligns with your project's conventions.
If the file is regenerated during the build process, consider adding it to
.gitignoreand documenting the generation step in your build instructions.internal/onchain/bitcoin-wallet/bdk/dynamic.go (1)
5-6: Verify runtime availability of libbdk.so.The rpath is set to
${SRCDIR}, solibbdk.somust be present at runtime in that directory (or onLD_LIBRARY_PATH). I didn’t find it in the repo—ensure your build and deployment process installs it alongside the Go binary.internal/onchain/wallet_test.go (1)
224-255: Userequire.Eventuallyfor mempool wait and clean up disabled tests.
- Replace the fixed
time.Sleep(2*time.Second)with arequire.Eventuallyloop (as inTestWallet_GetBalance) that callswallet.Sync()and checks fortxIdinwallet.GetTransactions().- Remove the commented-out Xpub test (Lines 290–301) or add a clear TODO explaining why it’s disabled.
- Verify and resolve the CI “DB-open failures” warning to ensure tests don’t clash when running in parallel.
internal/onchain/bitcoin-wallet/wallet.go (1)
163-165: No updates needed for errors.ErrUnsupported: go.mod targets Go 1.24.6 and errors.ErrUnsupported has been available since Go 1.21.
| #[derive(uniffi::Enum)] | ||
| pub enum Network { | ||
| Bitcoin, | ||
| Testnet, | ||
| Regtest, | ||
| } | ||
|
|
||
| impl From<Network> for BdkNetwork { | ||
| fn from(network: Network) -> Self { | ||
| match network { | ||
| Network::Bitcoin => BdkNetwork::Bitcoin, | ||
| Network::Testnet => BdkNetwork::Testnet, | ||
| Network::Regtest => BdkNetwork::Regtest, | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Signet would be nice indeed if BDK offers it
There was a problem hiding this comment.
The backend does support signet but we don't have a deployment there. And the client doesn't support signet in many places, so whatever. Doesn't matter
| /* | ||
| { | ||
| name: "Xpub/BTC", | ||
| credentials: onchain.WalletCredentials{ | ||
| WalletInfo: onchain.WalletInfo{ | ||
| Currency: boltz.CurrencyBtc, | ||
| }, | ||
| Xpub: "vpub5XzEwP9YWe4cJD6pB3njrMgWahQbzHhfGAyuErnswtPuzm6QdLqHH79DSZ6YW3McdE1pwxvr7wHU2nMtVbPZ1jW4tqg8ggx4ZV19U7i69pd", | ||
| }, | ||
| shouldError: false, | ||
| }, | ||
| */ |
There was a problem hiding this comment.
Why is that still commented out?
| }; | ||
|
|
||
| let descriptor_pubkey = DescriptorPublicKey::MultiXPub(multi); | ||
| Ok(Wpkh::new(descriptor_pubkey).context("wpkh")?.to_string()) |
There was a problem hiding this comment.
Do we want to default to P2WPKH or rather P2TR?
There was a problem hiding this comment.
Is P2TR supported widely enough in wallets for default?
There was a problem hiding this comment.
We agreed to default to P2WPKH
There was a problem hiding this comment.
Yes. Current code does so
|
|
||
| pub fn bump_transaction_fee(&self, tx_id: String, sat_per_vbyte: f64) -> Result<String, Error> { | ||
| let tx_id = Txid::from_str(&tx_id).context("parse tx id")?; | ||
| let fee_rate = FeeRate::from_sat_per_kwu((sat_per_vbyte * 1000.0) as u64); |
There was a problem hiding this comment.
How about using that https://docs.rs/bdk/latest/bdk/struct.FeeRate.html#method.from_sat_per_vb instead of trying kilo weight unit arithmetics?
| sat_per_vbyte: f64, | ||
| send_all: bool, | ||
| ) -> Result<WalletSendResult, Error> { | ||
| let fee_rate = FeeRate::from_sat_per_kwu((sat_per_vbyte * 1000.0) as u64); |
There was a problem hiding this comment.
Same here. https://docs.rs/bdk/latest/bdk/struct.FeeRate.html#method.from_sat_per_vb us that. 1 sat/vbyte != 1000 sat/kwu. 1 wu = 4 vbyte. Just keep the unit
There was a problem hiding this comment.
The from_sat_per_vb only accepts integers, but we accept a floats.
aa22d55 to
a953735
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/test/test.go (2)
143-155: Data race: concurrent writes to result mapeg.Go goroutines write to map without synchronization. Use a mutex.
Apply:
- result := make(map[boltz.Currency]*wallet.Wallet) + result := make(map[boltz.Currency]*wallet.Wallet) + var mu sync.Mutex @@ - result[currency] = wallet + mu.Lock() + result[currency] = wallet + mu.Unlock()Also add:
@@ -import ( +import ( + "sync"
249-255: Guard against negative block counts in MineUntilIf height <= current, blocks becomes negative and the CLI call is invalid.
Apply:
func MineUntil(t *testing.T, cli Cli, height int64) { blockHeight, err := strconv.ParseInt(cli("getblockcount"), 10, 64) require.NoError(t, err) blocks := height - blockHeight + if blocks <= 0 { + return + } cli(fmt.Sprintf("-generate %d", blocks)) syncCln() }
♻️ Duplicate comments (7)
internal/onchain/wallet_test.go (1)
290-301: Clean up commented-out test blockThis has been flagged before. Either delete it or convert into a t.Run with t.Skip and a brief rationale.
bdk/src/wallet.rs (3)
213-224: Avoid holding wallet mutex across network I/O in sync()Build the request under lock, drop it before full_scan, then re-lock to apply.
Apply:
pub fn sync(&self) -> Result<(), Error> { - let mut wallet = self.get_wallet()?; - let request = wallet.start_full_scan(); - let update = self + let request = { + let mut wallet = self.get_wallet()?; + wallet.start_full_scan() + }; + let update = self .backend .electrum .full_scan(request, 20, 50, false) .context("full scan")?; - wallet.apply_update(update).context("apply update")?; - self.persist(&mut wallet)?; + let mut wallet = self.get_wallet()?; + wallet.apply_update(update).context("apply update")?; + self.persist(&mut wallet)?; Ok(()) }
319-323: Pagination order bug (take before skip)Call skip() before take() to page correctly.
Apply:
- .transactions() - .take(limit as usize) - .skip(offset as usize) + .transactions() + .skip(offset as usize) + .take(limit as usize)
62-85: Wrong coin type in derivation path (mainnet-only path used for testnet/regtest)Use BIP-84 coin type 1 for Testnet/Regtest.
Apply:
fn derive_default_xpub(network: Network, mnemonic: String) -> Result<String, Error> { let secp = Secp256k1::new(); - let derivation_path = DerivationPath::from_str("m/84h/0h/0h").context("derivation path")?; + let coin_type = match network { + Network::Bitcoin => 0u32, + Network::Testnet | Network::Regtest => 1u32, + }; + let derivation_path = DerivationPath::from_str( + &format!("m/84h/{}h/0h", coin_type) + ).context("derivation path")?;internal/onchain/bitcoin-wallet/wallet.go (2)
148-159: Verify: GetSendFee may have unintended side effects.
GetSendFeecallsSendToAddress, which might build, sign, and potentially broadcast a transaction or reserve coins. Past review suggested using a dedicated estimate-only endpoint (e.g., dry-run via TxBuilder) to compute fees without side effects.Confirm whether BDK's
SendToAddressbinding modifies wallet state (e.g., reserves UTXOs, persists pending tx) when called but not broadcast. If it does, consider implementing a dedicated fee estimation method using BDK's TxBuilder in dry-run mode.
56-67: Fix nil dereference in panic path.If
networkis nil, the default branch will panic on nil dereference. Use the pointer directly in the error message.Apply this diff:
func convertNetwork(network *boltz.Network) bdk.Network { switch network { case boltz.MainNet: return bdk.NetworkBitcoin case boltz.TestNet: return bdk.NetworkTestnet case boltz.Regtest: return bdk.NetworkRegtest default: - panic(fmt.Sprintf("unexpected network %v", *network)) + panic(fmt.Sprintf("unexpected network %v", network)) } }internal/onchain/bitcoin-wallet/bdk/bdk.go (1)
1244-1252: Critical bug: FfiDestroyerError.Destroy type mismatch causes panic.The switch matches
ErrorGenericby value (line 1246), but errors are stored as pointers (see line 1186:err: &ErrorGeneric{...}). This will always fall through to the panic in the default case.Apply this diff:
func (_ FfiDestroyerError) Destroy(value *Error) { switch variantValue := value.err.(type) { - case ErrorGeneric: - variantValue.destroy() + case *ErrorGeneric: + variantValue.destroy() default: _ = variantValue panic(fmt.Sprintf("invalid error value `%v` in FfiDestroyerError.Destroy", value)) } }
🧹 Nitpick comments (5)
internal/onchain/wallet_test.go (2)
22-24: Currency coverage mismatchwalletTest iterates only Liquid, but comments imply both currencies. Either add BTC here or update the comment to avoid confusion.
Apply (if you want both):
- for _, currency := range []boltz.Currency{boltz.CurrencyLiquid} { + for _, currency := range []boltz.Currency{boltz.CurrencyBtc, boltz.CurrencyLiquid} {Or adjust the comment at Line 80 accordingly.
Also applies to: 76-81
126-129: Remove debug print from assertionsfmt.Println in assertions adds noise to test output.
Apply:
- fmt.Println(o.Address, searchAddress) return o.Address == searchAddressMakefile (1)
114-119: Good fix on lib name; consider binding generation flowNice correction to check/copy libbdk.a. Optionally, wire bdk-bindings into build if bindings need regeneration during CI.
Example:
-build: download-gdk build-bolt12 build-lwk build-bdk +build: download-gdk build-bolt12 build-lwk build-bdk bdk-bindingsbdk/Cargo.toml (1)
6-15: Correct version target: upgrade bdk_wallet to 2.1.0 (not 2.2.x)The latest published version of bdk_wallet is 2.1.0, and the features ["keys-bip39","file_store","rusqlite","std"] are present and valid. Consider updating from 2.0.0 to 2.1.0. The overall MSRV for bdk_wallet is 1.63.0 and bdk_electrum requires MSRV 1.75.0, both of which are consistent with your setup.
internal/onchain/bitcoin-wallet/bdk/bdk.go (1)
1156-1156: Fix typo in comment."Convience" should be "Convenience".
-// Convience method to turn *Error into error +// Convenience method to turn *Error into error
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bdk/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
.golangci.yml(1 hunks)Makefile(2 hunks)bdk/Cargo.toml(1 hunks)bdk/src/lib.rs(1 hunks)bdk/src/types.rs(1 hunks)bdk/src/wallet.rs(1 hunks)bdk/uniffi-bindgen.rs(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.go(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.h(1 hunks)internal/onchain/bitcoin-wallet/bdk/dynamic.go(1 hunks)internal/onchain/bitcoin-wallet/bdk/static.go(1 hunks)internal/onchain/bitcoin-wallet/wallet.go(1 hunks)internal/onchain/wallet_test.go(2 hunks)internal/rpcserver/server.go(2 hunks)internal/test/test.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- bdk/src/lib.rs
- internal/onchain/bitcoin-wallet/bdk/dynamic.go
- .golangci.yml
- internal/onchain/bitcoin-wallet/bdk/static.go
- internal/rpcserver/server.go
🧰 Additional context used
🧬 Code graph analysis (6)
internal/onchain/wallet_test.go (4)
internal/test/test.go (7)
WalletBackend(185-207)WalletCredentials(64-78)FundWallet(80-122)GetCli(217-223)GetNewAddress(257-259)SendToAddress(261-263)MineBlock(243-247)pkg/boltz/currency.go (2)
CurrencyBtc(11-11)CurrencyLiquid(12-12)internal/onchain/bitcoin-wallet/bdk/bdk.go (1)
WalletCredentials(968-971)internal/onchain/wallet.go (1)
WalletSendArgs(42-47)
bdk/src/types.rs (2)
internal/onchain/liquid-wallet/wallet.go (1)
Testnet(136-136)internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
WalletTransactionOutput(1108-1112)WalletSendResult(1008-1012)
bdk/src/wallet.rs (1)
internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
WalletSendResult(1008-1012)WalletTransactionOutput(1108-1112)
internal/onchain/bitcoin-wallet/wallet.go (3)
internal/onchain/bitcoin-wallet/bdk/bdk.go (12)
Backend(683-685)Wallet(757-759)Network(1254-1254)NewBackend(687-697)NetworkBitcoin(1257-1257)NetworkTestnet(1258-1258)NetworkRegtest(1259-1259)NewWallet(761-771)WalletCredentials(968-971)DeriveDefaultXpub(1410-1422)Balance(928-931)WalletTransaction(1052-1059)internal/onchain/onchain.go (2)
ChainProvider(40-48)ElectrumOptions(50-53)internal/onchain/wallet.go (1)
WalletSendArgs(42-47)
internal/test/test.go (4)
internal/onchain/bitcoin-wallet/bdk/bdk.go (4)
DeriveDefaultXpub(1410-1422)NetworkRegtest(1259-1259)NewBackend(687-697)Network(1254-1254)internal/electrum/electrum.go (1)
NewClient(20-52)internal/onchain/onchain.go (1)
RegtestElectrumConfig(60-63)internal/onchain/bitcoin-wallet/wallet.go (2)
NewBackend(33-54)Config(25-31)
internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
internal/onchain/bitcoin-wallet/wallet.go (3)
NewBackend(33-54)Backend(13-17)Wallet(19-23)internal/test/test.go (1)
WalletCredentials(64-78)
🪛 Clang (14.0.6)
internal/onchain/bitcoin-wallet/bdk/bdk.h
[error] 8-8: 'stdbool.h' file not found
(clang-diagnostic-error)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Lint
- GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (3)
bdk/src/types.rs (1)
1-69: LGTM! Well-structured UniFFI type definitions.The type definitions are clean and complete:
- Error handling properly chains the full error context via
error.chain()- Network enum now includes Signet (addressing past review feedback)
- All structs are properly annotated for UniFFI code generation
bdk/uniffi-bindgen.rs (1)
1-3: LGTM! Standard UniFFI bindgen entry point.The delegation to
uniffi::uniffi_bindgen_main()is the standard pattern for UniFFI binding generation.internal/onchain/bitcoin-wallet/bdk/bdk.h (1)
1-797: LGTM! Autogenerated UniFFI C header.This file is autogenerated by UniFFI for the C/FFI bindings. The static analysis error about missing
stdbool.his a false positive—the header will be available in the actual build environment.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/test/test.go (1)
143-156: Fix concurrent map writes to shared result.eg.Go goroutines write to the same map without sync; this can panic. Protect with a mutex.
func InitTestWallet(debug bool) (map[boltz.Currency]*wallet.Wallet, error) { @@ - result := make(map[boltz.Currency]*wallet.Wallet) + result := make(map[boltz.Currency]*wallet.Wallet) + var mu sync.Mutex @@ - wallet, err := wallet.Login(WalletCredentials(currency)) + w, err := wallet.Login(WalletCredentials(currency)) if err != nil { return err } time.Sleep(2 * time.Second) - result[currency] = wallet + mu.Lock() + result[currency] = w + mu.Unlock() return FundWallet(currency, w)Add import:
import ( "fmt" "math/rand/v2" "os" "os/exec" "strconv" "strings" "testing" "time" + "sync"
♻️ Duplicate comments (6)
internal/onchain/wallet_test.go (1)
290-301: Commented-out test block: either enable or convert to t.Skip with rationale.Keeping dead code commented confuses maintenance. Turn it into a skipped test with a brief reason, or remove it.
bdk/src/wallet.rs (3)
62-85: Use testnet/regtest coin type (1) for default xpub derivation.Currently hardcoded to mainnet coin type (0). Select coin type by network.
fn derive_default_xpub(network: Network, mnemonic: String) -> Result<String, Error> { let secp = Secp256k1::new(); - let derivation_path = DerivationPath::from_str("m/84h/0h/0h").context("derivation path")?; + let coin_type = match network { + Network::Bitcoin => 0, + Network::Testnet | Network::Regtest => 1, + }; + let derivation_path = DerivationPath::from_str(&format!("m/84h/{}h/0h", coin_type)) + .context("derivation path")?;Based on learnings
213-224: Don’t hold wallet mutex across network I/O in sync().Build the request under the lock, drop it, perform full_scan, then re-lock to apply/persist. Reduces contention.
- let mut wallet = self.get_wallet()?; - let request = wallet.start_full_scan(); - let update = self + let request = { + let mut wallet = self.get_wallet()?; + wallet.start_full_scan() + }; + let update = self .backend .electrum .full_scan(request, 20, 50, false) .context("full scan")?; - wallet.apply_update(update).context("apply update")?; - self.persist(&mut wallet)?; + let mut wallet = self.get_wallet()?; + wallet.apply_update(update).context("apply update")?; + self.persist(&mut wallet)?;Based on learnings
319-323: Fix pagination order (skip before take) and treat limit=0 as “no limit”.Current take-then-skip yields wrong pages; limit=0 returns no items.
- let transactions = wallet - .transactions() - .take(limit as usize) - .skip(offset as usize) + let effective_limit = if limit == 0 { usize::MAX } else { limit as usize }; + let transactions = wallet + .transactions() + .skip(offset as usize) + .take(effective_limit)internal/onchain/bitcoin-wallet/bdk/bdk.go (1)
1244-1252: Panic bug: wrong type in FfiDestroyerError.Destroy.Errors are stored as pointers; matching by value misses and panics.
func (_ FfiDestroyerError) Destroy(value *Error) { + if value == nil || value.err == nil { + return + } switch variantValue := value.err.(type) { - case ErrorGeneric: - variantValue.destroy() + case *ErrorGeneric: + variantValue.destroy() default: _ = variantValue panic(fmt.Sprintf("invalid error value `%v` in FfiDestroyerError.Destroy", value)) } }internal/onchain/bitcoin-wallet/wallet.go (1)
56-66: Fix nil deref in panic path.Default branch dereferences a possibly nil pointer. Print the pointer instead.
- default: - panic(fmt.Sprintf("unexpected network %v", *network)) + default: + // avoid dereferencing nil in panic path + panic(fmt.Sprintf("unexpected network %v", network))
🧹 Nitpick comments (13)
internal/test/test.go (1)
71-73: Don’t ignore DeriveDefaultXpub error.If derivation fails, tests silently proceed with an empty descriptor. Fail fast.
- if currency == boltz.CurrencyBtc { - creds.CoreDescriptor, _ = bdk.DeriveDefaultXpub(bdk.NetworkRegtest, WalletMnemonic) - } + if currency == boltz.CurrencyBtc { + desc, err := bdk.DeriveDefaultXpub(bdk.NetworkRegtest, WalletMnemonic) + if err != nil { panic("DeriveDefaultXpub failed: " + err.Error()) } + creds.CoreDescriptor = desc + }internal/onchain/wallet_test.go (1)
244-251: Avoid fixed sleeps; wait until tx is in mempool.Replace time.Sleep with require.Eventually to reduce flakes.
- // Wait a bit to ensure transaction is in mempool - time.Sleep(2 * time.Second) + // Wait until wallet sees the unconfirmed tx + require.Eventually(t, func() bool { + _ = wallet.Sync() + txs, _ := wallet.GetTransactions(50, 0) + for _, tx := range txs { if tx.Id == txId && tx.BlockHeight == 0 { return true } } + return false + }, 10*time.Second, 250*time.Millisecond)bdk/Cargo.toml (1)
17-19: Add an explicit bin target for uniffi-bindgen.Helps “cargo run --bin uniffi-bindgen” workflows and some generators. Optional but nice.
[lib] crate-type = ["staticlib", "cdylib", "rlib"] name = "bdk" + +[[bin]] +name = "uniffi-bindgen" +path = "uniffi-bindgen.rs"Makefile (2)
63-65: Prefer passing the .so to uniffi-bindgen-go.Dynamic lib tends to be more portable for metadata extraction than .a.
-bdk-bindings: build-bdk bindgen-go - cd $(BDK_BINDINGS_PATH) && uniffi-bindgen-go --out-dir ../internal/onchain/bitcoin-wallet/ --library ./target/release/libbdk.a +bdk-bindings: build-bdk bindgen-go + cd $(BDK_BINDINGS_PATH) && uniffi-bindgen-go --out-dir ../internal/onchain/bitcoin-wallet/ --library ./target/release/libbdk.so
114-120: Avoid stale BDK artifacts: build unconditionally.Guarded build won’t rebuild after source changes if libbdk.a exists. Cargo is incremental; let it decide.
-build-bdk: -ifeq ("$(wildcard internal/onchain/bitcoin-wallet/bdk/libbdk.a)","") - @$(call print, "Building bdk") - cd $(BDK_BINDINGS_PATH) && cargo build --release --lib - cp $(BDK_BINDINGS_PATH)/target/release/libbdk.a $(BDK_BINDINGS_PATH)/target/release/libbdk.so internal/onchain/bitcoin-wallet/bdk/ -endif +build-bdk: + @$(call print, "Building bdk") + cd $(BDK_BINDINGS_PATH) && cargo build --release --lib + cp $(BDK_BINDINGS_PATH)/target/release/libbdk.a $(BDK_BINDINGS_PATH)/target/release/libbdk.so internal/onchain/bitcoin-wallet/bdk/ || truebdk/src/wallet.rs (1)
163-165: Round fee-rate conversion to avoid underpaying by truncation.Use precise 250x conversion and round.
-fn parse_fee_rate(sat_per_vbyte: f64) -> FeeRate { - FeeRate::from_sat_per_kwu((sat_per_vbyte * 1000.0 / 4.0) as u64) -} +fn parse_fee_rate(sat_per_vbyte: f64) -> FeeRate { + let sp_kwu = (sat_per_vbyte * 250.0).round() as u64; + FeeRate::from_sat_per_kwu(sp_kwu) +}Based on learnings
bdk/src/types.rs (1)
47-54: Document field units/semantics for cross-language consumers.Please add brief docs indicating:
- timestamp is Unix seconds.
- block_height is 0 (or similar) when unconfirmed.
This avoids subtle bugs in bindings and callers.
#[derive(uniffi::Record)] pub struct WalletTransaction { pub id: String, - pub timestamp: u64, + /// Unix timestamp in seconds + pub timestamp: u64, pub outputs: Vec<WalletTransactionOutput>, - pub block_height: u32, + /// 0 if unconfirmed + pub block_height: u32, pub balance_change: i64, pub is_consolidation: bool, }internal/onchain/bitcoin-wallet/wallet.go (2)
45-54: Avoid double scheme in Electrum URL.If cfg.Electrum.Url already contains tcp:// or ssl://, this will produce tcp://tcp://... Consider normalizing.
- url = cfg.Electrum.Url - if cfg.Electrum.SSL { - url = "ssl://" + url - } else { - url = "tcp://" + url - } + u := cfg.Electrum.Url + if !strings.HasPrefix(u, "tcp://") && !strings.HasPrefix(u, "ssl://") { + if cfg.Electrum.SSL { + u = "ssl://" + u + } else { + u = "tcp://" + u + } + } + url = u(Add import "strings")
48-49: Use filepath.Join for portability.String concat can be brittle across platforms.
- cfg.DataDir+"/bdk.sqlite", + filepath.Join(cfg.DataDir, "bdk.sqlite"),(Add import "path/filepath")
internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
1256-1260: Add NetworkSignet to mirror Rust and avoid future mismatches.Rust exposes a Signet variant; define the Go constant for parity even if unused today.
const ( NetworkBitcoin Network = 1 NetworkTestnet Network = 2 NetworkRegtest Network = 3 + NetworkSignet Network = 4 )
67-69: Guard potential length overflow in ToGoBytes.C.int(cb.inner.len) may overflow on 32‑bit targets. Add a bound check.
func (cb GoRustBuffer) ToGoBytes() []byte { - return C.GoBytes(unsafe.Pointer(cb.inner.data), C.int(cb.inner.len)) + if cb.inner.len > C.uint64_t(math.MaxInt32) { + panic("buffer too large for GoBytes") + } + return C.GoBytes(unsafe.Pointer(cb.inner.data), C.int(cb.inner.len)) }internal/onchain/bitcoin-wallet/bdk/bdk.h (2)
8-8: Guard stdbool include for C++/toolchains missing it.Fixes portability and the reported clang error.
-#include <stdbool.h> +#ifndef __cplusplus +# include <stdbool.h> +#endif
3-5: Remove unprofessional autogenerated comment.Please replace with a neutral “Autogenerated by UniFFI. Do not edit.” notice.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bdk/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
.golangci.yml(1 hunks)Makefile(2 hunks)bdk/Cargo.toml(1 hunks)bdk/src/lib.rs(1 hunks)bdk/src/types.rs(1 hunks)bdk/src/wallet.rs(1 hunks)bdk/uniffi-bindgen.rs(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.go(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.h(1 hunks)internal/onchain/bitcoin-wallet/bdk/dynamic.go(1 hunks)internal/onchain/bitcoin-wallet/bdk/static.go(1 hunks)internal/onchain/bitcoin-wallet/wallet.go(1 hunks)internal/onchain/wallet_test.go(2 hunks)internal/rpcserver/server.go(2 hunks)internal/test/test.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- bdk/src/lib.rs
- internal/onchain/bitcoin-wallet/bdk/static.go
- internal/onchain/bitcoin-wallet/bdk/dynamic.go
🧰 Additional context used
🧬 Code graph analysis (7)
bdk/src/wallet.rs (1)
internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
WalletSendResult(1008-1012)WalletTransactionOutput(1108-1112)
internal/test/test.go (7)
pkg/boltz/currency.go (2)
CurrencyBtc(11-11)CurrencyLiquid(12-12)internal/onchain/bitcoin-wallet/bdk/bdk.go (4)
DeriveDefaultXpub(1410-1422)NetworkRegtest(1259-1259)Network(1254-1254)NewBackend(687-697)internal/onchain/onchain.go (3)
Currency(67-70)ChainProvider(40-48)RegtestElectrumConfig(60-63)internal/onchain/boltz.go (1)
NewBoltzChainProvider(16-18)internal/onchain/liquid-wallet/wallet.go (4)
Regtest(135-135)Config(40-48)Persister(20-23)NewBackend(68-131)internal/onchain/bitcoin-wallet/wallet.go (2)
Config(25-31)NewBackend(33-54)internal/onchain/wallet.go (1)
WalletBackend(126-129)
internal/onchain/wallet_test.go (4)
internal/test/test.go (7)
WalletBackend(189-207)WalletCredentials(64-78)FundWallet(80-122)GetCli(217-223)GetNewAddress(257-259)SendToAddress(261-263)MineBlock(243-247)pkg/boltz/currency.go (2)
CurrencyBtc(11-11)CurrencyLiquid(12-12)internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
NewWallet(761-771)WalletCredentials(968-971)internal/onchain/wallet.go (1)
WalletSendArgs(42-47)
internal/rpcserver/server.go (4)
internal/onchain/bitcoin-wallet/wallet.go (2)
Config(25-31)NewBackend(33-54)internal/config/config.go (1)
Config(55-98)internal/onchain/onchain.go (1)
ChainProvider(40-48)pkg/boltz/currency.go (1)
CurrencyBtc(11-11)
internal/onchain/bitcoin-wallet/wallet.go (4)
internal/onchain/bitcoin-wallet/bdk/bdk.go (12)
Backend(683-685)Wallet(757-759)Network(1254-1254)NewBackend(687-697)NetworkBitcoin(1257-1257)NetworkTestnet(1258-1258)NetworkRegtest(1259-1259)NewWallet(761-771)WalletCredentials(968-971)DeriveDefaultXpub(1410-1422)Balance(928-931)WalletTransaction(1052-1059)internal/onchain/onchain.go (2)
ChainProvider(40-48)ElectrumOptions(50-53)pkg/boltz/currency.go (1)
CurrencyBtc(11-11)internal/onchain/wallet.go (1)
WalletSendArgs(42-47)
bdk/src/types.rs (2)
internal/onchain/liquid-wallet/wallet.go (1)
Testnet(136-136)internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
WalletTransactionOutput(1108-1112)WalletSendResult(1008-1012)
internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
internal/onchain/bitcoin-wallet/wallet.go (3)
NewBackend(33-54)Backend(13-17)Wallet(19-23)internal/test/test.go (2)
SendToAddress(261-263)WalletCredentials(64-78)
🪛 Clang (14.0.6)
internal/onchain/bitcoin-wallet/bdk/bdk.h
[error] 8-8: 'stdbool.h' file not found
(clang-diagnostic-error)
🔇 Additional comments (4)
.golangci.yml (1)
6-6: LGTM for excluding generated bindings.Excluding internal/onchain/bitcoin-wallet/bdk/ from linters is appropriate for generated FFI code.
bdk/uniffi-bindgen.rs (1)
1-3: LGTM.Minimal entrypoint correctly defers to uniffi_bindgen_main().
bdk/src/types.rs (1)
21-37: Signet support and mapping look correct.Enum parity with BDK and the From mapping are complete. Good addition.
internal/onchain/bitcoin-wallet/wallet.go (1)
175-189: No issues found. Code is correct.The verification confirms that
result[i].Outputsis of type[]TransactionOutput, and the code correctly appendsonchain.TransactionOutputstructs with matching fields (Address, Amount, IsOurAddress). The types align perfectly with no implicit conversions or compile-time mismatches.
a953735 to
1b15a97
Compare
avoid double spends
skipped the wrong tests before (new suite instead of gdk)
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
internal/onchain/wallet_test.go (2)
290-301: Address the commented-out Xpub/BTC test case.This echoes michael1011's previous question: why is this test case commented out? If Xpub import for BTC is not yet supported, consider removing the commented code or adding a TODO comment explaining when it will be implemented. If it should work, please uncomment and verify.
224-255: Verify broadcast and txid semantics in BumpTransactionFee test.The test variable is named
newTxIdbut the backend may return raw transaction hex. Additionally, the test doesn't verify that:
- The bump actually broadcasts (no
Sync()call after bumping)- The replacement transaction exists in the mempool or wallet
- The old transaction is no longer present
Consider the approach in
internal/onchain/wallet/wallet_test.go(lines 29-51) which usesEventuallyto poll until the bump succeeds, or follow the previous review suggestion to decode the hex if needed and sync to verify broadcast.Apply this diff to improve the test:
// Try to bump the fee - newTxId, err := wallet.BumpTransactionFee(txId, 3) - require.NoError(t, err) - require.NotEmpty(t, newTxId) - require.NotEqual(t, txId, newTxId) + require.Eventually(t, func() bool { + newTxId, err := wallet.BumpTransactionFee(txId, 3) + if err != nil { + return false + } + require.NotEmpty(t, newTxId) + require.NotEqual(t, txId, newTxId) + + // Sync and verify the replacement is in the wallet + require.NoError(t, wallet.Sync()) + transactions, err := wallet.GetTransactions(30, 0) + require.NoError(t, err) + + // Verify new tx exists and old tx is gone + hasNew := false + hasOld := false + for _, tx := range transactions { + if tx.Id == newTxId { + hasNew = true + } + if tx.Id == txId { + hasOld = true + } + } + return hasNew && !hasOld + }, 15*time.Second, 250*time.Millisecond) test.MineBlock()
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
internal/onchain/bitcoin-wallet/wallet.go(1 hunks)internal/onchain/wallet/wallet_test.go(1 hunks)internal/onchain/wallet_test.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/onchain/bitcoin-wallet/wallet.go
🧰 Additional context used
🧬 Code graph analysis (1)
internal/onchain/wallet_test.go (5)
pkg/boltz/currency.go (2)
CurrencyLiquid(12-12)CurrencyBtc(11-11)internal/onchain/wallet/wallet_test.go (1)
TestWallet_BumpTransactionFee(30-52)internal/test/test.go (7)
WalletBackend(189-207)WalletCredentials(64-78)FundWallet(80-122)GetCli(217-223)GetNewAddress(257-259)SendToAddress(261-263)MineBlock(243-247)internal/onchain/bitcoin-wallet/bdk/bdk.go (1)
WalletCredentials(968-971)internal/onchain/wallet.go (1)
WalletSendArgs(42-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (2)
internal/onchain/wallet_test.go (2)
22-22: LGTM! BTC currency testing enabled.The addition of
boltz.CurrencyBtcto the currency list correctly enables BTC wallet testing across all tests using thewalletTesthelper function.
303-317: The Liquid CoreDescriptor change is correct.Git history shows this change (commit 05c39a0) was intentional: the test case was renamed from "CoreDescriptor/Liquid/NoCt" with a BTC descriptor and
shouldError: trueto "CoreDescriptor/Liquid" with a proper Liquid descriptor andshouldError: false.The old test validated that BTC descriptors (wpkh without the ct wrapper) fail on Liquid. The new test validates that proper Liquid descriptors succeed. The Liquid wallet implementation correctly parses and validates the descriptor format via
lwk.NewWolletDescriptor()at line 189 of liquid-wallet/wallet.go, so the behavioral change is justified.
it doesnt save any meaningful time and can cause outdated builds
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (8)
internal/onchain/bitcoin-wallet/bdk/bdk.h (1)
3-4: Unprofessional banner remains in generated header.The informal banner text is still present. Replace with a neutral autogeneration notice.
internal/onchain/bitcoin-wallet/wallet.go (2)
95-106: Nil dereference in panic path remains.The default case dereferences the network pointer in the panic message, which will panic with a nil dereference if network is nil.
Apply this diff:
default: - panic(fmt.Sprintf("unexpected network %v", *network)) + panic(fmt.Sprintf("unexpected network %v", network))
157-166: External-call hazard: ApplyTransaction failure loses broadcast txid.If ApplyTransaction fails after a successful broadcast, returning
"", errhides the txid and can trigger unsafe retries.Return the txid even when ApplyTransaction fails:
func (w *Wallet) broadcastTransaction(txHex string) (string, error) { txId, err := w.backend.cfg.ChainProvider.BroadcastTransaction(txHex) if err != nil { return "", err } if err := w.Wallet.ApplyTransaction(txHex); err != nil { - return "", err + return txId, fmt.Errorf("broadcast succeeded but apply failed: %w", err) } return txId, nil }bdk/src/wallet.rs (2)
65-65: Derivation path hardcoded to mainnet coin type.The path
m/84h/0h/0halways uses coin type 0 (mainnet). Testnet and Regtest should use coin type 1.Apply this diff:
fn derive_default_xpub(network: Network, mnemonic: String) -> Result<String, Error> { let secp = Secp256k1::new(); - let derivation_path = DerivationPath::from_str("m/84h/0h/0h").context("derivation path")?; + let coin_type = match network { + Network::Bitcoin => 0, + Network::Testnet | Network::Regtest => 1, + Network::Signet => 1, + }; + let derivation_path = DerivationPath::from_str( + &format!("m/84h/{}h/0h", coin_type) + ).context("derivation path")?;
312-320: Pagination order incorrect: take before skip.Calling
take(limit)beforeskip(offset)produces wrong pages.Apply this diff:
let transactions = wallet .transactions() - .take(if limit > 0 { - limit as usize - } else { - usize::MAX - }) .skip(offset as usize) + .take(if limit > 0 { + limit as usize + } else { + usize::MAX + })internal/onchain/bitcoin-wallet/bdk/bdk.go (3)
55-58: Missing overflow check in AsReader.
unsafe.Sliceexpects anintbut receivesuint64without bounds checking. Ifcb.inner.lenexceedsmath.MaxInt, the cast will overflow.Apply this diff:
func (cb GoRustBuffer) AsReader() *bytes.Reader { + n := int(cb.inner.len) + if n < 0 || uint64(n) != uint64(cb.inner.len) { + panic("RustBuffer length overflows int") + } - b := unsafe.Slice((*byte)(cb.inner.data), C.uint64_t(cb.inner.len)) + b := unsafe.Slice((*byte)(cb.inner.data), n) return bytes.NewReader(b) }
67-69: Missing overflow check in ToGoBytes.
C.GoBytesexpectsC.intbut receivescb.inner.lencast directly. If the length exceedsmath.MaxInt32, the cast will overflow.Apply this diff:
func (cb GoRustBuffer) ToGoBytes() []byte { + n := int(cb.inner.len) + if n < 0 || uint64(n) != uint64(cb.inner.len) { + panic("RustBuffer length overflows int") + } - return C.GoBytes(unsafe.Pointer(cb.inner.data), C.int(cb.inner.len)) + return C.GoBytes(unsafe.Pointer(cb.inner.data), C.int(n)) }
1244-1252: Type switch matches by value instead of pointer.The case
ErrorGenericmatches by value, but errors are stored as*ErrorGenericpointers. This will always fall through to the panic.Apply this diff:
func (_ FfiDestroyerError) Destroy(value *Error) { switch variantValue := value.err.(type) { - case ErrorGeneric: + case *ErrorGeneric: variantValue.destroy() default: _ = variantValue panic(fmt.Sprintf("invalid error value `%v` in FfiDestroyerError.Destroy", value)) } }
🧹 Nitpick comments (1)
bdk/src/wallet.rs (1)
152-154: Fee rate conversion may underpay due to truncation.Manual conversion to sat/kwu truncates fractional sat/vB and may underpay.
Consider rounding up to preserve user intent:
fn parse_fee_rate(sat_per_vbyte: f64) -> FeeRate { - FeeRate::from_sat_per_kwu((sat_per_vbyte * 1000.0 / 4.0) as u64) + // Round up to avoid underpaying + let sat_per_kwu = (sat_per_vbyte * 1000.0 / 4.0).ceil() as u64; + FeeRate::from_sat_per_kwu(sat_per_kwu) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
bdk/src/wallet.rs(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.go(1 hunks)internal/onchain/bitcoin-wallet/bdk/bdk.h(1 hunks)internal/onchain/bitcoin-wallet/wallet.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
bdk/src/wallet.rs (1)
internal/onchain/bitcoin-wallet/bdk/bdk.go (9)
Network(1254-1254)Wallet(757-759)Error(1152-1154)WalletCredentials(968-971)ChainClient(683-685)Balance(928-931)WalletSendResult(1008-1012)WalletTransaction(1052-1059)WalletTransactionOutput(1108-1112)
internal/onchain/bitcoin-wallet/bdk/bdk.go (3)
internal/onchain/liquid-wallet/lwk/lwk.go (3)
NewAddress(2758-2768)Mnemonic(5297-5299)Address(2753-2755)internal/test/test.go (1)
WalletCredentials(64-78)internal/onchain/bitcoin-wallet/wallet.go (1)
Wallet(24-29)
internal/onchain/bitcoin-wallet/wallet.go (4)
internal/onchain/onchain.go (3)
ElectrumOptions(50-53)ChainProvider(40-48)RegtestElectrumConfig(60-63)internal/onchain/bitcoin-wallet/bdk/bdk.go (13)
Wallet(757-759)Network(1254-1254)ChainClient(683-685)NewChainClient(687-697)NetworkBitcoin(1257-1257)NetworkTestnet(1258-1258)NetworkRegtest(1259-1259)NewWallet(761-771)WalletCredentials(968-971)DeriveDefaultXpub(1411-1423)Balance(928-931)WalletSendResult(1008-1012)Error(1152-1154)pkg/boltz/currency.go (1)
CurrencyBtc(11-11)internal/onchain/wallet.go (2)
WalletSendArgs(42-47)DefaultTransactionsLimit(11-11)
🪛 Clang (14.0.6)
internal/onchain/bitcoin-wallet/bdk/bdk.h
[error] 8-8: 'stdbool.h' file not found
(clang-diagnostic-error)
f683819 to
369397a
Compare
| #[derive(uniffi::Enum)] | ||
| pub enum Network { | ||
| Bitcoin, | ||
| Testnet, | ||
| Regtest, | ||
| } | ||
|
|
||
| impl From<Network> for BdkNetwork { | ||
| fn from(network: Network) -> Self { | ||
| match network { | ||
| Network::Bitcoin => BdkNetwork::Bitcoin, | ||
| Network::Testnet => BdkNetwork::Testnet, | ||
| Network::Regtest => BdkNetwork::Regtest, | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The backend does support signet but we don't have a deployment there. And the client doesn't support signet in many places, so whatever. Doesn't matter
| }; | ||
|
|
||
| let descriptor_pubkey = DescriptorPublicKey::MultiXPub(multi); | ||
| Ok(Wpkh::new(descriptor_pubkey).context("wpkh")?.to_string()) |
There was a problem hiding this comment.
We agreed to default to P2WPKH
| /* | ||
| { | ||
| name: "Xpub/BTC", | ||
| credentials: onchain.WalletCredentials{ | ||
| WalletInfo: onchain.WalletInfo{ | ||
| Currency: boltz.CurrencyBtc, | ||
| }, | ||
| Xpub: "vpub5XzEwP9YWe4cJD6pB3njrMgWahQbzHhfGAyuErnswtPuzm6QdLqHH79DSZ6YW3McdE1pwxvr7wHU2nMtVbPZ1jW4tqg8ggx4ZV19U7i69pd", | ||
| }, | ||
| shouldError: false, | ||
| }, | ||
| */ |
There was a problem hiding this comment.
Why is that still commented out?
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
bdk/src/wallet.rs (2)
207-217: Major: Wallet mutex held during network I/O blocks other operations.Despite being marked as addressed in a previous review, the lock is still held across the
full_scannetwork call (lines 208-215). This blocks all other wallet operations during synchronization, which can take several seconds.Apply this diff to release the lock during network I/O:
pub fn sync(&self, chain_client: Arc<ChainClient>) -> Result<(), Error> { - let mut wallet = self.get_wallet()?; - let request = wallet.start_full_scan(); - let update = chain_client + let request = { + let mut wallet = self.get_wallet()?; + wallet.start_full_scan() + }; + let update = chain_client .electrum .full_scan(request, 20, 50, false) .context("full scan")?; + let mut wallet = self.get_wallet()?; wallet.apply_update(update).context("apply update")?; self.persist(&mut wallet)?; Ok(()) }Based on learnings
62-85: Critical: Derivation path uses mainnet coin type for all networks.Line 65 hardcodes
m/84h/0h/0h, which always uses coin type 0 (mainnet). Per BIP44, testnet and regtest should use coin type 1. This was flagged in a previous review but remains unresolved.Apply this diff to fix the coin-type selection:
fn derive_default_xpub(network: Network, mnemonic: String) -> Result<String, Error> { let secp = Secp256k1::new(); - let derivation_path = DerivationPath::from_str("m/84h/0h/0h").context("derivation path")?; + let coin_type = match network { + Network::Bitcoin => 0, + Network::Testnet | Network::Regtest => 1, + }; + let derivation_path = DerivationPath::from_str( + &format!("m/84h/{}h/0h", coin_type) + ).context("derivation path")?;
🧹 Nitpick comments (1)
bdk/src/wallet.rs (1)
152-154: Consider rounding up fractional fee rates to avoid underpaying.The current cast to
u64truncates fractional sat/vB values. For example, 1.5 sat/vB becomes 375 sat/kwu (floored) instead of 376 (rounded up). While the math is correct, underpaying fees could cause transaction delays.Apply this diff to round up:
fn parse_fee_rate(sat_per_vbyte: f64) -> FeeRate { - FeeRate::from_sat_per_kwu((sat_per_vbyte * 1000.0 / 4.0) as u64) + FeeRate::from_sat_per_kwu((sat_per_vbyte * 1000.0 / 4.0).ceil() as u64) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
bdk/src/wallet.rs(1 hunks)internal/onchain/wallet_test.go(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/onchain/wallet_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
bdk/src/wallet.rs (1)
internal/onchain/bitcoin-wallet/bdk/bdk.go (9)
Network(1254-1254)Wallet(757-759)Error(1152-1154)WalletCredentials(968-971)ChainClient(683-685)Balance(928-931)WalletSendResult(1008-1012)WalletTransaction(1052-1059)WalletTransactionOutput(1108-1112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (10)
bdk/src/wallet.rs (10)
1-60: LGTM: Helper functions are well-structured.The mnemonic-to-xprv and key derivation helpers are implemented correctly with appropriate error handling.
87-133: LGTM: Descriptor derivation logic is sound.The credential handling and descriptor derivation correctly manages optional mnemonics and builds the keymap appropriately.
135-150: LGTM: ChainClient wrapper is straightforward.The Electrum client integration is clean and properly handles connection initialization.
156-205: LGTM: Wallet initialization is robust.The constructor properly handles both wallet loading and creation scenarios with appropriate descriptor setup and database persistence.
219-225: LGTM: Transaction application is correct.Properly deserializes and applies unconfirmed transactions to the wallet state.
244-258: LGTM: Address generation and balance retrieval are correct.Both methods properly use the wallet API and persist state when necessary.
305-350: LGTM: Pagination is now correct.The
skip()beforetake()ordering (line 314) properly implements pagination. This addresses the issue raised in a previous review.
353-366: LGTM: Helper methods are straightforward.The mutex management in
get_walletandpersistis appropriate for thread-safe wallet access.
260-303: Broadcasting is properly implemented in the Go layer.The Go wrapper at
internal/onchain/bitcoin-wallet/wallet.gocallssendToAddresswhich invokes the RustSendToAddressbinding and then automatically broadcasts the returned transaction hex viabroadcastTransaction, ensuring consistency with the Rust method's API design.
227-242: No action required—broadcasting is already implemented.The Go wrapper at
internal/onchain/bitcoin-wallet/wallet.go:168–174already broadcasts the bumped transaction viaw.broadcastTransaction(txHex)before returning. This follows the same pattern as other send methods (e.g.,sendToAddressat line 200), ensuring API consistency. The Rust layer correctly returns raw hex; the Go layer correctly handles broadcasting.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/onchain/wallet_test.go (1)
234-265: Address the concerns from previous reviews and improve test robustness.This test still has the issues flagged in past review comments:
- Line 258: The variable is named
newTxIdbut the backend may return raw transaction hex rather than a txid. The test only checksnewTxId != txId, which could pass trivially.- Line 254-255: Hard-coded sleep is fragile; prefer
require.Eventuallyto poll mempool state.- Line 263: Mining a block without verifying that the bumped transaction was actually confirmed or that the original transaction was replaced.
Consider this more robust approach:
- // Wait a bit to ensure transaction is in mempool - time.Sleep(2 * time.Second) - - // Try to bump the fee - newTxId, err := wallet.BumpTransactionFee(txId, 3) - require.NoError(t, err) - require.NotEmpty(t, newTxId) - require.NotEqual(t, txId, newTxId) - - test.MineBlock() + // Wait for transaction to appear in mempool + require.Eventually(t, func() bool { + require.NoError(t, wallet.Sync()) + txs, _ := wallet.GetTransactions(0, 0) + return slices.ContainsFunc(txs, func(tx onchain.Transaction) bool { + return tx.Id == txId && tx.Confirmations == 0 + }) + }, 10*time.Second, 250*time.Millisecond) + + // Bump the fee + newTxResult, err := wallet.BumpTransactionFee(txId, 3) + require.NoError(t, err) + require.NotEmpty(t, newTxResult) + + // If backend returns hex, decode to get txid; otherwise use as-is + // Verify the bumped transaction differs from original + require.NotEqual(t, txId, newTxResult) + + // Verify the bumped transaction is in mempool + require.Eventually(t, func() bool { + require.NoError(t, wallet.Sync()) + txs, _ := wallet.GetTransactions(0, 0) + return slices.ContainsFunc(txs, func(tx onchain.Transaction) bool { + return tx.Id == newTxResult && tx.Confirmations == 0 + }) + }, 10*time.Second, 250*time.Millisecond) + + test.MineBlock() + + // Verify the bumped transaction is confirmed + require.Eventually(t, func() bool { + require.NoError(t, wallet.Sync()) + txs, _ := wallet.GetTransactions(0, 0) + return slices.ContainsFunc(txs, func(tx onchain.Transaction) bool { + return tx.Id == newTxResult && tx.Confirmations > 0 + }) + }, 10*time.Second, 250*time.Millisecond)
🧹 Nitpick comments (1)
internal/onchain/wallet_test.go (1)
414-414: Clarify the intent of ignoring ValidateWalletCredentials error.The error from
ValidateWalletCredentialsis explicitly ignored. Looking at the implementation, this function has a side effect of populatingCoreDescriptorwhen it's empty. However, ignoring the error is confusing because some test cases with invalid credentials (e.g., "wrong wrong" mnemonic) would fail validation but are expected to fail later inNewWallet.Consider one of these approaches for clarity:
Option 1: Only call ValidateWalletCredentials for test cases expected to succeed:
backend := test.WalletBackend(t, tc.currency) tc.credentials.WalletInfo = test.WalletInfo(tc.currency) - _ = onchain.ValidateWalletCredentials(backend, &tc.credentials) + if !tc.shouldError { + require.NoError(t, onchain.ValidateWalletCredentials(backend, &tc.credentials)) + } wallet, err := backend.NewWallet(&tc.credentials)Option 2: Add a comment explaining why the error is ignored:
+ // Populate CoreDescriptor if missing; ignore errors as some test cases expect NewWallet to fail _ = onchain.ValidateWalletCredentials(backend, &tc.credentials)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/onchain/wallet_test.go(6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/onchain/wallet_test.go (5)
pkg/boltz/currency.go (2)
CurrencyLiquid(12-12)CurrencyBtc(11-11)internal/onchain/wallet/wallet_test.go (1)
TestWallet_BumpTransactionFee(30-52)internal/test/test.go (8)
WalletBackend(189-207)WalletCredentials(64-78)FundWallet(80-122)GetCli(217-223)GetNewAddress(257-259)SendToAddress(261-263)MineBlock(243-247)WalletMnemonic(51-51)internal/onchain/bitcoin-wallet/bdk/bdk.go (2)
WalletCredentials(968-971)Error(1152-1154)internal/onchain/wallet.go (2)
WalletSendArgs(42-47)ValidateWalletCredentials(131-146)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (2)
internal/onchain/wallet_test.go (2)
22-22: LGTM! Good test coverage expansion.Extending the test matrix to include both Liquid and BTC currencies is a solid approach to ensure wallet functionality works across both chains.
352-359: The test inconsistency is confirmed and appears intentional—verify the design decision.The two wallet backends have different validation strategies:
BTC: The
bdklibrary (Rust layer) validates that a provided descriptor matches the mnemonic's derived keys. When both credentials are provided with mismatched pubkeys,bdk.NewWallet()returns an error. Test expectsshouldError: true✓Liquid: The
lwklibrary validates descriptor syntax only vialwk.NewWolletDescriptor(), but does not validate that the descriptor's pubkeys match any provided mnemonic. The signer and descriptor are treated independently. Test expectsshouldError: false✓This difference allows Liquid wallets to import descriptors from external sources (hardware wallets, etc.) even when a mnemonic is also provided—the two don't need to derive the same keys.
Action: Confirm whether this design difference is intentional. If Liquid should enforce matching descriptors like BTC, update line 358 to
shouldError: true. If the permissive behavior is correct, consider documenting why BTC and Liquid differ.
| first := transactions[0] | ||
| count := len(transactions) |
There was a problem hiding this comment.
Add bounds check to prevent panic.
The test directly accesses transactions[0] without verifying the slice is non-empty. While walletTest funds the wallet, there's no guarantee transactions appear in GetTransactions before this assertion.
Apply this diff to add a defensive check:
+ require.NotEmpty(t, transactions, "wallet should have transactions after funding")
first := transactions[0]
count := len(transactions)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| first := transactions[0] | |
| count := len(transactions) | |
| require.NotEmpty(t, transactions, "wallet should have transactions after funding") | |
| first := transactions[0] | |
| count := len(transactions) |
🤖 Prompt for AI Agents
In internal/onchain/wallet_test.go around lines 204-205, the test accesses
transactions[0] without verifying the slice has elements; add a defensive bounds
check by validating len(transactions) > 0 before indexing, and if it's zero call
t.Fatalf (or t.Fatalf with a helpful message) so the test fails cleanly instead
of panicking; only proceed to inspect transactions[0] after that check.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/onchain/wallet.go (1)
136-138: Xpub deprecation logic is correct.The guard properly deprecates Xpub-only credentials and directs users to the newer CoreDescriptor/Mnemonic approach, aligning with the BDK integration.
Consider simplifying the error message punctuation:
- return errors.New("xpub is deprecated, use core descriptor and / or mnemonic") + return errors.New("xpub is deprecated, use core descriptor or mnemonic")The "or" is clearer since either credential type is sufficient (Mnemonic auto-derives the descriptor at lines 141-147).
This is a breaking change for existing Xpub-only configurations. Verify that migration documentation or upgrade notes explain how users should transition from Xpub to CoreDescriptor/Mnemonic-based credentials.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/onchain/wallet.go(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI (ubuntu-latest, 1.24.x)
|
TODO:
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
internal/onchain/bitcoin-wallet/wallet.go (2)
96-107: Fix nil dereference in panic path.If the network pointer is nil, line 105 will panic on dereference before the panic message can be formatted.
Apply this diff:
default: - panic(fmt.Sprintf("unexpected network %v", *network)) + panic(fmt.Sprintf("unexpected network %v", network))
158-167: External-call hazard: broadcast success but apply failure loses txid.After successful broadcast at line 159, if ApplyTransaction fails at line 163, the method returns
"", err, hiding the fact that the transaction was already broadcast. This can lead to unsafe retries and double-spends.Apply this diff to return the txid even when ApplyTransaction fails:
func (w *Wallet) broadcastTransaction(txHex string) (string, error) { txId, err := w.backend.cfg.ChainProvider.BroadcastTransaction(txHex) if err != nil { return "", err } if err := w.Wallet.ApplyTransaction(txHex); err != nil { - return "", err + // Transaction already broadcast; return txId to prevent unsafe retries + logger.Errorf("Failed to apply transaction %s to wallet state: %v", txId, err) + return txId, fmt.Errorf("transaction broadcast but apply failed: %w", err) } return txId, nil }
🧹 Nitpick comments (1)
internal/onchain/onchain.go (1)
55-60: LGTM! Consider refactoring to reduce duplication.The String() method is correct and provides a clean way to format Electrum connection strings.
Note: The logic in
internal/onchain/bitcoin-wallet/wallet.go:39-45(newChainClient) duplicates this formatting. Consider refactoring newChainClient to use this method:func newChainClient(electrum *onchain.ElectrumOptions) (*bdk.ChainClient, error) { - url := electrum.Url - if electrum.SSL { - url = "ssl://" + url - } else { - url = "tcp://" + url - } + url := electrum.String() logger.Debugf("Connecting to electrum server: %s", url) return bdk.NewChainClient(url) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/onchain/bitcoin-wallet/wallet.go(1 hunks)internal/onchain/onchain.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-11T16:16:33.431Z
Learnt from: jackstar12
Repo: BoltzExchange/boltz-client PR: 502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.431Z
Learning: In liquid-wallet implementation, the spentOutputs map uses caller-controlled locking pattern where SendToAddress holds write lock from createTransaction call through broadcast and marking inputs as spent to ensure atomicity. The createTransaction method operates under this protection without additional locking to maintain the atomic operation.
Applied to files:
internal/onchain/bitcoin-wallet/wallet.go
🧬 Code graph analysis (1)
internal/onchain/bitcoin-wallet/wallet.go (4)
internal/onchain/onchain.go (5)
ElectrumOptions(50-53)ChainProvider(40-48)RegtestElectrumConfig(67-70)Id(21-21)Output(35-38)internal/onchain/bitcoin-wallet/bdk/bdk.go (13)
Wallet(757-759)Network(1254-1254)ChainClient(683-685)NewChainClient(687-697)NetworkBitcoin(1257-1257)NetworkTestnet(1258-1258)NetworkRegtest(1259-1259)NewWallet(761-771)WalletCredentials(968-971)DeriveDefaultXpub(1411-1423)Balance(928-931)WalletSendResult(1008-1012)Error(1152-1154)internal/logger/logger.go (3)
Debugf(121-123)Infof(113-115)Errorf(97-99)internal/onchain/wallet.go (2)
WalletSendArgs(42-47)DefaultTransactionsLimit(11-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: CI (ubuntu-latest, 1.24.x)
- GitHub Check: Lint
🔇 Additional comments (3)
internal/onchain/bitcoin-wallet/wallet.go (3)
50-79: LGTM! Boltz fallback server properly configured.The NewBackend constructor correctly sets up Electrum server defaults and adds a fallback server for mainnet.
193-202: LGTM! Proper locking for atomic send operation.The sendLock ensures atomicity from transaction construction through broadcast and state application, consistent with the caller-controlled locking pattern used elsewhere in the codebase.
Based on learnings
239-265: LGTM! Transaction mapping logic is correct.The GetTransactions method properly maps BDK transaction types to onchain abstractions, with appropriate default limit handling and output mapping.
84af795 to
fb7eb72
Compare
seems to be more reliable
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
internal/onchain/wallet_test.go (2)
205-225: Guard against empty transaction list before indexing.Line 205 still reads
transactions[0]without verifying the slice has elements. IfGetTransactionsever returns an empty slice, this panics and the test aborts. Please restore the missingrequire.NotEmpty(t, transactions, ...)(or equivalent) before touchingtransactions[0]. This matches the earlier feedback that remains unresolved.transactions, err := wallet.GetTransactions(0, 0) require.NoError(t, err) require.NotNil(t, transactions) + require.NotEmpty(t, transactions, "wallet should have transactions after funding") first := transactions[0]
255-261: Strengthen bump-fee assertions (still treating hex as txid).The bump test continues to accept any non-empty string different from
txId. Our backend returns the raw tx hex, so the assertion remains vacuous—the bump can fail to broadcast and this still passes. Please decode the returned hex to a txid (or have the backend return the txid) and assert the replacement actually exists in the wallet/mempool after a sync, per the prior review.- require.Eventually(t, func() bool { - newTxId, err := wallet.BumpTransactionFee(txId, 3) + require.Eventually(t, func() bool { + newTxHex, err := wallet.BumpTransactionFee(txId, 3) if err != nil { return false } - return newTxId != txId + require.NotEmpty(t, newTxHex) + derived := test.BtcCli(fmt.Sprintf("decoderawtransaction %s | jq -r .txid", strconv.Quote(newTxHex))) + require.NotEmpty(t, derived) + require.NotEqual(t, txId, derived) + require.NoError(t, wallet.Sync()) + txns, err := wallet.GetTransactions(10, 0) + require.NoError(t, err) + return slices.ContainsFunc(txns, func(tx onchain.Transaction) bool { return tx.Id == derived }) }, 10*time.Second, 250*time.Millisecond)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/onchain/wallet_test.go(7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/onchain/wallet_test.go (4)
pkg/boltz/currency.go (2)
CurrencyLiquid(12-12)CurrencyBtc(11-11)internal/onchain/wallet/wallet_test.go (1)
TestWallet_BumpTransactionFee(30-52)internal/test/test.go (8)
WalletBackend(189-207)WalletCredentials(64-78)FundWallet(80-122)GetCli(217-223)GetNewAddress(257-259)SendToAddress(261-263)MineBlock(243-247)WalletMnemonic(51-51)internal/onchain/wallet.go (2)
WalletSendArgs(42-47)ValidateWalletCredentials(132-150)
🪛 GitHub Actions: Lint
internal/onchain/wallet_test.go
[error] 150-150: undefined: balance
🪛 GitHub Check: Lint
internal/onchain/wallet_test.go
[failure] 162-162:
undefined: balance (typecheck)
[failure] 161-161:
undefined: balance
[failure] 159-159:
undefined: balance
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI (ubuntu-latest, 1.24.x)
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
internal/onchain/wallet_test.go (2)
205-206: Add bounds check before accessing transactions[0].The test directly accesses
transactions[0]without verifying the slice is non-empty. WhilewalletTestwithfunded=trueshould ensure transactions exist, there's no guarantee they appear inGetTransactionsbefore this line.Apply this diff to add a defensive check:
+ require.NotEmpty(t, transactions, "wallet should have transactions after funding") first := transactions[0] count := len(transactions)
235-265: Verify broadcast and confirm the bumped transaction.The test polls for a successful
BumpTransactionFeecall but doesn't verify:
- The bumped transaction was actually broadcast to the mempool
- The returned value semantics (is it a txid or raw hex?)
- The bumped transaction is confirmed after mining
Consider these improvements:
- After the Eventually block succeeds, sync the wallet and verify the new transaction appears in
GetTransactions- After mining the block (line 263), verify the bumped transaction is confirmed and the original is no longer in the mempool
- If the backend returns raw hex rather than a txid, either decode the hex to get the txid for assertions, or rename the variable to
newTxHexfor clarity
🧹 Nitpick comments (1)
internal/onchain/wallet_test.go (1)
414-414: Clarify why ValidateWalletCredentials result is discarded.The validation function is called but its error return is discarded with
_. If validation is meant to prevent invalid credentials from reachingNewWallet, the error should be checked. If validation is optional/advisory here, consider adding a comment explaining why the result is ignored.If validation errors should be checked:
- _ = onchain.ValidateWalletCredentials(backend, &tc.credentials) + validateErr := onchain.ValidateWalletCredentials(backend, &tc.credentials) + if tc.shouldError && validateErr != nil { + t.Log("Validation error:", validateErr) + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/onchain/wallet_test.go(7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/onchain/wallet_test.go (4)
pkg/boltz/currency.go (2)
CurrencyLiquid(12-12)CurrencyBtc(11-11)internal/test/test.go (8)
MineBlock(243-247)WalletBackend(189-207)WalletCredentials(64-78)FundWallet(80-122)GetCli(217-223)GetNewAddress(257-259)SendToAddress(261-263)WalletMnemonic(51-51)internal/onchain/wallet/wallet_test.go (1)
TestWallet_BumpTransactionFee(30-52)internal/onchain/wallet.go (2)
WalletSendArgs(42-47)ValidateWalletCredentials(132-150)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI (ubuntu-latest, 1.24.x)
🔇 Additional comments (3)
internal/onchain/wallet_test.go (3)
21-21: LGTM! BTC support added to test suite.The addition of BTC currency enables comprehensive testing of the new BDK-backed Bitcoin wallet alongside the existing Liquid wallet tests.
154-163: LGTM! Balance verification after SendAll is correct.The test properly mines a block and polls for the wallet balance to reach zero, confirming that SendAll depleted the wallet. The balance variable is correctly declared on line 150.
418-421: LGTM! Logging improves test debuggability.The added
t.Logcalls for error messages and descriptors will help diagnose test failures.
Summary by CodeRabbit
New Features
Chores
Tests
Behavior