1use std::{
10 borrow::Cow,
11 sync::Arc,
12 time::{Duration, SystemTime},
13};
14
15use anyhow::{Context, ensure};
16use arc_swap::ArcSwapOption;
17use async_trait::async_trait;
18use lexe_api::{
19 auth::{self, BearerAuthenticator},
20 def::{
21 AppBackendApi, AppGatewayApi, AppNodeProvisionApi, AppNodeRunApi,
22 BearerAuthBackendApi,
23 },
24 error::{BackendApiError, GatewayApiError, NodeApiError, NodeErrorKind},
25 models::{
26 command::{
27 BackupInfo, CloseChannelRequest, CreateInvoiceRequest,
28 CreateInvoiceResponse, CreateOfferRequest, CreateOfferResponse,
29 EnclavesToProvisionRequest, GetAddressResponse, GetNewPayments,
30 GetUpdatedPayments, HumanBitcoinAddress, ListChannelsResponse,
31 NodeInfo, OpenChannelRequest, OpenChannelResponse,
32 PayInvoiceRequest, PayInvoiceResponse, PayOfferRequest,
33 PayOfferResponse, PayOnchainRequest, PayOnchainResponse,
34 PaymentCreatedIndexes, PaymentIdStruct,
35 PreflightCloseChannelRequest, PreflightCloseChannelResponse,
36 PreflightOpenChannelRequest, PreflightOpenChannelResponse,
37 PreflightPayInvoiceRequest, PreflightPayInvoiceResponse,
38 PreflightPayOfferRequest, PreflightPayOfferResponse,
39 PreflightPayOnchainRequest, PreflightPayOnchainResponse,
40 SetupGDrive, UpdatePaymentNote,
41 },
42 nwc::{
43 CreateNwcClientRequest, CreateNwcClientResponse,
44 ListNwcClientResponse, NostrPkStruct, UpdateNwcClientRequest,
45 UpdateNwcClientResponse,
46 },
47 },
48 rest::{POST, RequestBuilderExt, RestClient},
49 types::{
50 Empty,
51 payments::{MaybeBasicPaymentV2, VecBasicPaymentV1, VecBasicPaymentV2},
52 username::UsernameStruct,
53 },
54};
55use lexe_common::{
56 api::{
57 auth::{
58 BearerAuthRequestWire, BearerAuthResponse, BearerAuthToken, Scope,
59 TokenWithExpiration, UserSignupRequestWire,
60 UserSignupRequestWireV1,
61 },
62 fiat_rates::FiatRates,
63 models::{
64 SignMsgRequest, SignMsgResponse, VerifyMsgRequest,
65 VerifyMsgResponse,
66 },
67 provision::NodeProvisionRequest,
68 revocable_clients::{
69 CreateRevocableClientRequest, CreateRevocableClientResponse,
70 GetRevocableClients, RevocableClient, RevocableClients,
71 UpdateClientRequest, UpdateClientResponse,
72 },
73 user::UserPk,
74 version::{CurrentEnclaves, EnclavesToProvision, NodeEnclave},
75 },
76 byte_str::ByteStr,
77 constants::{self, node_provision_dns},
78 env::DeployEnv,
79};
80use lexe_crypto::{ed25519, rng::Crng};
81use lexe_enclave::enclave::Measurement;
82use lexe_tls::{attest_client, lexe_ca, rustls};
83use reqwest::Url;
84
85use crate::credentials::{ClientCredentials, CredentialsRef};
86
87#[derive(Clone)]
89pub struct GatewayClient {
90 rest: RestClient,
91 gateway_url: Cow<'static, str>,
92}
93
94#[derive(Clone)]
108pub struct NodeClient {
109 inner: Arc<NodeClientInner>,
110}
111
112struct NodeClientInner {
113 user_pk: Option<UserPk>,
115 gateway_client: GatewayClient,
116 run_rest: ArcSwapOption<RunRestClient>,
133 run_url: &'static str,
134 use_sgx: bool,
135 deploy_env: DeployEnv,
136 authenticator: Arc<BearerAuthenticator>,
137 tls_config: rustls::ClientConfig,
138}
139
140struct RunRestClient {
143 client: RestClient,
144 token_expiration: Option<SystemTime>,
147}
148
149impl GatewayClient {
152 pub fn new(
153 deploy_env: DeployEnv,
154 gateway_url: impl Into<Cow<'static, str>>,
155 user_agent: impl Into<Cow<'static, str>>,
156 ) -> anyhow::Result<Self> {
157 fn inner(
158 deploy_env: DeployEnv,
159 gateway_url: Cow<'static, str>,
160 user_agent: Cow<'static, str>,
161 ) -> anyhow::Result<GatewayClient> {
162 let tls_config = lexe_ca::app_gateway_client_config(deploy_env);
163 let rest = RestClient::new(user_agent, "gateway", tls_config);
164 Ok(GatewayClient { rest, gateway_url })
165 }
166 inner(deploy_env, gateway_url.into(), user_agent.into())
167 }
168}
169
170impl AppBackendApi for GatewayClient {
171 async fn signup_v2(
172 &self,
173 signed_req: &ed25519::Signed<&UserSignupRequestWire>,
174 ) -> Result<Empty, BackendApiError> {
175 let gateway_url = &self.gateway_url;
176 let req = self
177 .rest
178 .builder(POST, format!("{gateway_url}/app/v2/signup"))
179 .signed_bcs(signed_req)
180 .map_err(BackendApiError::bcs_serialize)?;
181 self.rest.send(req).await
182 }
183
184 async fn signup_v1(
185 &self,
186 _signed_req: &ed25519::Signed<&UserSignupRequestWireV1>,
187 ) -> Result<Empty, BackendApiError> {
188 debug_assert!(false, "Use `signup_v2`");
189 Err(BackendApiError::not_found("Use `/app/v2/signup`"))
190 }
191
192 async fn enclaves_to_provision(
193 &self,
194 req: &EnclavesToProvisionRequest,
195 auth: BearerAuthToken,
196 ) -> Result<EnclavesToProvision, BackendApiError> {
197 let gateway_url = &self.gateway_url;
198 let url = format!("{gateway_url}/app/v1/enclaves_to_provision");
199 let req = self.rest.post(url, req).bearer_auth(&auth);
200 self.rest.send(req).await
201 }
202}
203
204#[async_trait]
205impl BearerAuthBackendApi for GatewayClient {
206 async fn bearer_auth(
207 &self,
208 signed_req: &ed25519::Signed<&BearerAuthRequestWire>,
209 ) -> Result<BearerAuthResponse, BackendApiError> {
210 let gateway_url = &self.gateway_url;
211 let req = self
212 .rest
213 .builder(POST, format!("{gateway_url}/app/bearer_auth"))
214 .signed_bcs(signed_req)
215 .map_err(BackendApiError::bcs_serialize)?;
216 self.rest.send(req).await
217 }
218}
219
220impl AppGatewayApi for GatewayClient {
221 async fn get_fiat_rates(&self) -> Result<FiatRates, GatewayApiError> {
222 let gateway_url = &self.gateway_url;
223 let req = self
224 .rest
225 .get(format!("{gateway_url}/app/v1/fiat_rates"), &Empty {});
226 self.rest.send(req).await
227 }
228
229 async fn latest_release(&self) -> Result<NodeEnclave, GatewayApiError> {
230 let gateway_url = &self.gateway_url;
231 let req = self
232 .rest
233 .get(format!("{gateway_url}/app/v1/latest_release"), &Empty {});
234 self.rest.send(req).await
235 }
236
237 async fn current_releases(
238 &self,
239 ) -> Result<CurrentEnclaves, GatewayApiError> {
240 let gateway_url = &self.gateway_url;
241 let req = self
242 .rest
243 .get(format!("{gateway_url}/app/v1/current_releases"), &Empty {});
244 self.rest.send(req).await
245 }
246
247 async fn current_enclaves(
248 &self,
249 ) -> Result<CurrentEnclaves, GatewayApiError> {
250 let gateway_url = &self.gateway_url;
251 let req = self
252 .rest
253 .get(format!("{gateway_url}/app/v1/current_enclaves"), &Empty {});
254 self.rest.send(req).await
255 }
256}
257
258impl NodeClient {
261 pub fn new(
262 rng: &mut impl Crng,
263 use_sgx: bool,
264 deploy_env: DeployEnv,
265 gateway_client: GatewayClient,
266 credentials: CredentialsRef<'_>,
267 ) -> anyhow::Result<Self> {
268 let run_url = constants::NODE_RUN_URL;
269
270 let gateway_url = &gateway_client.gateway_url;
271 ensure!(
272 gateway_url.starts_with("https://"),
273 "proxy connection must be https: gateway url: {gateway_url}",
274 );
275
276 let user_pk = credentials.user_pk();
277 let authenticator = credentials.bearer_authenticator();
278 let tls_config = credentials.tls_config(rng, deploy_env)?;
279 let run_rest = ArcSwapOption::from(None);
280
281 Ok(Self {
282 inner: Arc::new(NodeClientInner {
283 user_pk,
284 gateway_client,
285 run_rest,
286 run_url,
287 use_sgx,
288 deploy_env,
289 authenticator,
290 tls_config,
291 }),
292 })
293 }
294
295 pub fn user_pk(&self) -> Option<UserPk> {
300 self.inner.user_pk
301 }
302
303 async fn authed_run_rest(
312 &self,
313 ) -> Result<Arc<RunRestClient>, NodeApiError> {
314 let now = SystemTime::now();
315
316 if let Some(run_rest) = self.maybe_authed_run_rest(now) {
318 return Ok(run_rest);
319 }
320
321 let auth_token = self.get_auth_token(now).await?;
326
327 if let Some(run_rest) = self.maybe_authed_run_rest(now) {
331 return Ok(run_rest);
333 }
334
335 let run_rest = RunRestClient::new(
337 &self.inner.gateway_client,
338 self.inner.run_url,
339 auth_token,
340 self.inner.tls_config.clone(),
341 )
342 .map_err(NodeApiError::bad_auth)?;
343 let run_rest = Arc::new(run_rest);
344
345 self.inner.run_rest.swap(Some(run_rest.clone()));
347
348 Ok(run_rest)
349 }
350
351 fn maybe_authed_run_rest(
354 &self,
355 now: SystemTime,
356 ) -> Option<Arc<RunRestClient>> {
357 let maybe_run_rest = self.inner.run_rest.load_full();
358 if let Some(run_rest) = maybe_run_rest
359 && !run_rest.token_needs_refresh(now)
360 {
361 Some(run_rest)
362 } else {
363 None
364 }
365 }
366
367 async fn get_auth_token(
370 &self,
371 now: SystemTime,
372 ) -> Result<TokenWithExpiration, NodeApiError> {
373 self.inner
374 .authenticator
375 .get_token_with_exp(&self.inner.gateway_client, now)
376 .await
377 .map_err(|backend_error| {
380 let msg = format!("{backend_error:#}");
382
383 let BackendApiError {
384 data, sensitive, ..
385 } = backend_error;
386
387 NodeApiError {
388 kind: NodeErrorKind::BadAuth,
389 msg,
390 data,
391 sensitive,
392 }
393 })
394 }
395
396 fn provision_rest_client(
402 &self,
403 provision_url: &str,
404 auth_token: BearerAuthToken,
405 measurement: Measurement,
406 ) -> anyhow::Result<RestClient> {
407 let proxy = static_proxy_config(
408 &self.inner.gateway_client.gateway_url,
409 provision_url,
410 auth_token,
411 )
412 .context("Invalid proxy config")?;
413
414 let tls_config = attest_client::app_node_provision_client_config(
415 self.inner.use_sgx,
416 self.inner.deploy_env,
417 measurement,
418 );
419
420 let user_agent = self.inner.gateway_client.rest.user_agent().clone();
421 let (from, to) = (user_agent, "node-provision");
422 let reqwest_client = RestClient::client_builder(&from)
423 .proxy(proxy)
424 .use_preconfigured_tls(tls_config)
425 .timeout(Duration::from_secs(30))
427 .build()
428 .context("Failed to build client")?;
429
430 let provision_rest = RestClient::from_inner(reqwest_client, from, to);
431
432 Ok(provision_rest)
433 }
434
435 pub async fn create_client_credentials(
438 &self,
439 req: CreateRevocableClientRequest,
440 ) -> anyhow::Result<(RevocableClient, ClientCredentials)> {
441 let lexe_auth_token = self.request_long_lived_connect_token().await?;
443
444 let resp = self.create_revocable_client(req.clone()).await?;
446
447 let client = RevocableClient {
448 pubkey: resp.pubkey,
449 created_at: resp.created_at,
450 label: req.label,
451 scope: req.scope,
452 expires_at: req.expires_at,
453 is_revoked: false,
454 };
455
456 let client_credentials =
457 ClientCredentials::from_response(lexe_auth_token, resp);
458
459 Ok((client, client_credentials))
460 }
461
462 async fn request_long_lived_connect_token(
465 &self,
466 ) -> anyhow::Result<BearerAuthToken> {
467 let user_key_pair = self
468 .inner
469 .authenticator
470 .user_key_pair()
471 .context("Somehow using a static bearer auth token")?;
472
473 let now = SystemTime::now();
474 let lifetime_secs = 10 * 365 * 24 * 60 * 60; let scope = Some(Scope::NodeConnect);
476 let long_lived_connect_token = lexe_api::auth::do_bearer_auth(
477 &self.inner.gateway_client,
478 now,
479 user_key_pair,
480 lifetime_secs,
481 scope,
482 )
483 .await
484 .context("Failed to get long-lived connect token")?;
485
486 Ok(long_lived_connect_token.token)
487 }
488
489 pub async fn request_provision_token(
491 &self,
492 ) -> anyhow::Result<BearerAuthToken> {
493 let user_key_pair = self
494 .inner
495 .authenticator
496 .user_key_pair()
497 .context("Somehow using a static bearer auth token")?;
498
499 let now = SystemTime::now();
500 let lifetime_secs = 60; let scope = Some(Scope::All);
502 let token = lexe_api::auth::do_bearer_auth(
503 &self.inner.gateway_client,
504 now,
505 user_key_pair,
506 lifetime_secs,
507 scope,
508 )
509 .await
510 .context("Failed to get app token")?;
511
512 Ok(token.token)
513 }
514}
515
516impl AppNodeProvisionApi for NodeClient {
517 async fn provision(
518 &self,
519 measurement: Measurement,
520 data: NodeProvisionRequest,
521 ) -> Result<Empty, NodeApiError> {
522 let now = SystemTime::now();
523 let mr_short = measurement.short();
524 let provision_dns = node_provision_dns(&mr_short);
525 let provision_url = format!("https://{provision_dns}");
526
527 let auth_token = self.get_auth_token(now).await?.token;
529 let provision_rest = self
530 .provision_rest_client(&provision_url, auth_token, measurement)
531 .context("Failed to build provision rest client")
532 .map_err(NodeApiError::provision)?;
533
534 let req = provision_rest
535 .post(format!("{provision_url}/app/provision"), &data);
536 provision_rest.send(req).await
537 }
538}
539
540impl AppNodeRunApi for NodeClient {
541 async fn node_info(&self) -> Result<NodeInfo, NodeApiError> {
542 let run_rest = &self.authed_run_rest().await?.client;
543 let run_url = &self.inner.run_url;
544 let url = format!("{run_url}/app/node_info");
545 let req = run_rest.get(url, &Empty {});
546 run_rest.send(req).await
547 }
548
549 async fn list_channels(
550 &self,
551 ) -> Result<ListChannelsResponse, NodeApiError> {
552 let run_rest = &self.authed_run_rest().await?.client;
553 let run_url = &self.inner.run_url;
554 let url = format!("{run_url}/app/list_channels");
555 let req = run_rest.get(url, &Empty {});
556 run_rest.send(req).await
557 }
558
559 async fn sign_message(
560 &self,
561 data: SignMsgRequest,
562 ) -> Result<SignMsgResponse, NodeApiError> {
563 let run_rest = &self.authed_run_rest().await?.client;
564 let run_url = &self.inner.run_url;
565 let url = format!("{run_url}/app/sign_message");
566 let req = run_rest.post(url, &data);
567 run_rest.send(req).await
568 }
569
570 async fn verify_message(
571 &self,
572 data: VerifyMsgRequest,
573 ) -> Result<VerifyMsgResponse, NodeApiError> {
574 let run_rest = &self.authed_run_rest().await?.client;
575 let run_url = &self.inner.run_url;
576 let url = format!("{run_url}/app/verify_message");
577 let req = run_rest.post(url, &data);
578 run_rest.send(req).await
579 }
580
581 async fn open_channel(
582 &self,
583 data: OpenChannelRequest,
584 ) -> Result<OpenChannelResponse, NodeApiError> {
585 let run_rest = &self.authed_run_rest().await?.client;
586 let run_url = &self.inner.run_url;
587 let url = format!("{run_url}/app/open_channel");
588 let req = run_rest.post(url, &data);
589 run_rest.send(req).await
590 }
591
592 async fn preflight_open_channel(
593 &self,
594 data: PreflightOpenChannelRequest,
595 ) -> Result<PreflightOpenChannelResponse, NodeApiError> {
596 let run_rest = &self.authed_run_rest().await?.client;
597 let run_url = &self.inner.run_url;
598 let url = format!("{run_url}/app/preflight_open_channel");
599 let req = run_rest.post(url, &data);
600 run_rest.send(req).await
601 }
602
603 async fn close_channel(
604 &self,
605 data: CloseChannelRequest,
606 ) -> Result<Empty, NodeApiError> {
607 let run_rest = &self.authed_run_rest().await?.client;
608 let run_url = &self.inner.run_url;
609 let url = format!("{run_url}/app/close_channel");
610 let req = run_rest.post(url, &data);
611 run_rest.send(req).await
612 }
613
614 async fn preflight_close_channel(
615 &self,
616 data: PreflightCloseChannelRequest,
617 ) -> Result<PreflightCloseChannelResponse, NodeApiError> {
618 let run_rest = &self.authed_run_rest().await?.client;
619 let run_url = &self.inner.run_url;
620 let url = format!("{run_url}/app/preflight_close_channel");
621 let req = run_rest.post(url, &data);
622 run_rest.send(req).await
623 }
624
625 async fn create_invoice(
626 &self,
627 data: CreateInvoiceRequest,
628 ) -> Result<CreateInvoiceResponse, NodeApiError> {
629 let run_rest = &self.authed_run_rest().await?.client;
630 let run_url = &self.inner.run_url;
631 let url = format!("{run_url}/app/create_invoice");
632 let req = run_rest.post(url, &data);
633 run_rest.send(req).await
634 }
635
636 async fn pay_invoice(
637 &self,
638 req: PayInvoiceRequest,
639 ) -> Result<PayInvoiceResponse, NodeApiError> {
640 let run_rest = &self.authed_run_rest().await?.client;
641 let run_url = &self.inner.run_url;
642 let url = format!("{run_url}/app/pay_invoice");
643 let req = run_rest
645 .post(url, &req)
646 .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
647 run_rest.send(req).await
648 }
649
650 async fn preflight_pay_invoice(
651 &self,
652 req: PreflightPayInvoiceRequest,
653 ) -> Result<PreflightPayInvoiceResponse, NodeApiError> {
654 let run_rest = &self.authed_run_rest().await?.client;
655 let run_url = &self.inner.run_url;
656 let url = format!("{run_url}/app/preflight_pay_invoice");
657 let req = run_rest
659 .post(url, &req)
660 .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
661 run_rest.send(req).await
662 }
663
664 async fn pay_onchain(
665 &self,
666 req: PayOnchainRequest,
667 ) -> Result<PayOnchainResponse, NodeApiError> {
668 let run_rest = &self.authed_run_rest().await?.client;
669 let run_url = &self.inner.run_url;
670 let url = format!("{run_url}/app/pay_onchain");
671 let req = run_rest.post(url, &req);
672 run_rest.send(req).await
673 }
674
675 async fn preflight_pay_onchain(
676 &self,
677 req: PreflightPayOnchainRequest,
678 ) -> Result<PreflightPayOnchainResponse, NodeApiError> {
679 let run_rest = &self.authed_run_rest().await?.client;
680 let run_url = &self.inner.run_url;
681 let url = format!("{run_url}/app/preflight_pay_onchain");
682 let req = run_rest.post(url, &req);
683 run_rest.send(req).await
684 }
685
686 async fn create_offer(
687 &self,
688 req: CreateOfferRequest,
689 ) -> Result<CreateOfferResponse, NodeApiError> {
690 let run_rest = &self.authed_run_rest().await?.client;
691 let run_url = &self.inner.run_url;
692 let url = format!("{run_url}/app/create_offer");
693 let req = run_rest.post(url, &req);
694 run_rest.send(req).await
695 }
696
697 async fn pay_offer(
698 &self,
699 req: PayOfferRequest,
700 ) -> Result<PayOfferResponse, NodeApiError> {
701 let run_rest = &self.authed_run_rest().await?.client;
702 let run_url = &self.inner.run_url;
703 let url = format!("{run_url}/app/pay_offer");
704 let req = run_rest.post(url, &req);
705 run_rest.send(req).await
706 }
707
708 async fn preflight_pay_offer(
709 &self,
710 req: PreflightPayOfferRequest,
711 ) -> Result<PreflightPayOfferResponse, NodeApiError> {
712 let run_rest = &self.authed_run_rest().await?.client;
713 let run_url = &self.inner.run_url;
714 let url = format!("{run_url}/app/preflight_pay_offer");
715 let req = run_rest.post(url, &req);
716 run_rest.send(req).await
717 }
718
719 async fn get_address(&self) -> Result<GetAddressResponse, NodeApiError> {
720 let run_rest = &self.authed_run_rest().await?.client;
721 let run_url = &self.inner.run_url;
722 let url = format!("{run_url}/app/get_address");
723 let req = run_rest.post(url, &Empty {});
724 run_rest.send(req).await
725 }
726
727 async fn get_payments_by_indexes(
728 &self,
729 _: PaymentCreatedIndexes,
730 ) -> Result<VecBasicPaymentV1, NodeApiError> {
731 unimplemented!("Deprecated")
732 }
733
734 async fn get_new_payments(
735 &self,
736 _: GetNewPayments,
737 ) -> Result<VecBasicPaymentV1, NodeApiError> {
738 unimplemented!("Deprecated")
739 }
740
741 async fn get_updated_payments(
742 &self,
743 req: GetUpdatedPayments,
744 ) -> Result<VecBasicPaymentV2, NodeApiError> {
745 let run_rest = &self.authed_run_rest().await?.client;
746 let run_url = &self.inner.run_url;
747 let url = format!("{run_url}/app/payments/updated");
748 let req = run_rest.get(url, &req);
749 run_rest.send(req).await
750 }
751
752 async fn get_payment_by_id(
753 &self,
754 req: PaymentIdStruct,
755 ) -> Result<MaybeBasicPaymentV2, NodeApiError> {
756 let run_rest = &self.authed_run_rest().await?.client;
757 let run_url = &self.inner.run_url;
758 let url = format!("{run_url}/app/v1/payments/id");
759 let req = run_rest.get(url, &req);
760 run_rest.send(req).await
761 }
762
763 async fn update_payment_note(
764 &self,
765 req: UpdatePaymentNote,
766 ) -> Result<Empty, NodeApiError> {
767 let run_rest = &self.authed_run_rest().await?.client;
768 let run_url = &self.inner.run_url;
769 let url = format!("{run_url}/app/payments/note");
770 let req = run_rest.put(url, &req);
771 run_rest.send(req).await
772 }
773
774 async fn get_revocable_clients(
775 &self,
776 req: GetRevocableClients,
777 ) -> Result<RevocableClients, NodeApiError> {
778 let run_rest = &self.authed_run_rest().await?.client;
779 let run_url = &self.inner.run_url;
780 let url = format!("{run_url}/app/clients");
781 let req = run_rest.get(url, &req);
782 run_rest.send(req).await
783 }
784
785 async fn create_revocable_client(
786 &self,
787 req: CreateRevocableClientRequest,
788 ) -> Result<CreateRevocableClientResponse, NodeApiError> {
789 let run_rest = &self.authed_run_rest().await?.client;
790 let run_url = &self.inner.run_url;
791 let url = format!("{run_url}/app/clients");
792 let req = run_rest.post(url, &req);
793 run_rest.send(req).await
794 }
795
796 async fn update_revocable_client(
797 &self,
798 req: UpdateClientRequest,
799 ) -> Result<UpdateClientResponse, NodeApiError> {
800 let run_rest = &self.authed_run_rest().await?.client;
801 let run_url = &self.inner.run_url;
802 let url = format!("{run_url}/app/clients");
803 let req = run_rest.put(url, &req);
804 run_rest.send(req).await
805 }
806
807 async fn list_broadcasted_txs(
808 &self,
809 ) -> Result<serde_json::Value, NodeApiError> {
810 let run_rest = &self.authed_run_rest().await?.client;
811 let run_url = &self.inner.run_url;
812 let url = format!("{run_url}/app/list_broadcasted_txs");
813 let req = run_rest.get(url, &Empty {});
814 run_rest.send(req).await
815 }
816
817 async fn backup_info(&self) -> Result<BackupInfo, NodeApiError> {
818 let run_rest = &self.authed_run_rest().await?.client;
819 let run_url = &self.inner.run_url;
820 let url = format!("{run_url}/app/backup");
821 let req = run_rest.get(url, &Empty {});
822 run_rest.send(req).await
823 }
824
825 async fn setup_gdrive(
826 &self,
827 req: SetupGDrive,
828 ) -> Result<Empty, NodeApiError> {
829 let run_rest = &self.authed_run_rest().await?.client;
830 let run_url = &self.inner.run_url;
831 let url = format!("{run_url}/app/backup/gdrive");
832 let req = run_rest.post(url, &req);
833 run_rest.send(req).await
834 }
835
836 async fn get_human_bitcoin_address(
837 &self,
838 ) -> Result<HumanBitcoinAddress, NodeApiError> {
839 let run_rest = &self.authed_run_rest().await?.client;
840 let run_url = &self.inner.run_url;
841 let url = format!("{run_url}/app/human_bitcoin_address");
842 let req = run_rest.get(url, &Empty {});
843 run_rest.send(req).await
844 }
845
846 async fn update_human_bitcoin_address(
847 &self,
848 req: UsernameStruct,
849 ) -> Result<HumanBitcoinAddress, NodeApiError> {
850 let run_rest = &self.authed_run_rest().await?.client;
851 let run_url = &self.inner.run_url;
852 let url = format!("{run_url}/app/human_bitcoin_address");
853 let req = run_rest.put(url, &req);
854 run_rest.send(req).await
855 }
856
857 #[allow(deprecated)]
858 async fn get_payment_address(
859 &self,
860 ) -> Result<HumanBitcoinAddress, NodeApiError> {
861 self.get_human_bitcoin_address().await
862 }
863
864 #[allow(deprecated)]
865 async fn update_payment_address(
866 &self,
867 req: UsernameStruct,
868 ) -> Result<HumanBitcoinAddress, NodeApiError> {
869 self.update_human_bitcoin_address(req).await
870 }
871
872 async fn list_nwc_clients(
873 &self,
874 ) -> Result<ListNwcClientResponse, NodeApiError> {
875 let run_rest = &self.authed_run_rest().await?.client;
876 let run_url = &self.inner.run_url;
877 let url = format!("{run_url}/app/nwc_clients");
878 let req = run_rest.get(url, &Empty {});
879 run_rest.send(req).await
880 }
881
882 async fn create_nwc_client(
883 &self,
884 req: CreateNwcClientRequest,
885 ) -> Result<CreateNwcClientResponse, NodeApiError> {
886 let run_rest = &self.authed_run_rest().await?.client;
887 let run_url = &self.inner.run_url;
888 let url = format!("{run_url}/app/nwc_clients");
889 let req = run_rest.post(url, &req);
890 run_rest.send(req).await
891 }
892
893 async fn update_nwc_client(
894 &self,
895 req: UpdateNwcClientRequest,
896 ) -> Result<UpdateNwcClientResponse, NodeApiError> {
897 let run_rest = &self.authed_run_rest().await?.client;
898 let run_url = &self.inner.run_url;
899 let url = format!("{run_url}/app/nwc_clients");
900 let req = run_rest.put(url, &req);
901 run_rest.send(req).await
902 }
903
904 async fn delete_nwc_client(
905 &self,
906 req: NostrPkStruct,
907 ) -> Result<Empty, NodeApiError> {
908 let run_rest = &self.authed_run_rest().await?.client;
909 let run_url = &self.inner.run_url;
910 let url = format!("{run_url}/app/nwc_clients");
911 let req = run_rest.delete(url, &req);
912 run_rest.send(req).await
913 }
914}
915
916impl RunRestClient {
919 fn new(
920 gateway_client: &GatewayClient,
921 run_url: &str,
922 auth_token: TokenWithExpiration,
923 tls_config: rustls::ClientConfig,
924 ) -> anyhow::Result<Self> {
925 let TokenWithExpiration { expiration, token } = auth_token;
926 let (from, to) = (gateway_client.rest.user_agent().clone(), "node-run");
927 let proxy =
928 static_proxy_config(&gateway_client.gateway_url, run_url, token)?;
929 let client = RestClient::client_builder(&from)
930 .proxy(proxy)
931 .use_preconfigured_tls(tls_config.clone())
932 .build()
933 .context("Failed to build client")?;
934 let client = RestClient::from_inner(client, from, to);
935
936 Ok(Self {
937 client,
938 token_expiration: expiration,
939 })
940 }
941
942 fn token_needs_refresh(&self, now: SystemTime) -> bool {
945 auth::token_needs_refresh(now, self.token_expiration)
946 }
947}
948
949fn static_proxy_config(
971 gateway_url: &str,
972 node_url: &str,
973 auth_token: BearerAuthToken,
974) -> anyhow::Result<reqwest::Proxy> {
975 let node_url = Url::parse(node_url).context("Invalid node url")?;
976 let gateway_url = gateway_url.to_owned();
977
978 let auth_header = http::HeaderValue::from_maybe_shared(ByteStr::from(
980 format!("Bearer {auth_token}"),
981 ))?;
982
983 let proxy = reqwest::Proxy::custom(move |url| {
984 if url_base_eq(url, &node_url) {
986 Some(gateway_url.clone())
987 } else {
988 None
989 }
990 })
991 .custom_http_auth(auth_header);
994
995 Ok(proxy)
996}
997
998fn url_base_eq(u1: &Url, u2: &Url) -> bool {
999 u1.scheme() == u2.scheme()
1000 && u1.host() == u2.host()
1001 && u1.port_or_known_default() == u2.port_or_known_default()
1002}
1003
1004#[cfg(test)]
1005mod test {
1006 use super::*;
1007
1008 #[test]
1009 fn test_url_base_eq() {
1010 let eq_classes = vec![
1013 vec![
1014 "https://hello.world",
1015 "https://hello.world/",
1016 "https://hello.world/my_cool_method",
1017 "https://hello.world/my_cool_method&query=params",
1018 "https://hello.world/&query=params",
1019 ],
1020 vec![
1021 "http://hello.world",
1022 "http://hello.world/",
1023 "http://hello.world/my_cool_method",
1024 "http://hello.world/my_cool_method&query=params",
1025 "http://hello.world/&query=params",
1026 ],
1027 vec![
1028 "https://hello.world:8080",
1029 "https://hello.world:8080/",
1030 "https://hello.world:8080/my_cool_method",
1031 "https://hello.world:8080/my_cool_method&query=params",
1032 "https://hello.world:8080/&query=params",
1033 ],
1034 vec![
1035 "https://127.0.0.1:8080",
1036 "https://127.0.0.1:8080/",
1037 "https://127.0.0.1:8080/my_cool_method",
1038 "https://127.0.0.1:8080/my_cool_method&query=params",
1039 "https://127.0.0.1:8080/&query=params",
1040 ],
1041 vec![
1042 "https://[::1]:8080",
1043 "https://[::1]:8080/",
1044 "https://[::1]:8080/my_cool_method",
1045 "https://[::1]:8080/my_cool_method&query=params",
1046 "https://[::1]:8080/&query=params",
1047 ],
1048 ];
1049
1050 let eq_classes = eq_classes
1051 .into_iter()
1052 .map(|eq_class| {
1053 eq_class
1054 .into_iter()
1055 .map(|url| Url::parse(url).unwrap())
1056 .collect::<Vec<_>>()
1057 })
1058 .collect::<Vec<_>>();
1059
1060 let n_classes = eq_classes.len();
1061 let n_urls = eq_classes[0].len();
1062
1063 for eq_class in &eq_classes {
1065 for idx_u1 in 0..n_urls {
1066 for idx_u2 in idx_u1..n_urls {
1068 let u1 = &eq_class[idx_u1];
1069 let u2 = &eq_class[idx_u2];
1070 assert!(url_base_eq(u1, u2));
1071 assert!(url_base_eq(u2, u1));
1073 }
1074 }
1075 }
1076
1077 for idx_class1 in 0..(n_classes - 1) {
1079 let eq_class1 = &eq_classes[idx_class1];
1080 for eq_class2 in eq_classes.iter().skip(idx_class1 + 1) {
1081 for u1 in eq_class1 {
1082 for u2 in eq_class2 {
1083 assert!(!url_base_eq(u1, u2));
1085 assert!(!url_base_eq(u2, u1));
1086 }
1087 }
1088 }
1089 }
1090 }
1091}