Skip to content

Commit a8d766e

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 a78cc3b commit a8d766e

File tree

9 files changed

+420
-29
lines changed

9 files changed

+420
-29
lines changed

Cargo.lock

Lines changed: 87 additions & 4 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
@@ -21,6 +21,8 @@ serde_json = "1.0"
2121
thiserror = "2.0.11"
2222
tokio = { version = "1", features = ["full"] }
2323
cli-table = "0.5.0"
24+
toml = "0.8.23"
25+
serde= {version = "1.0", features = ["derive"]}
2426

2527
# Optional dependencies
2628
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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,65 @@ You can optionally return outputs of commands in human-readable, tabular format
202202
cargo run --pretty -n signet wallet -w {wallet_name} -d sqlite balance
203203
```
204204
This is available for wallet, key, repl and compile features. When ommitted, outputs default to `JSON`.
205+
### Initializing a Wallet with `just`
206+
207+
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.
208+
This eliminate the need to provide repetitive arguments. To set up a wallet with persistent configuration values, use the following just command:
209+
210+
```shell
211+
just init <wallet_name> <ext_descriptor> <int_descriptor> <database_type> <client_type> <url> <rpc_user> <rpc_password> [--force]
212+
```
213+
The arguments must be provided in the order shown above. Replace each placeholder with the corresponding value:
214+
215+
> * `wallet_name`: The unique name for your wallet (e.g., my_wallet)
216+
> * `ext_descriptor`: The external descriptor for generating receiving addresses (e.g., tr(tprv.../0/*)#checksum)
217+
> * `int_descriptor`: The internal descriptor for generating change addresses (e.g., tr(tprv.../1/*)#checksum)
218+
> * `database_type`: The database type for wallet persistence (e.g., sqlite). Defaults to `sqlite` if omitted
219+
> * `client_type`: The blockchain backend (e.g., electrum, esplora, rpc, cbf)
220+
> * `url`: The server URL for the blockchain backend (e.g., ssl://mempool.space:60602 for Electrum).
221+
> * `rpc_user`: The RPC username for rpc client type (e.g., user). Defaults to user if omitted.
222+
> * `rpc_password`: The RPC password for rpc client type (e.g., password). Defaults to password if omitted.
223+
> * `--force`: Optional. Overwrites existing configuration for the specified <wallet_name> if set. By default, `just init` fails if the wallet config values already exists.
224+
225+
#### Example
226+
227+
To initialize a wallet named `my_wallet` with `electrum` as the backend:
228+
229+
```shell
230+
just init my_wallet "tr(tprv8Z.../0/*)#dtdqk3dx" "tr(tprv8Z.../1/*)#ulgptya7" sqlite electrum "ssl://mempool.space:60602" user password
231+
```
232+
233+
To overwrite an existing wallet configuration:
234+
235+
```shell
236+
just init-wallet my_wallet "tr(tprv8Z.../0/*)#dtdqk3dx" "tr(tprv8Z.../1/*)#ulgptya7" sqlite electrum "ssl://mempool.space:60602" user password --force
237+
```
238+
239+
You can omit the following arguments to use their default values:
240+
241+
`database_type`: Defaults to sqlite.
242+
`rpc_user`: Defaults to user.
243+
`rpc_password`: Defaults to password.
244+
245+
For example, to initialize a wallet with default database_type, rpc_user, and rpc_password:
246+
247+
```shell
248+
just init-wallet my_wallet "tr(tprv8Z.../0/*)#dtdqk3dx" "tr(tprv8Z.../1/*)#ulgptya7" electrum "ssl://mempool.space:60602"
249+
```
250+
251+
#### Using Saved Configuration
252+
253+
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.
254+
255+
With the wallet `my_wallet` initialized, generate a new address and sync the wallet as follow:
256+
257+
```shell
258+
cargo run --features electrum,sqlite -- -n signet wallet -w my_wallet new_address
259+
260+
cargo run --features electrum,sqlite -- -n signet wallet -w my_wallet sync
261+
```
262+
263+
#### Notes:
264+
265+
* Each wallet has its own configuration, allowing multiple wallets with different settings (e.g., different descriptors or backends).
266+
* 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`).

src/commands.rs

Lines changed: 65 additions & 4 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;
@@ -180,8 +183,8 @@ pub struct WalletOpts {
180183
pub database_type: DatabaseType,
181184
/// Sets the server url.
182185
#[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))]
183-
#[arg(env = "SERVER_URL", short = 'u', long, required = true)]
184-
pub url: String,
186+
#[arg(env = "SERVER_URL", short = 'u', long)]
187+
pub url: Option<String>,
185188
/// Electrum batch size.
186189
#[cfg(feature = "electrum")]
187190
#[arg(env = "ELECTRUM_BATCH_SIZE", short = 'b', long, default_value = "10")]
@@ -204,7 +207,7 @@ pub struct WalletOpts {
204207
value_parser = parse_proxy_auth,
205208
default_value = "user:password",
206209
)]
207-
pub basic_auth: (String, String),
210+
pub basic_auth: Option<(String, String)>,
208211
#[cfg(feature = "rpc")]
209212
/// Sets an optional cookie authentication.
210213
#[arg(env = "COOKIE")]
@@ -214,6 +217,64 @@ pub struct WalletOpts {
214217
pub compactfilter_opts: CompactFilterOpts,
215218
}
216219

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+
217278
/// Options to configure a SOCKS5 proxy for a blockchain client connection.
218279
#[cfg(any(feature = "electrum", feature = "esplora"))]
219280
#[derive(Debug, Args, Clone, PartialEq, Eq)]

0 commit comments

Comments
 (0)