1pub mod error;
2pub mod server;
3pub mod signer;
4pub mod state;
5
6mod app;
7mod handlers;
8mod queue;
9mod router;
10mod types;
11
12#[cfg(test)]
13mod tests {
14 use std::time::Duration;
15
16 use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256, address};
17 use alloy_rpc_types::TransactionRequest;
18 use axum::http::{HeaderMap, HeaderValue};
19 use tokio::task::JoinHandle;
20 use uuid::Uuid;
21
22 use crate::wallet_browser::{
23 error::BrowserWalletError,
24 server::BrowserWalletServer,
25 types::{
26 BrowserApiResponse, BrowserSignRequest, BrowserSignResponse, BrowserTransactionRequest,
27 BrowserTransactionResponse, Connection, SignRequest, SignType,
28 },
29 };
30
31 const ALICE: Address = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
32 const BOB: Address = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
33
34 const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
35 const DEFAULT_DEVELOPMENT: bool = false;
36
37 #[tokio::test]
38 async fn test_setup_server() {
39 let mut server = create_server();
40 let client = client_with_token(&server);
41
42 assert!(!server.is_connected().await);
44 assert!(!server.open_browser());
45 assert!(server.timeout() == DEFAULT_TIMEOUT);
46
47 server.start().await.unwrap();
49
50 check_transaction_request_queue_empty(&client, &server).await;
52
53 server.stop().await.unwrap();
55 }
56
57 #[tokio::test]
58 async fn test_connect_disconnect_wallet() {
59 let mut server = create_server();
60 let client = client_with_token(&server);
61 server.start().await.unwrap();
62
63 check_transaction_request_queue_empty(&client, &server).await;
65
66 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
68
69 let Connection { address, chain_id } =
71 server.get_connection().await.expect("expected an active wallet connection");
72 assert_eq!(address, ALICE);
73 assert_eq!(chain_id, 1);
74
75 disconnect_wallet(&client, &server).await;
77
78 assert!(!server.is_connected().await);
80
81 connect_wallet(&client, &server, Connection::new(BOB, 42)).await;
83
84 let Connection { address, chain_id } =
86 server.get_connection().await.expect("expected an active wallet connection");
87 assert_eq!(address, BOB);
88 assert_eq!(chain_id, 42);
89
90 server.stop().await.unwrap();
92 }
93
94 #[tokio::test]
95 async fn test_switch_wallet() {
96 let mut server = create_server();
97 let client = client_with_token(&server);
98 server.start().await.unwrap();
99
100 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
102 let Connection { address, chain_id } =
103 server.get_connection().await.expect("expected an active wallet connection");
104 assert_eq!(address, ALICE);
105 assert_eq!(chain_id, 1);
106
107 connect_wallet(&client, &server, Connection::new(BOB, 42)).await;
109 let Connection { address, chain_id } =
110 server.get_connection().await.expect("expected an active wallet connection");
111 assert_eq!(address, BOB);
112 assert_eq!(chain_id, 42);
113
114 server.stop().await.unwrap();
115 }
116
117 #[tokio::test]
118 async fn test_transaction_response_both_hash_and_error_rejected() {
119 let mut server = create_server();
120 let client = client_with_token(&server);
121 server.start().await.unwrap();
122 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
123
124 let (tx_request_id, tx_request) = create_browser_transaction_request();
126 let _handle = wait_for_transaction_signing(&server, tx_request).await;
127 check_transaction_request_content(&client, &server, tx_request_id).await;
128
129 let resp = client
131 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
132 .json(&BrowserTransactionResponse {
133 id: tx_request_id,
134 hash: Some(TxHash::random()),
135 error: Some("should not have both".into()),
136 })
137 .send()
138 .await
139 .unwrap()
140 .error_for_status()
141 .unwrap();
142
143 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
144 match api {
145 BrowserApiResponse::Error { message } => {
146 assert_eq!(message, "Only one of hash or error can be provided");
147 }
148 _ => panic!("expected error response"),
149 }
150 }
151
152 #[tokio::test]
153 async fn test_transaction_response_neither_hash_nor_error_rejected() {
154 let mut server = create_server();
155 let client = client_with_token(&server);
156 server.start().await.unwrap();
157 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
158
159 let (tx_request_id, tx_request) = create_browser_transaction_request();
160 let _handle = wait_for_transaction_signing(&server, tx_request).await;
161 check_transaction_request_content(&client, &server, tx_request_id).await;
162
163 let resp = client
165 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
166 .json(&BrowserTransactionResponse { id: tx_request_id, hash: None, error: None })
167 .send()
168 .await
169 .unwrap()
170 .error_for_status()
171 .unwrap();
172
173 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
174 match api {
175 BrowserApiResponse::Error { message } => {
176 assert_eq!(message, "Either hash or error must be provided");
177 }
178 _ => panic!("expected error response"),
179 }
180 }
181
182 #[tokio::test]
183 async fn test_transaction_response_zero_hash_rejected() {
184 let mut server = create_server();
185 let client = client_with_token(&server);
186 server.start().await.unwrap();
187 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
188
189 let (tx_request_id, tx_request) = create_browser_transaction_request();
190 let _handle = wait_for_transaction_signing(&server, tx_request).await;
191 check_transaction_request_content(&client, &server, tx_request_id).await;
192
193 let zero = TxHash::new([0u8; 32]);
195 let resp = client
196 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
197 .json(&BrowserTransactionResponse { id: tx_request_id, hash: Some(zero), error: None })
198 .send()
199 .await
200 .unwrap()
201 .error_for_status()
202 .unwrap();
203
204 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
205 match api {
206 BrowserApiResponse::Error { message } => {
207 assert!(
209 message.contains("Invalid") || message.contains("Malformed"),
210 "unexpected message: {message}"
211 );
212 }
213 _ => panic!("expected error response"),
214 }
215 }
216
217 #[tokio::test]
218 async fn test_send_transaction_client_accept() {
219 let mut server = create_server();
220 let client = client_with_token(&server);
221 server.start().await.unwrap();
222 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
223
224 let (tx_request_id, tx_request) = create_browser_transaction_request();
225 let handle = wait_for_transaction_signing(&server, tx_request).await;
226 check_transaction_request_content(&client, &server, tx_request_id).await;
227
228 let resp = client
230 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
231 .json(&BrowserTransactionResponse {
232 id: tx_request_id,
233 hash: Some(TxHash::random()),
234 error: None,
235 })
236 .send()
237 .await
238 .unwrap()
239 .error_for_status()
240 .unwrap();
241 assert_eq!(resp.status(), reqwest::StatusCode::OK);
242
243 let res = handle.await.expect("task panicked");
245 match res {
246 Ok(hash) => {
247 assert!(hash != TxHash::new([0; 32]));
248 }
249 other => panic!("expected success, got {other:?}"),
250 }
251 }
252
253 #[tokio::test]
254 async fn test_send_transaction_client_not_requested() {
255 let mut server = create_server();
256 let client = client_with_token(&server);
257 server.start().await.unwrap();
258 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
259
260 let tx_request_id = Uuid::new_v4();
262
263 let resp = client
265 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
266 .json(&BrowserTransactionResponse {
267 id: tx_request_id,
268 hash: Some(TxHash::random()),
269 error: None,
270 })
271 .send()
272 .await
273 .unwrap()
274 .error_for_status()
275 .unwrap();
276
277 assert_eq!(resp.status(), reqwest::StatusCode::OK);
278
279 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
281 match api {
282 BrowserApiResponse::Error { message } => {
283 assert_eq!(message, "Unknown transaction id");
284 }
285 _ => panic!("expected error response"),
286 }
287 }
288
289 #[tokio::test]
290 async fn test_send_transaction_invalid_response_format() {
291 let mut server = create_server();
292 let client = client_with_token(&server);
293 server.start().await.unwrap();
294 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
295
296 let resp = client
298 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
299 .body(
300 r#"{
301 "id": "invalid-uuid",
302 "hash": "invalid-hash",
303 "error": null
304 }"#,
305 )
306 .header("Content-Type", "application/json")
307 .send()
308 .await
309 .unwrap();
310
311 assert_eq!(resp.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY);
313 }
314
315 #[tokio::test]
316 async fn test_send_transaction_client_reject() {
317 let mut server = create_server();
318 let client = client_with_token(&server);
319 server.start().await.unwrap();
320 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
321
322 let (tx_request_id, tx_request) = create_browser_transaction_request();
324
325 let handle = wait_for_transaction_signing(&server, tx_request).await;
327
328 check_transaction_request_content(&client, &server, tx_request_id).await;
330
331 let resp = client
333 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
334 .json(&BrowserTransactionResponse {
335 id: tx_request_id,
336 hash: None,
337 error: Some("User rejected the transaction".into()),
338 })
339 .send()
340 .await
341 .unwrap()
342 .error_for_status()
343 .unwrap();
344 assert_eq!(resp.status(), reqwest::StatusCode::OK);
345
346 let res = handle.await.expect("task panicked");
348 match res {
349 Err(BrowserWalletError::Rejected { operation, reason }) => {
350 assert_eq!(operation, "Transaction");
351 assert_eq!(reason, "User rejected the transaction");
352 }
353 other => panic!("expected rejection, got {other:?}"),
354 }
355 }
356
357 #[tokio::test]
358 async fn test_send_multiple_transaction_requests() {
359 let mut server = create_server();
360 let client = client_with_token(&server);
361 server.start().await.unwrap();
362 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
363
364 let (tx_request_id1, tx_request1) = create_browser_transaction_request();
366 let (tx_request_id2, tx_request2) = create_different_browser_transaction_request();
367
368 let handle1 = wait_for_transaction_signing(&server, tx_request1.clone()).await;
370 let handle2 = wait_for_transaction_signing(&server, tx_request2.clone()).await;
371
372 {
374 let resp = client
375 .get(format!("http://localhost:{}/api/transaction/request", server.port()))
376 .send()
377 .await
378 .unwrap();
379
380 let BrowserApiResponse::Ok(pending_tx) =
381 resp.json::<BrowserApiResponse<BrowserTransactionRequest>>().await.unwrap()
382 else {
383 panic!("expected BrowserApiResponse::Ok with a pending transaction");
384 };
385
386 assert_eq!(
387 pending_tx.id, tx_request_id1,
388 "expected the first transaction to be at the front of the queue"
389 );
390 assert_eq!(pending_tx.request.from, tx_request1.request.from);
391 assert_eq!(pending_tx.request.to, tx_request1.request.to);
392 assert_eq!(pending_tx.request.value, tx_request1.request.value);
393 }
394
395 let resp1 = client
397 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
398 .json(&BrowserTransactionResponse {
399 id: tx_request_id1,
400 hash: Some(TxHash::random()),
401 error: None,
402 })
403 .send()
404 .await
405 .unwrap()
406 .error_for_status()
407 .unwrap();
408 assert_eq!(resp1.status(), reqwest::StatusCode::OK);
409
410 let res1 = handle1.await.expect("first signing flow panicked");
411 match res1 {
412 Ok(hash) => assert!(!hash.is_zero(), "first tx hash should not be zero"),
413 other => panic!("expected success, got {other:?}"),
414 }
415
416 {
418 let resp = client
419 .get(format!("http://localhost:{}/api/transaction/request", server.port()))
420 .send()
421 .await
422 .unwrap();
423
424 let BrowserApiResponse::Ok(pending_tx) =
425 resp.json::<BrowserApiResponse<BrowserTransactionRequest>>().await.unwrap()
426 else {
427 panic!("expected BrowserApiResponse::Ok with a pending transaction");
428 };
429
430 assert_eq!(
431 pending_tx.id, tx_request_id2,
432 "expected the second transaction to be pending after the first one completed"
433 );
434 assert_eq!(pending_tx.request.from, tx_request2.request.from);
435 assert_eq!(pending_tx.request.to, tx_request2.request.to);
436 assert_eq!(pending_tx.request.value, tx_request2.request.value);
437 }
438
439 let resp2 = client
441 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
442 .json(&BrowserTransactionResponse {
443 id: tx_request_id2,
444 hash: None,
445 error: Some("User rejected the transaction".into()),
446 })
447 .send()
448 .await
449 .unwrap()
450 .error_for_status()
451 .unwrap();
452 assert_eq!(resp2.status(), reqwest::StatusCode::OK);
453
454 let res2 = handle2.await.expect("second signing flow panicked");
455 match res2 {
456 Err(BrowserWalletError::Rejected { operation, reason }) => {
457 assert_eq!(operation, "Transaction");
458 assert_eq!(reason, "User rejected the transaction");
459 }
460 other => panic!("expected BrowserWalletError::Rejected, got {other:?}"),
461 }
462
463 check_transaction_request_queue_empty(&client, &server).await;
464
465 server.stop().await.unwrap();
466 }
467
468 #[tokio::test]
469 async fn test_send_sign_response_both_signature_and_error_rejected() {
470 let mut server = create_server();
471 let client = client_with_token(&server);
472 server.start().await.unwrap();
473 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
474
475 let (sign_request_id, sign_request) = create_browser_sign_request();
476 let _handle = wait_for_message_signing(&server, sign_request).await;
477 check_sign_request_content(&client, &server, sign_request_id).await;
478
479 let resp = client
481 .post(format!("http://localhost:{}/api/signing/response", server.port()))
482 .json(&BrowserSignResponse {
483 id: sign_request_id,
484 signature: Some(Bytes::from("Hello World")),
485 error: Some("Should not have both".into()),
486 })
487 .send()
488 .await
489 .unwrap()
490 .error_for_status()
491 .unwrap();
492
493 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
494 match api {
495 BrowserApiResponse::Error { message } => {
496 assert_eq!(message, "Only one of signature or error can be provided");
497 }
498 _ => panic!("expected error response"),
499 }
500 }
501
502 #[tokio::test]
503 async fn test_send_sign_response_neither_hash_nor_error_rejected() {
504 let mut server = create_server();
505 let client = client_with_token(&server);
506 server.start().await.unwrap();
507 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
508
509 let (sign_request_id, sign_request) = create_browser_sign_request();
510 let _handle = wait_for_message_signing(&server, sign_request).await;
511 check_sign_request_content(&client, &server, sign_request_id).await;
512
513 let resp = client
515 .post(format!("http://localhost:{}/api/signing/response", server.port()))
516 .json(&BrowserSignResponse { id: sign_request_id, signature: None, error: None })
517 .send()
518 .await
519 .unwrap()
520 .error_for_status()
521 .unwrap();
522
523 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
524 match api {
525 BrowserApiResponse::Error { message } => {
526 assert_eq!(message, "Either signature or error must be provided");
527 }
528 _ => panic!("expected error response"),
529 }
530 }
531
532 #[tokio::test]
533 async fn test_send_sign_client_accept() {
534 let mut server = create_server();
535 let client = client_with_token(&server);
536 server.start().await.unwrap();
537 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
538
539 let (sign_request_id, sign_request) = create_browser_sign_request();
540 let handle = wait_for_message_signing(&server, sign_request).await;
541 check_sign_request_content(&client, &server, sign_request_id).await;
542
543 let resp = client
545 .post(format!("http://localhost:{}/api/signing/response", server.port()))
546 .json(&BrowserSignResponse {
547 id: sign_request_id,
548 signature: Some(Bytes::from("FakeSignature")),
549 error: None,
550 })
551 .send()
552 .await
553 .unwrap()
554 .error_for_status()
555 .unwrap();
556 assert_eq!(resp.status(), reqwest::StatusCode::OK);
557
558 let res = handle.await.expect("task panicked");
560 match res {
561 Ok(signature) => {
562 assert_eq!(signature, Bytes::from("FakeSignature"));
563 }
564 other => panic!("expected success, got {other:?}"),
565 }
566 }
567
568 #[tokio::test]
569 async fn test_send_sign_client_not_requested() {
570 let mut server = create_server();
571 let client = client_with_token(&server);
572 server.start().await.unwrap();
573 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
574
575 let sign_request_id = Uuid::new_v4();
577
578 let resp = client
580 .post(format!("http://localhost:{}/api/signing/response", server.port()))
581 .json(&BrowserSignResponse {
582 id: sign_request_id,
583 signature: Some(Bytes::from("FakeSignature")),
584 error: None,
585 })
586 .send()
587 .await
588 .unwrap()
589 .error_for_status()
590 .unwrap();
591
592 assert_eq!(resp.status(), reqwest::StatusCode::OK);
593
594 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
596 match api {
597 BrowserApiResponse::Error { message } => {
598 assert_eq!(message, "Unknown signing request id");
599 }
600 _ => panic!("expected error response"),
601 }
602 }
603
604 #[tokio::test]
605 async fn test_send_sign_invalid_response_format() {
606 let mut server = create_server();
607 let client = client_with_token(&server);
608 server.start().await.unwrap();
609 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
610
611 let resp = client
613 .post(format!("http://localhost:{}/api/signing/response", server.port()))
614 .body(
615 r#"{
616 "id": "invalid-uuid",
617 "signature": "invalid-signature",
618 "error": null
619 }"#,
620 )
621 .header("Content-Type", "application/json")
622 .send()
623 .await
624 .unwrap();
625
626 assert_eq!(resp.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY);
628 }
629
630 #[tokio::test]
631 async fn test_send_sign_client_reject() {
632 let mut server = create_server();
633 let client = client_with_token(&server);
634 server.start().await.unwrap();
635 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
636
637 let (sign_request_id, sign_request) = create_browser_sign_request();
638 let handle = wait_for_message_signing(&server, sign_request).await;
639 check_sign_request_content(&client, &server, sign_request_id).await;
640
641 let resp = client
643 .post(format!("http://localhost:{}/api/signing/response", server.port()))
644 .json(&BrowserSignResponse {
645 id: sign_request_id,
646 signature: None,
647 error: Some("User rejected the signing request".into()),
648 })
649 .send()
650 .await
651 .unwrap()
652 .error_for_status()
653 .unwrap();
654 assert_eq!(resp.status(), reqwest::StatusCode::OK);
655
656 let res = handle.await.expect("task panicked");
658 match res {
659 Err(BrowserWalletError::Rejected { operation, reason }) => {
660 assert_eq!(operation, "Signing");
661 assert_eq!(reason, "User rejected the signing request");
662 }
663 other => panic!("expected rejection, got {other:?}"),
664 }
665 }
666
667 #[tokio::test]
668 async fn test_send_multiple_sign_requests() {
669 let mut server = create_server();
670 let client = client_with_token(&server);
671 server.start().await.unwrap();
672 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
673
674 let (sign_request_id1, sign_request1) = create_browser_sign_request();
676 let (sign_request_id2, sign_request2) = create_different_browser_sign_request();
677
678 let handle1 = wait_for_message_signing(&server, sign_request1.clone()).await;
680 let handle2 = wait_for_message_signing(&server, sign_request2.clone()).await;
681
682 {
684 let resp = client
685 .get(format!("http://localhost:{}/api/signing/request", server.port()))
686 .send()
687 .await
688 .unwrap();
689
690 let BrowserApiResponse::Ok(pending_sign) =
691 resp.json::<BrowserApiResponse<BrowserSignRequest>>().await.unwrap()
692 else {
693 panic!("expected BrowserApiResponse::Ok with a pending sign request");
694 };
695
696 assert_eq!(pending_sign.id, sign_request_id1);
697 assert_eq!(pending_sign.sign_type, sign_request1.sign_type);
698 assert_eq!(pending_sign.request.address, sign_request1.request.address);
699 assert_eq!(pending_sign.request.message, sign_request1.request.message);
700 }
701
702 let resp1 = client
704 .post(format!("http://localhost:{}/api/signing/response", server.port()))
705 .json(&BrowserSignResponse {
706 id: sign_request_id1,
707 signature: Some(Bytes::from("Signature1")),
708 error: None,
709 })
710 .send()
711 .await
712 .unwrap()
713 .error_for_status()
714 .unwrap();
715 assert_eq!(resp1.status(), reqwest::StatusCode::OK);
716
717 let res1 = handle1.await.expect("first signing flow panicked");
718 match res1 {
719 Ok(signature) => assert_eq!(signature, Bytes::from("Signature1")),
720 other => panic!("expected success, got {other:?}"),
721 }
722
723 {
725 let resp = client
726 .get(format!("http://localhost:{}/api/signing/request", server.port()))
727 .send()
728 .await
729 .unwrap();
730
731 let BrowserApiResponse::Ok(pending_sign) =
732 resp.json::<BrowserApiResponse<BrowserSignRequest>>().await.unwrap()
733 else {
734 panic!("expected BrowserApiResponse::Ok with a pending sign request");
735 };
736
737 assert_eq!(pending_sign.id, sign_request_id2,);
738 assert_eq!(pending_sign.sign_type, sign_request2.sign_type);
739 assert_eq!(pending_sign.request.address, sign_request2.request.address);
740 assert_eq!(pending_sign.request.message, sign_request2.request.message);
741 }
742
743 let resp2 = client
745 .post(format!("http://localhost:{}/api/signing/response", server.port()))
746 .json(&BrowserSignResponse {
747 id: sign_request_id2,
748 signature: None,
749 error: Some("User rejected the signing request".into()),
750 })
751 .send()
752 .await
753 .unwrap()
754 .error_for_status()
755 .unwrap();
756 assert_eq!(resp2.status(), reqwest::StatusCode::OK);
757
758 let res2 = handle2.await.expect("second signing flow panicked");
759 match res2 {
760 Err(BrowserWalletError::Rejected { operation, reason }) => {
761 assert_eq!(operation, "Signing");
762 assert_eq!(reason, "User rejected the signing request");
763 }
764 other => panic!("expected BrowserWalletError::Rejected, got {other:?}"),
765 }
766
767 check_sign_request_queue_empty(&client, &server).await;
768
769 server.stop().await.unwrap();
770 }
771
772 fn create_server() -> BrowserWalletServer {
774 BrowserWalletServer::new(0, false, DEFAULT_TIMEOUT, DEFAULT_DEVELOPMENT)
775 }
776
777 fn client_with_token(server: &BrowserWalletServer) -> reqwest::Client {
779 let mut headers = HeaderMap::new();
780 headers.insert("X-Session-Token", HeaderValue::from_str(server.session_token()).unwrap());
781 reqwest::Client::builder().default_headers(headers).build().unwrap()
782 }
783
784 async fn connect_wallet(
786 client: &reqwest::Client,
787 server: &BrowserWalletServer,
788 connection: Connection,
789 ) {
790 let resp = client
791 .post(format!("http://localhost:{}/api/connection", server.port()))
792 .json(&connection)
793 .send();
794 assert!(resp.await.is_ok());
795 }
796
797 async fn disconnect_wallet(client: &reqwest::Client, server: &BrowserWalletServer) {
799 let resp = client
800 .post(format!("http://localhost:{}/api/connection", server.port()))
801 .json(&Option::<Connection>::None)
802 .send();
803 assert!(resp.await.is_ok());
804 }
805
806 async fn wait_for_transaction_signing(
808 server: &BrowserWalletServer,
809 tx_request: BrowserTransactionRequest,
810 ) -> JoinHandle<Result<TxHash, BrowserWalletError>> {
811 let browser_server = server.clone();
813 let join_handle =
814 tokio::spawn(async move { browser_server.request_transaction(tx_request).await });
815 tokio::task::yield_now().await;
816 tokio::time::sleep(Duration::from_millis(100)).await;
817
818 join_handle
819 }
820
821 async fn wait_for_message_signing(
823 server: &BrowserWalletServer,
824 sign_request: BrowserSignRequest,
825 ) -> JoinHandle<Result<Bytes, BrowserWalletError>> {
826 let browser_server = server.clone();
828 let join_handle =
829 tokio::spawn(async move { browser_server.request_signing(sign_request).await });
830 tokio::task::yield_now().await;
831 tokio::time::sleep(Duration::from_millis(100)).await;
832
833 join_handle
834 }
835
836 fn create_browser_transaction_request() -> (Uuid, BrowserTransactionRequest) {
838 let id = Uuid::new_v4();
839 let tx = BrowserTransactionRequest {
840 id,
841 request: TransactionRequest {
842 from: Some(ALICE),
843 to: Some(TxKind::Call(BOB)),
844 value: Some(U256::from(1000)),
845 ..Default::default()
846 },
847 };
848 (id, tx)
849 }
850
851 fn create_different_browser_transaction_request() -> (Uuid, BrowserTransactionRequest) {
853 let id = Uuid::new_v4();
854 let tx = BrowserTransactionRequest {
855 id,
856 request: TransactionRequest {
857 from: Some(BOB),
858 to: Some(TxKind::Call(ALICE)),
859 value: Some(U256::from(2000)),
860 ..Default::default()
861 },
862 };
863 (id, tx)
864 }
865
866 fn create_browser_sign_request() -> (Uuid, BrowserSignRequest) {
868 let id = Uuid::new_v4();
869 let req = BrowserSignRequest {
870 id,
871 sign_type: SignType::PersonalSign,
872 request: SignRequest { message: "Hello, world!".into(), address: ALICE },
873 };
874 (id, req)
875 }
876
877 fn create_different_browser_sign_request() -> (Uuid, BrowserSignRequest) {
879 let id = Uuid::new_v4();
880 let req = BrowserSignRequest {
881 id,
882 sign_type: SignType::SignTypedDataV4,
883 request: SignRequest { message: "Different message".into(), address: BOB },
884 };
885 (id, req)
886 }
887
888 async fn check_transaction_request_queue_empty(
890 client: &reqwest::Client,
891 server: &BrowserWalletServer,
892 ) {
893 let resp = client
894 .get(format!("http://localhost:{}/api/transaction/request", server.port()))
895 .send()
896 .await
897 .unwrap();
898
899 let BrowserApiResponse::Error { message } =
900 resp.json::<BrowserApiResponse<BrowserTransactionRequest>>().await.unwrap()
901 else {
902 panic!("expected BrowserApiResponse::Error (no pending transaction), but got Ok");
903 };
904
905 assert_eq!(message, "No pending transaction request");
906 }
907
908 async fn check_transaction_request_content(
910 client: &reqwest::Client,
911 server: &BrowserWalletServer,
912 tx_request_id: Uuid,
913 ) {
914 let resp = client
915 .get(format!("http://localhost:{}/api/transaction/request", server.port()))
916 .send()
917 .await
918 .unwrap();
919
920 let BrowserApiResponse::Ok(pending_tx) =
921 resp.json::<BrowserApiResponse<BrowserTransactionRequest>>().await.unwrap()
922 else {
923 panic!("expected BrowserApiResponse::Ok with a pending transaction");
924 };
925
926 assert_eq!(pending_tx.id, tx_request_id);
927 assert_eq!(pending_tx.request.from, Some(ALICE));
928 assert_eq!(pending_tx.request.to, Some(TxKind::Call(BOB)));
929 assert_eq!(pending_tx.request.value, Some(U256::from(1000)));
930 }
931
932 async fn check_sign_request_queue_empty(
934 client: &reqwest::Client,
935 server: &BrowserWalletServer,
936 ) {
937 let resp = client
938 .get(format!("http://localhost:{}/api/signing/request", server.port()))
939 .send()
940 .await
941 .unwrap();
942
943 let BrowserApiResponse::Error { message } =
944 resp.json::<BrowserApiResponse<BrowserSignRequest>>().await.unwrap()
945 else {
946 panic!("expected BrowserApiResponse::Error (no pending signing request), but got Ok");
947 };
948
949 assert_eq!(message, "No pending signing request");
950 }
951
952 async fn check_sign_request_content(
954 client: &reqwest::Client,
955 server: &BrowserWalletServer,
956 sign_request_id: Uuid,
957 ) {
958 let resp = client
959 .get(format!("http://localhost:{}/api/signing/request", server.port()))
960 .send()
961 .await
962 .unwrap();
963
964 let BrowserApiResponse::Ok(pending_req) =
965 resp.json::<BrowserApiResponse<BrowserSignRequest>>().await.unwrap()
966 else {
967 panic!("expected BrowserApiResponse::Ok with a pending signing request");
968 };
969
970 assert_eq!(pending_req.id, sign_request_id);
971 assert_eq!(pending_req.sign_type, SignType::PersonalSign);
972 assert_eq!(pending_req.request.address, ALICE);
973 assert_eq!(pending_req.request.message, "Hello, world!");
974 }
975}