diff --git a/donations/LICENSE b/donations/LICENSE new file mode 100644 index 0000000..a9102c2 --- /dev/null +++ b/donations/LICENSE @@ -0,0 +1,22 @@ +Note: the modules in the ccan/ directory have their own licenses, but +the rest of the code is covered by the following (BSD-MIT) license: + + Copyright Rene Pickhardt 2018. + +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. \ No newline at end of file diff --git a/donations/README.md b/donations/README.md new file mode 100644 index 0000000..f6e1e88 --- /dev/null +++ b/donations/README.md @@ -0,0 +1,45 @@ +# Invoice Service (for Donations) plugin + +This plugin enables c-lightning nodes to start one or several small webserver +via the command line on specified port. The webserver is based on flask and +exposes the invoice API call. + +Therefor people can query for an invoice which they can use to pay. + +Run the plugin with: + +``` +lightningd --plugin=/path/to/lightning/contrib/plugins/donations/donations.py +``` + +Once the plugin is active you can run `lightning-cli help donationserver` +to learn about the command line API: + +Controls a donationserver with `start`/`stop`/`restart`/`list` on `port` + +A Simple HTTP Server is created that can serve a donation webpage and allow to issue invoices. +The plugin takes one of the following three commands {start/stop/restart} as the first agument +By default the plugin starts the server on port 8088. This can however be changed with the +port argument. + +This means after starting `lightningd` together with the plugin you can run: +`lightning-cli donationserver start` and access the server at +http://localhost:33506/donation (in case you run your lightning node at +`localhost`) + +## About the plugin +You can see a demo of the plugin on the [authors website][rene-donations]: + + +This plugin was created and is maintained by Rene Pickhardt. Thus Rene Pickhardt +is the copyright owner of this plugin. It shall serve as an educational resource +on his [Youtube channel][rene-youtube]. + +The plugin is licensed like the rest of c-lightning with BSD-MIT license +and comes without any warrenty. + +If you like my work feel free to support me on [patreon][rene-patreon]. + +[rene-donations]: https://ln.rene-pickhardt.de/donation +[rene-patreon]: https://www.patreon.com/renepickhardt +[rene-youtube]: https://www.youtube.com/user/RenePickhardt diff --git a/donations/donations.py b/donations/donations.py new file mode 100755 index 0000000..95d3a4c --- /dev/null +++ b/donations/donations.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +""" A small donation service so that users can request ln invoices + +This plugin spins up a small flask server that provides a form to +users who wish to donate some money to the owner of the lightning +node. The server can run on an arbitrary port and returns an invoice. +Also a list of previously paid invoices (only those that used this +service) will be displayed. Displaying paid invoices could be made +optionally in a future version. + +Author: Rene Pickhardt (https://ln.rene-pickhardt.de) + +you can see a demo of the plugin (and leave a tip) directly at: + https://ln.rene-pickhardt.de/donation + +LICENSE: MIT / APACHE +""" +import base64 +import json +import multiprocessing +import qrcode +import sys + + +from flask import Flask, render_template +from flask_bootstrap import Bootstrap +from flask_wtf import FlaskForm +from io import BytesIO +from lightning import LightningRpc, Plugin +from os.path import join +from random import random +from time import time +from wtforms import StringField, SubmitField, IntegerField +from wtforms.validators import Required, NumberRange + + +plugin = Plugin() + + +class DonationForm(FlaskForm): + """Form for donations """ + amount = IntegerField("Enter how many Satoshis you want to donate!", + validators=[Required(), NumberRange(min=1, max=16666666)]) + description = StringField("Leave a comment (displayed publically)") + submit = SubmitField('Donate') + + +def make_base64_qr_code(bolt11): + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=4, + border=4, + ) + + qr.add_data(bolt11) + qr.make(fit=True) + img = qr.make_image() + + buffered = BytesIO() + img.save(buffered, format="PNG") + img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") + return img_str + + +def ajax(label): + global plugin + msg = plugin.rpc.listinvoices(label)["invoices"][0] + if msg["status"] == "paid": + return "Your donation has been received and is well appricated." + return "waiting" + + +def donation_form(): + global plugin + form = DonationForm() + b11 = None + qr = None + label = None + if form.validate_on_submit(): + amount = form.amount.data + description = form.description.data + label = "ln-plugin-donation-{}".format(random()) + invoice = plugin.rpc.invoice( + int(amount)*1000, label, description) + b11 = invoice["bolt11"] + qr = make_base64_qr_code(b11) + + invoices = plugin.rpc.listinvoices()["invoices"] + donations = [] + for invoice in invoices: + if invoice["label"].startswith("ln-plugin-donation-"): + # FIXME: change to paid after debugging + if invoice["status"] == "paid": + bolt11 = plugin.rpc.decodepay(invoice["bolt11"]) + satoshis = int(bolt11["msatoshi"])//1000 + description = bolt11["description"] + ts = bolt11["created_at"] + donations.append((ts, satoshis, description)) + + if b11 is not None: + return render_template("donation.html", donations=sorted(donations, reverse=True), form=form, bolt11=b11, qr=qr, label=label) + else: + return render_template("donation.html", donations=sorted(donations, reverse=True), form=form) + + +def worker(port): + app = Flask(__name__) + # FIXME: use hexlified hsm secret or something else + app.config['SECRET_KEY'] = 'you-will-never-guess-this' + app.add_url_rule('/donation', 'donation', + donation_form, methods=["GET", "POST"]) + app.add_url_rule('/is_invoice_paid/