javascript-solid-server 0.0.105 → 0.0.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PAY.md ADDED
@@ -0,0 +1,364 @@
1
+ # PAY.md — HTTP 402 Payment System
2
+
3
+ ## What This Is
4
+
5
+ JSS has a built-in payment system. Resources under `/pay/*` cost satoshis to access. Users authenticate with a Nostr key, deposit sats, and spend them on API requests. Optionally, the pod mints its own token (MRC20 on Bitcoin) that users can buy, sell, and trade.
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ User (Nostr keypair)
11
+
12
+ ├── POST /pay/.deposit → credit sat balance (multi-chain: txo:tbtc3:, txo:tbtc4:, etc.)
13
+ ├── GET /pay/.balance → check balance (includes per-chain balances)
14
+ ├── GET /pay/* → spend 1 sat, get resource
15
+ ├── POST /pay/.buy → spend sats, get tokens (Bitcoin TX)
16
+ ├── POST /pay/.withdraw → spend balance, get tokens back
17
+ ├── POST /pay/.sell → list tokens for sale
18
+ ├── POST /pay/.swap → buy someone's sell order
19
+ ├── GET /pay/.pool → AMM pool state (multi-chain)
20
+ └── POST /pay/.pool → AMM: swap, add-liquidity, remove-liquidity
21
+ ```
22
+
23
+ All state lives in two places:
24
+ - **Webledger** — `/.well-known/webledgers/webledgers.json` — sat balances per `did:nostr:<pubkey>`
25
+ - **Token trail** — `/.well-known/token/<ticker>.json` — MRC20 state chain anchored to Bitcoin
26
+
27
+ ## Authentication
28
+
29
+ Every request to `/pay/*` (except `.info` and `.offers`) requires a NIP-98 auth header:
30
+
31
+ ```
32
+ Authorization: Nostr <base64-encoded-signed-event>
33
+ ```
34
+
35
+ The event is kind 27235 with tags `["u", "<url>"]` and `["method", "<METHOD>"]`, signed with the user's Nostr private key. The server extracts the pubkey and maps it to `did:nostr:<pubkey>` for balance lookup.
36
+
37
+ ## Endpoints
38
+
39
+ ### GET /pay/.info
40
+ **Public. No auth required.**
41
+
42
+ Returns pod payment configuration.
43
+
44
+ Response:
45
+ ```json
46
+ {
47
+ "cost": 1,
48
+ "unit": "sat",
49
+ "deposit": "/pay/.deposit",
50
+ "balance": "/pay/.balance",
51
+ "token": {
52
+ "ticker": "PODS",
53
+ "rate": 10,
54
+ "buy": "/pay/.buy",
55
+ "withdraw": "/pay/.withdraw",
56
+ "supply": 10000,
57
+ "issuer": "025e60b6..."
58
+ }
59
+ }
60
+ ```
61
+
62
+ The `token` field is only present when `--pay-token` is configured. `rate` is sats per token.
63
+
64
+ When `--pay-chains` is configured, the response also includes:
65
+ ```json
66
+ {
67
+ "chains": [
68
+ { "id": "tbtc3", "unit": "tbtc3", "name": "Bitcoin Testnet3" },
69
+ { "id": "tbtc4", "unit": "tbtc4", "name": "Bitcoin Testnet4" }
70
+ ],
71
+ "pool": "/pay/.pool"
72
+ }
73
+ ```
74
+
75
+ ### GET /pay/.balance
76
+ **Requires NIP-98 auth.**
77
+
78
+ Returns the caller's sat balance.
79
+
80
+ Response:
81
+ ```json
82
+ {
83
+ "did": "did:nostr:4fa459ad...",
84
+ "balance": 1041588,
85
+ "cost": 1,
86
+ "unit": "sat"
87
+ }
88
+ ```
89
+
90
+ ### POST /pay/.deposit
91
+ **Requires NIP-98 auth.**
92
+
93
+ Credits the caller's balance. Two deposit types:
94
+
95
+ **Sats (TXO URI):**
96
+ ```
97
+ POST /pay/.deposit
98
+ Content-Type: text/plain
99
+ Body: <txid>:<vout>
100
+ ```
101
+
102
+ The server calls the mempool API to verify the UTXO exists and reads its value. The sat amount is credited to the caller's webledger balance.
103
+
104
+ **MRC20 tokens (state proof):**
105
+ ```json
106
+ POST /pay/.deposit
107
+ Content-Type: application/json
108
+ {
109
+ "type": "mrc20",
110
+ "state": { ... },
111
+ "prevState": { ... },
112
+ "anchor": {
113
+ "pubkey": "<issuer-compressed-pubkey>",
114
+ "stateStrings": ["<jcs-of-each-state>"],
115
+ "network": "testnet4"
116
+ }
117
+ }
118
+ ```
119
+
120
+ The server verifies: state chain integrity (`state.prev == SHA256(JCS(prevState))`), transfer to the pod's `payAddress`, and optionally anchor verification (derives expected taproot address from pubkey + state chain, checks mempool for UTXO). Replay protection rejects duplicate state hashes.
121
+
122
+ ### POST /pay/.buy
123
+ **Requires NIP-98 auth. Requires `--pay-token` configured.**
124
+
125
+ Buy tokens from the pod at the configured `payRate` (sats per token).
126
+
127
+ Request (pick one):
128
+ ```json
129
+ { "amount": 100 } // buy 100 tokens
130
+ { "sats": 1000 } // spend 1000 sats worth
131
+ ```
132
+
133
+ The server:
134
+ 1. Checks caller's sat balance >= cost
135
+ 2. Loads the pod's MRC20 token trail
136
+ 3. Calls `transferToken()` — creates a new MRC20 state transferring tokens to the buyer's pubkey, derives a new taproot address via BIP-341 key chaining, builds and broadcasts a Bitcoin transaction
137
+ 4. Debits sats from caller's webledger balance
138
+
139
+ Response includes a portable proof:
140
+ ```json
141
+ {
142
+ "bought": 100,
143
+ "ticker": "PODS",
144
+ "cost": 1000,
145
+ "rate": 10,
146
+ "balance": 1040588,
147
+ "txid": "c3183f41...",
148
+ "proof": {
149
+ "state": { ... },
150
+ "prevState": { ... },
151
+ "anchor": {
152
+ "pubkey": "025e60b6...",
153
+ "stateStrings": ["<jcs>", "<jcs>"],
154
+ "network": "testnet4"
155
+ }
156
+ }
157
+ }
158
+ ```
159
+
160
+ The proof is independently verifiable: anyone can derive the expected taproot address from the pubkey + stateStrings and check the Bitcoin UTXO.
161
+
162
+ ### POST /pay/.withdraw
163
+ **Requires NIP-98 auth. Requires `--pay-token` configured.**
164
+
165
+ Convert sat balance back to portable tokens. Same mechanism as buy.
166
+
167
+ Request (pick one):
168
+ ```json
169
+ { "tokens": 50 } // withdraw 50 tokens
170
+ { "sats": 500 } // withdraw 500 sats worth
171
+ { "all": true } // drain entire balance
172
+ ```
173
+
174
+ Response is identical to buy, with `"withdrawn"` instead of `"bought"`.
175
+
176
+ ### GET /pay/.offers
177
+ **Public. No auth required.**
178
+
179
+ Returns open sell orders from the secondary market.
180
+
181
+ Response:
182
+ ```json
183
+ [
184
+ {
185
+ "id": "uuid",
186
+ "seller": "<pubkey>",
187
+ "ticker": "PODS",
188
+ "amount": 100,
189
+ "price": 1500,
190
+ "rate": 15,
191
+ "status": "pending",
192
+ "created": 1773259044534
193
+ }
194
+ ]
195
+ ```
196
+
197
+ ### POST /pay/.sell
198
+ **Requires NIP-98 auth. Requires `--pay-token` configured.**
199
+
200
+ Create a sell order. The seller must have tokens on the pod's MRC20 trail.
201
+
202
+ Request:
203
+ ```json
204
+ {
205
+ "amount": 100,
206
+ "price": 1500
207
+ }
208
+ ```
209
+
210
+ `amount` = tokens to sell, `price` = total sats asked. The server verifies the seller's token balance on the trail before creating the order.
211
+
212
+ ### POST /pay/.swap
213
+ **Requires NIP-98 auth. Requires `--pay-token` configured.**
214
+
215
+ Execute a swap against an open sell order.
216
+
217
+ Request:
218
+ ```json
219
+ { "id": "<offer-uuid>" }
220
+ ```
221
+
222
+ The server:
223
+ 1. Finds the pending offer
224
+ 2. Checks buyer's sat balance >= offer price
225
+ 3. Transfers tokens from seller to buyer on the MRC20 trail (Bitcoin TX)
226
+ 4. Debits buyer's sats, credits seller's sats on the webledger
227
+ 5. Marks offer as filled
228
+
229
+ Response includes the portable proof, same as buy.
230
+
231
+ ### GET /pay/*
232
+ **Requires NIP-98 auth.**
233
+
234
+ Access a paid resource. The server deducts `cost` sats from the caller's balance. If balance < cost, returns 402:
235
+
236
+ ```json
237
+ {
238
+ "error": "Payment Required",
239
+ "balance": 0,
240
+ "cost": 1,
241
+ "unit": "sat",
242
+ "deposit": "/pay/.deposit"
243
+ }
244
+ ```
245
+
246
+ On success, the resource is served normally with headers:
247
+ ```
248
+ X-Balance: 1040587
249
+ X-Cost: 1
250
+ ```
251
+
252
+ ### PUT /pay/*
253
+
254
+ Standard upload — goes through normal WAC auth, not the pay middleware. Only the pod owner can write to `/pay/`.
255
+
256
+ ## Configuration
257
+
258
+ ### CLI flags
259
+ ```
260
+ --pay Enable HTTP 402 for /pay/* routes
261
+ --pay-cost <n> Cost per request in satoshis (default: 1)
262
+ --pay-mempool-url <url> Mempool API URL (default: testnet4)
263
+ --pay-address <addr> Address for receiving MRC20 deposits
264
+ --pay-token <ticker> Token ticker (enables buy/withdraw/sell/swap)
265
+ --pay-rate <n> Sats per token for buy/withdraw (default: 1)
266
+ ```
267
+
268
+ ### Environment variables
269
+ ```
270
+ JSS_PAY=true
271
+ JSS_PAY_COST=1
272
+ JSS_PAY_MEMPOOL_URL=https://mempool.space/testnet4
273
+ JSS_PAY_ADDRESS=tb1q...
274
+ JSS_PAY_TOKEN=PODS
275
+ JSS_PAY_RATE=10
276
+ ```
277
+
278
+ ## Token Management (CLI)
279
+
280
+ ```bash
281
+ # Mint a new token (requires funded Bitcoin UTXO)
282
+ jss token mint --ticker PODS --supply 10000 \
283
+ --voucher "txo:btc:<txid>:<vout>?amount=<sats>&key=<privkey-hex>"
284
+
285
+ # Transfer tokens
286
+ jss token transfer --ticker PODS --to <pubkey> --amount 100
287
+
288
+ # Show token info
289
+ jss token info PODS
290
+ ```
291
+
292
+ ## Data Files
293
+
294
+ | File | Contents |
295
+ |------|----------|
296
+ | `/.well-known/webledgers/webledgers.json` | Balances per DID — multi-currency array format (webledgers.org spec) |
297
+ | `/.well-known/webledgers/replay.json` | Seen MRC20 state hashes (replay protection) |
298
+ | `/.well-known/webledgers/offers.json` | Open sell orders (secondary market) |
299
+ | `/.well-known/webledgers/pool.json` | AMM pool state (reserves, LP shares, k) |
300
+ | `/.well-known/token/<ticker>.json` | MRC20 token trail (state chain, keys, UTXO) |
301
+
302
+ ## Source Files
303
+
304
+ | File | Purpose |
305
+ |------|---------|
306
+ | `src/handlers/pay.js` | All `/pay/*` route handling |
307
+ | `src/webledger.js` | Balance read/write/credit/debit |
308
+ | `src/mrc20.js` | MRC20 verification, JCS, BIP-341 key chaining, bech32m |
309
+ | `src/token.js` | Token mint/transfer, Bitcoin TX building, trail persistence |
310
+ | `src/auth/nostr.js` | NIP-98 auth extraction |
311
+
312
+ ## Key Concepts
313
+
314
+ **Webledger**: A JSON file mapping URIs to numerical balances, following the [webledgers.org](https://webledgers.org/) spec. The URI format is `did:nostr:<pubkey>`.
315
+
316
+ **MRC20**: A token profile on blocktrails. Each state is a JSON object with `profile`, `prev` (hash link to previous state), `seq`, `ticker`, `balances`, and `ops`. States form a hash chain.
317
+
318
+ **BIP-341 Key Chaining**: Each MRC20 state is hashed and used as a taproot tweak scalar. The scalar is added to the issuer's public key via elliptic curve addition to derive a unique P2TR address per state. This anchors the state chain to Bitcoin — anyone can verify by re-deriving the address and checking the UTXO.
319
+
320
+ **JCS (RFC 8785)**: JSON Canonicalization Scheme — sorted keys, no whitespace, deterministic serialization. Used for hashing states.
321
+
322
+ **NIP-98**: Nostr HTTP Authentication. A signed event (kind 27235) with the request URL and method in tags, base64-encoded in the Authorization header.
323
+
324
+ **NIP-69 (kind 38383)**: P2P order events for trading. Used as the convention for sell orders in the secondary market.
325
+
326
+ ## Flow Examples
327
+
328
+ ### Agent buys API access
329
+ ```
330
+ 1. Agent has a funded TXO (Bitcoin UTXO with known private key)
331
+ 2. GET /pay/.info → learns cost=1, deposit endpoint
332
+ 3. POST /pay/.deposit → posts TXO URI, gets 1M sats credited
333
+ 4. GET /pay/data/feed.json → costs 1 sat, returns data
334
+ 5. (repeat step 4 up to 1M times)
335
+ ```
336
+
337
+ ### User buys and trades tokens
338
+ ```
339
+ 1. POST /pay/.deposit → deposit sats
340
+ 2. POST /pay/.buy → buy 100 PODS for 1000 sats, get Bitcoin proof
341
+ 3. POST /pay/.sell → list 50 PODS for sale at 750 sats
342
+ 4. (another user)
343
+ 5. GET /pay/.offers → sees the sell order
344
+ 6. POST /pay/.swap → buys the 50 PODS, seller gets 750 sats credited
345
+ ```
346
+
347
+ ### Cross-chain AMM trading
348
+ ```
349
+ 1. Configure pod: jss start --pay --pay-chains "tbtc3,tbtc4"
350
+ 2. User A deposits: POST /pay/.deposit "txo:tbtc3:<txid>:<vout>" → gets tbtc3 balance
351
+ 3. User B deposits: POST /pay/.deposit "txo:tbtc4:<txid>:<vout>" → gets tbtc4 balance
352
+ 4. User A adds liquidity: POST /pay/.pool { "action": "add-liquidity", "tbtc3": 1000, "tbtc4": 5000 }
353
+ 5. User B swaps: POST /pay/.pool { "action": "swap", "sell": "tbtc4", "amount": 500 }
354
+ → receives ~90 tbtc3 (constant product formula, 0.3% fee)
355
+ 6. User A removes liquidity: POST /pay/.pool { "action": "remove-liquidity", "all": true }
356
+ → gets back proportional share of both currencies + earned fees
357
+ ```
358
+
359
+ ### Full exit
360
+ ```
361
+ 1. POST /pay/.withdraw { "all": true } → converts entire balance to portable tokens
362
+ 2. User now holds MRC20 proof, independently verifiable on Bitcoin
363
+ 3. Can deposit on another pod, or trade peer-to-peer
364
+ ```
package/README.md CHANGED
@@ -158,6 +158,7 @@ jss --help # Show help
158
158
  | `--pay-address <addr>` | Address for receiving deposits | - |
159
159
  | `--pay-token <ticker>` | Token to sell (enables primary market + withdrawal) | - |
160
160
  | `--pay-rate <n>` | Sats per token for buy/withdraw | 1 |
161
+ | `--pay-chains <ids>` | Multi-chain deposits + AMM (e.g. "tbtc3,tbtc4") | - |
161
162
  | `--mongo` | Enable MongoDB-backed /db/ route | false |
162
163
  | `--mongo-url <url>` | MongoDB connection URL | mongodb://localhost:27017 |
163
164
  | `--mongo-database <name>` | MongoDB database name | solid |
@@ -834,11 +835,16 @@ jss start --pay --pay-cost 10 --pay-address your-address --pay-token PODS --pay-
834
835
 
835
836
  | Method | Path | Description |
836
837
  |--------|------|-------------|
837
- | GET | `/pay/.info` | Public: cost, token info, available routes |
838
+ | GET | `/pay/.info` | Public: cost, token info, chains, pool |
838
839
  | GET | `/pay/.balance` | Check your balance (NIP-98 auth) |
839
840
  | POST | `/pay/.deposit` | Deposit sats via TXO URI or MRC20 state proof |
840
841
  | POST | `/pay/.buy` | Buy tokens with sat balance (requires `--pay-token`) |
841
842
  | POST | `/pay/.withdraw` | Withdraw balance as portable tokens (requires `--pay-token`) |
843
+ | GET | `/pay/.offers` | List open sell orders (secondary market) |
844
+ | POST | `/pay/.sell` | Create a sell order (requires `--pay-token`) |
845
+ | POST | `/pay/.swap` | Execute a swap against a sell order |
846
+ | GET | `/pay/.pool` | AMM pool state (requires `--pay-chains`) |
847
+ | POST | `/pay/.pool` | AMM swap, add/remove liquidity |
842
848
  | GET | `/pay/*` | Paid resource access (deducts balance) |
843
849
 
844
850
  ### How It Works
@@ -879,6 +885,39 @@ curl -X POST -H "Authorization: Nostr <base64-event>" \
879
885
 
880
886
  Deposit verification uses the mempool API (default: testnet4). The `X-Balance` and `X-Cost` headers are returned on successful paid requests. Buy and withdraw return portable MRC20 proofs with Bitcoin anchor data for independent verification.
881
887
 
888
+ ### Secondary Market
889
+
890
+ 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.
891
+
892
+ ### Multi-Chain AMM
893
+
894
+ Enable multi-chain deposits and an automated market maker:
895
+
896
+ ```bash
897
+ jss start --pay --pay-chains "tbtc3,tbtc4"
898
+ ```
899
+
900
+ 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.
901
+
902
+ ```bash
903
+ # Add liquidity
904
+ curl -X POST -H "Authorization: Nostr <token>" \
905
+ -H "Content-Type: application/json" \
906
+ http://localhost:3000/pay/.pool \
907
+ -d '{"action": "add-liquidity", "tbtc3": 1000, "tbtc4": 5000}'
908
+
909
+ # Swap
910
+ curl -X POST -H "Authorization: Nostr <token>" \
911
+ -H "Content-Type: application/json" \
912
+ http://localhost:3000/pay/.pool \
913
+ -d '{"action": "swap", "sell": "tbtc3", "amount": 100}'
914
+
915
+ # Check pool state
916
+ curl http://localhost:3000/pay/.pool
917
+ ```
918
+
919
+ Supported chains: `btc`, `tbtc3`, `tbtc4`, `ltc`, `signet`.
920
+
882
921
  ## Authentication
883
922
 
884
923
  ### Simple Tokens (Development)
package/bin/jss.js CHANGED
@@ -87,6 +87,7 @@ program
87
87
  .option('--pay-address <addr>', 'Address for receiving deposits')
88
88
  .option('--pay-token <ticker>', 'Token to sell (enables primary market)')
89
89
  .option('--pay-rate <n>', 'Sats per token for primary market (default: 1)', parseInt)
90
+ .option('--pay-chains <chains>', 'Comma-separated chain IDs for multi-chain deposits/AMM (e.g. "tbtc3,tbtc4")')
90
91
  .option('--mongo', 'Enable MongoDB-backed /db/ route')
91
92
  .option('--no-mongo', 'Disable MongoDB-backed /db/ route')
92
93
  .option('--mongo-url <url>', 'MongoDB connection URL (default: mongodb://localhost:27017)')
@@ -159,6 +160,7 @@ program
159
160
  payAddress: config.payAddress,
160
161
  payToken: config.payToken,
161
162
  payRate: config.payRate,
163
+ payChains: config.payChains,
162
164
  mongo: config.mongo,
163
165
  mongoUrl: config.mongoUrl,
164
166
  mongoDatabase: config.mongoDatabase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -24,6 +24,7 @@
24
24
  "benchmark": "node benchmark.js"
25
25
  },
26
26
  "dependencies": {
27
+ "@noble/curves": "^1.2.0",
27
28
  "@fastify/middie": "^8.3.3",
28
29
  "@fastify/rate-limit": "^9.1.0",
29
30
  "@fastify/websocket": "^8.3.1",
package/src/config.js CHANGED
@@ -90,6 +90,7 @@ export const defaults = {
90
90
  payAddress: null,
91
91
  payToken: null,
92
92
  payRate: 1,
93
+ payChains: null, // comma-separated chain IDs, e.g. "tbtc3,tbtc4"
93
94
 
94
95
  // MongoDB-backed /db/ route
95
96
  mongo: false,
@@ -152,6 +153,7 @@ const envMap = {
152
153
  JSS_PAY_ADDRESS: 'payAddress',
153
154
  JSS_PAY_TOKEN: 'payToken',
154
155
  JSS_PAY_RATE: 'payRate',
156
+ JSS_PAY_CHAINS: 'payChains',
155
157
  JSS_MONGO: 'mongo',
156
158
  JSS_MONGO_URL: 'mongoUrl',
157
159
  JSS_MONGO_DATABASE: 'mongoDatabase',