wallet_adapter/wallet_ser_der/
wallet_account.rs

1use std::borrow::Cow;
2
3use wallet_adapter_common::{
4    chains::ChainSupport,
5    clusters::Cluster,
6    feature_support::FeatureSupport,
7    standardized_events::{
8        SOLANA_SIGN_AND_SEND_TRANSACTION_IDENTIFIER, SOLANA_SIGN_IN_IDENTIFIER,
9        SOLANA_SIGN_MESSAGE_IDENTIFIER, SOLANA_SIGN_TRANSACTION_IDENTIFIER,
10        STANDARD_CONNECT_IDENTIFIER, STANDARD_DISCONNECT_IDENTIFIER, STANDARD_EVENTS_IDENTIFIER,
11    },
12    WalletAccountData, WalletCommonUtils,
13};
14use web_sys::wasm_bindgen::JsValue;
15
16use crate::{Reflection, WalletError, WalletIcon, WalletResult};
17
18/// Interface of a **WalletAccount**, also referred to as an **Account**.
19/// An account is a _read-only data object_ that is provided from the Wallet to the app,
20/// authorizing the app to use it.
21/// The app can use an account to display and query information from a chain.
22/// The app can also act using an account by passing it to `features` field of the Wallet.
23#[derive(Clone, Default, PartialEq)]
24pub struct WalletAccount {
25    pub(crate) account: WalletAccountData,
26    /// The Javascript Value Representation of a wallet,
27    /// this mostly used internally in the wallet adapter
28    pub(crate) js_value: JsValue,
29}
30
31impl WalletAccount {
32    /// Address of the account, corresponding with a public key.
33    pub fn address(&self) -> &str {
34        self.account.address.as_str()
35    }
36
37    /// Public key of the account, corresponding with a secret key to use.
38    pub fn public_key(&self) -> [u8; 32] {
39        self.account.public_key
40    }
41
42    /// Chains supported by the account.
43    /// This must be a subset of the {@link Wallet.chains | chains} of the Wallet.
44    pub fn chains(&self) -> &[String] {
45        self.account.chains.as_slice()
46    }
47
48    /// Feature names supported by the account.
49    /// This must be a subset of the names of {@link Wallet.features | features} of the Wallet.
50    pub fn features(&self) -> &[String] {
51        self.account.features.as_slice()
52    }
53
54    /// Optional user-friendly descriptive label or name for the account. This may be displayed by the app.
55    pub fn label(&self) -> Option<&String> {
56        self.account.label.as_ref()
57    }
58
59    /// An optional [Wallet Icon](String)
60    pub fn icon(&self) -> Option<&String> {
61        self.account.icon.as_ref()
62    }
63
64    /// Get the shortened address of the `Base58 address` .
65    /// It displays the first 4 characters and the last for characters
66    /// separated by ellipsis eg `FXdl...RGd4` .
67    /// If the address is less than 8 characters, an error is thrown
68    pub fn shorten_address<'a>(&'a self) -> WalletResult<Cow<'a, str>> {
69        Ok(WalletCommonUtils::shorten_base58(&self.account.address)?)
70    }
71
72    /// Same as [Self::shorten_address] but with a custom range
73    /// instead of taking the first 4 character and the last 4 characters
74    /// it uses a custom range.
75    pub fn custom_shorten_address<'a>(&'a self, take: usize) -> WalletResult<Cow<'a, str>> {
76        Ok(WalletCommonUtils::custom_shorten_base58(
77            &self.account.address,
78            take,
79        )?)
80    }
81
82    /// Same as [Self::shorten_address] but with a custom range
83    /// instead of taking the first 4 character and the last 4 characters
84    /// it uses a custom range for first characters before ellipsis and last characters after ellipsis.
85    pub fn custom_shorten_address_rl<'a>(
86        &'a self,
87        left: usize,
88        right: usize,
89    ) -> WalletResult<Cow<'a, str>> {
90        Ok(WalletCommonUtils::custom_shorten_address_rl(
91            self.account.address(),
92            left,
93            right,
94        )?)
95    }
96
97    /// Parse A [WalletAccount] from [JsValue]
98    pub(crate) fn parse(reflection: Reflection) -> WalletResult<Self> {
99        let address = reflection.string("address")?;
100        let public_key = reflection.byte32array("publicKey")?;
101        let chains = reflection.vec_string_accept_undefined("chains")?;
102        let features = reflection.vec_string_accept_undefined("features")?;
103
104        let mut supported_chains = ChainSupport::default();
105
106        chains.iter().try_for_each(|chain| {
107            if chain.as_str() == Cluster::MainNet.chain() {
108                supported_chains.mainnet = true;
109            } else if chain.as_str() == Cluster::DevNet.chain() {
110                supported_chains.devnet = true;
111            } else if chain.as_str() == Cluster::TestNet.chain() {
112                supported_chains.testnet = true;
113            } else if chain.as_str() == Cluster::LocalNet.chain() {
114                supported_chains.localnet = true;
115            } else {
116                return Err(WalletError::UnsupportedChain(chain.to_owned()));
117            }
118
119            Ok(())
120        })?;
121
122        let mut supported_features = FeatureSupport::default();
123
124        features.iter().try_for_each(|feature| {
125            if feature.as_str() == STANDARD_CONNECT_IDENTIFIER {
126                supported_features.connect = true;
127            } else if feature.as_str() == STANDARD_DISCONNECT_IDENTIFIER {
128                supported_features.disconnect = true;
129            } else if feature.as_str() == STANDARD_EVENTS_IDENTIFIER {
130                supported_features.events = true;
131            } else if feature.as_str() == SOLANA_SIGN_IN_IDENTIFIER {
132                supported_features.sign_in = true;
133            } else if feature.as_str() == SOLANA_SIGN_AND_SEND_TRANSACTION_IDENTIFIER {
134                supported_features.sign_and_send_tx = true;
135            } else if feature.as_str() == SOLANA_SIGN_TRANSACTION_IDENTIFIER {
136                supported_features.sign_tx = true;
137            } else if feature.as_str() == SOLANA_SIGN_MESSAGE_IDENTIFIER {
138                supported_features.sign_message = true;
139            } else {
140                return Err(WalletError::UnsupportedWalletFeature(feature.to_owned()));
141            }
142
143            Ok(())
144        })?;
145
146        let icon = WalletIcon::from_jsvalue(&reflection)?;
147
148        let label = match reflection.string("label") {
149            Ok(value) => Some(value),
150            Err(error) => match error {
151                WalletError::InternalError(_) => Option::None,
152                _ => {
153                    return Err(error);
154                }
155            },
156        };
157
158        let account = WalletAccountData {
159            address,
160            public_key,
161            chains,
162            features,
163            label,
164            icon,
165            supported_chains,
166            supported_features,
167        };
168
169        Ok(Self {
170            account,
171            js_value: reflection.take(),
172        })
173    }
174
175    /// Checks if MainNet is supported
176    pub fn mainnet(&self) -> bool {
177        self.account.supported_chains.mainnet
178    }
179
180    /// Checks if DevNet is supported
181    pub fn devnet(&self) -> bool {
182        self.account.supported_chains.devnet
183    }
184
185    /// Checks if TestNet is supported
186    pub fn testnet(&self) -> bool {
187        self.account.supported_chains.testnet
188    }
189
190    /// Checks if LocalNet is supported
191    pub fn localnet(&self) -> bool {
192        self.account.supported_chains.localnet
193    }
194
195    /// Checks if `standard:connect` is supported
196    pub fn standard_connect(&self) -> bool {
197        self.account.supported_features.connect
198    }
199
200    /// Checks if `standard:disconnect` is supported
201    pub fn standard_disconnect(&self) -> bool {
202        self.account.supported_features.disconnect
203    }
204
205    /// Checks if `standard:events` is supported
206    pub fn standard_events(&self) -> bool {
207        self.account.supported_features.events
208    }
209
210    /// Checks if `solana:signIn` is supported
211    pub fn solana_signin(&self) -> bool {
212        self.account.supported_features.sign_in
213    }
214
215    /// Checks if `solana:signMessage` is supported
216    pub fn solana_sign_message(&self) -> bool {
217        self.account.supported_features.sign_message
218    }
219
220    /// Checks if `solana:signAndSendTransaction` is supported
221    pub fn solana_sign_and_send_transaction(&self) -> bool {
222        self.account.supported_features.sign_and_send_tx
223    }
224
225    /// Checks if `solana:signTransaction` is supported
226    pub fn solana_sign_transaction(&self) -> bool {
227        self.account.supported_features.sign_tx
228    }
229}
230
231impl core::fmt::Debug for WalletAccount {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        f.debug_struct("WalletAccount")
234            .field("address", &self.account.address)
235            .field("public_key", &self.account.public_key)
236            .field("chains", &self.account.chains)
237            .field("features", &self.account.features)
238            .field("label", &self.account.label)
239            .field("icon", &self.account.icon)
240            .finish()
241    }
242}
243
244impl PartialOrd for WalletAccount {
245    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
246        Some(self.cmp(other))
247    }
248}
249
250impl Ord for WalletAccount {
251    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
252        let inner_self: InnerWalletAccount = self.into();
253        let inner_other: InnerWalletAccount = other.into();
254
255        inner_self.cmp(&inner_other)
256    }
257}
258
259impl core::cmp::Eq for WalletAccount {}
260
261impl core::hash::Hash for WalletAccount {
262    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
263        let inner_self: InnerWalletAccount = self.into();
264
265        inner_self.hash(state);
266    }
267}
268
269// Reduce Eq, PartialEq, Ord, Hash work
270#[derive(Eq, PartialEq, PartialOrd, Ord, Hash)]
271struct InnerWalletAccount<'a> {
272    pub address: &'a str,
273    pub public_key: &'a [u8; 32],
274    pub chains: &'a [String],
275    pub features: &'a [String],
276    pub label: Option<&'a String>,
277    pub icon: Option<&'a String>,
278}
279
280impl<'a> From<&'a WalletAccount> for InnerWalletAccount<'a> {
281    fn from(value: &'a WalletAccount) -> Self {
282        Self {
283            address: value.account.address.as_str(),
284            public_key: &value.account.public_key,
285            chains: value.account.chains.as_slice(),
286            features: &value.account.features,
287            label: value.account.label.as_ref(),
288            icon: value.account.icon.as_ref(),
289        }
290    }
291}