111 lines
2.5 KiB
Go
111 lines
2.5 KiB
Go
package lnurl
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/btcsuite/btcutil/bech32"
|
|
"github.com/ekzyis/zaply/env"
|
|
"github.com/ekzyis/zaply/lightning"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
var (
|
|
MIN_SENDABLE_AMOUNT = 1000 // 1 sat
|
|
MAX_SENDABLE_AMOUNT = 100_000_000_000 // 100m sat
|
|
MAX_COMMENT_LENGTH = 128
|
|
)
|
|
|
|
func Router(e *echo.Echo, ln lightning.Lightning) {
|
|
e.GET("/.well-known/lnurlp/:name", payRequest)
|
|
e.GET("/.well-known/lnurlp/:name/pay", pay(ln))
|
|
}
|
|
|
|
func payRequest(c echo.Context) error {
|
|
callback, err := url.JoinPath(env.PublicUrl, "/.well-known/lnurlp/", c.Param("name"), "/pay")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(
|
|
http.StatusOK,
|
|
map[string]any{
|
|
"callback": callback,
|
|
"minSendable": MIN_SENDABLE_AMOUNT,
|
|
"maxSendable": MAX_SENDABLE_AMOUNT,
|
|
"metadata": lnurlMetadata(c),
|
|
"tag": "payRequest",
|
|
"commentAllowed": MAX_COMMENT_LENGTH,
|
|
},
|
|
)
|
|
}
|
|
|
|
func pay(ln lightning.Lightning) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
qAmount := c.QueryParam("amount")
|
|
if qAmount == "" {
|
|
return lnurlError(c, http.StatusBadRequest, errors.New("amount required"))
|
|
}
|
|
msats, err := strconv.ParseInt(qAmount, 10, 64)
|
|
if err != nil {
|
|
c.Logger().Error(err)
|
|
return lnurlError(c, http.StatusBadRequest, errors.New("invalid amount"))
|
|
}
|
|
if msats < 1000 {
|
|
return lnurlError(c, http.StatusBadRequest, errors.New("amount must be at least 1000 msats"))
|
|
}
|
|
|
|
comment := c.QueryParam("comment")
|
|
|
|
pr, err := ln.CreateInvoice(msats, comment)
|
|
if err != nil {
|
|
c.Logger().Error(err)
|
|
return lnurlError(c, http.StatusInternalServerError, errors.New("failed to create invoice"))
|
|
}
|
|
|
|
return c.JSON(
|
|
http.StatusOK,
|
|
map[string]any{
|
|
"pr": pr,
|
|
"routes": []string{},
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
func lnurlMetadata(c echo.Context) string {
|
|
s := "["
|
|
s += fmt.Sprintf("[\"text/plain\",\"Paying %s@%s\"]", c.Param("name"), c.Request().Host)
|
|
s += fmt.Sprintf(",[\"text/identifier\",\"%s@%s\"]", c.Param("name"), c.Request().Host)
|
|
s += "]"
|
|
return s
|
|
}
|
|
|
|
func lnurlError(c echo.Context, code int, err error) error {
|
|
return c.JSON(code, map[string]any{"status": "ERROR", "error": err.Error()})
|
|
}
|
|
|
|
func Encode(base string, parts ...string) string {
|
|
u, err := url.JoinPath(base, parts...)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
bech32Url, err := bech32.ConvertBits([]byte(u), 8, 5, true)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
lnurl, err := bech32.Encode("lnurl", bech32Url)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return strings.ToUpper(lnurl)
|
|
}
|