1#![allow(deprecated)]
2
3use std::str::FromStr;
4
5use anyhow::Result;
6use emitter_rs::EventEmitter;
7use js_sys::{Promise, Uint8Array};
8use log::info;
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11use wasm_bindgen::JsValue;
12use wasm_bindgen_futures::JsFuture;
13use web_sys::window;
14
15use solana_client_wasm::WasmClient as RpcClient;
16use solana_sdk::{bs58, pubkey::Pubkey, signature::Signature, transaction::Transaction};
17
18use crate::{
19 adapter::{
20 backpack::{BACKPACK, XNFT},
21 phantom::SOLANA,
22 solflare::SOLFLARE,
23 },
24 core::{
25 error::WalletError,
26 response::{JsSignatureObject, JsSignatureResponse, SignaturesObject},
27 traits::{WalletAdapter, WalletAdapterEvents},
28 transaction::TransactionOrVersionedTransaction,
29 },
30};
31
32#[derive(Debug, Clone, PartialEq, Default)]
33pub enum Wallet {
34 #[default]
35 Phantom,
36 Solflare,
37 Backpack,
38}
39
40impl From<Wallet> for BaseWalletAdapter {
41 fn from(val: Wallet) -> Self {
42 match val {
43 Wallet::Phantom => BaseWalletAdapter::new(
44 Wallet::Phantom,
45 "https://phantom.app",
46 "images/phantom_logo.png",
47 ),
48 Wallet::Solflare => BaseWalletAdapter::new(
49 Wallet::Solflare,
50 "https://solflare.com",
51 "images/solflare_logo.png",
52 ),
53 Wallet::Backpack => BaseWalletAdapter::new(
54 Wallet::Backpack,
55 "https://backpack.app",
56 "images/backpack_logo.png",
57 ),
58 }
59 }
60}
61
62#[derive(Default, Clone, PartialEq, Serialize, Deserialize)]
63pub enum WalletReadyState {
64 Installed,
65 #[default]
66 NotDetected,
67 Loadable,
68 Unsupported,
69}
70
71#[derive(Default, Clone, PartialEq)]
72pub struct BaseWalletAdapter {
73 name: Wallet,
74 url: String,
75 icon: String,
76 ready_state: WalletReadyState,
77 public_key: Option<Pubkey>,
78 connecting: bool,
79 pub emitter: EventEmitter,
80}
81
82impl BaseWalletAdapter {
83 pub fn new(name: Wallet, url: &str, icon: &str) -> Self {
84 let ready_state = if cfg!(target_arch = "wasm32") {
85 WalletReadyState::Unsupported
86 } else {
87 WalletReadyState::NotDetected
88 };
89
90 BaseWalletAdapter {
91 ready_state,
92 name,
93 url: url.to_string(),
94 icon: icon.to_string(),
95 public_key: None,
96 connecting: false,
97 emitter: EventEmitter::new(),
98 }
99 }
100}
101
102impl WalletAdapter for BaseWalletAdapter {
103 fn name(&self) -> String {
104 format!("{:?}", self.name).to_string()
105 }
106
107 fn url(&self) -> String {
108 self.url.to_string()
109 }
110
111 fn icon(&self) -> String {
112 self.icon.to_string()
113 }
114
115 fn ready_state(&self) -> WalletReadyState {
116 self.ready_state.clone()
117 }
118
119 fn public_key(&self) -> Option<Pubkey> {
120 self.public_key
121 }
122
123 fn connecting(&self) -> bool {
124 self.connecting
125 }
126
127 async fn auto_connect(&mut self) -> Result<bool, WalletError> {
128 self.connect().await
129 }
130
131 async fn connect(&mut self) -> Result<bool, WalletError> {
132 info!("Connecting to wallet...");
133
134 if self.connecting {
135 self.emit_error(WalletError::WalletConnectionError);
136 return Err(WalletError::WalletConnectionError);
137 }
138
139 self.connecting = true;
140
141 let options = js_sys::Object::new();
142 js_sys::Reflect::set(
143 &options,
144 &serde_wasm_bindgen::to_value("onlyIfTrusted").unwrap(),
145 &serde_wasm_bindgen::to_value(&true).unwrap(),
146 )
147 .unwrap();
148
149 let promise: Option<Promise> = match self.name {
150 Wallet::Phantom if !SOLANA.is_undefined() => Some(SOLANA.sign_in(&options)),
151 Wallet::Solflare if !SOLFLARE.is_undefined() => Some(SOLFLARE.connect(&options)),
152 Wallet::Backpack if !XNFT.is_undefined() => Some(BACKPACK.sign_in(&options)),
153 Wallet::Phantom | Wallet::Solflare | Wallet::Backpack => None,
154 };
155
156 if promise.is_some() {
157 let result = JsFuture::from(promise.unwrap()).await;
158
159 match result {
160 Ok(_response) => {
161 info!("Wallet connected");
164
165 let key: JsValue = match self.name {
166 Wallet::Phantom => SOLANA.publicKey(),
167 Wallet::Solflare => SOLFLARE.publicKey(),
168 Wallet::Backpack => BACKPACK.publicKey(),
169 };
170
171 if key.is_undefined() {
172 info!("Public key is undefined");
173 } else {
174 let key_str: String = JsValue::into_serde(&key).unwrap();
175
176 let public_key = Pubkey::from_str(&key_str).unwrap();
177 info!("Connected to wallet with public key: {:?}", public_key);
178 self.public_key = Some(public_key);
179 self.ready_state = WalletReadyState::Installed;
180 self.emit_connect(public_key);
181
182 self.connecting = false;
183 }
184 }
185 Err(err) => {
186 log::error!("Failed to connect wallet: {:?}", err);
187 }
188 }
189 } else {
190 let window = window().expect("no global `window` exists");
191 window
192 .open_with_url(&self.url)
193 .expect("failed to open a new tab");
194 }
195
196 Ok(!self.connecting)
197 }
198
199 async fn disconnect(&mut self) -> Result<bool, WalletError> {
200 info!("Disconnecting from wallet...");
201 let mut confirmed = false;
202
203 if self.public_key.is_none() {
204 self.emit_error(WalletError::WalletDisconnectedError);
205 return Err(WalletError::WalletDisconnectedError);
206 }
207
208 self.public_key = None;
209 self.ready_state = WalletReadyState::NotDetected;
210 self.emit_disconnect();
211 let promise: Promise = match self.name {
212 Wallet::Phantom => SOLANA.disconnect(),
213 Wallet::Solflare => SOLFLARE.disconnect(),
214 Wallet::Backpack => BACKPACK.disconnect(),
215 };
216 let result = JsFuture::from(promise).await;
217
218 match result {
219 Ok(_) => {
220 confirmed = true;
221 info!("Disconnected from wallet");
222 }
223 Err(err) => {
224 log::error!("Failed to disconnect wallet: {:?}", err);
225 }
226 }
227
228 Ok(confirmed)
229 }
230
231 async fn send_transaction(
232 &mut self,
233 client: Arc<RpcClient>,
234 transaction: TransactionOrVersionedTransaction,
235 ) -> Result<Signature, WalletError> {
236 info!("Sending transaction...");
237
238 if self.public_key.is_none() {
239 self.emit_error(WalletError::WalletNotConnectedError);
240 return Err(WalletError::WalletNotConnectedError);
241 }
242 let signature = match transaction {
243 TransactionOrVersionedTransaction::Transaction(tx) => client
244 .send_and_confirm_transaction(&tx)
245 .await
246 .map_err(|_| WalletError::WalletSendTransactionError)?,
247 TransactionOrVersionedTransaction::VersionedTransaction(_vtx) => {
248 Signature::default()
254 }
255 };
256
257 self.emit_transaction_sent(signature);
258 info!("Transaction sent: {}", signature);
259 Ok(signature)
260 }
261
262 async fn sign_transaction(
263 &mut self,
264 transaction: Transaction,
265 ) -> Result<Signature, WalletError> {
266 info!("Signing transaction...");
267
268 if self.public_key.is_none() {
269 self.emit_error(WalletError::WalletNotConnectedError);
270 return Err(WalletError::WalletNotConnectedError);
271 }
272
273 let transaction_bytes = bincode::serialize(&transaction)
274 .map_err(|_| WalletError::WalletSignTransactionError)?;
275
276 let transaction_js_array = Uint8Array::from(&transaction_bytes[..]);
277
278 let bs58_tx = bs58::encode(transaction_bytes).into_string();
279
280 let options = js_sys::Object::new();
281 js_sys::Reflect::set(
282 &options,
283 &serde_wasm_bindgen::to_value("method").unwrap(),
284 &serde_wasm_bindgen::to_value("signTransaction").unwrap(),
285 )
286 .expect("Failed to set method in options");
287
288 let params = js_sys::Object::new();
289 js_sys::Reflect::set(
290 ¶ms,
291 &serde_wasm_bindgen::to_value("message").unwrap(),
292 &serde_wasm_bindgen::to_value(&bs58_tx).unwrap(),
293 )
294 .expect("Failed to set message in params");
295
296 js_sys::Reflect::set(
297 &options,
298 &serde_wasm_bindgen::to_value("params").unwrap(),
299 &JsValue::from(¶ms),
300 )
301 .expect("Failed to set params in options");
302
303 let promise: Promise = match self.name {
304 Wallet::Phantom => SOLANA.request(&options),
305 Wallet::Solflare => SOLFLARE.request(&options),
306 Wallet::Backpack => BACKPACK.sign_transaction(
307 &transaction_js_array,
308 &JsValue::from(self.public_key.unwrap()),
309 &JsValue::from(""),
310 &JsValue::from("uuid"),
311 ),
312 };
313
314 let result = JsFuture::from(promise).await;
315
316 match result {
317 Ok(json_str) => {
318 let deserialized: SignaturesObject = JsValue::into_serde(&json_str).unwrap();
319
320 let signature_map = &deserialized.signatures[0].signature;
321 let mut signature_bytes = [0u8; 64];
322
323 for (key, value) in signature_map.iter() {
324 let index: usize = key.parse().unwrap();
325 signature_bytes[index] = value.as_u64().unwrap() as u8;
326 }
327
328 let signature = Signature::new(&signature_bytes);
329 info!("Got signature: {:?}", signature);
330 Ok(signature)
331 }
332 Err(err) => {
333 log::error!("Failed to sign transaction: {:?}", err);
334 Err(WalletError::WalletSignTransactionError)
335 }
336 }
337 }
338
339 async fn sign_send_transaction(
340 &mut self,
341 transaction: Transaction,
342 ) -> Result<Signature, WalletError> {
343 info!("Signing and sending transaction...");
344
345 if self.public_key.is_none() {
346 self.emit_error(WalletError::WalletNotConnectedError);
347 return Err(WalletError::WalletNotConnectedError);
348 }
349
350 let transaction_bytes = bincode::serialize(&transaction)
351 .map_err(|_| WalletError::WalletSignTransactionError)?;
352
353 let transaction_js_array = Uint8Array::from(&transaction_bytes[..]);
354
355 let bs58_tx = bs58::encode(transaction_bytes).into_string();
356
357 let options = js_sys::Object::new();
358 js_sys::Reflect::set(
359 &options,
360 &serde_wasm_bindgen::to_value("method").unwrap(),
361 &serde_wasm_bindgen::to_value("signAndSendTransaction").unwrap(),
362 )
363 .expect("Failed to set method in options");
364
365 let params = js_sys::Object::new();
366 js_sys::Reflect::set(
367 ¶ms,
368 &serde_wasm_bindgen::to_value("message").unwrap(),
369 &serde_wasm_bindgen::to_value(&bs58_tx).unwrap(),
370 )
371 .expect("Failed to set message in params");
372
373 js_sys::Reflect::set(
374 &options,
375 &serde_wasm_bindgen::to_value("params").unwrap(),
376 &JsValue::from(¶ms),
377 )
378 .expect("Failed to set params in options");
379
380 let promise: Promise = match self.name {
381 Wallet::Phantom => SOLANA.request(&options),
382 Wallet::Solflare => SOLFLARE.request(&options),
383 Wallet::Backpack => BACKPACK.sign_and_send_transaction(&transaction_js_array, &options),
384 };
385
386 let result = JsFuture::from(promise).await;
387
388 match result {
389 Ok(json_str) => {
390 let deserialized: JsSignatureObject = JsValue::into_serde(&json_str).unwrap();
391
392 let signature = Signature::from_str(&deserialized.signature).unwrap();
393 info!("Got signature: {:?}", signature);
394
395 Ok(signature)
396 }
397 Err(err) => {
398 log::error!("Failed to sign transaction: {:?}", err);
399 Err(WalletError::WalletSignTransactionError)
400 }
401 }
402 }
403
404 async fn sign_message(&mut self, message: &str) -> Result<Signature, WalletError> {
405 info!("Signing transaction...");
406
407 if self.public_key.is_none() {
408 return Err(WalletError::WalletNotConnectedError);
409 }
410
411 let message_bytes =
412 bincode::serialize(&message).map_err(|_| WalletError::WalletSignTransactionError)?;
413
414 let message_js_array = Uint8Array::from(&message_bytes[..]);
415
416 let promise: Promise = match self.name {
417 Wallet::Phantom => SOLANA.sign_message(&message_js_array),
418 Wallet::Solflare => SOLFLARE.sign_message(&message_js_array),
419 Wallet::Backpack => BACKPACK.sign_message(
420 &message_js_array,
421 &JsValue::from(self.public_key.unwrap()),
422 &JsValue::from("uuid"),
423 ),
424 };
425
426 let result = JsFuture::from(promise).await;
427
428 match result {
429 Ok(json_str) => {
430 let sig_obj: JsSignatureResponse = JsValue::into_serde(&json_str).unwrap();
431 let data = sig_obj.signature.data;
432
433 let signature = Signature::new(&data);
434 info!("Message signed: {:?}", signature);
435
436 Ok(signature)
437 }
438 Err(err) => {
439 log::error!("Failed to sign transaction: {:?}", err);
440 Err(WalletError::WalletSignTransactionError)
441 }
442 }
443 }
444}
445
446impl WalletAdapterEvents for BaseWalletAdapter {
447 fn emit_connect(&mut self, public_key: Pubkey) {
448 self.emitter.emit("connect", public_key);
449 }
450
451 fn emit_disconnect(&mut self) {
452 self.emitter.emit("disconnect", ());
453 }
454
455 fn emit_error(&mut self, error: WalletError) {
456 self.emitter.emit("error", error);
457 }
458
459 fn ready_state_change(&mut self, ready_state: WalletReadyState) {
460 self.emitter.emit("ready_state_change", ready_state);
461 }
462
463 fn emit_transaction_sent(&mut self, signature: Signature) {
464 self.emitter.emit("transaction_sent", signature);
465 }
466}