Go middleware for monetizing your API on a per-request basis with Bitcoin and Lightning ⚡️
Middlewares for:
- net/http
HandlerFunc
- net/http
Handler
- Compatible with routers like gorilla/mux, httprouter and chi
- Gin
- Echo
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.
Until the rise of cryptocurrencies, if you wanted to monetize your API (set up a paywall), you had to:
- Use a centralized service (like PayPal)
- Can shut you down any time
- High fees
- Your API users need an account
- Can be hacked
- Keep track of your API users (keep accounts and their API keys in some database)
- Privacy concerns
- Data breaches / leaks
- 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.
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:
- The first request gets rejected with the
402 Payment Required
HTTP status, aContent-Type: application/vnd.lightning.bolt11
header and a Lightning (BOLT-11-conforming) invoice in the body - 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.
There are currently two prerequisites:
- 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
- Run for example with Docker:
- eclair (not implemented yet - )
- Roll your own!
- Just implement the simple
wall.LNClient
interface (only two methods!)
- Just implement the simple
- lnd
- 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)!
- Note: In production you should use a configuration with password (check out
- groupcache (not implemented yet - )
- Roll your own!
- Just implement the simple
wall.StorageClient
interface (only two methods!)
- Just implement the simple
- A simple Go map
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.
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.
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:
- QR code generation API using Gin
- Ready-to-use Docker image: https://hub.docker.com/r/philippgille/qr-code/
- Try out the demo deployed on https://lightning.ws
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.
- https://github.com/ElementsProject/paypercall
- Middleware for the JavaScript web framework Express
- Can also act as reverse proxy for web services that aren't written in JavaScript
- Payment: Lightning Network
- Backend: c-lightning only (no lnd)
- https://github.com/interledgerjs/koa-web-monetization
- Middleware for the JavaScript web framework Koa
- Payment: Interledger
- https://moonbanking.com/api
- API that uses a similar functionality, not providing it
- https://www.coinbee.io/
- Paid service for Bitcoin paywalls (no Lightning)
- Looks like its meant for websites only, not APIs, because the browser session is involved