soroban_cli/commands/ledger/entry/fetch/
trustline.rs

1use std::array::TryFromSliceError;
2use std::fmt::Debug;
3
4use super::args::Args;
5use crate::{
6    commands::config::{self, locator},
7    xdr::{
8        AccountId, AlphaNum12, AlphaNum4, AssetCode12, AssetCode4, LedgerKey, LedgerKeyTrustLine,
9        MuxedAccount, PublicKey, TrustLineAsset, Uint256,
10    },
11};
12use clap::{command, Parser};
13use stellar_strkey::ed25519::PublicKey as Ed25519PublicKey;
14
15#[derive(Parser, Debug, Clone)]
16#[group(skip)]
17pub struct Cmd {
18    #[command(flatten)]
19    pub args: Args,
20
21    /// Account alias or address to lookup
22    #[arg(long)]
23    pub account: String,
24
25    /// Assets to get trustline info for
26    #[arg(long, required = true)]
27    pub asset: Vec<String>,
28
29    /// If account is a seed phrase use this hd path, default is 0
30    #[arg(long)]
31    pub hd_path: Option<usize>,
32}
33
34#[derive(thiserror::Error, Debug)]
35pub enum Error {
36    #[error(transparent)]
37    Config(#[from] config::key::Error),
38    #[error("provided asset is invalid: {0}")]
39    InvalidAsset(String),
40    #[error("provided data name is invalid: {0}")]
41    InvalidDataName(String),
42    #[error(transparent)]
43    Locator(#[from] locator::Error),
44    #[error(transparent)]
45    TryFromSliceError(#[from] TryFromSliceError),
46    #[error(transparent)]
47    Run(#[from] super::args::Error),
48}
49
50impl Cmd {
51    pub async fn run(&self) -> Result<(), Error> {
52        let mut ledger_keys = vec![];
53        self.insert_asset_keys(&mut ledger_keys)?;
54        Ok(self.args.run(ledger_keys).await?)
55    }
56
57    fn insert_asset_keys(&self, ledger_keys: &mut Vec<LedgerKey>) -> Result<(), Error> {
58        let acc = self.muxed_account(&self.account)?;
59        for asset in &self.asset {
60            let asset = if asset.eq_ignore_ascii_case("XLM") {
61                TrustLineAsset::Native
62            } else if asset.contains(':') {
63                let mut parts = asset.split(':');
64                let code = parts.next().ok_or(Error::InvalidAsset(asset.clone()))?;
65                let issuer = parts.next().ok_or(Error::InvalidAsset(asset.clone()))?;
66                if parts.next().is_some() {
67                    Err(Error::InvalidAsset(asset.clone()))?;
68                }
69                let source_bytes = Ed25519PublicKey::from_string(issuer).unwrap().0;
70                let issuer = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(source_bytes)));
71
72                match code.len() {
73                    4 => TrustLineAsset::CreditAlphanum4(AlphaNum4 {
74                        asset_code: AssetCode4(code.as_bytes().try_into()?),
75                        issuer,
76                    }),
77                    12 => TrustLineAsset::CreditAlphanum12(AlphaNum12 {
78                        asset_code: AssetCode12(code.as_bytes().try_into()?),
79                        issuer,
80                    }),
81                    _ => Err(Error::InvalidAsset(asset.clone()))?,
82                }
83            } else {
84                Err(Error::InvalidAsset(asset.clone()))?
85            };
86
87            let key = LedgerKey::Trustline(LedgerKeyTrustLine {
88                account_id: acc.clone().account_id(),
89                asset,
90            });
91
92            ledger_keys.push(key);
93        }
94        Ok(())
95    }
96
97    fn muxed_account(&self, account: &str) -> Result<MuxedAccount, Error> {
98        Ok(self
99            .args
100            .locator
101            .read_key(account)?
102            .muxed_account(self.hd_path)?)
103    }
104}