diff --git a/.gitignore b/.gitignore index 9c36fb7..db36602 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ fm_client_db .cargo .vscode +.DS_Store diff --git a/fedimint-clientd/src/router/handlers/cashu/melt/method.rs b/fedimint-clientd/src/router/handlers/cashu/melt/method.rs index 727b8df..b9bd4c8 100644 --- a/fedimint-clientd/src/router/handlers/cashu/melt/method.rs +++ b/fedimint-clientd/src/router/handlers/cashu/melt/method.rs @@ -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( @@ -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}"); diff --git a/fedimint-clientd/src/router/handlers/cashu/mint/method.rs b/fedimint-clientd/src/router/handlers/cashu/mint/method.rs index e0dac51..c7d4993 100644 --- a/fedimint-clientd/src/router/handlers/cashu/mint/method.rs +++ b/fedimint-clientd/src/router/handlers/cashu/mint/method.rs @@ -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), diff --git a/fedimint-clientd/src/router/handlers/fedimint/admin/discover_version.rs b/fedimint-clientd/src/router/handlers/fedimint/admin/discover_version.rs index 29d2777..54e2cc6 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/admin/discover_version.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/admin/discover_version.rs @@ -15,7 +15,10 @@ pub struct DiscoverVersionRequest { threshold: Option, } -async fn _discover_version(multimint: MultiMint, threshold: Option) -> Result { +async fn _discover_version( + multimint: MultiMint, + threshold: Option, +) -> Result { let mut api_versions = HashMap::new(); for (id, client) in multimint.clients.lock().await.iter() { api_versions.insert( diff --git a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs index 0764adf..92df8be 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/ln/invoice.rs @@ -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( diff --git a/fedimint-clientd/src/router/handlers/fedimint/ln/mod.rs b/fedimint-clientd/src/router/handlers/fedimint/ln/mod.rs index bfe8355..18dd4a6 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/ln/mod.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/ln/mod.rs @@ -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}; diff --git a/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs b/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs index b3fa4bc..c3e98e1 100644 --- a/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs +++ b/fedimint-clientd/src/router/handlers/fedimint/ln/pay.rs @@ -45,22 +45,27 @@ async fn _pay(client: ClientHandleArc, req: LnPayRequest) -> Result Result { 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::(); - 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 }) } diff --git a/wrappers/fedimint-go/.gitignore b/wrappers/fedimint-go/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/wrappers/fedimint-go/.gitignore @@ -0,0 +1 @@ +.env diff --git a/wrappers/fedimint-go/README.md b/wrappers/fedimint-go/README.md new file mode 100644 index 0000000..d6002db --- /dev/null +++ b/wrappers/fedimint-go/README.md @@ -0,0 +1,42 @@ + + +# 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 +``` diff --git a/wrappers/fedimint-go/assets/fedimint-gophers.png b/wrappers/fedimint-go/assets/fedimint-gophers.png new file mode 100644 index 0000000..089a883 Binary files /dev/null and b/wrappers/fedimint-go/assets/fedimint-gophers.png differ diff --git a/wrappers/fedimint-go/cmd/main.go b/wrappers/fedimint-go/cmd/main.go new file mode 100644 index 0000000..77a6e24 --- /dev/null +++ b/wrappers/fedimint-go/cmd/main.go @@ -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) +} diff --git a/wrappers/fedimint-go/flake.nix b/wrappers/fedimint-go/flake.nix new file mode 100644 index 0000000..e12a111 --- /dev/null +++ b/wrappers/fedimint-go/flake.nix @@ -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)" + ''; + }; + }; + }); +} diff --git a/wrappers/fedimint-go/go.mod b/wrappers/fedimint-go/go.mod new file mode 100644 index 0000000..96da908 --- /dev/null +++ b/wrappers/fedimint-go/go.mod @@ -0,0 +1,5 @@ +module fedimint-go-client + +go 1.21.3 + +require github.com/joho/godotenv v1.5.1 // indirect diff --git a/wrappers/fedimint-go/go.sum b/wrappers/fedimint-go/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/wrappers/fedimint-go/go.sum @@ -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= diff --git a/wrappers/fedimint-go/pkg/fedimint/client.go b/wrappers/fedimint-go/pkg/fedimint/client.go new file mode 100644 index 0000000..408d4fc --- /dev/null +++ b/wrappers/fedimint-go/pkg/fedimint/client.go @@ -0,0 +1,382 @@ +package fedimint + +import ( + "bytes" + "encoding/json" + "fedimint-go-client/pkg/fedimint/types" + "fedimint-go-client/pkg/fedimint/types/modules" + "fmt" + "io/ioutil" + "net/http" +) + +type FedimintClient struct { + BaseURL string + Password string + FederationId string + Ln LnModule + Wallet WalletModule + Mint MintModule +} + +type LnModule struct { + Client *FedimintClient +} + +type MintModule struct { + Client *FedimintClient +} + +type WalletModule struct { + Client *FedimintClient +} + +func NewFedimintClient(baseURL, password string, federationId string) *FedimintClient { + fc := &FedimintClient{ + BaseURL: baseURL + "/fedimint/v2", + Password: password, + FederationId: federationId, + } + fc.Ln.Client = fc + fc.Wallet.Client = fc + fc.Mint.Client = fc + + return fc +} + +func (fc *FedimintClient) fetchWithAuth(endpoint string, method string, body []byte) ([]byte, error) { + client := &http.Client{} + req, err := http.NewRequest(method, fc.BaseURL+endpoint, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+fc.Password) + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP error! status: %d", resp.StatusCode) + } + return ioutil.ReadAll(resp.Body) +} + +func (fc *FedimintClient) getActiveFederationId() string { + return fc.FederationId +} + +func (fc *FedimintClient) setActiveFederationId(federationId string) { + fc.FederationId = federationId +} + +func (fc *FedimintClient) get(endpoint string) ([]byte, error) { + return fc.fetchWithAuth(endpoint, "GET", nil) +} + +func (fc *FedimintClient) post(endpoint string, body interface{}) ([]byte, error) { + jsonBody, err := json.Marshal(body) + fmt.Println("jsonBody: ", string(jsonBody)) + if err != nil { + return nil, err + } + return fc.fetchWithAuth(endpoint, "POST", jsonBody) +} + +func (fc *FedimintClient) postWithId(endpoint string, body interface{}, federationId string) ([]byte, error) { + effectiveFederationId := federationId + if effectiveFederationId == "" { + effectiveFederationId = fc.FederationId + } + + return fc.post(endpoint, map[string]interface{}{ + "body": body, + "federationId": effectiveFederationId, + }) +} + +func (fc *FedimintClient) Info() (*types.InfoResponse, error) { + resp, err := fc.get("/admin/info") + if err != nil { + return nil, err + } + var infoResp types.InfoResponse + err = json.Unmarshal(resp, &infoResp) + if err != nil { + return nil, err + } + return &infoResp, nil +} + +func (fc *FedimintClient) Backup(metadata *types.BackupRequest, federationId string) error { + _, err := fc.postWithId("/admin/backup", metadata, federationId) + return err +} + +func (fc *FedimintClient) DiscoverVersion() (*types.FedimintResponse, error) { + resp, err := fc.get("/admin/discover-version") + if err != nil { + return nil, err + } + var versionResp types.FedimintResponse + err = json.Unmarshal(resp, &versionResp) + if err != nil { + return nil, err + } + return &versionResp, nil +} + +func (fc *FedimintClient) ListOperations(request *types.ListOperationsRequest, federationId *string) (*types.OperationOutput, error) { + resp, err := fc.post("/admin/list-operations", request) + if err != nil { + return nil, err + } + var operationsResp types.OperationOutput + err = json.Unmarshal(resp, &operationsResp) + if err != nil { + return nil, err + } + return &operationsResp, nil +} + +func (fc *FedimintClient) Config() (*types.FedimintResponse, error) { + resp, err := fc.get("/admin/config") + if err != nil { + return nil, err + } + var configResp types.FedimintResponse + err = json.Unmarshal(resp, &configResp) + if err != nil { + return nil, err + } + return &configResp, nil +} + +func (fc *FedimintClient) Join(inviteCode string, setDefault bool) (types.FederationIdsResponse, error) { + var response types.FederationIdsResponse + responseBody, err := fc.post("/admin/join", map[string]interface{}{ + "inviteCode": inviteCode, + "setDefault": setDefault, + }) + + if err != nil { + return response, err + } + + err = json.Unmarshal(responseBody, &response) + if err != nil { + return response, err + } + return response, nil +} + +func (fc *FedimintClient) FederationIds() (types.FederationIdsResponse, error) { + var response types.FederationIdsResponse + responseBody, err := fc.get("/admin/federation-ids") + + if err != nil { + return response, err + } + + err = json.Unmarshal(responseBody, &response) + if err != nil { + return response, err + } + return response, nil +} + +//////////// +// Wallet // +//////////// + +func (wallet *WalletModule) createDepositAddress(request modules.DepositAddressRequest, federationId *string) (*modules.DepositAddressResponse, error) { + resp, err := wallet.Client.postWithId("/wallet/deposit-address", request, *federationId) + if err != nil { + return nil, err + } + var depositAddressResp modules.DepositAddressResponse + err = json.Unmarshal(resp, &depositAddressResp) + if err != nil { + return nil, err + } + return &depositAddressResp, nil +} + +func (wallet *WalletModule) awaitDeposit(request modules.AwaitDepositRequest, federationId *string) (*modules.AwaitDepositResponse, error) { + resp, err := wallet.Client.postWithId("/wallet/await-deposit", request, *federationId) + if err != nil { + return nil, err + } + var depositResp modules.AwaitDepositResponse + err = json.Unmarshal(resp, &depositResp) + if err != nil { + return nil, err + } + return &depositResp, nil +} + +func (wallet *WalletModule) withdraw(request modules.WithdrawRequest, federationId *string) (*modules.WithdrawResponse, error) { + resp, err := wallet.Client.postWithId("/wallet/withdraw", request, *federationId) + if err != nil { + return nil, err + } + var withdrawResp modules.WithdrawResponse + err = json.Unmarshal(resp, &withdrawResp) + if err != nil { + return nil, err + } + return &withdrawResp, nil +} + +////////// +// mint // +////////// + +func (mint *MintModule) Reissue(request modules.ReissueRequest, federationId *string) (*modules.ReissueResponse, error) { + resp, err := mint.Client.postWithId("/mint/reissue", request, *federationId) + if err != nil { + return nil, err + } + var reissueResp modules.ReissueResponse + err = json.Unmarshal(resp, &reissueResp) + if err != nil { + return nil, err + } + return &reissueResp, nil +} + +func (mint *MintModule) Spend(request modules.SpendRequest, federationId *string) (*modules.SpendResponse, error) { + resp, err := mint.Client.postWithId("/mint/spend", request, *federationId) + if err != nil { + return nil, err + } + var spendResp modules.SpendResponse + err = json.Unmarshal(resp, &spendResp) + if err != nil { + return nil, err + } + return &spendResp, nil +} + +func (mint *MintModule) Validate(request modules.ValidateRequest, federationId *string) (*modules.ValidateResponse, error) { + resp, err := mint.Client.postWithId("/mint/validate", request, *federationId) + if err != nil { + return nil, err + } + var validateResp modules.ValidateResponse + err = json.Unmarshal(resp, &validateResp) + if err != nil { + return nil, err + } + return &validateResp, nil +} + +func (mint *MintModule) Split(request modules.SplitRequest) (*modules.SplitResponse, error) { + resp, err := mint.Client.post("/mint/split", request) + if err != nil { + return nil, err + } + var splitResp modules.SplitResponse + err = json.Unmarshal(resp, &splitResp) + if err != nil { + return nil, err + } + return &splitResp, nil +} + +func (mint *MintModule) Combine(request modules.CombineRequest) (*modules.CombineResponse, error) { + resp, err := mint.Client.post("/mint/combine", request) + if err != nil { + return nil, err + } + var combineResp modules.CombineResponse + err = json.Unmarshal(resp, &combineResp) + if err != nil { + return nil, err + } + return &combineResp, nil +} + +//////// +// Ln // +//////// + +func (ln *LnModule) CreateInvoice(request modules.LnInvoiceRequest, federationId *string) (*modules.LnInvoiceResponse, error) { + fmt.Println("request: ", request) + resp, err := ln.Client.postWithId("/ln/invoice", request, *federationId) + if err != nil { + return nil, err + } + var invoiceResp modules.LnInvoiceResponse + err = json.Unmarshal(resp, &invoiceResp) + if err != nil { + return nil, err + } + return &invoiceResp, nil +} + +func (ln *LnModule) AwaitInvoice(request modules.AwaitInvoiceRequest, federationId *string) (*types.InfoResponse, error) { + resp, err := ln.Client.postWithId("/ln/await-invoice", request, *federationId) + if err != nil { + return nil, err + } + var infoResp types.InfoResponse + err = json.Unmarshal(resp, &infoResp) + if err != nil { + return nil, err + } + return &infoResp, nil +} + +func (ln *LnModule) Pay(request modules.LnPayRequest, federationId *string) (*modules.LnPayResponse, error) { + resp, err := ln.Client.postWithId("/ln/pay", request, *federationId) + if err != nil { + return nil, err + } + var payResp modules.LnPayResponse + err = json.Unmarshal(resp, &payResp) + if err != nil { + return nil, err + } + return &payResp, nil +} + +func (ln *LnModule) AwaitPay(request modules.AwaitLnPayRequest, federationId *string) (*modules.LnPayResponse, error) { + resp, err := ln.Client.postWithId("/ln/await-pay", request, *federationId) + if err != nil { + return nil, err + } + var payResp modules.LnPayResponse + err = json.Unmarshal(resp, &payResp) + if err != nil { + return nil, err + } + return &payResp, nil +} + +func (ln *LnModule) ListGateways() ([]modules.Gateway, error) { + resp, err := ln.Client.get("/ln/list-gateways") + if err != nil { + return nil, err + } + var gateways []modules.Gateway + err = json.Unmarshal(resp, &gateways) + if err != nil { + return nil, err + } + return gateways, nil +} + +func (ln *LnModule) SwitchGateway(request modules.SwitchGatewayRequest, federationId *string) (*modules.Gateway, error) { + resp, err := ln.Client.postWithId("/ln/switch-gateway", request, *federationId) + if err != nil { + return nil, err + } + var gateway modules.Gateway + err = json.Unmarshal(resp, &gateway) + if err != nil { + return nil, err + } + return &gateway, nil +} diff --git a/wrappers/fedimint-go/pkg/fedimint/types/common.go b/wrappers/fedimint-go/pkg/fedimint/types/common.go new file mode 100644 index 0000000..f73eda5 --- /dev/null +++ b/wrappers/fedimint-go/pkg/fedimint/types/common.go @@ -0,0 +1,38 @@ +package types + +type Tiered map[int]interface{} + +type TieredSummary struct { + Tiered Tiered `json:"tiered"` +} + +type InfoResponse struct { + FederationID string `json:"federation_id"` + Network string `json:"network"` + Meta map[string]string `json:"meta"` + TotalAmountMsat int `json:"total_amount_msat"` + TotalNumNotes int `json:"total_num_notes"` + DenominationsMsat TieredSummary `json:"denominations_msat"` +} + +type BackupRequest struct { + Metadata map[string]string `json:"metadata"` +} + +type ListOperationsRequest struct { + Limit int `json:"limit"` +} + +type FederationIdsResponse struct { + FederationIds []string `json:"federationIds"` +} + +type OperationOutput struct { + ID string `json:"id"` + CreationTime string `json:"creation_time"` + OperationKind string `json:"operation_kind"` + OperationMeta interface{} `json:"operation_meta"` + Outcome interface{} `json:"outcome,omitempty"` +} + +type FedimintResponse map[string]interface{} diff --git a/wrappers/fedimint-go/pkg/fedimint/types/modules/ln.go b/wrappers/fedimint-go/pkg/fedimint/types/modules/ln.go new file mode 100644 index 0000000..1c7569d --- /dev/null +++ b/wrappers/fedimint-go/pkg/fedimint/types/modules/ln.go @@ -0,0 +1,46 @@ +package modules + +type LnInvoiceRequest struct { + AmountMsat int `json:"amount_msat"` + Description string `json:"description"` + ExpiryTime *int `json:"expiry_time"` +} + +type LnInvoiceResponse struct { + OperationID string `json:"operation_id"` + Invoice string `json:"invoice"` +} + +type AwaitInvoiceRequest struct { + OperationID string `json:"operation_id"` +} + +type LnPayRequest struct { + Payment_info string `json:"payment_info"` + Amount_msat *int `json:"amount_msat"` + Finish_in_background bool `json:"finish_in_background"` + Lnurl_comment *string `json:"lnurl_comment"` +} + +type LnPayResponse struct { + Pperation_id string `json:"operation_id"` + Payment_type string `json:"payment_type"` + Contract_id string `json:"contract_id"` + Fee int `json:"fee"` +} + +type AwaitLnPayRequest struct { + Operation_id string `json:"operation_id"` +} + +type Gateway struct { + Node_pub_key string `json:"node_pub_key"` + Active bool `json:"active"` +} + +// string::> FederationId +type ListGatewaysResponse map[string][]Gateway + +type SwitchGatewayRequest struct { + Gateway_id string `json:"gateway_id"` +} diff --git a/wrappers/fedimint-go/pkg/fedimint/types/modules/mint.go b/wrappers/fedimint-go/pkg/fedimint/types/modules/mint.go new file mode 100644 index 0000000..7d3cbff --- /dev/null +++ b/wrappers/fedimint-go/pkg/fedimint/types/modules/mint.go @@ -0,0 +1,93 @@ +package modules + +type FederationIdPrefix struct { + Value [4]byte `json:"value"` +} + +type TieredMulti struct { + Amount []interface{} `json:"amount"` +} + +type Signature struct { + Zero G1Affine `json:"zero"` +} + +type G1Affine struct { + X Fp `json:"x"` + Y Fp `json:"y"` + Infinity Choice `json:"infinity"` +} + +type Fp struct { + Zero []uint64 `json:"zero"` +} + +type Choice struct { + Zero uint8 `json:"zero"` +} + +type KeyPair struct { + Zero []uint8 `json:"zero"` +} + +type OOBNotesData struct { + Notes *TieredMulti `json:"notes"` + FederationIdPrefix *FederationIdPrefix `json:"federation_id_prefix"` + Default struct { + Variant uint64 `json:"variant"` + Bytes []uint8 `json:"bytes"` + } `json:"default"` +} + +type OOBNotes struct { + Zero []OOBNotesData `json:"zero"` +} + +type SpendableNote struct { + Signature Signature `json:"signature"` + SpendKey KeyPair `json:"spend_key"` +} + +// @> `ReissueRequest` notes should be string? as fedimint-ts does uses string. +type ReissueRequest struct { + Notes OOBNotes `json:"notes"` +} + +type ReissueResponse struct { + AmountMsat uint64 `json:"amount_msat"` +} + +type SpendRequest struct { + AmountMsat uint64 `json:"amount_msat"` + AllowOverpay bool `json:"allow_overpay"` + Timeout uint64 `json:"timeout"` +} + +type SpendResponse struct { + Operation string `json:"operation"` + Notes OOBNotes `json:"notes"` +} + +type ValidateRequest struct { + Notes OOBNotes `json:"notes"` +} + +type ValidateResponse struct { + AmountMsat uint64 `json:"amount_msat"` +} + +type SplitRequest struct { + Notes OOBNotes `json:"notes"` +} + +type SplitResponse struct { + Notes map[uint64]OOBNotes `json:"notes"` +} + +type CombineRequest struct { + Notes []OOBNotes `json:"notes"` +} + +type CombineResponse struct { + Notes OOBNotes `json:"notes"` +} diff --git a/wrappers/fedimint-go/pkg/fedimint/types/modules/wallet.go b/wrappers/fedimint-go/pkg/fedimint/types/modules/wallet.go new file mode 100644 index 0000000..a5ac24b --- /dev/null +++ b/wrappers/fedimint-go/pkg/fedimint/types/modules/wallet.go @@ -0,0 +1,28 @@ +package modules + +type DepositAddressRequest struct { + Timeout int `json:"timeout"` +} + +type DepositAddressResponse struct { + OperationID string `json:"operation_id"` + Address string `json:"address"` +} + +type AwaitDepositRequest struct { + OperationID string `json:"operation_id"` +} + +type AwaitDepositResponse struct { + Status string `json:"status"` +} + +type WithdrawRequest struct { + Address string `json:"address"` + AmountMsat string `json:"amount_msat"` +} + +type WithdrawResponse struct { + Txid string `json:"txid"` + FeesSat int `json:"fees_sat"` +} diff --git a/wrappers/fedimint-py/.gitignore b/wrappers/fedimint-py/.gitignore new file mode 100644 index 0000000..3b72af0 --- /dev/null +++ b/wrappers/fedimint-py/.gitignore @@ -0,0 +1,2 @@ +.env +__pycache__ diff --git a/wrappers/fedimint-py/AsyncFedimintClient.py b/wrappers/fedimint-py/AsyncFedimintClient.py new file mode 100644 index 0000000..411d102 --- /dev/null +++ b/wrappers/fedimint-py/AsyncFedimintClient.py @@ -0,0 +1,193 @@ +import aiohttp +from models.common import ( + InfoResponse, + ListOperationsRequest, + OperationOutput, + BackupRequest, +) +from models.ln import ( + LnInvoiceRequest, + LnInvoiceResponse, + AwaitInvoiceRequest, + LnPayRequest, + LnPayResponse, + AwaitLnPayRequest, + Gateway, + SwitchGatewayRequest, +) +from models.wallet import DepositAddressRequest, AwaitDepositRequest, WithdrawRequest +from models.mint import ( + ReissueRequest, + SpendRequest, + ValidateRequest, + SplitRequest, + CombineRequest, +) + + +class AsyncFedimintClient: + def __init__(self, base_url: str, password: str, active_federation_id: str): + self.base_url = f"{base_url}/fedimint/v2" + self.password = password + self.active_federation_id = active_federation_id + self.session = aiohttp.ClientSession() + + self.ln = self.LN(self) + self.wallet = self.Wallet(self) + self.mint = self.Mint(self) + + def get_active_federation_id(self): + return self.active_federation_id + + def set_active_federation_id(self, federation_id: str): + self.active_federation_id = federation_id + + async def _handle_response(self, response): + if response.status != 200: + text = await response.text() + raise Exception(f"HTTP error! status: {response.status}, Body: {text}") + + async def _get(self, endpoint: str): + headers = {"Authorization": f"Bearer {self.password}"} + async with self.session.get( + f"{self.base_url}{endpoint}", headers=headers + ) as response: + await self._handle_response(response) + return await response.json() + + async def _post(self, endpoint: str, data=None): + headers = { + "Authorization": f"Bearer {self.password}", + "Content-Type": "application/json", + } + async with self.session.post( + f"{self.base_url}{endpoint}", json=data, headers=headers + ) as response: + await self._handle_response(response) + return await response.json() + + async def _post_with_id(self, endpoint: str, data=None, federation_id: str = None): + if federation_id is None: + federation_id = self.get_active_federation_id() + + if data is None: + data = {} + data["federationId"] = federation_id + + return await self._post(endpoint, data) + + async def info(self): + return await self._get("/admin/info") + + async def backup(self, request: BackupRequest, federation_id: str = None): + return await self._post_with_id("/admin/backup", request, federation_id) + + async def config(self): + return await self._get("/admin/config") + + async def discover_version(self): + return await self._get("/admin/discover-version") + + async def federation_ids(self): + return await self._get("/admin/federation-ids") + + async def list_operations(self, request: ListOperationsRequest): + return await self._post_with_id("/admin/list-operations", request) + + async def join(self, invite_code: str, set_default: bool = False): + return await self._post( + "/admin/join", {"inviteCode": invite_code, "setDefault": set_default} + ) + + class LN: + def __init__(self, client): + self.client = client + + async def create_invoice( + self, request: LnInvoiceRequest, federation_id: str = None + ): + return await self.client._post_with_id( + "/ln/invoice", request, federation_id + ) + + async def await_invoice( + self, request: AwaitInvoiceRequest, federation_id: str = None + ): + return await self.client._post_with_id( + "/ln/await-invoice", request, federation_id + ) + + async def pay(self, request: LnPayRequest, federation_id: str = None): + return await self.client._post_with_id("/ln/pay", request, federation_id) + + async def await_pay( + self, request: AwaitLnPayRequest, federation_id: str = None + ): + return await self.client._post_with_id( + "/ln/await-pay", request, federation_id + ) + + async def list_gateways(self): + return await self.client._get("/ln/list-gateways") + + async def switch_gateway( + self, request: SwitchGatewayRequest, federation_id: str = None + ): + return await self.client._post_with_id( + "/ln/switch-gateway", request, federation_id + ) + + class Wallet: + def __init__(self, client): + self.client = client + + async def create_deposit_address( + self, request: DepositAddressRequest, federation_id: str = None + ): + return self.client._post_with_id( + "/wallet/deposit-address", data=request, federation_id=federation_id + ) + + async def await_deposit( + self, request: AwaitDepositRequest, federation_id: str = None + ): + return await self.client._post_with_id( + "/wallet/await-deposit", request, federation_id + ) + + async def withdraw(self, request: WithdrawRequest, federation_id: str = None): + return await self.client._post_with_id( + "/wallet/withdraw", request, federation_id + ) + + class Mint: + def __init__(self, client): + self.client = client + + async def reissue(self, request: ReissueRequest, federation_id: str = None): + return await self.client._post_with_id( + "/mint/reissue", request, federation_id + ) + + async def spend(self, request: SpendRequest, federation_id: str = None): + return await self.client._post_with_id( + "/mint/spend", request, federation_id + ) + + async def validate(self, request: ValidateRequest, federation_id: str = None): + return await self.client._post_with_id( + "/mint/validate", request, federation_id + ) + + async def split(self, request: SplitRequest, federation_id: str = None): + return await self.client._post_with_id( + "/mint/split", request, federation_id + ) + + async def combine(self, request: CombineRequest, federation_id: str = None): + return await self.client._post_with_id( + "/mint/combine", request, federation_id + ) + + async def close(self): + await self.session.close() diff --git a/wrappers/fedimint-py/FedimintClient.py b/wrappers/fedimint-py/FedimintClient.py new file mode 100644 index 0000000..61b605e --- /dev/null +++ b/wrappers/fedimint-py/FedimintClient.py @@ -0,0 +1,192 @@ +import requests +from models.common import ( + InfoResponse, + ListOperationsRequest, + OperationOutput, + BackupRequest, +) +from models.ln import ( + LnInvoiceRequest, + LnInvoiceResponse, + AwaitInvoiceRequest, + LnPayRequest, + LnPayResponse, + AwaitLnPayRequest, + Gateway, + SwitchGatewayRequest, +) +from models.wallet import DepositAddressRequest, AwaitDepositRequest, WithdrawRequest +from models.mint import ( + ReissueRequest, + SpendRequest, + ValidateRequest, + SplitRequest, + CombineRequest, +) + + +class FedimintClient: + def __init__(self, base_url: str, password: str, active_federation_id: str): + self.base_url = f"{base_url}/fedimint/v2" + self.password = password + self.active_federation_id = active_federation_id + + self.ln = self.LN(self) + self.wallet = self.Wallet(self) + self.mint = self.Mint(self) + + def get_active_federation_id(self): + return self.active_federation_id + + def set_active_federation_id(self, federation_id: str): + self.active_federation_id = federation_id + + def _handle_response(self, response): + if not response.ok: + raise Exception( + f"HTTP error! status: {response.status_code}, Body: {response.text}" + ) + + def _get(self, endpoint: str): + headers = {"Authorization": f"Bearer {self.password}"} + response = requests.get(f"{self.base_url}{endpoint}", headers=headers) + self._handle_response(response) + return response.json() + + def _post(self, endpoint: str, data=None): + headers = { + "Authorization": f"Bearer {self.password}", + "Content-Type": "application/json", + } + response = requests.post( + f"{self.base_url}{endpoint}", json=data, headers=headers + ) + self._handle_response(response) + return response.json() + + def _post_with_id(self, endpoint: str, data=None, federation_id: str = None): + if federation_id is None: + federation_id = self.get_active_federation_id() + + if data is None: + data = {} + data["federationId"] = federation_id + + return self._post(endpoint, data) + + def info(self): + return self._get("/admin/info") + + def backup(self, request: BackupRequest, federation_id: str = None): + return self._post_with_id("/admin/backup", request, federation_id) + + def config(self): + return self.get("/admin/config") + + def discover_version(self): + return self._get("/admin/discover-version") + + def federation_ids(self): + return self._get("/admin/federation-ids") + + def list_operations(self, request: ListOperationsRequest): + return self._fetch_with_auth("/admin/list-operations", "POST", data=request) + + def join(self, invite_code: str, set_default: bool = False): + return self._post( + "/admin/join", {"inviteCode": invite_code, "setDefault": set_default} + ) + + # def module(self, name: str): + # return self._fetch_with_auth(f'/admin/module', 'POST') + + # def restore(self, request: RestoreRequest): + # return self._fetch_with_auth('/admin/restore', 'POST', data=request) + + class LN: + def __init__(self, client): + self.client = client + + def create_invoice(self, request: LnInvoiceRequest, federation_id: str = None): + return self.client._post_with_id( + "/ln/invoice", data=request, federation_id=federation_id + ) + + def await_invoice( + self, request: AwaitInvoiceRequest, federation_id: str = None + ): + return self.client._post_with_id( + "/ln/await-invoice", data=request, federation_id=federation_id + ) + + def pay(self, request: LnPayRequest, federation_id: str = None): + return self.client._post_with_id( + "/ln/pay", data=request, federation_id=federation_id + ) + + def await_pay(self, request: AwaitLnPayRequest, federation_id: str = None): + return self.client._post_with_id( + "/ln/await-pay", data=request, federation_id=federation_id + ) + + def list_gateways(self): + return self.client._get("/ln/list-gateways") + + def switch_gateway( + self, request: SwitchGatewayRequest, federation_id: str = None + ): + return self.client._post_with_id( + "/ln/switch-gateway", data=request, federation_id=federation_id + ) + + class Wallet: + def __init__(self, client): + self.client = client + + def create_deposit_address( + self, request: DepositAddressRequest, federation_id: str = None + ): + return self.client._post_with_id( + "/wallet/deposit-address", data=request, federation_id=federation_id + ) + + def await_deposit( + self, request: AwaitDepositRequest, federation_id: str = None + ): + return self.client._post_with_id( + "/wallet/await-deposit", data=request, federation_id=federation_id + ) + + def withdraw(self, request: WithdrawRequest, federation_id: str = None): + return self.client._post_with_id( + "/wallet/withdraw", data=request, federation_id=federation_id + ) + + class Mint: + def __init__(self, client): + self.client = client + + def reissue(self, request: ReissueRequest, federation_id: str = None): + return self.client._post_with_id( + "/mint/reissue", data=request, federation_id=federation_id + ) + + def spend(self, request: SpendRequest, federation_id: str = None): + return self.client._post_with_id( + "/mint/spend", data=request, federation_id=federation_id + ) + + def validate(self, request: ValidateRequest, federation_id: str = None): + return self.client._post_with_id( + "/mint/validate", data=request, federation_id=federation_id + ) + + def split(self, request: SplitRequest, federation_id: str = None): + return self.client._post_with_id( + "/mint/split", data=request, federation_id=federation_id + ) + + def combine(self, request: CombineRequest, federation_id: str = None): + return self.client._post_with_id( + "/mint/combine", data=request, federation_id=federation_id + ) diff --git a/wrappers/fedimint-py/README.md b/wrappers/fedimint-py/README.md new file mode 100644 index 0000000..b37691a --- /dev/null +++ b/wrappers/fedimint-py/README.md @@ -0,0 +1,43 @@ +# Fedimint SDK in Python + +This is a Python 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 Python 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 Python (will be mirrored in TS 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 python 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 +pip install -r requirements.txt +``` + +To run (this just runs an example that creates FedimintClient in python and creates an invoice): +@TODO: make this actually export the client for pip or poetry or whatever + +```bash +python3 example.py +python3 async_example.py +``` + +This project was created using `bun init` in bun v1.0.15. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/wrappers/fedimint-py/__init__.py b/wrappers/fedimint-py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wrappers/fedimint-py/async_example.py b/wrappers/fedimint-py/async_example.py new file mode 100644 index 0000000..898ebc8 --- /dev/null +++ b/wrappers/fedimint-py/async_example.py @@ -0,0 +1,41 @@ +import asyncio +from AsyncFedimintClient import AsyncFedimintClient +from models.ln import LnInvoiceRequest, AwaitInvoiceRequest +import os + + +async def main(): + base_url = os.getenv("BASE_URL", "http://localhost:3333") + password = os.getenv("PASSWORD", "password") + active_federation_id = os.getenv( + "ACTIVE_FEDERATION_ID", "some-active-federation-id" + ) + + fedimint_client = AsyncFedimintClient(base_url, password, active_federation_id) + + try: + response = await fedimint_client.info() + print("Current Total Msats Ecash: ", response["total_amount_msat"]) + + invoice_request = LnInvoiceRequest( + amount_msat=10000, description="test", expiry_time=3600 + ) + invoice_response = await fedimint_client.ln.create_invoice(invoice_request) + + print("Created 10 sat Invoice: ", invoice_response["invoice"]) + + print("Waiting for payment...") + + await_invoice_request = AwaitInvoiceRequest( + operation_id=invoice_response["operation_id"] + ) + payment_response = await fedimint_client.ln.await_invoice(await_invoice_request) + + print("Payment Received!") + print("New Total Msats Ecash: ", payment_response["total_amount_msat"]) + finally: + await fedimint_client.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/wrappers/fedimint-py/example.py b/wrappers/fedimint-py/example.py new file mode 100644 index 0000000..484ad06 --- /dev/null +++ b/wrappers/fedimint-py/example.py @@ -0,0 +1,29 @@ +from FedimintClient import FedimintClient +from models.ln import LnInvoiceRequest, AwaitInvoiceRequest +import os + +base_url = os.getenv("BASE_URL", "http://localhost:5000") +password = os.getenv("PASSWORD", "password") +active_federation_id = os.getenv("ACTIVE_FEDERATION_ID", "some-active-federation-id") + +fedimint_client = FedimintClient(base_url, password, active_federation_id) + +response = fedimint_client.info() +print("Current Total Msats Ecash: ", response["total_amount_msat"]) + +invoice_request = LnInvoiceRequest( + amount_msat=10000, description="test", expiry_time=3600 +) +invoice_response = fedimint_client.ln.create_invoice(invoice_request) + +print("Created 10 sat Invoice: ", invoice_response["invoice"]) + +print("Waiting for payment...") + +await_invoice_request = AwaitInvoiceRequest( + operation_id=invoice_response["operation_id"] +) +payment_response = fedimint_client.ln.await_invoice(await_invoice_request) + +print("Payment Received!") +print("New Total Msats Ecash: ", payment_response["total_amount_msat"]) diff --git a/wrappers/fedimint-py/models/__init__.py b/wrappers/fedimint-py/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wrappers/fedimint-py/models/common.py b/wrappers/fedimint-py/models/common.py new file mode 100644 index 0000000..4eebd4a --- /dev/null +++ b/wrappers/fedimint-py/models/common.py @@ -0,0 +1,35 @@ +from pydantic import RootModel, BaseModel +from typing import Optional, Dict, Any + + +class Tiered(RootModel): + root: Dict[int, Any] + + +class TieredSummary(BaseModel): + tiered: Tiered + + +class InfoResponse(BaseModel): + federation_id: str + network: str + meta: Dict[str, str] + total_amount_msat: int + total_num_notes: int + denominations_msat: TieredSummary + + +class BackupRequest(BaseModel): + metadata: Dict[str, str] + + +class ListOperationsRequest(BaseModel): + limit: int + + +class OperationOutput(BaseModel): + id: str + creation_time: str + operation_kind: str + operation_meta: Any + outcome: Optional[Any] diff --git a/wrappers/fedimint-py/models/ln.py b/wrappers/fedimint-py/models/ln.py new file mode 100644 index 0000000..3d09006 --- /dev/null +++ b/wrappers/fedimint-py/models/ln.py @@ -0,0 +1,44 @@ +from pydantic import BaseModel +from typing import Optional + + +class LnInvoiceRequest(BaseModel): + amount_msat: int + description: str + expiry_time: Optional[int] + + +class LnInvoiceResponse(BaseModel): + operation_id: str + invoice: str + + +class AwaitInvoiceRequest(BaseModel): + operation_id: str + + +class LnPayRequest(BaseModel): + payment_info: str + amount_msat: Optional[int] + finish_in_background: bool + lnurl_comment: Optional[str] + + +class LnPayResponse(BaseModel): + operation_id: str + payment_type: str + contract_id: str + fee: int + + +class AwaitLnPayRequest(BaseModel): + operation_id: str + + +class Gateway(BaseModel): + node_pub_key: str + active: bool + + +class SwitchGatewayRequest(BaseModel): + gateway_id: str diff --git a/wrappers/fedimint-py/models/mint.py b/wrappers/fedimint-py/models/mint.py new file mode 100644 index 0000000..bde3ce2 --- /dev/null +++ b/wrappers/fedimint-py/models/mint.py @@ -0,0 +1,90 @@ +from pydantic import BaseModel, RootModel +from typing import Optional, Dict, List, Union + + +class FederationIdPrefix(RootModel): + root: List[int] + + +class Fp(RootModel): + root: List[int] + + +class Choice(RootModel): + root: int + + +class G1Affine(BaseModel): + x: Fp + y: Fp + infinity: Choice + + +class Signature(RootModel): + root: G1Affine + + +class KeyPair(RootModel): + root: List[int] + + +class SpendableNote(BaseModel): + signature: Signature + spend_key: KeyPair + + +class TieredMulti(RootModel): + root: Dict[int, List[SpendableNote]] + + +class OOBNotesData(BaseModel): + Notes: Optional[TieredMulti] + FederationIdPrefix: Optional[FederationIdPrefix] + Default: Optional[Dict[str, Union[int, List[int]]]] + + +class OOBNotes(RootModel): + root: List[OOBNotesData] + + +class ReissueRequest(BaseModel): + notes: OOBNotes + + +class ReissueResponse(BaseModel): + amount_msat: int + + +class SpendRequest(BaseModel): + amount_msat: int + allow_overpay: bool + timeout: int + + +class SpendResponse(BaseModel): + operation: str + notes: OOBNotes + + +class ValidateRequest(BaseModel): + notes: OOBNotes + + +class ValidateResponse(BaseModel): + amount_msat: int + + +class SplitRequest(BaseModel): + notes: OOBNotes + + +class SplitResponse(BaseModel): + notes: Dict[int, OOBNotes] + + +class CombineRequest(BaseModel): + notes: List[OOBNotes] + + +class CombineResponse(BaseModel): + notes: OOBNotes diff --git a/wrappers/fedimint-py/models/wallet.py b/wrappers/fedimint-py/models/wallet.py new file mode 100644 index 0000000..83879cb --- /dev/null +++ b/wrappers/fedimint-py/models/wallet.py @@ -0,0 +1,28 @@ +from pydantic import BaseModel + + +class DepositAddressRequest(BaseModel): + timeout: int + + +class DepositAddressResponse(BaseModel): + operation_id: str + address: str + + +class AwaitDepositRequest(BaseModel): + operation_id: str + + +class AwaitDepositResponse(BaseModel): + status: str + + +class WithdrawRequest(BaseModel): + address: str + amount_msat: str + + +class WithdrawResponse(BaseModel): + txid: str + fees_sat: int diff --git a/wrappers/fedimint-py/requirements.txt b/wrappers/fedimint-py/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/wrappers/fedimint-py/requirements.txt @@ -0,0 +1 @@ +requests