1use std::{collections::HashMap, path::PathBuf, rc::Rc};
2
3use eyre::OptionExt;
4use solana_remote_wallet::remote_wallet::RemoteWalletManager;
5use solana_sdk::signature::{read_keypair_file, Keypair};
6use url::Url;
7
8fn parse_url_or_path(source: &str) -> eyre::Result<Url> {
10 let url = match Url::parse(source) {
11 Ok(url) => url,
12 Err(_) => {
13 let path = shellexpand::tilde(source);
14 let path: PathBuf = path.parse()?;
15 let path = std::fs::canonicalize(path)?;
16 Url::from_file_path(&path).expect("must be valid file path")
17 }
18 };
19
20 Ok(url)
21}
22
23pub fn load_keypair(source: &str) -> eyre::Result<Keypair> {
25 let url = parse_url_or_path(source)?;
26
27 match url.scheme() {
28 "file" => read_keypair_file(url.path()).map_err(|err| eyre::eyre!("{err}")),
29 other => {
30 eyre::bail!("{other} scheme is not support");
31 }
32 }
33}
34
35pub fn signer_from_source(
37 source: &str,
38 confirm_key: bool,
39 keypair_name: &str,
40 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
41) -> eyre::Result<crate::utils::LocalSignerRef> {
42 const QUERY_KEY: &str = "key";
43
44 use crate::utils::local_signer;
45 use anchor_client::solana_sdk::derivation_path::DerivationPath;
46 use solana_remote_wallet::{
47 locator::Locator, remote_keypair::generate_remote_keypair,
48 remote_wallet::maybe_wallet_manager,
49 };
50
51 let url = parse_url_or_path(source)?;
52
53 match url.scheme() {
54 "file" => {
55 let keypair = read_keypair_file(url.path()).map_err(|err| eyre::eyre!("{err}"))?;
56 Ok(local_signer(keypair))
57 }
58 "usb" => {
59 let manufacturer = url.host_str().ok_or_eyre("missing manufacturer")?;
60 let path = url.path();
61 let path = path.strip_prefix('/').unwrap_or(path);
62 let pubkey = (!path.is_empty()).then_some(path);
63 let locator = Locator::new_from_parts(manufacturer, pubkey)?;
64 let query = url.query_pairs().collect::<HashMap<_, _>>();
65 if query.len() > 1 {
66 eyre::bail!("invalid query string, extra fields not supported");
67 }
68 let derivation_path = query
69 .get(QUERY_KEY)
70 .map(|value| DerivationPath::from_key_str(value))
71 .transpose()?;
72 if wallet_manager.is_none() {
73 *wallet_manager = maybe_wallet_manager()?;
74 }
75 let wallet_manager = wallet_manager.as_ref().ok_or_eyre("no device found")?;
76 let keypair = generate_remote_keypair(
77 locator,
78 derivation_path.unwrap_or_default(),
79 wallet_manager,
80 confirm_key,
81 keypair_name,
82 )?;
83 Ok(local_signer(keypair))
84 }
85 scheme => Err(eyre::eyre!("unsupported scheme: {scheme}")),
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn test_parse_url_or_path() -> eyre::Result<()> {
95 let path = "~/.config/solana/id.json";
96 assert!(parse_url_or_path(path).is_ok());
97 Ok(())
98 }
99}