Skip to content

Commit 610a5de

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 3f3f31a commit 610a5de

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
@@ -1054,20 +1054,14 @@ pub(crate) fn handle_compile_subcommand(
10541054
#[cfg(feature = "hwi")]
10551055
pub async fn handle_hwi_subcommand(
10561056
network: Network,
1057-
wallet_opts: &WalletOpts,
1057+
hwi_opts: &HwiOpts,
10581058
subcommand: HwiSubCommand,
10591059
) -> Result<serde_json::Value, Error> {
10601060
match subcommand {
10611061
HwiSubCommand::Devices => {
1062-
let devices = crate::utils::connect_to_hardware_wallet(
1063-
wallet.network(),
1064-
wallet_opts,
1065-
Some(wallet),
1066-
)
1067-
.await?;
1068-
let device = if let Some(device) = device {
1062+
let devices = crate::utils::connect_to_hardware_wallet(network, hwi_opts).await?;
1063+
let device = if let Some(device) = devices {
10691064
json!({
1070-
"type": device.device_kind().to_string(),
10711065
"fingerprint": device.get_master_fingerprint().await?.to_string(),
10721066
"model": device.device_kind().to_string(),
10731067
})
@@ -1077,97 +1071,82 @@ pub async fn handle_hwi_subcommand(
10771071
Ok(json!({ "devices": device }))
10781072
}
10791073
HwiSubCommand::Register => {
1080-
let policy = wallet_opts.ext_descriptor.clone().ok_or_else(|| {
1074+
let policy = hwi_opts.ext_descriptor.clone().ok_or_else(|| {
10811075
Error::Generic("External descriptor required for wallet registration".to_string())
10821076
})?;
1083-
let wallet_name = wallet_opts.wallet.clone().ok_or_else(|| {
1077+
let wallet_name = hwi_opts.wallet.clone().ok_or_else(|| {
10841078
Error::Generic("Wallet name is required for wallet registration".to_string())
10851079
})?;
10861080

1087-
let home_dir = prepare_home_dir(None)?;
1088-
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1089-
#[cfg(feature = "sqlite")]
1090-
let wallet = {
1091-
let mut persister = match &wallet_opts.database_type {
1092-
DatabaseType::Sqlite => {
1093-
let db_file = database_path.join("wallet.sqlite");
1094-
let connection = Connection::open(db_file)?;
1095-
log::debug!("Sqlite database opened successfully");
1096-
connection
1081+
let device = crate::utils::connect_to_hardware_wallet(network, hwi_opts).await?;
1082+
1083+
match device {
1084+
None => Ok(json!({
1085+
"success": false,
1086+
"error": "No hardware wallet detected"
1087+
})),
1088+
Some(device) => match device.register_wallet(&wallet_name, &policy).await {
1089+
Ok(hmac_opt) => {
1090+
let hmac_hex = hmac_opt.map(|h| {
1091+
let bytes: &[u8] = &h;
1092+
bytes.to_lower_hex_string()
1093+
});
1094+
Ok(json!({
1095+
"success": true,
1096+
"hmac": hmac_hex
1097+
}))
10971098
}
1098-
};
1099-
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1100-
wallet.persist(&mut persister)?;
1101-
wallet
1102-
};
1103-
#[cfg(not(feature = "sqlite"))]
1104-
let wallet = new_wallet(network, wallet_opts)?;
1105-
1106-
let device = crate::utils::connect_to_hardware_wallet(
1107-
wallet.network(),
1108-
wallet_opts,
1109-
Some(wallet),
1110-
)
1111-
.await?;
1112-
let hmac = if let Some(device) = device {
1113-
let hmac = device.register_wallet(&wallet_name, &policy).await?;
1114-
hmac.map(|h| h.to_lower_hex_string())
1115-
} else {
1116-
None
1117-
};
1118-
Ok(json!({ "hmac": hmac }))
1099+
Err(e) => Err(Error::Generic(format!("Wallet registration failed: {e}"))),
1100+
},
1101+
}
11191102
}
11201103
HwiSubCommand::Address => {
1104+
let ext_descriptor = hwi_opts.ext_descriptor.clone().ok_or_else(|| {
1105+
Error::Generic("External descriptor required for address generation".to_string())
1106+
})?;
1107+
let wallet_name = hwi_opts.wallet.clone().ok_or_else(|| {
1108+
Error::Generic("Wallet name is required for address generation".to_string())
1109+
})?;
1110+
1111+
let database = hwi_opts.database_type.clone().ok_or_else(|| {
1112+
Error::Generic("Database type is required for address generation".to_string())
1113+
})?;
1114+
11211115
let home_dir = prepare_home_dir(None)?;
1122-
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1116+
let database_path = prepare_wallet_db_dir(&Some(wallet_name.clone()), &home_dir)?;
1117+
1118+
let wallet_opts = WalletOpts {
1119+
wallet: Some(wallet_name),
1120+
verbose: false,
1121+
ext_descriptor: Some(ext_descriptor),
1122+
int_descriptor: None,
1123+
#[cfg(feature = "sqlite")]
1124+
database_type: database,
1125+
};
1126+
11231127
#[cfg(feature = "sqlite")]
1124-
let wallet = {
1125-
let mut persister = match &wallet_opts.database_type {
1126-
DatabaseType::Sqlite => {
1127-
let db_file = database_path.join("wallet.sqlite");
1128-
let connection = Connection::open(db_file)?;
1129-
log::debug!("Sqlite database opened successfully");
1130-
connection
1131-
}
1132-
};
1133-
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1128+
let mut wallet = if hwi_opts.database_type.is_some() {
1129+
let db_file = database_path.join("wallet.sqlite");
1130+
let mut persister = Connection::open(db_file)?;
1131+
let mut wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
11341132
wallet.persist(&mut persister)?;
11351133
wallet
1134+
} else {
1135+
return Err(Error::Generic(
1136+
"Could not connect to sqlite database".to_string(),
1137+
));
11361138
};
1139+
11371140
#[cfg(not(feature = "sqlite"))]
1138-
let wallet = new_wallet(network, wallet_opts)?;
1141+
let mut wallet = new_wallet(network, &wallet_opts)?;
11391142

11401143
let address = wallet.next_unused_address(KeychainKind::External);
11411144
Ok(json!({ "address": address.address }))
11421145
}
11431146
HwiSubCommand::Sign { psbt } => {
1144-
let home_dir = prepare_home_dir(None)?;
1145-
let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?;
1146-
#[cfg(feature = "sqlite")]
1147-
let wallet = {
1148-
let mut persister = match &wallet_opts.database_type {
1149-
DatabaseType::Sqlite => {
1150-
let db_file = database_path.join("wallet.sqlite");
1151-
let connection = Connection::open(db_file)?;
1152-
log::debug!("Sqlite database opened successfully");
1153-
connection
1154-
}
1155-
};
1156-
let mut wallet = new_persisted_wallet(network, &mut persister, wallet_opts)?;
1157-
wallet.persist(&mut persister)?;
1158-
wallet
1159-
};
1160-
#[cfg(not(feature = "sqlite"))]
1161-
let wallet = new_wallet(network, wallet_opts)?;
1162-
11631147
let mut psbt = Psbt::from_str(&psbt)
11641148
.map_err(|e| Error::Generic(format!("Failed to parse PSBT: {e}")))?;
1165-
let device = crate::utils::connect_to_hardware_wallet(
1166-
wallet.network(),
1167-
wallet_opts,
1168-
Some(wallet),
1169-
)
1170-
.await?;
1149+
let device = crate::utils::connect_to_hardware_wallet(network, hwi_opts).await?;
11711150
let signed_psbt = if let Some(device) = device {
11721151
device
11731152
.sign_tx(&mut psbt)
@@ -1390,6 +1369,15 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
13901369
}
13911370
Ok("".to_string())
13921371
}
1372+
1373+
#[cfg(feature = "hwi")]
1374+
CliSubCommand::Hwi {
1375+
hwi_opts,
1376+
subcommand,
1377+
} => {
1378+
let result = handle_hwi_subcommand(network, &hwi_opts, subcommand).await?;
1379+
Ok(serde_json::to_string_pretty(&result).map_err(|e| Error::SerdeJson(e))?)
1380+
}
13931381
};
13941382
result
13951383
}

0 commit comments

Comments
 (0)