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