dynamic_waas_sdk/
client.rs1use dynamic_waas_sdk_core::{
9 api::{ApiClient, ApiClientOpts, Auth},
10 Environment, Error, Result, ThresholdSignatureScheme, WalletProperties,
11};
12use tracing::{debug, instrument};
13
14const DEFAULT_BASE_API_URL: &str = "https://app.dynamicauth.com";
16
17#[derive(Debug, Clone)]
20#[non_exhaustive]
21pub struct DynamicWalletClientOpts {
22 pub environment_id: String,
23 pub base_api_url: Option<String>,
24 pub base_mpc_relay_url: Option<String>,
25}
26
27impl DynamicWalletClientOpts {
28 pub fn new(environment_id: impl Into<String>) -> Self {
29 Self {
30 environment_id: environment_id.into(),
31 base_api_url: None,
32 base_mpc_relay_url: None,
33 }
34 }
35
36 #[must_use]
37 pub fn base_api_url(mut self, url: impl Into<String>) -> Self {
38 self.base_api_url = Some(url.into());
39 self
40 }
41
42 #[must_use]
43 pub fn base_mpc_relay_url(mut self, url: impl Into<String>) -> Self {
44 self.base_mpc_relay_url = Some(url.into());
45 self
46 }
47}
48
49pub struct DynamicWalletClient {
52 api: ApiClient,
53 base_api_url: String,
54 environment_id: String,
55 #[allow(dead_code)]
56 base_mpc_relay_url: String,
57 is_authenticated: bool,
58}
59
60impl DynamicWalletClient {
61 pub fn new(opts: DynamicWalletClientOpts) -> Result<Self> {
66 let base_api_url = opts
67 .base_api_url
68 .unwrap_or_else(|| DEFAULT_BASE_API_URL.to_string());
69 let env = Environment::detect(&base_api_url);
70 let base_mpc_relay_url = opts
71 .base_mpc_relay_url
72 .unwrap_or_else(|| env.mpc_relay_url().to_string());
73
74 let api = ApiClient::new(Self::build_api_opts(
75 &base_api_url,
76 &opts.environment_id,
77 env,
78 Auth::Unauthenticated,
79 ))?;
80
81 Ok(Self {
82 api,
83 base_api_url,
84 environment_id: opts.environment_id,
85 base_mpc_relay_url,
86 is_authenticated: false,
87 })
88 }
89
90 pub(crate) fn build_api_opts(
95 base_api_url: &str,
96 environment_id: &str,
97 env: Environment,
98 auth: Auth,
99 ) -> ApiClientOpts {
100 ApiClientOpts {
101 base_api_url: base_api_url.to_owned(),
102 environment_id: environment_id.to_owned(),
103 auth,
104 relay_base_url: Some(crate::mpc_config::keyshares_relay_url_for(env).to_owned()),
105 relay_app_id: crate::mpc_config::relay_app_id_for(env).map(str::to_owned),
106 relay_api_key: crate::mpc_config::relay_api_key_for(env).map(str::to_owned),
107 }
108 }
109
110 pub(crate) fn new_delegated(
115 opts: DynamicWalletClientOpts,
116 wallet_api_key: String,
117 ) -> Result<Self> {
118 let base_api_url = opts
119 .base_api_url
120 .unwrap_or_else(|| DEFAULT_BASE_API_URL.to_string());
121 let env = Environment::detect(&base_api_url);
122 let base_mpc_relay_url = opts
123 .base_mpc_relay_url
124 .unwrap_or_else(|| env.mpc_relay_url().to_string());
125 let api = ApiClient::new(Self::build_api_opts(
126 &base_api_url,
127 &opts.environment_id,
128 env,
129 Auth::Delegated(wallet_api_key),
130 ))?;
131 Ok(Self {
132 api,
133 base_api_url,
134 environment_id: opts.environment_id,
135 base_mpc_relay_url,
136 is_authenticated: true,
137 })
138 }
139
140 pub fn environment_id(&self) -> &str {
141 &self.environment_id
142 }
143
144 pub fn is_authenticated(&self) -> bool {
145 self.is_authenticated
146 }
147
148 #[instrument(skip(self, auth_token), level = "debug")]
153 pub async fn authenticate_api_token(&mut self, auth_token: &str) -> Result<()> {
154 let env = Environment::detect(&self.base_api_url);
155 let tmp = ApiClient::new(Self::build_api_opts(
157 &self.base_api_url,
158 &self.environment_id,
159 env,
160 Auth::Bearer(auth_token.to_owned()),
161 ))?;
162 let response = tmp
163 .authenticate_api_token()
164 .await
165 .map_err(|e| Error::Authentication(format!("token exchange failed: {e}")))?;
166
167 self.api = ApiClient::new(Self::build_api_opts(
169 &self.base_api_url,
170 &self.environment_id,
171 env,
172 Auth::Bearer(response.encoded_jwts.minified_jwt),
173 ))?;
174 self.is_authenticated = true;
175 debug!("authenticated successfully");
176 Ok(())
177 }
178
179 #[instrument(skip(self), level = "debug")]
188 pub async fn fetch_wallet_metadata(&self, account_address: &str) -> Result<WalletProperties> {
189 self.ensure_authenticated()?;
190 let raw = self.api.get_waas_wallet_by_address(account_address).await?;
191 let threshold = match raw.threshold_signature_scheme.as_deref() {
192 Some("TWO_OF_TWO") | None => ThresholdSignatureScheme::TwoOfTwo,
193 Some("TWO_OF_THREE") => ThresholdSignatureScheme::TwoOfThree,
194 Some(other) => {
195 return Err(Error::InvalidArgument(format!(
196 "unknown threshold signature scheme: {other}"
197 )))
198 }
199 };
200 let mut wp = WalletProperties::new(raw.chain_name, raw.wallet_id, raw.account_address)
201 .with_threshold(threshold);
202 if let Some(path) = raw.derivation_path {
203 wp = wp.with_derivation_path(path);
204 }
205 Ok(wp)
206 }
207
208 fn ensure_authenticated(&self) -> Result<()> {
209 if self.is_authenticated {
210 Ok(())
211 } else {
212 Err(Error::Authentication(
213 "client must be authenticated before making API calls — \
214 call authenticate_api_token first"
215 .into(),
216 ))
217 }
218 }
219
220 pub(crate) fn api(&self) -> &ApiClient {
222 &self.api
223 }
224
225 pub(crate) fn base_mpc_relay_url(&self) -> &str {
226 &self.base_mpc_relay_url
227 }
228}