wallet_standard_browser/
browser_wallet_info.rs1#![allow(unsafe_code)]
2
3use std::collections::hash_map::DefaultHasher;
4use std::hash::Hash;
5use std::hash::Hasher;
6
7use js_sys::Array;
8use js_sys::Function;
9use js_sys::Object;
10use wallet_standard::WalletAccountInfo;
11use wallet_standard::WalletError;
12use wallet_standard::WalletInfo;
13use wallet_standard::WalletResult;
14use wasm_bindgen::prelude::*;
15use wasm_bindgen::JsCast;
16use wasm_bindgen::JsValue;
17
18use crate::FeatureFromJs;
19use crate::StandardConnectFeature;
20use crate::StandardDisconnectFeature;
21use crate::StandardEventsFeature;
22
23#[wasm_bindgen(module = "/js/wallet.js")]
24extern "C" {
25 #[allow(unsafe_code)]
44 #[wasm_bindgen(js_name = registerWallet, catch)]
45 pub fn register_wallet(wallet: &BrowserWalletInfo) -> Result<(), JsValue>;
46}
47
48#[wasm_bindgen(module = "/js/app.js")]
49extern "C" {
50 #[derive(Clone, Debug)]
51 pub type BrowserWalletInfo;
52 #[wasm_bindgen(getter, method, js_name = version)]
58 pub fn _version(this: &BrowserWalletInfo) -> String;
59 #[wasm_bindgen(getter, method, js_name = name)]
64 pub fn _name(this: &BrowserWalletInfo) -> String;
65 #[wasm_bindgen(getter, method, js_name = icon)]
71 pub fn _icon(this: &BrowserWalletInfo) -> String;
72 #[wasm_bindgen(getter, method, js_name = chains)]
85 pub fn _chains(this: &BrowserWalletInfo) -> Vec<String>;
86 #[wasm_bindgen(getter, method, js_name = features)]
119 pub fn features_object(this: &BrowserWalletInfo) -> Object;
120 #[wasm_bindgen(getter, method, js_name = accounts)]
132 pub fn _accounts(this: &BrowserWalletInfo) -> Vec<BrowserWalletAccountInfo>;
133 #[derive(Clone, Debug)]
148 pub type BrowserWalletAccountInfo;
149 #[wasm_bindgen(getter, method, js_name = address)]
151 pub fn _address(this: &BrowserWalletAccountInfo) -> String;
152 #[wasm_bindgen(getter, method, js_name = publicKey)]
154 pub fn _public_key(this: &BrowserWalletAccountInfo) -> Vec<u8>;
155 #[wasm_bindgen(getter, method, js_name = chains)]
160 pub fn _chains(this: &BrowserWalletAccountInfo) -> Vec<String>;
161 #[wasm_bindgen(getter, method, js_name = features)]
166 pub fn _features(this: &BrowserWalletAccountInfo) -> Vec<String>;
167 #[wasm_bindgen(getter, method, js_name = label)]
170 pub fn _label(this: &BrowserWalletAccountInfo) -> Option<String>;
171 #[wasm_bindgen(getter, method, js_name = icon)]
174 pub fn _icon(this: &BrowserWalletAccountInfo) -> Option<String>;
175 #[derive(Clone, Debug)]
176 pub type Wallets;
177 #[wasm_bindgen(method)]
181 pub fn get(this: &Wallets) -> Vec<BrowserWalletInfo>;
182 #[wasm_bindgen(method)]
198 pub fn on(
199 this: &Wallets,
200 event_name: &str,
201 callback: &Closure<dyn Fn(BrowserWalletInfo)>,
202 ) -> Function;
203 #[wasm_bindgen(method, js_name = register, getter)]
216 pub fn register_fn(this: &Wallets) -> Function;
217
218 #[wasm_bindgen(js_name = getWallets)]
219 pub fn get_wallets() -> Wallets;
220}
221
222impl PartialEq for BrowserWalletInfo {
223 fn eq(&self, other: &Self) -> bool {
224 self.name().eq(&other.name())
225 && self.chains().eq(&other.chains())
226 && self.version().eq(&other.version())
227 && self.icon().eq(&other.icon())
228 }
229}
230
231impl Eq for BrowserWalletInfo {}
232
233impl Hash for BrowserWalletInfo {
234 fn hash<H: Hasher>(&self, state: &mut H) {
235 self.name().hash(state);
236 self.chains().hash(state);
237 self.version().hash(state);
238 self.icon().hash(state);
239 }
240}
241
242impl BrowserWalletInfo {
243 pub fn get_hash(&self) -> u64 {
244 let mut hasher = DefaultHasher::new();
245 self.name().hash(&mut hasher);
246 self.chains().hash(&mut hasher);
247
248 hasher.finish()
249 }
250
251 pub fn get_feature_option<T: FeatureFromJs>(&self) -> Option<T> {
253 T::feature_from_js_object(&self.features_object())
254 }
255
256 pub fn get_feature<T: FeatureFromJs>(&self) -> WalletResult<T> {
258 self.get_feature_option::<T>()
259 .ok_or(WalletError::UnsupportedFeature {
260 feature: T::NAME.to_string(),
261 wallet: self.name(),
262 })
263 }
264
265 pub fn is_feature_supported<T: FeatureFromJs>(&self) -> bool {
267 self.get_feature_option::<T>().is_some()
268 }
269
270 pub fn is_standard_compatible(&self) -> bool {
271 self.is_feature_supported::<StandardConnectFeature>()
272 && self.is_feature_supported::<StandardEventsFeature>()
273 && self.is_feature_supported::<StandardDisconnectFeature>()
274 }
275}
276
277impl WalletInfo for BrowserWalletInfo {
278 type Account = BrowserWalletAccountInfo;
279
280 fn version(&self) -> String {
281 self._version()
282 }
283
284 fn name(&self) -> String {
285 self._name()
286 }
287
288 fn icon(&self) -> String {
289 self._icon()
290 }
291
292 fn chains(&self) -> Vec<String> {
293 self._chains()
294 }
295
296 fn features(&self) -> Vec<String> {
297 Object::keys(&self.features_object())
298 .into_iter()
299 .map(|value| value.as_string().unwrap_throw())
300 .collect::<Vec<_>>()
301 }
302
303 fn accounts(&self) -> Vec<Self::Account> {
304 self._accounts()
305 }
306}
307
308impl PartialEq for BrowserWalletAccountInfo {
309 fn eq(&self, other: &Self) -> bool {
310 self.address().eq(&other.address())
311 && self.public_key().eq(&other.public_key())
312 && self.chains().eq(&other.chains())
313 && self.features().eq(&other.features())
314 && self.label().eq(&other.label())
315 && self.icon().eq(&other.icon())
316 }
317}
318
319impl Eq for BrowserWalletAccountInfo {}
320
321impl Hash for BrowserWalletAccountInfo {
322 fn hash<H: Hasher>(&self, state: &mut H) {
323 self.address().hash(state);
324 self.public_key().hash(state);
325 self.chains().hash(state);
326 self.features().hash(state);
327 self.label().hash(state);
328 self.icon().hash(state);
329 }
330}
331
332impl WalletAccountInfo for BrowserWalletAccountInfo {
333 fn address(&self) -> String {
334 self._address()
335 }
336
337 fn public_key(&self) -> Vec<u8> {
338 self._public_key()
339 }
340
341 fn chains(&self) -> Vec<String> {
342 self._chains()
343 }
344
345 fn features(&self) -> Vec<String> {
346 self._features()
347 }
348
349 fn label(&self) -> Option<String> {
350 self._label()
351 }
352
353 fn icon(&self) -> Option<String> {
354 self._icon()
355 }
356}
357
358impl Wallets {
359 pub fn on_register(&self, callback: &Closure<dyn Fn(BrowserWalletInfo)>) -> Box<dyn Fn()> {
362 let dispose = self.on("register", callback);
363
364 Box::new(move || {
365 let _ = dispose.call0(&JsValue::NULL);
366 })
367 }
368
369 pub fn on_unregister(&self, callback: &Closure<dyn Fn(BrowserWalletInfo)>) -> Box<dyn Fn()> {
372 let dispose = self.on("unregister", callback);
373
374 Box::new(move || {
375 let _ = dispose.call0(&JsValue::NULL);
376 })
377 }
378
379 pub fn register(&self, wallets: &[BrowserWalletInfo]) -> Box<dyn Fn()> {
380 let args = Array::new();
381
382 for wallet in wallets {
383 args.push(wallet.unchecked_ref());
384 }
385
386 let dispose: Function = self
387 .register_fn()
388 .apply(self.unchecked_ref(), &args)
389 .unwrap()
390 .dyn_into()
391 .unwrap();
392
393 Box::new(move || {
394 let _ = dispose.call0(&JsValue::NULL);
395 })
396 }
397}