soroban_cli/config/
token.rs1use std::str::FromStr;
2
3use crate::{
4 config::{alias::UnresolvedContract, locator},
5 tx::builder,
6 utils::contract_id_hash_from_asset,
7};
8
9#[derive(thiserror::Error, Debug)]
10pub enum Error {
11 #[error(transparent)]
12 Locator(#[from] locator::Error),
13 #[error(transparent)]
14 Asset(#[from] builder::asset::Error),
15}
16
17impl Error {
18 #[must_use]
20 pub fn error_type(&self) -> &'static str {
21 match self {
22 Error::Locator(_) => "config",
23 Error::Asset(_) => "invalid_asset",
24 }
25 }
26}
27
28#[derive(Clone, Debug)]
30pub enum TokenKind {
31 Sac(crate::xdr::Asset),
33 Contract,
35}
36
37#[derive(Clone, Debug)]
43pub struct ResolvedToken {
44 pub contract_id: stellar_strkey::Contract,
45 pub kind: TokenKind,
46}
47
48impl ResolvedToken {
49 #[must_use]
51 pub fn is_sac(&self) -> bool {
52 matches!(self.kind, TokenKind::Sac(_))
53 }
54
55 #[must_use]
57 pub fn asset(&self) -> Option<&crate::xdr::Asset> {
58 match &self.kind {
59 TokenKind::Sac(asset) => Some(asset),
60 TokenKind::Contract => None,
61 }
62 }
63}
64
65#[derive(Clone, Debug)]
72pub enum UnresolvedToken {
73 Asset(builder::Asset),
75 Contract(UnresolvedContract),
77}
78
79impl FromStr for UnresolvedToken {
80 type Err = builder::asset::Error;
81
82 fn from_str(value: &str) -> Result<Self, Self::Err> {
83 if value == "native" || value.contains(':') {
88 Ok(UnresolvedToken::Asset(value.parse()?))
89 } else {
90 Ok(UnresolvedToken::Contract(value.parse().unwrap()))
92 }
93 }
94}
95
96impl UnresolvedToken {
97 pub fn resolve(
100 &self,
101 locator: &locator::Args,
102 network_passphrase: &str,
103 ) -> Result<ResolvedToken, Error> {
104 match self {
105 UnresolvedToken::Asset(asset) => {
106 let asset = asset.resolve(locator)?;
107 let contract_id = contract_id_hash_from_asset(&asset, network_passphrase);
108 Ok(ResolvedToken {
109 contract_id,
110 kind: TokenKind::Sac(asset),
111 })
112 }
113 UnresolvedToken::Contract(contract) => {
114 let contract_id = contract.resolve_contract_id(locator, network_passphrase)?;
115 Ok(ResolvedToken {
116 contract_id,
117 kind: TokenKind::Contract,
118 })
119 }
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 const CONTRACT: &str = "CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2";
130 const NETWORK: &str = "Test Network";
131
132 #[test]
133 fn native_parses_as_asset() {
134 assert!(matches!(
135 "native".parse::<UnresolvedToken>().unwrap(),
136 UnresolvedToken::Asset(builder::Asset::Native)
137 ));
138 }
139
140 #[test]
141 fn code_issuer_parses_as_asset() {
142 assert!(matches!(
143 "USDC:issuer".parse::<UnresolvedToken>().unwrap(),
144 UnresolvedToken::Asset(builder::Asset::Asset(..))
145 ));
146 }
147
148 #[test]
149 fn contract_strkey_parses_as_resolved_contract() {
150 assert!(matches!(
151 CONTRACT.parse::<UnresolvedToken>().unwrap(),
152 UnresolvedToken::Contract(UnresolvedContract::Resolved(_))
153 ));
154 }
155
156 #[test]
157 fn bare_name_parses_as_contract_alias() {
158 assert!(matches!(
159 "alice".parse::<UnresolvedToken>().unwrap(),
160 UnresolvedToken::Contract(UnresolvedContract::Alias(_))
161 ));
162 }
163
164 #[test]
165 fn native_resolves_to_sac() {
166 let locator = locator::Args::default();
167 let resolved = "native"
168 .parse::<UnresolvedToken>()
169 .unwrap()
170 .resolve(&locator, NETWORK)
171 .unwrap();
172
173 assert!(resolved.is_sac());
174 assert!(matches!(resolved.asset(), Some(crate::xdr::Asset::Native)));
175 assert_eq!(
176 resolved.contract_id,
177 contract_id_hash_from_asset(&crate::xdr::Asset::Native, NETWORK)
178 );
179 }
180
181 #[test]
182 fn code_issuer_resolves_to_sac() {
183 let locator = locator::Args::default();
184 let resolved = "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"
187 .parse::<UnresolvedToken>()
188 .unwrap()
189 .resolve(&locator, NETWORK)
190 .unwrap();
191
192 assert!(resolved.is_sac());
193 assert!(matches!(
194 resolved.asset(),
195 Some(crate::xdr::Asset::CreditAlphanum4(_))
196 ));
197 }
198
199 #[test]
200 fn contract_strkey_resolves_to_contract() {
201 let locator = locator::Args::default();
202 let resolved = CONTRACT
203 .parse::<UnresolvedToken>()
204 .unwrap()
205 .resolve(&locator, NETWORK)
206 .unwrap();
207
208 assert!(!resolved.is_sac());
209 assert!(resolved.asset().is_none());
210 assert_eq!(resolved.contract_id, CONTRACT.parse().unwrap());
211 }
212}