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

Compare commits

...

61 Commits

Author SHA1 Message Date
70028e4264
Merge pull request #60 from reneaaron/fix/isenabled
fix: implement webln spec
2024-05-31 20:54:03 +03:00
René Aaron
47efbfcc96 fix: implement webln spec 2024-05-21 15:30:47 +02:00
b9b2ce8d6b
Merge pull request #58 from crc32/patch-3
Update README.md
2023-06-05 09:20:46 +02:00
Arceris
f604ca21ce
Update README.md
Added location of LND TLS for Start9
2023-06-03 16:54:49 -06:00
530c7c0942
Merge pull request #50 from ziggie1984/add-private-route-hints
add private routing hints for invoices
2023-01-28 13:02:18 +02:00
ziggie
23a134b28e
add private routing hints for invoices 2023-01-23 10:08:26 +01:00
c6dc9308c3
Merge pull request #46 from bumi/dependabot/go_modules/github.com/labstack/echo/v4-4.9.0
Bump github.com/labstack/echo/v4 from 4.7.2 to 4.9.0
2022-11-06 23:24:54 +01:00
dependabot[bot]
897217995a
Bump github.com/labstack/echo/v4 from 4.7.2 to 4.9.0
Bumps [github.com/labstack/echo/v4](https://github.com/labstack/echo) from 4.7.2 to 4.9.0.
- [Release notes](https://github.com/labstack/echo/releases)
- [Changelog](https://github.com/labstack/echo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/labstack/echo/compare/v4.7.2...v4.9.0)

---
updated-dependencies:
- dependency-name: github.com/labstack/echo/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-30 16:05:20 +00:00
a21e8b96b7 Add fly.io deployment notes 2022-08-19 10:55:00 +02:00
a383a747ef
Merge pull request #40 from yanascz/lnurl-comment-allowed
Add configuration property for allowed length of LNURL-pay comments
2022-08-11 09:02:27 +02:00
yanas
9ceea9e6bf Add configuration property for allowed length of LNURL-pay comments 2022-08-11 08:45:19 +02:00
d42c12be9b
Merge pull request #43 from ok300/patch-1
Document `listen` startup config
2022-08-11 02:34:25 +02:00
ok300
53253eba76
Document listen startup config 2022-08-09 07:30:02 +02:00
a3675ec746
Update README.md 2022-07-13 20:22:12 +02:00
3c8a76a1c1
Update README.md 2022-05-17 14:14:26 +02:00
f6d089c194
lowercase sats 2022-05-13 11:06:13 +02:00
ae9f643fbf go fmt 2022-05-07 12:50:15 +02:00
5fd10bc647 Add icon assets 2022-05-07 11:36:42 +02:00
5d2c9cd639 Fix: use correct header to get the original request host
Proxy servers can set the X-Forwarded-Host and X-Forwarded-Proto headers to pass on the original host and protocol.
We should also support the Forwarded header (which combines this in one header described in RFC7239 - but seems echo does not support parsing that one?)
2022-05-06 20:30:35 +02:00
bdd3b70e1f
Merge pull request #35 from AaronDewes/patch-1
Fix typo
2022-04-18 17:27:51 +02:00
Aaron Dewes
a9a3e8b073
Fix typo
clipboad -> clipboard
2022-04-18 17:24:43 +02:00
e400782449 Some auto-formatting 2022-04-14 20:58:13 -04:00
488eef551f Flag for the LND hex strings 2022-04-14 20:57:51 -04:00
fae94fc021 update dependencies 2022-04-14 20:32:17 -04:00
c2b11e3146
Merge pull request #33 from runcitadel/go-1.17
Drop go rice and switch to go 1.17
2022-04-15 02:15:25 +02:00
1fbfdf059a
Update README.md 2022-04-11 17:56:03 +02:00
Aaron Dewes
f8f8525b01 Actually fix the bug 2022-02-01 20:57:28 +01:00
Aaron Dewes
1ea6ce082b Bug fix 2022-02-01 20:23:42 +01:00
Aaron Dewes
31c8b4d080
Remove useless conversion to string 2022-01-31 08:35:08 +01:00
Aaron Dewes
77d21734c3 Drop go rice and switch to go 1.17 2022-01-30 13:41:25 +01:00
c4b4430387
Update README.md 2022-01-14 17:09:20 +02:00
c864602c1f
Merge pull request #32 from jerolimov/icon
Add icon to readme and favicon to generated code
2021-12-03 14:04:44 +02:00
Christoph Jerolimov
cffdded688 Add icon to readme and favicon to generated code, fixes #28 2021-12-03 12:55:56 +01:00
b100455e09
Merge pull request #30 from bumi/feature/support-forwarded-for-header
Generate lightning address host either from the x-forwarded-for header or from the request host
2021-11-17 17:44:15 +02:00
6f2b72caa7
Merge pull request #24 from runcitadel/master
Docker container
2021-11-17 17:43:49 +02:00
Aaron Dewes
51a8d1ca22
Update README.md 2021-11-06 17:39:17 +00:00
d1be2e20aa Generate lightning address host either from the x-forwarded-for header or from the request host
When LnMe runs behind a proxy and the X-Forwarded-For HTTP header is set we want to use that value to generate the lightning address and not the local/internal host
2021-11-05 13:21:02 +01:00
bf44f314dd
Update README.md 2021-10-29 17:29:57 +02:00
5ffac5e077
Merge pull request #26 from bumi/configurable-tor-path
Configurable tor path
2021-10-29 11:52:52 +02:00
399144acf5
Update README.md 2021-10-29 11:51:35 +02:00
6548504333 Use full path 2021-10-28 15:15:36 +02:00
324d5b3b43 Make Tor executable configurable
It seems the `tor` executable is not in the $PATH on heroku and thus needs to be configurable.
2021-10-28 15:11:10 +02:00
Aaron Dewes
44befafc6d
Add expose command with default port 2021-10-27 17:35:06 +01:00
Aaron Dewes
e3bf2c0537
Update Dockerfile 2021-10-27 15:57:55 +01:00
Aaron Dewes
2b095bbba3 WIP: Docker container 2021-10-27 15:29:18 +01:00
6b7ab33efe
Update FUNDING.yml 2021-10-25 11:24:02 +02:00
36d4dec5fd
Merge pull request #22 from bumi/lnurl-logging
Better error logging for the LNURL endpoint
2021-10-25 11:00:55 +02:00
0fd121d130
Merge pull request #23 from bumi/feature/nicer-lnurlp-urls
Nicer LNURLp URLs
2021-10-25 10:59:43 +02:00
9ae6153d47 Nicer LNURLp URLs
this also registers a shorter lnurlp that can be used additionally to the lightning address
2021-10-24 11:44:05 +02:00
3b7ac31615 Better error logging for the LNURL endpoint
This checks for errors when creating the invoice and logs the error and returns an LNURL error response.
Before it would return a blank pr.
2021-10-24 11:32:00 +02:00
22bc75d564
Merge pull request #20 from GlenCooper/patch-1
Fix typo in README.md
2021-10-15 12:46:08 +02:00
Glen Cooper
2bcd2fd96f
Fix typo in README.md
Settigns -> Settings
2021-10-15 02:56:50 +00:00
9ec4db5144
Create LICENSE 2021-10-14 23:43:18 +02:00
4a4614d8fb
Merge pull request #18 from crc32/patch-2
Add Tor support for Heroku
2021-10-13 20:50:34 +02:00
Colin Crossman
00b33e3bd9
Update README.md with additional Heroku info
Add a statement about the non-standard Tor buildpack, along with instructions to remove. Also add a pointer to the Heroku page describing the method to link the app to a personal domain.
2021-10-13 12:26:07 -06:00
Colin Crossman
109fa20593
Merge branch 'bumi:master' into patch-2 2021-10-13 12:16:32 -06:00
24cdca056f
Merge pull request #19 from crc32/master
Update README.md for clarity
2021-10-13 13:24:12 +02:00
Colin Crossman
03117dc326
Update README.md for clarity
It was unclear how to get the HEX of the TLS cert. Initially I had been trying to use openssl to export the hex of the signature, which was wrong. I finally realized that the xxd command was intended to be applied on the entire tls.cert file, and that worked. So I added this to clarify that aspect.
2021-10-12 20:27:11 -06:00
Colin Crossman
1fe4dd3dd6
Update app.json for Tor on Heroku
Add heroku buildpack for Tor integration
2021-10-12 18:06:20 -06:00
Colin Crossman
5dd2c2cdb5
Update app.json
For Heroku, add the Tor buildpack to allow for Tor connections.
2021-10-08 22:33:55 -06:00
Gregor Pogačnik
3200622b97
Possibility to specify listening IP (#17) 2021-09-30 18:07:43 +02:00
36 changed files with 1369 additions and 480 deletions

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: https://ln.michaelbumann.com?lightning=lnurlp:ln.michaelbumann.com/.well-known/lnurlp/sats custom: https://ln.michaelbumann.com?lightning=lnurlp:ln.michaelbumann.com/lnurlp/github

43
.github/workflows/on-push.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Build on push
permissions:
packages: write
on:
push:
branches:
- main
- master
jobs:
build:
name: Build image
runs-on: ubuntu-20.04
steps:
- name: Checkout project
uses: actions/checkout@v2
- name: Set env variables
run: |
echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g')" >> $GITHUB_ENV
IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
- name: Login to Docker Hub
run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
id: qemu
- name: Setup Docker buildx action
uses: docker/setup-buildx-action@v1
id: buildx
- name: Run Docker buildx
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:$BRANCH \
--output "type=registry" ./

43
.github/workflows/on-tag.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Build on tag
permissions:
packages: write
on:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
- v[0-9]+.[0-9]+.[0-9]+-*
jobs:
build:
name: Build image
runs-on: ubuntu-20.04
steps:
- name: Checkout project
uses: actions/checkout@v2
- name: Set env variables
run: |
echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
- name: Login to Docker Hub
run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
id: qemu
- name: Setup Docker buildx action
uses: docker/setup-buildx-action@v1
id: buildx
- name: Run Docker buildx
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:$BRANCH \
--output "type=registry" ./

18
Dockerfile Normal file
View File

@ -0,0 +1,18 @@
# Go base image
FROM golang:1.17-alpine as builder
WORKDIR /app
COPY . .
RUN go install
RUN go build
FROM alpine:3.14
COPY --from=builder /app/lnme /lnme
EXPOSE 1323
CMD ["/lnme"]

21
LICENSE Normal file
View 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.

157
README.md
View File

@ -1,10 +1,11 @@
# LnMe - your friendly ⚡ payment page <p align="center"><br/><br/><img src="https://raw.githubusercontent.com/bumi/lnme/master/icon/lnme-icon-rrect.svg" height="128" width="128" /><br/><br/></p>
<h1 align="center">LnMe - your friendly payment page</h1><br/><br/>
LnMe is a personal Bitcoin Lightning payment page/widget and self-hosted [Lightning Address](https://lightningaddress.com/) server. 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/) - 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). LnMe focusses on simplicity and ease of deployment. It connects to an existing lightning node (currently LND is supported).
@ -17,7 +18,8 @@ LnMe is one [simple executable](https://github.com/bumi/lnme/releases) file that
- [x] WebLN integration - if [WebLN](https://webln.dev/) is not available a QRcode and the invoice will be shown - [x] WebLN integration - if [WebLN](https://webln.dev/) is not available a QRcode and the invoice will be shown
- [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
- [ ] [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
@ -34,10 +36,14 @@ There are no other dependencies. Simply download the binary and run it!
1. `$ git clone https://github.com/bumi/lnme.git && cd lnme` 1. `$ git clone https://github.com/bumi/lnme.git && cd lnme`
2. `$ go install` 2. `$ go install`
3. `$ rice embed-go && go build` 3. `$ go build`
4. `$ ./lnme` 4. `$ ./lnme`
Note: You must have [go.rice installed](https://github.com/GeertJohan/go.rice#installation) ### Running in Docker
LnMe is now available in docker too.
You can start LnMe like this: `docker run -it --rm ghcr.io/bumi/lnme:master`
A list of existing tags is available [here](https://github.com/bumi/lnme/pkgs/container/lnme).
### Configuration ### Configuration
@ -45,23 +51,25 @@ Note: You must have [go.rice installed](https://github.com/GeertJohan/go.rice#in
To connect to the lnd node the cert, macaroon and address of the lnd node has to be configured. LnMe uses the LND defaults. To connect to the lnd node the cert, macaroon and address of the lnd node has to be configured. LnMe uses the LND defaults.
* `lnd-address`: Host and port of the LND gRPC service. default: localhost:10009 - `lnd-address`: Host and port of the LND gRPC service. default: localhost:10009
* `lnd-cert-path`: Path to the LND TLS cert file. default: ~/.lnd/tls.cert - `lnd-cert-path`: Path to the LND TLS cert file. default: ~/.lnd/tls.cert
* `lnd-macaroon-path`: Path to the LND macaroon file. default: ~/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon (invoice.macaroon is recommended) - `lnd-macaroon-path`: Path to the LND macaroon file. default: ~/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon (invoice.macaroon is recommended)
Instead of the path to the macaroon and cert files you can also provide the hex strings: Instead of the path to the macaroon and cert files you can also provide the hex strings:
* `lnd-cert`: LND TLS cert as HEX string. - `lnd-cert`: LND TLS cert as HEX string.
* `lnd-macaroon`: LND macaroon HEX string. (invoice.macaroon is recommended) - `lnd-macaroon`: LND macaroon HEX string. (invoice.macaroon is recommended)
#### 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
* `disable-website`: Disable the default LnMe website. Disable the website if you only want to embed the LnMe widget on your existing website. - `lnurlp-comment-allowed`: Allowed length of LNURL-pay comments, maximum around [~2000 characters](https://stackoverflow.com/a/417184). (default: 210)
* `disable-cors`: Disable CORS headers. (default: false) - `disable-website`: Disable the default LnMe website. Disable the website if you only want to embed the LnMe widget on your existing website.
* `disable-ln-address`: Disable [Lightning Address](https://lightningaddress.com/) handling. - `disable-cors`: Disable CORS headers. (default: false)
* `port`: Port to listen on. (default: 1323) - `disable-ln-address`: Disable [Lightning Address](https://lightningaddress.com/) handling.
* `request-limit`: Limit the allowed requests per second. (default: 5) - `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: Depending on your deployment needs LnMe can be configured using the following options:
@ -93,8 +101,8 @@ All environment variables must be prefixed by `LNME_` use `_` instead of `-`
LnMe needs the following LND permissions: LnMe needs the following LND permissions:
* Read/Write permission for invoices - Read/Write permission for `invoices`
* Write permission for onchain address (if you want to use the onchain option) - Write permission for `address` (if you want to use the onchain option)
Use the LND [macaroon bakery](http://macaroon-bakery.freedomnode.com/) to create a new macaroon for LnMe. Use the LND [macaroon bakery](http://macaroon-bakery.freedomnode.com/) to create a new macaroon for LnMe.
@ -104,34 +112,85 @@ To get the HEX versions of the files use `xxd -plain` e.g. `xxd -plain invoice.m
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). 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 ## 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. 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.
### Heroku If you run LNMe on a different server you will need your LND address, the LND TLS certificate (HEX) and the macaroon (HEX). (see above)
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 LND files use `xxd -plain YOUR_FILE.cert | tr -d '\n'`. For example for 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
- /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.
### Heroku
One click deployment with Heroku:
[![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)
Here is a [Video Demo of the Heroku deployment](https://www.youtube.com/watch?v=hSFXhnLp_Rc) Here is a [Video Demo of the Heroku deployment](https://www.youtube.com/watch?v=hSFXhnLp_Rc)
### Deployment Notes 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
### 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)
I am running LnMe behind a reverse proxy using [caddy](https://caddyserver.com/) which comes with [fully-managed HTTPS](https://caddyserver.com/docs/quick-starts/https) via [letsencrypt](https://letsencrypt.org/). I am running LnMe behind a reverse proxy using [caddy](https://caddyserver.com/) which comes with [fully-managed HTTPS](https://caddyserver.com/docs/quick-starts/https) via [letsencrypt](https://letsencrypt.org/).
Example Caddyfile: Example Caddyfile:
``` ```
lnme.michaelbumann.com { lnme.michaelbumann.com {
reverse_proxy 127.0.0.1:1323 reverse_proxy 127.0.0.1:1323
} }
``` ```
`$ caddy --config /etc/caddy/Caddyfile`
`$ caddy --config /etc/caddy/Caddyfile`
## Feature Usage ## Feature Usage
@ -144,6 +203,13 @@ For more information check out the website: [lightningaddress.com](https://light
Your Lightning Address: `{anything}@{your domain}` Your Lightning Address: `{anything}@{your domain}`
### LNURL
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 ### Customize your ⚡ website
@ -155,6 +221,12 @@ 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
@ -163,7 +235,10 @@ You can integrate the LnMe widget in your existing website.
#### 1. Add the LnMe JavaScript files #### 1. Add the LnMe JavaScript files
```html ```html
<script data-lnme-base-url="https://your-lnme-host.com:1323" src="https://your-lnme-host.com/lnme/lnme.js"></script> <script
data-lnme-base-url="https://your-lnme-host.com:1323"
src="https://your-lnme-host.com/lnme/lnme.js"
></script>
``` ```
#### 2. Usage #### 2. Usage
@ -171,37 +246,40 @@ You can integrate the LnMe widget in your existing website.
To request a lightning payment simply call `request()` on a `new LnMe({value: value, memo: memo})`: To request a lightning payment simply call `request()` on a `new LnMe({value: value, memo: memo})`:
```js ```js
var lnme = new LnMe({ value: 1000, memo: 'high5' }); var lnme = new LnMe({ value: 1000, memo: "high5" });
lnme.request(); lnme.request();
``` ```
Use it from a plain HTML link: Use it from a plain HTML link:
```html ```html
<a href="#" onclick="javascript:new LnMe({ 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: ##### More advanced JS API:
```js ```js
let lnme = new LnMe({ value: 1000, memo: 'high5' }); let lnme = new LnMe({ value: 1000, memo: "high5" });
// get a new invoice and watch for a payment // get a new invoice and watch for a payment
// promise resolves if the invoice is settled // promise resolves if the invoice is settled
lnme.requestPayment().then(invoice => { lnme.requestPayment().then((invoice) => {
alert('YAY, thanks!'); alert("YAY, thanks!");
}); });
// create a new invoice // create a new invoice
lnme.addInvoice().then(invoice => { lnme.addInvoice().then((invoice) => {
console.log(invoice.PaymentRequest) console.log(invoice.PaymentRequest);
}); });
// periodically watch if an invoice is settled // periodically watch if an invoice is settled
lnme.watchPayment().then(invoice => { lnme.watchPayment().then((invoice) => {
alert('YAY, thanks!'); alert("YAY, thanks!");
}); });
``` ```
## Motivation ## Motivation
@ -211,21 +289,22 @@ BTCPay Server is too big and hard to run for that and I do not need most of its
## Development ## Development
Use `go run` to ron the service locally: Use `go run` to run the service locally:
$ go run lnme.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
## Build ## Build
LnMe uses [go.rice](https://github.com/GeertJohan/go.rice) to embed assets (HTML, JS, and CSS files). run `rice embed-go` (needs to be [installed](https://github.com/GeertJohan/go.rice#installation))
$ rice embed-go
$ go build $ go build
## Contributing ## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/bumi/lnme Bug reports and pull requests are welcome on GitHub at https://github.com/bumi/lnme
## Support
If you like this software and it is valuable for you, you can send sats to ⚡bumi@getalby.com
## License ## License
Available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). Available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).

View File

@ -16,11 +16,18 @@
"LNME_LND_CERT": { "LNME_LND_CERT": {
"description": "LND TLS cert as HEX", "description": "LND TLS cert as HEX",
"value": "" "value": ""
},
"LNME_TOR_EXE_PATH": {
"description": "Path to the tor executable",
"value": "/app/tor/bin/tor"
} }
}, },
"buildpacks": [ "buildpacks": [
{ {
"url": "https://github.com/heroku/heroku-buildpack-go" "url": "https://github.com/heroku/heroku-buildpack-go"
},
{
"url": "https://github.com/iamashks/heroku-buildpack-tor-proxy.git"
} }
] ]
} }

20
files/assets/favicon.svg Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>lnme-icon-no-padding</title>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#FFA500" offset="0%"></stop>
<stop stop-color="#FFE300" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#FFE300" offset="0%"></stop>
<stop stop-color="#FFA500" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lnme-icon-no-padding" transform="translate(0.000000, -0.460904)">
<polygon id="Path1" fill="url(#linearGradient-1)" points="439.147203 0 192.83828 475.102188 823.013089 475.102188 909.205258 386.884293 491.070577 386.884293 737.132519 0.460904"></polygon>
<polygon id="Path2" fill="url(#linearGradient-2)" points="115.14578 615.666174 528.131075 615.666174 327.461698 1024 793.918502 509.464888 174.115801 509.464888"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

27
files/assets/icon.svg Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>lnme-icon-rect</title>
<defs>
<linearGradient x1="0%" y1="64.5790537%" x2="100%" y2="64.5790537%" id="linearGradient-1">
<stop stop-color="#3D3393" offset="0%"></stop>
<stop stop-color="#2B76B9" offset="24.6013508%"></stop>
<stop stop-color="#2CACD1" offset="67.981896%"></stop>
<stop stop-color="#35EB93" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#FFA500" offset="0%"></stop>
<stop stop-color="#FFE300" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-3">
<stop stop-color="#FFE300" offset="0%"></stop>
<stop stop-color="#FFA500" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lnme-icon-rect">
<rect id="Background" fill="url(#linearGradient-1)" x="0" y="0" width="1024" height="1024"></rect>
<polygon id="Path1" fill="url(#linearGradient-2)" points="456 91.539096 254.655336 479.91029 769.790288 479.91029 840.247879 407.796774 498.444643 407.796774 698.541282 95.539096"></polygon>
<polygon id="Path2" fill="url(#linearGradient-3)" points="191.14578 594.813998 528.739662 594.813998 364.70294 928.605547 746.006985 508 239.350688 508"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

View File

@ -120,15 +120,11 @@ class LnMe {
} }
payWithWebln() { payWithWebln() {
if (!webln.isEnabled) { webln.enable().then((weblnResponse) => {
webln.enable().then((weblnResponse) => {
return webln.sendPayment(this.invoice.payment_request);
}).catch((e) => {
return this.showPaymentRequest();
})
} else {
return webln.sendPayment(this.invoice.payment_request); return webln.sendPayment(this.invoice.payment_request);
} }).catch((e) => {
return this.showPaymentRequest();
})
} }
populatePaymentRequest() { populatePaymentRequest() {
@ -148,7 +144,7 @@ class LnMe {
this.target.querySelectorAll('.lnme-copy').forEach(element => { this.target.querySelectorAll('.lnme-copy').forEach(element => {
element.addEventListener('click', (e) => { element.addEventListener('click', (e) => {
navigator.clipboard.writeText(this.invoice.payment_request).then(() => { navigator.clipboard.writeText(this.invoice.payment_request).then(() => {
alert('Copied to clipboad'); alert('Copied to clipboard');
}); });
}); });
}); });

View File

@ -1,3 +0,0 @@
<svg width="128" height="128" style="enable-background:new 0 0 128 128;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M115.36,61.84L70.22,50.49L114.45,2.4c0.41-0.45,0.43-1.13,0.05-1.6c-0.39-0.48-1.07-0.59-1.59-0.27 L12.3,61.98c-0.41,0.25-0.64,0.72-0.57,1.2c0.06,0.48,0.4,0.87,0.87,1.01l45.07,13.25L13.38,125.6c-0.42,0.46-0.44,1.15-0.04,1.61 c0.24,0.29,0.58,0.44,0.94,0.44c0.22,0,0.45-0.06,0.65-0.19l100.78-63.41c0.42-0.26,0.64-0.75,0.56-1.22 C116.19,62.34,115.84,61.95,115.36,61.84z" style="fill:#FCC21B;"/>
</svg>

Before

Width:  |  Height:  |  Size: 559 B

View File

@ -6,9 +6,9 @@
<meta property="og:site_name" content=""> <meta property="og:site_name" content="">
<meta property="og:description" content=""> <meta property="og:description" content="">
<meta property="og:image" content="/lnme/zap.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%;
@ -111,13 +111,14 @@
cursor: pointer; cursor: pointer;
} }
</style> </style>
<link rel="icon" type="image/svg+xml" href="/lnme/favicon.svg" />
</head> </head>
<body> <body>
<div class="wrapper"> <div class="wrapper">
<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>
@ -158,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 Normal file
View 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"

149
go.mod
View File

@ -1,17 +1,146 @@
// +heroku goVersion 1.15 // +heroku goVersion 1.17
module github.com/bumi/lnme module github.com/bumi/lnme
go 1.15 go 1.17
// https://github.com/lightningnetwork/lnd/issues/5624#issuecomment-897512230
replace go.etcd.io/etcd => go.etcd.io/etcd v0.5.0-alpha.5.0.20201125193152-8a03d2e9614b
require ( require (
github.com/GeertJohan/go.rice v1.0.2
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
github.com/didip/tollbooth/v6 v6.1.1 github.com/didip/tollbooth/v6 v6.1.2
github.com/knadh/koanf v1.2.1 github.com/knadh/koanf v1.4.1
github.com/labstack/echo/v4 v4.5.0 github.com/labstack/echo/v4 v4.9.0
github.com/lightningnetwork/lnd v0.13.1-beta github.com/lightningnetwork/lnd v0.14.1-beta
google.golang.org/grpc v1.40.0 google.golang.org/grpc v1.45.0
gopkg.in/macaroon.v2 v2.1.0 gopkg.in/macaroon.v2 v2.1.0
) )
require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/siphash v1.0.1 // indirect
github.com/andybalholm/brotli v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.22.0-beta.0.20211005184431-e3449998be39 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/btcutil v1.0.3-0.20210527170813-e2ba6805a890 // indirect
github.com/btcsuite/btcutil/psbt v1.0.3-0.20210527170813-e2ba6805a890 // indirect
github.com/btcsuite/btcwallet v0.13.0 // indirect
github.com/btcsuite/btcwallet/wallet/txauthor v1.1.0 // indirect
github.com/btcsuite/btcwallet/wallet/txrules v1.1.0 // indirect
github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec // indirect
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210822222949-9b5a201c344c // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/lru v1.0.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fergusstrange/embedded-postgres v1.10.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-pkgz/expirable-cache v0.0.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.10.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.8.1 // indirect
github.com/jackc/pgx/v4 v4.13.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/jrick/logrotate v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
github.com/kkdai/bstream v1.0.0 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/lib/pq v1.10.3 // indirect
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
github.com/lightninglabs/neutrino v0.13.0 // indirect
github.com/lightningnetwork/lightning-onion v1.0.2-0.20210520211913-522b799e65b1 // indirect
github.com/lightningnetwork/lnd/clock v1.1.0 // indirect
github.com/lightningnetwork/lnd/healthcheck v1.2.0 // indirect
github.com/lightningnetwork/lnd/kvdb v1.2.1 // indirect
github.com/lightningnetwork/lnd/queue v1.1.0 // indirect
github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mholt/archiver/v3 v3.5.0 // indirect
github.com/miekg/dns v1.1.43 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/nwaples/rardecode v1.1.2 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/pierrec/lz4/v4 v4.1.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/sirupsen/logrus v1.7.0 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/v2 v2.305.0 // indirect
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect
go.etcd.io/etcd/raft/v3 v3.5.0 // indirect
go.etcd.io/etcd/server/v3 v3.5.0 // indirect
go.opentelemetry.io/contrib v0.20.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
go.opentelemetry.io/otel v0.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
go.opentelemetry.io/otel/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
go.opentelemetry.io/otel/trace v0.20.0 // indirect
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

977
go.sum

File diff suppressed because it is too large Load Diff

BIN
icon/lnme-icon-bnw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

27
icon/lnme-icon-bnw.svg Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>lnme-icon-bnw</title>
<defs>
<linearGradient x1="0%" y1="64.5790537%" x2="100%" y2="64.5790537%" id="linearGradient-1">
<stop stop-color="#3A3A3A" offset="0%"></stop>
<stop stop-color="#595959" offset="24.6013508%"></stop>
<stop stop-color="#7B7B7B" offset="67.981896%"></stop>
<stop stop-color="#8B8B8B" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#B4B4B4" offset="0%"></stop>
<stop stop-color="#FFFFFF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-3">
<stop stop-color="#FFFFFF" offset="0%"></stop>
<stop stop-color="#AEAEAE" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lnme-icon-bnw">
<rect id="Background" fill="url(#linearGradient-1)" x="0" y="0" width="1024" height="1024" rx="128"></rect>
<polygon id="Path1" fill="url(#linearGradient-2)" points="456 91.539096 254.655336 479.91029 769.790288 479.91029 840.247879 407.796774 498.444643 407.796774 698.541282 95.539096"></polygon>
<polygon id="Path2" fill="url(#linearGradient-3)" points="191.15 594.813998 528.743882 594.813998 364.70716 928.605547 746.011205 508 239.354908 508"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icon/lnme-icon-bnw@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>lnme-icon-no-padding</title>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#FFA500" offset="0%"></stop>
<stop stop-color="#FFE300" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#FFE300" offset="0%"></stop>
<stop stop-color="#FFA500" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lnme-icon-no-padding" transform="translate(0.000000, -0.460904)">
<polygon id="Path1" fill="url(#linearGradient-1)" points="439.147203 0 192.83828 475.102188 823.013089 475.102188 909.205258 386.884293 491.070577 386.884293 737.132519 0.460904"></polygon>
<polygon id="Path2" fill="url(#linearGradient-2)" points="115.14578 615.666174 528.131075 615.666174 327.461698 1024 793.918502 509.464888 174.115801 509.464888"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

BIN
icon/lnme-icon-rect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

27
icon/lnme-icon-rect.svg Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>lnme-icon-rect</title>
<defs>
<linearGradient x1="0%" y1="64.5790537%" x2="100%" y2="64.5790537%" id="linearGradient-1">
<stop stop-color="#3D3393" offset="0%"></stop>
<stop stop-color="#2B76B9" offset="24.6013508%"></stop>
<stop stop-color="#2CACD1" offset="67.981896%"></stop>
<stop stop-color="#35EB93" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#FFA500" offset="0%"></stop>
<stop stop-color="#FFE300" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-3">
<stop stop-color="#FFE300" offset="0%"></stop>
<stop stop-color="#FFA500" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lnme-icon-rect">
<rect id="Background" fill="url(#linearGradient-1)" x="0" y="0" width="1024" height="1024"></rect>
<polygon id="Path1" fill="url(#linearGradient-2)" points="456 91.539096 254.655336 479.91029 769.790288 479.91029 840.247879 407.796774 498.444643 407.796774 698.541282 95.539096"></polygon>
<polygon id="Path2" fill="url(#linearGradient-3)" points="191.14578 594.813998 528.739662 594.813998 364.70294 928.605547 746.006985 508 239.350688 508"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icon/lnme-icon-rect@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

BIN
icon/lnme-icon-rrect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 KiB

27
icon/lnme-icon-rrect.svg Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>lnme-icon-rrect</title>
<defs>
<linearGradient x1="0%" y1="64.5790537%" x2="100%" y2="64.5790537%" id="linearGradient-1">
<stop stop-color="#3D3393" offset="0%"></stop>
<stop stop-color="#2B76B9" offset="24.6013508%"></stop>
<stop stop-color="#2CACD1" offset="67.981896%"></stop>
<stop stop-color="#35EB93" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#FFA500" offset="0%"></stop>
<stop stop-color="#FFE300" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-3">
<stop stop-color="#FFE300" offset="0%"></stop>
<stop stop-color="#FFA500" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lnme-icon-rrect">
<rect id="Background" fill="url(#linearGradient-1)" x="0" y="0" width="1024" height="1024" rx="128"></rect>
<polygon id="Path1" fill="url(#linearGradient-2)" points="456 91.539096 254.655336 479.91029 769.790288 479.91029 840.247879 407.796774 498.444643 407.796774 698.541282 95.539096"></polygon>
<polygon id="Path2" fill="url(#linearGradient-3)" points="191.14578 594.813998 528.739662 594.813998 364.70294 928.605547 746.006985 508 239.350688 508"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icon/lnme-icon-rrect@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
icon/lnme-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
icon/lnme-icon.sketch Normal file

Binary file not shown.

20
icon/lnme-icon.svg Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>lnme-icon</title>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#FFA500" offset="0%"></stop>
<stop stop-color="#FFE300" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
<stop stop-color="#FFE300" offset="0%"></stop>
<stop stop-color="#FFA500" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lnme-icon">
<polygon id="Path1" fill="url(#linearGradient-1)" points="456 91.539096 254.655336 479.91029 769.790288 479.91029 840.247879 407.796774 498.444643 407.796774 698.541282 95.539096"></polygon>
<polygon id="Path2" fill="url(#linearGradient-2)" points="191.14578 594.813998 528.739662 594.813998 364.70294 928.605547 746.006985 508 239.350688 508"></polygon>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
icon/lnme-icon@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

View File

@ -37,6 +37,7 @@ type LNDoptions struct {
CertHex string CertHex string
MacaroonFile string MacaroonFile string
MacaroonHex string MacaroonHex string
TorExePath string // used when connecting through tor to LND
} }
type LNDclient struct { type LNDclient struct {
@ -46,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) (Invoice, error) { func (c LNDclient) AddInvoice(value int64, memo string, descriptionHash []byte, private bool) (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)
@ -54,6 +55,7 @@ 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 {
@ -138,7 +140,7 @@ func NewLNDclient(lndOptions LNDoptions) (LNDclient, error) {
if strings.Contains(lndOptions.Address, ".onion") { if strings.Contains(lndOptions.Address, ".onion") {
// Start Tor // Start Tor
t, err := tor.Start(nil, nil) t, err := tor.Start(nil, &tor.StartConf{ExePath: lndOptions.TorExePath})
if err != nil { if err != nil {
return result, err return result, err
} }
@ -172,7 +174,10 @@ func NewLNDclient(lndOptions LNDoptions) (LNDclient, error) {
if err := mac.UnmarshalBinary(macaroonData); err != nil { if err := mac.UnmarshalBinary(macaroonData); err != nil {
return result, err return result, err
} }
macCred := macaroons.NewMacaroonCredential(mac) macCred, err := macaroons.NewMacaroonCredential(mac)
if err != nil {
return result, err
}
opts = append(opts, grpc.WithPerRPCCredentials(macCred)) opts = append(opts, grpc.WithPerRPCCredentials(macCred))
conn, err := grpc.Dial(lndOptions.Address, opts...) conn, err := grpc.Dial(lndOptions.Address, opts...)

92
lnme.go
View File

@ -2,15 +2,16 @@ package main
import ( import (
"crypto/sha256" "crypto/sha256"
"embed"
"flag" "flag"
"fmt" "fmt"
"io/fs"
"log" "log"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"strings" "strings"
rice "github.com/GeertJohan/go.rice"
"github.com/bumi/lnme/ln" "github.com/bumi/lnme/ln"
"github.com/bumi/lnme/lnurl" "github.com/bumi/lnme/lnurl"
"github.com/didip/tollbooth/v6" "github.com/didip/tollbooth/v6"
@ -24,6 +25,8 @@ import (
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
) )
const DEFAULT_LISTEN = ":1323"
// Middleware for request limited to prevent too many requests // Middleware for request limited to prevent too many requests
// TODO: move to file // TODO: move to file
func LimitMiddleware(lmt *limiter.Limiter) echo.MiddlewareFunc { func LimitMiddleware(lmt *limiter.Limiter) echo.MiddlewareFunc {
@ -45,6 +48,12 @@ type Invoice struct {
Memo string `json:"memo"` Memo string `json:"memo"`
} }
//go:embed files/assets/*
var embeddedAssets embed.FS
//go:embed files/root/index.html
var indexPage string
func main() { func main() {
cfg := LoadConfig() cfg := LoadConfig()
@ -55,19 +64,17 @@ func main() {
e.Static("/", cfg.String("static-path")) e.Static("/", cfg.String("static-path"))
// Serve default page // Serve default page
} else if !cfg.Bool("disable-website") { } else if !cfg.Bool("disable-website") {
rootBox := rice.MustFindBox("files/root") stdOutLogger.Print("Running embedded page")
indexPage, err := rootBox.String("index.html") e.GET("/", func(c echo.Context) error {
if err == nil { return c.HTML(200, indexPage)
stdOutLogger.Print("Running embedded page") })
e.GET("/", func(c echo.Context) error { }
return c.HTML(200, indexPage) assetSubdir, err := fs.Sub(embeddedAssets, "files/assets")
}) if err != nil {
} else { log.Fatal(err)
stdOutLogger.Printf("Failed to run embedded website: %s", err)
}
} }
// Embed static files and serve those on /lnme (e.g. /lnme/lnme.js) // Embed static files and serve those on /lnme (e.g. /lnme/lnme.js)
assetHandler := http.FileServer(rice.MustFindBox("files/assets").HTTPBox()) assetHandler := http.FileServer(http.FS(assetSubdir))
e.GET("/lnme/*", echo.WrapHandler(http.StripPrefix("/lnme/", assetHandler))) e.GET("/lnme/*", echo.WrapHandler(http.StripPrefix("/lnme/", assetHandler)))
// CORS settings // CORS settings
@ -92,6 +99,7 @@ func main() {
CertHex: cfg.String("lnd-cert"), CertHex: cfg.String("lnd-cert"),
MacaroonFile: cfg.String("lnd-macaroon-path"), MacaroonFile: cfg.String("lnd-macaroon-path"),
MacaroonHex: cfg.String("lnd-macaroon"), MacaroonHex: cfg.String("lnd-macaroon"),
TorExePath: cfg.String("tor-exe-path"),
} }
lnClient, err := ln.NewLNDclient(lndOptions) lnClient, err := ln.NewLNDclient(lndOptions)
if err != nil { if err != nil {
@ -109,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) invoice, err := lnClient.AddInvoice(i.Value, i.Memo, nil, cfg.Bool("enable-private-channels"))
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")
@ -142,19 +150,29 @@ func main() {
}) })
if !cfg.Bool("disable-ln-address") { if !cfg.Bool("disable-ln-address") {
e.GET("/.well-known/lnurlp/:name", func(c echo.Context) error { lnurlHandler := func(c echo.Context) error {
host := c.Request().Host
proto := c.Scheme()
// TODO: support RFC7239 Forwarded header
if c.Request().Header.Get("X-Forwarded-Host") != "" {
host = c.Request().Header.Get("X-Forwarded-Host")
}
if c.Request().Header.Get("X-Forwarded-Proto") != "" {
proto = c.Request().Header.Get("X-Forwarded-Proto")
}
name := c.Param("name") name := c.Param("name")
lightningAddress := name + "@" + c.Request().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{
LNURLResponse: lnurl.LNURLResponse{Status: "OK"}, LNURLResponse: lnurl.LNURLResponse{Status: "OK"},
Callback: fmt.Sprintf("%s://%s%s", c.Scheme(), c.Request().Host, c.Request().URL.Path), Callback: fmt.Sprintf("%s://%s%s", proto, host, c.Request().URL.Path),
MinSendable: 1000, MinSendable: 1000,
MaxSendable: 100000000, MaxSendable: 100000000,
EncodedMetadata: lnurlMetadata, EncodedMetadata: lnurlMetadata,
CommentAllowed: 0, CommentAllowed: lnurlpCommentAllowed,
Tag: "payRequest", Tag: "payRequest",
} }
return c.JSON(http.StatusOK, lnurlPayResponse1) return c.JSON(http.StatusOK, lnurlPayResponse1)
@ -162,11 +180,21 @@ func main() {
stdOutLogger.Printf("New LightningAddress request amount: %s", amount) stdOutLogger.Printf("New LightningAddress request amount: %s", amount)
msats, err := strconv.ParseInt(amount, 10, 64) msats, err := strconv.ParseInt(amount, 10, 64)
if err != nil || msats < 1000 { if err != nil || msats < 1000 {
stdOutLogger.Printf("Invalid amount: %s", amount)
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, 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"})
}
lnurlPayResponse2 := lnurl.LNURLPayResponse2{ lnurlPayResponse2 := lnurl.LNURLPayResponse2{
LNURLResponse: lnurl.LNURLResponse{Status: "OK"}, LNURLResponse: lnurl.LNURLResponse{Status: "OK"},
PR: invoice.PaymentRequest, PR: invoice.PaymentRequest,
@ -176,7 +204,9 @@ func main() {
} }
return c.JSON(http.StatusOK, lnurlPayResponse2) return c.JSON(http.StatusOK, lnurlPayResponse2)
} }
}) }
e.GET("/.well-known/lnurlp/:name", lnurlHandler)
e.GET("/lnurlp/:name", lnurlHandler)
} }
// Debug test endpoint // Debug test endpoint
@ -185,10 +215,24 @@ func main() {
}) })
port := cfg.String("port") port := cfg.String("port")
// Special case for PORT instead of LNME_PORT due to cloud-providers
if os.Getenv("PORT") != "" { if os.Getenv("PORT") != "" {
port = 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 { func LoadConfig() *koanf.Koanf {
@ -197,13 +241,19 @@ func LoadConfig() *koanf.Koanf {
f := flag.NewFlagSet("LnMe", flag.ExitOnError) f := flag.NewFlagSet("LnMe", flag.ExitOnError)
f.String("lnd-address", "localhost:10009", "The host and port of the LND gRPC server.") f.String("lnd-address", "localhost:10009", "The host and port of the LND gRPC server.")
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-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.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", "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))
f.String("tor-exe-path", "tor", "Path to the Tor executable. Used when connecting through Tor. (default: tor)")
var configPath string var configPath string
f.StringVar(&configPath, "config", "config.toml", "Path to a .toml config file.") f.StringVar(&configPath, "config", "config.toml", "Path to a .toml config file.")
f.Parse(os.Args[1:]) f.Parse(os.Args[1:])

File diff suppressed because one or more lines are too long