1
1
mirror of https://github.com/bumi/lntip synced 2025-06-16 09:45:35 +00:00

Merge pull request #11 from bumi/lightning-address

Add Lightning Address support
This commit is contained in:
bumi 2021-08-29 08:55:42 +02:00 committed by GitHub
commit 7283544b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 6 deletions

View File

@ -1,19 +1,20 @@
# LnMe - your friendly ⚡ payment page # LnMe - your friendly ⚡ payment page
LnMe is a personal Bitcoin Lightning payment website and payment widget. LnMe is a personal Bitcoin Lightning payment page/widget and self-hosted [Lightning Address](https://lightningaddress.com/) server.
![demo](./lnme-demo.gif) ![demo](./lnme-demo.gif)
**See it in action: [ln.michaelbumann.com](https://ln.michaelbumann.com/)** **See it in action: [ln.michaelbumann.com](https://ln.michaelbumann.com/) - my lightning address: bumi@ln.michaelbumann.com**
LnMe focusses on simplicity and ease of deployment. It connects to an existing lightning node and provides a configurable personal payment page and offers a JavaScript widget to integrate into existing websites. LnMe focusses on simplicity and ease of deployment. It connects to an existing lightning node (currently LND is supported).
LnMe is one [simple executable](https://github.com/bumi/lnme/releases) file that can be deployed anywhere with no dependencies. (on your own node or for example with [one click on Heroku](#heroku)) LnMe is one [simple executable](https://github.com/bumi/lnme/releases) file that can be deployed anywhere with no dependencies. (on your own node or for example with [one click on Heroku](#heroku))
## Installation ## Installation
LnMe connects to your [LND node](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md), so a running LND node is required. LnMe connects to your [LND node](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md), so a running LND node is required.
LnMe can easily run next to LND on the same system or any other hosting provider. LnMe can easily run next to LND on the same system or any other hosting provider.
1. Download the latest [release](https://github.com/bumi/lnme/releases) 1. Download the latest [release](https://github.com/bumi/lnme/releases)
@ -49,6 +50,7 @@ Instead of the path to the macaroon and cert files you can also provide the hex
* `static-path`: Path to a folder that you want to serve with LnMe (e.g. /home/bitcoin/lnme/website). Use this if you want to customize your ⚡website. default: disabled * `static-path`: Path to a folder that you want to serve with LnMe (e.g. /home/bitcoin/lnme/website). Use this if you want to customize your ⚡website. default: disabled
* `disable-website`: Disable the default LnMe website. Disable the website if you only want to embed the LnMe widget on your existing website. * `disable-website`: Disable the default LnMe website. Disable the website if you only want to embed the LnMe widget on your existing website.
* `disable-cors`: Disable CORS headers. (default: false) * `disable-cors`: Disable CORS headers. (default: false)
* `disable-ln-address`: Disable [Lightning Address](https://lightningaddress.com/) handling.
* `port`: Port to listen on. (default: 1323) * `port`: Port to listen on. (default: 1323)
* `request-limit`: Limit the allowed requests per second. (default: 5) * `request-limit`: Limit the allowed requests per second. (default: 5)
@ -93,10 +95,10 @@ To get the HEX versions of the files use `xxd -ps -u -c 100000` e.g. `cat invoic
It is the easiest to run LnMe on the same node as LND. But you can run it anywhere as long as your LND node is accessible. It is the easiest to run LnMe on the same node as LND. But you can run it anywhere as long as your LND node is accessible.
#### Heroku #### Heroku
One click deployment with Heroku: One click deployment with Heroku:
You will need your LND address, the LND tls certificate (HEX) and the macaroon (HEX). You will need your LND address, the LND tls certificate (HEX) and the macaroon (HEX).
[![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/bumi/lnme) [![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/bumi/lnme)
@ -117,6 +119,16 @@ lnme.michaelbumann.com {
`$ caddy --config /etc/caddy/Caddyfile` `$ caddy --config /etc/caddy/Caddyfile`
### Lightning Address
The Lightning Address is an Internet Identifier that allows anyone to send you Bitcoin over the Lightning Network.
Lightning Address builds on [LNURL-pay](https://github.com/fiatjaf/lnurl-rfc/blob/luds/06.md) LnMe handles the necessary requests for you.
For more information check out the website: [lightningaddress.com](https://lightningaddress.com/)
Your Lightning Address: `{anything}@{your domain}`
### Customize your ⚡ website ### Customize your ⚡ website
LnMe comes with a default website but you can easily configure and build your own using the the LnMe JavaScript widget or JSON API. LnMe comes with a default website but you can easily configure and build your own using the the LnMe JavaScript widget or JSON API.

45
lnme.go
View File

@ -1,14 +1,19 @@
package main package main
import ( import (
"crypto/sha256"
"encoding/hex"
"flag" "flag"
"fmt"
"log" "log"
"net/http" "net/http"
"os" "os"
"strconv"
"strings" "strings"
rice "github.com/GeertJohan/go.rice" rice "github.com/GeertJohan/go.rice"
"github.com/bumi/lnme/ln" "github.com/bumi/lnme/ln"
"github.com/bumi/lnme/lnurl"
"github.com/didip/tollbooth/v6" "github.com/didip/tollbooth/v6"
"github.com/didip/tollbooth/v6/limiter" "github.com/didip/tollbooth/v6/limiter"
"github.com/knadh/koanf" "github.com/knadh/koanf"
@ -137,6 +142,45 @@ func main() {
return c.JSON(http.StatusOK, invoice) return c.JSON(http.StatusOK, invoice)
}) })
if !cfg.Bool("disable-ln-address") {
e.GET("/.well-known/lnurlp/:name", func(c echo.Context) error {
name := c.Param("name")
lightningAddress := name + "@" + c.Request().Host
lnurlMetadata := "[[\"text/identifier\", \"" + lightningAddress + "\"], [\"text/plain\", \"Sats for " + lightningAddress + "\"]]"
if amount := c.QueryParam("amount"); amount == "" {
lnurlPayResponse1 := lnurl.LNURLPayResponse1{
LNURLResponse: lnurl.LNURLResponse{Status: "OK"},
Callback: fmt.Sprintf("%s://%s%s", c.Scheme(), c.Request().Host, c.Request().URL.Path),
MinSendable: 1000,
MaxSendable: 100000000,
EncodedMetadata: lnurlMetadata,
CommentAllowed: 0,
Tag: "payRequest",
}
return c.JSON(http.StatusOK, lnurlPayResponse1)
} else {
stdOutLogger.Printf("New LightningAddress request amount: %s", amount)
msats, err := strconv.ParseInt(amount, 10, 64)
if err != nil || msats < 1000 {
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Invalid Amount"})
}
sats := msats / 1000 // we need sats
metadataHash := sha256.Sum256([]byte(lnurlMetadata))
memo := hex.EncodeToString(metadataHash[:])
invoice, err := lnClient.AddInvoice(sats, memo)
lnurlPayResponse2 := lnurl.LNURLPayResponse2{
LNURLResponse: lnurl.LNURLResponse{Status: "OK"},
PR: invoice.PaymentRequest,
Routes: make([][]lnurl.RouteInfo, 0),
Disposable: false,
SuccessAction: &lnurl.SuccessAction{Tag: "message", Message: "Thanks, payment received!"},
}
return c.JSON(http.StatusOK, lnurlPayResponse2)
}
})
}
// Debug test endpoint // Debug test endpoint
e.GET("/ping", func(c echo.Context) error { e.GET("/ping", func(c echo.Context) error {
return c.JSON(http.StatusOK, "pong") return c.JSON(http.StatusOK, "pong")
@ -157,6 +201,7 @@ func LoadConfig() *koanf.Koanf {
f.String("lnd-macaroon-path", "~/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon", "Path to the LND macaroon file.") f.String("lnd-macaroon-path", "~/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon", "Path to the LND macaroon file.")
f.String("lnd-cert-path", "~/.lnd/tls.cert", "Path to the LND tls.cert file.") f.String("lnd-cert-path", "~/.lnd/tls.cert", "Path to the LND tls.cert file.")
f.Bool("disable-website", false, "Disable default embedded website.") f.Bool("disable-website", false, "Disable default embedded website.")
f.Bool("disable-ln-address", false, "Disable Lightning Address handling")
f.Bool("disable-cors", false, "Disable CORS headers.") f.Bool("disable-cors", false, "Disable CORS headers.")
f.Float64("request-limit", 5, "Request limit per second.") f.Float64("request-limit", 5, "Request limit per second.")
f.String("static-path", "", "Path to a static assets directory.") f.String("static-path", "", "Path to a static assets directory.")

52
lnurl/types.go Normal file
View File

@ -0,0 +1,52 @@
// THANKS: https://github.com/fiatjaf/go-lnurl/blob/d50a8e916232580895822178fe36e0f5cf400554/base.go
// only using the LNURL types here
package lnurl
import "net/url"
type LNURLResponse struct {
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
}
type LNURLPayResponse1 struct {
LNURLResponse
Callback string `json:"callback"`
CallbackURL *url.URL `json:"-"`
Tag string `json:"tag"`
MaxSendable int64 `json:"maxSendable"`
MinSendable int64 `json:"minSendable"`
EncodedMetadata string `json:"metadata"`
Metadata Metadata `json:"-"`
CommentAllowed int64 `json:"commentAllowed"`
}
type LNURLPayResponse2 struct {
LNURLResponse
SuccessAction *SuccessAction `json:"successAction"`
Routes [][]RouteInfo `json:"routes"`
PR string `json:"pr"`
Disposable bool `json:"disposable,omitempty"`
}
type RouteInfo struct {
NodeId string `json:"nodeId"`
ChannelUpdate string `json:"channelUpdate"`
}
type SuccessAction struct {
Tag string `json:"tag"`
Description string `json:"description,omitempty"`
URL string `json:"url,omitempty"`
Message string `json:"message,omitempty"`
Ciphertext string `json:"ciphertext,omitempty"`
IV string `json:"iv,omitempty"`
}
type LNURLErrorResponse struct {
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
URL *url.URL `json:"-"`
}
type Metadata [][]string