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.
- package/package.json +1 -1
- package/src/handlers/pay.js +102 -4
package/package.json
CHANGED
package/src/handlers/pay.js
CHANGED
|
@@ -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
|
|
9
|
-
* POST /pay/.deposit
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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);
|