From ea6846280ad3efbaf49c3b9ac6c3589aa500946f Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Sun, 2 Aug 2026 00:05:56 +0200 Subject: [PATCH] docs: publish a project page on GitHub Pages Add docs/index.html, a self-contained landing page explaining what the library does, why self-hosting a Lightning Address is worth it, how the LNURL-pay flow works and how to configure it, plus a workflow that deploys docs/ to Pages on every push to main. Also corrects two stale claims in docs/architecture.md: EmptyBackendInvoice is not wired into the factory, and the image-metadata TODO no longer exists. --- .github/workflows/pages.yml | 40 ++++ README.md | 3 +- docs/.nojekyll | 0 docs/README.md | 4 + docs/architecture.md | 6 +- docs/index.html | 381 ++++++++++++++++++++++++++++++++++++ 6 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/pages.yml create mode 100644 docs/.nojekyll create mode 100644 docs/index.html diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..e3cc470 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,40 @@ +name: GitHub Pages + +on: + push: + branches: + - main + paths: + - 'docs/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + deploy: + name: Deploy docs/ to GitHub Pages + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/configure-pages@v5 + + - name: Upload docs/ as artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs + + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 2feb688..582028f 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ Wallets resolve `bob@yourdomain.com` through | [Architecture](docs/architecture.md) | Modules, layers, adding a backend | | [Development](docs/development.md) | Scripts, tests, static analysis, releasing | -Also: [CHANGELOG](CHANGELOG.md) · +Also: [project page](https://php-lightning.github.io/lnaddress/) · +[CHANGELOG](CHANGELOG.md) · [wiki](https://github.com/php-lightning/lnaddress/wiki) · [demo template](https://github.com/php-lightning/demo-lnaddress) diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md index 0027ed6..69c7b3e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,9 @@ # Documentation +The landing page for this folder is published at + (`index.html`, deployed by +`.github/workflows/pages.yml`). + `php-lightning/lnaddress` turns a domain you control into a [Lightning Address](https://lightningaddress.com/) provider: it implements [LNURL-pay (LUD-06)](https://github.com/lnurl/luds/blob/luds/06.md) and issues invoices diff --git a/docs/architecture.md b/docs/architecture.md index 1d71d4b..06d4040 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -69,7 +69,7 @@ src/ | `Application\InvoiceGenerator` | Validates the amount, converts msat → sat, maps `InvoiceTransfer` to the response | | `Domain\CallbackUrl\LnAddressGenerator` | Resolves `username@domain`, falling back to the configured receiver | | `Domain\BackendInvoice\LnbitsBackendInvoice` | `POST {api_endpoint}/api/v1/payments` with `X-Api-Key`, `description_hash`, `unhashed_description` | -| `Domain\BackendInvoice\EmptyBackendInvoice` | Null object for users without a usable backend | +| `Domain\BackendInvoice\EmptyBackendInvoice` | No-op implementation of the interface; currently unused by the factory | | `Infrastructure\Middleware\CorsMiddleware` | `Access-Control-Allow-Origin: *`; answers `OPTIONS` preflight | | `Infrastructure\Handler\InvoiceExceptionHandler` | Turns any `Throwable` into `{status: ERROR, reason: …}` | | `Shared\Value\SendableRange` | Min/max msat bounds and `contains()` | @@ -90,5 +90,5 @@ The Application layer stays untouched — it only knows the interface. ## Not implemented -Marked as TODO in the code: LNURL comments (`commentAllowed` is always `false`) and image -metadata in the pay params. +LNURL comments: `commentAllowed` is always `false` (LUD-12), marked as TODO in +`Application\CallbackUrl`. diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..d09305a --- /dev/null +++ b/docs/index.html @@ -0,0 +1,381 @@ + + + + + +PHP Lightning Address — self-host your own Lightning Address + + + + + +
+
+
+

PHP Lightning Address

+

+ Self-host you@yourdomain.com as a Bitcoin Lightning + payment address. A small PHP library that implements LNURL-pay (LUD-06), serves any + number of users from one domain, and issues invoices through your own node. +

+ +
+
+ +
+ +

What it is

+

+ A Lightning Address looks like an email address and works like one for payments: anyone + can type bob@example.com into a wallet and pay you, without QR codes, + invoices pasted over chat, or a new invoice for every payment. +

+

+ Under the hood it is just HTTPS. A wallet turns bob@example.com into a + request to https://example.com/.well-known/lnurlp/bob, reads the payment + parameters it gets back, and then asks the same server for a bolt11 invoice of the + amount the payer chose. This library is that server, written in PHP. +

+ +
+
+

Spec-compliant

+

Implements LNURL-pay (LUD-06), the format wallets already speak.

+
+
+

Multi-user

+

One deployment serves many usernames, each mapped to its own invoice backend and API key.

+
+
+

Backend-agnostic

+

LNbits ships today; other backends are an interface implementation away.

+
+
+

Small and typed

+

PHP 8.3+, Psalm errorLevel 1 and PHPStan level max on every commit.

+
+
+ +

Why self-host it

+
    +
  • The address is yours. It lives on your domain, so no provider can rename, rate-limit, or discontinue it.
  • +
  • Payments go straight to your node. The library asks your wallet backend for invoices; nobody custodies the sats on the way.
  • +
  • No new stack. If you already run PHP, this is one route and two config files, not another service to operate.
  • +
  • Multiple identities, one deployment. Family, team, or project addresses can share a domain while each keeps its own backend.
  • +
+ +

How it works

+
    +
  1. +

    The wallet resolves the address

    +

    + bob@example.com becomes + GET https://example.com/.well-known/lnurlp/bob, which your server + routes to this library. +

    +
  2. +
  3. +

    The server returns pay parameters

    +

    Minimum and maximum sendable amounts, a description, and the callback URL to use next.

    +
    {
    +  "callback": "https://example.com",
    +  "maxSendable": 10000000000,
    +  "minSendable": 100000,
    +  "metadata": "[[\"text/plain\",\"Pay to bob@example.com\"], …]",
    +  "tag": "payRequest",
    +  "commentAllowed": false
    +}
    +
  4. +
  5. +

    The wallet asks for an invoice

    +

    + It calls the callback with the chosen amount in millisatoshis: + GET /bob?amount=2000. +

    +
  6. +
  7. +

    Your node issues it

    +

    + The library requests a bolt11 invoice from the backend configured for that + username and hands it back under pr. The wallet pays it directly — + the library never touches funds. +

    +
    {
    +  "pr": "lnbc20n1p…",
    +  "status": "OK",
    +  "successAction": { "tag": "message", "message": "Payment received!" },
    +  "routes": [],
    +  "disposable": false,
    +  "error": null
    +}
    +
  8. +
+ +

Quick start

+

Requires PHP 8.3 or newer and an LNbits wallet API key.

+
composer require php-lightning/lnaddress
+
+cp lightning-config.dist.php lightning-config.php   # settings
+cp backends.dist.json backends.json                 # per-user invoice backends
+
+composer serve                                      # http://localhost:8080
+ +

Settings — lightning-config.php

+
use PhpLightning\Config\LightningConfig;
+
+return (new LightningConfig())
+    ->setDomain('yourdomain.com')
+    ->setReceiver('default-receiver')
+    ->setDescriptionTemplate('Pay to %s')                 // %s = the lightning address
+    ->setSuccessMessage('Thanks for the payment!')
+    ->setSendableRange(min: 100_000, max: 10_000_000_000) // millisats
+    ->setCallbackUrl('https://yourdomain.com')
+    ->addBackendsFile(getcwd() . '/backends.json');
+ +

Backends — backends.json

+
{
+  "bob":   { "type": "lnbits", "api_key": "abc...123", "api_endpoint": "http://localhost:5000" },
+  "alice": { "type": "lnbits", "api_key": "def...456", "api_endpoint": "http://localhost:5000" }
+}
+ +

+ Then point /.well-known/lnurlp/{username} at the app's + /{username} route, over HTTPS, and the address is live. +

+ +

Under the hood

+

+ Built on the Gacela framework as a small + modular monolith. Each module exposes a facade; the domain layer is pure PHP that + knows nothing about HTTP, which is what makes swapping the invoice backend — or + calling the library directly instead of over HTTP — straightforward. +

+
public/index.php → Router → InvoiceRoutesPlugin
+                              ├─ CorsMiddleware            wallets call cross-origin
+                              ├─ InvoiceExceptionHandler   errors → LNURL error object
+                              └─ InvoiceController
+                                   └─ InvoiceFacade
+                                        ├─ CallbackUrl       → pay parameters
+                                        └─ InvoiceGenerator  → bolt11 invoice
+                                             └─ LnbitsBackendInvoice → your node
+ +

Using it as a library, without the HTTP layer:

+
use Gacela\Framework\Gacela;
+use PhpLightning\Invoice\InvoiceFacade;
+
+Gacela::bootstrap(__DIR__);
+
+$facade = new InvoiceFacade();
+$payParams = $facade->getCallbackUrl('bob');
+$invoice = $facade->generateInvoice('bob', 2_000); // millisats
+ +

Documentation

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
GuideContents
Getting startedInstall, configure, run locally, deploy behind a real domain
ConfigurationEvery setter, its default, and the backends file format
HTTP APIRoutes, payloads, CORS, LNURL error objects, nginx rewrite
ArchitectureModules, layers, request flow, adding a backend
DevelopmentComposer scripts, tests, static analysis, releasing
+
+ +

FAQ

+ +

Does the library hold my money?

+

+ No. It asks your configured backend for an invoice and returns it. The payment settles + between the payer's wallet and your node. +

+ +

Do I need LNbits?

+

+ LNbits is the backend implemented today. Adding another means a case in the + BackendType enum and an implementation of + BackendInvoiceInterface — the rest of the code depends on the interface, + not on LNbits. +

+ +

Can one domain serve several people?

+

+ Yes. Every entry in backends.json is a username with its own endpoint and + API key, so bob@ and alice@ can point at different wallets. +

+ +

Does it work in a browser wallet?

+

+ Yes — every response carries permissive CORS headers and OPTIONS + preflights are answered directly. +

+ +

What is not implemented?

+

+ LNURL comments (LUD-12): commentAllowed is always false. +

+ +
+ + + + +