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_network::{Ethereum, Network, TransactionBuilder};
17 use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256, address};
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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::<Ethereum>();
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) = resp
381 .json::<BrowserApiResponse<BrowserTransactionRequest<Ethereum>>>()
382 .await
383 .unwrap()
384 else {
385 panic!("expected BrowserApiResponse::Ok with a pending transaction");
386 };
387
388 assert_eq!(
389 pending_tx.id, tx_request_id1,
390 "expected the first transaction to be at the front of the queue"
391 );
392 assert_eq!(pending_tx.request.from, tx_request1.request.from);
393 assert_eq!(pending_tx.request.to, tx_request1.request.to);
394 assert_eq!(pending_tx.request.value, tx_request1.request.value);
395 }
396
397 let resp1 = client
399 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
400 .json(&BrowserTransactionResponse {
401 id: tx_request_id1,
402 hash: Some(TxHash::random()),
403 error: None,
404 })
405 .send()
406 .await
407 .unwrap()
408 .error_for_status()
409 .unwrap();
410 assert_eq!(resp1.status(), reqwest::StatusCode::OK);
411
412 let res1 = handle1.await.expect("first signing flow panicked");
413 match res1 {
414 Ok(hash) => assert!(!hash.is_zero(), "first tx hash should not be zero"),
415 other => panic!("expected success, got {other:?}"),
416 }
417
418 {
420 let resp = client
421 .get(format!("http://localhost:{}/api/transaction/request", server.port()))
422 .send()
423 .await
424 .unwrap();
425
426 let BrowserApiResponse::Ok(pending_tx) = resp
427 .json::<BrowserApiResponse<BrowserTransactionRequest<Ethereum>>>()
428 .await
429 .unwrap()
430 else {
431 panic!("expected BrowserApiResponse::Ok with a pending transaction");
432 };
433
434 assert_eq!(
435 pending_tx.id, tx_request_id2,
436 "expected the second transaction to be pending after the first one completed"
437 );
438 assert_eq!(pending_tx.request.from, tx_request2.request.from);
439 assert_eq!(pending_tx.request.to, tx_request2.request.to);
440 assert_eq!(pending_tx.request.value, tx_request2.request.value);
441 }
442
443 let resp2 = client
445 .post(format!("http://localhost:{}/api/transaction/response", server.port()))
446 .json(&BrowserTransactionResponse {
447 id: tx_request_id2,
448 hash: None,
449 error: Some("User rejected the transaction".into()),
450 })
451 .send()
452 .await
453 .unwrap()
454 .error_for_status()
455 .unwrap();
456 assert_eq!(resp2.status(), reqwest::StatusCode::OK);
457
458 let res2 = handle2.await.expect("second signing flow panicked");
459 match res2 {
460 Err(BrowserWalletError::Rejected { operation, reason }) => {
461 assert_eq!(operation, "Transaction");
462 assert_eq!(reason, "User rejected the transaction");
463 }
464 other => panic!("expected BrowserWalletError::Rejected, got {other:?}"),
465 }
466
467 check_transaction_request_queue_empty(&client, &server).await;
468
469 server.stop().await.unwrap();
470 }
471
472 #[tokio::test]
473 async fn test_send_sign_response_both_signature_and_error_rejected() {
474 let mut server = create_server::<Ethereum>();
475 let client = client_with_token(&server);
476 server.start().await.unwrap();
477 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
478
479 let (sign_request_id, sign_request) = create_browser_sign_request();
480 let _handle = wait_for_message_signing(&server, sign_request).await;
481 check_sign_request_content(&client, &server, sign_request_id).await;
482
483 let resp = client
485 .post(format!("http://localhost:{}/api/signing/response", server.port()))
486 .json(&BrowserSignResponse {
487 id: sign_request_id,
488 signature: Some(Bytes::from("Hello World")),
489 error: Some("Should not have both".into()),
490 })
491 .send()
492 .await
493 .unwrap()
494 .error_for_status()
495 .unwrap();
496
497 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
498 match api {
499 BrowserApiResponse::Error { message } => {
500 assert_eq!(message, "Only one of signature or error can be provided");
501 }
502 _ => panic!("expected error response"),
503 }
504 }
505
506 #[tokio::test]
507 async fn test_send_sign_response_neither_hash_nor_error_rejected() {
508 let mut server = create_server::<Ethereum>();
509 let client = client_with_token(&server);
510 server.start().await.unwrap();
511 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
512
513 let (sign_request_id, sign_request) = create_browser_sign_request();
514 let _handle = wait_for_message_signing(&server, sign_request).await;
515 check_sign_request_content(&client, &server, sign_request_id).await;
516
517 let resp = client
519 .post(format!("http://localhost:{}/api/signing/response", server.port()))
520 .json(&BrowserSignResponse { id: sign_request_id, signature: None, error: None })
521 .send()
522 .await
523 .unwrap()
524 .error_for_status()
525 .unwrap();
526
527 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
528 match api {
529 BrowserApiResponse::Error { message } => {
530 assert_eq!(message, "Either signature or error must be provided");
531 }
532 _ => panic!("expected error response"),
533 }
534 }
535
536 #[tokio::test]
537 async fn test_send_sign_client_accept() {
538 let mut server = create_server::<Ethereum>();
539 let client = client_with_token(&server);
540 server.start().await.unwrap();
541 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
542
543 let (sign_request_id, sign_request) = create_browser_sign_request();
544 let handle = wait_for_message_signing(&server, sign_request).await;
545 check_sign_request_content(&client, &server, sign_request_id).await;
546
547 let resp = client
549 .post(format!("http://localhost:{}/api/signing/response", server.port()))
550 .json(&BrowserSignResponse {
551 id: sign_request_id,
552 signature: Some(Bytes::from("FakeSignature")),
553 error: None,
554 })
555 .send()
556 .await
557 .unwrap()
558 .error_for_status()
559 .unwrap();
560 assert_eq!(resp.status(), reqwest::StatusCode::OK);
561
562 let res = handle.await.expect("task panicked");
564 match res {
565 Ok(signature) => {
566 assert_eq!(signature, Bytes::from("FakeSignature"));
567 }
568 other => panic!("expected success, got {other:?}"),
569 }
570 }
571
572 #[tokio::test]
573 async fn test_send_sign_client_not_requested() {
574 let mut server = create_server::<Ethereum>();
575 let client = client_with_token(&server);
576 server.start().await.unwrap();
577 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
578
579 let sign_request_id = Uuid::new_v4();
581
582 let resp = client
584 .post(format!("http://localhost:{}/api/signing/response", server.port()))
585 .json(&BrowserSignResponse {
586 id: sign_request_id,
587 signature: Some(Bytes::from("FakeSignature")),
588 error: None,
589 })
590 .send()
591 .await
592 .unwrap()
593 .error_for_status()
594 .unwrap();
595
596 assert_eq!(resp.status(), reqwest::StatusCode::OK);
597
598 let api: BrowserApiResponse<()> = resp.json().await.unwrap();
600 match api {
601 BrowserApiResponse::Error { message } => {
602 assert_eq!(message, "Unknown signing request id");
603 }
604 _ => panic!("expected error response"),
605 }
606 }
607
608 #[tokio::test]
609 async fn test_send_sign_invalid_response_format() {
610 let mut server = create_server::<Ethereum>();
611 let client = client_with_token(&server);
612 server.start().await.unwrap();
613 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
614
615 let resp = client
617 .post(format!("http://localhost:{}/api/signing/response", server.port()))
618 .body(
619 r#"{
620 "id": "invalid-uuid",
621 "signature": "invalid-signature",
622 "error": null
623 }"#,
624 )
625 .header("Content-Type", "application/json")
626 .send()
627 .await
628 .unwrap();
629
630 assert_eq!(resp.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY);
632 }
633
634 #[tokio::test]
635 async fn test_send_sign_client_reject() {
636 let mut server = create_server::<Ethereum>();
637 let client = client_with_token(&server);
638 server.start().await.unwrap();
639 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
640
641 let (sign_request_id, sign_request) = create_browser_sign_request();
642 let handle = wait_for_message_signing(&server, sign_request).await;
643 check_sign_request_content(&client, &server, sign_request_id).await;
644
645 let resp = client
647 .post(format!("http://localhost:{}/api/signing/response", server.port()))
648 .json(&BrowserSignResponse {
649 id: sign_request_id,
650 signature: None,
651 error: Some("User rejected the signing request".into()),
652 })
653 .send()
654 .await
655 .unwrap()
656 .error_for_status()
657 .unwrap();
658 assert_eq!(resp.status(), reqwest::StatusCode::OK);
659
660 let res = handle.await.expect("task panicked");
662 match res {
663 Err(BrowserWalletError::Rejected { operation, reason }) => {
664 assert_eq!(operation, "Signing");
665 assert_eq!(reason, "User rejected the signing request");
666 }
667 other => panic!("expected rejection, got {other:?}"),
668 }
669 }
670
671 #[tokio::test]
672 async fn test_send_multiple_sign_requests() {
673 let mut server = create_server::<Ethereum>();
674 let client = client_with_token(&server);
675 server.start().await.unwrap();
676 connect_wallet(&client, &server, Connection::new(ALICE, 1)).await;
677
678 let (sign_request_id1, sign_request1) = create_browser_sign_request();
680 let (sign_request_id2, sign_request2) = create_different_browser_sign_request();
681
682 let handle1 = wait_for_message_signing(&server, sign_request1.clone()).await;
684 let handle2 = wait_for_message_signing(&server, sign_request2.clone()).await;
685
686 {
688 let resp = client
689 .get(format!("http://localhost:{}/api/signing/request", server.port()))
690 .send()
691 .await
692 .unwrap();
693
694 let BrowserApiResponse::Ok(pending_sign) =
695 resp.json::<BrowserApiResponse<BrowserSignRequest>>().await.unwrap()
696 else {
697 panic!("expected BrowserApiResponse::Ok with a pending sign request");
698 };
699
700 assert_eq!(pending_sign.id, sign_request_id1);
701 assert_eq!(pending_sign.sign_type, sign_request1.sign_type);
702 assert_eq!(pending_sign.request.address, sign_request1.request.address);
703 assert_eq!(pending_sign.request.message, sign_request1.request.message);
704 }
705
706 let resp1 = client
708 .post(format!("http://localhost:{}/api/signing/response", server.port()))
709 .json(&BrowserSignResponse {
710 id: sign_request_id1,
711 signature: Some(Bytes::from("Signature1")),
712 error: None,
713 })
714 .send()
715 .await
716 .unwrap()
717 .error_for_status()
718 .unwrap();
719 assert_eq!(resp1.status(), reqwest::StatusCode::OK);
720
721 let res1 = handle1.await.expect("first signing flow panicked");
722 match res1 {
723 Ok(signature) => assert_eq!(signature, Bytes::from("Signature1")),
724 other => panic!("expected success, got {other:?}"),
725 }
726
727 {
729 let resp = client
730 .get(format!("http://localhost:{}/api/signing/request", server.port()))
731 .send()
732 .await
733 .unwrap();
734
735 let BrowserApiResponse::Ok(pending_sign) =
736 resp.json::<BrowserApiResponse<BrowserSignRequest>>().await.unwrap()
737 else {
738 panic!("expected BrowserApiResponse::Ok with a pending sign request");
739 };
740
741 assert_eq!(pending_sign.id, sign_request_id2,);
742 assert_eq!(pending_sign.sign_type, sign_request2.sign_type);
743 assert_eq!(pending_sign.request.address, sign_request2.request.address);
744 assert_eq!(pending_sign.request.message, sign_request2.request.message);
745 }
746
747 let resp2 = client
749 .post(format!("http://localhost:{}/api/signing/response", server.port()))
750 .json(&BrowserSignResponse {
751 id: sign_request_id2,
752 signature: None,
753 error: Some("User rejected the signing request".into()),
754 })
755 .send()
756 .await
757 .unwrap()
758 .error_for_status()
759 .unwrap();
760 assert_eq!(resp2.status(), reqwest::StatusCode::OK);
761
762 let res2 = handle2.await.expect("second signing flow panicked");
763 match res2 {
764 Err(BrowserWalletError::Rejected { operation, reason }) => {
765 assert_eq!(operation, "Signing");
766 assert_eq!(reason, "User rejected the signing request");
767 }
768 other => panic!("expected BrowserWalletError::Rejected, got {other:?}"),
769 }
770
771 check_sign_request_queue_empty(&client, &server).await;
772
773 server.stop().await.unwrap();
774 }
775
776 fn create_server<N: Network>() -> BrowserWalletServer<N> {
778 BrowserWalletServer::new(0, false, DEFAULT_TIMEOUT, DEFAULT_DEVELOPMENT)
779 }
780
781 fn client_with_token<N: Network>(server: &BrowserWalletServer<N>) -> reqwest::Client {
783 let mut headers = HeaderMap::new();
784 headers.insert("X-Session-Token", HeaderValue::from_str(server.session_token()).unwrap());
785 reqwest::Client::builder().default_headers(headers).build().unwrap()
786 }
787
788 async fn connect_wallet<N: Network>(
790 client: &reqwest::Client,
791 server: &BrowserWalletServer<N>,
792 connection: Connection,
793 ) {
794 let resp = client
795 .post(format!("http://localhost:{}/api/connection", server.port()))
796 .json(&connection)
797 .send();
798 assert!(resp.await.is_ok());
799 }
800
801 async fn disconnect_wallet<N: Network>(
803 client: &reqwest::Client,
804 server: &BrowserWalletServer<N>,
805 ) {
806 let resp = client
807 .post(format!("http://localhost:{}/api/connection", server.port()))
808 .json(&Option::<Connection>::None)
809 .send();
810 assert!(resp.await.is_ok());
811 }
812
813 async fn wait_for_transaction_signing<N: Network>(
815 server: &BrowserWalletServer<N>,
816 tx_request: BrowserTransactionRequest<N>,
817 ) -> JoinHandle<Result<TxHash, BrowserWalletError>> {
818 let browser_server = server.clone();
820 let join_handle =
821 tokio::spawn(async move { browser_server.request_transaction(tx_request).await });
822 tokio::task::yield_now().await;
823 tokio::time::sleep(Duration::from_millis(100)).await;
824
825 join_handle
826 }
827
828 async fn wait_for_message_signing<N: Network>(
830 server: &BrowserWalletServer<N>,
831 sign_request: BrowserSignRequest,
832 ) -> JoinHandle<Result<Bytes, BrowserWalletError>> {
833 let browser_server = server.clone();
835 let join_handle =
836 tokio::spawn(async move { browser_server.request_signing(sign_request).await });
837 tokio::task::yield_now().await;
838 tokio::time::sleep(Duration::from_millis(100)).await;
839
840 join_handle
841 }
842
843 fn create_browser_transaction_request<N: Network>() -> (Uuid, BrowserTransactionRequest<N>) {
845 let id = Uuid::new_v4();
846 let request = N::TransactionRequest::default()
847 .with_from(ALICE)
848 .with_to(BOB)
849 .with_value(U256::from(1000));
850 let tx = BrowserTransactionRequest { id, request };
851 (id, tx)
852 }
853
854 fn create_different_browser_transaction_request<N: Network>()
856 -> (Uuid, BrowserTransactionRequest<N>) {
857 let id = Uuid::new_v4();
858 let request = N::TransactionRequest::default()
859 .with_from(BOB)
860 .with_to(ALICE)
861 .with_value(U256::from(2000));
862 let tx = BrowserTransactionRequest { id, request };
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<N: Network>(
890 client: &reqwest::Client,
891 server: &BrowserWalletServer<N>,
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<N>>>().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<N: Network>(
910 client: &reqwest::Client,
911 server: &BrowserWalletServer<N>,
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<N>>>().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.kind(), 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<N: Network>(
934 client: &reqwest::Client,
935 server: &BrowserWalletServer<N>,
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<N: Network>(
954 client: &reqwest::Client,
955 server: &BrowserWalletServer<N>,
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}