feat: lwk#504
Conversation
* feat: lwk server integration * feat: lwk `GetOutputs` implementation for MRHs * refactor: improve wallet tests * chore: .gitignore * chore: deprecate subaccount apis * feat: electrum backend * refactor: `getOwnWallet` -> `getGdkWallet` * fix: dont select subaccounts in cli wallet import not needed anymore * feat: `TxProvider` in wallet backend important to not broadcast via boltz * feat: lwk index persister * refactor: move full scan of wallets into seperate `Sync` function this way, we can create the wallet inside of a transaction with the other db operations and have the heavier sync not block the db * feat: transactions pagination * docs: clarify persister comments * fix: correct insufficient funds error want to be consistent with gdk * fix: encrypt wallets after logging in * feat: derive default descriptor before creating wallet in db users will see descriptor in credentials of hot wallets this way * fix: use `GetSendFee` from wallet interface in `checkBalance` * feat: implement `transaction.direct` status update (#497) * feat: implement `transaction.direct` status update * refactor: dont set mrh for external claim addresses this simplifies the handling of direct payments and gets rid of the problem where we cant verify the value of liquid transactions made to these transactions * fix: flaky gdk bump test * fix: remove manual GC run debugging leftover
* feat: lwk esplora concurrency * refactor: remove manual destroy calls leave that to go GC
* feat: spent txo map in lwk important to not double spend between syncs * refactor: lwk test package move to `liquid_wallet` package so we can access the spenOutputs in tests
* fix: direct tx tests for btc * chore: skip flaky gdk bump test
WalkthroughThis change introduces a new Liquid wallet implementation, integrating it into the system with support for wallet management, transaction creation, synchronization, and blockchain interactions. It adds backend configuration, database schema extensions, new Makefile targets, test coverage, and updates to RPC and gRPC interfaces to support Liquid wallets and deprecate subaccount features. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant RPCServer
participant DB
participant LiquidBackend
participant LiquidWallet
User->>RPCServer: ImportWallet(credentials)
RPCServer->>DB: Begin transaction
alt Liquid wallet and no descriptor
RPCServer->>LiquidWallet: DeriveDefaultDescriptor
end
RPCServer->>DB: CreateWallet (with lastIndex)
RPCServer->>LiquidBackend: NewWallet(credentials)
LiquidBackend->>LiquidWallet: Initialize
LiquidWallet-->>LiquidBackend: Wallet instance
RPCServer->>LiquidWallet: Sync()
RPCServer->>DB: Commit transaction
RPCServer-->>User: Success
sequenceDiagram
participant LiquidWallet
participant BlockchainBackend
participant Esplora/Electrum
participant DB
loop Sync Loop
LiquidWallet->>BlockchainBackend: Query UTXOs, transactions
BlockchainBackend->>Esplora/Electrum: Fetch data
Esplora/Electrum-->>BlockchainBackend: Data
BlockchainBackend-->>LiquidWallet: Data
LiquidWallet->>DB: Persist lastIndex if needed
alt UTXO count > threshold
LiquidWallet->>BlockchainBackend: Consolidate UTXOs
end
end
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 7
🔭 Outside diff range comments (1)
internal/rpcserver/router.go (1)
1394-1444: Consider handling sync errors more gracefully.The wallet import succeeds even if the sync fails at line 1443. This could leave the wallet in an inconsistent state where it's imported but not properly synchronized.
Consider either:
- Making sync errors non-fatal with a warning
- Providing a way to retry sync later
- Adding a wallet state to indicate sync status
- // TODO: maybe allow returning without sync here - return imported.Sync() + // TODO: maybe allow returning without sync here + if err := imported.Sync(); err != nil { + logger.Warnf("Initial wallet sync failed for %s: %v", imported.GetWalletInfo().Name, err) + // Wallet is still usable, sync will retry in background + } + return nil
🧹 Nitpick comments (13)
.gitignore (1)
30-31: Use a consistent directory-ignore patternLine 30 ignores
internal/rpcserver/test/walletwithout a trailing slash, while the new entry on line 31 adds the slash. Functionally both work, but mixing styles is noisy and may confuse future edits. Pick one convention (directory paths ending with/is common in this file) and apply it to both lines.-internal/rpcserver/test/wallet -internal/rpcserver/test/liquid-wallet/ +internal/rpcserver/test/wallet/ +internal/rpcserver/test/liquid-wallet/lwk (1)
1-1: Verifylwk’s license compatibilityPulling in a new submodule adds transitive license obligations.
Confirm thatlwk’s license terms are compatible with the BoltzExchange/boltz-client license before merging.internal/rpcserver/serializer.go (1)
231-239: Add explicit TODO for later removal of deprecated Subaccount code
//nolint:staticchecksilences the “deprecated” warning, but nothing in the code signals future removal.
Add a short TODO so the team tracks technical-debt clean-up once subaccounts are fully removed.-//nolint:staticcheck +//nolint:staticcheck // TODO(boltz#451): drop once subaccounts are removed from the API.gitmodules (1)
11-14: Trackingmainbreaks reproducible buildsSubmodules should normally be pinned to a commit hash.
Usingbranch = mainmeans everygit submodule updatepulls the latest commit, making CI/debugging nondeterministic and potentially introducing breaking changes without a version bump.Recommend:
-[submodule "lwk"] - path = lwk - url = https://github.com/Blockstream/lwk - branch = main +[submodule "lwk"] + path = lwk + url = https://github.com/Blockstream/lwk + # Pinned to <commit-hash>; update manually when bumping lwk + # branch = maininternal/onchain/wallet/wallet_test.go (1)
31-33: Test stability improvement with race condition mitigation.The addition of
t.SkipNow()and the 10-second sleep addresses race conditions related to transaction confirmation timing. However, consider usingrequire.Eventuallyinstead of a fixed sleep for more robust and potentially faster test execution.Consider replacing the fixed sleep with a more robust waiting mechanism:
-t.SkipNow() -// we sleep here to avoid a race where tx doesn't get confirmed by a block we mined, causing the tx to be confirmed -time.Sleep(10 * time.Second) +t.SkipNow() +// Wait for transaction to be in a suitable state for fee bumping +require.Eventually(t, func() bool { + // Add appropriate condition check here + return true +}, 15*time.Second, 250*time.Millisecond)internal/rpcserver/rpcserver_test.go (1)
1896-1896: Consider replacing sleep with proper synchronization.Using
time.Sleep(1 * time.Second)can make tests flaky and slow. Consider using a more deterministic approach like waiting for a specific event or state change.- time.Sleep(1 * time.Second) + // Wait for any pending operations to complete + require.Eventually(t, func() bool { + // Check for expected state or use a synchronization primitive + return true // Replace with actual condition + }, 2*time.Second, 100*time.Millisecond)internal/onchain/liquid-wallet/wallet_test.go (4)
69-73: Consider making timeouts configurable for CI environments.The timeout of
10*syncIntervalmight be insufficient in slow CI environments. Consider making this configurable or more generous.+ const testTimeout = 30 * time.Second require.Eventually(t, func() bool { balance, err = wallet.GetBalance() require.NoError(t, err) return balance.Total == amount && balance.Unconfirmed == amount - }, 10*syncInterval, syncInterval) + }, testTimeout, syncInterval)
126-129: Address the TODO comment.There's a TODO indicating that output address verification is not working correctly. This should be tracked and fixed.
Would you like me to open an issue to track this TODO for fixing the transaction output verification?
168-169: Consider extracting test transaction data.The
someTxconstant is a very long hex string. Consider moving it to a test fixture file or generating it programmatically for better maintainability.Also applies to: 265-265
241-241: Add delay explanation.Consider adding a comment explaining why the 100ms delay is needed between transactions.
+ // Small delay to ensure transactions are processed in order time.Sleep(100 * time.Millisecond)internal/onchain/liquid-wallet/wallet.go (2)
296-299: Consider more accurate Ready() implementation.The
Ready()method always returns true, but the comment mentions waiting for sync during initialization. This could be misleading if:
- Initial sync fails
- Wallet loses connection to backend
Consider tracking sync status and returning false if the wallet is not properly synced or connected.
Also,
BumpTransactionFeeshould document that it's not yet implemented for Liquid wallets.Also applies to: 307-309
459-462: Verify typed error support for insufficient funds
Our search didn’t find anyInsufficientFundserror types or constants in the lwk library, so the current string‐based check remains the only way to detect this condition and is fragile if the library’s message changes. Please manually confirm whether lwk exposes a dedicated error enum or code for insufficient funds. If it does, update the check to use that typed error; otherwise, consider requesting lwk to add one and document the existing string match with a clear comment.• Location: internal/onchain/liquid-wallet/wallet.go Lines 459–462
internal/nursery/reverse.go (1)
300-313: Add debug logging for rejected outputs.Consider adding debug logging to help troubleshoot why outputs are rejected, especially for amount validation failures.
if output.Value < swap.OnchainAmount { if err := boltz.CheckAmounts( boltz.ReverseSwap, swap.Pair, swap.InvoiceAmount, output.Value, swap.ServiceFeePercent, feeEstimations, true, ); err != nil { logger.Infof("Output from %s does not pass amount checks: %s", output.TxId, err) + logger.Debugf("Output value: %d, expected: %d", output.Value, swap.OnchainAmount) continue } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
pkg/boltzrpc/autoswaprpc/autoswaprpc.pb.gois excluded by!**/*.pb.gopkg/boltzrpc/autoswaprpc/autoswaprpc_grpc.pb.gois excluded by!**/*.pb.gopkg/boltzrpc/boltzrpc.pb.gois excluded by!**/*.pb.gopkg/boltzrpc/boltzrpc.pb.gw.gois excluded by!**/*.pb.gw.gopkg/boltzrpc/boltzrpc_grpc.pb.gois excluded by!**/*.pb.go
📒 Files selected for processing (34)
.github/workflows/test.yml(3 hunks).gitignore(1 hunks).gitmodules(1 hunks).golangci.yml(1 hunks)Makefile(3 hunks)cmd/boltzcli/commands.go(2 hunks)docs/grpc.md(1 hunks)internal/cln/cln.go(2 hunks)internal/database/database.go(1 hunks)internal/database/migration.go(2 hunks)internal/database/wallet.go(4 hunks)internal/lnd/lnd.go(2 hunks)internal/mocks/lightning/LightningNode_mock.go(2 hunks)internal/mocks/onchain/Wallet_mock.go(2 hunks)internal/nursery/nursery.go(2 hunks)internal/nursery/nursery_test.go(2 hunks)internal/nursery/refund.go(2 hunks)internal/nursery/reverse.go(5 hunks)internal/onchain/liquid-wallet/lwk/dynamic.go(1 hunks)internal/onchain/liquid-wallet/lwk/static.go(1 hunks)internal/onchain/liquid-wallet/wallet.go(1 hunks)internal/onchain/liquid-wallet/wallet_test.go(1 hunks)internal/onchain/wallet.go(3 hunks)internal/onchain/wallet/wallet.go(4 hunks)internal/onchain/wallet/wallet_test.go(2 hunks)internal/rpcserver/router.go(18 hunks)internal/rpcserver/rpcserver_test.go(8 hunks)internal/rpcserver/serializer.go(1 hunks)internal/rpcserver/server.go(2 hunks)internal/test/test.go(3 hunks)lwk(1 hunks)pkg/boltzrpc/boltzrpc.proto(4 hunks)pkg/boltzrpc/client/client.go(1 hunks)rust-toolchain.toml(1 hunks)
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.421Z
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.
pkg/boltzrpc/client/client.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
.gitignore (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
internal/nursery/nursery_test.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
internal/onchain/wallet.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.421Z
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.
internal/nursery/refund.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
.github/workflows/test.yml (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#451
File: .github/workflows/test.yml:33-36
Timestamp: 2025-06-02T18:32:38.175Z
Learning: The actions-rust-lang/setup-rust-toolchain GitHub Action has caching enabled by default (cache: true), and the cache-workspaces parameter works independently to specify workspace paths for caching without requiring explicit cache: cargo configuration.
internal/rpcserver/server.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
internal/rpcserver/rpcserver_test.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
internal/onchain/liquid-wallet/wallet_test.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.421Z
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.
internal/mocks/onchain/Wallet_mock.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.421Z
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.
internal/nursery/reverse.go (2)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.421Z
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.
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
internal/rpcserver/router.go (2)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.421Z
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.
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
internal/onchain/liquid-wallet/wallet.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#502
File: internal/onchain/liquid-wallet/wallet.go:425-442
Timestamp: 2025-07-11T16:16:33.421Z
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.
🧬 Code Graph Analysis (10)
pkg/boltzrpc/client/client.go (2)
internal/onchain/wallet/wallet.go (1)
Subaccount(52-58)pkg/boltzrpc/boltzrpc.pb.go (6)
Subaccount(5993-6002)Subaccount(6017-6017)Subaccount(6032-6034)SetSubaccountRequest(4619-4627)SetSubaccountRequest(4642-4642)SetSubaccountRequest(4657-4659)
internal/nursery/nursery_test.go (4)
internal/test/test.go (1)
InitLogger(143-145)internal/database/reverse.go (1)
ReverseSwap(17-49)pkg/boltz/fees.go (1)
FeeEstimations(31-31)pkg/boltz/currency.go (2)
CurrencyBtc(11-11)CurrencyLiquid(12-12)
internal/cln/cln.go (2)
internal/onchain/onchain.go (1)
Output(33-36)internal/lightning/lightning.go (1)
ErrUnsupported(28-28)
cmd/boltzcli/commands.go (3)
internal/utils/utils.go (1)
Satoshis(16-18)internal/onchain/wallet.go (1)
Balance(13-17)pkg/boltzrpc/boltzrpc.pb.go (3)
Balance(5929-5937)Balance(5952-5952)Balance(5967-5969)
internal/onchain/wallet/wallet_test.go (1)
internal/onchain/wallet/wallet.go (1)
MaxInputs(36-36)
internal/onchain/wallet.go (1)
internal/onchain/onchain.go (1)
Output(33-36)
internal/onchain/wallet/wallet.go (1)
internal/onchain/wallet.go (2)
Wallet(49-61)DefaultTransactionsLimit(11-11)
internal/lnd/lnd.go (2)
internal/onchain/onchain.go (1)
Output(33-36)internal/lightning/lightning.go (1)
ErrUnsupported(28-28)
internal/mocks/lightning/LightningNode_mock.go (2)
internal/onchain/onchain.go (1)
Output(33-36)internal/nursery/swap.go (1)
Output(32-39)
internal/mocks/onchain/Wallet_mock.go (2)
internal/onchain/onchain.go (1)
Output(33-36)internal/nursery/swap.go (1)
Output(32-39)
⏰ 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 (61)
rust-toolchain.toml (1)
2-2: Comment LGTM, but keep version drift in mindLocking to 1.81.0 is sensible for cache-hits; just remember to bump when
lwkmoves to a newer compiler.
No code changes needed.internal/database/database.go (1)
221-222: It looks like the migration forlegacyandlastIndexis already implemented ininternal/database/migration.go:ALTER TABLE wallets ADD COLUMN legacy BOOLEAN DEFAULT FALSE; ALTER TABLE wallets ADD COLUMN lastIndex INT; UPDATE wallets SET legacy = TRUE;No “version 16” bump is required here—this file uses a self-contained migration sequence rather than discrete numbered versions. All existing wallets will be marked as
legacy=true, preserving the intended backward-compatibility.All set!
docs/grpc.md (1)
1514-1514: LGTM! Proper deprecation marking aligns with subaccount removal.The documentation correctly marks the
subaccountfield as deprecated, which aligns with the coordinated deprecation of subaccount functionality across the codebase mentioned in the AI summary.cmd/boltzcli/commands.go (2)
1832-1832: LGTM - Appropriate use of linter directive for deprecated function.The
staticchecklinter directive is correctly applied to suppress warnings for theprintSubaccountfunction, which aligns with the deprecation of subaccount functionality mentioned in the PR objectives.
1912-1912: LGTM - Simplified wallet import flow.The removal of
selectSubaccountcall and always printing the total balance aligns with the deprecation of subaccount functionality. The change provides consistent user feedback regardless of wallet type.internal/onchain/liquid-wallet/lwk/static.go (1)
1-8: LGTM - Correct CGO static linking implementation.The build constraints and linker flags are properly configured for static linking:
- Build tag
static && !dynamiccorrectly excludes dynamic builds- Static library path
${SRCDIR}/liblwk.ais appropriate for build-time linking- Linker flags
-Wl,--no-as-needed -ldlensure proper static linking with dynamic loading support.github/workflows/test.yml (3)
23-24: LGTM - Required for lwk submodule integration.The addition of
submodules: recursiveis necessary to properly checkout the new lwk Git submodule introduced in this PR.
34-36: LGTM - Appropriate cache expansion for new Rust workspace.The addition of
./lwkto the cache-workspaces list is correct for the new lwk Rust library. This will improve CI performance by caching Rust dependencies for both the existing bolt12 and new lwk workspaces.
47-47: LGTM - Simplified regtest startup aligns with Makefile refactoring.The removal of
make download-regtestis consistent with the Makefile refactoring that replaced it withsetup-regtestand integrated the functionality into the startup process.internal/onchain/liquid-wallet/lwk/dynamic.go (1)
1-9: LGTM - Correct CGO dynamic linking implementation.The build constraints and linker flags are properly configured for dynamic linking:
- Build tag
dynamic || !staticcorrectly includes dynamic builds and default builds- Dynamic library linking
-llwkwith search path-L${SRCDIR}is appropriate- Runtime path
-Wl,-rpath=${SRCDIR}ensures library can be found at runtimeinternal/lnd/lnd.go (2)
77-79: LGTM - Appropriate no-op implementation for interface compliance.The
Sync()method returningnilis correct for LND since synchronization is handled internally by the LND node. This aligns with the wallet interface standardization across implementations.
404-406: LGTM - Consistent unsupported method implementation.The
GetOutputs()method correctly returnslightning.ErrUnsupported, which is consistent with other unsupported methods in the LND implementation likeGetTransactions()andBumpTransactionFee().internal/onchain/wallet.go (3)
11-11: LGTM: Well-defined constant for transaction limits.The constant name and value are appropriate for pagination purposes.
59-61: LGTM: Interface extension follows consistent patterns.The new methods
GetOutputsandSyncare well-defined and follow the existing interface patterns. TheGetOutputsmethod properly returns a slice of pointers toOutputstructs, and both methods have appropriate error handling.
78-78: LGTM: Simple and clear field addition.The
Legacyboolean field is a straightforward addition to the struct that aligns with the wallet credential requirements.internal/cln/cln.go (2)
102-104: LGTM: Appropriate no-op implementation for Lightning.The
Sync()method correctly returnsnilas a no-op, which is appropriate for CLN since Lightning nodes don't require explicit synchronization like onchain wallets.
358-362: LGTM: Correct unsupported operation handling.The
GetOutputsmethod appropriately returnslightning.ErrUnsupportedsince CLN is a Lightning implementation that doesn't manage individual UTXOs. The method signature correctly matches the interface and the fix for the missing closing brace resolves the syntax issue.internal/nursery/refund.go (1)
35-35: LGTM: Method call simplified correctly.The removal of the empty string parameter from
checkExternalReverseSwapsaligns with the method signature change mentioned in the AI summary, making the call cleaner and more focused.internal/nursery/nursery_test.go (1)
111-221: LGTM: Comprehensive test coverage for direct output selection.The test function provides excellent coverage with well-structured table-driven tests covering three important scenarios:
- Match: Successfully finding a suitable output within the expected range
- NoMatch: Handling cases where no suitable output exists
- Overpaid: Handling overpaid scenarios gracefully
The test data is well-constructed with realistic
ReverseSwapconfigurations, fee estimations, and output values. The use ofrequire.Eventuallypattern would be beneficial here if the tested method involves any asynchronous operations, but the current synchronous assertion approach is appropriate for this logic.internal/onchain/wallet/wallet_test.go (1)
262-275: LGTM: Improved test reliability with require.Eventually.The replacement of the manual timeout loop with
require.Eventuallyis an excellent improvement. This approach:
- Eliminates the need for explicit channel management
- Provides cleaner error reporting
- Uses consistent polling intervals
- Reduces complexity in the test logic
The 15-second timeout with 250ms intervals is appropriate for blockchain-related operations.
internal/database/migration.go (3)
24-24: LGTM on schema version increment.The increment from 15 to 16 is correct for the new migration.
591-592: Good addition of logging for consistency.Adding the
logMigration(oldVersion)call to case 14 maintains consistency with other migration cases.
598-608: Migration columns look safe—confirm legacy column is read by your queriesWe’ve verified that:
lastIndexis represented assql.NullInt64in internal/database/wallet.go, and all codepaths check.Validbefore using the value.- The DDL sets
legacy BOOLEAN DEFAULT FALSEand immediately flips existing rows to TRUE, so old wallets are correctly marked while new ones default to FALSE.Please double-check that your data-access layer actually selects and scans the new
legacycolumn so it’s surfaced in your Wallet structs. In particular, review:
- internal/database/wallet.go – ensure your SELECT clauses and
Scan(...)calls include thelegacyfield.- internal/rpcserver (or wherever you map DB rows to RPC models) – confirm the
Legacyflag is being loaded and returned.internal/onchain/wallet/wallet.go (3)
770-773: Good refactoring to use shared constant.Replacing the local
GetTransactionsMaxLimitwithonchain.DefaultTransactionsLimitimproves consistency across the codebase.
858-858: Consistent usage of shared constant in BumpTransactionFee.The replacement of hardcoded limits with
onchain.DefaultTransactionsLimitis consistent with the interface changes.Also applies to: 868-868, 871-871
509-511: Appropriate no-op implementation of Sync method.The no-op implementation is appropriate for this wallet type and satisfies the interface requirement. The method can be enhanced in the future if synchronization logic is needed.
internal/test/test.go (3)
50-50: Good addition of WalletId constant.Adding the
WalletIdconstant and using it in theWalletCredentialsfunction provides consistency across tests.Also applies to: 59-59
66-105: Well-implemented FundWallet function.The
FundWalletfunction is well-structured with:
- Early return if wallet already has funds
- Proper error handling
- Reasonable timeout (15 seconds)
- Clean separation of concerns
The function improves code reusability and maintainability.
135-137: Good refactoring of InitTestWallet.Replacing the inline funding logic with the
FundWalletfunction call improves code organization and reusability.internal/nursery/nursery.go (3)
252-252: Appropriate parameter removal.Removing the
TxIdparameter from thecheckExternalReverseSwapscall simplifies the interface and aligns with the refactoring mentioned in the AI summary.
368-380: Well-implemented fee estimation method.The
GetFeeEstimationsmethod is well-structured with:
- Clear separation of concerns
- Proper error handling for both currencies
- Clean return of fee estimations map
- Appropriate error propagation
This consolidates fee estimation logic effectively.
382-388: Good refactoring of CheckAmounts method.The refactoring to use the new
GetFeeEstimationsmethod improves code organization while maintaining the same functionality. Error handling is preserved correctly.internal/rpcserver/server.go (2)
19-19: Appropriate imports for Liquid wallet backend.The addition of
databaseandliquid_walletimports are necessary for the new backend initialization.Also applies to: 23-23
210-223: Well-implemented Liquid wallet backend initialization.The backend initialization is well-structured with:
- Proper configuration setup using server's network and data directory
- Appropriate use of existing onchain Liquid transaction provider
- Database-backed wallet persister
- Conditional Electrum configuration based on availability
- Proper error handling and propagation
The initialization follows established patterns and integrates well with the existing server architecture.
pkg/boltzrpc/boltzrpc.proto (1)
108-108: Consistent deprecation of subaccount functionality.The deprecation of subaccount-related RPCs, fields, and messages is properly implemented following protobuf conventions. This maintains backward compatibility while signaling to users that these features should no longer be used.
Also applies to: 113-113, 722-722, 747-747, 754-754, 759-759, 900-900
internal/rpcserver/rpcserver_test.go (4)
20-21: LGTM! Clean import additions.The new imports for liquid wallet packages are properly organized and necessary for the new wallet implementation and serialization.
Also applies to: 34-34
1161-1186: Improved wallet retrieval and funding logic.The refactored
fundedWalletfunction now properly checks for existing wallets before importing, avoiding duplicate imports. The funding logic withrequire.Eventuallyis more robust than the previous implementation.
1740-1762: Test updates align with new wallet implementation.The test correctly validates both mnemonic and the new CoreDescriptor field after wallet creation. The removal of the deprecated
SetSubaccountcall is appropriate.
3134-3171: Excellent backward compatibility test.The test properly validates that wallets can switch between the new Liquid wallet implementation and the legacy implementation based on the database flag. This ensures smooth migration paths for existing users.
Makefile (3)
50-55: Proper Rust-Go binding generation setup.The use of uniffi-bindgen-go with a specific version tag ensures reproducible builds. The conditional installation of the tool is a good practice.
96-101: Efficient conditional build for lwk library.The build target properly checks for the existence of the library before building, preventing unnecessary rebuilds. Copying both
.aand.sofiles ensures compatibility with different linking scenarios.
68-86: Improved test isolation with wallet data cleanup.The addition of
clear-wallet-dataensures clean test runs by removing any persisted wallet data. This prevents test pollution and makes tests more reliable.internal/database/wallet.go (2)
10-41: Well-structured persistence layer for Liquid wallet.The
WalletPersisterprovides a clean implementation of theliquid_wallet.Persisterinterface with proper error handling and null value support for thelastIndexfield.
84-106: Robust null handling for optional LastIndex field.The use of
sql.NullInt64properly handles nullable database columns, and the error wrapping provides better context for debugging.internal/mocks/onchain/Wallet_mock.go (1)
204-264: LGTM!The auto-generated mock methods for
GetOutputsandSyncfollow the established patterns correctly.Also applies to: 601-643
internal/mocks/lightning/LightningNode_mock.go (1)
446-506: LGTM!The auto-generated mock methods for
GetOutputsandSyncare correctly implemented.Also applies to: 1130-1172
internal/onchain/liquid-wallet/wallet_test.go (5)
22-48: LGTM! Well-structured test helpers.The helper functions provide clean test setup with in-memory database and proper configuration.
116-119: Good test coverage for spent outputs tracking.The test properly verifies that spent outputs are tracked and cleared after sync, preventing double-spending.
Also applies to: 149-156
189-201: LGTM! Clean wallet creation helper.The helper function properly handles credential generation and wallet initialization.
203-225: Excellent test for address generation and persistence.The test thoroughly verifies address uniqueness and proper index persistence.
245-261: Well-designed consolidation test.The test effectively verifies automatic UTXO consolidation behavior. The balance verification logic correctly accounts for the consolidation transaction.
internal/rpcserver/router.go (5)
72-72: LGTM!The addition of
liquidBackendfield to the server struct is appropriate for managing Liquid wallet operations.
1042-1050: LGTM!The condition correctly ensures both
claimAddressandwalletIdare present before generating the address signature, which is necessary to prove ownership of the claim address.
1751-1776: LGTM!The fee estimation logic is properly updated to be currency-aware, correctly using the wallet's currency for fee rate estimation and dummy address selection.
2396-2401: LGTM!The
loginWalletmethod correctly routes between liquid wallet and GDK wallet implementations based on currency and legacy flag.
2029-2036: Good error handling for wallet sync during unlock.The implementation correctly handles sync failures by logging errors without preventing the wallet from being added. This allows the wallet to be used even if initial sync fails, with the sync loop retrying later.
internal/onchain/liquid-wallet/wallet.go (5)
20-37: LGTM!The
Persisterinterface and struct definitions are well-designed. TheWalletstruct properly includes synchronization primitives and thread-safe spent outputs tracking.
77-131: LGTM!The
NewBlockchainBackendfunction has proper validation, sensible defaults, and good error handling. The network-specific configuration is well-structured.
226-240: Excellent implementation of the sync loop!The random jitter (0.75-1.25x interval) is a thoughtful addition to prevent traffic spikes when multiple wallets sync simultaneously. The error handling appropriately logs failures without crashing the sync loop.
413-501: Excellent implementation of spent output tracking!The caller-controlled locking pattern is correctly implemented:
SendToAddressholds write lock throughout the entire operationGetSendFeeuses read lock for concurrent accesscreateTransactionoperates under the caller's lock protectionThis ensures atomicity and prevents double-spending of UTXOs.
336-350: LGTM!The address generation correctly ensures uniqueness by persisting the incremented index immediately after generating the address. This prevents address reuse even in case of crashes.
| @@ -0,0 +1 @@ | |||
| Subproject commit 3a6b16400e9b7bf08f48cbff7ed9bfa53db0eb99 | |||
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Pin to a signed release tag & track drift with CI
The submodule currently points to an arbitrary commit.
For easier provenance auditing and CVE tracking, consider moving to a signed release tag (e.g. lwk-vX.Y.Z) and add a CI check that fails when the pointer drifts.
🏁 Script executed:
#!/bin/bash
# Fails if the chosen commit does not correspond to any tag.
git ls-remote --tags https://github.com/Blockstream/lwk.git \
| grep 3a6b16400e9b7bf08f48cbff7ed9bfa53db0eb99 \
|| { echo "Commit is untagged – confirm this is intentional."; exit 1; }Length of output: 227
Pin the lwk submodule to a signed release tag & enforce drift detection
The current submodule pointer (commit 3a6b16400e9b7bf08f48cbff7ed9bfa53db0eb99) is untagged, making provenance auditing and CVE tracking harder.
• Location:
– File: lwk (submodule declaration at line 1)
• Action items:
- Update your submodule reference to use a signed release tag, for example:
[submodule "lwk"] path = lwk url = https://github.com/Blockstream/lwk.git tag = lwk-vX.Y.Z
- Add a CI check that fails if the checked‐out commit drifts from any signed tag, for example:
# Fail if the current submodule commit isn’t tagged git ls-remote --tags https://github.com/Blockstream/lwk.git \ | grep -q "$(git submodule status lwk | awk '{print $1}')" \ || { echo "❌ lwk submodule is not at a signed tag"; exit 1; }
This ensures clear, auditable provenance and automatic drift detection in CI.
🤖 Prompt for AI Agents
In the lwk file at line 1, the submodule is currently pinned to an untagged
commit, which complicates provenance auditing and CVE tracking. Update the
submodule declaration to reference a signed release tag by adding a 'tag =
lwk-vX.Y.Z' line under the lwk submodule section. Additionally, implement a CI
check script that verifies the checked-out submodule commit matches a signed tag
by using git ls-remote and grep commands, and fail the build if it does not,
ensuring automatic drift detection.
| //nolint:staticcheck | ||
| func (boltz *Boltz) SetSubaccount(walletId uint64, subaccount *uint64) (*boltzrpc.Subaccount, error) { | ||
| return boltz.Client.SetSubaccount(boltz.Ctx, &boltzrpc.SetSubaccountRequest{Subaccount: subaccount, WalletId: walletId}) | ||
| } | ||
|
|
||
| //nolint:staticcheck | ||
| func (boltz *Boltz) GetSubaccounts(walletId uint64) (*boltzrpc.GetSubaccountsResponse, error) { | ||
| return boltz.Client.GetSubaccounts(boltz.Ctx, &boltzrpc.GetSubaccountsRequest{WalletId: walletId}) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Mark methods as deprecated and hide from GoDoc
You suppressed staticcheck, but callers (and IDEs) still see these methods as first-class.
Add Go doc // Deprecated: prefix so users migrate away without relying on linters.
-//nolint:staticcheck
-func (boltz *Boltz) SetSubaccount(...){
+// Deprecated: Subaccounts are being removed; prefer GetWallet / CreateWallet.
+//nolint:staticcheck
+func (boltz *Boltz) SetSubaccount(...){Apply the same to GetSubaccounts.
📝 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.
| //nolint:staticcheck | |
| func (boltz *Boltz) SetSubaccount(walletId uint64, subaccount *uint64) (*boltzrpc.Subaccount, error) { | |
| return boltz.Client.SetSubaccount(boltz.Ctx, &boltzrpc.SetSubaccountRequest{Subaccount: subaccount, WalletId: walletId}) | |
| } | |
| //nolint:staticcheck | |
| func (boltz *Boltz) GetSubaccounts(walletId uint64) (*boltzrpc.GetSubaccountsResponse, error) { | |
| return boltz.Client.GetSubaccounts(boltz.Ctx, &boltzrpc.GetSubaccountsRequest{WalletId: walletId}) | |
| } | |
| // Deprecated: Subaccounts are being removed; prefer GetWallet / CreateWallet. | |
| //nolint:staticcheck | |
| func (boltz *Boltz) SetSubaccount(walletId uint64, subaccount *uint64) (*boltzrpc.Subaccount, error) { | |
| return boltz.Client.SetSubaccount(boltz.Ctx, &boltzrpc.SetSubaccountRequest{Subaccount: subaccount, WalletId: walletId}) | |
| } | |
| // Deprecated: Subaccounts are being removed; prefer GetWallet / CreateWallet. | |
| //nolint:staticcheck | |
| func (boltz *Boltz) GetSubaccounts(walletId uint64) (*boltzrpc.GetSubaccountsResponse, error) { | |
| return boltz.Client.GetSubaccounts(boltz.Ctx, &boltzrpc.GetSubaccountsRequest{WalletId: walletId}) | |
| } |
🤖 Prompt for AI Agents
In pkg/boltzrpc/client/client.go around lines 118 to 126, the SetSubaccount and
GetSubaccounts methods are marked with nolint for staticcheck but lack proper
deprecation comments. Add a Go doc comment starting with "// Deprecated:"
immediately above each method to indicate they are deprecated and guide users to
migrate away. This will make the deprecation visible in GoDoc and IDEs, not just
linters.
| msg := strings.ToLower(err.Error()) | ||
| errors := []string{"transaction already in block chain", "transaction outputs already in utxo set"} | ||
| if !slices.Contains(errors, msg) { | ||
| handleError("Could not broadcast transaction: " + err.Error()) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use error types instead of string comparison for better reliability.
Comparing error strings is fragile and can break if the error messages change. Consider using error types or wrapping these errors in typed errors.
If these are standard errors from the blockchain libraries, check if they expose specific error types. Otherwise, consider creating constants for these error patterns.
🤖 Prompt for AI Agents
In internal/nursery/reverse.go around lines 186 to 189, the code compares error
messages as strings, which is fragile and unreliable. Refactor this by
identifying and using specific error types or error variables provided by the
blockchain libraries instead of string matching. If such types are not
available, define error constants or sentinel errors for these cases and compare
errors using errors.Is or errors.As to improve robustness and maintainability.
| if err := tx.SetReverseSwapClaimTransactionId(swap, output.TxId, 0); err != nil { | ||
| return fmt.Errorf("set paid at: %s", err) | ||
| } |
There was a problem hiding this comment.
Fix error message to match the operation.
The error message says "set paid at" but the operation is setting the claim transaction ID.
if err := tx.SetReverseSwapClaimTransactionId(swap, output.TxId, 0); err != nil {
- return fmt.Errorf("set paid at: %s", err)
+ return fmt.Errorf("set claim transaction id: %s", err)
}📝 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.
| if err := tx.SetReverseSwapClaimTransactionId(swap, output.TxId, 0); err != nil { | |
| return fmt.Errorf("set paid at: %s", err) | |
| } | |
| if err := tx.SetReverseSwapClaimTransactionId(swap, output.TxId, 0); err != nil { | |
| return fmt.Errorf("set claim transaction id: %s", err) | |
| } |
🤖 Prompt for AI Agents
In internal/nursery/reverse.go around lines 352 to 354, the error message
incorrectly says "set paid at" while the operation is setting the claim
transaction ID. Update the error message in the fmt.Errorf call to accurately
reflect the operation, such as "set reverse swap claim transaction ID" or a
similar descriptive phrase matching the function
SetReverseSwapClaimTransactionId.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
internal/onchain/liquid-wallet/wallet.go (1)
270-294: Replace hardcoded consolidation fee rate.The auto-consolidation logic uses a hardcoded fee rate of 0.1 sat/vbyte which is too low and can cause transactions to be stuck during high fee periods.
🧹 Nitpick comments (3)
internal/onchain/liquid-wallet/wallet_test.go (1)
116-116: Avoid accessing private fields directly in tests.The test accesses the private
spentOutputsfield directly, which couples the test to implementation details and makes refactoring harder.Consider adding a public method to the Wallet type for checking spent outputs status in tests, or use the public API to verify the behavior indirectly through balance checks or transaction attempts.
Also applies to: 149-149, 161-161
internal/onchain/liquid-wallet/wallet.go (2)
226-240: Document the randomization factors for sync interval.The magic numbers used for randomizing sync intervals (0.75 and 0.5) should be documented or extracted as constants.
+// syncJitterMin and syncJitterRange define the randomization bounds for sync intervals +// to avoid traffic spikes. The actual interval will be between 75% and 125% of the configured interval. +const ( + syncJitterMin = 0.75 + syncJitterRange = 0.5 +) + func (w *Wallet) syncLoop(ctx context.Context) { for { // avoid traffic spikes if a lot of wallets are using the same backend - sleep := time.Duration(float64(w.backend.cfg.SyncInterval) * (0.75 + rand.Float64()*0.5)) + sleep := time.Duration(float64(w.backend.cfg.SyncInterval) * (syncJitterMin + rand.Float64()*syncJitterRange))
307-309: Document or implement fee bumping functionality.The
BumpTransactionFeemethod returns "not implemented". Please either:
- Add a comment explaining why fee bumping is not supported for Liquid wallets
- Add a TODO comment if this is planned for future implementation
- Implement the functionality
Would you like me to help implement fee bumping or create an issue to track this?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/onchain/liquid-wallet/wallet.go(1 hunks)internal/onchain/liquid-wallet/wallet_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#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.
internal/onchain/liquid-wallet/wallet.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#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.
⏰ 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)
| }, 30*syncInterval, syncInterval) | ||
| } | ||
|
|
||
| const someTx = "0200000001015a75b8607566159620994af84594a6b6d903aebbcf59d1382771694bf611eb8a0100000000fdffffff030b59cf2adff4bf2e7d3b8a36816d9fe3c675f2ba6d7198c35195d8d4868578f22808f5bee2a15d01413e36374ff5dc21224b230e9d70f247fcc85712f63fd914074d02aed6c7e3d0008a693a7bccaeb3aac96827cd5af5bbfbd75b2c6a549a0635914116001436532949385ba612cb7b9b3a12e2363666c5a1f40a8c5e056e5c43e77c3f3b49642e1a992e8552b74a9635cdf0e37751709314bf0d09b5b6e450adb2b9f95bc86f0d16279a58734f3f2ed8de73e85489f1ad318b912c02d48e551f064e5e9661f6317f354ccd15184c2b6f0dacf9c3160caf5819aadce11600141961af5494492cb063ddd798b6ac73274f5f65440125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000001a00009e0000000000024730440220263f44bba67dfd29e45d6fef6e360e9f9605dbb8438e7b7c663c36dd40126436022054b1018aad29ea345bdcdb8de501e9bbc5f15a66851c8241f132cc42f01bd69c012103797c2f42feaf60680bf31ad2b2edc371a545f3cc96f95a900f5f0cf45aad0d8d004301000185ef088f87f452468e84ea5e7b2750c7664d2ed849bb5b9154414780e367c23c25d180eef6dfc4f8aa5a54116a5567b1f6b6c15059959f4db7872c6d0585ed7ffd4e1060330000000000000001856ba700f8aae132c01f52c60e60029e1341dcd575d1c4aa58d509f791697f9b482aa194f5dd647635d23f2dd019adf96762907d4f8b7047e1af12ff82ba5e69c00dedd7583c2b91ba3ec0040ee9fad69ae0bf58a49827056638fa4564a6e7de3f929ed6e93bbcd405b1cf9d967bebde29fd061da7e70be1464a47806add1f103d8ef9eb5c487cb82befdb60e0c620919ada15f6cf4fc443d2191e984a4c164627270af6a81380599285a4ee01c92c8991203e03478563d850bf86f8ff885216f6830d9cc845cfd1710f34fc61994a6c590085a9e73e637a6d00075d7d5ef893ac963d2ab9d21d9104cfd92d96e4818568411f18e9c28a353cd726e27e7e35d98d37240ab3317e5b5c352c9aa9ccecc3806a83008a89eeb255199c546a3132be478c991f07c12c5b386e03150f7bcb302e8ac84a14ee7544b75f78cd730a17df38cb7beb61b4ac716a69946da88bec0fb417eab843fc16758eb38822716f3ee9d0a77df7a78721fc6e4849d279a3aacfc3750ea8630657e16163e00ba1e26d60adfe9b4302d557e3b5a721f315e9d187701e7f29ff40c7456fb16e2622f7bc980b46d200bf720de5388b27e80fd61839aa1158d96059da6b16ebf857fbdff7559858bf9b56a28ef62f328f16c1f3010c2966ae812a7846570ef186a03fbf246e4b97a30ca1b728550a47d3589d00b8f17b2fb399c7028258f877af9cddb6912604d724fd08a6e1c241e0d52e2d00ba8943f37d15847743272bbc349d419c4efe0eb822e572d5d84c7a609c010cb15f1bdb10c2fa70cfb58e2fb8e360679ec62c2aee7456755032f74cac7defe0030d431182fdfa00af3a36673ecf0cf30fffa4f6266c36c915e76c196a070eb14a097d72fffb4135c30516d54e43d385f73b9c75059d0406c0eb9b53f6737fcb7216486f1247a3513c49195223bc26a1609aed453e6343cc87c4f1224b2c29cdf0059089e79e88193eba1cf9659dc4049752a1c3903141d0aa99a0fbdecb65bb08b21a0ea390402295c28936b6948ffdfb9d2ea3a851630940e9b476d243e408516950e4373da18e2eb3a01e03061754df134abd099e64993c19b9ad20ece54553269db69be5862c77e06ac92e30b2bcc63be9098b0e1d557107b65a19cb7a765a6dff57abf5698e59a00432a7a964f6abe56d418d3c677db439882da38f8a9d29b69ac00676122b18598aa4f3b21b4f0f661219fd02be718ac1c3b3332ab25261e2df57f9493c96120e6393a0e7a9330aaf019f25476bd27e3c44340fe20cc45b82c5bfe0919dbe748eaaca484fc259c140bca1a9b96d96bc05112e9e1995e519360a88e2b8758b7f90fdc70079e9039757f387cf77bbed041ebb92037ecbb4b178a996012f58e0caa50fbeff7977b3779afe413bcee07aee9624ce45f2522d5cff987c9201562bdc8ddfec083c2a74e79323708cef604732c07b767472f94d9d16b6df50105633047e7fc81f533398b4ab7a7b3ce9a9699e57dddad9196862a47d99d8e1548fbd1d404875644b82f1376e323cd743afd17b48a6834edc8abeb169b055d7fef8b127d91d18b99dbce77b3edfe19305ea8aa6a3be2dc849ea905ac8615f8c31ed717f09c963a99c2acb40a470248e2c649c4b55ef7931a642dff38909c1a127ee1778bf6b98ea8aa62a168203d59a23854881bdd521c8b27519f7e51d3b8c13c503d9db8620d50b111fb0181739338c43c0865916f474d92ca64c13d4b00fe72e79b9c799d48ac066e0f33fd4bf8dfa1c0445077827e02e5ba0652e139fc8ca9833454b0dd86b6751c55423e915053866bd22f2a24e2dc1aab797f2413b6f286ff288f7b47ac2d68af8da8ac0c64a0a080ea7e81786cc888f715ad6d649a66ece9129232ecbadfce552e6e98e3b207a3b47671fa90564a1527fb9e28b4ae1b7b958a3abdebfd5eeac9c4e8f9896a403335a5591e4b6a056094a8f6bc744b5b3c01431754286fa85079717fa51421b3a810f3e0ed2d6d1b38a9cc91ff5283cc7b9e1a6c9183f56e159d1a855184672ec3ee0a675289d40fd5766aeb29c03fabd0b5a8cbd4182fdd93ca45a9e002a43728e60123b2e61bf384ac397c30bf8f0ccf5bc58596c0e073f5b0aa3cfcbd4e6a5649317cdabc72012a607564dc4bc2a7745f59ed18a8fc644bbd4349597474847bb8309e2412df79241f1df4626b75ade4fead30326c2584999b0b4c142b1a9307767fd1ba1f080cd63280fc075c36f483f3f67a00444ade453812843f05090c3edb8a8505f9676074f5b4906b8ea8620254d37234147dbe97202596361af7c7d275463ba8a940e0045fa461e4a2875deab7b5efce9474899d126a3cd7844707e69810f61354d92a5dfabce6e6f880a707815b1373329be135405df8166f7a39f5564594767294cce489c92f47039a59615dc4380bca06262820ab13e299ea8c620011c4612014ed4c50e5de23bd15b3ba53b99f00781fc55ad83fa685843776a210e64283ee9476e48b7450ebd7461477a94e93fcc679beccbc71707b3030f14546bc919e5d0875ffce5a99840f0ba1ecccdfbe211faa2853f2ba4d74f760d9b09ae7908d14cdbd0a5fdf040c0fc6b9291389d9f973f5cebe875d54f53a414d881205927722d9c6bdb0ccd5cfb1f371a3ec0198486a54f10a68942fda45522aaa9c07d8345cec7e01baa8ddadb994d4d271b3821c2cba6dcfe2df9b9b43aded6406d1e9b55eaf822dd5e5b684b6d9a8c6d829df1654b033b96730a4cf74f1e1d32f05adb4ba83e23427e6c4049f6633859f0ef3542a3854e2d36bc36a2d0d6d7d24de3f527ded1dcedad5b19be0e1961304de66e73a9d3cfe21802f15f1563eaa436ec783fd11cf9cae7fe9e5168d507c4c59934fc2afb7d2aed192c17d64db828d43e86781d2ee3bc1e327f662442d390f4bb5c1da3b1d4450ada85d8db5c85d275789ba37e47e35ece57931c580da5db2d20d252f223b43d9bd4617d113e39d194305989f44609bb2c45b1b1e14dc62fcbdbb3d07b271abb6f097571d94e913687bcfbb24f9a8320e14e6f564414083dede15eb54cb0bc376117d5b72baddfc42cd274a5690f46ce633894d9574ea21d1599d9fa97b2ad328960e93257b26d932594592c76cfa3854059aad8ffe9c4e1d943772db91450f6c40c18c7c3d94f16b340a12213efdd99f74fdfc2d0d60f8e256804a7fe7af0a97f4f1680d3c855c1351bf97544af0bb738a28ec8e97c7d2e732926d7bcb1ab0d628c987cea474d50f2d0a6e93fac257200e5b50f112f9edf861f1b8752dd0a587e22185d9efff531571273f968f3d3fe111a4762564b1d8c702e2c997716011d1cc4580d3bab762e5787f9f3699a190735f94a4dc8def2989e966275b35ceda21f9cb707953e393d46ee797306144bcacd2c7acd734faea356669c97caf03617146413a85580ca67f5a9cdece60c8e385f5d028c11f919dbc2b3152aa81e785ae69e9ae565f3a3ed42d3063472f5faed7ff139529380f9311f4f8c89ab1868cd90b251624fb5644fa2d5defc456fa14621b79c133b60d49b1a2699dfcfb431459b5a3aa74e378071bed4ad98f74bd9f3e748cb66de8e264930632e592c3f9e3dfb96af9dd212e7f697fcbbd930e6ed377bbe1bbf580584c35e4aa5b2dca6232bc6cee69d47c6ff43825526201ffeb2a48af6594d80ea58be562ddd80ad0c53e9166e6dda5b5f581192a8fca643a371d69703d7e795b9e40573209a6c549f1dd80ad79ec42eb29f64f80c525d6a80734e9afee5b1c3d245c44601a113682ac72bf88144c771bd906c8b005706cfdbedf1d06ff0b52fbd6f848357765c3dbb4677d09c08353bad7e35a0453bba554e5cb074677efc0f1eb4ee6e7aa39a2463453dc0c2720325a03254d95790b1a8493e565b9ccd8bf8d4528f55184e3e4baf837dbd741b28c81030ea50070ec83693afb50d97ae8d2903fa021c2c358d76bcf7b4fe71580db877c8ff9d6a3555a0ce1fab974a6c7d6e6047002a342184209a2363f2ede37faf6596db9964d593c29e933b9e8ad8df31396054aae6a890aa8153460b8178fd3c5eeb071a3b95e3bc9fa9a341b79436c1146fe5c8bfeb6b01aa7dd1e48cb511d7717fe2122710705578aea833c48e38c520fb80cf8ba0e28c0d7599d4a7366f1df7f54327fe11d1a89e4673145344d527ad757453fc420bb69a483825269f83474f384c588b0de49e61fa9b02c7087fe004a721660920048c7607ab2602b2e6c1c59b9b50e70d200aae2d4d89a446f082ed9d6e695bf2706742bbb272be8d324c6584341fe9d71c63ce8b38e8dea9ef1c3a90cc777a1452d2a79679577c804a71a7d37aa5cc8e8cb6c4c0f7500ebeba6942e77765dc35a5e199165cd84e86ddca7df1601533620f4f00bb51bdb798afe8e28d0593ab8df2534d8ee5ccabf57df6ce9d138f51f63a8b6ca2bfc9b2628a8e663bbe59660fb3e40ee25676d1d0ba4ca4be2e59a6f988a716c4ab14afe9c01d5dbad350c0502102ae24820aef01aa3d047c716123a663efa319273753282225d016325711446f3c15cd33f3ba2a9fb2b8d20cb34068be64065b37efce56facb7ff04371f3b2f3f5c1de6cb87282e8bf80c9d0d748d1b6b54b2a5289734a47035d35ae42de768ee3ea17d235db3ad493fff6ebf0d3f1dde3ec7c045d420956555f865d4c3c1f91c1994c95ab6c0b1e36b56c0f419ad9281c2901752ca04231d7a623202d054855f0ba70511a4ca957e50800653316387d6fbaf939b8a53338e9aec607bee81218b440b5cde69ac72cecb77cd188e77c8b6a161b74977bdea693881276c18f9098cd6d56f4b11fc106153a4e1283746f71e584b6018c7084f407814919e41ed7829950ec30a75c1067f7c37579a4bdeac145ca1138b0877df53c2101f328365b650ca1709ba3b0af78e8a97c8acc003811e8de4bcc2b3553ea4319eb4bed5db8b11d6950914650eea444cf86a215e3b96441883cc8555489d0ddfb7a43d327b84d1262bf91a280867dec44781418d9a540fd6c76539d111dbe42e727ecff5836d2f14207c0645bccfc8feee71620dc51b5171a3ac7f43df64ac99cea0c527d900a936444a647fd99f4aaa4512d5ad8d05859db1ede9a0ed929ab9eaca2c17df1ab4fbb96ac7764ad5a4508daef5dd776e049de878774c7943d4ee043552bc1fa1610f4b83c9ff7dc5bf69f2ea7e9bf336e80a4ab989718a5b2e0cbd3d82f0dee44447810724c695af404ae9bf13a350fdd4ec0398fba0701161259069619df55503e187a9a10b11d736da3702563800701dfe5a806558798f374a75ac33d2e6b4349970c5892498d359b70b33e0319d7e3750307e8f188f7c04920a1e70651b938435fd1df9b9d2c83ca2e45e3b43c12fd79e2cb0459e22980d8f15f7f3a4ac6680694b2d62a91c07406e7741f0c1908ae7dd0b284d0b4957fe8ece891fcc860ff9ee05044af00a8013f80eaa30d619660fc5ef58cebd5570bc39d84a3eafad02fc7869da09446e21b4faa6fcb0f53285df51f82d9a266143ca923e1ac125e285f63b9fb3056dd18b487e4d49a87890955fe14e60c7882ea2144b9a81c5be4ad81dd48903381639167182f5dc9346135cfb4161b1624e811c4c666d94b7eb2d9fc975345e67a8fe447d0b611b3221741770674dcce82575ba764ee772ffe406eeb46d20e4e2bae34f2bf8a519a721e1592048694d4b5269cd3db343c25ef3a0ae4d24e616732b53399f60c1ad07688b7f6f21513f96b314f0e02805489732e1c36bd05ca15c4376d9d7b61cda8097cd865e9512d9afcb1d1b0b00991634c8a8fa00e9ad2c417175adb4ca835664eda3e70d69fc93561ba98d48e77633625bddce0d86ffe76b2e7dceacbda657d4301000161bdfecdc508ff0e2ff126f75a31bf15e7f7306fd300856b1c273a27440399a6658bc6c3abf4406e718168f96637f76bc9735a12ab8d4d3ea76d56a91c19b67bfd4e1060330000000000000001c3f46501f2eab8012026167ab8e6150bdc56bcd69c61769534b1e2f59b903ba72330216284e18c9478e284d64c1678376628c1c90779e8f18ccdc67415122f03d4d4ae6ade3307cc5bab6424507399c1e5c6be2fc0cc0df0b1dd19dd2b163f2bff6864d651a63a30290de5101c65735afe42f7a58fb12406d64765bf7788120a8325924ae310d7c2a84391ec9dc8037c96e0553c827003faf43fa8006c6f51a16b00ac0ae28e4a9fe8fe3a4d6204e26b50245dbd50cc4b3ed0c6b9b79c5ca5335338b63e95a87bfecac449d45bdc0820fe5ecc625d8a83b33b744f84bc17d553f95dc02504d2fc58df2a533c5071076f8053f26517067bdbcef786eac100c2338d62bf6b0054f75a234efe4fb204aa674935cd10a53f460a6ca5ffe785e47b33d41202e16fa1988111b14a3fbbfdc9e4ac4cd60c823e69b17c3d1c5a60ad8c33d76279d9089945549fdcd5a5fd8c6fc18d2df713ade66a2c739bba4cdbec8eb60cc1e9c4e126bbad65e9fac3ce4927d823febe6a3ae9c4285204f270e973b95b154f35de906300a2a97bb6181c7da93c907fd4deaff9cf0e272235e483f046a6fd32af7ee9c66eb3f7402988375901b463745ac0b3eb1e1d8e0d4ae8bf178f9cb68a17bb0005955476a166d7f63bf304e4a995fe495ba008c962d97817a2e7407922e73a6d3a887789463d2b2ac5c3de6bad633d23c890b9d8ee02d89da2dc25f22245413efe4738ecdfef34360c1b3d2c1a8c5674e3eebc2054b972a5f4cbbfc25cac7477179b272820f33e81a67f24bf34b5c8d96921f410737703ae98c31b164fd6f3447c289f91fd7e333444dd947a2881784c6c9ee4a69b2061fa4a08acef4fe5695690c6bf863a1a58a7777a6dd2155ac362c4b4eab18771f7fead06d90e217485d940d71851aa911dc6137ac1ad37a898861ea6c25cb3f8d9b6fbc4c69d667ef94b580ee1cb70062f59475201b07ff1dfed42fc14ba2c5e83eea3af3cee0cc20122f2e67ecb2158fec917ab0ec06692651a380f93e2bc9496d1b285fb496c6e9c92bb6551d6c113e93d44a62dc031c7817467c2c296080d0b24eec50ca729957a7c37a36c868335488b054e5410f230317b224ebc8364e32ad6bc53c69faf9d34303d2201d2c41926de7a53d476d7aec6a9fbe8d0a5f6b358dfcc9ffd69b7f1e1ff172bc48c894896b4e88be2cca107e702279b60fe27132730e107cc207806be8430383f200805705efd68145f8a2dadfcc12d75ac6575186d4cd23d1314e276e72a75c8f94c51f538974e45aeb070dab42d7cfaf28f35a3c407f57ba94bcd73db63ed7b675cc9592ca5ae16580bf11d21eaefb8e6e466538c9e3d9bd56f01666b40d594c4a95a342adcaa95b507b3ac01c96b7ef244492c878e62e222cb9f999bd984b3bcd36ce68dfbaf031afe7b8f03e413045a4aff41f0428d9e19652f136eb9b36e485f4c71c2341cc0f474433c8b67c3abc271c573e13ef3023263a921470eb2b9bcb4448bcace53d441af5b8b660016dea16782edab12cf035f74fc143935c93d12b11e177e6a8e23e60157085ab7a62b375d26c9115eb368aa2743355d4824022cdfc774799028cd27aea2edc8866a17118af2a7e153f51413dad79b589a39e16c9b24e038f7a21d7e9fe14539061c3df534f54c6a8bd4d1b2bd69d656ea430d531ebf53acca1b7806ee7e1aff0a653994ae5ae8920eb026b58af68495f603e0f62af8cbe1e3bcdb51998f0fd7173d9e695e35272e1ad26920963d77109b87ba27240db901ab98a54be3f2f2aba0f1d5e9a89a89c6bb735d2afa3b898714700537c88b2f97ff2c684667259a6286cf96118f6560dd5ce986bc885e0128a18d99a17e5159fbb0e0c0015c7cc59a91bbf2a860e00490ed7e7b6cd93ab7825be05db8d6fd9f40b6901e9552863fce1969891c91bf880363a4f2173d7f1570251ca4477fb4f3b20c78c8ab6aa0f1171a9b4cd0cab48058836f82cd78bf2eedf4229353cbb8d953cb308d275a5d8776827e8460ce90d4d37dfaca256b5af719e837d1ebb41c64b380e0c1c980eea533117fcbb1035d5640142a73b11370958f64f97606189b55924f5c04b7afe835a7fa9133a1f9da3835e7a9e4cbb60d50d6eb53003374e4513e0cca18951edf27807cd701f7877edb20973f3bea5a77a5bdefa4744d523df366b2ad43b21564a3f83ab3ae8880e0856f1647f67009baa4882983736a9de6e9c4c0e4b52c49736d8ebb7cc6331e0bcb071fbb1602c653ac12f51381ff0272ff65cfede23b8bdc3bed67639c1cd5a885a41c42771185f02572a8494081382635db315d91c3e82af2365f220b07f85b9acbfab3b0901b8a115d361cdb70ccbfad524c99c52ff85e64f65ebc1467593f941ac42324b479fb731bd4fb46f6271beb72917b06a0c576306ed2ec1ecdd462ee7152cbc3252548edfa7f41704966e2a9465179980c541eaf0bfec29ef9b27337d50de417bdb2ba97cf7cdc96e9a508d6e6eae10de4a8d567af7de9c21b85b5da0d2f30a9e75acb05441cb447115201aaa131b9bec2ae2b8abaab5f580f31f5804e337eb4f17ecbe65aa17cd4f965d4e03c3033214647ac4cf5a01d8d4ef940d250731ecebd6fd269f0a41859a991719ee12f41fe5bf6a95ee01a5e3f9a05c3f87b5209fb70385fc233b0047a5410a51953576cf0d97049d16c9b4e9d8c27df2c3c419ce89b0b418461b364379e1a3d216323b85c8e61aa4e2da9cd0d13dc897e999cb46821167ff5a3658545c47b34effd697dd4de7fb627652ae21487a23cfb1380409eb12f0d82a1760e0190bab7955c4da60bb954360716748c7af1b7f5d9d3dbec56011a0ae81d184a9dd5beab37732d6a38c7c9f98445b04a0eae2bc41a825276bde2e97acc27e4f7e86e83e81d93dcd85097c224699c25f4a33b1e2323c94c3077a55cd8b40762d503bc992c9cdc2a5813c401e26f937d0ca3f4f9d28424d14820f897af1be20f8e0fae261ae7df80a112944a9b443fd743d6295d1410eb04e007d639da4acd9887d3f64652b8251793738024a6982f855af9b4ab6bf4f786dcf4a3378dd7892d385a4450c681f1decebd434c63b815b7773f07d86dd592e5695eba10f812a9935971d9e79b410f9fd90f2154970978e6ac4c7c6648b225d1a6312a36dbcc3c1834431b76a4dcec048fa81ae5ad95962236e6d70220720682f07565ba8b22d663f5ecbc6098f2f94cc81732205b1ef35c2cf31fcd0a5f5160df64c240aff9810e01c5ba7d3b804d985ddde7e38e8bcb945d42ec7c6d811ac3d12a2223d83094d8c7faa27a6d374ec09dc5b0f2ab657408e5cda8393f6ffc135f8d22a749dbba4dc5a90ef03e0a7253458cfc31cf563df0a92593ebc985a5bea991c56d2d2675fcb3ee6a000f33135494c7f667ed1890fd7bafa1c02bbc8d6e42b7ddd9250dcfd2994ecd887fcc54fa04b8e26dd2ac86793f527e1398780bfa12c8f90c77f7e65bcfbb4b60eee30ab15c5e96c400b205b4c80ba4a872cfd58dbb0150190d43a7f9b2e92ba2eac2681a16a13a4516fb5b45919bd117d3f54c84214a504b2ca3b5d9c210e551232c5cff89bb44fee2a6082b80b80955a3b153220391d0ee156c941b52b91705ddcfc5077d20ce00ac479cb909fdf864edd3f3e90cd635b75273b16b52fd43eb2af0fa177f43fd07e4ee61347f6d2dca33e460611f3568e8c5c6959209c496c32671c30bfabb342cf1a754858f98e50e0beed20889cae45715bd68a71a9b6c75b8b95c7a4434c4907690dd0cd0e76d2f443ed9cd0ba31e9e583a9baf5eda443a7e2e7ec7ef3370a5b7327e9df2357237e0b4da62fb1b15004d4d7b7b2e0f0039de4b45636f32118930db4140102276acde7a537c7691b59e719bac1b957d9f79f4cf46732b380e89bad066b0596148b038ec23c0ae5a0c926a65fbfce2f869d997f84e03e3800057755a0d3ac6e598bf072c0b830be2fb58966082c41c8b82bfd6d244ffecef6495a0a554b249493eb687ded543b82a214d18463089efbc101be7b35c79c47e42b471d1097ef23cefacb086480e37be19063bd112c665ffd9e35e326b5f6b87901edc6a2df30a3d272293ab6f1f970ccb9cbb37279abdd21fda5826ca5005d88595109c6c158bbaa9ebcdc8fff3ced6f18ea9ecfea651f52c5c9235fc7b57e7ebdcde73d32292bc9455cb78e30ab93d5a7f602605408fe1901951803f7502b7ad21bb441adf1932fcd9da4a4277129cf9321765c21aec46300be5b8946199b5b2575643e9fe85d21f6de581dfaa4c26eb70be60b073961bf0ccf8a17ca108bd2b747f2b890e6e6160d9ce4b9d7515679881148c8a57a0c66b48dcb9d66def11b2f2b5ce391ec1d4116946adf50a2f155afa7560fed4e8af6509c3b4b9a3b908f086eabd1af81042a81f6daf3ab31493d68e33f867dd4e1492549816898f56fc434c00cb674420f15f79417418e0ea1d797d97eae095a68573060c0d6039b951bdfc1a2565224276e63a7da4e0199c0badef39f4a7c3ab89ad43fc42dd900896a4333ec211bc74cf30f5b680cf098db27e51994d9a462dc40ae063c9b492177c6cde379134faae13f3195e0994d329c8075d66e9f93f13fcdaee84fcf25ce0300bbdc152e197de1a075f4e2c797b6f3ac31398f01df3e9497559f1279713c4cc0a8393ad39efc25ae2327a8d1284e8d12a26a5f6be135958cea150bb16880253fdf28ff874144ec5880fe4df2d08f73c30a349ba4c17156070418b19b2b6c19d0b0c4df72db27522ee4d90ea7afe8d6b0f18303a2cd57ecd49f4ba2b3fc5985cca1713c74a997ad42668c74e2b7c4ac6f9fe202a49b983fdecec6e0fc228498ad39d9ee270ec0b0b5e6a406124f8bd2dbed0799f7779078c51e0446ef1fe5c7c44c587bce68ac788a9d0468feb067e9918faf811b3e79414ba49a5a588b4b1087374dbdc082c011f409bc8b9b49632cdd616833f193416602c2dfb5a31a9ead6cb05477355adec97c96886d649fa0dd5094e71bf437bdbc512330b04accd0215f240b299e87db39bd8d7fa63b2d560c8da139df2e30bf9eb9447080e89f6add5c02b01858c9a042cf615732ff5851b7782fd7c3da902c6ec761ea69a36bda6785f44622701ec8a82100ac9adc2d134ae4ef6ee99dc0fa0a7140b8e0314d3f1e30ac4513ac71e49b3f1fe843f106bc27a04ed05d4f9825fa1c8f3dfe3cddd4e1d7696b1cdc77844faaa3aacc3b5f2e12d2d18ab75eb831ab626a4e4de6fcd11cf7a7029415beb3a7971953982e56c710d0390fb4e11685992182c62def1e20872c454095a1672dc87d0612a3a0305021d6af549d95e2d1dcea03804819a206b1316261a0673e59673f6827de10bd8390c2f9de62d816e09f3dd6934e792be83ce6ccdc7e3d4ec949bf503ed65900cdd7ad6a6507a3cf233d5f5a370c5c1ae8df2678d6f8783f1eb072222c80363631bad809e0408f2cc3ef1fe6635db75008c99a91a6e4e4e4098429be9d95cc0032358ab54489042dbf7b7e08173a5117b986d7d67cb41b2665fc154c2e2e7ebfa77d42861b55af6e44225ee594bf5b5bbd5c1fc3f80d74b6a529567ae80bcbee12dcc97c0b2fc12e2fdcc262c8c21d8c2f94a31a5e90aaef344571ce5b175d1a2d785e8a8884369335a641eeceab244f4a8cf631b3c55f3b77fb29b0c4ce40bbb54a769b5b12e84fe5ee6b96b4d3a32b937497ed92f7df82bc757d1990da05aa38031827047a4d4f2b92a9d84e0e4c901a4e9eb6badcb0fc3359f0eeec3bc95173c6ceac7c3779bf363b6a9cdfaeca084400b17d965134b9337f0d45b4aed7bdeba9192582829c4eafe29c42abbee645b0ce0a49635b6d38ddcb084d8b8cc326058cd6af615260000" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Document or relocate the hardcoded test transaction.
The someTx constant contains a large hex string without any documentation. This makes the test less maintainable.
Consider one of these approaches:
- Add a comment explaining what this transaction represents and why it's used
- Move it to a test fixtures file
- Generate it programmatically in the test setup
Example documentation:
+// someTx is a valid Liquid transaction hex used for testing broadcast functionality.
+// This represents a [describe transaction type/purpose].
const someTx = "0200000001015a75b8607566159620994af84594a6b6d903aebbcf59d1382771694bf611eb8a0100000000fdffffff030b59cf2adff4bf2e7d3b8a36816d9fe3c675f2ba6d7198c35195d8d4868578f22808f5bee2a15d01413e36374ff5dc21224b230e9d70f247fcc85712f63fd914074d02aed6c7e3d0008a693a7bccaeb3aac96827cd5af5bbfbd75b2c6a549a0635914116001436532949385ba612cb7b9b3a12e2363666c5a1f40a8c5e056e5c43e77c3f3b49642e1a992e8552b74a9635cdf0e37751709314bf0d09b5b6e450adb2b9f95bc86f0d16279a58734f3f2ed8de73e85489f1ad318b912c02d48e551f064e5e9661f6317f354ccd15184c2b6f0dacf9c3160caf5819aadce11600141961af5494492cb063ddd798b6ac73274f5f65440125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000001a00009e0000000000024730440220263f44bba67dfd29e45d6fef6e360e9f9605dbb8438e7b7c663c36dd40126436022054b1018aad29ea345bdcdb8de501e9bbc5f15a66851c8241f132cc42f01bd69c012103797c2f42feaf60680bf31ad2b2edc371a545f3cc96f95a900f5f0cf45aad0d8d004301000185ef088f87f452468e84ea5e7b2750c7664d2ed849bb5b9154414780e367c23c25d180eef6dfc4f8aa5a54116a5567b1f6b6c15059959f4db7872c6d0585ed7ffd4e1060330000000000000001856ba700f8aae132c01f52c60e60029e1341dcd575d1c4aa58d509f791697f9b482aa194f5dd647635d23f2dd019adf96762907d4f8b7047e1af12ff82ba5e69c00dedd7583c2b91ba3ec0040ee9fad69ae0bf58a49827056638fa4564a6e7de3f929ed6e93bbcd405b1cf9d967bebde29fd061da7e70be1464a47806add1f103d8ef9eb5c487cb82befdb60e0c620919ada15f6cf4fc443d2191e984a4c164627270af6a81380599285a4ee01c92c8991203e03478563d850bf86f8ff885216f6830d9cc845cfd1710f34fc61994a6c590085a9e73e637a6d00075d7d5ef893ac963d2ab9d21d9104cfd92d96e4818568411f18e9c28a353cd726e27e7e35d98d37240ab3317e5b5c352c9aa9ccecc3806a83008a89eeb255199c546a3132be478c991f07c12c5b386e03150f7bcb302e8ac84a14ee7544b75f78cd730a17df38cb7beb61b4ac716a69946da88bec0fb417eab843fc16758eb38822716f3ee9d0a77df7a78721fc6e4849d279a3aacfc3750ea8630657e16163e00ba1e26d60adfe9b4302d557e3b5a721f315e9d187701e7f29ff40c7456fb16e2622f7bc980b46d200bf720de5388b27e80fd61839aa1158d96059da6b16ebf857fbdff7559858bf9b56a28ef62f328f16c1f3010c2966ae812a7846570ef186a03fbf246e4b97a30ca1b728550a47d3589d00b8f17b2fb399c7028258f877af9cddb6912604d724fd08a6e1c241e0d52e2d00ba8943f37d15847743272bbc349d419c4efe0eb822e572d5d84c7a609c010cb15f1bdb10c2fa70cfb58e2fb8e360679ec62c2aee7456755032f74cac7defe0030d431182fdfa00af3a36673ecf0cf30fffa4f6266c36c915e76c196a070eb14a097d72fffb4135c30516d54e43d385f73b9c75059d0406c0eb9b53f6737fcb7216486f1247a3513c49195223bc26a1609aed453e6343cc87c4f1224b2c29cdf0059089e79e88193eba1cf9659dc4049752a1c3903141d0aa99a0fbdecb65bb08b21a0ea390402295c28936b6948ffdfb9d2ea3a851630940e9b476d243e408516950e4373da18e2eb3a01e03061754df134abd099e64993c19b9ad20ece54553269db69be5862c77e06ac92e30b2bcc63be9098b0e1d557107b65a19cb7a765a6dff57abf5698e59a00432a7a964f6abe56d418d3c677db439882da38f8a9d29b69ac00676122b18598aa4f3b21b4f0f661219fd02be718ac1c3b3332ab25261e2df57f9493c96120e6393a0e7a9330aaf019f25476bd27e3c44340fe20cc45b82c5bfe0919dbe748eaaca484fc259c140bca1a9b96d96bc05112e9e1995e519360a88e2b8758b7f90fdc70079e9039757f387cf77bbed041ebb92037ecbb4b178a996012f58e0caa50fbeff7977b3779afe413bcee07aee9624ce45f2522d5cff987c9201562bdc8ddfec083c2a74e79323708cef604732c07b767472f94d9d16b6df50105633047e7fc81f533398b4ab7a7b3ce9a9699e57dddad9196862a47d99d8e1548fbd1d404875644b82f1376e323cd743afd17b48a6834edc8abeb169b055d7fef8b127d91d18b99dbce77b3edfe19305ea8aa6a3be2dc849ea905ac8615f8c31ed717f09c963a99c2acb40a470248e2c649c4b55ef7931a642dff38909c1a127ee1778bf6b98ea8aa62a168203d59a23854881bdd521c8b27519f7e51d3b8c13c503d9db8620d50b111fb0181739338c43c0865916f474d92ca64c13d4b00fe72e79b9c799d48ac066e0f33fd4bf8dfa1c0445077827e02e5ba0652e139fc8ca9833454b0dd86b6751c55423e915053866bd22f2a24e2dc1aab797f2413b6f286ff288f7b47ac2d68af8da8ac0c64a0a080ea7e81786cc888f715ad6d649a66ece9129232ecbadfce552e6e98e3b207a3b47671fa90564a1527fb9e28b4ae1b7b958a3abdebfd5eeac9c4e8f9896a403335a5591e4b6a056094a8f6bc744b5b3c01431754286fa85079717fa51421b3a810f3e0ed2d6d1b38a9cc91ff5283cc7b9e1a6c9183f56e159d1a855184672ec3ee0a675289d40fd5766aeb29c03fabd0b5a8cbd4182fdd93ca45a9e002a43728e60123b2e61bf384ac397c30bf8f0ccf5bc58596c0e073f5b0aa3cfcbd4e6a5649317cdabc72012a607564dc4bc2a7745f59ed18a8fc644bbd4349597474847bb8309e2412df79241f1df4626b75ade4fead30326c2584999b0b4c142b1a9307767fd1ba1f080cd63280fc075c36f483f3f67a00444ade453812843f05090c3edb8a8505f9676074f5b4906b8ea8620254d37234147dbe97202596361af7c7d275463ba8a940e0045fa461e4a2875deab7b5efce9474899d126a3cd7844707e69810f61354d92a5dfabce6e6f880a707815b1373329be135405df8166f7a39f5564594767294cce489c92f47039a59615dc4380bca06262820ab13e299ea8c620011c4612014ed4c50e5de23bd15b3ba53b99f00781fc55ad83fa685843776a210e64283ee9476e48b7450ebd7461477a94e93fcc679beccbc71707b3030f14546bc919e5d0875ffce5a99840f0ba1ecccdfbe211faa2853f2ba4d74f760d9b09ae7908d14cdbd0a5fdf040c0fc6b9291389d9f973f5cebe875d54f53a414d881205927722d9c6bdb0ccd5cfb1f371a3ec0198486a54f10a68942fda45522aaa9c07d8345cec7e01baa8ddadb994d4d271b3821c2cba6dcfe2df9b9b43aded6406d1e9b55eaf822dd5e5b684b6d9a8c6d829df1654b033b96730a4cf74f1e1d32f05adb4ba83e23427e6c4049f6633859f0ef3542a3854e2d36bc36a2d0d6d7d24de3f527ded1dcedad5b19be0e1961304de66e73a9d3cfe21802f15f1563eaa436ec783fd11cf9cae7fe9e5168d507c4c59934fc2afb7d2aed192c17d64db828d43e86781d2ee3bc1e327f662442d390f4bb5c1da3b1d4450ada85d8db5c85d275789ba37e47e35ece57931c580da5db2d20d252f223b43d9bd4617d113e39d194305989f44609bb2c45b1b1e14dc62fcbdbb3d07b271abb6f097571d94e913687bcfbb24f9a8320e14e6f564414083dede15eb54cb0bc376117d5b72baddfc42cd274a5690f46ce633894d9574ea21d1599d9fa97b2ad328960e93257b26d932594592c76cfa3854059aad8ffe9c4e1d943772db91450f6c40c18c7c3d94f16b340a12213efdd99f74fdfc2d0d60f8e256804a7fe7af0a97f4f1680d3c855c1351bf97544af0bb738a28ec8e97c7d2e732926d7bcb1ab0d628c987cea474d50f2d0a6e93fac257200e5b50f112f9edf861f1b8752dd0a587e22185d9efff531571273f968f3d3fe111a4762564b1d8c702e2c997716011d1cc4580d3bab762e5787f9f3699a190735f94a4dc8def2989e966275b35ceda21f9cb707953e393d46ee797306144bcacd2c7acd734faea356669c97caf03617146413a85580ca67f5a9cdece60c8e385f5d028c11f919dbc2b3152aa81e785ae69e9ae565f3a3ed42d3063472f5faed7ff139529380f9311f4f8c89ab1868cd90b251624fb5644fa2d5defc456fa14621b79c133b60d49b1a2699dfcfb431459b5a3aa74e378071bed4ad98f74bd9f3e748cb66de8e264930632e592c3f9e3dfb96af9dd212e7f697fcbbd930e6ed377bbe1bbf580584c35e4aa5b2dca6232bc6cee69d47c6ff43825526201ffeb2a48af6594d80ea58be562ddd80ad0c53e9166e6dda5b5f581192a8fca643a371d69703d7e795b9e40573209a6c549f1dd80ad79ec42eb29f64f80c525d6a80734e9afee5b1c3d245c44601a113682ac72bf88144c771bd906c8b005706cfdbedf1d06ff0b52fbd6f848357765c3dbb4677d09c08353bad7e35a0453bba554e5cb074677efc0f1eb4ee6e7aa39a2463453dc0c2720325a03254d95790b1a8493e565b9ccd8bf8d4528f55184e3e4baf837dbd741b28c81030ea50070ec83693afb50d97ae8d2903fa021c2c358d76bcf7b4fe71580db877c8ff9d6a3555a0ce1fab974a6c7d6e6047002a342184209a2363f2ede37faf6596db9964d593c29e933b9e8ad8df31396054aae6a890aa8153460b8178fd3c5eeb071a3b95e3bc9fa9a341b79436c1146fe5c8bfeb6b01aa7dd1e48cb511d7717fe2122710705578aea833c48e38c520fb80cf8ba0e28c0d7599d4a7366f1df7f54327fe11d1a89e4673145344d527ad757453fc420bb69a483825269f83474f384c588b0de49e61fa9b02c7087fe004a721660920048c7607ab2602b2e6c1c59b9b50e70d200aae2d4d89a446f082ed9d6e695bf2706742bbb272be8d324c6584341fe9d71c63ce8b38e8dea9ef1c3a90cc777a1452d2a79679577c804a71a7d37aa5cc8e8cb6c4c0f7500ebeba6942e77765dc35a5e199165cd84e86ddca7df1601533620f4f00bb51bdb798afe8e28d0593ab8df2534d8ee5ccabf57df6ce9d138f51f63a8b6ca2bfc9b2628a8e663bbe59660fb3e40ee25676d1d0ba4ca4be2e59a6f988a716c4ab14afe9c01d5dbad350c0502102ae24820aef01aa3d047c716123a663efa319273753282225d016325711446f3c15cd33f3ba2a9fb2b8d20cb34068be64065b37efce56facb7ff04371f3b2f3f5c1de6cb87282e8bf80c9d0d748d1b6b54b2a5289734a47035d35ae42de768ee3ea17d235db3ad493fff6ebf0d3f1dde3ec7c045d420956555f865d4c3c1f91c1994c95ab6c0b1e36b56c0f419ad9281c2901752ca04231d7a623202d054855f0ba70511a4ca957e50800653316387d6fbaf939b8a53338e9aec607bee81218b440b5cde69ac72cecb77cd188e77c8b6a161b74977bdea693881276c18f9098cd6d56f4b11fc106153a4e1283746f71e584b6018c7084f407814919e41ed7829950ec30a75c1067f7c37579a4bdeac145ca1138b0877df53c2101f328365b650ca1709ba3b0af78e8a97c8acc003811e8de4bcc2b3553ea4319eb4bed5db8b11d6950914650eea444cf86a215e3b96441883cc8555489d0ddfb7a43d327b84d1262bf91a280867dec44781418d9a540fd6c76539d111dbe42e727ecff5836d2f14207c0645bccfc8feee71620dc51b5171a3ac7f43df64ac99cea0c527d900a936444a647fd99f4aaa4512d5ad8d05859db1ede9a0ed929ab9eaca2c17df1ab4fbb96ac7764ad5a4508daef5dd776e049de878774c7943d4ee043552bc1fa1610f4b83c9ff7dc5bf69f2ea7e9bf336e80a4ab989718a5b2e0cbd3d82f0dee44447810724c695af404ae9bf13a350fdd4ec0398fba0701161259069619df55503e187a9a10b11d736da3702563800701dfe5a806558798f374a75ac33d2e6b4349970c5892498d359b70b33e0319d7e3750307e8f188f7c04920a1e70651b938435fd1df9b9d2c83ca2e45e3b43c12fd79e2cb0459e22980d8f15f7f3a4ac6680694b2d62a91c07406e7741f0c1908ae7dd0b284d0b4957fe8ece891fcc860ff9ee05044af00a8013f80eaa30d619660fc5ef58cebd5570bc39d84a3eafad02fc7869da09446e21b4faa6fcb0f53285df51f82d9a266143ca923e1ac125e285f63b9fb3056dd18b487e4d49a87890955fe14e60c7882ea2144b9a81c5be4ad81dd48903381639167182f5dc9346135cfb4161b1624e811c4c666d94b7eb2d9fc975345e67a8fe447d0b611b3221741770674dcce82575ba764ee772ffe406eeb46d20e4e2bae34f2bf8a519a721e1592048694d4b5269cd3db343c25ef3a0ae4d24e616732b53399f60c1ad07688b7f6f21513f96b314f0e02805489732e1c36bd05ca15c4376d9d7b61cda8097cd865e9512d9afcb1d1b0b00991634c8a8fa00e9ad2c417175adb4ca835664eda3e70d69fc93561ba98d48e77633625bddce0d86ffe76b2e7dceacbda657d4301000161bdfecdc508ff0e2ff126f75a31bf15e7f7306fd300856b1c273a27440399a6658bc6c3abf4406e718168f96637f76bc9735a12ab8d4d3ea76d56a91c19b67bfd4e1060330000000000000001c3f46501f2eab8012026167ab8e6150bdc56bcd69c61769534b1e2f59b903ba72330216284e18c9478e284d64c1678376628c1c90779e8f18ccdc67415122f03d4d4ae6ade3307cc5bab6424507399c1e5c6be2fc0cc0df0b1dd19dd2b163f2bff6864d651a63a30290de5101c65735afe42f7a58fb12406d64765bf7788120a8325924ae310d7c2a84391ec9dc8037c96e0553c827003faf43fa8006c6f51a16b00ac0ae28e4a9fe8fe3a4d6204e26b50245dbd50cc4b3ed0c6b9b79c5ca5335338b63e95a87bfecac449d45bdc0820fe5ecc625d8a83b33b744f84bc17d553f95dc02504d2fc58df2a533c5071076f8053f26517067bdbcef786eac100c2338d62bf6b0054f75a234efe4fb204aa674935cd10a53f460a6ca5ffe785e47b33d41202e16fa1988111b14a3fbbfdc9e4ac4cd60c823e69b17c3d1c5a60ad8c33d76279d9089945549fdcd5a5fd8c6fc18d2df713ade66a2c739bba4cdbec8eb60cc1e9c4e126bbad65e9fac3ce4927d823febe6a3ae9c4285204f270e973b95b154f35de906300a2a97bb6181c7da93c907fd4deaff9cf0e272235e483f046a6fd32af7ee9c66eb3f7402988375901b463745ac0b3eb1e1d8e0d4ae8bf178f9cb68a17bb0005955476a166d7f63bf304e4a995fe495ba008c962d97817a2e7407922e73a6d3a887789463d2b2ac5c3de6bad633d23c890b9d8ee02d89da2dc25f22245413efe4738ecdfef34360c1b3d2c1a8c5674e3eebc2054b972a5f4cbbfc25cac7477179b272820f33e81a67f24bf34b5c8d96921f410737703ae98c31b164fd6f3447c289f91fd7e333444dd947a2881784c6c9ee4a69b2061fa4a08acef4fe5695690c6bf863a1a58a7777a6dd2155ac362c4b4eab18771f7fead06d90e217485d940d71851aa911dc6137ac1ad37a898861ea6c25cb3f8d9b6fbc4c69d667ef94b580ee1cb70062f59475201b07ff1dfed42fc14ba2c5e83eea3af3cee0cc20122f2e67ecb2158fec917ab0ec06692651a380f93e2bc9496d1b285fb496c6e9c92bb6551d6c113e93d44a62dc031c7817467c2c296080d0b24eec50ca729957a7c37a36c868335488b054e5410f230317b224ebc8364e32ad6bc53c69faf9d34303d2201d2c41926de7a53d476d7aec6a9fbe8d0a5f6b358dfcc9ffd69b7f1e1ff172bc48c894896b4e88be2cca107e702279b60fe27132730e107cc207806be8430383f200805705efd68145f8a2dadfcc12d75ac6575186d4cd23d1314e276e72a75c8f94c51f538974e45aeb070dab42d7cfaf28f35a3c407f57ba94bcd73db63ed7b675cc9592ca5ae16580bf11d21eaefb8e6e466538c9e3d9bd56f01666b40d594c4a95a342adcaa95b507b3ac01c96b7ef244492c878e62e222cb9f999bd984b3bcd36ce68dfbaf031afe7b8f03e413045a4aff41f0428d9e19652f136eb9b36e485f4c71c2341cc0f474433c8b67c3abc271c573e13ef3023263a921470eb2b9bcb4448bcace53d441af5b8b660016dea16782edab12cf035f74fc143935c93d12b11e177e6a8e23e60157085ab7a62b375d26c9115eb368aa2743355d4824022cdfc774799028cd27aea2edc8866a17118af2a7e153f51413dad79b589a39e16c9b24e038f7a21d7e9fe14539061c3df534f54c6a8bd4d1b2bd69d656ea430d531ebf53acca1b7806ee7e1aff0a653994ae5ae8920eb026b58af68495f603e0f62af8cbe1e3bcdb51998f0fd7173d9e695e35272e1ad26920963d77109b87ba27240db901ab98a54be3f2f2aba0f1d5e9a89a89c6bb735d2afa3b898714700537c88b2f97ff2c684667259a6286cf96118f6560dd5ce986bc885e0128a18d99a17e5159fbb0e0c0015c7cc59a91bbf2a860e00490ed7e7b6cd93ab7825be05db8d6fd9f40b6901e9552863fce1969891c91bf880363a4f2173d7f1570251ca4477fb4f3b20c78c8ab6aa0f1171a9b4cd0cab48058836f82cd78bf2eedf4229353cbb8d953cb308d275a5d8776827e8460ce90d4d37dfaca256b5af719e837d1ebb41c64b380e0c1c980eea533117fcbb1035d5640142a73b11370958f64f97606189b55924f5c04b7afe835a7fa9133a1f9da3835e7a9e4cbb60d50d6eb53003374e4513e0cca18951edf27807cd701f7877edb20973f3bea5a77a5bdefa4744d523df366b2ad43b21564a3f83ab3ae8880e0856f1647f67009baa4882983736a9de6e9c4c0e4b52c49736d8ebb7cc6331e0bcb071fbb1602c653ac12f51381ff0272ff65cfede23b8bdc3bed67639c1cd5a885a41c42771185f02572a8494081382635db315d91c3e82af2365f220b07f85b9acbfab3b0901b8a115d361cdb70ccbfad524c99c52ff85e64f65ebc1467593f941ac42324b479fb731bd4fb46f6271beb72917b06a0c576306ed2ec1ecdd462ee7152cbc3252548edfa7f41704966e2a9465179980c541eaf0bfec29ef9b27337d50de417bdb2ba97cf7cdc96e9a508d6e6eae10de4a8d567af7de9c21b85b5da0d2f30a9e75acb05441cb447115201aaa131b9bec2ae2b8abaab5f580f31f5804e337eb4f17ecbe65aa17cd4f965d4e03c3033214647ac4cf5a01d8d4ef940d250731ecebd6fd269f0a41859a991719ee12f41fe5bf6a95ee01a5e3f9a05c3f87b5209fb70385fc233b0047a5410a51953576cf0d97049d16c9b4e9d8c27df2c3c419ce89b0b418461b364379e1a3d216323b85c8e61aa4e2da9cd0d13dc897e999cb46821167ff5a3658545c47b34effd697dd4de7fb627652ae21487a23cfb1380409eb12f0d82a1760e0190bab7955c4da60bb954360716748c7af1b7f5d9d3dbec56011a0ae81d184a9dd5beab37732d6a38c7c9f98445b04a0eae2bc41a825276bde2e97acc27e4f7e86e83e81d93dcd85097c224699c25f4a33b1e2323c94c3077a55cd8b40762d503bc992c9cdc2a5813c401e26f937d0ca3f4f9d28424d14820f897af1be20f8e0fae261ae7df80a112944a9b443fd743d6295d1410eb04e007d639da4acd9887d3f64652b8251793738024a6982f855af9b4ab6bf4f786dcf4a3378dd7892d385a4450c681f1decebd434c63b815b7773f07d86dd592e5695eba10f812a9935971d9e79b410f9fd90f2154970978e6ac4c7c6648b225d1a6312a36dbcc3c1834431b76a4dcec048fa81ae5ad95962236e6d70220720682f07565ba8b22d663f5ecbc6098f2f94cc81732205b1ef35c2cf31fcd0a5f5160df64c240aff9810e01c5ba7d3b804d985ddde7e38e8bcb945d42ec7c6d811ac3d12a2223d83094d8c7faa27a6d374ec09dc5b0f2ab657408e5cda8393f6ffc135f8d22a749dbba4dc5a90ef03e0a7253458cfc31cf563df0a92593ebc985a5bea991c56d2d2675fcb3ee6a000f33135494c7f667ed1890fd7bafa1c02bbc8d6e42b7ddd9250dcfd2994ecd887fcc54fa04b8e26dd2ac86793f527e1398780bfa12c8f90c77f7e65bcfbb4b60eee30ab15c5e96c400b205b4c80ba4a872cfd58dbb0150190d43a7f9b2e92ba2eac2681a16a13a4516fb5b45919bd117d3f54c84214a504b2ca3b5d9c210e551232c5cff89bb44fee2a6082b80b80955a3b153220391d0ee156c941b52b91705ddcfc5077d20ce00ac479cb909fdf864edd3f3e90cd635b75273b16b52fd43eb2af0fa177f43fd07e4ee61347f6d2dca33e460611f3568e8c5c6959209c496c32671c30bfabb342cf1a754858f98e50e0beed20889cae45715bd68a71a9b6c75b8b95c7a4434c4907690dd0cd0e76d2f443ed9cd0ba31e9e583a9baf5eda443a7e2e7ec7ef3370a5b7327e9df2357237e0b4da62fb1b15004d4d7b7b2e0f0039de4b45636f32118930db4140102276acde7a537c7691b59e719bac1b957d9f79f4cf46732b380e89bad066b0596148b038ec23c0ae5a0c926a65fbfce2f869d997f84e03e3800057755a0d3ac6e598bf072c0b830be2fb58966082c41c8b82bfd6d244ffecef6495a0a554b249493eb687ded543b82a214d18463089efbc101be7b35c79c47e42b471d1097ef23cefacb086480e37be19063bd112c665ffd9e35e326b5f6b87901edc6a2df30a3d272293ab6f1f970ccb9cbb37279abdd21fda5826ca5005d88595109c6c158bbaa9ebcdc8fff3ced6f18ea9ecfea651f52c5c9235fc7b57e7ebdcde73d32292bc9455cb78e30ab93d5a7f602605408fe1901951803f7502b7ad21bb441adf1932fcd9da4a4277129cf9321765c21aec46300be5b8946199b5b2575643e9fe85d21f6de581dfaa4c26eb70be60b073961bf0ccf8a17ca108bd2b747f2b890e6e6160d9ce4b9d7515679881148c8a57a0c66b48dcb9d66def11b2f2b5ce391ec1d4116946adf50a2f155afa7560fed4e8af6509c3b4b9a3b908f086eabd1af81042a81f6daf3ab31493d68e33f867dd4e1492549816898f56fc434c00cb674420f15f79417418e0ea1d797d97eae095a68573060c0d6039b951bdfc1a2565224276e63a7da4e0199c0badef39f4a7c3ab89ad43fc42dd900896a4333ec211bc74cf30f5b680cf098db27e51994d9a462dc40ae063c9b492177c6cde379134faae13f3195e0994d329c8075d66e9f93f13fcdaee84fcf25ce0300bbdc152e197de1a075f4e2c797b6f3ac31398f01df3e9497559f1279713c4cc0a8393ad39efc25ae2327a8d1284e8d12a26a5f6be135958cea150bb16880253fdf28ff874144ec5880fe4df2d08f73c30a349ba4c17156070418b19b2b6c19d0b0c4df72db27522ee4d90ea7afe8d6b0f18303a2cd57ecd49f4ba2b3fc5985cca1713c74a997ad42668c74e2b7c4ac6f9fe202a49b983fdecec6e0fc228498ad39d9ee270ec0b0b5e6a406124f8bd2dbed0799f7779078c51e0446ef1fe5c7c44c587bce68ac788a9d0468feb067e9918faf811b3e79414ba49a5a588b4b1087374dbdc082c011f409bc8b9b49632cdd616833f193416602c2dfb5a31a9ead6cb05477355adec97c96886d649fa0dd5094e71bf437bdbc512330b04accd0215f240b299e87db39bd8d7fa63b2d560c8da139df2e30bf9eb9447080e89f6add5c02b01858c9a042cf615732ff5851b7782fd7c3da902c6ec761ea69a36bda6785f44622701ec8a82100ac9adc2d134ae4ef6ee99dc0fa0a7140b8e0314d3f1e30ac4513ac71e49b3f1fe843f106bc27a04ed05d4f9825fa1c8f3dfe3cddd4e1d7696b1cdc77844faaa3aacc3b5f2e12d2d18ab75eb831ab626a4e4de6fcd11cf7a7029415beb3a7971953982e56c710d0390fb4e11685992182c62def1e20872c454095a1672dc87d0612a3a0305021d6af549d95e2d1dcea03804819a206b1316261a0673e59673f6827de10bd8390c2f9de62d816e09f3dd6934e792be83ce6ccdc7e3d4ec949bf503ed65900cdd7ad6a6507a3cf233d5f5a370c5c1ae8df2678d6f8783f1eb072222c80363631bad809e0408f2cc3ef1fe6635db75008c99a91a6e4e4e4098429be9d95cc0032358ab54489042dbf7b7e08173a5117b986d7d67cb41b2665fc154c2e2e7ebfa77d42861b55af6e44225ee594bf5b5bbd5c1fc3f80d74b6a529567ae80bcbee12dcc97c0b2fc12e2fdcc262c8c21d8c2f94a31a5e90aaef344571ce5b175d1a2d785e8a8884369335a641eeceab244f4a8cf631b3c55f3b77fb29b0c4ce40bbb54a769b5b12e84fe5ee6b96b4d3a32b937497ed92f7df82bc757d1990da05aa38031827047a4d4f2b92a9d84e0e4c901a4e9eb6badcb0fc3359f0eeec3bc95173c6ceac7c3779bf363b6a9cdfaeca084400b17d965134b9337f0d45b4aed7bdeba9192582829c4eafe29c42abbee645b0ce0a49635b6d38ddcb084d8b8cc326058cd6af615260000"🤖 Prompt for AI Agents
In internal/onchain/liquid-wallet/wallet_test.go at line 270, the someTx
constant is a large hardcoded hex string without any explanation. Add a comment
above this constant describing what the transaction represents and why it is
used in the test to improve maintainability. Alternatively, consider moving this
hex string to a dedicated test fixtures file or generate it programmatically
during test setup for better clarity and reuse.
| func convertNetwork(network *boltz.Network) *lwk.Network { | ||
| if network == nil { | ||
| return nil | ||
| } | ||
| switch network { | ||
| case boltz.Regtest: | ||
| return Regtest | ||
| case boltz.TestNet: | ||
| return Testnet | ||
| case boltz.MainNet: | ||
| return Mainnet | ||
| default: | ||
| panic(fmt.Sprintf("unsupported network %v", *network)) | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Replace panic with error return for unsupported networks.
Using panic in production code makes error handling difficult. Consider returning an error instead.
-func convertNetwork(network *boltz.Network) *lwk.Network {
+func convertNetwork(network *boltz.Network) (*lwk.Network, error) {
if network == nil {
- return nil
+ return nil, nil
}
switch network {
case boltz.Regtest:
- return Regtest
+ return Regtest, nil
case boltz.TestNet:
- return Testnet
+ return Testnet, nil
case boltz.MainNet:
- return Mainnet
+ return Mainnet, nil
default:
- panic(fmt.Sprintf("unsupported network %v", *network))
+ return nil, fmt.Errorf("unsupported network %v", *network)
}
}Update all callers to handle the error:
- return lwk.NewSigner(parsed, convertNetwork(network))
+ net, err := convertNetwork(network)
+ if err != nil {
+ return nil, err
+ }
+ return lwk.NewSigner(parsed, net)🤖 Prompt for AI Agents
In internal/onchain/liquid-wallet/wallet.go lines 139 to 153, replace the panic
call for unsupported networks with returning an error to improve error handling.
Change the function signature to return (*lwk.Network, error), return nil and an
error for unsupported networks instead of panicking, and update all callers of
convertNetwork to handle the error properly.
* feat: populate external outputs in lwk * fix: set non-btc assets amounts to 0 * fix: use correct fee output in tests
* fix: lwk `GetSendFee` send amount calculation * fix: check for confirmed balance when funding test wallet current state can cause flaky tests
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
internal/onchain/liquid-wallet/wallet_test.go (1)
298-298: Document or relocate the hardcoded test transaction.The
someTxconstant contains a large hex string without any documentation. This makes the test less maintainable.internal/onchain/liquid-wallet/wallet.go (1)
140-154: Replace panic with error return for unsupported networks.Using panic in production code makes error handling difficult. Consider returning an error instead.
🧹 Nitpick comments (3)
internal/rpcserver/rpcserver_test.go (2)
1162-1186: Improve the fundedWallet function logic and error handling.The rewritten
fundedWalletfunction has improved logic that first attempts to fetch a wallet by name before importing, which is more efficient. However, there are several areas for improvement:
- Error handling inconsistency: The function handles the wallet fetch error but doesn't validate the specific error type
- Magic numbers: The hardcoded loop count (5) and amounts (10_000_000) should be constants
- Potential race condition: The sleep after sending funds might not be sufficient in all cases
Consider these improvements:
func fundedWallet(t *testing.T, client client.Boltz, currency boltzrpc.Currency) *boltzrpc.Wallet { + const ( + fundingIterations = 5 + fundingAmount = 10_000_000 + fundingDelay = 200 * time.Millisecond + ) + params := &boltzrpc.WalletParams{Currency: currency, Name: walletName(currency)} wallet, err := client.GetWallet(params.Name) if err != nil { + // Only import if wallet doesn't exist + if !strings.Contains(err.Error(), "not found") { + require.NoError(t, err) + } mnemonic := test.WalletMnemonic subaccount := uint64(test.WalletSubaccount) creds := &boltzrpc.WalletCredentials{Mnemonic: &mnemonic, Subaccount: &subaccount} wallet, err = client.ImportWallet(params, creds) require.NoError(t, err) } if wallet.Balance.Total == 0 { - for i := 0; i < 5; i++ { + for i := 0; i < fundingIterations; i++ { receive, err := client.WalletReceive(wallet.Id) require.NoError(t, err) - test.SendToAddress(getCli(currency), receive.Address, 10_000_000) - time.Sleep(200 * time.Millisecond) + test.SendToAddress(getCli(currency), receive.Address, fundingAmount) + time.Sleep(fundingDelay) }
1884-1898: Improve test logic and remove unnecessary sleep.The test logic improvements are good, but there are some concerns:
- Unnecessary sleep: The 1-second sleep at the end serves no clear purpose and slows down tests
- Complex conditional logic: The mining logic could be simplified
Consider these improvements:
if !tc.zeroconf { if tc.currency == boltzrpc.Currency_BTC { // for btc, we only check on new blocks, so we mine one here, // but the mocked tx provider says that the tx isn't confirmed yet test.MineBlock() } statusStream(boltzrpc.SwapState_PENDING, boltz.TransactionDirectMempool) } confirmed = true test.MineBlock() info := statusStream(boltzrpc.SwapState_SUCCESSFUL, boltz.TransactionDirect) require.Equal(t, info.ReverseSwap.ClaimAddress, swap.Address) - time.Sleep(1 * time.Second)The sleep appears to be unnecessary and should be removed unless there's a specific timing issue that needs to be addressed.
internal/onchain/liquid-wallet/wallet.go (1)
57-57: Fix typo in comment.- // cfg used for buliding this instance + // cfg used for building this instance
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
internal/autoswap/chain.go(1 hunks)internal/onchain/liquid-wallet/wallet.go(1 hunks)internal/onchain/liquid-wallet/wallet_test.go(1 hunks)internal/rpcserver/rpcserver_test.go(9 hunks)internal/test/test.go(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- internal/autoswap/chain.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/test/test.go
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#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.
internal/rpcserver/rpcserver_test.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
internal/onchain/liquid-wallet/wallet_test.go (1)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#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.
internal/onchain/liquid-wallet/wallet.go (2)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#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.
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
🧬 Code Graph Analysis (1)
internal/onchain/liquid-wallet/wallet_test.go (5)
internal/onchain/liquid-wallet/wallet.go (9)
Persister(22-25)Wallet(27-37)BlockchainBackend(56-61)NewBlockchainBackend(78-132)Config(44-54)Regtest(136-136)GenerateMnemonic(575-585)DeriveDefaultDescriptor(164-180)NewWallet(182-225)internal/test/test.go (6)
WalletCredentials(52-64)SendToAddress(195-197)LiquidCli(163-165)MineBlock(177-181)FundWallet(66-105)GetNewAddress(191-193)pkg/boltz/currency.go (1)
CurrencyLiquid(12-12)internal/onchain/onchain.go (3)
FeeProvider(38-40)Id(19-19)TxProvider(50-54)internal/mocks/onchain/TxProvider_mock.go (1)
NewMockTxProvider(13-23)
⏰ 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 (9)
internal/rpcserver/rpcserver_test.go (7)
20-21: LGTM: New imports support liquid wallet integration.The imports for
liquid_walletandwalletpackages are appropriate for the PR's objective of integrating liquid wallet functionality and supporting legacy wallet types.
34-34: LGTM: Serializers import supports currency parsing.The serializers import is needed for the new currency parsing functionality used in the direct reverse swap payment tests.
1741-1747: LGTM: Wallet creation and credential handling improvements.The changes properly capture the creation response and validate that both mnemonic and core descriptor are non-empty, which ensures the wallet credentials are properly generated.
1752-1753: LGTM: Consistent use of wallet ID and full credentials.The changes correctly use the created wallet ID for removal and the full credentials object for import, which is more robust than the previous approach.
Also applies to: 1762-1763
1265-1266: LGTM: Proper recommendation validation.The changes correctly check that the recommendation swap is nil, which aligns with the expected behavior after swap execution.
1849-1849: LGTM: Optional BroadcastTransaction mock expectation.The
.Maybe()call on theBroadcastTransactionexpectation is appropriate since the call is optional depending on the test scenario.
3135-3171: LGTM: Comprehensive legacy wallet test coverage.The new
TestLegacyWallettest provides excellent coverage for the legacy wallet functionality:
- Type verification: Properly checks that a new wallet returns
liquid_wallet.Wallet- Database manipulation: Correctly marks the wallet as legacy
- Reinitialization: Properly reinitializes the system to test the change
- Legacy behavior: Verifies that the wallet now returns
wallet.WalletThe test aligns well with the retrieved learning about liquid wallet implementation and the PR's objective of supporting legacy wallet types.
internal/onchain/liquid-wallet/wallet.go (2)
271-298: Good implementation of auto-consolidation with proper fee estimation.The auto-consolidation logic now properly uses the fee provider to estimate fees dynamically, which addresses the previous concern about hardcoded fee rates.
498-516: Correct implementation of the caller-controlled locking pattern.The
SendToAddressmethod properly holds the write lock throughout the transaction creation, broadcast, and marking inputs as spent, ensuring atomicity as per the established pattern.
There was a problem hiding this comment.
Couple of nits only, which may also be handled as follow ups, as some are not directly related to this PR:
- Boltz Client is logging wallet creation, but not deletion
-
boltzcli wallet send command [command options] name destination amounthaving command options in the middle of all that is unusual and not nice to use. I didn't figure out how to use--sweep:
./boltzcli wallet send --sweep liqui lq1....
amount required
-
wallet listshows unconfirmed balance as 0 until change tx is confirmed. That's not how wallets do it.
After I send a tx, consuming both of my UTXOs:
wallet list
{
"wallets": [
{
"id": "2",
"name": "liqui",
"currency": "LBTC",
"readonly": false,
"balance": {
"total": "0",
"confirmed": "0",
"unconfirmed": "0"
},
"tenantId": "1"
}
]
}
After my tx confirmed:
{
"wallets": [
{
"id": "2",
"name": "liqui",
"currency": "LBTC",
"readonly": false,
"balance": {
"total": "17",
"confirmed": "0",
"unconfirmed": "17"
},
"tenantId": "1"
}
]
}
Not until the change TX is confirmed, but until it gets picked up by the wallet - your example shows the balance in the |
Your shown usage here is actually right and is already fixed on master. The options being forced before the positional arguments comes from the CLI lib, which is actually somewhat common. |
I see, now looking at it again, you are right. I just wonder how GDK handled change txes as it was picking up transactions not exactly timely either, but there was never a balance drop of the entire UTXO visible anywhere. As LWK knows the change tx, it should handle this smarter and immediately internally and not rely on the change tx to be picked up from an external source for the balance to go back to normal. The current behavior will always cause temporary balance drops, even if LWK picks up the change tx very quickly. Surely an LWK thing, but I am not aware of any other wallet lib that behaves that way, so please look into how e.g. GDK does it and at least open an issue on LWK side to change this behavior down the road. Not a blocker for the PR though. |
Goodie! |
This one you'll add? |
This behaviour is a result of our own logic. We immediately mark the utxos as spent to prevent potential double spend errors. If we weren't doing that, the balance would simply stay constant until the TX is picked up by the wallet, similar to GDK. But we can optimize that by giving LWK the transaction right after broadcasting it, so it can update its internal state. Lets bump the sync interval to 15s for first pre-release and get that done for official release |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/rpcserver/rpcserver_test.go (1)
1897-1897: Consider if the sleep duration is sufficient for test stability.A 1-second sleep after successful reverse swap state assertion may be addressing timing issues, but consider if this duration is adequate for all test environments.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
docs/grpc.md(1 hunks)internal/rpcserver/router.go(19 hunks)internal/rpcserver/rpcserver_test.go(9 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/grpc.md
- internal/rpcserver/router.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#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.
internal/rpcserver/rpcserver_test.go (2)
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#487
File: internal/rpcserver/router.go:2304-2314
Timestamp: 2025-07-16T09:46:41.587Z
Learning: In the boltz-client codebase, RPC methods are protected by the macaroon permission system defined in internal/macaroons/permissions.go. Methods like GetSwapMnemonic and SetSwapMnemonic are already restricted to admin entities through the RPCServerPermissions map, so explicit isAdmin(ctx) checks are not needed in these methods.
Learnt from: jackstar12
PR: BoltzExchange/boltz-client#444
File: internal/rpcserver/router.go:2036-2052
Timestamp: 2025-05-26T09:32:25.533Z
Learning: In boltz-client, when handling Boltz backend connectivity during startup, the code intentionally uses different error handling strategies: logger.Fatalf for version incompatibility (fail fast approach) and retry logic for availability issues. Version incompatibility requires immediate human intervention and should not be retried.
⏰ 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 (8)
internal/rpcserver/rpcserver_test.go (8)
20-21: LGTM! Proper imports for new wallet implementations.These imports are necessary for the new liquid wallet implementation and legacy wallet support being added in this PR.
34-34: LGTM! Required import for serializers functionality.The serializers import is needed for the enhanced wallet functionality in the test suite.
1161-1186: Excellent refactoring of fundedWallet helper function.The new implementation is more efficient and follows a logical flow:
- First attempts to get existing wallet by name
- Only imports if wallet doesn't exist
- Only funds if wallet balance is zero
This eliminates unnecessary wallet imports and provides better test performance.
1741-1762: Improved TestWallet function with better resource management.The changes properly capture the wallet creation response and use it consistently throughout the test. The credential retrieval with password assertion and using the full credentials object for import are good improvements.
1849-1849: Appropriate use of Maybe() for optional mock expectation.Adding
.Maybe()to theBroadcastTransactionmock makes it optional, which is reasonable since the broadcasting behavior may vary depending on test conditions and swap logic changes.
1884-1892: Enhanced confirmation logic for different currencies.The conditional logic properly handles BTC vs other currencies for confirmation requirements. Mining a block for BTC when not zero-conf ensures proper transaction confirmation handling.
1265-1266: Correct assertion update for autoswap behavior.The change from checking length to checking for nil swap aligns with the new autoswap behavior where the swap field can be nil rather than an empty array.
3133-3169: Excellent test coverage for legacy wallet functionality.The new
TestLegacyWalletfunction properly validates the wallet type switching mechanism:
- Verifies initial liquid wallet implementation
- Tests database legacy flag modification
- Confirms wallet type switches to legacy implementation after reinitialization
This comprehensive test ensures the legacy wallet support works correctly.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Documentation
Chores
.gitignoreand linter configuration to exclude new components.