Skip to content

Commit

Permalink
feat: add fedimint-py and fedimint-go
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodylow committed Mar 21, 2024
1 parent 3c55119 commit e58fa2e
Show file tree
Hide file tree
Showing 33 changed files with 1,519 additions and 69 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
fm_client_db
.cargo
.vscode
.DS_Store
23 changes: 14 additions & 9 deletions fedimint-clientd/src/router/handlers/cashu/melt/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,19 @@ pub async fn melt_bolt11(
return Err(AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("No gateways available"),
))
));
}
};
let gateway = lightning_module.select_gateway(&gateway_id).await.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;
let gateway = lightning_module
.select_gateway(&gateway_id)
.await
.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;

let bolt11 = Bolt11Invoice::from_str(&request)?;
let bolt11_amount = Amount::from_msats(
Expand All @@ -109,7 +112,9 @@ pub async fn melt_bolt11(
payment_type,
contract_id: _,
fee,
} = lightning_module.pay_bolt11_invoice(Some(gateway), bolt11, ()).await?;
} = lightning_module
.pay_bolt11_invoice(Some(gateway), bolt11, ())
.await?;

let operation_id = payment_type.operation_id();
info!("Gateway fee: {fee}, payment operation id: {operation_id}");
Expand Down
23 changes: 14 additions & 9 deletions fedimint-clientd/src/router/handlers/cashu/mint/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,26 @@ pub async fn mint_bolt11(
return Err(AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("No gateways available"),
))
));
}
};
let gateway = lightning_module.select_gateway(&gateway_id).await.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;
let gateway = lightning_module
.select_gateway(&gateway_id)
.await
.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;

let (operation_id, invoice, _) = lightning_module
.create_bolt11_invoice(
amount_msat,
Bolt11InvoiceDescription::Direct(&Description::new(DEFAULT_MINT_DESCRIPTION.to_string())?),
Bolt11InvoiceDescription::Direct(&Description::new(
DEFAULT_MINT_DESCRIPTION.to_string(),
)?),
Some(expiry_time),
(),
Some(gateway),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ pub struct DiscoverVersionRequest {
threshold: Option<usize>,
}

async fn _discover_version(multimint: MultiMint, threshold: Option<usize>) -> Result<Value, AppError> {
async fn _discover_version(
multimint: MultiMint,
threshold: Option<usize>,
) -> Result<Value, AppError> {
let mut api_versions = HashMap::new();
for (id, client) in multimint.clients.lock().await.iter() {
api_versions.insert(
Expand Down
19 changes: 11 additions & 8 deletions fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,19 @@ async fn _invoice(
return Err(AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("No gateways available"),
))
));
}
};
let gateway = lightning_module.select_gateway(&gateway_id).await.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;
let gateway = lightning_module
.select_gateway(&gateway_id)
.await
.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;

let (operation_id, invoice, _) = lightning_module
.create_bolt11_invoice(
Expand Down
2 changes: 1 addition & 1 deletion fedimint-clientd/src/router/handlers/fedimint/ln/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use anyhow::{bail, Context};
use fedimint_client::ClientHandleArc;
use fedimint_core::Amount;
use fedimint_ln_client::{InternalPayState, LightningClientModule, LnPayState, PayType};
use futures_util::StreamExt;
use lightning_invoice::Bolt11Invoice;
use tracing::{debug, info};
use futures_util::StreamExt;

use self::pay::{LnPayRequest, LnPayResponse};

Expand Down
23 changes: 14 additions & 9 deletions fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,27 @@ async fn _pay(client: ClientHandleArc, req: LnPayRequest) -> Result<LnPayRespons
return Err(AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("No gateways available"),
))
));
}
};
let gateway = lightning_module.select_gateway(&gateway_id).await.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;
let gateway = lightning_module
.select_gateway(&gateway_id)
.await
.ok_or_else(|| {
error!("Failed to select gateway");
AppError::new(
StatusCode::INTERNAL_SERVER_ERROR,
anyhow!("Failed to select gateway"),
)
})?;

let OutgoingLightningPayment {
payment_type,
contract_id,
fee,
} = lightning_module.pay_bolt11_invoice(Some(gateway), bolt11, ()).await?;
} = lightning_module
.pay_bolt11_invoice(Some(gateway), bolt11, ())
.await?;
let operation_id = payment_type.operation_id();
info!("Gateway fee: {fee}, payment operation id: {operation_id}");
if req.finish_in_background {
Expand Down
66 changes: 34 additions & 32 deletions fedimint-clientd/src/router/handlers/fedimint/mint/spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use fedimint_client::ClientHandleArc;
use fedimint_core::config::FederationId;
use fedimint_core::core::OperationId;
use fedimint_core::Amount;
use fedimint_mint_client::{MintClientModule, OOBNotes, SelectNotesWithAtleastAmount, SelectNotesWithExactAmount};
use fedimint_mint_client::{
MintClientModule, OOBNotes, SelectNotesWithAtleastAmount, SelectNotesWithExactAmount,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tracing::{info, warn};
Expand Down Expand Up @@ -36,39 +38,39 @@ pub struct SpendResponse {
async fn _spend(client: ClientHandleArc, req: SpendRequest) -> Result<SpendResponse, AppError> {
warn!("The client will try to double-spend these notes after the duration specified by the --timeout option to recover any unclaimed e-cash.");
let mint_module = client.get_first_module::<MintClientModule>();
let timeout = Duration::from_secs(req.timeout);
let (operation, notes) = if req.allow_overpay {
let (operation, notes) = mint_module
.spend_notes_with_selector(
&SelectNotesWithAtleastAmount,
req.amount_msat,
timeout,
req.include_invite,
(),
)
.await?;
let timeout = Duration::from_secs(req.timeout);
let (operation, notes) = if req.allow_overpay {
let (operation, notes) = mint_module
.spend_notes_with_selector(
&SelectNotesWithAtleastAmount,
req.amount_msat,
timeout,
req.include_invite,
(),
)
.await?;

let overspend_amount = notes.total_amount() - req.amount_msat;
if overspend_amount != Amount::ZERO {
warn!(
"Selected notes {} worth more than requested",
overspend_amount
);
}
let overspend_amount = notes.total_amount() - req.amount_msat;
if overspend_amount != Amount::ZERO {
warn!(
"Selected notes {} worth more than requested",
overspend_amount
);
}

(operation, notes)
} else {
mint_module
.spend_notes_with_selector(
&SelectNotesWithExactAmount,
req.amount_msat,
timeout,
req.include_invite,
(),
)
.await?
};
info!("Spend e-cash operation: {operation}");
(operation, notes)
} else {
mint_module
.spend_notes_with_selector(
&SelectNotesWithExactAmount,
req.amount_msat,
timeout,
req.include_invite,
(),
)
.await?
};
info!("Spend e-cash operation: {operation}");
Ok(SpendResponse { operation, notes })
}

Expand Down
1 change: 1 addition & 0 deletions wrappers/fedimint-go/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
42 changes: 42 additions & 0 deletions wrappers/fedimint-go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<img src="assets/fedimint-gophers.png" width="400px" />

# Fedimint SDK for Go

This is a Go client that consumes the Fedimint Http Client (https://github.com/kodylow/fedimint-http-client)[https://github.com/kodylow/fedimint-http-client], communicating with it via HTTP and a password. It's a hacky prototype, but it works until we can get a proper Go client for Fedimint. All of the federation handling code happens in the fedimint-http-client, this just exposes a simple API for interacting with the client from Go (will be mirrored in Python and Go).

Start the following in the fedimint-http-client .env environment variables:

```bash
FEDERATION_INVITE_CODE = 'fed1-some-invite-code'
SECRET_KEY = 'some-secret-key' # generate this with `openssl rand -base64 32`
FM_DB_PATH = '/absolute/path/to/fm.db' # just make this a new dir called `fm_db` in the root of the fedimint-http-client and use the absolute path to thatm it'll create the db file for you on startup
PASSWORD = 'password'
DOMAIN = 'localhost'
PORT = 5000
BASE_URL = 'http://localhost:5000'
```

Then start the fedimint-http-client server:

```bash
cargo run
```

Then you're ready to run the go client, which will use the same base url and password as the fedimint-http-client:

```bash
BASE_URL = 'http://localhost:5000'
PASSWORD = 'password'
```

To install dependencies:
```bash
go get
```

To run (this just runs an example that creates FedimintClient in go and creates an invoice):
@TODO: make this actually export the client for go registry

```bash
go run cmd/main.go
```
Binary file added wrappers/fedimint-go/assets/fedimint-gophers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions wrappers/fedimint-go/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import (
"fedimint-go-client/pkg/fedimint"
"fedimint-go-client/pkg/fedimint/types/modules"
"fmt"
"os"

"github.com/joho/godotenv"
)

func main() {
err := godotenv.Load()
if err != nil {
fmt.Println("Error loading .env file")
}

baseUrl := os.Getenv("BASE_URL")
if baseUrl == "" {
baseUrl = "http://localhost:5000"
}

password := os.Getenv("PASSWORD")
if password == "" {
password = "password"
}

federationId := os.Getenv("FEDERATION_ID")
if federationId == "" {
federationId = "defaultId"
}

fedimintClient := fedimint.NewFedimintClient(baseUrl, password, federationId)

info, err := fedimintClient.Info()
if err != nil {
fmt.Println("Error getting info: ", err)
return
}
fmt.Println("Current Total Msats Ecash: ", info.TotalAmountMsat)

invoiceRequest := modules.LnInvoiceRequest{
AmountMsat: 10000,
Description: "test",
}

invoiceResponse, err := fedimintClient.Ln.CreateInvoice(invoiceRequest, &federationId)
if err != nil {
fmt.Println("Error creating invoice: ", err)
return
}

fmt.Println("Created 10 sat Invoice: ", invoiceResponse.Invoice)

fmt.Println("Waiting for payment...")

awaitInvoiceRequest := modules.AwaitInvoiceRequest{
OperationID: invoiceResponse.OperationID,
}

_, err = fedimintClient.Ln.AwaitInvoice(awaitInvoiceRequest, &federationId)
if err != nil {
fmt.Println("Error awaiting invoice: ", err)
return
}

fmt.Println("Payment Received!")
// fmt.Println("New Total Msats Ecash: ", awaitInvoiceResponse.TotalAmountMsat)
}
22 changes: 22 additions & 0 deletions wrappers/fedimint-go/flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
description = "Fedimint Go SDK";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; };
in {
devShells = {
default = pkgs.mkShell {
nativeBuildInputs = [ pkgs.go_1_21 pkgs.starship ];
shellHook = ''
eval "$(starship init bash)"
'';
};
};
});
}
5 changes: 5 additions & 0 deletions wrappers/fedimint-go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module fedimint-go-client

go 1.21.3

require github.com/joho/godotenv v1.5.1 // indirect
2 changes: 2 additions & 0 deletions wrappers/fedimint-go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
Loading

0 comments on commit e58fa2e

Please sign in to comment.