Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/elixir lfg #7

Merged
merged 13 commits into from
Sep 30, 2024
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
LN_BITS_API_ENDPOINT=https://your-lnbits-node
LN_BITS_API_KEY=ADMIN_API_KEY
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ cashubrew-*.tar

priv/static/

benchmarks/output
benchmarks/output

.env
2 changes: 1 addition & 1 deletion infra/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3.8"

services:
postgres:
image: postgres:13
image: postgres:17
container_name: cashubrew_postgres
environment:
POSTGRES_DB: cashubrew_dev
Expand Down
4 changes: 3 additions & 1 deletion lib/cashubrew/application.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule Cashubrew.Application do
@moduledoc false
use Application
alias Cashubrew.Lightning.LightningNetworkService
alias Cashubrew.Lightning.MockLightningNetworkService
alias Cashubrew.Web.Endpoint

Expand All @@ -10,7 +11,8 @@ defmodule Cashubrew.Application do
Cashubrew.Web.Telemetry,
{Phoenix.PubSub, name: Cashubrew.PubSub},
Endpoint,
MockLightningNetworkService
MockLightningNetworkService,
LightningNetworkService
]

# Conditionally add the appropriate repo to the children list
Expand Down
90 changes: 88 additions & 2 deletions lib/cashubrew/core/mint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ defmodule Cashubrew.Mint do
use GenServer
alias Cashubrew.Cashu.BlindSignature
alias Cashubrew.Crypto.BDHKE
alias Cashubrew.Lightning.LightningNetworkService
alias Cashubrew.Lightning.MockLightningNetworkService
alias Cashubrew.Schema.{Key, Keyset, MintConfiguration, MintQuote}
alias Cashubrew.LNBitsApi
alias Cashubrew.Query.MeltTokens
alias Cashubrew.Schema.{Key, Keyset, MeltQuote, MeltTokens, MintConfiguration, MintQuote}

import Ecto.Query

Expand Down Expand Up @@ -123,7 +126,7 @@ defmodule Cashubrew.Mint do
def handle_call({:create_mint_quote, amount, description}, _from, state) do
repo = Application.get_env(:cashubrew, :repo)

case MockLightningNetworkService.create_invoice(amount, description) do
case LightningNetworkService.create_invoice(amount, description) do
{:ok, payment_request, _payment_hash} ->
# 1 hour expiry
expiry = :os.system_time(:second) + 3600
Expand All @@ -133,6 +136,7 @@ defmodule Cashubrew.Mint do
payment_request: payment_request,
expiry: expiry,
description: description
# payment_hash: _payment_hash,
}

case repo.insert(MintQuote.changeset(%MintQuote{}, attrs)) do
Expand Down Expand Up @@ -221,6 +225,80 @@ defmodule Cashubrew.Mint do
{:ok, signatures}
end

def handle_call({:create_melt_quote, request, unit}, _from, state) do
repo = Application.get_env(:cashubrew, :repo)
# # Check LN invoice and info
{:ok, invoice} = Bitcoinex.LightningNetwork.decode_invoice(request)
# To call the function and print the hash:
{:ok, request} = RandomHash.generate_hash()
# Used amount
# If :amount exists, returns its value; otherwise returns 1000
amount = Map.get(invoice, :amount_msat, 1000)

fee_reserve = 0
# Create and Saved melt quote
expiry = :os.system_time(:second) + 3600

attrs = %{
# quote_id
request: request,
unit: unit,
amount: amount,
fee_reserve: fee_reserve,
expiry: expiry,
request_lookup_id: request
}

case repo.insert(MeltQuote.changeset(%MeltQuote{}, attrs)) do
{:ok, melt_quote} ->
{:reply, {:ok, melt_quote}, state}

{:error, changeset} ->
{:reply, {:error, changeset}, state}
end
end

def handle_call({:create_melt_tokens, quote_id, inputs}, _from, state) do
repo = Application.get_env(:cashubrew, :repo)

# TODO
# Verify quote_id

{:ok, melt_find} = Cashubrew.Query.MeltTokens.get_melt_by_quote_id!(quote_id)
IO.puts("melt_find: #{melt_find}")

# Check if quote is already paid or not

# Check total amount

# Check proofs

# Verify proof spent

fee_reserve = 0
# Create and Saved melt quote

attrs = %{
# quote_id
request: quote_id,
unit: quote_id,
amount: 0,
fee_reserve: 0,
expiry: 0,
request_lookup_id: quote_id
}

expiry = :os.system_time(:second) + 3600

case repo.insert(MeltTokens.changeset(%MeltTokens{}, attrs)) do
{:ok, melt_quote} ->
{:reply, {:ok, melt_quote}, state}

{:error, changeset} ->
{:reply, {:error, changeset}, state}
end
end

# Public API

def get_keysets do
Expand Down Expand Up @@ -267,4 +345,12 @@ defmodule Cashubrew.Mint do
def mint_tokens(quote, blinded_messages) do
GenServer.call(__MODULE__, {:mint_tokens, quote, blinded_messages})
end

def create_melt_quote(request, unit) do
GenServer.call(__MODULE__, {:create_melt_quote, request, unit})
end

def create_melt_tokens(quote_id, inputs) do
GenServer.call(__MODULE__, {:create_melt_tokens, quote_id, inputs})
end
end
24 changes: 24 additions & 0 deletions lib/cashubrew/crypto/random.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule RandomHash do
@moduledoc """
Random Hash module
"""
# Import the necessary modules
require Logger
alias :crypto, as: Crypto

def generate_hash do
# Step 1: Generate a random 32-byte binary
random_bytes = :crypto.strong_rand_bytes(32)

# Step 2: Hash the random bytes using SHA-256
hash = Crypto.hash(:sha256, random_bytes)

# Step 3: Encode the hash in hexadecimal
hash_hex = Base.encode16(hash, case: :lower)

# Return the result
{:ok, hash_hex}
rescue
error -> {:error, error}
end
end
25 changes: 25 additions & 0 deletions lib/cashubrew/db/query/melt_tokens.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Cashubrew.Query.MeltTokens do
@moduledoc """
Query Melt Tokens
"""
import Ecto.Query, warn: false
alias Cashubrew.Repo
alias Cashubrew.Schema.MeltTokens

# Fetch all users
def list_melt_tokens do
Repo.all(MeltTokens)
end

# Fetch a quote by id
def get_melt_by_quote_id!(quote_id) do
query =
from(u in MeltTokens,
where: u.request == ^quote_id,
select: u
)

# Return a single user (or nil if no match)
Repo.one(query)
end
end
54 changes: 54 additions & 0 deletions lib/cashubrew/lightning/lightning_network_service.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
defmodule Cashubrew.Lightning.LightningNetworkService do
@moduledoc """
Lightning Network Services.
"""
use GenServer
alias Cashubrew.LNBitsApi

def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end

def init(_) do
{:ok, %{invoices: %{}}}
end

def create_invoice(amount, description) do
GenServer.call(__MODULE__, {:create_invoice, amount, description})
end

def check_payment(payment_hash) do
GenServer.call(__MODULE__, {:check_payment, payment_hash})
end

def handle_call({:create_invoice, amount, _description}, _from, state) do
unit_input = "sat"

attributes = %{
out: "false",
amount: amount,
unit_input: unit_input
}

case LNBitsApi.post_data("api/v1/payments", attributes) do
{:ok, response_body} ->
IO.puts("Success create in: #{response_body}")

response_map = Jason.decode!(response_body)
payment_hash = response_map["payment_hash"]
payment_request = response_map["payment_request"]
{:reply, {:ok, payment_request, payment_hash}, response_map}

{:error, reason} ->
IO.puts("Error: #{reason}")
end
end

def handle_call({:check_payment, payment_hash}, _from, state) do
case get_in(state, [:invoices, payment_hash]) do
nil -> {:reply, {:error, :not_found}, state}
%{paid: true} -> {:reply, {:ok, :paid}, state}
%{paid: false} -> {:reply, {:ok, :unpaid}, state}
end
end
end
53 changes: 53 additions & 0 deletions lib/cashubrew/lightning/ln_bits.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule Cashubrew.LNBitsApi do
@moduledoc """
LN BITS Api Network Services.
"""
Dotenv.load()

@api_endpoint System.get_env("LN_BITS_API_ENDPOINT")
@api_key System.get_env("LN_BITS_API_KEY")

def fetch_data(path, attributes) do
api_base_url = System.get_env("LN_BITS_API_ENDPOINT")
api_key = System.get_env("LN_BITS_API_KEY")
headers = [{"X-Api-Key", "#{api_key}"}]
full_url = "#{api_base_url}#{path}"

case HTTPoison.get(full_url, headers) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
{:ok, body}

{:ok, %HTTPoison.Response{status_code: status_code}} ->
{:error, "Received #{status_code} status code"}

{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
end
end

# Function to send a POST request with a JSON body
def post_data(path, attributes) do
api_base_url = System.get_env("LN_BITS_API_ENDPOINT")
api_key = System.get_env("LN_BITS_API_KEY")

headers = [
{"X-Api-Key", "#{api_key}"},
{"Content-Type", "application/json"}
]

# Convert Elixir map to JSON string
body = Jason.encode!(attributes)
full_url = "#{api_base_url}#{path}"

case HTTPoison.post(full_url, body, headers) do
{:ok, %HTTPoison.Response{status_code: 201, body: response_body}} ->
{:ok, response_body}

{:ok, %HTTPoison.Response{status_code: status_code, body: error_body}} ->
{:error, "Received #{status_code}: #{error_body}"}

{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}
end
end
end
21 changes: 21 additions & 0 deletions lib/cashubrew/lightning/ln_lib.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Cashubrew.LightningLib do
@moduledoc """
Mock Lightning Network Service for testing purposes.
"""
use GenServer

def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end

def init(_) do
{:ok, %{invoices: %{}}}
end

def decode_invoice(payment_hash) do
GenServer.call(__MODULE__, {:decode_invoice, payment_hash})
end

def handle_call({:decode_invoice, invoice}, _from, state) do
end
end
14 changes: 14 additions & 0 deletions lib/cashubrew/mix/tasks/test.e2e.mint.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Mix.Tasks.Test.E2e.Mint do
@moduledoc """
Test for the BMint
"""
use Mix.Task

@shortdoc "Runs the Mint end-to-end test"
def run(_) do
Mix.Task.run("app.start", ["--no-start"])
ExUnit.start()
Code.require_file("test/integration_mint.exs")
ExUnit.run()
end
end
25 changes: 25 additions & 0 deletions lib/cashubrew/schema/melt_quote.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Cashubrew.Schema.MeltQuote do
@moduledoc """
Schema for a mint quote.
"""
use Ecto.Schema
import Ecto.Changeset

schema "melt_quote" do
field(:request, :string)
field(:unit, :string)
# Make sure to use the correct field name
field(:amount, :integer)
field(:fee_reserve, :integer)
field(:expiry, :integer)
field(:request_lookup_id, :string)

timestamps()
end

def changeset(quote, attrs) do
quote
|> cast(attrs, [:request, :unit, :amount, :fee_reserve, :expiry, :request_lookup_id])
|> validate_required([:request, :unit, :amount, :fee_reserve, :expiry, :request_lookup_id])
end
end
Loading
Loading