mirror of
https://github.com/bumi/lntip
synced 2025-06-17 10:15:35 +00:00
Compare commits
No commits in common. "master" and "1.5.1" have entirely different histories.
51
README.md
51
README.md
@ -5,7 +5,7 @@ LnMe is a personal Bitcoin Lightning payment page/widget and self-hosted [Lightn
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
**See it in action: [ln.michaelbumann.com](http://ln.michaelbumann.com/) - my lightning address: bumi@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 (currently LND is supported).
|
LnMe focusses on simplicity and ease of deployment. It connects to an existing lightning node (currently LND is supported).
|
||||||
|
|
||||||
@ -19,7 +19,6 @@ LnMe is one [simple executable](https://github.com/bumi/lnme/releases) file that
|
|||||||
- [x] [JavaScript widget](#javascript-widget-integration) for existing websites
|
- [x] [JavaScript widget](#javascript-widget-integration) for existing websites
|
||||||
- [x] [Invoice API](https://github.com/bumi/lnme/wiki/API) - simple REST API to create LN invoices from existing JS code
|
- [x] [Invoice API](https://github.com/bumi/lnme/wiki/API) - simple REST API to create LN invoices from existing JS code
|
||||||
- [x] [LNURL-pay](https://github.com/fiatjaf/lnurl-rfc/blob/luds/06.md) support
|
- [x] [LNURL-pay](https://github.com/fiatjaf/lnurl-rfc/blob/luds/06.md) support
|
||||||
- [x] [LNURL-pay comment](https://github.com/fiatjaf/lnurl-rfc/blob/luds/12.md) support
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -63,12 +62,10 @@ Instead of the path to the macaroon and cert files you can also provide the hex
|
|||||||
#### Other configuration
|
#### 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
|
- `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
|
||||||
- `lnurlp-comment-allowed`: Allowed length of LNURL-pay comments, maximum around [~2000 characters](https://stackoverflow.com/a/417184). (default: 210)
|
|
||||||
- `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.
|
- `disable-ln-address`: Disable [Lightning Address](https://lightningaddress.com/) handling.
|
||||||
- `port`: Port to listen on. (default: 1323)
|
- `port`: Port to listen on. (default: 1323)
|
||||||
- `listen`: IP and port to listen to. Supersedes `port`. (default: :1323).
|
|
||||||
- `request-limit`: Limit the allowed requests per second. (default: 5)
|
- `request-limit`: Limit the allowed requests per second. (default: 5)
|
||||||
|
|
||||||
Depending on your deployment needs LnMe can be configured using the following options:
|
Depending on your deployment needs LnMe can be configured using the following options:
|
||||||
@ -124,7 +121,6 @@ The TLS cert is located in the lnd directory:
|
|||||||
|
|
||||||
- ~/umbrel/lnd/tls.cert on Umbrel
|
- ~/umbrel/lnd/tls.cert on Umbrel
|
||||||
- /mnt/hdd/lnd/tls.cert on Raspiblitz
|
- /mnt/hdd/lnd/tls.cert on Raspiblitz
|
||||||
- /embassy-data/package-data/volumes/lnd/data/main/tls.cert on Start9 Embassy
|
|
||||||
- Can also be located in ~/.lnd
|
- Can also be located in ~/.lnd
|
||||||
|
|
||||||
You should find the macaroon files in the LND data dir (e.g. ~.lnd/data/chain/bitcoin/mainnet/) or see "LND Permissions" how to create a new one.
|
You should find the macaroon files in the LND data dir (e.g. ~.lnd/data/chain/bitcoin/mainnet/) or see "LND Permissions" how to create a new one.
|
||||||
@ -142,41 +138,7 @@ This buildpack can be disabled and removed if not needed or desired, through the
|
|||||||
|
|
||||||
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
|
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
|
||||||
### Fly.io
|
|
||||||
|
|
||||||
#### 0. Clone the repo
|
|
||||||
|
|
||||||
$ git clone https://github.com/bumi/lnme.git
|
|
||||||
$ cd lnme
|
|
||||||
|
|
||||||
#### 1. Create a new app
|
|
||||||
|
|
||||||
$ flyctl launch --generate-name // or set a custom app name: flyctl launch --name lnme-test-1
|
|
||||||
|
|
||||||
You will be asked a few things:
|
|
||||||
|
|
||||||
* Copy the configuration to the new app
|
|
||||||
* You do NOT need to create a Postgresql Database
|
|
||||||
* Do NOT deploy it directly, we first need to set some configs
|
|
||||||
|
|
||||||
#### 2. Set the configuration using environment variables:
|
|
||||||
|
|
||||||
The LND config variablse are required. Others are optional:
|
|
||||||
|
|
||||||
$ flyctl secrets set LNME_LND_ADDRESS="xxx.xxx.xxx.xxx:10009" LNME_LND_CERT=xxx LNME_LND_MACAROON=xxx
|
|
||||||
$ flyctl secrets set DISABLE_WEBSITE=1 // etc.
|
|
||||||
|
|
||||||
#### 3. Launch the app:
|
|
||||||
|
|
||||||
$ flyctl deploy
|
|
||||||
|
|
||||||
#### 4.Configure your domain
|
|
||||||
|
|
||||||
To configure a custom domain check the [fly.io guides](https://fly.io/docs/app-guides/custom-domains-with-fly/)
|
|
||||||
|
|
||||||
|
|
||||||
### Custom 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)
|
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)
|
||||||
|
|
||||||
@ -209,8 +171,6 @@ if you got the Lightning Address enabled you also get a LNURL-pay URL:
|
|||||||
|
|
||||||
https://`{your domain}/lnurlp/{anything}`
|
https://`{your domain}/lnurlp/{anything}`
|
||||||
|
|
||||||
If you need an bech32 encoded version you can use this online tool: [https://lnurl.fiatjaf.com/codec/](https://lnurl.fiatjaf.com/codec/)
|
|
||||||
|
|
||||||
### 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.
|
||||||
@ -221,13 +181,6 @@ Take a look at the [embedded default website](https://github.com/bumi/lnme/blob/
|
|||||||
2. Create your index.html
|
2. Create your index.html
|
||||||
3. Run lnme: `lnme --static-path=/home/satoshi/my-ln-page
|
3. Run lnme: `lnme --static-path=/home/satoshi/my-ln-page
|
||||||
|
|
||||||
### Usage with 21 Payment Widgets
|
|
||||||
|
|
||||||
[widgets.twentyuno.net](https://widgets.twentyuno.net/) is a beautiful embeddable payment widget for any existing website.
|
|
||||||
You can use your LnMe instance with the widget by using your [LnMe LNURL](https://github.com/bumi/lnme#lnurl) with the widget.
|
|
||||||
|
|
||||||
Use your bech32 encoded [LNURL](https://github.com/bumi/lnme#lnurl) as `Receiver` in the [widget configuration](https://widgets.twentyuno.net/get-started)
|
|
||||||
|
|
||||||
### JavaScript Widget integration
|
### JavaScript Widget integration
|
||||||
|
|
||||||
You can integrate the LnMe widget in your existing website.
|
You can integrate the LnMe widget in your existing website.
|
||||||
|
@ -120,11 +120,15 @@ class LnMe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
payWithWebln() {
|
payWithWebln() {
|
||||||
|
if (!webln.isEnabled) {
|
||||||
webln.enable().then((weblnResponse) => {
|
webln.enable().then((weblnResponse) => {
|
||||||
return webln.sendPayment(this.invoice.payment_request);
|
return webln.sendPayment(this.invoice.payment_request);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
return this.showPaymentRequest();
|
return this.showPaymentRequest();
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
return webln.sendPayment(this.invoice.payment_request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
populatePaymentRequest() {
|
populatePaymentRequest() {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<meta property="og:image" content="/lnme/icon.svg">
|
<meta property="og:image" content="/lnme/icon.svg">
|
||||||
|
|
||||||
<title>Send me some sats</title>
|
<title>Send me some Sats</title>
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -118,7 +118,7 @@
|
|||||||
<div class="form" id="form">
|
<div class="form" id="form">
|
||||||
<p>
|
<p>
|
||||||
Send me<br>
|
Send me<br>
|
||||||
<input type="number" placeholder="10000" class="amount" id="amount" autofocus="true" autocomplete="off" min="100"> sats
|
<input type="number" placeholder="10000" class="amount" id="amount" autofocus="true" autocomplete="off" min="100"> Sats
|
||||||
<br>
|
<br>
|
||||||
for
|
for
|
||||||
<br>
|
<br>
|
||||||
@ -159,7 +159,7 @@
|
|||||||
}
|
}
|
||||||
var siteDescription = document.querySelector('head > meta[property="og:description"]');
|
var siteDescription = document.querySelector('head > meta[property="og:description"]');
|
||||||
if (siteDescription && !siteDescription.content) {
|
if (siteDescription && !siteDescription.content) {
|
||||||
siteDescription.content = "sats for " + window.location.host;
|
siteDescription.content = "Sats for " + window.location.host;
|
||||||
}
|
}
|
||||||
document.getElementById("get-new-address").addEventListener('click', function(e) {
|
document.getElementById("get-new-address").addEventListener('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
37
fly.toml
37
fly.toml
@ -1,37 +0,0 @@
|
|||||||
# fly.toml file generated
|
|
||||||
|
|
||||||
kill_signal = "SIGINT"
|
|
||||||
kill_timeout = 5
|
|
||||||
processes = []
|
|
||||||
|
|
||||||
[env]
|
|
||||||
|
|
||||||
[experimental]
|
|
||||||
allowed_public_ports = []
|
|
||||||
auto_rollback = true
|
|
||||||
|
|
||||||
[[services]]
|
|
||||||
http_checks = []
|
|
||||||
internal_port = 1323
|
|
||||||
processes = ["app"]
|
|
||||||
protocol = "tcp"
|
|
||||||
script_checks = []
|
|
||||||
[services.concurrency]
|
|
||||||
hard_limit = 25
|
|
||||||
soft_limit = 20
|
|
||||||
type = "connections"
|
|
||||||
|
|
||||||
[[services.ports]]
|
|
||||||
force_https = true
|
|
||||||
handlers = ["http"]
|
|
||||||
port = 80
|
|
||||||
|
|
||||||
[[services.ports]]
|
|
||||||
handlers = ["tls", "http"]
|
|
||||||
port = 443
|
|
||||||
|
|
||||||
[[services.tcp_checks]]
|
|
||||||
grace_period = "1s"
|
|
||||||
interval = "15s"
|
|
||||||
restart_limit = 0
|
|
||||||
timeout = "2s"
|
|
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/didip/tollbooth/v6 v6.1.2
|
github.com/didip/tollbooth/v6 v6.1.2
|
||||||
github.com/knadh/koanf v1.4.1
|
github.com/knadh/koanf v1.4.1
|
||||||
github.com/labstack/echo/v4 v4.9.0
|
github.com/labstack/echo/v4 v4.7.2
|
||||||
github.com/lightningnetwork/lnd v0.14.1-beta
|
github.com/lightningnetwork/lnd v0.14.1-beta
|
||||||
google.golang.org/grpc v1.45.0
|
google.golang.org/grpc v1.45.0
|
||||||
gopkg.in/macaroon.v2 v2.1.0
|
gopkg.in/macaroon.v2 v2.1.0
|
||||||
|
4
go.sum
4
go.sum
@ -463,8 +463,8 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
|
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
|
||||||
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
|
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
|
||||||
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
|
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
|
||||||
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
@ -47,7 +47,7 @@ type LNDclient struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddInvoice generates an invoice with the given price and memo.
|
// AddInvoice generates an invoice with the given price and memo.
|
||||||
func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte, private bool) (Invoice, error) {
|
func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte) (Invoice, error) {
|
||||||
result := Invoice{}
|
result := Invoice{}
|
||||||
|
|
||||||
stdOutLogger.Printf("Adding invoice: memo=%s value=%v", memo, value)
|
stdOutLogger.Printf("Adding invoice: memo=%s value=%v", memo, value)
|
||||||
@ -55,7 +55,6 @@ func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte,
|
|||||||
Memo: memo,
|
Memo: memo,
|
||||||
DescriptionHash: descriptionHash,
|
DescriptionHash: descriptionHash,
|
||||||
Value: value,
|
Value: value,
|
||||||
Private: private,
|
|
||||||
}
|
}
|
||||||
res, err := c.lndClient.AddInvoice(c.ctx, &invoice)
|
res, err := c.lndClient.AddInvoice(c.ctx, &invoice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
14
lnme.go
14
lnme.go
@ -117,7 +117,7 @@ func main() {
|
|||||||
return c.JSON(http.StatusBadRequest, "Bad request")
|
return c.JSON(http.StatusBadRequest, "Bad request")
|
||||||
}
|
}
|
||||||
|
|
||||||
invoice, err := lnClient.AddInvoice(i.Value, i.Memo, nil, cfg.Bool("enable-private-channels"))
|
invoice, err := lnClient.AddInvoice(i.Value, i.Memo, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stdOutLogger.Printf("Error creating invoice: %s", err)
|
stdOutLogger.Printf("Error creating invoice: %s", err)
|
||||||
return c.JSON(http.StatusInternalServerError, "Error adding invoice")
|
return c.JSON(http.StatusInternalServerError, "Error adding invoice")
|
||||||
@ -163,7 +163,6 @@ func main() {
|
|||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
lightningAddress := name + "@" + host
|
lightningAddress := name + "@" + host
|
||||||
lnurlMetadata := "[[\"text/identifier\", \"" + lightningAddress + "\"], [\"text/plain\", \"Sats for " + lightningAddress + "\"]]"
|
lnurlMetadata := "[[\"text/identifier\", \"" + lightningAddress + "\"], [\"text/plain\", \"Sats for " + lightningAddress + "\"]]"
|
||||||
lnurlpCommentAllowed := cfg.Int64("lnurlp-comment-allowed")
|
|
||||||
|
|
||||||
if amount := c.QueryParam("amount"); amount == "" {
|
if amount := c.QueryParam("amount"); amount == "" {
|
||||||
lnurlPayResponse1 := lnurl.LNURLPayResponse1{
|
lnurlPayResponse1 := lnurl.LNURLPayResponse1{
|
||||||
@ -172,7 +171,7 @@ func main() {
|
|||||||
MinSendable: 1000,
|
MinSendable: 1000,
|
||||||
MaxSendable: 100000000,
|
MaxSendable: 100000000,
|
||||||
EncodedMetadata: lnurlMetadata,
|
EncodedMetadata: lnurlMetadata,
|
||||||
CommentAllowed: lnurlpCommentAllowed,
|
CommentAllowed: 0,
|
||||||
Tag: "payRequest",
|
Tag: "payRequest",
|
||||||
}
|
}
|
||||||
return c.JSON(http.StatusOK, lnurlPayResponse1)
|
return c.JSON(http.StatusOK, lnurlPayResponse1)
|
||||||
@ -184,13 +183,8 @@ func main() {
|
|||||||
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Invalid Amount"})
|
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Invalid Amount"})
|
||||||
}
|
}
|
||||||
sats := msats / 1000 // we need sats
|
sats := msats / 1000 // we need sats
|
||||||
comment := c.QueryParam("comment")
|
|
||||||
if commentLength := int64(len(comment)); commentLength > lnurlpCommentAllowed {
|
|
||||||
stdOutLogger.Printf("Invalid comment length: %d", commentLength)
|
|
||||||
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Invalid comment length"})
|
|
||||||
}
|
|
||||||
metadataHash := sha256.Sum256([]byte(lnurlMetadata))
|
metadataHash := sha256.Sum256([]byte(lnurlMetadata))
|
||||||
invoice, err := lnClient.AddInvoice(sats, comment, metadataHash[:], cfg.Bool("enable-private-channels"))
|
invoice, err := lnClient.AddInvoice(sats, lightningAddress, metadataHash[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stdOutLogger.Printf("Error creating invoice: %s", err)
|
stdOutLogger.Printf("Error creating invoice: %s", err)
|
||||||
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Server Error"})
|
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Server Error"})
|
||||||
@ -244,11 +238,9 @@ func LoadConfig() *koanf.Koanf {
|
|||||||
f.String("lnd-macaroon", "", "HEX string of LND macaroon file.")
|
f.String("lnd-macaroon", "", "HEX string of 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.String("lnd-cert", "", "HEX string of LND tls cert file.")
|
f.String("lnd-cert", "", "HEX string of LND tls cert file.")
|
||||||
f.Int64("lnurlp-comment-allowed", 210, "Allowed length of LNURL-pay comments.")
|
|
||||||
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-ln-address", false, "Disable Lightning Address handling")
|
||||||
f.Bool("disable-cors", false, "Disable CORS headers.")
|
f.Bool("disable-cors", false, "Disable CORS headers.")
|
||||||
f.Bool("enable-private-channels", false, "Adds private routing hints to invoices")
|
|
||||||
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.")
|
||||||
f.String("port", "", "Port to bind on (deprecated - use listen).")
|
f.String("port", "", "Port to bind on (deprecated - use listen).")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user