javascript-solid-server 0.0.133 → 0.0.134
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 +49 -18
- package/src/token.js +1 -1
package/package.json
CHANGED
package/src/handlers/pay.js
CHANGED
|
@@ -29,7 +29,7 @@ import crypto from 'crypto';
|
|
|
29
29
|
import { getNostrPubkey, pubkeyToDidNostr } from '../auth/nostr.js';
|
|
30
30
|
import { readLedger, writeLedger, getBalance, credit, debit } from '../webledger.js';
|
|
31
31
|
import { verifyMrc20Deposit, verifyMrc20Anchor, jcs, btAddress } from '../mrc20.js';
|
|
32
|
-
import { loadTrail, transferToken, buildTransaction, broadcastTx, p2trScript } from '../token.js';
|
|
32
|
+
import { loadTrail, transferToken, buildTransaction, broadcastTx, p2trScript, btDeriveChainedPrivkey } from '../token.js';
|
|
33
33
|
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
34
34
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
35
35
|
import fs from 'fs-extra';
|
|
@@ -282,7 +282,7 @@ export function createPayHandler(options = {}) {
|
|
|
282
282
|
return reply.send(info);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
// --- GET /pay/.address —
|
|
285
|
+
// --- GET /pay/.address — deposit address (optional per-user tweak) ---
|
|
286
286
|
if (url === '/pay/.address' && request.method === 'GET') {
|
|
287
287
|
const chain = request.query?.chain || (payChains ? payChains[0] : 'tbtc4');
|
|
288
288
|
if (!CHAIN_REGISTRY[chain]) {
|
|
@@ -293,8 +293,15 @@ export function createPayHandler(options = {}) {
|
|
|
293
293
|
}
|
|
294
294
|
const kp = await loadOrCreateKeypair();
|
|
295
295
|
const network = chain === 'btc' ? 'mainnet' : (chain === 'tbtc3' ? 'testnet' : 'testnet4');
|
|
296
|
-
const
|
|
297
|
-
|
|
296
|
+
const user = request.query?.user?.trim().toLowerCase() || null;
|
|
297
|
+
if (user && !/^did:nostr:[0-9a-f]{64}$/.test(user)) {
|
|
298
|
+
return reply.code(400).send({ error: 'Invalid user DID. Expected: did:nostr:<64-hex>' });
|
|
299
|
+
}
|
|
300
|
+
const states = user ? [user] : [];
|
|
301
|
+
const address = btAddress(kp.pubkey, states, network);
|
|
302
|
+
const response = { address, chain, pubkey: kp.pubkey };
|
|
303
|
+
if (user) response.user = user;
|
|
304
|
+
return reply.send(response);
|
|
298
305
|
}
|
|
299
306
|
|
|
300
307
|
// --- GET /pay/.balance ---
|
|
@@ -435,8 +442,10 @@ export function createPayHandler(options = {}) {
|
|
|
435
442
|
return reply.code(400).send({ error: `Unknown chain: ${chainId}` });
|
|
436
443
|
}
|
|
437
444
|
|
|
438
|
-
// Derive
|
|
445
|
+
// Derive address — try per-user tweaked address first, fall back to generic
|
|
439
446
|
const network = chainId === 'btc' ? 'mainnet' : (chainId === 'tbtc3' ? 'testnet' : 'testnet4');
|
|
447
|
+
const didUri = pubkeyToDidNostr(pubkey);
|
|
448
|
+
const userAddress = btAddress(kp.pubkey, [didUri], network);
|
|
440
449
|
const podAddress = btAddress(kp.pubkey, [], network);
|
|
441
450
|
|
|
442
451
|
// Fetch transaction from mempool
|
|
@@ -455,9 +464,11 @@ export function createPayHandler(options = {}) {
|
|
|
455
464
|
return reply.code(400).send({ error: `Output ${deposit.vout} not found` });
|
|
456
465
|
}
|
|
457
466
|
|
|
458
|
-
// Verify output pays our address
|
|
459
|
-
|
|
460
|
-
|
|
467
|
+
// Verify output pays our address (per-user tweaked or generic pod address)
|
|
468
|
+
const outputAddr = output.scriptpubkey_address;
|
|
469
|
+
const tweak = outputAddr === userAddress ? didUri : null;
|
|
470
|
+
if (outputAddr !== userAddress && outputAddr !== podAddress) {
|
|
471
|
+
return reply.code(400).send({ error: 'Output does not pay this pod\'s address', expected: { user: userAddress, pod: podAddress } });
|
|
461
472
|
}
|
|
462
473
|
|
|
463
474
|
const amount = output.value;
|
|
@@ -465,14 +476,12 @@ export function createPayHandler(options = {}) {
|
|
|
465
476
|
|
|
466
477
|
// Replay protection + UTXO tracking
|
|
467
478
|
const utxos = await loadUtxos();
|
|
468
|
-
const utxoKey = `${deposit.txid}:${deposit.vout}`;
|
|
469
479
|
if (utxos.find(u => u.txid === deposit.txid && u.vout === deposit.vout)) {
|
|
470
480
|
return reply.code(400).send({ error: 'This output has already been claimed' });
|
|
471
481
|
}
|
|
472
|
-
utxos.push({ txid: deposit.txid, vout: deposit.vout, amount, scriptpubkey: output.scriptpubkey, chain: chainId, spent: false });
|
|
482
|
+
utxos.push({ txid: deposit.txid, vout: deposit.vout, amount, scriptpubkey: output.scriptpubkey, chain: chainId, tweak, spent: false });
|
|
473
483
|
await saveUtxos(utxos);
|
|
474
484
|
|
|
475
|
-
const didUri = pubkeyToDidNostr(pubkey);
|
|
476
485
|
const ledger = await readLedger();
|
|
477
486
|
const newBalance = credit(ledger, didUri, amount, currency);
|
|
478
487
|
await writeLedger(ledger);
|
|
@@ -752,23 +761,45 @@ export function createPayHandler(options = {}) {
|
|
|
752
761
|
return reply.code(400).send({ error: 'No UTXOs available for withdrawal' });
|
|
753
762
|
}
|
|
754
763
|
|
|
755
|
-
//
|
|
756
|
-
|
|
757
|
-
|
|
764
|
+
// Load pod keypair
|
|
765
|
+
const kp = await loadOrCreateKeypair();
|
|
766
|
+
|
|
767
|
+
// Select UTXOs — group by tweak so we can sign with one key
|
|
768
|
+
// Prefer untweaked UTXOs first, then tweaked ones
|
|
758
769
|
const fee = 300;
|
|
759
770
|
const needed = withdrawAmount + fee;
|
|
760
|
-
|
|
771
|
+
let selected = [];
|
|
772
|
+
let total = 0;
|
|
773
|
+
let selectedTweak = null;
|
|
774
|
+
|
|
775
|
+
// Try untweaked first
|
|
776
|
+
for (const utxo of available.filter(u => !u.tweak)) {
|
|
761
777
|
selected.push(utxo);
|
|
762
778
|
total += utxo.amount;
|
|
763
779
|
if (total >= needed) break;
|
|
764
780
|
}
|
|
781
|
+
// If not enough, try tweaked (same tweak group only)
|
|
782
|
+
if (total < needed) {
|
|
783
|
+
const tweaked = available.filter(u => u.tweak);
|
|
784
|
+
selected = [];
|
|
785
|
+
total = 0;
|
|
786
|
+
selectedTweak = null;
|
|
787
|
+
for (const utxo of tweaked) {
|
|
788
|
+
if (selectedTweak && utxo.tweak !== selectedTweak) continue;
|
|
789
|
+
selected.push(utxo);
|
|
790
|
+
selectedTweak = utxo.tweak;
|
|
791
|
+
total += utxo.amount;
|
|
792
|
+
if (total >= needed) break;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
765
795
|
if (total < needed) {
|
|
766
796
|
return reply.code(400).send({ error: 'Not enough UTXO value for withdrawal + fee', available: total, needed });
|
|
767
797
|
}
|
|
768
798
|
|
|
769
|
-
//
|
|
770
|
-
const
|
|
771
|
-
|
|
799
|
+
// Derive signing key (tweaked if UTXOs are tweaked)
|
|
800
|
+
const privkeyBytes = selectedTweak
|
|
801
|
+
? btDeriveChainedPrivkey(hexToBytes(kp.privkey), [selectedTweak])
|
|
802
|
+
: hexToBytes(kp.privkey);
|
|
772
803
|
|
|
773
804
|
// Generate a new keypair for the voucher recipient
|
|
774
805
|
const voucherPrivkey = secp256k1.utils.randomPrivateKey();
|
package/src/token.js
CHANGED
|
@@ -98,7 +98,7 @@ function btDeriveChainedPubkey(pubkeyBase, states) {
|
|
|
98
98
|
return cur;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function btDeriveChainedPrivkey(privkeyBytes, states) {
|
|
101
|
+
export function btDeriveChainedPrivkey(privkeyBytes, states) {
|
|
102
102
|
let d = bytesToBigInt(privkeyBytes);
|
|
103
103
|
let cur = new Uint8Array(secp256k1.getPublicKey(privkeyBytes, true));
|
|
104
104
|
for (const s of states) {
|