mirror of
https://github.com/bumi/lntip
synced 2026-02-17 22:47:50 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b7ab33efe | |||
| 36d4dec5fd | |||
| 0fd121d130 | |||
| 9ae6153d47 | |||
| 3b7ac31615 | |||
| 22bc75d564 | |||
|
|
2bcd2fd96f | ||
| 9ec4db5144 | |||
| 4a4614d8fb | |||
|
|
00b33e3bd9 | ||
|
|
109fa20593 | ||
| 24cdca056f | |||
|
|
03117dc326 | ||
|
|
1fe4dd3dd6 | ||
|
|
5dd2c2cdb5 | ||
|
|
3200622b97 | ||
| 561610a7b9 | |||
| 6569ac0ded | |||
| 2a4b943887 | |||
| 4fb8fa9a73 | |||
| 492ffb2d3f | |||
| 148dfcba22 | |||
| 1292e5498f | |||
| 1fff67684a | |||
| 8ff22327c9 | |||
| 7efef4a2b6 | |||
| 91c62ed7ed | |||
| fec31b2bd1 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
custom: https://ln.michaelbumann.com?lightning=lnurlp:ln.michaelbumann.com/lnurlp/github
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
vendor
|
||||
config.toml
|
||||
config.toml
|
||||
|
||||
data-dir*
|
||||
1
.lndonate
Normal file
1
.lndonate
Normal file
@@ -0,0 +1 @@
|
||||
lnurlp://ln.michaelbumann.com/.well-known/lnurlp/bumi
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Michael Bumann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
19
README.md
19
README.md
@@ -2,7 +2,6 @@
|
||||
|
||||
LnMe is a personal Bitcoin Lightning payment page/widget and self-hosted [Lightning Address](https://lightningaddress.com/) server.
|
||||
|
||||
|
||||

|
||||
|
||||
**See it in action: [ln.michaelbumann.com](https://ln.michaelbumann.com/) - my lightning address: bumi@ln.michaelbumann.com**
|
||||
@@ -25,7 +24,7 @@ LnMe is one [simple executable](https://github.com/bumi/lnme/releases) file that
|
||||
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.
|
||||
|
||||
There are no other dependencies. Simply download the binary and run it!
|
||||
There are no other dependencies. Simply download the binary and run it!
|
||||
|
||||
1. Download the latest [release](https://github.com/bumi/lnme/releases)
|
||||
2. Run `lnme`
|
||||
@@ -101,6 +100,11 @@ Use the LND [macaroon bakery](http://macaroon-bakery.freedomnode.com/) to create
|
||||
|
||||
To get the HEX versions of the files use `xxd -plain` e.g. `xxd -plain invoice.macaroon | tr -d '\n'`
|
||||
|
||||
### TOR
|
||||
|
||||
LnMe can connect to your lightning node through [Tor](https://www.torproject.org/). You need to have Tor installed on your system and then simply provide your LND `.onion` address (don't forget to specify the port).
|
||||
|
||||
|
||||
## Deployment
|
||||
|
||||
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. Simply run the binary and make sure the PORT is accessible.
|
||||
@@ -110,10 +114,21 @@ One click deployment with Heroku:
|
||||
|
||||
You will need your LND address, the LND tls certificate (HEX) and the macaroon (HEX).
|
||||
|
||||
When getting the HEX of the TLS certificate, use `xxd -plain tls.cert | tr -d '\n'`. The TLS cert is located in the lnd directory:
|
||||
* ~/umbrel/lnd/tls.cert on Umbrel
|
||||
* /mnt/hdd/lnd/tls.cert on Raspiblitz
|
||||
* Can also be located in ~/.lnd
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/bumi/lnme)
|
||||
|
||||
Here is a [Video Demo of the Heroku deployment](https://www.youtube.com/watch?v=hSFXhnLp_Rc)
|
||||
|
||||
In order to run Tor on Heroku, the Heroku deployment includes a non-official buildpack: https://github.com/iamashks/heroku-buildpack-tor-proxy
|
||||
This buildpack can be disabled and removed if not needed or desired, through the Settings tab on the Heroku dashboard, or by editing app.json and removing the buildpack.
|
||||
|
||||
Lastly, using the Heroku deployment, you can link the app to your own domain by following the directions here: https://help.heroku.com/MTG1BIA7/how-do-i-connect-a-domain-to-my-heroku-app
|
||||
|
||||
|
||||
### Deployment Notes
|
||||
|
||||
To run LnMe as systemd service have a look at the [systemd service example config](https://github.com/bumi/lnme/blob/master/examples/lnme.service)
|
||||
|
||||
5
app.json
5
app.json
@@ -21,6 +21,9 @@
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-go"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/iamashks/heroku-buildpack-tor-proxy.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -7,6 +7,7 @@ go 1.15
|
||||
replace go.etcd.io/etcd => go.etcd.io/etcd v0.5.0-alpha.5.0.20201125193152-8a03d2e9614b
|
||||
require (
|
||||
github.com/GeertJohan/go.rice v1.0.2
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/didip/tollbooth/v6 v6.1.1
|
||||
github.com/knadh/koanf v1.2.1
|
||||
github.com/labstack/echo/v4 v4.5.0
|
||||
|
||||
7
go.sum
7
go.sum
@@ -135,6 +135,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -568,6 +570,8 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rB
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -599,6 +603,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -636,6 +642,7 @@ golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 h1:kHSDPqCtsHZOg0nVylfTo20DDhE9gG4Y0jn7hKQ0QAM=
|
||||
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
|
||||
27
ln/lnd.go
27
ln/lnd.go
@@ -7,12 +7,15 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"gopkg.in/macaroon.v2"
|
||||
|
||||
"github.com/cretz/bine/tor"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
@@ -43,13 +46,14 @@ type LNDclient struct {
|
||||
}
|
||||
|
||||
// AddInvoice generates an invoice with the given price and memo.
|
||||
func (c LNDclient) AddInvoice(value int64, memo string) (Invoice, error) {
|
||||
func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte) (Invoice, error) {
|
||||
result := Invoice{}
|
||||
|
||||
stdOutLogger.Printf("Adding invoice: memo=%s value=%v ", memo, value)
|
||||
stdOutLogger.Printf("Adding invoice: memo=%s value=%v", memo, value)
|
||||
invoice := lnrpc.Invoice{
|
||||
Memo: memo,
|
||||
Value: value,
|
||||
Memo: memo,
|
||||
DescriptionHash: descriptionHash,
|
||||
Value: value,
|
||||
}
|
||||
res, err := c.lndClient.AddInvoice(c.ctx, &invoice)
|
||||
if err != nil {
|
||||
@@ -132,6 +136,21 @@ func NewLNDclient(lndOptions LNDoptions) (LNDclient, error) {
|
||||
grpc.WithTransportCredentials(creds),
|
||||
}
|
||||
|
||||
if strings.Contains(lndOptions.Address, ".onion") {
|
||||
// Start Tor
|
||||
t, err := tor.Start(nil, nil)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
torDialer, err := t.Dialer(context.Background(), nil)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
opts = append(opts, grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
|
||||
return torDialer.DialContext(ctx, "tcp", addr)
|
||||
}))
|
||||
}
|
||||
|
||||
var macaroonData []byte
|
||||
if lndOptions.MacaroonHex != "" {
|
||||
macBytes, err := hex.DecodeString(lndOptions.MacaroonHex)
|
||||
|
||||
38
lnme.go
38
lnme.go
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -25,6 +24,8 @@ import (
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
const DEFAULT_LISTEN = ":1323"
|
||||
|
||||
// Middleware for request limited to prevent too many requests
|
||||
// TODO: move to file
|
||||
func LimitMiddleware(lmt *limiter.Limiter) echo.MiddlewareFunc {
|
||||
@@ -110,7 +111,7 @@ func main() {
|
||||
return c.JSON(http.StatusBadRequest, "Bad request")
|
||||
}
|
||||
|
||||
invoice, err := lnClient.AddInvoice(i.Value, i.Memo)
|
||||
invoice, err := lnClient.AddInvoice(i.Value, i.Memo, nil)
|
||||
if err != nil {
|
||||
stdOutLogger.Printf("Error creating invoice: %s", err)
|
||||
return c.JSON(http.StatusInternalServerError, "Error adding invoice")
|
||||
@@ -143,7 +144,7 @@ func main() {
|
||||
})
|
||||
|
||||
if !cfg.Bool("disable-ln-address") {
|
||||
e.GET("/.well-known/lnurlp/:name", func(c echo.Context) error {
|
||||
lnurlHandler := func(c echo.Context) error {
|
||||
name := c.Param("name")
|
||||
lightningAddress := name + "@" + c.Request().Host
|
||||
lnurlMetadata := "[[\"text/identifier\", \"" + lightningAddress + "\"], [\"text/plain\", \"Sats for " + lightningAddress + "\"]]"
|
||||
@@ -163,12 +164,16 @@ func main() {
|
||||
stdOutLogger.Printf("New LightningAddress request amount: %s", amount)
|
||||
msats, err := strconv.ParseInt(amount, 10, 64)
|
||||
if err != nil || msats < 1000 {
|
||||
stdOutLogger.Printf("Invalid amount: %s", amount)
|
||||
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)
|
||||
invoice, err := lnClient.AddInvoice(sats, lightningAddress, metadataHash[:])
|
||||
if err != nil {
|
||||
stdOutLogger.Printf("Error creating invoice: %s", err)
|
||||
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Server Error"})
|
||||
}
|
||||
lnurlPayResponse2 := lnurl.LNURLPayResponse2{
|
||||
LNURLResponse: lnurl.LNURLResponse{Status: "OK"},
|
||||
PR: invoice.PaymentRequest,
|
||||
@@ -178,7 +183,9 @@ func main() {
|
||||
}
|
||||
return c.JSON(http.StatusOK, lnurlPayResponse2)
|
||||
}
|
||||
})
|
||||
}
|
||||
e.GET("/.well-known/lnurlp/:name", lnurlHandler)
|
||||
e.GET("/lnurlp/:name", lnurlHandler)
|
||||
}
|
||||
|
||||
// Debug test endpoint
|
||||
@@ -187,10 +194,24 @@ func main() {
|
||||
})
|
||||
|
||||
port := cfg.String("port")
|
||||
// Special case for PORT instead of LNME_PORT due to cloud-providers
|
||||
if os.Getenv("PORT") != "" {
|
||||
port = os.Getenv("PORT")
|
||||
}
|
||||
e.Logger.Fatal(e.Start(":" + port))
|
||||
listen := cfg.String("listen")
|
||||
if listen != "" && port != "" {
|
||||
log.Fatalf("Port and listen options are mutually exclusive, please just use listen.")
|
||||
}
|
||||
if listen == "" {
|
||||
if port != "" {
|
||||
stdOutLogger.Printf("Please use listen instead of deprecated port setting.")
|
||||
listen = fmt.Sprintf(":%s", port)
|
||||
} else {
|
||||
listen = DEFAULT_LISTEN
|
||||
}
|
||||
}
|
||||
|
||||
e.Logger.Fatal(e.Start(listen))
|
||||
}
|
||||
|
||||
func LoadConfig() *koanf.Koanf {
|
||||
@@ -205,7 +226,8 @@ func LoadConfig() *koanf.Koanf {
|
||||
f.Bool("disable-cors", false, "Disable CORS headers.")
|
||||
f.Float64("request-limit", 5, "Request limit per second.")
|
||||
f.String("static-path", "", "Path to a static assets directory.")
|
||||
f.String("port", "1323", "Port to bind on.")
|
||||
f.String("port", "", "Port to bind on (deprecated - use listen).")
|
||||
f.String("listen", "", fmt.Sprintf("Address to bind on. (default \"%s\")", DEFAULT_LISTEN))
|
||||
var configPath string
|
||||
f.StringVar(&configPath, "config", "config.toml", "Path to a .toml config file.")
|
||||
f.Parse(os.Args[1:])
|
||||
|
||||
Reference in New Issue
Block a user