1use std::{borrow::Borrow, sync::Arc};
2
3use async_channel::{bounded, Receiver};
4use async_lock::RwLock;
5use ed25519_dalek::Signature;
6use wallet_adapter_common::{clusters::Cluster, signin_standard::SignInOutput};
7use web_sys::{js_sys::Object, Document, Window};
8
9use crate::{
10 events::InitEvents, send_wallet_event, SendOptions, SignedMessageOutput, SigninInput, Wallet,
11 WalletAccount, WalletError, WalletEvent, WalletEventReceiver, WalletEventSender, WalletResult,
12 WalletStorage,
13};
14
15#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
20pub struct ConnectionInfo {
21 wallet: Option<Wallet>,
22 account: Option<WalletAccount>,
23 previous_accounts: Vec<WalletAccount>,
24}
25
26impl ConnectionInfo {
27 pub fn new() -> Self {
29 ConnectionInfo::default()
30 }
31
32 pub fn set_wallet(&mut self, wallet: Wallet) -> &mut Self {
34 self.wallet.replace(wallet);
35
36 self
37 }
38
39 pub fn set_account(&mut self, account: WalletAccount) -> &mut Self {
41 self.account.replace(account);
42
43 self
44 }
45
46 pub async fn connect(&mut self, sender: WalletEventSender) -> WalletResult<WalletAccount> {
48 let wallet = self.connected_wallet()?;
49
50 let connected_account = wallet.features.connect.call_connect().await?;
51
52 self.set_account(connected_account.clone());
53
54 send_wallet_event(WalletEvent::Connected(connected_account.clone()), sender).await;
55
56 Ok(connected_account)
57 }
58
59 pub async fn set_disconnected(&mut self, sender: WalletEventSender) -> &mut Self {
61 self.wallet.take();
62 self.account.take();
63 self.previous_accounts.clear();
64
65 send_wallet_event(WalletEvent::Disconnected, sender).await;
66
67 self
68 }
69
70 pub fn connected_wallet(&self) -> WalletResult<&Wallet> {
72 self.wallet.as_ref().ok_or(WalletError::WalletNotFound)
73 }
74
75 pub fn connected_account(&self) -> WalletResult<&WalletAccount> {
77 self.account.as_ref().ok_or(WalletError::AccountNotFound)
78 }
79
80 pub fn connected_wallet_raw(&self) -> Option<&Wallet> {
83 self.wallet.as_ref()
84 }
85
86 pub fn connected_account_raw(&self) -> Option<&WalletAccount> {
89 self.account.as_ref()
90 }
91
92 pub async fn emit_wallet_event(
94 &mut self,
95 wallet_name: &str,
96 account_processing: Option<WalletAccount>,
97 sender: WalletEventSender,
98 ) {
99 match self.connected_wallet() {
100 Ok(wallet) => {
101 let event_outcome = match account_processing {
102 Some(connected_account) => {
103 if self.account.is_none()
104 && self.wallet.is_none()
105 && self.previous_accounts.is_empty()
106 {
107 self.set_account(connected_account.clone());
108
109 WalletEvent::Connected(connected_account)
110 } else if self.account.is_none()
111 && self.wallet.is_some()
112 && self.previous_accounts.iter().any(|wallet_account| {
113 wallet_account.account.public_key
114 == connected_account.account.public_key
115 })
116 {
117 self.push_previous_account();
118 self.set_account(connected_account.clone());
119
120 WalletEvent::Connected(connected_account)
121 } else if wallet.name().as_bytes() == wallet_name.as_bytes()
122 && self.account.is_none()
123 && self.previous_accounts.iter().any(|wallet_account| {
124 wallet_account.account.public_key
125 == connected_account.account.public_key
126 })
127 {
128 self.push_previous_account();
129 self.set_account(connected_account.clone());
130
131 WalletEvent::Reconnected(connected_account)
132 } else if wallet.name().as_bytes() == wallet_name.as_bytes()
133 && self.account.is_some()
134 {
135 self.push_previous_account();
136 self.set_account(connected_account.clone());
137
138 WalletEvent::AccountChanged(connected_account)
139 } else {
140 WalletEvent::Skip
141 }
142 }
143 None => {
144 if wallet.name().as_bytes() == wallet_name.as_bytes() {
145 self.push_previous_account();
146 WalletEvent::Disconnected
147 } else {
148 WalletEvent::Skip
149 }
150 }
151 };
152
153 send_wallet_event(event_outcome, sender).await
154 }
155 Err(error) => {
156 web_sys::console::log_2(
157 &"ON EVENT EMITTED BUT NO CONNECTED WALLET FOUND: ".into(),
158 &format!("{error:?}").into(),
159 );
160 }
161 }
162 }
163
164 fn push_previous_account(&mut self) {
165 let take_connected_account = self.account.take();
166
167 if let Some(connected_account_inner) = take_connected_account {
168 self.previous_accounts.push(connected_account_inner);
169 }
170
171 self.previous_accounts.dedup();
172 }
173}
174
175pub type ConnectionInfoInner = Arc<RwLock<ConnectionInfo>>;
177
178#[derive(Debug, Clone)]
182pub struct WalletAdapter {
183 window: Window,
184 document: Document,
185 storage: WalletStorage,
186 connection_info: ConnectionInfoInner,
187 wallet_events: WalletEventReceiver,
188 wallet_events_sender: WalletEventSender,
189 signal_receiver: Receiver<()>,
190}
191
192impl WalletAdapter {
193 pub fn init() -> WalletResult<Self> {
198 let window = if let Some(window) = web_sys::window() {
199 window
200 } else {
201 return Err(WalletError::MissingAccessToBrowserWindow);
202 };
203
204 let document = if let Some(document) = window.document() {
205 document
206 } else {
207 return Err(WalletError::MissingAccessToBrowserDocument);
208 };
209
210 Self::init_with_channel_capacity_window_and_document(5, window, document)
211 }
212
213 pub fn init_with_channel_capacity(capacity: usize) -> WalletResult<Self> {
217 let window = if let Some(window) = web_sys::window() {
218 window
219 } else {
220 return Err(WalletError::MissingAccessToBrowserWindow);
221 };
222
223 let document = if let Some(document) = window.document() {
224 document
225 } else {
226 return Err(WalletError::MissingAccessToBrowserDocument);
227 };
228
229 Self::init_with_channel_capacity_window_and_document(capacity, window, document)
230 }
231
232 #[allow(clippy::arc_with_non_send_sync)]
235 pub fn init_with_channel_capacity_window_and_document(
236 capacity: usize,
237 window: Window,
238 document: Document,
239 ) -> WalletResult<Self> {
240 let storage = WalletStorage::default();
241
242 let (sender, receiver) = bounded::<WalletEvent>(capacity);
243 let (_, signal_receiver) = bounded::<()>(capacity);
244
245 let mut new_self = Self {
246 window: window.clone(),
247 document,
248 storage,
249 connection_info: Arc::new(RwLock::new(ConnectionInfo::default())),
250 wallet_events: receiver,
251 wallet_events_sender: sender,
252 signal_receiver,
253 };
254
255 InitEvents::new(&window).init(&mut new_self)?;
256
257 Ok(new_self)
258 }
259
260 pub fn init_custom(window: Window, document: Document) -> WalletResult<Self> {
264 Self::init_with_channel_capacity_window_and_document(5, window, document)
265 }
266
267 pub fn events(&self) -> WalletEventReceiver {
271 self.wallet_events.clone()
272 }
273
274 pub async fn connect(&mut self, wallet: Wallet) -> WalletResult<WalletAccount> {
276 let wallet_name = wallet.name().to_string();
277 let sender = self.wallet_events_sender.clone();
278 let signal_receiver = self.signal_receiver.clone();
279
280 if self.connection_info().await.connected_wallet().is_ok() {
281 let capacity = signal_receiver.capacity().unwrap_or(5);
282 let (_, signal_receiver) = bounded::<()>(capacity);
283 self.signal_receiver = signal_receiver;
284 }
285
286 let wallet_account = self
287 .connection_info
288 .write()
289 .await
290 .set_wallet(wallet)
291 .connect(sender.clone())
292 .await?;
293
294 self.connection_info()
295 .await
296 .connected_wallet()?
297 .call_on_event(
298 self.connection_info.clone(),
299 wallet_name,
300 sender,
301 signal_receiver,
302 )
303 .await?;
304
305 Ok(wallet_account)
306 }
307
308 pub async fn connect_by_name(&mut self, wallet_name: &str) -> WalletResult<WalletAccount> {
311 let wallet = self.get_wallet(wallet_name)?;
312
313 self.connect(wallet).await
314 }
315
316 pub async fn disconnect(&mut self) {
318 let sender = self.wallet_events_sender.clone();
319
320 self.connection_info
321 .write()
322 .await
323 .set_disconnected(sender)
324 .await;
325 self.signal_receiver.close();
326 }
327
328 pub async fn sign_in(
330 &self,
331 signin_input: &SigninInput,
332 public_key: [u8; 32],
333 ) -> WalletResult<SignInOutput> {
334 self.connection_info()
335 .await
336 .connected_wallet()?
337 .sign_in(signin_input, public_key)
338 .await
339 }
340
341 pub async fn sign_and_send_transaction(
343 &self,
344 transaction_bytes: &[u8],
345 cluster: Cluster,
346 options: SendOptions,
347 ) -> WalletResult<Signature> {
348 let connection_info = self.connection_info();
349
350 connection_info
351 .await
352 .connected_wallet()?
353 .sign_and_send_transaction(
354 transaction_bytes,
355 cluster,
356 options,
357 self.connection_info().await.connected_account()?,
358 )
359 .await
360 }
361
362 pub async fn sign_transaction(
364 &self,
365 transaction_bytes: &[u8],
366 cluster: Option<Cluster>,
367 ) -> WalletResult<Vec<Vec<u8>>> {
368 let connection_info = self.connection_info();
369
370 connection_info
371 .await
372 .connected_wallet()?
373 .sign_transaction(
374 transaction_bytes,
375 cluster,
376 self.connection_info().await.connected_account()?,
377 )
378 .await
379 }
380
381 pub async fn sign_message<'a>(
383 &self,
384 message: &'a [u8],
385 ) -> WalletResult<SignedMessageOutput<'a>> {
386 let connection_info = self.connection_info();
387
388 self.connection_info()
389 .await
390 .connected_wallet()?
391 .sign_message(message, connection_info.await.connected_account()?)
392 .await
393 }
394
395 pub async fn is_connected(&self) -> bool {
397 self.connection_info
398 .as_ref()
399 .write()
400 .await
401 .account
402 .is_some()
403 }
404
405 pub async fn connection_info(&self) -> async_lock::RwLockReadGuard<'_, ConnectionInfo> {
408 self.connection_info.as_ref().read().await
409 }
410
411 pub fn get_entry(&self, property: &str) -> Option<Object> {
413 self.window.get(property)
414 }
415
416 pub fn window(&self) -> &Window {
418 &self.window
419 }
420
421 pub fn document(&self) -> &Document {
423 &self.document
424 }
425
426 pub fn storage(&self) -> &WalletStorage {
428 self.storage.borrow()
429 }
430
431 pub async fn clusters(&self) -> WalletResult<Vec<Cluster>> {
433 let mut clusters = Vec::<Cluster>::default();
434
435 if self.mainnet().await? {
436 clusters.push(Cluster::MainNet);
437 }
438 if self.devnet().await? {
439 clusters.push(Cluster::DevNet);
440 }
441 if self.localnet().await? {
442 clusters.push(Cluster::LocalNet);
443 }
444 if self.testnet().await? {
445 clusters.push(Cluster::TestNet);
446 }
447
448 Ok(clusters)
449 }
450
451 pub fn wallets(&self) -> Vec<Wallet> {
453 self.storage.borrow().get_wallets()
454 }
455
456 pub fn get_wallet(&self, wallet_name: &str) -> WalletResult<Wallet> {
458 self.storage
459 .get_wallet(wallet_name)
460 .ok_or(WalletError::WalletNotFound)
461 }
462
463 pub async fn mainnet(&self) -> WalletResult<bool> {
465 Ok(self.connection_info().await.connected_wallet()?.mainnet())
466 }
467
468 pub async fn devnet(&self) -> WalletResult<bool> {
470 Ok(self.connection_info().await.connected_wallet()?.devnet())
471 }
472
473 pub async fn testnet(&self) -> WalletResult<bool> {
475 Ok(self.connection_info().await.connected_wallet()?.testnet())
476 }
477
478 pub async fn localnet(&self) -> WalletResult<bool> {
480 Ok(self.connection_info().await.connected_wallet()?.localnet())
481 }
482
483 pub async fn standard_connect(&self) -> WalletResult<bool> {
485 Ok(self
486 .connection_info()
487 .await
488 .connected_wallet()?
489 .standard_connect())
490 }
491
492 pub async fn standard_disconnect(&self) -> WalletResult<bool> {
494 Ok(self
495 .connection_info()
496 .await
497 .connected_wallet()?
498 .standard_disconnect())
499 }
500
501 pub async fn standard_events(&self) -> WalletResult<bool> {
503 Ok(self
504 .connection_info()
505 .await
506 .connected_wallet()?
507 .standard_events())
508 }
509
510 pub async fn solana_signin(&self) -> WalletResult<bool> {
512 Ok(self
513 .connection_info()
514 .await
515 .connected_wallet()?
516 .solana_signin())
517 }
518
519 pub async fn solana_sign_message(&self) -> WalletResult<bool> {
521 Ok(self
522 .connection_info()
523 .await
524 .connected_wallet()?
525 .solana_sign_message())
526 }
527
528 pub async fn solana_sign_and_send_transaction(&self) -> WalletResult<bool> {
530 Ok(self
531 .connection_info()
532 .await
533 .connected_wallet()?
534 .solana_sign_and_send_transaction())
535 }
536
537 pub async fn solana_sign_transaction(&self) -> WalletResult<bool> {
539 Ok(self
540 .connection_info()
541 .await
542 .connected_wallet()?
543 .solana_sign_transaction())
544 }
545}
546
547impl PartialEq for WalletAdapter {
548 fn eq(&self, other: &Self) -> bool {
549 self.window.eq(&other.window)
550 && self.document.eq(&other.document)
551 && self.storage.eq(&other.storage)
552 }
553}
554impl Eq for WalletAdapter {}