php artisan boost:add-skill openreceive/laravel
Save this content to: AGENTS.md
# OpenReceive Agent Rules
Optimize for a
small, honest API and a good developer experience.
## Product overviews
Every README that provides a basic OpenReceive, package, integration, or demo
overview must mention optional swaps and name USDT, USDC, SOL, and ETH. Explain
that a configured swap provider converts these payments to BTC over Lightning
in the merchant's connected wallet, with asset and network availability depending
on the provider. Keep package descriptions consistent with this positioning;
describe supporting libraries in the context of the full integration.
Lead public package READMEs with what the developer can build and the package's
specific role. Include installation requirements and a working quickstart link
before detailed internals. Use links that resolve from published packages and
split repositories. Keep registry descriptions aligned with the README; avoid
blanket security guarantees or implying that a helper package is a full backend.
## Non-negotiables
- OpenReceive never owns orders, users, prices, or fulfillment, and never requires a separate
database, Redis, or migration runner. It MAY own payment-attempt rows (`openreceive_payments`)
inside the host application's existing database: the host passes its database handle and runs
the migration through its own workflow; the library owns the schema, locking, settlement
write-once, and reconciliation state machine. Implementing a custom
`PaymentRepository` is the documented escape hatch, never the quickstart.
- The host owns orders and prices. Direct server code passes `{ reference, amount }`; mounted
HTTP handlers resolve the amount from host-owned data and reject payer-supplied amounts.
- One `openreceive_payments` row per attempt is committed before payer instructions are
exposed. A reference may have multiple historical attempts; `payment_hash` is globally unique,
settlement is write-once per attempt, and fulfillment runs only for the reference's first settled
attempt.
- Attempts carry an explicit status (`pending`, `settled`, `expired`, `failed`, `attention`).
Only `pending` attempts are reconciled; closure of an unpaid attempt requires a successful
wallet scan at or after expiry plus `OPENRECEIVE_ATTEMPT_EXPIRY_GRACE_SECONDS` (an exported
constant, 900 seconds — not an environment variable) — a local clock alone never closes a
row. A settled row is never overwritten.
- Settlement discovery is opportunistic by default: every mounted OpenReceive route first runs
one reconcile pass through the durable `openreceive_meta` gate (CAS in the host database,
minimum 3 seconds between real wallet scans, stretched by invoice age), shared by every
worker/process — the gate IS the NWC scan budget, and `payments/check` serves the requested
hash from that pass (or the host row on `gate_busy`), never a second per-invoice wallet
walk. No web process starts a settlement timer; the optional notifications worker
(NWC-02 listener + periodic pass) is a separate process. A custom repository without
`claimReconcileGate` must disable `opportunisticReconcile` explicitly — the default
settlement path never degrades silently, and process-local memory never backs the gate.
- A retry or concurrent create serializes per reference inside the library repository. A reference has
one live payment session; within it, at most one live attempt per rail/asset so a payer can
switch methods. The host never sees the live/supersede/conflict vocabulary — it sees an order
as unpaid or paid. Do not add a separately configured OpenReceive idempotency store.
- Receive-only NWC codes must never reach browser/mobile code, logs, tests, screenshots,
docs, source maps, or demo assets. Receive APIs never expose send-payment methods. Wallet
preflight fails closed when the connection advertises spend methods; booting anyway requires
the explicit `allowSpendCapableWallet` / `OPENRECEIVE_ALLOW_SPEND_CAPABLE_NWC` override.
- NWC notifications are authenticated wallet data: a conforming client decrypts them with the
same key material that authenticates RPC responses. A `payment_received` payload that
satisfies the settlement rule (`settled_at` or `transaction_state/state == "settled"`; a
preimage alone is corroborating evidence) settles the matching pending attempt directly,
with no redundant wallet scan for that invoice. Anything less — a payload without a finality
signal, or an unknown payment hash — only triggers a bounded reconciliation scan. The
notifications worker's own periodic pass (plus request-path opportunistic reconcile) remains
the safety net for notifications missed while offline. Direct settlement
assumes the NWC client binds notification decryption to the connection's wallet pubkey (the
bundled SDK does); a custom client that skips author verification must not be granted it.
- Use `amount_msats` for millisatoshi values in public results and exact integer/decimal money
math. Never use binary floats for fiat math.
- Swap provider credentials live only in the host payment attempt's optional server-only
`swap_data` field.
They never appear in browser responses or logs. Provider completion is not wallet
settlement; refund decisions refresh provider state.
- Do not duplicate provider data, supported currencies, settlement rules, polling cadence, or
demo product data.
- Schema or route changes update their vectors in the same change. Invoice behavior needs
host-row retry/concurrency tests; settlement behavior needs replay-safe tests.
- We should not worry about validating the NWC's responses or behavior: a user of OpenReceive implicitly
trusts their backing NWC service, because they will need to use that NWC service anyway to withdraw any funds.
OpenReceive users can also build their own NWC service, or run one on their own
hardware, like an Alby Hub instance.
## Shipped routes and hooks
- `@openreceive/http` adapters and the Rails engine ship the route set in
`spec/openapi/openreceive-http.v1.yaml`.
- The host keeps authentication. The quickstart host contract is `authorize`, `amountFor`,
and `onPaid` plus a database handle; the library derives
`resolveCheckout` / `onCheckoutCreated` and the reconciliation transitions from its own
repository. The advanced hook surface remains for custom-repository hosts.
- OpenReceive mints no authentication, recovery, or refund tokens. The host authorizes every
request and verifies the requested `payment_hash` belongs to the reference before resolving
server-only `swap_data`.
- `onCheckoutCreated` runs before a create response. Failure returns 409 and withholds the
invoice or swap instructions.
## Releases
General package releases do not publish the BTCPay Server plugin. Its source
version is independent of the npm/gem release version. A source version bump
does not authorize submitting a Plugin Builder build or publishing the plugin;
do either only when the user explicitly requests a BTCPay release.
ALWAYS GIVE THE MAINTAINER THE APPROVAL LINKS. Pushing a release tag starts
`Publish Gems`, `Publish PyPI` and `Publish Composer`, and each waits for a
maintainer's click in the browser. Gems, PyPI, Packagist and the GitHub
release all stall until then. As soon as the runs exist, list all three
labelled run URLs (`https://github.com/OpenReceive/openreceive/actions/runs/<id>`)
from:
```sh
gh run list --repo OpenReceive/openreceive --branch v<x.y.z> --json workflowName,status,url
```
Put them in the LAST message of the turn that pushed the tag. Text between tool
calls does not count, because the maintainer may never see it. Repeat them at
the end of every later release message until each run is approved, and name
any stale run from an older tag that must not be approved. This step has been
missed before; it is part of the release, not a courtesy.
Publishing the GitHub release starts a fourth gated run, `Publish WordPress.org`,
which commits the release's WordPress ZIP to the plugin directory. Give its URL
the same way, in the last message of the turn that published the release:
```sh
gh run list --repo OpenReceive/openreceive --workflow publish-wordpress.yml --limit 1 --json status,headBranch,url
```
## Testing
Run demo application servers and their backing services in Docker. Builds,
tests, lint/type checks, browser automation, and release verification may run
on the host or in Docker. The Docker requirement applies to running the demo
applications, not to the verification tools or test runners.
Use the smallest relevant test while iterating. `npm test` is the JS suite; the contract and
secret check is:
```sh
npm run check
```
For JS/TS changes, run a focused test first and then at minimum:
```sh
npm run typecheck && npm test
```
For broad route, package, contract, schema, release, or deployment changes, run:
```sh
npm run test:ci
```
Wallet behavior also requires `npm run test:live:nwc`; it must skip clearly when `nwc` is not
configured. Ruby is a second settlement engine and must match the shared money, settlement,
swap-data, and HTTP vectors:
```sh
npm run test:ruby
```
Before declaring work done, report the exact checks run and any intentional skip.
## Trust model
Our own server, the configured NWC wallet, and the configured swap provider are trusted. The
frontend formats what the server sent: a throw in a checkout panel is a bug in our own API
surfacing, not a display-boundary bug class to defend against. Do not reintroduce
"untrusted-wire" readers or blanking display wrappers for our own JSON, NWC replies, or
FixedFloat deposit addresses; adapter work (field aliases, string-to-number coercion) is fine.
Keep validating what is genuinely untrusted: the payer and browser client (payment_hash shape,
refund-address checksums, request bodies hitting HTTP handlers), the host amount callback
(`amount_for` / `amountFor` — the host owns prices), and public price feeds (fail closed on
missing or stale rates). Invoice expiry keeps its rule: local clock alone never closes a row — a
successful wallet scan at/after expiry plus the grace window is the close rule, and an
unrecognized `list_transactions` shape must fail the scan rather than look empty, because an
empty-looking scan at expiry+grace closes unpaid attempts.
## Private boundary
Do not add private openreceive.org application code, infrastructure inventory, host IPs,
deployment credentials, analytics, landing pages, or business logic to this public repo.
Never commit an absolute path into a contributor's home folder (`/Users/<name>/…`,
`/home/<name>/…`); it publishes their account name. Write repo-relative paths, or `~/`
for tool locations. `npm run scan:secrets` fails on them.
Specify openreceive.org changes here through the generated `docs/site-contract.json`
and `docs/internal/site-build.md`. Update the canonical docs, agent directions, or
contract generator, then regenerate the docs bundle. The private Rails app consumes
that revision through its docs sync and applies the requirements. Use this one-way
workflow for site routes and redirects too; a specification change here does not
require locating or editing the private site repository.
Optimize for a small, honest API and a good developer experience.
Every README that provides a basic OpenReceive, package, integration, or demo overview must mention optional swaps and name USDT, USDC, SOL, and ETH. Explain that a configured swap provider converts these payments to BTC over Lightning in the merchant's connected wallet, with asset and network availability depending on the provider. Keep package descriptions consistent with this positioning; describe supporting libraries in the context of the full integration. Lead public package READMEs with what the developer can build and the package's specific role. Include installation requirements and a working quickstart link before detailed internals. Use links that resolve from published packages and split repositories. Keep registry descriptions aligned with the README; avoid blanket security guarantees or implying that a helper package is a full backend.
openreceive_payments)
inside the host application's existing database: the host passes its database handle and runs
the migration through its own workflow; the library owns the schema, locking, settlement
write-once, and reconciliation state machine. Implementing a custom
PaymentRepository is the documented escape hatch, never the quickstart.{ reference, amount }; mounted
HTTP handlers resolve the amount from host-owned data and reject payer-supplied amounts.openreceive_payments row per attempt is committed before payer instructions are
exposed. A reference may have multiple historical attempts; payment_hash is globally unique,
settlement is write-once per attempt, and fulfillment runs only for the reference's first settled
attempt.pending, settled, expired, failed, attention).
Only pending attempts are reconciled; closure of an unpaid attempt requires a successful
wallet scan at or after expiry plus OPENRECEIVE_ATTEMPT_EXPIRY_GRACE_SECONDS (an exported
constant, 900 seconds — not an environment variable) — a local clock alone never closes a
row. A settled row is never overwritten.openreceive_meta gate (CAS in the host database,
minimum 3 seconds between real wallet scans, stretched by invoice age), shared by every
worker/process — the gate IS the NWC scan budget, and payments/check serves the requested
hash from that pass (or the host row on gate_busy), never a second per-invoice wallet
walk. No web process starts a settlement timer; the optional notifications worker
(NWC-02 listener + periodic pass) is a separate process. A custom repository without
claimReconcileGate must disable opportunisticReconcile explicitly — the default
settlement path never degrades silently, and process-local memory never backs the gate.allowSpendCapableWallet / OPENRECEIVE_ALLOW_SPEND_CAPABLE_NWC override.payment_received payload that
satisfies the settlement rule (settled_at or transaction_state/state == "settled"; a
preimage alone is corroborating evidence) settles the matching pending attempt directly,
with no redundant wallet scan for that invoice. Anything less — a payload without a finality
signal, or an unknown payment hash — only triggers a bounded reconciliation scan. The
notifications worker's own periodic pass (plus request-path opportunistic reconcile) remains
the safety net for notifications missed while offline. Direct settlement
assumes the NWC client binds notification decryption to the connection's wallet pubkey (the
bundled SDK does); a custom client that skips author verification must not be granted it.amount_msats for millisatoshi values in public results and exact integer/decimal money
math. Never use binary floats for fiat math.swap_data field.
They never appear in browser responses or logs. Provider completion is not wallet
settlement; refund decisions refresh provider state.@openreceive/http adapters and the Rails engine ship the route set in
spec/openapi/openreceive-http.v1.yaml.authorize, amountFor,
and onPaid plus a database handle; the library derives
resolveCheckout / onCheckoutCreated and the reconciliation transitions from its own
repository. The advanced hook surface remains for custom-repository hosts.payment_hash belongs to the reference before resolving
server-only swap_data.onCheckoutCreated runs before a create response. Failure returns 409 and withholds the
invoice or swap instructions.General package releases do not publish the BTCPay Server plugin. Its source version is independent of the npm/gem release version. A source version bump does not authorize submitting a Plugin Builder build or publishing the plugin; do either only when the user explicitly requests a BTCPay release.
ALWAYS GIVE THE MAINTAINER THE APPROVAL LINKS. Pushing a release tag starts
Publish Gems, Publish PyPI and Publish Composer, and each waits for a
maintainer's click in the browser. Gems, PyPI, Packagist and the GitHub
release all stall until then. As soon as the runs exist, list all three
labelled run URLs (https://github.com/OpenReceive/openreceive/actions/runs/<id>)
from:
gh run list --repo OpenReceive/openreceive --branch v<x.y.z> --json workflowName,status,url
Put them in the LAST message of the turn that pushed the tag. Text between tool calls does not count, because the maintainer may never see it. Repeat them at the end of every later release message until each run is approved, and name any stale run from an older tag that must not be approved. This step has been missed before; it is part of the release, not a courtesy.
Publishing the GitHub release starts a fourth gated run, Publish WordPress.org,
which commits the release's WordPress ZIP to the plugin directory. Give its URL
the same way, in the last message of the turn that published the release:
gh run list --repo OpenReceive/openreceive --workflow publish-wordpress.yml --limit 1 --json status,headBranch,url
Run demo application servers and their backing services in Docker. Builds, tests, lint/type checks, browser automation, and release verification may run on the host or in Docker. The Docker requirement applies to running the demo applications, not to the verification tools or test runners.
Use the smallest relevant test while iterating. npm test is the JS suite; the contract and
secret check is:
npm run check
For JS/TS changes, run a focused test first and then at minimum:
npm run typecheck && npm test
For broad route, package, contract, schema, release, or deployment changes, run:
npm run test:ci
Wallet behavior also requires npm run test:live:nwc; it must skip clearly when nwc is not
configured. Ruby is a second settlement engine and must match the shared money, settlement,
swap-data, and HTTP vectors:
npm run test:ruby
Before declaring work done, report the exact checks run and any intentional skip.
Our own server, the configured NWC wallet, and the configured swap provider are trusted. The frontend formats what the server sent: a throw in a checkout panel is a bug in our own API surfacing, not a display-boundary bug class to defend against. Do not reintroduce "untrusted-wire" readers or blanking display wrappers for our own JSON, NWC replies, or FixedFloat deposit addresses; adapter work (field aliases, string-to-number coercion) is fine.
Keep validating what is genuinely untrusted: the payer and browser client (payment_hash shape,
refund-address checksums, request bodies hitting HTTP handlers), the host amount callback
(amount_for / amountFor — the host owns prices), and public price feeds (fail closed on
missing or stale rates). Invoice expiry keeps its rule: local clock alone never closes a row — a
successful wallet scan at/after expiry plus the grace window is the close rule, and an
unrecognized list_transactions shape must fail the scan rather than look empty, because an
empty-looking scan at expiry+grace closes unpaid attempts.
Do not add private openreceive.org application code, infrastructure inventory, host IPs, deployment credentials, analytics, landing pages, or business logic to this public repo.
Never commit an absolute path into a contributor's home folder (/Users/<name>/…,
/home/<name>/…); it publishes their account name. Write repo-relative paths, or ~/
for tool locations. npm run scan:secrets fails on them.
Specify openreceive.org changes here through the generated docs/site-contract.json
and docs/internal/site-build.md. Update the canonical docs, agent directions, or
contract generator, then regenerate the docs bundle. The private Rails app consumes
that revision through its docs sync and applies the requirements. Use this one-way
workflow for site routes and redirects too; a specification change here does not
require locating or editing the private site repository.
How can I help you explore Laravel packages today?