-
Notifications
You must be signed in to change notification settings - Fork 6
Plan: Lightning Address payment hash verification API endpoint #966
Description
Summary
Build a payment hash verification API endpoint that CDK mints call to validate whether a payment hash belongs to a merchant before allowing ecash to be melted. This enables closed-loop lightning address payments.
Context
The CDK ClosedLoopPayment decorator (see CDK plan) intercepts melt requests and calls this API to verify: "does this payment hash belong to this merchant?" If yes, the melt proceeds. If no, it's rejected.
API Specification
GET /api/payment-hash/{merchant_user_id}/{payment_hash}
Success Response (200)
{
"status": "OK",
"found": true,
"state": "PENDING",
"created_at": "2026-03-30T..."
}Not Found Response (404)
{
"status": "ERROR",
"reason": "Payment hash not found for this merchant"
}Implementation Plan
1. Create the API route
File: app/routes/api.payment-hash.$merchantUserId.$paymentHash.ts
Follow existing patterns from api.lnurlp.callback.$userId.ts:
- Export a
loaderfunction (GET only) - Return JSON with CORS headers (
Access-Control-Allow-Origin: *) - Use LNURL-style error format
2. Query payment hashes from existing tables
Payment hashes are already stored in:
wallet.cashu_receive_quotes— haspayment_hashcolumnwallet.spark_receive_quotes— haspayment_hashcolumn
Query both tables for matching (user_id, payment_hash) where user_id = merchant's Agicash user ID.
3. Return payment state
Map quote states to response:
UNPAID→ invoice created but not yet paidPENDING→ payment in flightPAID/COMPLETED→ payment settledEXPIRED/FAILED→ invoice expired or failed
4. Database indexing
Check if (user_id, payment_hash) index exists on receive quote tables. Spark send quotes already have spark_send_quotes_payment_hash_active_unique — receive quotes may need similar indexing since this endpoint will be called frequently by CDK mints during melt operations.
5. No auth required
This endpoint is called by CDK mints (server-to-server), not by users. No Supabase auth needed. The payment hash itself acts as a capability — knowing it proves you have the invoice.
Design Decisions
- Fail closed: If the API is unreachable or errors, the CDK mint rejects the melt (safety default)
- Merchant identifier: Agicash user ID (UUID), not username (stable across renames)
- Scope: Melt validation only — this doesn't affect mint or receive flows
- Both quote types: Must check both Cashu and Spark receive quotes since either could have generated the invoice
Existing Patterns to Follow
- Route structure:
app/routes/api.*.tswithloaderexport - Service layer: Create
PaymentHashVerificationServicesimilar toLightningAddressService - Response format: JSON + CORS, LNURL error style
- Money handling: Use
Moneyclass from@agicash/sdk/lib/money