Comments and documentation

This commit is contained in:
bumi 2020-10-23 20:38:18 +02:00
parent c0171c0e81
commit 05fa7500b0
3 changed files with 95 additions and 68 deletions

View File

@ -1,88 +1,108 @@
# LnTip - your friendly lightning tipping widget
# LnMe - notBTCPayServer but your friendly ⚡ payment page
LnTip provides a Bitcoin lightning tipping widget that can easily be integrated into any website.
LnMe is your personal Bitcoin Lightning payment website and payment widget.
It consistes of a small service written in Go that connects to a lnd node and exposes
a simple HTTP JSON API to create and monitor invoices. This is a HTTP/REST proxy to the LND add and receive invoices API.
That API is consumed from a tiny JavaScript widget that can be integrated into any website.
It is a small service written in Go that connects to a lnd node and exposes a simple HTTP JSON API to create and monitor invoices.
It comes with a configurable personal ⚡ website and offers a JavaScript widget to integrate in existing websites.
If [webln](https://github.com/wbobeirne/webln) is available the widget automatically use webln to request the payment;
otherwise an overlay will be shown with the payment request and a QR code.
## Motivation
I wanted a simple tipping button for my website that uses my own lightning node and does not rely on external services (does not need to trusts external services to handle the payments and hold the coins).
I wanted a simple way for people to send Lightning payments to me using my own lightning node.
BTCPay Server is too big and hard to run for that as I do not need most features.
## Installation
To use LnTip a running [LND node](https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md)
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.
1. download the latest [release](https://github.com/bumi/lntip/releases)
2. run `invoices_proxy` (to run it as systemd service have a look at the [systemd service example config](https://github.com/bumi/lntip/blob/master/examples/invoices-proxy.service))
3. integrate the widget on website
1. Download the latest [release](https://github.com/bumi/lntip/releases)
2. Run `lnme` (to run it as systemd service have a look at the [systemd service example config](https://github.com/bumi/lntip/blob/master/examples/invoices-proxy.service))
3. Done.
### Configuration
To connect to the lnd node the cert, macaroon and address of the lnd node has to be configured:
#### LND configuration
To connect to the lnd node the cert, macaroon and address of the lnd node has to be configured. LnMe uses the LND defaults.
* address: Host and port of the lnd gRPC service. default: localhost:10009
* cert: Path to the lnd cert file. default: ~/.lnd/tls.cert
* macaroon: Path to the macaroon file. default: ~/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon
* bind: Host and port to listen on. default: :1323 (localhost:1323)
* static-path: The proxy can serve files from a static folder (e.g. the JS/CSS files). Use this option to configure the path to a filder. (e.g. /home/bitcoin/lntip/assets) default: disabled
#### Other configuration
* 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-cors: Disable CORS headers. (default: false)
* request-limit: Limit the allowed requests per second. (default: 10)
* bind: Host and port to listen on. (default: :1323)
* request-limit: Limit the allowed requests per second. (default: 5)
Examples:
#### Examples:
$ ./invoices_proxy --help
$ ./invoices_proxy --address=lndhost.com:10009 --bind=localhost:4711
$ ./lnme --help
$ ./lnme --address=lndhost.com:10009 --bind=localhost:4711
$ ./lnme --disable-website
### Customize your ⚡ website
LnMe comes with a default website but you can easily configure and build your own website and use the LnMe widget.
Take a look at the [embedded default website](https://github.com/bumi/lntip/blob/master/files/root/index.html) for an example and use the `--static-path` option to configure LnMe to serve your static file.
1. Create a new folder (e.g. /home/satoshi/my-ln-page)
2. Create your index.html
3. Run lnme: `lnme --static-path=/home/satoshi/my-ln-page
### JavaScript Widget integration
Load the JavaScript file in your HTML page and configure the `lntip-host` attribute
to the host and port on which your lntip instance is running:
You can integrate the LnMe widget in your existing website.
#### 1. Add the LnMe JavaScript files
```html
<script lntip-host="https://your-lntip-host.com:1323" src="https://cdn.jsdelivr.net/gh/bumi/lntip/assets/lntip.js" id="lntip-script"></script>
<script data-lnme-base-url="https://your-lnme-host.com:1323" src="https://your-lnme-host.com/lnme/lnme.js"></script>
```
#### Usage
#### 2. Usage
To request a lightning payment simply call `request()` on a `new LnTip({value: value, memo: memo})`:
To request a lightning payment simply call `request()` on a `new LnMe({value: value, memo: memo})`:
```js
new LnTip({ value: 1000, memo: 'high5' }).request()
var lnme = new LnMe({ value: 1000, memo: 'high5' });
lnme.request();
```
Use it from a plain HTML link:
```html
<a href="#" onclick="javascript:new LnTip({ value: 1000, memo: 'high5' }).request();return false;">Tip me</a>
<a href="#" onclick="javascript:new LnMe({ value: 1000, memo: 'high5' }).request();return false;">Tip me</a>
```
##### More advanced JS API:
```js
let tip = new LnTip({ value: 1000, memo: 'high5' });
let lnme = new LnMe({ value: 1000, memo: 'high5' });
// get a new invoice and watch for a payment
// promise resolves if the invoice is settled
tip.requestPayment().then((invoice) => {
lnme.requestPayment().then(invoice => {
alert('YAY, thanks!');
});
// create a new invoice
tip.addInvoice().then((invoice) => {
lnme.addInvoice().then(invoice => {
console.log(invoice.PaymentRequest)
});
// periodically watch if an invoice is settled
tip.watchPayment().then((invoice) => {
lnme.watchPayment().then(invoice => {
alert('YAY, thanks!');
});
@ -90,14 +110,14 @@ tip.watchPayment().then((invoice) => {
## Development
Use `go run` to ron the service locally:
Use `go run` to ron the service locally:
$ go run invoices_proxy.go --address=127.0.0.1:10009 --cert=/home/bitcoin/lightning/tls.cert --macaroon=/home/bitcoin/lightning/invoice.macaroon
$ go run lnme.go --address=127.0.0.1:10009 --cert=/home/bitcoin/lightning/tls.cert --macaroon=/home/bitcoin/lightning/invoice.macaroon
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/bumi/lntip
Bug reports and pull requests are welcome on GitHub at https://github.com/bumi/lnme
## License

View File

@ -25,6 +25,13 @@ type Invoice struct {
Settled bool `json:"settled"`
}
// LNDoptions are the options for the connection to the lnd node.
type LNDoptions struct {
Address string
CertFile string
MacaroonFile string
}
type LNDclient struct {
lndClient lnrpc.LightningClient
ctx context.Context
@ -50,6 +57,7 @@ func (c LNDclient) AddInvoice(value int64, memo string) (Invoice, error) {
return result, nil
}
// NewAddress gets the next BTC onchain address.
func (c LNDclient) NewAddress() (string, error) {
stdOutLogger.Printf("Getting a new BTC address")
request := lnrpc.NewAddressRequest{
@ -130,19 +138,3 @@ func NewLNDclient(lndOptions LNDoptions) (LNDclient, error) {
return result, nil
}
// LNDoptions are the options for the connection to the lnd node.
type LNDoptions struct {
// Address of your LND node, including the port.
// Optional ("localhost:10009" by default).
Address string
// Path to the "tls.cert" file that your LND node uses.
// Optional ("tls.cert" by default).
CertFile string
// Path to the macaroon file that your LND node uses.
// "invoice.macaroon" if you only use the AddInvoice() and GetInvoice() methods
// (required by the middleware in the package "wall").
// "admin.macaroon" if you use the Pay() method (required by the client in the package "pay").
// Optional ("invoice.macaroon" by default).
MacaroonFile string
}

51
lnme.go
View File

@ -13,7 +13,8 @@ import (
"os"
)
// move to file
// Middleware for request limited to prevent too many requests
// TODO: move to file
func LimitMiddleware(lmt *limiter.Limiter) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return echo.HandlerFunc(func(c echo.Context) error {
@ -34,45 +35,55 @@ type Invoice struct {
}
func main() {
address := flag.String("address", "localhost:10009", "The host and port of the ln gRPC server")
certFile := flag.String("cert", "~/.lnd/tls.cert", "Path to the lnd tls.cert file")
macaroonFile := flag.String("macaroon", "~/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon", "Path to the lnd macaroon file")
bind := flag.String("bind", ":1323", "Host and port to bind on")
staticPath := flag.String("static-path", "", "Path to a static assets directory. Blank to not serve any static files")
disableHTML := flag.Bool("disable-html", false, "Disable HTML page")
disableCors := flag.Bool("disable-cors", false, "Disable CORS headers")
requestLimit := flag.Float64("request-limit", 5, "Request limit per second")
address := flag.String("address", "localhost:10009", "The host and port of the lnd gRPC server.")
certFile := flag.String("cert", "~/.lnd/tls.cert", "Path to the lnd tls.cert file.")
macaroonFile := flag.String("macaroon", "~/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon", "Path to the lnd macaroon file.")
bind := flag.String("bind", ":1323", "Host and port to bind on.")
staticPath := flag.String("static-path", "", "Path to a static assets directory. Leave blank to not serve any static files.")
disableWebsite := flag.Bool("disable-website", false, "Disable default embedded website.")
disableCors := flag.Bool("disable-cors", false, "Disable CORS headers.")
requestLimit := flag.Float64("request-limit", 5, "Request limit per second.")
flag.Parse()
e := echo.New()
// Serve static files if configured
if *staticPath != "" {
e.Static("/", *staticPath)
} else if !*disableHTML {
// Serve default page
} else if !*disableWebsite {
rootBox := rice.MustFindBox("files/root")
indexPage, err := rootBox.String("index.html")
if err == nil {
stdOutLogger.Print("Running page")
stdOutLogger.Print("Running embedded page")
e.GET("/", func(c echo.Context) error {
return c.HTML(200, indexPage)
})
}
} else {
stdOutLogger.Printf("Failed to run embedded website: %s", err)
}
}
// Embed static files and serve those on /lnme (e.g. /lnme/lnme.js)
assetHandler := http.FileServer(rice.MustFindBox("files/assets").HTTPBox())
e.GET("/lnme/*", echo.WrapHandler(http.StripPrefix("/lnme/", assetHandler)))
// CORS settings
if !*disableCors {
e.Use(middleware.CORS())
}
// Recover middleware recovers from panics anywhere in the request chain
e.Use(middleware.Recover())
// Request limit per second. DoS protection
if *requestLimit > 0 {
limiter := tollbooth.NewLimiter(*requestLimit, nil)
e.Use(LimitMiddleware(limiter))
}
// Embed static files and serve those on /lnme (e.g. /lnme/lnme.js)
assetHandler := http.FileServer(rice.MustFindBox("files/assets").HTTPBox())
e.GET("/lnme/*", echo.WrapHandler(http.StripPrefix("/lnme/", assetHandler)))
// Setup lightning client
stdOutLogger.Printf("Connection to %s using macaroon %s and cert %s", *address, *macaroonFile, *certFile)
lndOptions := ln.LNDoptions{
Address: *address,
@ -85,7 +96,9 @@ func main() {
panic(err)
}
// endpoint URLs compatible to the LND REST API
// Endpoint URLs compatible to the LND REST API v1
//
// Create new invoice
e.POST("/v1/invoices", func(c echo.Context) error {
i := new(Invoice)
if err := c.Bind(i); err != nil {
@ -102,6 +115,7 @@ func main() {
return c.JSON(http.StatusOK, invoice)
})
// Get next BTC onchain address
e.POST("/v1/newaddress", func(c echo.Context) error {
address, err := lnClient.NewAddress()
if err != nil {
@ -111,6 +125,7 @@ func main() {
return c.JSON(http.StatusOK, address)
})
// Check invoice status
e.GET("/v1/invoice/:invoiceId", func(c echo.Context) error {
invoiceId := c.Param("invoiceId")
invoice, err := lnClient.GetInvoice(invoiceId)
@ -123,7 +138,7 @@ func main() {
return c.JSON(http.StatusOK, invoice)
})
// debug test endpoint
// Debug test endpoint
e.GET("/ping", func(c echo.Context) error {
return c.JSON(http.StatusOK, "pong")
})