Skip to content

Commit 93cb941

Browse files
committed
feat: add initializing wallet config
- add config.rs to store and retrieve values - add toml and serde crates for desearilizing and reading values - update utils, commands and handlers files to use values from config.toml -refactor prepare_wallet_db fn - fix clippy issues [Issue: #192]
1 parent a46aa9d commit 93cb941

File tree

9 files changed

+440
-42
lines changed

9 files changed

+440
-42
lines changed

Cargo.lock

Lines changed: 88 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ log = "0.4"
2020
serde_json = "1.0"
2121
thiserror = "2.0.11"
2222
tokio = { version = "1", features = ["full"] }
23+
toml = "0.8.23"
24+
serde= {version = "1.0", features = ["derive"]}
2325

2426
# Optional dependencies
2527
bdk_bitcoind_rpc = { version = "0.20.0", optional = true }

Justfile

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,31 @@ descriptors private wallet=default_wallet:
9999
# run any bitcoin-cli rpc command
100100
[group('rpc')]
101101
rpc command wallet=default_wallet:
102-
bitcoin-cli -datadir={{default_datadir}} -regtest -rpcwallet={{wallet}} -rpcuser={{rpc_user}} -rpcpassword={{rpc_password}} {{command}}
102+
bitcoin-cli -datadir={{default_datadir}} -regtest -rpcwallet={{wallet}} -rpcuser={{rpc_user}} -rpcpassword={{rpc_password}} {{command}}
103+
104+
[group('wallet')]
105+
init wallet_name ext_descriptor int_descriptor client_type url database_type='sqlite' rpc_user='user' rpc_password='password' force='false':
106+
mkdir -p {{default_datadir}}
107+
# Check if wallet configuration exists
108+
if [ "{{force}}" = "false" ] && grep -Fx "[wallets.{{wallet_name}}]" {{default_datadir}}/config.toml > /dev/null; then \
109+
echo "Error: Wallet '{{wallet_name}}' already configured in {{default_datadir}}/config.toml. Use --force to overwrite."; \
110+
exit 1; \
111+
fi
112+
# Remove existing configuration if --force is true
113+
if [ "{{force}}" = "true" ] && grep -Fx "[wallets.{{wallet_name}}]" {{default_datadir}}/config.toml > /dev/null; then \
114+
sed -i.bak '/^\[wallets\.{{wallet_name}}\]/,/^\[/d' {{default_datadir}}/config.toml; \
115+
sed -i.bak '/^\[wallets\.{{wallet_name}}\]/d' {{default_datadir}}/config.toml; \
116+
rm {{default_datadir}}/config.toml.bak; \
117+
fi
118+
# Append new configuration
119+
echo "" >> {{default_datadir}}/config.toml || touch {{default_datadir}}/config.toml
120+
echo "[wallets.{{wallet_name}}]" >> {{default_datadir}}/config.toml
121+
echo "name = \"{{wallet_name}}\"" >> {{default_datadir}}/config.toml
122+
echo "ext_descriptor = \"{{ext_descriptor}}\"" >> {{default_datadir}}/config.toml
123+
echo "int_descriptor = \"{{int_descriptor}}\"" >> {{default_datadir}}/config.toml
124+
echo "database_type = \"sqlite\"" >> {{default_datadir}}/config.toml
125+
echo "client_type = \"{{client_type}}\"" >> {{default_datadir}}/config.toml
126+
echo "server_url = \"{{url}}\"" >> {{default_datadir}}/config.toml
127+
echo "rpc_user = \"{{rpc_user}}\"" >> {{default_datadir}}/config.toml
128+
echo "rpc_password = \"{{rpc_password}}\"" >> {{default_datadir}}/config.toml
129+
echo "Wallet configuration for {{wallet_name}} added to {{default_datadir}}/config.toml"

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,70 @@ Note: You can modify the `Justfile` to reflect your nodes' configuration values.
193193
cargo run --features rpc -- wallet -u "127.0.0.1:18443" -c rpc -a user:password sync
194194
cargo run --features rpc -- wallet -u "127.0.0.1:18443" -c rpc -a user:password balance
195195
```
196+
197+
### Initializing a Wallet with `just`
198+
199+
When using `bdk-cli`, repeatedly specifying parameter values for each command can be tedious. The `just` command allows you to initialize a wallet with configuration values once, saving them for reuse in subsequent `bdk-cli` commands.
200+
This eliminate the need to provide repetitive arguments. To set up a wallet with persistent configuration values, use the following just command:
201+
202+
```shell
203+
just init <wallet_name> <ext_descriptor> <int_descriptor> <database_type> <client_type> <url> <rpc_user> <rpc_password> [--force]
204+
```
205+
The arguments must be provided in the order shown above. Replace each placeholder with the corresponding value:
206+
207+
> * `wallet_name`: The unique name for your wallet (e.g., my_wallet)
208+
> * `ext_descriptor`: The external descriptor for generating receiving addresses (e.g., tr(tprv.../0/*)#checksum)
209+
> * `int_descriptor`: The internal descriptor for generating change addresses (e.g., tr(tprv.../1/*)#checksum)
210+
> * `database_type`: The database type for wallet persistence (e.g., sqlite). Defaults to `sqlite` if omitted
211+
> * `client_type`: The blockchain backend (e.g., electrum, esplora, rpc, cbf)
212+
> * `url`: The server URL for the blockchain backend (e.g., ssl://mempool.space:60602 for Electrum).
213+
> * `rpc_user`: The RPC username for rpc client type (e.g., user). Defaults to user if omitted.
214+
> * `rpc_password`: The RPC password for rpc client type (e.g., password). Defaults to password if omitted.
215+
> * `--force`: Optional. Overwrites existing configuration for the specified <wallet_name> if set. By default, `just init` fails if the wallet config values already exists.
216+
217+
#### Example
218+
219+
To initialize a wallet named `my_wallet` with `electrum` as the backend:
220+
221+
```shell
222+
just init my_wallet "tr(tprv8Z.../0/*)#dtdqk3dx" "tr(tprv8Z.../1/*)#ulgptya7" sqlite electrum "ssl://mempool.space:60602" user password
223+
```
224+
225+
To overwrite an existing wallet configuration:
226+
227+
```shell
228+
just init-wallet my_wallet "tr(tprv8Z.../0/*)#dtdqk3dx" "tr(tprv8Z.../1/*)#ulgptya7" sqlite electrum "ssl://mempool.space:60602" user password --force
229+
```
230+
231+
You can omit the following arguments to use their default values:
232+
233+
`database_type`: Defaults to sqlite.
234+
`rpc_user`: Defaults to user.
235+
`rpc_password`: Defaults to password.
236+
237+
For example, to initialize a wallet with default database_type, rpc_user, and rpc_password:
238+
239+
```shell
240+
just init-wallet my_wallet "tr(tprv8Z.../0/*)#dtdqk3dx" "tr(tprv8Z.../1/*)#ulgptya7" electrum "ssl://mempool.space:60602"
241+
```
242+
243+
#### Using Saved Configuration
244+
245+
After initializing a wallet with `just init`, the configuration is saved in `~/.bdk-bitcoin/config.toml`. You can then run `bdk-cli` wallet commands without specifying the parameters, referencing only the wallet name and network.
246+
247+
With the wallet `my_wallet` initialized, generate a new address and sync the wallet as follow:
248+
249+
```shell
250+
cargo run --features electrum,sqlite -- -n signet wallet -w my_wallet new_address
251+
252+
cargo run --features electrum,sqlite -- -n signet wallet -w my_wallet sync
253+
```
254+
255+
#### Notes:
256+
257+
* Each wallet has its own configuration, allowing multiple wallets with different settings (e.g., different descriptors or backends).
258+
* You can override saved configuration values for a single command by specifying them explicitly (e.g., `--client-type esplora` or `--url https://mempool.space/signet/api`).
259+
260+
## Minimum Supported Rust Version (MSRV)
261+
262+
This library should always compile with any valid combination of features on Rust **1.75.0**.

src/commands.rs

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
1515
#![allow(clippy::large_enum_variant)]
1616

17+
use crate::config::WalletConfig;
18+
use crate::error::BDKCliError as Error;
1719
use bdk_wallet::bitcoin::{
1820
Address, Network, OutPoint, ScriptBuf,
1921
bip32::{DerivationPath, Xpriv},
2022
};
21-
use clap::{Args, Parser, Subcommand, ValueEnum, value_parser};
23+
use clap::{value_parser, Args, Parser, Subcommand, ValueEnum};
24+
use std::path::Path;
2225

2326
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
2427
use crate::utils::parse_proxy_auth;
@@ -167,15 +170,21 @@ pub struct WalletOpts {
167170
feature = "rpc",
168171
feature = "cbf"
169172
))]
170-
#[arg(env = "CLIENT_TYPE", short = 'c', long, value_enum, required = true)]
171-
pub client_type: ClientType,
173+
#[arg(env = "CLIENT_TYPE", short = 'c', long, value_enum)]
174+
pub client_type: Option<ClientType>,
172175
#[cfg(feature = "sqlite")]
173-
#[arg(env = "DATABASE_TYPE", short = 'd', long, value_enum, required = true)]
174-
pub database_type: DatabaseType,
176+
#[arg(
177+
env = "DATABASE_TYPE",
178+
short = 'd',
179+
long,
180+
value_enum,
181+
default_value = "sqlite"
182+
)]
183+
pub database_type: Option<DatabaseType>,
175184
/// Sets the server url.
176185
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
177-
#[arg(env = "SERVER_URL", short = 'u', long, required = true)]
178-
pub url: String,
186+
#[arg(env = "SERVER_URL", short = 'u', long)]
187+
pub url: Option<String>,
179188
/// Electrum batch size.
180189
#[cfg(feature = "electrum")]
181190
#[arg(env = "ELECTRUM_BATCH_SIZE", short = 'b', long, default_value = "10")]
@@ -198,7 +207,7 @@ pub struct WalletOpts {
198207
value_parser = parse_proxy_auth,
199208
default_value = "user:password",
200209
)]
201-
pub basic_auth: (String, String),
210+
pub basic_auth: Option<(String, String)>,
202211
#[cfg(feature = "rpc")]
203212
/// Sets an optional cookie authentication.
204213
#[arg(env = "COOKIE")]
@@ -208,6 +217,64 @@ pub struct WalletOpts {
208217
pub compactfilter_opts: CompactFilterOpts,
209218
}
210219

220+
impl WalletOpts {
221+
/// Load configuration from TOML file and merge with CLI options
222+
pub fn load_config(&mut self, wallet_name: &str, datadir: &Path) -> Result<(), Error> {
223+
if let Some(config) = WalletConfig::load(datadir)? {
224+
if let Ok(config_opts) = config.get_wallet_opts(wallet_name) {
225+
self.wallet = self.wallet.take().or(config_opts.wallet);
226+
self.verbose = self.verbose || config_opts.verbose;
227+
self.ext_descriptor = self.ext_descriptor.take().or(config_opts.ext_descriptor);
228+
self.int_descriptor = self.int_descriptor.take().or(config_opts.int_descriptor);
229+
#[cfg(any(
230+
feature = "electrum",
231+
feature = "esplora",
232+
feature = "rpc",
233+
feature = "cbf"
234+
))]
235+
{
236+
self.client_type = self.client_type.clone().or(config_opts.client_type);
237+
}
238+
#[cfg(feature = "sqlite")]
239+
{
240+
// prioritizing the config.toml value for database type as it has a default value
241+
self.database_type = config_opts.database_type.or(self.database_type.clone());
242+
}
243+
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
244+
{
245+
self.url = self.url.take().or(config_opts.url);
246+
}
247+
#[cfg(feature = "electrum")]
248+
{
249+
self.batch_size = if self.batch_size != 10 {
250+
config_opts.batch_size
251+
} else {
252+
self.batch_size
253+
};
254+
}
255+
#[cfg(feature = "esplora")]
256+
{
257+
self.parallel_requests = if self.parallel_requests != 5 {
258+
config_opts.parallel_requests
259+
} else {
260+
self.parallel_requests
261+
};
262+
}
263+
#[cfg(feature = "rpc")]
264+
{
265+
self.basic_auth = self.basic_auth.take().or(config_opts.basic_auth);
266+
self.cookie = self.cookie.take().or(config_opts.cookie);
267+
}
268+
#[cfg(feature = "cbf")]
269+
{
270+
self.compactfilter_opts = config_opts.compactfilter_opts;
271+
}
272+
}
273+
}
274+
Ok(())
275+
}
276+
}
277+
211278
/// Options to configure a SOCKS5 proxy for a blockchain client connection.
212279
#[cfg(any(feature = "electrum", feature = "esplora"))]
213280
#[derive(Debug, Args, Clone, PartialEq, Eq)]

0 commit comments

Comments
 (0)