## HTTP 402 Paid Access
JSS supports two approaches to paid content:
1. **ACL Conditions** any resource at any URL can require payment via `acl:condition` in its ACL file
2. **Pay Route** the `/pay/*` prefix provides a full payment backend with deposits, balances, and token trading
### ACL-Based Payments (Generic)
Any resource can be payment-gated by adding a `PaymentCondition` to its ACL:
```json
{
"@type": "acl:Authorization",
"acl:agentClass": { "@id": "acl:AuthenticatedAgent" },
"acl:accessTo": { "@id": "/premium/article.jsonld" },
"acl:mode": [{ "@id": "acl:Read" }],
"acl:condition": {
"@type": "PaymentCondition",
"amount": "1000",
"currency": "sats"
}
}
```
When a client requests the resource:
1. Server evaluates the ACL and finds the `PaymentCondition`
2. Server checks the agent's balance in the webledger
3. If balance >= cost deducts and serves the resource (200)
4. If balance < cost responds with `402 Payment Required` and payment details
To fund their balance, users deposit via the `/pay/.deposit` endpoint using a TXO URI (currently testnet4 for development). The balance is tracked in the webledger at `/.well-known/webledgers/webledgers.json`.
**Design: fail-closed** if the server encounters a condition type it doesn't support, access is denied. Unsupported conditions are never silently ignored.
#### Quick Demo
```bash
# Start JSS with payments (testnet4 by default)
jss start --pay --pay-cost 10
# Create an article and payment-gated ACL
curl -X PUT http://localhost:4443/premium/article.jsonld \
-H "Content-Type: application/ld+json" \
-d '{"@type": "Article", "headline": "Premium Content"}'
curl -X PUT http://localhost:4443/premium/article.jsonld.acl \
-H "Content-Type: application/ld+json" \
-d '{"@type":"acl:Authorization","acl:agent":{"@id":"did:nostr:YOUR_PUBKEY"},"acl:accessTo":{"@id":"/premium/article.jsonld"},"acl:mode":[{"@id":"acl:Read"}],"acl:condition":{"@type":"PaymentCondition","amount":"10","currency":"sats"}}'
# Try to read 402 Payment Required
curl -H "Authorization: Nostr " http://localhost:4443/premium/article.jsonld
# Deposit testnet4 sats
curl -X POST -H "Authorization: Nostr " \
http://localhost:4443/pay/.deposit -d 'txo:tbtc4:txid:vout'
# Try again 200 OK + article
curl -H "Authorization: Nostr " http://localhost:4443/premium/article.jsonld
```
### Pay Route (Full Backend)
Monetize API endpoints with per-request satoshi payments. Resources under `/pay/*` require NIP-98 authentication and a positive balance.
```bash
jss start --pay --pay-cost 10 --pay-address your-address --pay-token PODS --pay-rate 10
```
### Routes
| Method | Path | Description |
|--------|------|-------------|
| GET | `/pay/.info` | Public: cost, token info, chains, pool |
| GET | `/pay/.address` | Public: pod's taproot deposit address (optional `?user=did:nostr:...` for per-user address) |
| GET | `/pay/.balance` | Check your balance (NIP-98 auth) also auto-detects new deposits |
| POST | `/pay/.deposit` | Deposit sats via TXO URI, claim `{txid, vout, chain}`, or MRC20 state proof |
| POST | `/pay/.withdraw-sats` | Withdraw sats as a TXO voucher URI |
| POST | `/pay/.buy` | Buy tokens with sat balance (requires `--pay-token`) |
| POST | `/pay/.withdraw` | Withdraw balance as portable tokens (requires `--pay-token`) |
| GET | `/pay/.offers` | List open sell orders (secondary market) |
| POST | `/pay/.sell` | Create a sell order (requires `--pay-token`) |
| POST | `/pay/.swap` | Execute a swap against a sell order |
| GET | `/pay/.pool` | AMM pool state (requires `--pay-chains`) |
| POST | `/pay/.pool` | AMM swap, add/remove liquidity |
| GET | `/pay/*` | Paid resource access (deducts balance) |
### How It Works
1. Get your deposit address: `GET /pay/.address?user=did:nostr:YOUR_PUBKEY`
2. Send sats to that address from any Bitcoin wallet
3. Check balance at `/pay/.balance` deposits are auto-detected
4. Access paid resources each request deducts the configured cost
5. Withdraw sats as a portable voucher: `POST /pay/.withdraw-sats`
6. Optionally buy tokens (`/pay/.buy`) or withdraw as portable tokens (`/pay/.withdraw`)
7. Balance tracked in a [Web Ledger](https://webledgers.org/) at `/.well-known/webledgers/webledgers.json`
Each user gets a unique taproot deposit address derived from the pod's master key + their identity. The pod auto-detects deposits by scanning the mempool API when you check your balance.
### Example
```bash
# Get your deposit address
curl http://localhost:4443/pay/.address?chain=tbtc4&user=did:nostr:YOUR_PUBKEY
# {"address": "tb1p...", "chain": "tbtc4", "pubkey": "02..."}
# Send sats to that address, then check balance (auto-detects deposits)
curl -H "Authorization: Nostr " http://localhost:4443/pay/.balance
# Or deposit manually with a TXO voucher URI
curl -X POST -H "Authorization: Nostr " \
http://localhost:4443/pay/.deposit \
-d "txo:tbtc4:txid:vout?amount=X&key=Y"
# Or claim a deposit to the pod's address
curl -X POST -H "Authorization: Nostr " \
-H "Content-Type: application/json" \
http://localhost:4443/pay/.deposit \
-d '{"txid": "abc...", "vout": 0, "chain": "tbtc4"}'
# Access paid resource
curl -H "Authorization: Nostr " http://localhost:4443/pay/my-resource
# Withdraw sats as a portable TXO voucher
curl -X POST -H "Authorization: Nostr " \
-H "Content-Type: application/json" \
http://localhost:4443/pay/.withdraw-sats \
-d '{"amount": 10000, "chain": "tbtc4"}'
# {"voucher": "txo:tbtc4:txid:0?amount=10000&key=...", ...}
# Buy tokens with sat balance
curl -X POST -H "Authorization: Nostr " \
-H "Content-Type: application/json" \
http://localhost:4443/pay/.buy \
-d '{"amount": 100, "currency": "tbtc4"}'
# Withdraw entire balance as portable tokens
curl -X POST -H "Authorization: Nostr " \
-H "Content-Type: application/json" \
http://localhost:4443/pay/.withdraw \
-d '{"all": true, "currency": "tbtc4"}'
```
Three deposit methods: (1) send to your per-user address and check balance (auto-detected), (2) TXO voucher URI with private key, (3) claim a txid after sending to the pod's address. The `X-Balance`, `X-Cost`, and `X-Pay-Currency` headers are returned on successful paid requests. Buy and withdraw return portable MRC20 proofs with Bitcoin anchor data for independent verification.
### Secondary Market
Users can trade tokens peer-to-peer through the pod. Sell orders are created via `/pay/.sell` and filled via `/pay/.swap`. The pod acts as escrow transferring tokens on the Bitcoin-anchored MRC20 trail and settling sats in the webledger.
### Multi-Chain AMM
Enable multi-chain deposits and an automated market maker:
```bash
jss start --pay --pay-chains "tbtc3,tbtc4"
```
Deposits detect the chain from the TXO URI prefix (`txo:tbtc3:txid:vout`). Each chain's balance is tracked separately. The AMM uses a constant-product formula (x y = k) with a 0.3% fee.
```bash
# Add liquidity
curl -X POST -H "Authorization: Nostr " \
-H "Content-Type: application/json" \
http://localhost:4443/pay/.pool \
-d '{"action": "add-liquidity", "tbtc3": 1000, "tbtc4": 5000}'
# Swap
curl -X POST -H "Authorization: Nostr " \
-H "Content-Type: application/json" \
http://localhost:4443/pay/.pool \
-d '{"action": "swap", "sell": "tbtc3", "amount": 100}'
# Check pool state
curl http://localhost:4443/pay/.pool
```
Supported chains: `btc`, `tbtc3`, `tbtc4`, `ltc`, `signet`.