diff --git a/fedimint-clientd/src/router/handlers/fedimint/admin/join.rs b/fedimint-clientd/src/router/handlers/fedimint/admin/join.rs index 5d248dd..c47376a 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/admin/join.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/admin/join.rs @@ -23,6 +23,7 @@ pub struct JoinRequest { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct JoinResponse { + pub this_federation_id: FederationId, pub federation_ids: Vec, } @@ -40,13 +41,16 @@ async fn _join(mut multimint: MultiMint, req: JoinRequest) -> Result>(); - Ok(JoinResponse { federation_ids }) + Ok(JoinResponse { + this_federation_id, + federation_ids, + }) } pub async fn handle_ws(state: AppState, v: Value) -> Result { diff --git a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs index 92df8be..2060038 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs @@ -2,6 +2,7 @@ use anyhow::anyhow; use axum::extract::State; use axum::http::StatusCode; use axum::Json; +use bitcoin::secp256k1::PublicKey; use fedimint_client::ClientHandleArc; use fedimint_core::config::FederationId; use fedimint_core::core::OperationId; @@ -21,6 +22,7 @@ pub struct LnInvoiceRequest { pub amount_msat: Amount, pub description: String, pub expiry_time: Option, + pub gateway_id: PublicKey, pub federation_id: FederationId, } @@ -36,21 +38,11 @@ async fn _invoice( req: LnInvoiceRequest, ) -> Result { let lightning_module = client.get_first_module::(); - let gateway_id = match lightning_module.list_gateways().await.first() { - Some(gateway_announcement) => gateway_announcement.info.gateway_id, - None => { - error!("No gateways available"); - return Err(AppError::new( - StatusCode::INTERNAL_SERVER_ERROR, - anyhow!("No gateways available"), - )); - } - }; let gateway = lightning_module - .select_gateway(&gateway_id) + .select_gateway(&req.gateway_id) .await .ok_or_else(|| { - error!("Failed to select gateway"); + error!("Failed to select gateway: {}", req.gateway_id); AppError::new( StatusCode::INTERNAL_SERVER_ERROR, anyhow!("Failed to select gateway"), diff --git a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey.rs b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey.rs index 0dac00c..eaa7dc3 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey.rs @@ -23,6 +23,7 @@ pub struct LnInvoiceExternalPubkeyRequest { pub description: String, pub expiry_time: Option, pub external_pubkey: PublicKey, + pub gateway_id: PublicKey, pub federation_id: FederationId, } @@ -38,21 +39,11 @@ async fn _invoice( req: LnInvoiceExternalPubkeyRequest, ) -> Result { let lightning_module = client.get_first_module::(); - let gateway_id = match lightning_module.list_gateways().await.first() { - Some(gateway_announcement) => gateway_announcement.info.gateway_id, - None => { - error!("No gateways available"); - return Err(AppError::new( - StatusCode::INTERNAL_SERVER_ERROR, - anyhow!("No gateways available"), - )); - } - }; let gateway = lightning_module - .select_gateway(&gateway_id) + .select_gateway(&req.gateway_id) .await .ok_or_else(|| { - error!("Failed to select gateway"); + error!("Failed to select gateway: {}", req.gateway_id); AppError::new( StatusCode::INTERNAL_SERVER_ERROR, anyhow!("Failed to select gateway"), diff --git a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey_tweaked.rs b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey_tweaked.rs index 6fad6c4..52f2d24 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey_tweaked.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice_external_pubkey_tweaked.rs @@ -24,6 +24,7 @@ pub struct LnInvoiceExternalPubkeyTweakedRequest { pub expiry_time: Option, pub external_pubkey: PublicKey, pub tweak: u64, + pub gateway_id: PublicKey, pub federation_id: FederationId, } @@ -39,21 +40,11 @@ async fn _invoice( req: LnInvoiceExternalPubkeyTweakedRequest, ) -> Result { let lightning_module = client.get_first_module::(); - let gateway_id = match lightning_module.list_gateways().await.first() { - Some(gateway_announcement) => gateway_announcement.info.gateway_id, - None => { - error!("No gateways available"); - return Err(AppError::new( - StatusCode::INTERNAL_SERVER_ERROR, - anyhow!("No gateways available"), - )); - } - }; let gateway = lightning_module - .select_gateway(&gateway_id) + .select_gateway(&req.gateway_id) .await .ok_or_else(|| { - error!("Failed to select gateway"); + error!("Failed to select gateway: {}", req.gateway_id); AppError::new( StatusCode::INTERNAL_SERVER_ERROR, anyhow!("Failed to select gateway"), diff --git a/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs b/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs index 7bfc56a..bc2e6d8 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs @@ -2,6 +2,7 @@ use anyhow::anyhow; use axum::extract::State; use axum::http::StatusCode; use axum::Json; +use bitcoin::secp256k1::PublicKey; use fedimint_client::ClientHandleArc; use fedimint_core::config::FederationId; use fedimint_core::core::OperationId; @@ -21,6 +22,7 @@ pub struct LnPayRequest { pub payment_info: String, pub amount_msat: Option, pub lnurl_comment: Option, + pub gateway_id: PublicKey, pub federation_id: FederationId, } @@ -37,21 +39,11 @@ async fn _pay(client: ClientHandleArc, req: LnPayRequest) -> Result(); - let gateway_id = match lightning_module.list_gateways().await.first() { - Some(gateway_announcement) => gateway_announcement.info.gateway_id, - None => { - error!("No gateways available"); - return Err(AppError::new( - StatusCode::INTERNAL_SERVER_ERROR, - anyhow!("No gateways available"), - )); - } - }; let gateway = lightning_module - .select_gateway(&gateway_id) + .select_gateway(&req.gateway_id) .await .ok_or_else(|| { - error!("Failed to select gateway"); + error!("Failed to select gateway: {}", req.gateway_id); AppError::new( StatusCode::INTERNAL_SERVER_ERROR, anyhow!("Failed to select gateway"), diff --git a/wrappers/fedimint-ts/FedimintClient.ts b/wrappers/fedimint-ts/FedimintClient.ts index bc3fedc..922c6d6 100644 --- a/wrappers/fedimint-ts/FedimintClient.ts +++ b/wrappers/fedimint-ts/FedimintClient.ts @@ -40,6 +40,7 @@ import type { NotesJson, MintEncodeNotesResponse, MintDecodeNotesResponse, + JoinResponse, } from "./types"; type FedimintResponse = Promise; @@ -48,11 +49,13 @@ class FedimintClientBuilder { private baseUrl: string; private password: string; private activeFederationId: string; + private activeGatewayId: string; constructor() { this.baseUrl = ""; this.password = ""; this.activeFederationId = ""; + this.activeGatewayId = ""; } setBaseUrl(baseUrl: string): FedimintClientBuilder { @@ -73,19 +76,22 @@ class FedimintClientBuilder { return this; } + setActiveGatewayId(gatewayId: string): FedimintClientBuilder { + this.activeGatewayId = gatewayId; + + return this; + } + build(): FedimintClient { - if ( - this.baseUrl === "" || - this.password === "" || - this.activeFederationId === "" - ) { - throw new Error("baseUrl, password, and activeFederationId must be set"); + if (this.baseUrl === "" || this.password === "") { + throw new Error("baseUrl, and password must be set"); } const client = new FedimintClient( this.baseUrl, this.password, - this.activeFederationId + this.activeFederationId, + this.activeGatewayId ); return client; @@ -96,19 +102,61 @@ class FedimintClient { private baseUrl: string; private password: string; private activeFederationId: string; - - constructor(baseUrl: string, password: string, activeFederationId: string) { + private activeGatewayId: string; + + constructor( + baseUrl: string, + password: string, + activeFederationId: string, + activeGatewayId: string = "" + ) { this.baseUrl = baseUrl + "/v2"; this.password = password; this.activeFederationId = activeFederationId; + this.activeGatewayId = activeGatewayId; + console.log( + "Fedimint Client initialized, must set activeGatewayId after intitalization to use lightning module methods or manually pass in gateways" + ); } getActiveFederationId(): string { return this.activeFederationId; } - setActiveFederationId(federationId: string) { + setActiveFederationId(federationId: string, useDefaultGateway: boolean) { this.activeFederationId = federationId; + console.log("Changed active federation id to: ", federationId); + + if (useDefaultGateway) { + this.ln.listGateways().then((gateways) => { + this.activeGatewayId = gateways[0].info.gateway_id; + }); + } else { + console.log( + "Clearing active gateway id, must be set manually on lightning calls or setDefaultGatewayId to true" + ); + this.activeGatewayId = ""; + } + } + + getActiveGatewayId(): string { + return this.activeGatewayId; + } + + setActiveGatewayId(gatewayId: string) { + this.activeGatewayId = gatewayId; + } + + async useDefaultGateway() { + // hits list_gateways and sets activeGatewayId to the first gateway + try { + const gateways = await this.ln.listGateways(); + console.log("Gateways: ", gateways); + this.activeGatewayId = gateways[0].info.gateway_id; + console.log("Set active gateway id to: ", this.activeGatewayId); + } catch (error) { + console.error("Error setting default gateway id: ", error); + } } /** @@ -158,14 +206,11 @@ class FedimintClient { return (await res.json()) as T; } - // Adjust postWithId to not require federationId as a mandatory parameter - // since ensureactiveFederationId will ensure it's set. - private async postWithId( + private async postWithFederationId( endpoint: string, body: any, federationId?: string ): FedimintResponse { - // Note: No need to call ensureactiveFederationId here since post already does. const effectiveFederationId = federationId || this.activeFederationId; return this.post(endpoint, { @@ -174,6 +219,33 @@ class FedimintClient { }); } + private async postWithGatewayIdAndFederationId( + endpoint: string, + body: any, + gatewayId?: string, + federationId?: string + ): FedimintResponse { + try { + const effectiveGatewayId = gatewayId || this.activeGatewayId; + const effectiveFederationId = federationId || this.activeFederationId; + + if (effectiveFederationId === "" || effectiveGatewayId === "") { + throw new Error( + "Must set active federation and gateway id before posting with them" + ); + } + + return this.post(endpoint, { + ...body, + federationId: effectiveFederationId, + gatewayId: effectiveGatewayId, + }); + } catch (error) { + console.error("Error posting with federation and gateway id: ", error); + throw error; + } + } + /** * Uploads the encrypted snapshot of mint notest to the federation */ @@ -181,7 +253,11 @@ class FedimintClient { metadata: BackupRequest, federationId?: string ): FedimintResponse { - await this.postWithId("/admin/backup", metadata, federationId); + await this.postWithFederationId( + "/admin/backup", + metadata, + federationId + ); } /** @@ -226,11 +302,19 @@ class FedimintClient { */ public async join( inviteCode: string, + setActiveFederationId: boolean, + useDefaultGateway: boolean, useManualSecret: boolean = false - ): FedimintResponse { + ): FedimintResponse { const request: JoinRequest = { inviteCode, useManualSecret }; - return await this.post("/admin/join", request); + const response = await this.post("/admin/join", request); + + if (setActiveFederationId) { + this.setActiveFederationId(response.thisFederationId, useDefaultGateway); + } + + return response; } /** @@ -242,7 +326,7 @@ class FedimintClient { ): FedimintResponse { const request: ListOperationsRequest = { limit }; - return await this.postWithId( + return await this.postWithFederationId( "/admin/list-operations", request, federationId @@ -260,13 +344,15 @@ class FedimintClient { amountMsat: number, description: string, expiryTime?: number, + gatewayId?: string, federationId?: string ): FedimintResponse => { const request: LnInvoiceRequest = { amountMsat, description, expiryTime }; - return await this.postWithId( + return await this.postWithGatewayIdAndFederationId( "/ln/invoice", request, + gatewayId, federationId ); }, @@ -280,6 +366,7 @@ class FedimintClient { amountMsat: number, description: string, expiryTime?: number, + gatewayId?: string, federationId?: string ): FedimintResponse => { const request: LnInvoiceExternalPubkeyRequest = { @@ -289,9 +376,10 @@ class FedimintClient { expiryTime, }; - return await this.postWithId( + return await this.postWithGatewayIdAndFederationId( "/ln/invoice-external-pubkey", request, + gatewayId, federationId ); }, @@ -307,6 +395,7 @@ class FedimintClient { amountMsat: number, description: string, expiryTime?: number, + gatewayId?: string, federationId?: string ): FedimintResponse => { const request: LnInvoiceExternalPubkeyTweakedRequest = { @@ -317,9 +406,10 @@ class FedimintClient { expiryTime, }; - return await this.postWithId( + return await this.postWithGatewayIdAndFederationId( "/ln/invoice-external-pubkey-tweaked", request, + gatewayId, federationId ); }, @@ -333,7 +423,7 @@ class FedimintClient { ): FedimintResponse => { const request: LnClaimPubkeyReceiveRequest = { privateKey }; - return await this.postWithId( + return await this.postWithFederationId( "/ln/claim-external-receive", request, federationId @@ -354,7 +444,7 @@ class FedimintClient { tweaks, }; - return await this.postWithId( + return await this.postWithFederationId( "/ln/claim-external-receive-tweaked", request, federationId @@ -369,7 +459,7 @@ class FedimintClient { ): FedimintResponse => { const request: LnAwaitInvoiceRequest = { operationId }; - return await this.postWithId( + return await this.postWithFederationId( "/ln/await-invoice", request, federationId @@ -383,6 +473,7 @@ class FedimintClient { paymentInfo: string, amountMsat?: number, lnurlComment?: string, + gatewayId?: string, federationId?: string ): FedimintResponse => { const request: LnPayRequest = { @@ -391,9 +482,10 @@ class FedimintClient { lnurlComment, }; - return await this.postWithId( + return await this.postWithGatewayIdAndFederationId( "/ln/pay", request, + gatewayId, federationId ); }, @@ -402,7 +494,7 @@ class FedimintClient { * Outputs a list of registered lighting lightning gateways */ listGateways: async (): FedimintResponse => - await this.postWithId("/ln/list-gateways", {}), + await this.postWithFederationId("/ln/list-gateways", {}), }; /** @@ -451,7 +543,7 @@ class FedimintClient { ): FedimintResponse => { const request: MintReissueRequest = { notes }; - return await this.postWithId( + return await this.postWithFederationId( "/mint/reissue", request, federationId @@ -475,7 +567,7 @@ class FedimintClient { includeInvite, }; - return await this.postWithId( + return await this.postWithFederationId( "/mint/spend", request, federationId @@ -491,7 +583,7 @@ class FedimintClient { ): FedimintResponse => { const request: MintValidateRequest = { notes }; - return await this.postWithId( + return await this.postWithFederationId( "/mint/validate", request, federationId @@ -532,7 +624,7 @@ class FedimintClient { ): FedimintResponse => { const request: OnchainDepositAddressRequest = { timeout }; - return await this.postWithId( + return await this.postWithFederationId( "/wallet/deposit-address", request, federationId @@ -548,7 +640,7 @@ class FedimintClient { ): FedimintResponse => { const request: OnchainAwaitDepositRequest = { operationId }; - return await this.postWithId( + return await this.postWithFederationId( "/wallet/await-deposit", request, federationId @@ -565,7 +657,7 @@ class FedimintClient { ): FedimintResponse => { const request: OnchainWithdrawRequest = { address, amountSat }; - return await this.postWithId( + return await this.postWithFederationId( "/wallet/withdraw", request, federationId diff --git a/wrappers/fedimint-ts/test.ts b/wrappers/fedimint-ts/test.ts index d1b7c53..9ae021f 100644 --- a/wrappers/fedimint-ts/test.ts +++ b/wrappers/fedimint-ts/test.ts @@ -43,13 +43,18 @@ async function buildTestClient() { "15db8cb4f1ec8e484d73b889372bec94812580f929e8148b7437d359af422cd3" // Fedi Alpha Mutinynet ); - return await builder.build(); + const client = await builder.build(); + + await client.useDefaultGateway(); + + console.log("Default gateway id: ", client.getActiveGatewayId()); + + return client; } // Runs through all of the methods in the Fedimint Client async function main() { const fedimintClient = await buildTestClient(); - const keyPair = newKeyPair(); console.log("Generated key pair: ", keyPair); @@ -75,7 +80,7 @@ async function main() { process.env.INVITE_CODE || "fed11qgqrgvnhwden5te0v9k8q6rp9ekh2arfdeukuet595cr2ttpd3jhq6rzve6zuer9wchxvetyd938gcewvdhk6tcqqysptkuvknc7erjgf4em3zfh90kffqf9srujn6q53d6r056e4apze5cw27h75"; logMethod("/v2/admin/join"); - data = await fedimintClient.join(inviteCode); + data = await fedimintClient.join(inviteCode, true, true); logInputAndOutput({ inviteCode }, data); // `/v2/admin/list-operations` logMethod("/v2/admin/list-operations"); diff --git a/wrappers/fedimint-ts/types.ts b/wrappers/fedimint-ts/types.ts index 88ada07..dea6180 100644 --- a/wrappers/fedimint-ts/types.ts +++ b/wrappers/fedimint-ts/types.ts @@ -32,6 +32,11 @@ interface JoinRequest { useManualSecret: boolean; } +interface JoinResponse { + thisFederationId: string; + federationIds: string[]; +} + interface BackupRequest { metadata: { [key: string]: string }; } @@ -165,9 +170,33 @@ interface LnAwaitPayRequest { operationId: string; } +interface GatewayInfo { + api: string; + fees: GatewayFees; + gateway_id: string; + gateway_redeem_key: string; + lightning_alias: string; + mint_channel_id: number; + node_pub_key: string; + route_hints: any[]; // Adjust the type according to the actual structure of route hints + supports_private_payments: boolean; +} + +interface GatewayFees { + base_msat: number; + proportional_millionths: number; +} + +interface GatewayTTL { + nanos: number; + secs: number; +} + interface Gateway { - nodePubKey: string; - active: boolean; + federation_id: string; + info: GatewayInfo; + ttl: GatewayTTL; + vetted: boolean; } interface SwitchGatewayRequest { @@ -258,6 +287,7 @@ export type { DiscoverVersionRequest, DiscoverVersionResponse, JoinRequest, + JoinResponse, BackupRequest, ListOperationsRequest, OperationOutput,