javascript-solid-server 0.0.103 → 0.0.104

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/handlers/pay.js +102 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.103",
3
+ "version": "0.0.104",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -5,10 +5,12 @@
5
5
  * Authentication via NIP-98. Balance tracking via Web Ledgers spec.
6
6
  *
7
7
  * Routes:
8
- * GET /pay/.balance — check your balance
9
- * POST /pay/.deposit — deposit sats (TXO URI) or tokens (MRC20 state proof)
10
- * GET /pay/* paid resource access (requires balance >= cost)
11
- * PUT /pay/* upload resources (standard auth)
8
+ * GET /pay/.balance — check your balance
9
+ * POST /pay/.deposit — deposit sats (TXO URI) or tokens (MRC20 state proof)
10
+ * POST /pay/.buy buy tokens with sat balance (primary market)
11
+ * POST /pay/.withdraw withdraw balance as tokens (portable MRC20 proof)
12
+ * GET /pay/* — paid resource access (requires balance >= cost)
13
+ * PUT /pay/* — upload resources (standard auth)
12
14
  *
13
15
  * Ledger: /.well-known/webledgers/webledgers.json (webledgers.org spec)
14
16
  *
@@ -363,6 +365,102 @@ export function createPayHandler(options = {}) {
363
365
  });
364
366
  }
365
367
 
368
+ // --- POST /pay/.withdraw — withdraw balance as tokens ---
369
+ if (url === '/pay/.withdraw' && request.method === 'POST') {
370
+ const pubkey = await getNostrPubkey(request);
371
+ if (!pubkey) {
372
+ return reply.code(401).send({ error: 'NIP-98 authentication required' });
373
+ }
374
+
375
+ if (!payToken) {
376
+ return reply.code(400).send({ error: 'Withdrawal not configured (no --pay-token set)' });
377
+ }
378
+
379
+ // Parse withdraw request
380
+ let body = request.body;
381
+ if (Buffer.isBuffer(body)) body = JSON.parse(body.toString('utf8'));
382
+ if (typeof body === 'string') body = JSON.parse(body);
383
+
384
+ const didUri = pubkeyToDidNostr(pubkey);
385
+ const ledger = await readLedger();
386
+ const balance = getBalance(ledger, didUri);
387
+
388
+ // Calculate withdrawal amount
389
+ let satCost, tokenAmount;
390
+ if (body?.all) {
391
+ satCost = balance;
392
+ tokenAmount = Math.floor(balance / payRate);
393
+ } else if (body?.sats) {
394
+ satCost = Math.floor(body.sats);
395
+ tokenAmount = Math.floor(satCost / payRate);
396
+ } else if (body?.tokens) {
397
+ tokenAmount = Math.floor(body.tokens);
398
+ satCost = tokenAmount * payRate;
399
+ } else {
400
+ return reply.code(400).send({
401
+ error: 'Specify tokens, sats, or all: true',
402
+ balance,
403
+ rate: payRate,
404
+ unit: 'sat/token'
405
+ });
406
+ }
407
+
408
+ if (tokenAmount <= 0) {
409
+ return reply.code(400).send({ error: 'Nothing to withdraw', balance, rate: payRate });
410
+ }
411
+
412
+ if (balance < satCost) {
413
+ return reply.code(402).send({
414
+ error: 'Insufficient balance',
415
+ balance,
416
+ cost: satCost,
417
+ rate: payRate
418
+ });
419
+ }
420
+
421
+ // Load token trail
422
+ const trail = await loadTrail(payToken);
423
+ if (!trail) {
424
+ return reply.code(500).send({ error: `Token ${payToken} not minted on this pod` });
425
+ }
426
+
427
+ // Transfer tokens to user
428
+ let result;
429
+ try {
430
+ result = await transferToken({
431
+ ticker: payToken,
432
+ to: pubkey,
433
+ amount: tokenAmount,
434
+ mempoolUrl
435
+ });
436
+ } catch (err) {
437
+ return reply.code(500).send({ error: `Transfer failed: ${err.message}` });
438
+ }
439
+
440
+ // Debit balance
441
+ debit(ledger, didUri, satCost);
442
+ await writeLedger(ledger);
443
+
444
+ return reply.send({
445
+ withdrawn: tokenAmount,
446
+ ticker: payToken,
447
+ cost: satCost,
448
+ rate: payRate,
449
+ balance: getBalance(ledger, didUri),
450
+ unit: 'sat',
451
+ txid: result.txid,
452
+ proof: {
453
+ state: result.state,
454
+ prevState: result.prevState,
455
+ anchor: {
456
+ pubkey: result.trail.pubkeyBase,
457
+ stateStrings: result.trail.stateStrings,
458
+ network: result.trail.network
459
+ }
460
+ }
461
+ });
462
+ }
463
+
366
464
  // --- GET/HEAD /pay/* — paid resource access ---
367
465
  if (request.method === 'GET' || request.method === 'HEAD') {
368
466
  const pubkey = await getNostrPubkey(request);