mirror of
https://github.com/bumi/lntip
synced 2025-06-16 09:45:35 +00:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
70028e4264 | |||
|
47efbfcc96 | ||
b9b2ce8d6b | |||
|
f604ca21ce | ||
530c7c0942 | |||
|
23a134b28e | ||
c6dc9308c3 | |||
|
897217995a | ||
a21e8b96b7 | |||
a383a747ef | |||
|
9ceea9e6bf | ||
d42c12be9b | |||
|
53253eba76 | ||
a3675ec746 | |||
3c8a76a1c1 | |||
f6d089c194 |
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](https://ln.michaelbumann.com/) - my lightning address: bumi@ln.michaelbumann.com**
|
||||
**See it in action: [ln.michaelbumann.com](http://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).
|
||||
|
||||
@ -19,6 +19,7 @@ LnMe is one [simple executable](https://github.com/bumi/lnme/releases) file that
|
||||
- [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] [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
|
||||
|
||||
@ -62,10 +63,12 @@ Instead of the path to the macaroon and cert files you can also provide the hex
|
||||
#### 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
|
||||
- `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-cors`: Disable CORS headers. (default: false)
|
||||
- `disable-ln-address`: Disable [Lightning Address](https://lightningaddress.com/) handling.
|
||||
- `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)
|
||||
|
||||
Depending on your deployment needs LnMe can be configured using the following options:
|
||||
@ -121,6 +124,7 @@ The TLS cert is located in the lnd directory:
|
||||
|
||||
- ~/umbrel/lnd/tls.cert on Umbrel
|
||||
- /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
|
||||
|
||||
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.
|
||||
@ -138,7 +142,41 @@ 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
|
||||
|
||||
### 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)
|
||||
|
||||
@ -171,6 +209,8 @@ if you got the Lightning Address enabled you also get a LNURL-pay URL:
|
||||
|
||||
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
|
||||
|
||||
LnMe comes with a default website but you can easily configure and build your own using the the LnMe JavaScript widget or JSON API.
|
||||
@ -181,6 +221,13 @@ Take a look at the [embedded default website](https://github.com/bumi/lnme/blob/
|
||||
2. Create your index.html
|
||||
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
|
||||
|
||||
You can integrate the LnMe widget in your existing website.
|
||||
|
@ -120,15 +120,11 @@ class LnMe {
|
||||
}
|
||||
|
||||
payWithWebln() {
|
||||
if (!webln.isEnabled) {
|
||||
webln.enable().then((weblnResponse) => {
|
||||
return webln.sendPayment(this.invoice.payment_request);
|
||||
}).catch((e) => {
|
||||
return this.showPaymentRequest();
|
||||
})
|
||||
} else {
|
||||
webln.enable().then((weblnResponse) => {
|
||||
return webln.sendPayment(this.invoice.payment_request);
|
||||
}
|
||||
}).catch((e) => {
|
||||
return this.showPaymentRequest();
|
||||
})
|
||||
}
|
||||
|
||||
populatePaymentRequest() {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
<meta property="og:image" content="/lnme/icon.svg">
|
||||
|
||||
<title>Send me some Sats</title>
|
||||
<title>Send me some sats</title>
|
||||
<style>
|
||||
html {
|
||||
width: 100%;
|
||||
@ -118,7 +118,7 @@
|
||||
<div class="form" id="form">
|
||||
<p>
|
||||
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>
|
||||
for
|
||||
<br>
|
||||
@ -159,7 +159,7 @@
|
||||
}
|
||||
var siteDescription = document.querySelector('head > meta[property="og:description"]');
|
||||
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) {
|
||||
e.preventDefault();
|
||||
|
37
fly.toml
Normal file
37
fly.toml
Normal file
@ -0,0 +1,37 @@
|
||||
# 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/didip/tollbooth/v6 v6.1.2
|
||||
github.com/knadh/koanf v1.4.1
|
||||
github.com/labstack/echo/v4 v4.7.2
|
||||
github.com/labstack/echo/v4 v4.9.0
|
||||
github.com/lightningnetwork/lnd v0.14.1-beta
|
||||
google.golang.org/grpc v1.45.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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
|
||||
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
|
||||
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
|
||||
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
|
||||
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
|
||||
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=
|
||||
|
@ -47,7 +47,7 @@ type LNDclient struct {
|
||||
}
|
||||
|
||||
// AddInvoice generates an invoice with the given price and memo.
|
||||
func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte) (Invoice, error) {
|
||||
func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte, private bool) (Invoice, error) {
|
||||
result := Invoice{}
|
||||
|
||||
stdOutLogger.Printf("Adding invoice: memo=%s value=%v", memo, value)
|
||||
@ -55,6 +55,7 @@ func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte)
|
||||
Memo: memo,
|
||||
DescriptionHash: descriptionHash,
|
||||
Value: value,
|
||||
Private: private,
|
||||
}
|
||||
res, err := c.lndClient.AddInvoice(c.ctx, &invoice)
|
||||
if err != nil {
|
||||
|
14
lnme.go
14
lnme.go
@ -117,7 +117,7 @@ func main() {
|
||||
return c.JSON(http.StatusBadRequest, "Bad request")
|
||||
}
|
||||
|
||||
invoice, err := lnClient.AddInvoice(i.Value, i.Memo, nil)
|
||||
invoice, err := lnClient.AddInvoice(i.Value, i.Memo, nil, cfg.Bool("enable-private-channels"))
|
||||
if err != nil {
|
||||
stdOutLogger.Printf("Error creating invoice: %s", err)
|
||||
return c.JSON(http.StatusInternalServerError, "Error adding invoice")
|
||||
@ -163,6 +163,7 @@ func main() {
|
||||
name := c.Param("name")
|
||||
lightningAddress := name + "@" + host
|
||||
lnurlMetadata := "[[\"text/identifier\", \"" + lightningAddress + "\"], [\"text/plain\", \"Sats for " + lightningAddress + "\"]]"
|
||||
lnurlpCommentAllowed := cfg.Int64("lnurlp-comment-allowed")
|
||||
|
||||
if amount := c.QueryParam("amount"); amount == "" {
|
||||
lnurlPayResponse1 := lnurl.LNURLPayResponse1{
|
||||
@ -171,7 +172,7 @@ func main() {
|
||||
MinSendable: 1000,
|
||||
MaxSendable: 100000000,
|
||||
EncodedMetadata: lnurlMetadata,
|
||||
CommentAllowed: 0,
|
||||
CommentAllowed: lnurlpCommentAllowed,
|
||||
Tag: "payRequest",
|
||||
}
|
||||
return c.JSON(http.StatusOK, lnurlPayResponse1)
|
||||
@ -183,8 +184,13 @@ func main() {
|
||||
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Invalid Amount"})
|
||||
}
|
||||
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))
|
||||
invoice, err := lnClient.AddInvoice(sats, lightningAddress, metadataHash[:])
|
||||
invoice, err := lnClient.AddInvoice(sats, comment, metadataHash[:], cfg.Bool("enable-private-channels"))
|
||||
if err != nil {
|
||||
stdOutLogger.Printf("Error creating invoice: %s", err)
|
||||
return c.JSON(http.StatusOK, lnurl.LNURLErrorResponse{Status: "ERROR", Reason: "Server Error"})
|
||||
@ -238,9 +244,11 @@ func LoadConfig() *koanf.Koanf {
|
||||
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", "", "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-ln-address", false, "Disable Lightning Address handling")
|
||||
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.String("static-path", "", "Path to a static assets directory.")
|
||||
f.String("port", "", "Port to bind on (deprecated - use listen).")
|
||||
|
Loading…
x
Reference in New Issue
Block a user