Skip to content

Commit ac08b52

Browse files
committed
feat(hwi): set hwi as top level command
- add ledger and coldcard integration - update hwi as top level command - update CHANGELOG
1 parent 05b0940 commit ac08b52

File tree

5 files changed

+133
-117
lines changed

5 files changed

+133
-117
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
55

66
## [Unreleased]
77

8+
- Add `hwi` top level command with subcommands: `devices`, `register`, `address` and `sign` transaction
9+
810
## [2.0.0]
911

1012
- Removed MSRV and bumped Rust Edition to 2024
@@ -18,6 +20,7 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
1820
- Renamed `BuilderError` to `KyotoBuilderError` and added `KyotoUpdateError`
1921
- Updated `bdk_electrum` to 0.23.0
2022
- Added `just` example for starting, connecting and funding a wallet in regtest
23+
- Add `--pretty` top level flag for formatting commands output in a tabular format
2124

2225
## [1.0.0]
2326

Cargo.lock

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

src/commands.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub enum CliSubCommand {
116116
#[cfg(feature = "hwi")]
117117
Hwi {
118118
#[command(flatten)]
119-
wallet_opts: WalletOpts,
119+
hwi_opts: HwiOpts,
120120
#[clap(subcommand)]
121121
subcommand: HwiSubCommand,
122122
},
@@ -227,6 +227,23 @@ pub struct WalletOpts {
227227
pub compactfilter_opts: CompactFilterOpts,
228228
}
229229

230+
/// HWI specific options
231+
#[cfg(feature = "hwi")]
232+
#[derive(Clone, Debug, PartialEq, Eq, Args)]
233+
pub struct HwiOpts {
234+
/// Wallet name
235+
#[arg(env = "WALLET", short = 'w', long = "wallet")]
236+
pub wallet: Option<String>,
237+
238+
/// External descriptor
239+
#[arg(env = "EXT_DESCRIPTOR", short = 'e', long = "ext_descriptor")]
240+
pub ext_descriptor: Option<String>,
241+
242+
/// Database type
243+
#[arg(short = 'd', long = "database_type")]
244+
pub database_type: Option<DatabaseType>,
245+
}
246+
230247
/// Options to configure a SOCKS5 proxy for a blockchain client connection.
231248
#[cfg(any(feature = "electrum", feature = "esplora"))]
232249
#[derive(Debug, Args, Clone, PartialEq, Eq)]

src/handlers.rs

Lines changed: 67 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,20 +1073,14 @@ pub(crate) fn handle_compile_subcommand(
10731073
#[cfg(feature = "hwi")]
10741074
pub async fn handle_hwi_subcommand(
10751075
network: Network,
1076-
wallet_opts: &WalletOpts,
1076+
hwi_opts: &HwiOpts,
10771077
subcommand: HwiSubCommand,
10781078
) -> Result<serde_json::Value, Error> {
10791079
match subcommand {
10801080
HwiSubCommand::Devices => {
1081-
let devices = crate::utils::connect_to_hardware_wallet(
1082-
wallet.network(),
1083-
wallet_opts,
1084-
Some(wallet),
1085-
)
1086-
.await?;
1087-
let device = if let Some(device) = device {
1081+
let devices = crate::utils::connect_to_hardware_wallet(network, hwi_opts).await?;
1082+
let device = if let Some(device) = devices {
10881083
json!({
1089-
"type": device.device_kind().to_string(),
10901084
"fingerprint": device.get_master_fingerprint().await?.to_string(),
10911085
"model": device.device_kind().to_string(),
10921086
})
@@ -1096,97 +1090,82 @@ pub async fn handle_hwi_subcommand(
10961090
Ok(json!({ "devices": device }))
10971091
}
10981092
HwiSubCommand::Register => {
1099-
let policy = wallet_opts.ext_descriptor.clone().ok_or_else(|| {
1093+
let policy = hwi_opts.ext_descriptor.clone().ok_or_else(|| {
11001094
Error::Generic("External descriptor required for wallet registration".to_string())
11011095
})?;
1102-
let wallet_name = wallet_opts.wallet.clone().ok_or_else(|| {
1096+
let wallet_name = hwi_opts.wallet.clone().ok_or_else(|| {
11031097
Error::Generic("Wallet name is required for wallet registration".to_string())
11041098
})?;
11051099

1106-
let home_dir = prepare_home_dir(None)?;
1107-
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1108-
#[cfg(feature = "sqlite")]
1109-
let wallet = {
1110-
let mut persister = match &wallet_opts.database_type {
1111-
DatabaseType::Sqlite => {
1112-
let db_file = database_path.join("wallet.sqlite");
1113-
let connection = Connection::open(db_file)?;
1114-
log::debug!("Sqlite database opened successfully");
1115-
connection
1100+
let device = crate::utils::connect_to_hardware_wallet(network, hwi_opts).await?;
1101+
1102+
match device {
1103+
None => Ok(json!({
1104+
"success": false,
1105+
"error": "No hardware wallet detected"
1106+
})),
1107+
Some(device) => match device.register_wallet(&wallet_name, &policy).await {
1108+
Ok(hmac_opt) => {
1109+
let hmac_hex = hmac_opt.map(|h| {
1110+
let bytes: &[u8] = &h;
1111+
bytes.to_lower_hex_string()
1112+
});
1113+
Ok(json!({
1114+
"success": true,
1115+
"hmac": hmac_hex
1116+
}))
11161117
}
1117-
};
1118-
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1119-
wallet.persist(&mut persister)?;
1120-
wallet
1121-
};
1122-
#[cfg(not(feature = "sqlite"))]
1123-
let wallet = new_wallet(network, wallet_opts)?;
1124-
1125-
let device = crate::utils::connect_to_hardware_wallet(
1126-
wallet.network(),
1127-
wallet_opts,
1128-
Some(wallet),
1129-
)
1130-
.await?;
1131-
let hmac = if let Some(device) = device {
1132-
let hmac = device.register_wallet(&wallet_name, &policy).await?;
1133-
hmac.map(|h| h.to_lower_hex_string())
1134-
} else {
1135-
None
1136-
};
1137-
Ok(json!({ "hmac": hmac }))
1118+
Err(e) => Err(Error::Generic(format!("Wallet registration failed: {e}"))),
1119+
},
1120+
}
11381121
}
11391122
HwiSubCommand::Address => {
1123+
let ext_descriptor = hwi_opts.ext_descriptor.clone().ok_or_else(|| {
1124+
Error::Generic("External descriptor required for address generation".to_string())
1125+
})?;
1126+
let wallet_name = hwi_opts.wallet.clone().ok_or_else(|| {
1127+
Error::Generic("Wallet name is required for address generation".to_string())
1128+
})?;
1129+
1130+
let database = hwi_opts.database_type.clone().ok_or_else(|| {
1131+
Error::Generic("Database type is required for address generation".to_string())
1132+
})?;
1133+
11401134
let home_dir = prepare_home_dir(None)?;
1141-
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1135+
let database_path = prepare_wallet_db_dir(&Some(wallet_name.clone()), &home_dir)?;
1136+
1137+
let wallet_opts = WalletOpts {
1138+
wallet: Some(wallet_name),
1139+
verbose: false,
1140+
ext_descriptor: Some(ext_descriptor),
1141+
int_descriptor: None,
1142+
#[cfg(feature = "sqlite")]
1143+
database_type: database,
1144+
};
1145+
11421146
#[cfg(feature = "sqlite")]
1143-
let wallet = {
1144-
let mut persister = match &wallet_opts.database_type {
1145-
DatabaseType::Sqlite => {
1146-
let db_file = database_path.join("wallet.sqlite");
1147-
let connection = Connection::open(db_file)?;
1148-
log::debug!("Sqlite database opened successfully");
1149-
connection
1150-
}
1151-
};
1152-
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1147+
let mut wallet = if hwi_opts.database_type.is_some() {
1148+
let db_file = database_path.join("wallet.sqlite");
1149+
let mut persister = Connection::open(db_file)?;
1150+
let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
11531151
wallet.persist(&mut persister)?;
11541152
wallet
1153+
} else {
1154+
return Err(Error::Generic(
1155+
"Could not connect to sqlite database".to_string(),
1156+
));
11551157
};
1158+
11561159
#[cfg(not(feature = "sqlite"))]
1157-
let wallet = new_wallet(network, wallet_opts)?;
1160+
let mut wallet = new_wallet(network, &wallet_opts)?;
11581161

11591162
let address = wallet.next_unused_address(KeychainKind::External);
11601163
Ok(json!({ "address": address.address }))
11611164
}
11621165
HwiSubCommand::Sign { psbt } => {
1163-
let home_dir = prepare_home_dir(None)?;
1164-
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1165-
#[cfg(feature = "sqlite")]
1166-
let wallet = {
1167-
let mut persister = match &wallet_opts.database_type {
1168-
DatabaseType::Sqlite => {
1169-
let db_file = database_path.join("wallet.sqlite");
1170-
let connection = Connection::open(db_file)?;
1171-
log::debug!("Sqlite database opened successfully");
1172-
connection
1173-
}
1174-
};
1175-
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1176-
wallet.persist(&mut persister)?;
1177-
wallet
1178-
};
1179-
#[cfg(not(feature = "sqlite"))]
1180-
let wallet = new_wallet(network, wallet_opts)?;
1181-
11821166
let mut psbt = Psbt::from_str(&psbt)
11831167
.map_err(|e| Error::Generic(format!("Failed to parse PSBT: {e}")))?;
1184-
let device = crate::utils::connect_to_hardware_wallet(
1185-
wallet.network(),
1186-
wallet_opts,
1187-
Some(wallet),
1188-
)
1189-
.await?;
1168+
let device = crate::utils::connect_to_hardware_wallet(network, hwi_opts).await?;
11901169
let signed_psbt = if let Some(device) = device {
11911170
device
11921171
.sign_tx(&mut psbt)
@@ -1409,6 +1388,15 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
14091388
}
14101389
Ok("".to_string())
14111390
}
1391+
1392+
#[cfg(feature = "hwi")]
1393+
CliSubCommand::Hwi {
1394+
hwi_opts,
1395+
subcommand,
1396+
} => {
1397+
let result = handle_hwi_subcommand(network, &hwi_opts, subcommand).await?;
1398+
Ok(serde_json::to_string_pretty(&result).map_err(|e| Error::SerdeJson(e))?)
1399+
}
14121400
};
14131401
result
14141402
}

0 commit comments

Comments
 (0)