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    public_routes = public_routes.merge(lnv1_routes());
173    public_routes = public_routes.merge(lnv2_routes());
174
175    // Authenticated routes used for gateway administration
176    let authenticated_routes = Router::new()
177        .route(ADDRESS_ENDPOINT, post(address))
178        .route(WITHDRAW_ENDPOINT, post(withdraw))
179        .route(CONNECT_FED_ENDPOINT, post(connect_fed))
180        .route(LEAVE_FED_ENDPOINT, post(leave_fed))
181        .route(BACKUP_ENDPOINT, post(backup))
182        .route(
183            CREATE_BOLT11_INVOICE_FOR_OPERATOR_ENDPOINT,
184            post(create_invoice_for_operator),
185        )
186        .route(
187            CREATE_BOLT12_OFFER_FOR_OPERATOR_ENDPOINT,
188            post(create_offer_for_operator),
189        )
190        .route(
191            PAY_INVOICE_FOR_OPERATOR_ENDPOINT,
192            post(pay_invoice_operator),
193        )
194        .route(PAY_OFFER_FOR_OPERATOR_ENDPOINT, post(pay_offer_operator))
195        .route(GET_INVOICE_ENDPOINT, post(get_invoice))
196        .route(GET_LN_ONCHAIN_ADDRESS_ENDPOINT, get(get_ln_onchain_address))
197        .route(OPEN_CHANNEL_ENDPOINT, post(open_channel))
198        .route(
199            CLOSE_CHANNELS_WITH_PEER_ENDPOINT,
200            post(close_channels_with_peer),
201        )
202        .route(LIST_CHANNELS_ENDPOINT, get(list_channels))
203        .route(LIST_TRANSACTIONS_ENDPOINT, post(list_transactions))
204        .route(SEND_ONCHAIN_ENDPOINT, post(send_onchain))
205        .route(ADDRESS_RECHECK_ENDPOINT, post(recheck_address))
206        .route(GET_BALANCES_ENDPOINT, get(get_balances))
207        .route(SPEND_ECASH_ENDPOINT, post(spend_ecash))
208        .route(MNEMONIC_ENDPOINT, get(mnemonic))
209        .route(STOP_ENDPOINT, get(stop))
210        .route(PAYMENT_LOG_ENDPOINT, post(payment_log))
211        .route(PAYMENT_SUMMARY_ENDPOINT, post(payment_summary))
212        .route(SET_FEES_ENDPOINT, post(set_fees))
213        .route(CONFIGURATION_ENDPOINT, post(configuration))
214        // FIXME: deprecated >= 0.3.0
215        .route(GATEWAY_INFO_POST_ENDPOINT, post(handle_post_info))
216        .route(GATEWAY_INFO_ENDPOINT, get(info))
217        .layer(middleware::from_fn(auth_middleware));
218
219    Router::new()
220        .merge(public_routes)
221        .merge(authenticated_routes)
222        .layer(Extension(gateway))
223        .layer(Extension(task_group))
224        .layer(CorsLayer::permissive())
225}
226
227/// Display high-level information about the Gateway
228// FIXME: deprecated >= 0.3.0
229// This endpoint exists only to remain backwards-compatible with the original POST endpoint
230#[instrument(target = LOG_GATEWAY, skip_all, err)]
231async fn handle_post_info(
232    Extension(gateway): Extension<Arc<Gateway>>,
233    Json(_payload): Json<InfoPayload>,
234) -> Result<impl IntoResponse, AdminGatewayError> {
235    let info = gateway.handle_get_info().await?;
236    Ok(Json(json!(info)))
237}
238
239/// Display high-level information about the Gateway
240#[instrument(target = LOG_GATEWAY, skip_all, err)]
241async fn info(
242    Extension(gateway): Extension<Arc<Gateway>>,
243) -> Result<impl IntoResponse, AdminGatewayError> {
244    let info = gateway.handle_get_info().await?;
245    Ok(Json(json!(info)))
246}
247
248/// Display high-level information about the Gateway config
249#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
250async fn configuration(
251    Extension(gateway): Extension<Arc<Gateway>>,
252    Json(payload): Json<ConfigPayload>,
253) -> Result<impl IntoResponse, AdminGatewayError> {
254    let gateway_fed_config = gateway
255        .handle_get_federation_config(payload.federation_id)
256        .await?;
257    Ok(Json(json!(gateway_fed_config)))
258}
259
260/// Generate deposit address
261#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
262async fn address(
263    Extension(gateway): Extension<Arc<Gateway>>,
264    Json(payload): Json<DepositAddressPayload>,
265) -> Result<impl IntoResponse, AdminGatewayError> {
266    let address = gateway.handle_address_msg(payload).await?;
267    Ok(Json(json!(address)))
268}
269
270/// Withdraw from a gateway federation.
271#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
272async fn withdraw(
273    Extension(gateway): Extension<Arc<Gateway>>,
274    Json(payload): Json<WithdrawPayload>,
275) -> Result<impl IntoResponse, AdminGatewayError> {
276    let txid = gateway.handle_withdraw_msg(payload).await?;
277    Ok(Json(json!(txid)))
278}
279
280#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
281async fn create_invoice_for_operator(
282    Extension(gateway): Extension<Arc<Gateway>>,
283    Json(payload): Json<CreateInvoiceForOperatorPayload>,
284) -> Result<impl IntoResponse, AdminGatewayError> {
285    let invoice = gateway
286        .handle_create_invoice_for_operator_msg(payload)
287        .await?;
288    Ok(Json(json!(invoice)))
289}
290
291#[instrument(target = LOG_GATEWAY, skip_all, err)]
292async fn pay_invoice_operator(
293    Extension(gateway): Extension<Arc<Gateway>>,
294    Json(payload): Json<PayInvoiceForOperatorPayload>,
295) -> Result<impl IntoResponse, AdminGatewayError> {
296    let preimage = gateway.handle_pay_invoice_for_operator_msg(payload).await?;
297    Ok(Json(json!(preimage.0.encode_hex::<String>())))
298}
299
300#[instrument(target = LOG_GATEWAY, skip_all, err)]
301async fn pay_invoice(
302    Extension(gateway): Extension<Arc<Gateway>>,
303    Json(payload): Json<fedimint_ln_client::pay::PayInvoicePayload>,
304) -> Result<impl IntoResponse, PublicGatewayError> {
305    let preimage = gateway.handle_pay_invoice_msg(payload).await?;
306    Ok(Json(json!(preimage.0.encode_hex::<String>())))
307}
308
309/// Connect a new federation
310#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
311async fn connect_fed(
312    Extension(gateway): Extension<Arc<Gateway>>,
313    Json(payload): Json<ConnectFedPayload>,
314) -> Result<impl IntoResponse, AdminGatewayError> {
315    let fed = gateway.handle_connect_federation(payload).await?;
316    Ok(Json(json!(fed)))
317}
318
319/// Leave a federation
320#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
321async fn leave_fed(
322    Extension(gateway): Extension<Arc<Gateway>>,
323    Json(payload): Json<LeaveFedPayload>,
324) -> Result<impl IntoResponse, AdminGatewayError> {
325    let fed = gateway.handle_leave_federation(payload).await?;
326    Ok(Json(json!(fed)))
327}
328
329/// Backup a gateway actor state
330#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
331async fn backup(
332    Extension(gateway): Extension<Arc<Gateway>>,
333    Json(payload): Json<BackupPayload>,
334) -> Result<impl IntoResponse, AdminGatewayError> {
335    gateway.handle_backup_msg(payload).await?;
336    Ok(Json(json!(())))
337}
338
339#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
340async fn set_fees(
341    Extension(gateway): Extension<Arc<Gateway>>,
342    Json(payload): Json<SetFeesPayload>,
343) -> Result<impl IntoResponse, AdminGatewayError> {
344    gateway.handle_set_fees_msg(payload).await?;
345    Ok(Json(json!(())))
346}
347
348#[instrument(target = LOG_GATEWAY, skip_all, err)]
349async fn get_ln_onchain_address(
350    Extension(gateway): Extension<Arc<Gateway>>,
351) -> Result<impl IntoResponse, AdminGatewayError> {
352    let address = gateway.handle_get_ln_onchain_address_msg().await?;
353    Ok(Json(json!(address.to_string())))
354}
355
356#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
357async fn open_channel(
358    Extension(gateway): Extension<Arc<Gateway>>,
359    Json(payload): Json<OpenChannelRequest>,
360) -> Result<impl IntoResponse, AdminGatewayError> {
361    let funding_txid = gateway.handle_open_channel_msg(payload).await?;
362    Ok(Json(json!(funding_txid)))
363}
364
365#[instrument(target = LOG_GATEWAY, skip_all, err, fields(?payload))]
366async fn close_channels_with_peer(
367    Extension(gateway): Extension<Arc<Gateway>>,
368    Json(payload): Json<CloseChannelsWithPeerRequest>,
369) -> Result<impl IntoResponse, AdminGatewayError> {
370    let response = gateway.handle_close_channels_with_peer_msg(payload).await?;
371    Ok(Json(json!(response)))
372}
373
374#[instrument(target = LOG_GATEWAY, skip_all, err)]
375async fn list_channels(
376    Extension(gateway): Extension<Arc<Gateway>>,
377) -> Result<impl IntoResponse, AdminGatewayError> {
378    let channels = gateway.handle_list_channels_msg().await?;
379    Ok(Json(json!(channels)))
380}
381
382#[instrument(target = LOG_GATEWAY, skip_all, err)]
383async fn send_onchain(
384    Extension(gateway): Extension<Arc<Gateway>>,
385    Json(payload): Json<SendOnchainRequest>,
386) -> Result<impl IntoResponse, AdminGatewayError> {
387    let txid = gateway.handle_send_onchain_msg(payload).await?;
388    Ok(Json(json!(txid)))
389}
390
391#[instrument(target = LOG_GATEWAY, skip_all, err)]
392async fn recheck_address(
393    Extension(gateway): Extension<Arc<Gateway>>,
394    Json(payload): Json<DepositAddressRecheckPayload>,
395) -> Result<impl IntoResponse, AdminGatewayError> {
396    gateway.handle_recheck_address_msg(payload).await?;
397    Ok(Json(json!({})))
398}
399
400#[instrument(target = LOG_GATEWAY, skip_all, err)]
401async fn get_balances(
402    Extension(gateway): Extension<Arc<Gateway>>,
403) -> Result<impl IntoResponse, AdminGatewayError> {
404    let balances = gateway.handle_get_balances_msg().await?;
405    Ok(Json(json!(balances)))
406}
407
408#[instrument(target = LOG_GATEWAY, skip_all, err)]
409async fn get_gateway_id(
410    Extension(gateway): Extension<Arc<Gateway>>,
411) -> Result<impl IntoResponse, PublicGatewayError> {
412    Ok(Json(json!(gateway.gateway_id)))
413}
414
415#[instrument(target = LOG_GATEWAY, skip_all, err)]
416async fn routing_info_v2(
417    Extension(gateway): Extension<Arc<Gateway>>,
418    Json(federation_id): Json<FederationId>,
419) -> Result<impl IntoResponse, PublicGatewayError> {
420    let routing_info = gateway.routing_info_v2(&federation_id).await?;
421    Ok(Json(json!(routing_info)))
422}
423
424#[instrument(target = LOG_GATEWAY, skip_all, err)]
425async fn pay_bolt11_invoice_v2(
426    Extension(gateway): Extension<Arc<Gateway>>,
427    Json(payload): Json<SendPaymentPayload>,
428) -> Result<impl IntoResponse, PublicGatewayError> {
429    let payment_result = gateway.send_payment_v2(payload).await?;
430    Ok(Json(json!(payment_result)))
431}
432
433#[instrument(target = LOG_GATEWAY, skip_all, err)]
434async fn create_bolt11_invoice_v2(
435    Extension(gateway): Extension<Arc<Gateway>>,
436    Json(payload): Json<CreateBolt11InvoicePayload>,
437) -> Result<impl IntoResponse, PublicGatewayError> {
438    let invoice = gateway.create_bolt11_invoice_v2(payload).await?;
439    Ok(Json(json!(invoice)))
440}
441
442async fn verify_bolt11_preimage_v2_get(
443    Extension(gateway): Extension<Arc<Gateway>>,
444    Path(payment_hash): Path<sha256::Hash>,
445    Query(query): Query<HashMap<String, String>>,
446) -> Result<Json<VerifyResponse>, LnurlError> {
447    let response = gateway
448        .verify_bolt11_preimage_v2(payment_hash, query.contains_key("wait"))
449        .await
450        .map_err(|e| LnurlError::internal(anyhow!(e)))?;
451
452    Ok(Json(response))
453}
454
455#[instrument(target = LOG_GATEWAY, skip_all, err)]
456async fn spend_ecash(
457    Extension(gateway): Extension<Arc<Gateway>>,
458    Json(payload): Json<SpendEcashPayload>,
459) -> Result<impl IntoResponse, AdminGatewayError> {
460    Ok(Json(json!(gateway.handle_spend_ecash_msg(payload).await?)))
461}
462
463#[instrument(target = LOG_GATEWAY, skip_all, err)]
464async fn receive_ecash(
465    Extension(gateway): Extension<Arc<Gateway>>,
466    Json(payload): Json<ReceiveEcashPayload>,
467) -> Result<impl IntoResponse, PublicGatewayError> {
468    Ok(Json(json!(
469        gateway.handle_receive_ecash_msg(payload).await?
470    )))
471}
472
473#[instrument(target = LOG_GATEWAY, skip_all, err)]
474async fn mnemonic(
475    Extension(gateway): Extension<Arc<Gateway>>,
476) -> Result<impl IntoResponse, AdminGatewayError> {
477    let words = gateway.handle_mnemonic_msg().await?;
478    Ok(Json(json!(words)))
479}
480
481#[instrument(target = LOG_GATEWAY, skip_all, err)]
482async fn stop(
483    Extension(task_group): Extension<TaskGroup>,
484    Extension(gateway): Extension<Arc<Gateway>>,
485) -> Result<impl IntoResponse, AdminGatewayError> {
486    gateway.handle_shutdown_msg(task_group).await?;
487    Ok(Json(json!(())))
488}
489
490#[instrument(target = LOG_GATEWAY, skip_all, err)]
491async fn payment_log(
492    Extension(gateway): Extension<Arc<Gateway>>,
493    Json(payload): Json<PaymentLogPayload>,
494) -> Result<impl IntoResponse, AdminGatewayError> {
495    let payment_log = gateway.handle_payment_log_msg(payload).await?;
496    Ok(Json(json!(payment_log)))
497}
498
499#[instrument(target = LOG_GATEWAY, skip_all, err)]
500async fn payment_summary(
501    Extension(gateway): Extension<Arc<Gateway>>,
502    Json(payload): Json<PaymentSummaryPayload>,
503) -> Result<impl IntoResponse, AdminGatewayError> {
504    let payment_summary = gateway.handle_payment_summary_msg(payload).await?;
505    Ok(Json(json!(payment_summary)))
506}
507
508#[instrument(target = LOG_GATEWAY, skip_all, err)]
509async fn get_invoice(
510    Extension(gateway): Extension<Arc<Gateway>>,
511    Json(payload): Json<GetInvoiceRequest>,
512) -> Result<impl IntoResponse, AdminGatewayError> {
513    let invoice = gateway.handle_get_invoice_msg(payload).await?;
514    Ok(Json(json!(invoice)))
515}
516
517#[instrument(target = LOG_GATEWAY, skip_all, err)]
518async fn list_transactions(
519    Extension(gateway): Extension<Arc<Gateway>>,
520    Json(payload): Json<ListTransactionsPayload>,
521) -> Result<impl IntoResponse, AdminGatewayError> {
522    let transactions = gateway.handle_list_transactions_msg(payload).await?;
523    Ok(Json(json!(transactions)))
524}
525
526#[instrument(target = LOG_GATEWAY, skip_all, err)]
527async fn create_offer_for_operator(
528    Extension(gateway): Extension<Arc<Gateway>>,
529    Json(payload): Json<CreateOfferPayload>,
530) -> Result<impl IntoResponse, AdminGatewayError> {
531    let offer = gateway
532        .handle_create_offer_for_operator_msg(payload)
533        .await?;
534    Ok(Json(json!(offer)))
535}
536
537#[instrument(target = LOG_GATEWAY, skip_all, err)]
538async fn pay_offer_operator(
539    Extension(gateway): Extension<Arc<Gateway>>,
540    Json(payload): Json<PayOfferPayload>,
541) -> Result<impl IntoResponse, AdminGatewayError> {
542    let response = gateway.handle_pay_offer_for_operator_msg(payload).await?;
543    Ok(Json(json!(response)))
544}