-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
579 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Used by "mix format" | ||
[ | ||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# The directory Mix will write compiled artifacts to. | ||
/_build/ | ||
|
||
# If you run "mix test --cover", coverage assets end up here. | ||
/cover/ | ||
|
||
# The directory Mix downloads your dependencies sources to. | ||
/deps/ | ||
|
||
# Where third-party dependencies like ExDoc output generated docs. | ||
/doc/ | ||
|
||
# Ignore .fetch files in case you like to edit your project deps locally. | ||
/.fetch | ||
|
||
# If the VM crashes, it generates a dump, let's ignore it too. | ||
erl_crash.dump | ||
|
||
# Also ignore archive artifacts (built via "mix archive.build"). | ||
*.ez | ||
|
||
# Ignore package tarball (built via "mix hex.build"). | ||
fedimintex-*.tar | ||
|
||
# Temporary files, for example, from tests. | ||
/tmp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Fedimintex | ||
|
||
# Fedimintex: Fedimint SDK in Elixir | ||
|
||
This is an elixir library that consumes Fedimint HTTP (https://github.com/kodylow/fedimint-http-client)[https://github.com/kodylow/fedimint-http-client], communicating with it via REST endpoints + passowrd. It's a hacky prototype, but it works until we can get a proper elixir client for Fedimint. All of the federation handling code happens in the fedimint-http, this just exposes a simple API for interacting with the client from elixir (mirrored in Go, Python, and TS). | ||
|
||
Start the following in the fedimint-http-client `.env` file: | ||
|
||
```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 use the elixir client, which will use the same base url and password as the fedimint-http-client, so you'll need to set those in your elixir project's `.env` file: | ||
|
||
```bash | ||
export BASE_URL='http://localhost:5000' | ||
export PASSWORD='password' | ||
``` | ||
|
||
Source the `.env` file and enter the iex shell: | ||
|
||
```bash | ||
source .env | ||
iex -S mix | ||
``` | ||
|
||
Then you can use the client: | ||
|
||
```bash | ||
iex > client = Fedimintex.new() | ||
iex > invoice = Fedimintex.ln.create_invoice(client, 1000) | ||
# pay the invoice | ||
iex > Fedimintex.ln.await_invoice | ||
``` | ||
|
||
## Installation | ||
|
||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed | ||
by adding `fedimintex` to your list of dependencies in `mix.exs`: | ||
|
||
```elixir | ||
def deps do | ||
[ | ||
{:fedimintex, "~> 0.1.0"} | ||
] | ||
end | ||
``` | ||
|
||
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) | ||
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can | ||
be found at <https://hexdocs.pm/fedimintex>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export PASSWORD='password' | ||
export DOMAIN='localhost' | ||
export PORT=5000 | ||
export BASE_URL=http://localhost:5000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
s: | ||
iex -S mix | ||
env: | ||
source .env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
defmodule Fedimintex.Admin do | ||
import Fedimintex.Client, only: [post: 3, get: 2] | ||
|
||
@type tiered :: %{required(integer()) => any()} | ||
@type tiered_summary :: %{required(:tiered) => tiered()} | ||
@type info_response :: %{ | ||
required(:federation_id) => String.t(), | ||
required(:network) => String.t(), | ||
required(:meta) => %{required(String.t()) => String.t()}, | ||
required(:total_amount_msat) => integer(), | ||
required(:total_num_notes) => integer(), | ||
required(:denominations_msat) => tiered_summary() | ||
} | ||
|
||
@doc """ | ||
Fetches wallet (mint and onchain) information including holdings, tiers, and federation metadata. | ||
""" | ||
@spec info(Fedimintex.Client.t()) :: {:ok, info_response()} | {:error, String.t()} | ||
def info(client) do | ||
get(client, "/admin/info") | ||
end | ||
|
||
@type backup_request :: %{required(:metadata) => %{required(String.t()) => String.t()}} | ||
|
||
@doc """ | ||
Uploads the encrypted snapshot of mint notest to the federation | ||
""" | ||
def backup(client, metadata) do | ||
post(client, "/admin/backup", metadata) | ||
end | ||
|
||
@type version_response :: %{required(:version) => String.t()} | ||
|
||
@doc """ | ||
Discovers the highest common version of the mint and api | ||
""" | ||
@spec discover_version(Fedimintex.Client.t()) :: | ||
{:ok, version_response()} | {:error, String.t()} | ||
def discover_version(client) do | ||
get(client, "/admin/discover-version") | ||
end | ||
|
||
@type list_operations_request :: %{required(:limit) => integer()} | ||
@type operation_output :: %{ | ||
required(:id) => String.t(), | ||
required(:creation_time) => String.t(), | ||
required(:operation_kind) => String.t(), | ||
required(:operation_meta) => any(), | ||
optional(:outcome) => any() | ||
} | ||
@type list_operations_response :: [operation_output()] | ||
|
||
@doc """ | ||
Lists all ongoing operations | ||
""" | ||
@spec list_operations(Fedimintex.Client.t(), list_operations_request()) :: | ||
{:ok, list_operations_response()} | {:error, String.t()} | ||
def list_operations(client, request) do | ||
post(client, "/admin/list-operations", request) | ||
end | ||
|
||
@type config_response :: map() | ||
|
||
@doc """ | ||
Get configuration information | ||
""" | ||
@spec config(Fedimintex.Client.t()) :: {:ok, config_response()} | {:error, String.t()} | ||
def config(client) do | ||
get(client, "/admin/config") | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
defmodule Fedimintex.Client do | ||
@moduledoc """ | ||
Handles HTTP requests for the `Fedimintex` client. | ||
""" | ||
|
||
@type t :: %__MODULE__{ | ||
base_url: String.t(), | ||
password: String.t(), | ||
admin: atom(), | ||
mint: atom(), | ||
ln: atom(), | ||
wallet: atom() | ||
} | ||
|
||
@type http_response :: {:ok, map()} | {:error, String.t()} | ||
|
||
defstruct base_url: nil, password: nil, admin: nil, mint: nil, ln: nil, wallet: nil | ||
|
||
@doc """ | ||
Creates a new `Fedimintex.Client` struct. | ||
""" | ||
@spec new() :: t() | {:error, String.t()} | ||
def new() do | ||
base_url = System.get_env("BASE_URL") | ||
password = System.get_env("PASSWORD") | ||
new(base_url, password) | ||
end | ||
|
||
@spec new(nil, nil) :: {:error, String.t()} | ||
def new(nil, nil), do: {:error, "Could not load base_url and password from environment."} | ||
|
||
@spec new(String.t(), String.t()) :: t() | ||
def new(base_url, password) do | ||
%__MODULE__{ | ||
base_url: base_url <> "/fedimint/v2", | ||
password: password, | ||
admin: Fedimintex.Admin, | ||
mint: Fedimintex.Mint, | ||
ln: Fedimintex.Ln, | ||
wallet: Fedimintex.Wallet | ||
} | ||
end | ||
|
||
@doc """ | ||
Makes a GET request to the `baseURL` at the given `endpoint`. | ||
Receives a JSON response. | ||
""" | ||
@spec get(t(), String.t()) :: http_response() | ||
def get(%__MODULE__{base_url: base_url, password: password}, endpoint) do | ||
headers = [{"Authorization", "Bearer #{password}"}] | ||
|
||
(base_url <> endpoint) | ||
|> Req.get!(headers: headers) | ||
|> handle_response() | ||
end | ||
|
||
@doc """ | ||
Makes a POST request to the `baseURL` at the given `endpoint` | ||
Receives a JSON response. | ||
""" | ||
@spec post(t(), String.t(), map()) :: http_response() | ||
def post(%__MODULE__{password: password, base_url: base_url}, endpoint, body) do | ||
headers = [ | ||
{"Authorization", "Bearer #{password}"}, | ||
{"Content-Type", "application/json"} | ||
] | ||
|
||
(base_url <> endpoint) | ||
|> Req.post!(json: body, headers: headers) | ||
|> handle_response() | ||
end | ||
|
||
@spec handle_response(Req.Response.t()) :: http_response() | ||
defp handle_response(%{status: 200, body: body}) do | ||
case Jason.decode(body) do | ||
{:ok, body} -> {:ok, body} | ||
{:error, _} -> {:error, "Failed to decode JSON, got #{body}"} | ||
end | ||
end | ||
|
||
defp handle_response(%{status: status}) do | ||
{:error, "Request failed with status #{status}"} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
defmodule Fedimintex.Example do | ||
alias Fedimintex.{Client, Ln} | ||
alias Fedimintex.Ln.{InvoiceRequest, AwaitInvoiceRequest} | ||
|
||
def main() do | ||
client = Fedimintex.Client.new() | ||
|
||
case Client.get(client, "/admin/info") do | ||
{:ok, body} -> IO.puts("Current Total Msats Ecash: " <> body["total_amount_msat"]) | ||
{:error, err} -> IO.inspect(err) | ||
end | ||
|
||
invoice_request = %InvoiceRequest{amount_msat: 10000, description: "test", expiry_time: 3600} | ||
invoice_response = Ln.create_invoice(client, invoice_request) | ||
IO.puts(invoice_response["invoice"]) | ||
|
||
await_invoice_request = %AwaitInvoiceRequest{operation_id: invoice_response["operation_id"]} | ||
payment_response = Ln.await_invoice(client, await_invoice_request) | ||
|
||
case payment_response do | ||
{:ok, resp} -> | ||
IO.puts("Payment received!") | ||
IO.puts("New Total Msats Ecash: " <> resp["total_amount_msat"]) | ||
|
||
{:error, err} -> | ||
IO.inspect(err) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
defmodule Fedimintex do | ||
@moduledoc """ | ||
Documentation for `Fedimintex`. | ||
""" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
defmodule Fedimintex.Ln do | ||
alias Fedimintex.Client | ||
|
||
alias Fedimint.Ln.{ | ||
AwaitInvoiceRequest, | ||
InvoiceRequest, | ||
InvoiceResponse, | ||
PayRequest, | ||
PayResponse, | ||
AwaitPayRequest, | ||
Gateway, | ||
SwitchGatewayRequest | ||
} | ||
|
||
@spec create_invoice(Client.t(), InvoiceRequest.t()) :: | ||
{:ok, InvoiceResponse.t()} | {:error, String.t()} | ||
def create_invoice(client, request) do | ||
Client.post(client, "/ln/invoice", request) | ||
end | ||
|
||
@spec await_invoice(Client.t(), AwaitInvoiceRequest.t()) :: | ||
{:ok, InvoiceResponse.t()} | {:error, String.t()} | ||
def await_invoice(client, request) do | ||
Client.post(client, "/ln/await-invoice", request) | ||
end | ||
|
||
@spec pay(Client.t(), PayRequest.t()) :: {:ok, PayResponse.t()} | {:error, String.t()} | ||
def pay(client, request) do | ||
Client.post(client, "/ln/pay", request) | ||
end | ||
|
||
@spec await_pay(Client.t(), AwaitPayRequest.t()) :: | ||
{:ok, PayResponse.t()} | {:error, String.t()} | ||
def await_pay(client, request) do | ||
Client.post(client, "/ln/await-pay", request) | ||
end | ||
|
||
@spec list_gateways(Client.t()) :: {:ok, [Gateway.t()]} | {:error, String.t()} | ||
def list_gateways(client) do | ||
Client.get(client, "/ln/list-gateways") | ||
end | ||
|
||
@spec switch_gateway(Client.t(), SwitchGatewayRequest.t()) :: | ||
{:ok, String.t()} | {:error, String.t()} | ||
def switch_gateway(client, request) do | ||
Client.post(client, "/ln/switch-gateway", request) | ||
end | ||
end |
Oops, something went wrong.