Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .claude/commands/check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
description: Run the full quality suite and fix everything that fails
allowed-tools: Bash, Read, Edit, Grep, Glob
---

Run `composer test-all` (php-cs-fixer dry-run, Psalm, PHPStan, PHPUnit, Rector dry-run).

If anything fails:

1. Fix the root cause in the source, not by widening ignores in `phpstan.neon` / `psalm.xml`.
2. Auto-fixable style/Rector diffs: run `composer fix`, then review the diff.
3. PHPStan OOM inside `resultCache.php` = stale cache -> `vendor/bin/phpstan clear-result-cache`.
4. Re-run `composer test-all` until green.

Report: what failed, what you changed, final suite status. Do not commit unless asked.

$ARGUMENTS
23 changes: 23 additions & 0 deletions .claude/commands/module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
description: Scaffold a new Gacela module following this repo's layout
allowed-tools: Bash, Read, Write, Edit, Grep, Glob
argument-hint: <ModuleName> [what it does]
---

Create module `$1` in `src/$1/`, mirroring `src/Invoice/`:

- `$1Facade.php` — `/** @extends AbstractFacade<$1Factory> */`, thin delegation only.
- `$1Factory.php` — `/** @extends AbstractFactory<$1Config> */`, builds Application/Domain objects.
- `$1Config.php` — extends `AbstractConfig`, typed getters over `$this->get(...)`.
- `$1DependencyProvider.php` — only if external deps are needed;
`/** @extends AbstractProvider<$1Config> */`, constants as `public const string`.
- `Application/`, `Domain/`, `Infrastructure/` as needed; Domain talks to interfaces only.

Rules: `declare(strict_types=1);`, `final` classes, `readonly` where stateless, DTOs in
`src/Shared/Transfer/` with the `Transfer` suffix. Cross-module access goes through the
other module's Facade.

Add unit tests under `tests/Unit/$1/` mirroring the source paths; add a Feature test only
if the module needs Gacela bootstrapping. Finish with `composer test-all`.

Context: $ARGUMENTS
55 changes: 55 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"includeCoAuthoredBy": false,
"env": {
"XDEBUG_MODE": "off"
},
"permissions": {
"allow": [
"Bash(composer test:*)",
"Bash(composer test-all:*)",
"Bash(composer quality:*)",
"Bash(composer phpunit:*)",
"Bash(composer psalm:*)",
"Bash(composer phpstan:*)",
"Bash(composer csrun:*)",
"Bash(composer csfix:*)",
"Bash(composer fix:*)",
"Bash(composer rector:*)",
"Bash(composer ctal:*)",
"Bash(composer static-clear-cache:*)",
"Bash(composer test-coverage:*)",
"Bash(composer validate:*)",
"Bash(composer outdated:*)",
"Bash(composer show:*)",
"Bash(composer dump-autoload:*)",
"Bash(vendor/bin/phpunit:*)",
"Bash(vendor/bin/psalm:*)",
"Bash(vendor/bin/phpstan:*)",
"Bash(vendor/bin/php-cs-fixer:*)",
"Bash(vendor/bin/rector:*)",
"Bash(php -v)",
"Bash(php -r:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git show:*)",
"Bash(gh pr view:*)",
"Bash(gh pr diff:*)",
"Bash(gh issue view:*)"
],
"ask": [
"Bash(composer update:*)",
"Bash(composer require:*)",
"Bash(composer remove:*)",
"Bash(git push:*)",
"Bash(gh pr create:*)"
],
"deny": [
"Read(./lightning-config.php)",
"Read(./nostr.json)",
"Read(./.env)",
"Read(./.env.*)"
]
}
}
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

- uses: shivammathur/setup-php@v2
with:
php-version: 8.2
php-version: 8.3
coverage: none
tools: composer

Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:

- uses: shivammathur/setup-php@v2
with:
php-version: 8.2
php-version: 8.3
coverage: none
tools: composer

Expand Down Expand Up @@ -78,7 +78,7 @@ jobs:
fail-fast: false
matrix:
dependencies: [ "locked", "highest" ]
php-version: [ "8.2" , "8.2", "8.3" ]
php-version: [ "8.3", "8.4", "8.5" ]
operating-system:
- "ubuntu-latest"
steps:
Expand Down
4 changes: 2 additions & 2 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
build:
environment:
php: 8.2
php: 8.3
nodes:
analysis:
project_setup:
Expand All @@ -14,7 +14,7 @@ build:
tests:
override:
-
# xdebug is installed but mode=off; PHPUnit 10 exits 1 when coverage
# xdebug is installed but mode=off; PHPUnit exits 1 when coverage
# is requested without a driver, so enable it explicitly.
command: 'XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover=coverage.clover --configuration phpunit.xml'
coverage:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project are documented in this file.

## [Unreleased]

- **BREAKING**: require PHP >= 8.3 (PHPUnit 12 and the Rector php83 set need it)
- chore: update dependencies to their latest majors — gacela 1.21, PHPUnit 12.5,
Rector 2.5, php-cs-fixer 3.95, Psalm 6.16, symfony/http-client 7.4
- chore: run CI on PHP 8.3, 8.4 and 8.5 (the matrix listed 8.2 twice)
- docs: split the README into `docs/` (getting started, configuration, HTTP API,
architecture, development)

**Full Changelog**: https://github.com/php-lightning/lnaddress/compare/0.9.0...main

## [0.9.0] - 2026-07-23
Expand Down
78 changes: 78 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# lnaddress

PHP library serving a [Lightning Address](https://lightningaddress.com/) endpoint
(LNURL-pay / LUD-06). Built on [Gacela](https://gacela-project.com) (modular monolith) +
`gacela-project/router`. PHP >= 8.3, PSR-4 `PhpLightning\` -> `src/`,
`PhpLightningTest\` -> `tests/`.

Full docs live in [`docs/`](docs/) — architecture, config reference, HTTP API.

## Request flow

`public/index.php` bootstraps Gacela (`gacela.php`) -> `Router` -> `InvoiceRoutesPlugin`
(route + `CorsMiddleware` + `InvoiceExceptionHandler`) -> `InvoiceController` ->
`InvoiceFacade`.

- `GET /{username}` without `amount` -> `getCallbackUrl()` -> LNURL-pay params
- `GET /{username}?amount=<millisats>` -> `generateInvoice()` -> bolt11 under `pr`
- Errors bubble to `InvoiceExceptionHandler`, which returns `{status: ERROR, reason: …}` —
controllers do not try/catch.

Config comes from `lightning-config.dist.php` (overridable by gitignored
`lightning-config.php`) plus users/backends in `backends.json`.

## Module layout (Gacela)

Modules live in `src/<Module>/`. A module's public API is its **Facade** — never reach
into another module's Application/Domain/Infrastructure.

```
src/Invoice/
InvoiceFacade.php entry point; @extends AbstractFacade<InvoiceFactory>
InvoiceFactory.php wiring; @extends AbstractFactory<InvoiceConfig>
InvoiceConfig.php typed config reads
InvoiceDependencyProvider.php external deps (HTTP_API)
Application/ use cases: CallbackUrl, InvoiceGenerator
Domain/ pure logic + interfaces (BackendInvoice, CallbackUrl, Http)
Infrastructure/ Controller, Handler, Middleware, Http, Plugin
src/Config/ LightningConfig builder, BackendsConfig, BackendType enum
src/Shared/ ConfigKey, Transfer DTOs, Value objects
```

Gacela generics matter: `AbstractFacade`/`AbstractFactory`/`AbstractProvider` are
templated, so annotate with `@extends ...<T>`. For docblock service resolution use
`ServiceResolverAwareTrait` (`DocBlockResolverAwareTrait` is deprecated).

Config keys are constants in `PhpLightning\Shared\Config\ConfigKey`, shared by the writer
(`LightningConfig`) and reader (`InvoiceConfig`) — add new keys there, not as literals.

## Conventions

- `declare(strict_types=1);` everywhere; classes `final`, `readonly` when stateless.
- Typed class constants (`public const string FOO = '…';`) — enforced by the Rector php83 set.
- Domain depends on interfaces; concrete wiring lives in the Factory / DependencyProvider.
- DTO suffixes: `Transfer` for data flowing into operations, `Result` for handler output.
No `T` prefix in this repo. See `src/Shared/Transfer/`.
- Conventional commits, `ref:` instead of `refactor:`.

## Testing

- `tests/Unit/` mirrors `src/` — plain PHPUnit, no bootstrap.
- `tests/Feature/` boots Gacela and overrides `InvoiceDependencyProvider` to inject
`FakeHttpApi`; fixtures in `tests/Feature/backends.json`.
- PHPUnit 12: attributes, `createStub()`/`createMock()`, no `withConsecutive`.

## Commands

```bash
composer test-all # quality (cs, psalm, phpstan) + phpunit + rector --dry-run
composer quality # csrun + psalm + phpstan
composer phpunit # tests only
composer fix # php-cs-fixer + rector (writes changes)
composer ctal # clear static caches, fix, then full suite
composer serve # php -S localhost:8080 public/index.php
```

Psalm runs at errorLevel 1, PHPStan at level max. If PHPStan dies with an OOM inside
`resultCache.php`, that is a stale cache — run `vendor/bin/phpstan clear-result-cache`,
not a bigger memory limit.
Loading
Loading