/ln-paywall

Go middleware for monetizing your API on a per-request basis with Bitcoin and Lightning ⚡️

Primary LanguageGoMozilla Public License 2.0MPL-2.0

ln-paywall

GoDoc Build Status Go Report Card GitHub Releases

Go middleware for monetizing your API on a per-request basis with Bitcoin and Lightning ⚡️

Middlewares for:

A client package exists as well to make consuming LN-paywalled APIs extremely easy (you just use it like the standard Go http.Client and the payment handling is done in the background).

An API gateway is on the roadmap as well, which you can use to monetize your API that's written in any language, not just in Go.

Contents

Purpose

Until the rise of cryptocurrencies, if you wanted to monetize your API (set up a paywall), you had to:

  1. Use a centralized service (like PayPal)
    • Can shut you down any time
    • High fees
    • Your API users need an account
    • Can be hacked
  2. Keep track of your API users (keep accounts and their API keys in some database)
    • Privacy concerns
    • Data breaches / leaks
  3. Charge for a bunch of requests, like 10.000 at a time, because real per-request payments weren't possible

With cryptocurrencies in general some of those problems were solved, but with long confirmation times and high per-transaction fees a real per-request billing was still not feasable.

But then came the Lightning Network, an implementation of routed payment channels, which enables real near-instant microtransactions with extremely low fees, which cryptocurrencies have long promised, but never delivered. It's a second layer on top of existing cryptocurrencies like Bitcoin that scales far beyond the limitations of the underlying blockchain.

ln-paywall makes it easy to set up an API paywall for payments over the Lightning Network.

How it works

With ln-paywall you can simply use one of the provided middlewares in your Go web service to have your web service do two things:

  1. The first request gets rejected with the 402 Payment Required HTTP status, a Content-Type: application/vnd.lightning.bolt11 header and a Lightning (BOLT-11-conforming) invoice in the body
  2. The second request must contain a X-Preimage header with the preimage of the paid Lightning invoice (hex encoded). The middleware checks if 1) the invoice was paid and 2) not already used for a previous request. If both preconditions are met, it continues to the next middleware or final request handler.

Prerequisites

There are currently two prerequisites:

  1. A running Lightning Network node. The middleware connects to the node for example to create invoices for a request. The ln package currently provides factory functions for the following LN implementations:
    • lnd
      • Requires the node to listen to gRPC connections
      • If you don't run it locally, it needs to listen to connections from external machines (so for example on 0.0.0.0 instead of localhost) and has the TLS certificate configured to include the external IP address of the node.
    • c-lightning with Lightning Charge
      • Run for example with Docker: docker run -d -u `id -u` -v `pwd`/data:/data -p 9112:9112 -e API_TOKEN=secret shesek/lightning-charge
      • Vanilla c-lightning (without Lightning Charge) won't be supported as long as c-lightning's RPC API only works via Unix socket and cannot be used as a remote server, because this is not a good fit for potentially multiple web service instances elastically scaled across a cluster of host machines
    • eclair (not implemented yet - PRs Welcome )
    • Roll your own!
      • Just implement the simple wall.LNClient interface (only two methods!)
  2. A supported storage mechanism. It's used to cache preimages that have been used as a payment for an API call, so that a user can't do multiple requests with the same preimage of a settled Lightning payment. The wall package currently provides factory functions for the following storages:
    • A simple Go map
      • The fastest option, but 1) can't be used across horizontally scaled service instances and 2) doesn't persist data, so when you restart your server, users can re-use old preimages
    • bbolt - a fork of Bolt maintained by CoreOS
      • Very fast, doesn't require any remote or local TCP connections and persists the data, but can't be used across horizontally scaled service instances because it's file-based. Production-ready for single-instance web services though.
    • Redis
      • Although the slowest of these options, still fast and most suited for popular web services: Requires a remote or local TCP connection and some administration, but allows data persistency and can even be used with a horizontally scaled web service
      • Run for example with Docker: docker run -d -p 6379:6379 redis
        • Note: In production you should use a configuration with password (check out bitnami/redis which makes that easy)!
    • groupcache (not implemented yet - PRs Welcome )
    • Roll your own!
      • Just implement the simple wall.StorageClient interface (only two methods!)

Usage

GoDoc

Get the package with go get -u github.com/philippgille/ln-paywall/....

We strongly encourage you to use vendoring, because as long as ln-paywall is version 0.x, breaking changes may be introduced in new versions, including changes to the package name / import path. The project adheres to Semantic Versioning and all notable changes to this project are documented in RELEASES.md.

Middleware

The best way to see how to use ln-paywall is by example. In the below examples we create a web service that responds to requests to /ping with "pong", using Gin as the web framework.

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/philippgille/ln-paywall/ln"
	"github.com/philippgille/ln-paywall/storage"
	"github.com/philippgille/ln-paywall/wall"
)

func main() {
	r := gin.Default()

	// Configure middleware
	invoiceOptions := wall.DefaultInvoiceOptions // Price: 1 Satoshi; Memo: "API call"
	lndOptions := ln.DefaultLNDoptions           // Address: "localhost:10009", CertFile: "tls.cert", MacaroonFile: "invoice.macaroon"
	storageClient := storage.NewGoMap()          // Local in-memory cache
	lnClient, err := ln.NewLNDclient(lndOptions)
	if err != nil {
		panic(err)
	}
	// Use middleware
	r.Use(wall.NewGinMiddleware(invoiceOptions, lnClient, storageClient))

	r.GET("/ping", func(c *gin.Context) {
		c.String(http.StatusOK, "pong")
	})

	r.Run() // Listen and serve on 0.0.0.0:8080
}

This is just the most basic example. See the list of examples below for examples with other web frameworks / routers / just the stdlib, as well as for a more complex and useful example.

List of examples

Follow the links to the example code files.

Simple examples to show the use for the different web frameworks / routers / just the stdlib:

More complex and useful example:

Client

package main

import (
	"fmt"
	"io/ioutil"

	"github.com/philippgille/ln-paywall/ln"
	"github.com/philippgille/ln-paywall/pay"
)

func main() {
	// Set up client
	lndOptions := ln.LNDoptions{ // Default address: "localhost:10009", CertFile: "tls.cert"
		MacaroonFile: "admin.macaroon", // admin.macaroon is required for making payments
	}
	lnClient, err := ln.NewLNDclient(lndOptions)
	if err != nil {
		panic(err)
	}
	client := pay.NewClient(nil, lnClient) // Uses http.DefaultClient if no http.Client is passed

	// Send request to an ln-paywalled API
	res, err := client.Get("http://localhost:8080/ping")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	// Print response body
	resBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(resBody))
}

You can also view this example here.

Related Projects