@@ -876,6 +876,35 @@ pub fn handle_config_subcommand(
876876 wallet_opts : & WalletOpts ,
877877 force : bool ,
878878) -> Result < String , Error > {
879+ if network == Network :: Bitcoin {
880+ eprintln ! (
881+ "\n ⚠️ WARNING: You are configuring a wallet for Bitcoin MAINNET.\n \
882+ This software is experimental and not recommended for use with real funds.\n \
883+ Consider using a testnet for testing purposes. \n "
884+ ) ;
885+ }
886+
887+ let ext_descriptor = wallet_opts. ext_descriptor . clone ( ) ;
888+ let int_descriptor = wallet_opts. int_descriptor . clone ( ) ;
889+
890+ if ext_descriptor. contains ( "xprv" ) || ext_descriptor. contains ( "tprv" ) {
891+ eprintln ! (
892+ "\n ⚠️ WARNING: Your external descriptor contains PRIVATE KEYS.\n \
893+ Private keys will be saved in PLAINTEXT in the config file.\n \
894+ This is a security risk. Consider using public descriptors instead."
895+ ) ;
896+ }
897+
898+ if let Some ( ref internal_desc) = int_descriptor {
899+ if internal_desc. contains ( "xprv" ) || internal_desc. contains ( "tprv" ) {
900+ eprintln ! (
901+ "\n ⚠️ WARNING: Your internal descriptor contains PRIVATE KEYS.\n \
902+ Private keys will be saved in PLAINTEXT in the config file.\n \
903+ This is a security risk. Consider using public descriptors instead."
904+ ) ;
905+ }
906+ }
907+
879908 let mut config = WalletConfig :: load ( datadir) ?. unwrap_or ( WalletConfig {
880909 network,
881910 wallets : HashMap :: new ( ) ,
@@ -887,8 +916,6 @@ pub fn handle_config_subcommand(
887916 ) ) ) ;
888917 }
889918
890- let ext_descriptor = wallet_opts. ext_descriptor . clone ( ) ;
891- let int_descriptor = wallet_opts. int_descriptor . clone ( ) ;
892919 #[ cfg( any(
893920 feature = "electrum" ,
894921 feature = "esplora" ,
@@ -1160,6 +1187,139 @@ pub(crate) fn handle_compile_subcommand(
11601187 }
11611188}
11621189
1190+ /// Handle list subcommand to show all saved wallet configurations
1191+ pub fn handle_list_subcommand ( datadir : & Path , pretty : bool ) -> Result < String , Error > {
1192+ let load_config = WalletConfig :: load ( datadir) ?;
1193+
1194+ let config = match load_config {
1195+ Some ( c) if !c. wallets . is_empty ( ) => c,
1196+ _ => {
1197+ return Ok ( if pretty {
1198+ "No wallet configurations found." . to_string ( )
1199+ } else {
1200+ serde_json:: to_string_pretty ( & json ! ( {
1201+ "wallets" : [ ]
1202+ } ) ) ?
1203+ } ) ;
1204+ }
1205+ } ;
1206+
1207+ if pretty {
1208+ let mut rows: Vec < Vec < CellStruct > > = vec ! [ ] ;
1209+
1210+ for ( name, wallet_config) in config. wallets . iter ( ) {
1211+ let mut row = vec ! [ name. cell( ) , wallet_config. network. clone( ) . cell( ) ] ;
1212+
1213+ #[ cfg( any( feature = "sqlite" , feature = "redb" ) ) ]
1214+ row. push ( wallet_config. database_type . clone ( ) . cell ( ) ) ;
1215+
1216+ #[ cfg( any(
1217+ feature = "electrum" ,
1218+ feature = "esplora" ,
1219+ feature = "rpc" ,
1220+ feature = "cbf"
1221+ ) ) ]
1222+ {
1223+ let client_str = wallet_config. client_type . as_deref ( ) . unwrap_or ( "N/A" ) ;
1224+ row. push ( client_str. cell ( ) ) ;
1225+ }
1226+
1227+ #[ cfg( any( feature = "electrum" , feature = "esplora" , feature = "rpc" ) ) ]
1228+ {
1229+ let url_str = wallet_config. server_url . as_deref ( ) . unwrap_or ( "N/A" ) ;
1230+ let display_url = if url_str. len ( ) > 20 {
1231+ shorten ( url_str, 15 , 10 )
1232+ } else {
1233+ url_str. to_string ( )
1234+ } ;
1235+ row. push ( display_url. cell ( ) ) ;
1236+ }
1237+
1238+ let ext_desc_display = if wallet_config. ext_descriptor . len ( ) > 40 {
1239+ shorten ( & wallet_config. ext_descriptor , 20 , 15 )
1240+ } else {
1241+ wallet_config. ext_descriptor . clone ( )
1242+ } ;
1243+ row. push ( ext_desc_display. cell ( ) ) ;
1244+
1245+ let has_int_desc = if wallet_config. int_descriptor . is_some ( ) {
1246+ "Yes"
1247+ } else {
1248+ "No"
1249+ } ;
1250+ row. push ( has_int_desc. cell ( ) ) ;
1251+
1252+ rows. push ( row) ;
1253+ }
1254+
1255+ let mut title_cells = vec ! [ "Wallet Name" . cell( ) . bold( true ) , "Network" . cell( ) . bold( true ) ] ;
1256+
1257+ #[ cfg( any( feature = "sqlite" , feature = "redb" ) ) ]
1258+ title_cells. push ( "Database" . cell ( ) . bold ( true ) ) ;
1259+
1260+ #[ cfg( any(
1261+ feature = "electrum" ,
1262+ feature = "esplora" ,
1263+ feature = "rpc" ,
1264+ feature = "cbf"
1265+ ) ) ]
1266+ title_cells. push ( "Client" . cell ( ) . bold ( true ) ) ;
1267+
1268+ #[ cfg( any( feature = "electrum" , feature = "esplora" , feature = "rpc" ) ) ]
1269+ title_cells. push ( "Server URL" . cell ( ) . bold ( true ) ) ;
1270+
1271+ title_cells. push ( "External Desc" . cell ( ) . bold ( true ) ) ;
1272+ title_cells. push ( "Internal Desc" . cell ( ) . bold ( true ) ) ;
1273+
1274+ let table = rows
1275+ . table ( )
1276+ . title ( title_cells)
1277+ . display ( )
1278+ . map_err ( |e| Error :: Generic ( e. to_string ( ) ) ) ?;
1279+
1280+ Ok ( format ! ( "{table}" ) )
1281+ } else {
1282+ let wallets_summary: Vec < _ > = config
1283+ . wallets
1284+ . iter ( )
1285+ . map ( |( name, wallet_config) | {
1286+ let mut wallet_json = json ! ( {
1287+ "name" : name,
1288+ "network" : wallet_config. network,
1289+ "ext_descriptor" : wallet_config. ext_descriptor,
1290+ "int_descriptor" : wallet_config. int_descriptor,
1291+ } ) ;
1292+
1293+ #[ cfg( any( feature = "sqlite" , feature = "redb" ) ) ]
1294+ {
1295+ wallet_json[ "database_type" ] = json ! ( wallet_config. database_type. clone( ) ) ;
1296+ }
1297+
1298+ #[ cfg( any(
1299+ feature = "electrum" ,
1300+ feature = "esplora" ,
1301+ feature = "rpc" ,
1302+ feature = "cbf"
1303+ ) ) ]
1304+ {
1305+ wallet_json[ "client_type" ] = json ! ( wallet_config. client_type. clone( ) ) ;
1306+ }
1307+
1308+ #[ cfg( any( feature = "electrum" , feature = "esplora" , feature = "rpc" ) ) ]
1309+ {
1310+ wallet_json[ "server_url" ] = json ! ( wallet_config. server_url. clone( ) ) ;
1311+ }
1312+
1313+ wallet_json
1314+ } )
1315+ . collect ( ) ;
1316+
1317+ Ok ( serde_json:: to_string_pretty ( & json ! ( {
1318+ "wallets" : wallets_summary
1319+ } ) ) ?)
1320+ }
1321+ }
1322+
11631323/// The global top level handler.
11641324pub ( crate ) async fn handle_command ( cli_opts : CliOpts ) -> Result < String , Error > {
11651325 let network = cli_opts. network ;
@@ -1298,6 +1458,14 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
12981458 let result = handle_config_subcommand ( & home_dir, network, wallet, & wallet_opts, force) ?;
12991459 Ok ( result)
13001460 }
1461+ CliSubCommand :: Wallet {
1462+ wallet : _,
1463+ subcommand : WalletSubCommand :: List ,
1464+ } => {
1465+ let home_dir = prepare_home_dir ( cli_opts. datadir ) ?;
1466+ let result = handle_list_subcommand ( & home_dir, pretty) ?;
1467+ Ok ( result)
1468+ }
13011469 CliSubCommand :: Key {
13021470 subcommand : key_subcommand,
13031471 } => {
@@ -1315,21 +1483,19 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
13151483 #[ cfg( feature = "repl" ) ]
13161484 CliSubCommand :: Repl {
13171485 wallet : wallet_name,
1318- mut wallet_opts,
13191486 } => {
13201487 let network = cli_opts. network ;
13211488 let home_dir = prepare_home_dir ( cli_opts. datadir . clone ( ) ) ?;
1322- wallet_opts. wallet = Some ( wallet_name. clone ( ) ) ;
13231489
13241490 let config = WalletConfig :: load ( & home_dir) ?. ok_or ( Error :: Generic ( format ! (
1325- "No config found for wallet {}" ,
1491+ "No config found for wallet {}, please run 'config' command first. " ,
13261492 wallet_name. clone( )
13271493 ) ) ) ?;
1328- let loaded_wallet_opts = config. get_wallet_opts ( & wallet_name) ?;
1494+ let wallet_opts = config. get_wallet_opts ( & wallet_name) ?;
13291495
13301496 #[ cfg( any( feature = "sqlite" , feature = "redb" ) ) ]
13311497 let ( mut wallet, mut persister) = {
1332- let mut persister: Persister = match & loaded_wallet_opts . database_type {
1498+ let mut persister: Persister = match & wallet_opts . database_type {
13331499 #[ cfg( feature = "sqlite" ) ]
13341500 DatabaseType :: Sqlite => {
13351501 let database_path = prepare_wallet_db_dir ( & home_dir, & wallet_name) ?;
@@ -1348,7 +1514,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
13481514 Persister :: RedbStore ( store)
13491515 }
13501516 } ;
1351- let wallet = new_persisted_wallet ( network, & mut persister, & loaded_wallet_opts ) ?;
1517+ let wallet = new_persisted_wallet ( network, & mut persister, & wallet_opts ) ?;
13521518 ( wallet, persister)
13531519 } ;
13541520 #[ cfg( not( any( feature = "sqlite" , feature = "redb" ) ) ) ]
@@ -1448,6 +1614,13 @@ async fn respond(
14481614 . map_err ( |e| e. to_string ( ) ) ?;
14491615 Some ( value)
14501616 }
1617+ ReplSubCommand :: Wallet {
1618+ subcommand : WalletSubCommand :: List ,
1619+ } => {
1620+ let value =
1621+ handle_list_subcommand ( & _datadir, cli_opts. pretty ) . map_err ( |e| e. to_string ( ) ) ?;
1622+ Some ( value)
1623+ }
14511624 ReplSubCommand :: Key { subcommand } => {
14521625 let value = handle_key_subcommand ( network, subcommand, cli_opts. pretty )
14531626 . map_err ( |e| e. to_string ( ) ) ?;
0 commit comments