fedimint_gateway_server/
rpc_server.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use anyhow::anyhow;
5use axum::body::Body;
6use axum::extract::{Path, Query, Request};
7use axum::http::{Response, StatusCode, header};
8use axum::middleware::{self, Next};
9use axum::response::IntoResponse;
10use axum::routing::{get, post};
11use axum::{Extension, Json, Router};
12use bitcoin::hashes::sha256;
13use fedimint_core::config::FederationId;
14use fedimint_core::task::TaskGroup;
15use fedimint_core::util::FmtCompact;
16use fedimint_gateway_common::{
17    ADDRESS_ENDPOINT, ADDRESS_RECHECK_ENDPOINT, BACKUP_ENDPOINT, BackupPayload,
18    CLOSE_CHANNELS_WITH_PEER_ENDPOINT, CONFIGURATION_ENDPOINT, CONNECT_FED_ENDPOINT,
19    CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT, CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
20    CloseChannelsWithPeerRequest, ConfigPayload, ConnectFedPayload,
21    CreateInvoiceForOperatorPayload, CreateOfferPayload, DepositAddressPayload,
22    DepositAddressRecheckPayload, GATEWAY_INFO_ENDPOINT, GATEWAY_INFO_POST_ENDPOINT,
23    GET_BALANCES_ENDPOINT, GET_INVOICE_ENDPOINT, GET_LN_ONCHAIN_ADDRESS_ENDPOINT,
24    GetInvoiceRequest, InfoPayload, LEAVE_FED_ENDPOINT, LIST_CHANNELS_ENDPOINT,
25    LIST_TRANSACTIONS_ENDPOINT, LeaveFedPayload, ListTransactionsPayload, MNEMONIC_ENDPOINT,
26    OPEN_CHANNEL_ENDPOINT, OpenChannelRequest, PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
27    PAY_OFFER_FOR_OPERATOR_ENDPOINT, PAYMENT_LOG_ENDPOINT, PAYMENT_SUMMARY_ENDPOINT,
28    PayInvoiceForOperatorPayload, PayOfferPayload, PaymentLogPayload, PaymentSummaryPayload,
29    RECEIVE_ECASH_ENDPOINT, ReceiveEcashPayload, SEND_ONCHAIN_ENDPOINT, SET_FEES_ENDPOINT,
30    SPEND_ECASH_ENDPOINT, STOP_ENDPOINT, SendOnchainRequest, SetFeesPayload, SpendEcashPayload,
31    V1_API_ENDPOINT, WITHDRAW_ENDPOINT, WithdrawPayload,
32};
33use fedimint_ln_common::gateway_endpoint_constants::{
34    GET_GATEWAY_ID_ENDPOINT, PAY_INVOICE_ENDPOINT,
35};
36use fedimint_lnv2_common::endpoint_constants::{
37    CREATE_BOLT11_INVOICE_ENDPOINT, ROUTING_INFO_ENDPOINT, SEND_PAYMENT_ENDPOINT,
38};
39use fedimint_lnv2_common::gateway_api::{CreateBolt11InvoicePayload, SendPaymentPayload};
40use fedimint_lnv2_common::lnurl::VerifyResponse;
41use fedimint_logging::LOG_GATEWAY;
42use hex::ToHex;
43use serde_json::json;
44use tokio::net::TcpListener;
45use tower_http::cors::CorsLayer;
46use tracing::{info, instrument, warn};
47
48use crate::Gateway;
49use crate::error::{AdminGatewayError, PublicGatewayError};
50
51/// LNURL-compliant error response for verify endpoints
52struct LnurlError {
53    code: StatusCode,
54    reason: anyhow::Error,
55}
56
57impl LnurlError {
58    fn internal(reason: anyhow::Error) -> Self {
59        Self {
60            code: StatusCode::INTERNAL_SERVER_ERROR,
61            reason,
62        }
63    }
64}
65
66impl IntoResponse for LnurlError {
67    fn into_response(self) -> Response<Body> {
68        let json = Json(serde_json::json!({
69            "status": "ERROR",
70            "reason": self.reason.to_string(),
71        }));
72
73        (self.code, json).into_response()
74    }
75}
76
77/// Creates the webserver's routes and spawns the webserver in a separate task.
78pub async fn run_webserver(gateway: Arc<Gateway>) -> anyhow::Result<()> {
79    let task_group = gateway.task_group.clone();
80    let v1_routes = v1_routes(gateway.clone(), task_group.clone());
81    let api_v1 = Router::new()
82        .nest(&format!("/{V1_API_ENDPOINT}"), v1_routes.clone())
83        // Backwards compatibility: Continue supporting gateway APIs without versioning
84        .merge(v1_routes);
85
86    let handle = task_group.make_handle();
87    let shutdown_rx = handle.make_shutdown_rx();
88    let listener = TcpListener::bind(&gateway.listen).await?;
89    let serve = axum::serve(listener, api_v1.into_make_service());
90    task_group.spawn("Gateway Webserver", |_| async {
91        let graceful = serve.with_graceful_shutdown(async {
92            shutdown_rx.await;
93        });
94
95        match graceful.await {
96            Err(err) => {
97                warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Error shutting down gatewayd webserver");
98            }
99            _ => {
100                info!(target: LOG_GATEWAY, "Successfully shutdown webserver");
101            }
102        }
103    });
104
105    info!(target: LOG_GATEWAY, listen = %gateway.listen, "Successfully started webserver");
106    Ok(())
107}
108
109/// Extracts the Bearer token from the Authorization header of the request.
110fn extract_bearer_token(request: &Request) -> Result<String, StatusCode> {
111    let headers = request.headers();
112    let auth_header = headers.get(header::AUTHORIZATION);
113    if let Some(header_value) = auth_header {
114        let auth_str = header_value
115            .to_str()
116            .map_err(|_| StatusCode::UNAUTHORIZED)?;
117        let token = auth_str.trim_start_matches("Bearer ").to_string();
118        return Ok(token);
119    }
120
121    Err(StatusCode::UNAUTHORIZED)
122}
123
124/// Middleware to authenticate an incoming request. Routes that are
125/// authenticated with this middleware always require a Bearer token to be
126/// supplied in the Authorization header.
127async fn auth_middleware(
128    Extension(gateway): Extension<Arc<Gateway>>,
129    request: Request,
130    next: Next,
131) -> Result<impl IntoResponse, StatusCode> {
132    let token = extract_bearer_token(&request)?;
133    if bcrypt::verify(token, &gateway.bcrypt_password_hash.to_string())
134        .expect("Bcrypt hash is valid since we just stringified it")
135    {
136        return Ok(next.run(request).await);
137    }
138
139    Err(StatusCode::UNAUTHORIZED)
140}
141
142/// Public routes that are used in the LNv1 protocol
143fn lnv1_routes() -> Router {
144    Router::new()
145        .route(PAY_INVOICE_ENDPOINT, post(pay_invoice))
146        .route(GET_GATEWAY_ID_ENDPOINT, get(get_gateway_id))
147}
148
149/// Public routes that are used in the LNv2 protocol
150fn lnv2_routes() -> Router {
151    Router::new()
152        .route(ROUTING_INFO_ENDPOINT, post(routing_info_v2))
153        .route(SEND_PAYMENT_ENDPOINT, post(pay_bolt11_invoice_v2))
154        .route(
155            CREATE_BOLT11_INVOICE_ENDPOINT,
156            post(create_bolt11_invoice_v2),
157        )
158        .route("/verify/{payment_hash}", get(verify_bolt11_preimage_v2_get))
159}
160
161/// Gateway Webserver Routes. The gateway supports three types of routes
162/// - Always Authenticated: these routes always require a Bearer token. Used by
163///   gateway administrators.
164/// - Authenticated after config: these routes are unauthenticated before
165///   configuring the gateway to allow the user to set a password. After setting
166///   the password, they become authenticated.
167/// - Un-authenticated: anyone can request these routes. Used by fedimint
168///   clients.
169fn v1_routes(gateway: Arc<Gateway>, task_group: TaskGroup) -> Router {
170    // Public routes on gateway webserver
171    let mut public_routes = Router::new().route(RECEIVE_ECASH_ENDPOINT, post(receive_ecash));
172
173    if gateway.is_running_lnv1() {
174        public_routes = public_routes.merge(lnv1_routes());
175    }
176
177    if gateway.is_running_lnv2() {
178        public_routes = public_routes.merge(lnv2_routes());
179    }
180
181    // Authenticated routes used for gateway administration
182    let authenticated_routes = Router::new()
183        .route(ADDRESS_ENDPOINT, post(address))
184        .route(WITHDRAW_ENDPOINT, post(withdraw))
185        .route(CONNECT_FED_ENDPOINT, post(connect_fed))
186        .route(LEAVE_FED_ENDPOINT, post(leave_fed))
187        .route(BACKUP_ENDPOINT, post(backup))
188        .route(
189            CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT,
190            post(create_invoice_for_operator),
191        )
192        .route(
193            CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
194            post(create_offer_for_operator),
195        )
196        .route(
197            PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
198            post(pay_invoice_operator),
199        )
200        .route(PAY_OFFER_FOR_OPERATOR_ENDPOINT, post(pay_offer_operator))
201        .route(GET_INVOICE_ENDPOINT, post(get_invoice))
202        .route(GET_LN_ONCHAIN_ADDRESS_ENDPOINT, get(get_ln_onchain_address))
203        .route(OPEN_CHANNEL_ENDPOINT, post(open_channel))
204        .route(
205            CLOSE_CHANNELS_WITH_PEER_ENDPOINT,
206            post(close_channels_with_peer),
207        )
208        .route(LIST_CHANNELS_ENDPOINT, get(list_channels))
209        .route(LIST_TRANSACTIONS_ENDPOINT, post(list_transactions))
210        .route(SEND_ONCHAIN_ENDPOINT, post(send_onchain))
211        .route(ADDRESS_RECHECK_ENDPOINT, post(recheck_address))
212        .route(GET_BALANCES_ENDPOINT, get(get_balances))
213        .route(SPEND_ECASH_ENDPOINT, post(spend_ecash))
214        .route(MNEMONIC_ENDPOINT, get(mnemonic))
215        .route(STOP_ENDPOINT, get(stop))
216        .route(PAYMENT_LOG_ENDPOINT, post(payment_log))
217        .route(PAYMENT_SUMMARY_ENDPOINT, post(payment_summary))
218        .route(SET_FEES_ENDPOINT, post(set_fees))
219        .route(CONFIGURATION_ENDPOINT, post(configuration))
220        // FIXME: deprecated >= 0.3.0
221        .route(GATEWAY_INFO_POST_ENDPOINT, post(handle_post_info))
222        .route(GATEWAY_INFO_ENDPOINT, get(info))
223        .layer(middleware::from_fn(auth_middleware));
224
225    Router::new()
226        .merge(public_routes)
227        .merge(authenticated_routes)
228        .layer(Extension(gateway))
229        .layer(Extension(task_group))
230        .layer(CorsLayer::permissive())
231}
232
233/// Display high-level information about the Gateway
234// FIXME: deprecated >= 0.3.0
235// This endpoint exists only to remain backwards-compatible with the original POST endpoint
236#[instrument(target = LOG_GATEWAY, skip_all, err)]
237async fn handle_post_info(
238    Extension(gateway): Extension<Arc<Gateway>>,
239    Json(_payload): Json<InfoPayload>,
240) -> Result<impl IntoResponse, AdminGatewayError> {
241    let info = gateway.handle_get_info().await?;
242    Ok(Json(json!(info)))
243}
244
245/// Display high-level information about the Gateway
246#[instrument(target = LOG_GATEWAY, skip_all, err)]
247async fn info(
248    Extension(gateway): Extension<Arc<Gateway>>,
249) -> Result<impl IntoResponse, AdminGatewayError> {
250    let info = gateway.handle_get_info().await?;
251    Ok(Json(json!(info)))
252}
253
254/// Display high-level information about the Gateway config
255#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
256async fn configuration(
257    Extension(gateway): Extension<Arc<Gateway>>,
258    Json(payload): Json<ConfigPayload>,
259) -> Result<impl IntoResponse, AdminGatewayError> {
260    let gateway_fed_config = gateway
261        .handle_get_federation_config(payload.federation_id)
262        .await?;
263    Ok(Json(json!(gateway_fed_config)))
264}
265
266/// Generate deposit address
267#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
268async fn address(
269    Extension(gateway): Extension<Arc<Gateway>>,
270    Json(payload): Json<DepositAddressPayload>,
271) -> Result<impl IntoResponse, AdminGatewayError> {
272    let address = gateway.handle_address_msg(payload).await?;
273    Ok(Json(json!(address)))
274}
275
276/// Withdraw from a gateway federation.
277#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
278async fn withdraw(
279    Extension(gateway): Extension<Arc<Gateway>>,
280    Json(payload): Json<WithdrawPayload>,
281) -> Result<impl IntoResponse, AdminGatewayError> {
282    let txid = gateway.handle_withdraw_msg(payload).await?;
283    Ok(Json(json!(txid)))
284}
285
286#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
287async fn create_invoice_for_operator(
288    Extension(gateway): Extension<Arc<Gateway>>,
289    Json(payload): Json<CreateInvoiceForOperatorPayload>,
290) -> Result<impl IntoResponse, AdminGatewayError> {
291    let invoice = gateway
292        .handle_create_invoice_for_operator_msg(payload)
293        .await?;
294    Ok(Json(json!(invoice)))
295}
296
297#[instrument(target = LOG_GATEWAY, skip_all, err)]
298async fn pay_invoice_operator(
299    Extension(gateway): Extension<Arc<Gateway>>,
300    Json(payload): Json<PayInvoiceForOperatorPayload>,
301) -> Result<impl IntoResponse, AdminGatewayError> {
302    let preimage = gateway.handle_pay_invoice_for_operator_msg(payload).await?;
303    Ok(Json(json!(preimage.0.encode_hex::<String>())))
304}
305
306#[instrument(target = LOG_GATEWAY, skip_all, err)]
307async fn pay_invoice(
308    Extension(gateway): Extension<Arc<Gateway>>,
309    Json(payload): Json<fedimint_ln_client::pay::PayInvoicePayload>,
310) -> Result<impl IntoResponse, PublicGatewayError> {
311    let preimage = gateway.handle_pay_invoice_msg(payload).await?;
312    Ok(Json(json!(preimage.0.encode_hex::<String>())))
313}
314
315/// Connect a new federation
316#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
317async fn connect_fed(
318    Extension(gateway): Extension<Arc<Gateway>>,
319    Json(payload): Json<ConnectFedPayload>,
320) -> Result<impl IntoResponse, AdminGatewayError> {
321    let fed = gateway.handle_connect_federation(payload).await?;
322    Ok(Json(json!(fed)))
323}
324
325/// Leave a federation
326#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
327async fn leave_fed(
328    Extension(gateway): Extension<Arc<Gateway>>,
329    Json(payload): Json<LeaveFedPayload>,
330) -> Result<impl IntoResponse, AdminGatewayError> {
331    let fed = gateway.handle_leave_federation(payload).await?;
332    Ok(Json(json!(fed)))
333}
334
335/// Backup a gateway actor state
336#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
337async fn backup(
338    Extension(gateway): Extension<Arc<Gateway>>,
339    Json(payload): Json<BackupPayload>,
340) -> Result<impl IntoResponse, AdminGatewayError> {
341    gateway.handle_backup_msg(payload).await?;
342    Ok(Json(json!(())))
343}
344
345#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
346async fn set_fees(
347    Extension(gateway): Extension<Arc<Gateway>>,
348    Json(payload): Json<SetFeesPayload>,
349) -> Result<impl IntoResponse, AdminGatewayError> {
350    gateway.handle_set_fees_msg(payload).await?;
351    Ok(Json(json!(())))
352}
353
354#[instrument(target = LOG_GATEWAY, skip_all, err)]
355async fn get_ln_onchain_address(
356    Extension(gateway): Extension<Arc<Gateway>>,
357) -> Result<impl IntoResponse, AdminGatewayError> {
358    let address = gateway.handle_get_ln_onchain_address_msg().await?;
359    Ok(Json(json!(address.to_string())))
360}
361
362#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
363async fn open_channel(
364    Extension(gateway): Extension<Arc<Gateway>>,
365    Json(payload): Json<OpenChannelRequest>,
366) -> Result<impl IntoResponse, AdminGatewayError> {
367    let funding_txid = gateway.handle_open_channel_msg(payload).await?;
368    Ok(Json(json!(funding_txid)))
369}
370
371#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
372async fn close_channels_with_peer(
373    Extension(gateway): Extension<Arc<Gateway>>,
374    Json(payload): Json<CloseChannelsWithPeerRequest>,
375) -> Result<impl IntoResponse, AdminGatewayError> {
376    let response = gateway.handle_close_channels_with_peer_msg(payload).await?;
377    Ok(Json(json!(response)))
378}
379
380#[instrument(target = LOG_GATEWAY, skip_all, err)]
381async fn list_channels(
382    Extension(gateway): Extension<Arc<Gateway>>,
383) -> Result<impl IntoResponse, AdminGatewayError> {
384    let channels = gateway.handle_list_channels_msg().await?;
385    Ok(Json(json!(channels)))
386}
387
388#[instrument(target = LOG_GATEWAY, skip_all, err)]
389async fn send_onchain(
390    Extension(gateway): Extension<Arc<Gateway>>,
391    Json(payload): Json<SendOnchainRequest>,
392) -> Result<impl IntoResponse, AdminGatewayError> {
393    let txid = gateway.handle_send_onchain_msg(payload).await?;
394    Ok(Json(json!(txid)))
395}
396
397#[instrument(target = LOG_GATEWAY, skip_all, err)]
398async fn recheck_address(
399    Extension(gateway): Extension<Arc<Gateway>>,
400    Json(payload): Json<DepositAddressRecheckPayload>,
401) -> Result<impl IntoResponse, AdminGatewayError> {
402    gateway.handle_recheck_address_msg(payload).await?;
403    Ok(Json(json!({})))
404}
405
406#[instrument(target = LOG_GATEWAY, skip_all, err)]
407async fn get_balances(
408    Extension(gateway): Extension<Arc<Gateway>>,
409) -> Result<impl IntoResponse, AdminGatewayError> {
410    let balances = gateway.handle_get_balances_msg().await?;
411    Ok(Json(json!(balances)))
412}
413
414#[instrument(target = LOG_GATEWAY, skip_all, err)]
415async fn get_gateway_id(
416    Extension(gateway): Extension<Arc<Gateway>>,
417) -> Result<impl IntoResponse, PublicGatewayError> {
418    Ok(Json(json!(gateway.gateway_id)))
419}
420
421#[instrument(target = LOG_GATEWAY, skip_all, err)]
422async fn routing_info_v2(
423    Extension(gateway): Extension<Arc<Gateway>>,
424    Json(federation_id): Json<FederationId>,
425) -> Result<impl IntoResponse, PublicGatewayError> {
426    let routing_info = gateway.routing_info_v2(&federation_id).await?;
427    Ok(Json(json!(routing_info)))
428}
429
430#[instrument(target = LOG_GATEWAY, skip_all, err)]
431async fn pay_bolt11_invoice_v2(
432    Extension(gateway): Extension<Arc<Gateway>>,
433    Json(payload): Json<SendPaymentPayload>,
434) -> Result<impl IntoResponse, PublicGatewayError> {
435    let payment_result = gateway.send_payment_v2(payload).await?;
436    Ok(Json(json!(payment_result)))
437}
438
439#[instrument(target = LOG_GATEWAY, skip_all, err)]
440async fn create_bolt11_invoice_v2(
441    Extension(gateway): Extension<Arc<Gateway>>,
442    Json(payload): Json<CreateBolt11InvoicePayload>,
443) -> Result<impl IntoResponse, PublicGatewayError> {
444    let invoice = gateway.create_bolt11_invoice_v2(payload).await?;
445    Ok(Json(json!(invoice)))
446}
447
448async fn verify_bolt11_preimage_v2_get(
449    Extension(gateway): Extension<Arc<Gateway>>,
450    Path(payment_hash): Path<sha256::Hash>,
451    Query(query): Query<HashMap<String, String>>,
452) -> Result<Json<VerifyResponse>, LnurlError> {
453    let response = gateway
454        .verify_bolt11_preimage_v2(payment_hash, query.contains_key("wait"))
455        .await
456        .map_err(|e| LnurlError::internal(anyhow!(e)))?;
457
458    Ok(Json(response))
459}
460
461#[instrument(target = LOG_GATEWAY, skip_all, err)]
462async fn spend_ecash(
463    Extension(gateway): Extension<Arc<Gateway>>,
464    Json(payload): Json<SpendEcashPayload>,
465) -> Result<impl IntoResponse, AdminGatewayError> {
466    Ok(Json(json!(gateway.handle_spend_ecash_msg(payload).await?)))
467}
468
469#[instrument(target = LOG_GATEWAY, skip_all, err)]
470async fn receive_ecash(
471    Extension(gateway): Extension<Arc<Gateway>>,
472    Json(payload): Json<ReceiveEcashPayload>,
473) -> Result<impl IntoResponse, PublicGatewayError> {
474    Ok(Json(json!(
475        gateway.handle_receive_ecash_msg(payload).await?
476    )))
477}
478
479#[instrument(target = LOG_GATEWAY, skip_all, err)]
480async fn mnemonic(
481    Extension(gateway): Extension<Arc<Gateway>>,
482) -> Result<impl IntoResponse, AdminGatewayError> {
483    let words = gateway.handle_mnemonic_msg().await?;
484    Ok(Json(json!(words)))
485}
486
487#[instrument(target = LOG_GATEWAY, skip_all, err)]
488async fn stop(
489    Extension(task_group): Extension<TaskGroup>,
490    Extension(gateway): Extension<Arc<Gateway>>,
491) -> Result<impl IntoResponse, AdminGatewayError> {
492    gateway.handle_shutdown_msg(task_group).await?;
493    Ok(Json(json!(())))
494}
495
496#[instrument(target = LOG_GATEWAY, skip_all, err)]
497async fn payment_log(
498    Extension(gateway): Extension<Arc<Gateway>>,
499    Json(payload): Json<PaymentLogPayload>,
500) -> Result<impl IntoResponse, AdminGatewayError> {
501    let payment_log = gateway.handle_payment_log_msg(payload).await?;
502    Ok(Json(json!(payment_log)))
503}
504
505#[instrument(target = LOG_GATEWAY, skip_all, err)]
506async fn payment_summary(
507    Extension(gateway): Extension<Arc<Gateway>>,
508    Json(payload): Json<PaymentSummaryPayload>,
509) -> Result<impl IntoResponse, AdminGatewayError> {
510    let payment_summary = gateway.handle_payment_summary_msg(payload).await?;
511    Ok(Json(json!(payment_summary)))
512}
513
514#[instrument(target = LOG_GATEWAY, skip_all, err)]
515async fn get_invoice(
516    Extension(gateway): Extension<Arc<Gateway>>,
517    Json(payload): Json<GetInvoiceRequest>,
518) -> Result<impl IntoResponse, AdminGatewayError> {
519    let invoice = gateway.handle_get_invoice_msg(payload).await?;
520    Ok(Json(json!(invoice)))
521}
522
523#[instrument(target = LOG_GATEWAY, skip_all, err)]
524async fn list_transactions(
525    Extension(gateway): Extension<Arc<Gateway>>,
526    Json(payload): Json<ListTransactionsPayload>,
527) -> Result<impl IntoResponse, AdminGatewayError> {
528    let transactions = gateway.handle_list_transactions_msg(payload).await?;
529    Ok(Json(json!(transactions)))
530}
531
532#[instrument(target = LOG_GATEWAY, skip_all, err)]
533async fn create_offer_for_operator(
534    Extension(gateway): Extension<Arc<Gateway>>,
535    Json(payload): Json<CreateOfferPayload>,
536) -> Result<impl IntoResponse, AdminGatewayError> {
537    let offer = gateway
538        .handle_create_offer_for_operator_msg(payload)
539        .await?;
540    Ok(Json(json!(offer)))
541}
542
543#[instrument(target = LOG_GATEWAY, skip_all, err)]
544async fn pay_offer_operator(
545    Extension(gateway): Extension<Arc<Gateway>>,
546    Json(payload): Json<PayOfferPayload>,
547) -> Result<impl IntoResponse, AdminGatewayError> {
548    let response = gateway.handle_pay_offer_for_operator_msg(payload).await?;
549    Ok(Json(json!(response)))
550}