soroban_cli/config/
sign_with.rs

1use crate::{
2    config::UnresolvedMuxedAccount,
3    print::Print,
4    signer::{self, ledger, Signer, SignerKind},
5    xdr::{self, TransactionEnvelope},
6};
7use clap::arg;
8
9use super::{
10    locator,
11    network::{self, Network},
12    secret,
13};
14
15#[derive(thiserror::Error, Debug)]
16pub enum Error {
17    #[error(transparent)]
18    Network(#[from] network::Error),
19    #[error(transparent)]
20    Signer(#[from] signer::Error),
21    #[error(transparent)]
22    Secret(#[from] secret::Error),
23    #[error(transparent)]
24    Locator(#[from] locator::Error),
25    #[error(transparent)]
26    Rpc(#[from] soroban_rpc::Error),
27    #[error("No sign with key provided")]
28    NoSignWithKey,
29    #[error(transparent)]
30    StrKey(#[from] stellar_strkey::DecodeError),
31    #[error(transparent)]
32    Xdr(#[from] xdr::Error),
33    #[error(transparent)]
34    Ledger(#[from] signer::ledger::Error),
35}
36
37#[derive(Debug, clap::Args, Clone, Default)]
38#[group(skip)]
39pub struct Args {
40    /// Sign with a local key or key saved in OS secure storage. Can be an identity (--sign-with-key alice), a secret key (--sign-with-key SC36…), or a seed phrase (--sign-with-key "kite urban…"). If using seed phrase, `--hd-path` defaults to the `0` path.
41    #[arg(long, env = "STELLAR_SIGN_WITH_KEY")]
42    pub sign_with_key: Option<String>,
43
44    #[arg(long, conflicts_with = "sign_with_lab")]
45    /// If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0`
46    pub hd_path: Option<usize>,
47
48    #[allow(clippy::doc_markdown)]
49    /// Sign with https://lab.stellar.org
50    #[arg(long, conflicts_with = "sign_with_key", env = "STELLAR_SIGN_WITH_LAB")]
51    pub sign_with_lab: bool,
52
53    /// Sign with a ledger wallet
54    #[arg(
55        long,
56        conflicts_with = "sign_with_key",
57        conflicts_with = "sign_with_lab",
58        env = "STELLAR_SIGN_WITH_LEDGER"
59    )]
60    pub sign_with_ledger: bool,
61}
62
63impl Args {
64    // when a default_signer_account is provided, it will be used as the tx signer if the user does not specify a signer. The default signer should be the tx's source_account.
65    pub async fn sign_tx_env(
66        &self,
67        tx: &TransactionEnvelope,
68        locator: &locator::Args,
69        network: &Network,
70        quiet: bool,
71        default_signer_account: Option<&UnresolvedMuxedAccount>,
72    ) -> Result<TransactionEnvelope, Error> {
73        let print = Print::new(quiet);
74        let signer = if self.sign_with_lab {
75            Signer {
76                kind: SignerKind::Lab,
77                print,
78            }
79        } else if self.sign_with_ledger {
80            let ledger = ledger::new(
81                self.hd_path
82                    .unwrap_or_default()
83                    .try_into()
84                    .unwrap_or_default(),
85            )
86            .await?;
87            Signer {
88                kind: SignerKind::Ledger(ledger),
89                print,
90            }
91        } else {
92            // default to using the source account local key, if the user did not pass in a key
93            let key_or_name = match self.sign_with_key.as_deref() {
94                Some(k) => k,
95                None => match default_signer_account {
96                    Some(UnresolvedMuxedAccount::AliasOrSecret(ref s)) => s.as_str(),
97                    _ => return Err(Error::NoSignWithKey),
98                },
99            };
100
101            let secret = locator.get_secret_key(key_or_name)?;
102            secret.signer(self.hd_path, print).await?
103        };
104        Ok(signer.sign_tx_env(tx, network).await?)
105    }
106}