@dvmkit/sdk 0.16.2 → 0.17.0

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 (52) hide show
  1. package/dist/{chunk-XSF5YTEY.js → chunk-2PN4DKZW.js} +1 -1
  2. package/dist/{chunk-F2LNWZCV.js → chunk-3KRFFLAT.js} +1 -1
  3. package/dist/{chunk-KERFCBT7.js → chunk-4QYADZFN.js} +498 -129
  4. package/dist/{chunk-V73WRF46.js → chunk-5CCQEAQV.js} +1 -1
  5. package/dist/{chunk-4IFZHJQK.js → chunk-5RLNRLLB.js} +1 -1
  6. package/dist/{chunk-7QGS27UT.js → chunk-72A4PHLD.js} +1 -1
  7. package/dist/{chunk-XBQTFOIT.js → chunk-7RWE6ELV.js} +1 -1
  8. package/dist/{chunk-N4SBDUVD.js → chunk-A6EY4K46.js} +1 -1
  9. package/dist/{chunk-5U2RG3ZW.js → chunk-ALTE7I6C.js} +5 -0
  10. package/dist/{chunk-K4ZQE7IB.js → chunk-B5MPLDRC.js} +2 -2
  11. package/dist/{chunk-PP5WHB6T.js → chunk-C5XNZLZ7.js} +1 -1
  12. package/dist/{chunk-2UZOJRRS.js → chunk-CVN2YNMG.js} +1 -1
  13. package/dist/{chunk-MD34LE6M.js → chunk-CXRI4N4Y.js} +1 -1
  14. package/dist/{chunk-ELNWQS24.js → chunk-EIDRCGTY.js} +1 -1
  15. package/dist/{chunk-ICHC4VDZ.js → chunk-KRX7VNBG.js} +1 -1
  16. package/dist/{chunk-TN6MWTZI.js → chunk-LY2JYSJS.js} +1 -1
  17. package/dist/{chunk-7N5VDCG3.js → chunk-MWDUGMN2.js} +2 -2
  18. package/dist/{chunk-FRGKANTZ.js → chunk-ORBWKYDE.js} +1 -1
  19. package/dist/{chunk-VQHBJXBL.js → chunk-QTKGFG3I.js} +1 -1
  20. package/dist/{chunk-PBT3H2LZ.js → chunk-RX6FT7XA.js} +1 -1
  21. package/dist/{chunk-OYBSDJR6.js → chunk-VKFR5PCW.js} +2 -2
  22. package/dist/{chunk-V4CBI5HB.js → chunk-Y6JJDOGO.js} +1 -1
  23. package/dist/{content-retention-BASsjgKw.d.ts → content-retention-B_hOQS36.d.ts} +1 -1
  24. package/dist/{credit-ledger-VWZ5NP7V.js → credit-ledger-5HMJ6VUX.js} +3 -3
  25. package/dist/{credit-menu-BfXaeHgR.d.ts → credit-menu-D-h0zJCZ.d.ts} +42 -10
  26. package/dist/{fx-BmvWGISH.d.ts → fx-CpqQaIfg.d.ts} +1 -1
  27. package/dist/index.d.ts +5 -5
  28. package/dist/index.js +4 -4
  29. package/dist/internal/caller.d.ts +14 -8
  30. package/dist/internal/caller.js +7 -7
  31. package/dist/internal/server.d.ts +7 -7
  32. package/dist/internal/server.js +20 -20
  33. package/dist/{job-store-CA7u1nHV.d.ts → job-store-CgXT79tS.d.ts} +1 -1
  34. package/dist/{lightning-backend-BOcopzXk.d.ts → lightning-backend-B1hRUIOb.d.ts} +1 -1
  35. package/dist/{memory-credit-ledger-DZWRQGSF.js → memory-credit-ledger-ME6FH462.js} +4 -4
  36. package/dist/{mpp-setup-3NJY354I.js → mpp-setup-BLIYBORK.js} +3 -3
  37. package/dist/{payout-reporter-P3652TLG.js → payout-reporter-5QAHCYIO.js} +2 -2
  38. package/dist/{postgres-consumed-credential-store-S2CBQ4NO.js → postgres-consumed-credential-store-4PCJEZBD.js} +1 -1
  39. package/dist/{postgres-job-store-JRSG54VJ.js → postgres-job-store-E5GXLRB4.js} +2 -2
  40. package/dist/{postgres-replay-store-ZPILIJIW.js → postgres-replay-store-GH5AINQR.js} +2 -2
  41. package/dist/{pricing-3P7KAONY.js → pricing-JJYLTTWK.js} +3 -3
  42. package/dist/{revenue-reporter-2P3PWJJM.js → revenue-reporter-6XARUEG6.js} +2 -2
  43. package/dist/server/index.d.ts +10 -10
  44. package/dist/server/index.js +32 -31
  45. package/dist/{step-cache-BCRHimiY.d.ts → step-cache-Bezn-_ZL.d.ts} +41 -3
  46. package/dist/{tempo-charge-store-2LK6IDLS.js → tempo-charge-store-A3CDF7D5.js} +2 -2
  47. package/dist/{tempo-session-store-5MHCH3YW.js → tempo-session-store-EWBE5HJM.js} +3 -3
  48. package/dist/testing/index.d.ts +3 -3
  49. package/dist/testing/index.js +6 -6
  50. package/dist/{usd-B8_WMfHT.d.ts → usd-CJwU3-A4.d.ts} +1 -1
  51. package/dist/{x402-6T73AZQ6.js → x402-HVCUEWWU.js} +3 -3
  52. package/package.json +1 -1
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-XRLKGCAW.js";
6
6
  import {
7
7
  x402EarnedCeiling
8
- } from "./chunk-ICHC4VDZ.js";
8
+ } from "./chunk-KRX7VNBG.js";
9
9
  import {
10
10
  createX402FacilitatorAuthHeaders,
11
11
  createX402FacilitatorClient,
@@ -14,13 +14,13 @@ import {
14
14
  verifyWithFacilitator,
15
15
  withPaymentDiagnostics,
16
16
  withPaymentStage
17
- } from "./chunk-F2LNWZCV.js";
17
+ } from "./chunk-3KRFFLAT.js";
18
18
  import {
19
19
  MemoryCreditLedger
20
- } from "./chunk-PP5WHB6T.js";
20
+ } from "./chunk-C5XNZLZ7.js";
21
21
  import {
22
22
  PostgresJobStore
23
- } from "./chunk-N4SBDUVD.js";
23
+ } from "./chunk-A6EY4K46.js";
24
24
  import {
25
25
  SIGNED_ENVELOPE_FIELDS,
26
26
  SignedRequestError,
@@ -28,7 +28,7 @@ import {
28
28
  humanizeZodIssues,
29
29
  signedRequestInput,
30
30
  zodIssuesOf
31
- } from "./chunk-V73WRF46.js";
31
+ } from "./chunk-5CCQEAQV.js";
32
32
  import {
33
33
  MemoryJobStore,
34
34
  UNCAPPABLE_ASK_TOTAL,
@@ -37,7 +37,7 @@ import {
37
37
  isJobRetentionStore,
38
38
  isReceiptIssuingStore,
39
39
  isStreamableJobStore
40
- } from "./chunk-V4CBI5HB.js";
40
+ } from "./chunk-Y6JJDOGO.js";
41
41
  import {
42
42
  ContentHoldError,
43
43
  JobCancelledError,
@@ -78,7 +78,7 @@ import {
78
78
  receiptPubkeyFromSecret,
79
79
  toLockPubkey,
80
80
  verifyChallenge
81
- } from "./chunk-XBQTFOIT.js";
81
+ } from "./chunk-7RWE6ELV.js";
82
82
  import {
83
83
  X402_BATCH_SETTLEMENT_SCHEME,
84
84
  X402_DEFAULT_FACILITATOR,
@@ -105,7 +105,7 @@ import {
105
105
  } from "./chunk-SXL44PL5.js";
106
106
  import {
107
107
  createConsoleLogger
108
- } from "./chunk-VQHBJXBL.js";
108
+ } from "./chunk-QTKGFG3I.js";
109
109
  import {
110
110
  StepCache
111
111
  } from "./chunk-BQJIFRAZ.js";
@@ -122,7 +122,7 @@ import {
122
122
  roundUsd,
123
123
  satsToFiat,
124
124
  validateCurrency
125
- } from "./chunk-7QGS27UT.js";
125
+ } from "./chunk-72A4PHLD.js";
126
126
  import {
127
127
  CreditLedger,
128
128
  CreditLedgerError,
@@ -138,7 +138,7 @@ import {
138
138
  isNonChannelBitcoinRail,
139
139
  netOwedSats,
140
140
  x402SettlementPending
141
- } from "./chunk-K4ZQE7IB.js";
141
+ } from "./chunk-B5MPLDRC.js";
142
142
  import {
143
143
  parseStoreValue
144
144
  } from "./chunk-YG7G4DPZ.js";
@@ -156,7 +156,7 @@ import {
156
156
  } from "./chunk-5UC7TOQC.js";
157
157
  import {
158
158
  withPostgresRecoveryContext
159
- } from "./chunk-ELNWQS24.js";
159
+ } from "./chunk-EIDRCGTY.js";
160
160
  import {
161
161
  DvmError,
162
162
  x402NetworkByCaip2
@@ -174,17 +174,17 @@ import {
174
174
  installHostedTempoCloseGuard,
175
175
  isTempoInsufficientFundsError,
176
176
  resolveTempoNetwork
177
- } from "./chunk-7N5VDCG3.js";
177
+ } from "./chunk-MWDUGMN2.js";
178
178
  import {
179
179
  loggers,
180
180
  pinDimension,
181
181
  withSpan,
182
182
  withTraceContext
183
- } from "./chunk-TN6MWTZI.js";
183
+ } from "./chunk-LY2JYSJS.js";
184
184
  import {
185
185
  createDiagnosticCapabilityAuthority,
186
186
  diagnosticConsole
187
- } from "./chunk-5U2RG3ZW.js";
187
+ } from "./chunk-ALTE7I6C.js";
188
188
 
189
189
  // src/sdk/server/lock-pubkey-store.ts
190
190
  async function initLockPubkeyTable(db) {
@@ -428,13 +428,26 @@ async function insertAccumulatorRows(db, args) {
428
428
  client.release();
429
429
  }
430
430
  }
431
- async function hasAccumulatedProof(db, dvmId, secrets) {
432
- if (secrets.length === 0) return false;
431
+ async function claimAccumulatorRequestId(tx, dvmId, requestId) {
432
+ await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [
433
+ JSON.stringify(["wallet_accumulator_request", dvmId, requestId])
434
+ ]);
435
+ const { rows: rows2 } = await tx.query(
436
+ `SELECT 1 FROM wallet_accumulator WHERE dvm_id = $1 AND request_id = $2 LIMIT 1`,
437
+ [dvmId, requestId]
438
+ );
439
+ if (rows2.length > 0) throw new AccumulatorReplayError(dvmId, requestId);
440
+ }
441
+ async function acceptedRequestIds(db, dvmId, secrets) {
442
+ if (secrets.length === 0) return [];
443
+ const hashes = secrets.map(hashProofSecret);
433
444
  const { rows: rows2 } = await db.query(
434
- `SELECT 1 FROM wallet_accumulator WHERE dvm_id = $1 AND proof_secret_hash = ANY($2::text[]) LIMIT 1`,
435
- [dvmId, secrets.map(hashProofSecret)]
445
+ `SELECT proof_secret_hash, request_id FROM wallet_accumulator
446
+ WHERE dvm_id = $1 AND proof_secret_hash = ANY($2::text[])`,
447
+ [dvmId, hashes]
436
448
  );
437
- return rows2.length > 0;
449
+ const byHash = new Map(rows2.map((row) => [row.proof_secret_hash, row.request_id]));
450
+ return hashes.map((hash3) => byHash.get(hash3));
438
451
  }
439
452
  async function listPendingBatches(db, dvmId) {
440
453
  const { rows: rows2 } = await db.query(
@@ -3234,7 +3247,7 @@ function paymentFailureFields(error) {
3234
3247
  }
3235
3248
 
3236
3249
  // src/sdk/server/payment.ts
3237
- import { createHash as createHash4, randomBytes, randomUUID as randomUUID2 } from "crypto";
3250
+ import { createHash as createHash5, randomBytes, randomUUID as randomUUID2 } from "crypto";
3238
3251
  import { Challenge } from "mppx";
3239
3252
 
3240
3253
  // src/lib/cashu/verify.ts
@@ -3328,6 +3341,63 @@ function methodNativeAmountToDecimal(amount, methodName) {
3328
3341
  return `${nativeAmount2 / scale}.${(nativeAmount2 % scale).toString().padStart(decimals, "0")}`;
3329
3342
  }
3330
3343
 
3344
+ // src/sdk/server/cashu-payment-request.ts
3345
+ import { createHash as createHash4 } from "crypto";
3346
+ import { PaymentRequest } from "@cashu/cashu-ts";
3347
+ function cashuPaymentRequest(args) {
3348
+ return new PaymentRequest(
3349
+ void 0,
3350
+ void 0,
3351
+ Math.ceil(args.requiredMsats / 1e3),
3352
+ "sat",
3353
+ args.mints,
3354
+ void 0,
3355
+ false,
3356
+ { kind: "P2PK", data: args.lockPubkey, tags: [] }
3357
+ ).toEncodedCreqA();
3358
+ }
3359
+ function proofSetRequestId(secrets) {
3360
+ const canonical = JSON.stringify([...secrets].sort());
3361
+ return `${DERIVED_REQUEST_ID_PREFIX}${createHash4("sha256").update(canonical).digest("hex")}`;
3362
+ }
3363
+ var DERIVED_REQUEST_ID_PREFIX = "nut24-";
3364
+ function withFxHistory(inner, opts = {}) {
3365
+ const holdWindowMs = opts.holdWindowMs ?? FX_HOLD_WINDOW_MS;
3366
+ const now = opts.now ?? Date.now;
3367
+ const history = [];
3368
+ return {
3369
+ async fetch() {
3370
+ const snapshot = await inner.fetch();
3371
+ const latest = history.at(-1);
3372
+ if (latest && sameRates(latest.snapshot, snapshot)) {
3373
+ latest.lastPricedAt = now();
3374
+ } else {
3375
+ history.push({ snapshot, lastPricedAt: now() });
3376
+ if (history.length > 4) history.shift();
3377
+ }
3378
+ return snapshot;
3379
+ },
3380
+ supportedCurrencies: () => inner.supportedCurrencies(),
3381
+ ...inner.lastKnown && { lastKnown: inner.lastKnown.bind(inner) },
3382
+ previousFor(pricedAt) {
3383
+ for (let i = history.length - 1; i > 0; i--) {
3384
+ if (!sameRates(history[i].snapshot, pricedAt)) continue;
3385
+ const replaced = history[i - 1];
3386
+ return now() - replaced.lastPricedAt <= holdWindowMs ? replaced.snapshot : void 0;
3387
+ }
3388
+ return void 0;
3389
+ }
3390
+ };
3391
+ }
3392
+ function sameRates(a, b) {
3393
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a.rates), ...Object.keys(b.rates)]);
3394
+ for (const key of keys) {
3395
+ if (a.rates[key] !== b.rates[key]) return false;
3396
+ }
3397
+ return true;
3398
+ }
3399
+ var FX_HOLD_WINDOW_MS = 10 * 6e4;
3400
+
3331
3401
  // src/lib/mints.ts
3332
3402
  function isTestMintUrl(url) {
3333
3403
  let host;
@@ -3459,13 +3529,20 @@ async function acceptAccumulatorPayment(opts) {
3459
3529
  if (opts.disposable && !opts.mints.every(isTestMintUrl)) {
3460
3530
  throw new Error("Disposable Cashu accepts only known test or loopback mints.");
3461
3531
  }
3462
- if (!opts.requestId) {
3532
+ if (!opts.requestId && !opts.deriveRequestId) {
3463
3533
  return {
3464
3534
  kind: "rejected",
3465
3535
  code: "missing_request_id",
3466
3536
  message: "X-Cashu-Request-Id header required for the agent-wallet path."
3467
3537
  };
3468
3538
  }
3539
+ if (opts.requestId?.startsWith(DERIVED_REQUEST_ID_PREFIX)) {
3540
+ return {
3541
+ kind: "rejected",
3542
+ code: "reserved_request_id",
3543
+ message: `A Cashu request id may not start with "${DERIVED_REQUEST_ID_PREFIX}".`
3544
+ };
3545
+ }
3469
3546
  if (opts.lockPubkeys.length === 0) {
3470
3547
  return {
3471
3548
  kind: "rejected",
@@ -3486,6 +3563,9 @@ async function acceptAccumulatorPayment(opts) {
3486
3563
  if (meta.unit !== "sat" || proofs.length === 0) {
3487
3564
  throw new Error("Cashu payment requires nonempty sat proofs.");
3488
3565
  }
3566
+ if (new Set(proofs.map((proof) => proof.secret)).size !== proofs.length) {
3567
+ throw new Error("Cashu payment carries the same proof more than once.");
3568
+ }
3489
3569
  for (const proof of proofs) {
3490
3570
  if (!proof.dleq || !verifyDleqIfPresent(proof, wallet.getKeyset(proof.id))) {
3491
3571
  throw new Error(
@@ -3501,10 +3581,12 @@ async function acceptAccumulatorPayment(opts) {
3501
3581
  message: err instanceof Error ? err.message : "Cashu token decode failed"
3502
3582
  };
3503
3583
  }
3584
+ const requestId = opts.requestId || proofSetRequestId(proofs.map((proof) => proof.secret));
3504
3585
  const acceptedValue = {
3505
3586
  ...valuation,
3506
3587
  mintUrl,
3507
- paidMsats: valuation.netMsats
3588
+ paidMsats: valuation.netMsats,
3589
+ requestId
3508
3590
  };
3509
3591
  const nowSec = Math.floor(Date.now() / 1e3);
3510
3592
  const tExpireMsByIndex = [];
@@ -3521,9 +3603,8 @@ async function acceptAccumulatorPayment(opts) {
3521
3603
  tExpireMsByIndex.push(parseNut11LocktimeMs(p.secret));
3522
3604
  }
3523
3605
  if (belowFloorMessage) {
3524
- const secrets = proofs.map((p) => p.secret);
3525
- const banked = opts.disposable ? await opts.disposable.hasAccepted(secrets) : opts.db ? await hasAccumulatedProof(opts.db, opts.dvmId, secrets) : false;
3526
- if (banked) return replayRejected();
3606
+ const banked = await acceptedUnder(opts, proofs);
3607
+ if (banked.some((id) => id !== void 0)) return replayRejected(banked, requestId);
3527
3608
  return { kind: "rejected", code: "lock_not_exclusive", message: belowFloorMessage };
3528
3609
  }
3529
3610
  try {
@@ -3567,23 +3648,24 @@ async function acceptAccumulatorPayment(opts) {
3567
3648
  lockPubkeyByIndex: matchedLockPubkeyByIndex,
3568
3649
  mintUrl,
3569
3650
  proofs,
3570
- requestId: opts.requestId,
3651
+ requestId,
3571
3652
  tExpireMsByIndex
3572
3653
  };
3573
3654
  try {
3574
3655
  if (opts.disposable) {
3575
3656
  const account = opts.withDisposableLedger;
3576
3657
  if (!account) throw new Error("Disposable Cashu requires atomic credit accounting.");
3577
- await opts.disposable.commit(opts.requestId, proofs, (ledger) => account(ledger, acceptedValue));
3658
+ await opts.disposable.commit(requestId, proofs, (ledger) => account(ledger, acceptedValue));
3578
3659
  } else if (!opts.db) {
3579
3660
  throw new Error("Cashu accumulator requires a storage backend.");
3580
- } else if (opts.withCommitTx) {
3581
- const withCommitTx = opts.withCommitTx;
3661
+ } else {
3582
3662
  const client = await opts.db.connect();
3583
3663
  try {
3584
3664
  await client.query("BEGIN");
3665
+ await client.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED");
3666
+ await claimAccumulatorRequestId(client, opts.dvmId, requestId);
3585
3667
  await insertAccumulatorRows(opts.db, { ...insertArgs, tx: client });
3586
- await withCommitTx(client, acceptedValue);
3668
+ await opts.withCommitTx?.(client, acceptedValue);
3587
3669
  await client.query("COMMIT");
3588
3670
  } catch (err) {
3589
3671
  try {
@@ -3594,11 +3676,13 @@ async function acceptAccumulatorPayment(opts) {
3594
3676
  } finally {
3595
3677
  client.release();
3596
3678
  }
3597
- } else {
3598
- await insertAccumulatorRows(opts.db, insertArgs);
3599
3679
  }
3600
3680
  } catch (err) {
3601
- if (err instanceof AccumulatorReplayError) return replayRejected();
3681
+ if (err instanceof AccumulatorReplayError) {
3682
+ const banked = await acceptedUnder(opts, proofs);
3683
+ if (banked.every((id) => id === void 0)) return requestIdReused(requestId);
3684
+ return replayRejected(banked, requestId);
3685
+ }
3602
3686
  throw err;
3603
3687
  }
3604
3688
  return {
@@ -3607,14 +3691,33 @@ async function acceptAccumulatorPayment(opts) {
3607
3691
  grossMsats: valuation.grossMsats,
3608
3692
  inputFeeMsats: valuation.inputFeeMsats,
3609
3693
  netMsats: valuation.netMsats,
3610
- mintUrl
3694
+ mintUrl,
3695
+ requestId
3611
3696
  };
3612
3697
  }
3613
- function replayRejected() {
3698
+ async function acceptedUnder(opts, proofs) {
3699
+ const secrets = proofs.map((p) => p.secret);
3700
+ if (opts.disposable) return await opts.disposable.acceptedRequestIds(secrets);
3701
+ if (opts.db) return await acceptedRequestIds(opts.db, opts.dvmId, secrets);
3702
+ return secrets.map(() => void 0);
3703
+ }
3704
+ function requestIdReused(requestId) {
3705
+ return {
3706
+ kind: "rejected",
3707
+ code: "request_id_reused",
3708
+ requestId,
3709
+ message: "This request id already carries a different payment at this DVM; these proofs were not accepted."
3710
+ };
3711
+ }
3712
+ function replayRejected(accepted, requestId) {
3713
+ const [first] = accepted;
3714
+ const bound = first !== void 0 && accepted.every((id) => id === first);
3614
3715
  return {
3615
3716
  kind: "rejected",
3616
3717
  code: "replay_rejected",
3617
- message: "Proofs or request id already accepted at this DVM."
3718
+ message: "Proofs or request id already accepted at this DVM.",
3719
+ requestId,
3720
+ ...bound && { acceptedRequestId: first }
3618
3721
  };
3619
3722
  }
3620
3723
  function parseNut11LocktimeMs(secret) {
@@ -3850,7 +3953,7 @@ async function verifyUpfrontPaymentInScope(opts) {
3850
3953
  }
3851
3954
  };
3852
3955
  }
3853
- if (opts.cashuMode === "p2pk-accumulator" && (opts.accumulatorDb || opts.disposableCashu) && opts.dvmId && opts.lockPubkeys && opts.lockPubkeys.length > 0) {
3956
+ if (cashuAccumulatorReady(opts)) {
3854
3957
  return verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx);
3855
3958
  }
3856
3959
  return {
@@ -4014,7 +4117,7 @@ async function verifyUpfrontPaymentInScope(opts) {
4014
4117
  x402Requirements
4015
4118
  });
4016
4119
  }
4017
- const { verifyX402Payment } = await import("./x402-6T73AZQ6.js");
4120
+ const { verifyX402Payment } = await import("./x402-HVCUEWWU.js");
4018
4121
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4019
4122
  x402Payment,
4020
4123
  x402Config,
@@ -4469,6 +4572,8 @@ async function verifyUpfrontPaymentInScope(opts) {
4469
4572
  opts.fundOnly?.allowOneShotStablecoin,
4470
4573
  opts.priceFiat
4471
4574
  ) : void 0;
4575
+ const lockPubkey = opts.lockPubkeys?.[0];
4576
+ const cashuRequest = opts.nut24 && opts.nut24.mints.length > 0 && !opts.fundOnly && cashuAccumulatorReady(opts) && lockPubkey ? cashuPaymentRequest({ requiredMsats, mints: opts.nut24.mints, lockPubkey }) : void 0;
4472
4577
  return {
4473
4578
  paidMsats: 0,
4474
4579
  receivedProofs: [],
@@ -4479,6 +4584,7 @@ async function verifyUpfrontPaymentInScope(opts) {
4479
4584
  ...mints && { mints }
4480
4585
  },
4481
4586
  status: 402,
4587
+ ...cashuRequest && { cashuPaymentRequest: cashuRequest },
4482
4588
  ...mppChallenges && { mppChallenges },
4483
4589
  ...x402Requirements && { x402Requirements },
4484
4590
  ...x402BatchRequirements && { x402V2Requirements: x402BatchRequirements }
@@ -4612,6 +4718,11 @@ var PAYMENT_ERROR_COPY = {
4612
4718
  hint: "Request a fresh quote and pay again \u2014 the previous payment can't be reused.",
4613
4719
  retryable: false
4614
4720
  },
4721
+ payment_method_requires_signature: {
4722
+ display: "This service accepts unsigned requests only when they are paid with Cashu, so this payment was not applied to the job.",
4723
+ hint: "Pay with Cashu to call without signing, or sign the request (secp256k1 Schnorr) to use another payment method.",
4724
+ retryable: false
4725
+ },
4615
4726
  x402_not_offered: {
4616
4727
  display: "This service does not offer x402 payment for prices in its configured currency. Nothing was charged.",
4617
4728
  hint: "Use one of the payment methods advertised by this service and submit the request again.",
@@ -4837,6 +4948,11 @@ var PAYMENT_ERROR_COPY = {
4837
4948
  hint: "Include the X-Cashu-Request-Id header and pay again.",
4838
4949
  retryable: false
4839
4950
  },
4951
+ reserved_request_id: {
4952
+ display: "The payment's request identifier isn't allowed, so the payment wasn't processed. Nothing was charged.",
4953
+ hint: 'Send a fresh UUIDv4 as X-Cashu-Request-Id. Ids starting with "nut24-" are reserved for payments sent without one.',
4954
+ retryable: false
4955
+ },
4840
4956
  missing_lock_pubkey: {
4841
4957
  display: "This service can't accept the payment right now. Nothing was charged.",
4842
4958
  hint: "The service is missing payment configuration; retrying won't help. Contact the service operator.",
@@ -4903,6 +5019,11 @@ var PAYMENT_ERROR_COPY = {
4903
5019
  // agent could follow straight into paying twice for the same charge.
4904
5020
  hint: "Don't re-send this payment \u2014 the proofs already landed under this request id. Only a genuinely new charge needs a fresh token and request id.",
4905
5021
  retryable: false
5022
+ },
5023
+ request_id_reused: {
5024
+ display: "This payment was not accepted and nothing was charged.",
5025
+ hint: "Each X-Cashu-Request-Id belongs to the first payment made under it. Send these proofs again under a new request id.",
5026
+ retryable: false
4906
5027
  }
4907
5028
  };
4908
5029
  function paymentErrorBody(code, opts) {
@@ -5044,7 +5165,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
5044
5165
  snapshot
5045
5166
  });
5046
5167
  }
5047
- const { verifyX402Payment } = await import("./x402-6T73AZQ6.js");
5168
+ const { verifyX402Payment } = await import("./x402-HVCUEWWU.js");
5048
5169
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5049
5170
  body.content.x402_payment,
5050
5171
  opts.x402Config,
@@ -5495,7 +5616,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
5495
5616
  try {
5496
5617
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5497
5618
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5498
- const { verifyX402Payment } = await import("./x402-6T73AZQ6.js");
5619
+ const { verifyX402Payment } = await import("./x402-HVCUEWWU.js");
5499
5620
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5500
5621
  body.content.x402_payment,
5501
5622
  opts.x402Config,
@@ -5620,6 +5741,16 @@ function creditPendingPayment(job) {
5620
5741
  job.pendingPaymentMsats = void 0;
5621
5742
  }
5622
5743
  }
5744
+ function cashuAccumulatorReady(opts) {
5745
+ return opts.cashuMode === "p2pk-accumulator" && Boolean(opts.accumulatorDb || opts.disposableCashu) && Boolean(opts.dvmId) && (opts.lockPubkeys?.length ?? 0) > 0;
5746
+ }
5747
+ var NUT24_PAYER_FIXABLE = /* @__PURE__ */ new Set([
5748
+ "invalid_token",
5749
+ "not_p2pk_locked",
5750
+ "wrong_lock_pubkey",
5751
+ "lock_not_exclusive",
5752
+ "payment_insufficient"
5753
+ ]);
5623
5754
  async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
5624
5755
  const { cashuToken, accumulatorDb, dvmId, lockPubkeys, mintHealthTracker } = opts;
5625
5756
  if (!cashuToken || !accumulatorDb && !opts.disposableCashu || !dvmId || !lockPubkeys || lockPubkeys.length === 0) {
@@ -5668,7 +5799,13 @@ async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
5668
5799
  };
5669
5800
  }
5670
5801
  let credited;
5671
- const requestId = opts.requestId;
5802
+ const headerless = !opts.requestId && opts.nut24 !== void 0;
5803
+ const previousRequired = headerless ? opts.nut24?.previousRequiredMsats : void 0;
5804
+ const requiredFor = (paidMsats) => requiredMsats !== void 0 && paidMsats < requiredMsats && previousRequired !== void 0 && paidMsats >= previousRequired ? previousRequired : requiredMsats;
5805
+ const held = (ctx, paidMsats) => ({
5806
+ ...ctx,
5807
+ requiredMsats: requiredFor(paidMsats) ?? ctx.requiredMsats
5808
+ });
5672
5809
  const signedFund = ledgerCtx?.creditEnvelope?.fund;
5673
5810
  const beforeCommit = ledgerCtx && signedFund ? (accepted) => {
5674
5811
  const expected = fundedMicroFor(
@@ -5680,12 +5817,12 @@ async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
5680
5817
  throw new CashuFundingAmountMismatchError(signedFund.amountMicro, expected);
5681
5818
  }
5682
5819
  } : void 0;
5683
- const withCommitTx = ledgerCtx && requestId ? async (tx, accepted) => {
5820
+ const withCommitTx = ledgerCtx ? async (tx, accepted) => {
5684
5821
  credited = await fundAndDraw(
5685
- ledgerCtx,
5822
+ held(ledgerCtx, accepted.paidMsats),
5686
5823
  {
5687
5824
  rail: "cashu",
5688
- paymentId: requestId,
5825
+ paymentId: accepted.requestId,
5689
5826
  paidMsats: accepted.paidMsats,
5690
5827
  grossPaidMsats: accepted.grossMsats,
5691
5828
  cashuInputFeeMsats: accepted.inputFeeMsats,
@@ -5699,13 +5836,14 @@ async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
5699
5836
  result = await acceptAccumulatorPayment({
5700
5837
  cashuToken,
5701
5838
  requestId: opts.requestId,
5839
+ deriveRequestId: headerless,
5702
5840
  mints,
5703
5841
  db: accumulatorDb,
5704
5842
  disposable: opts.disposableCashu,
5705
- withDisposableLedger: ledgerCtx && requestId ? async (ledger, accepted) => {
5706
- credited = await fundAndDraw({ ...ledgerCtx, ledger }, {
5843
+ withDisposableLedger: ledgerCtx ? async (ledger, accepted) => {
5844
+ credited = await fundAndDraw({ ...held(ledgerCtx, accepted.paidMsats), ledger }, {
5707
5845
  rail: "cashu",
5708
- paymentId: requestId,
5846
+ paymentId: accepted.requestId,
5709
5847
  paidMsats: accepted.paidMsats,
5710
5848
  grossPaidMsats: accepted.grossMsats,
5711
5849
  cashuInputFeeMsats: accepted.inputFeeMsats,
@@ -5729,24 +5867,35 @@ async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
5729
5867
  throw err;
5730
5868
  }
5731
5869
  if (result.kind === "rejected") {
5732
- const status = result.code === "replay_rejected" ? 409 : 402;
5870
+ const status = result.code === "replay_rejected" || result.code === "request_id_reused" ? 409 : result.code === "reserved_request_id" || headerless && NUT24_PAYER_FIXABLE.has(result.code) ? 400 : 402;
5871
+ const requestId = result.requestId;
5733
5872
  return {
5734
5873
  paidMsats: 0,
5735
5874
  receivedProofs: [],
5736
5875
  error: {
5737
- body: paymentErrorBody(result.code, { message: result.message, extra: { mints } }),
5876
+ body: paymentErrorBody(result.code, {
5877
+ message: result.message,
5878
+ // Without a request id the proof set is the payment's identity, so
5879
+ // only these exact proofs with the original body recover its job.
5880
+ ...headerless && result.code === "replay_rejected" && {
5881
+ hint: "These proofs already paid for an earlier request. Resend them with that request's exact body to fetch its job; a new request needs new proofs."
5882
+ },
5883
+ extra: { mints }
5884
+ }),
5738
5885
  status,
5739
- // The accumulator keys replay on the request id, which is also the
5740
- // funding id a mid-job top-up committed under, so the route can hand
5741
- // back the original draw funding evidence beside the 409.
5742
- ...status === 409 && requestId && { replayPaymentId: requestId }
5886
+ // The request id is also the funding id a mid-job top-up committed
5887
+ // under and the job's payment reference, so the route can return the
5888
+ // original evidence beside the 409. It is passed on only when every
5889
+ // presented proof was accepted under this same request id.
5890
+ ...requestId && result.acceptedRequestId === requestId && { replayPaymentId: requestId }
5743
5891
  }
5744
5892
  };
5745
5893
  }
5746
5894
  if (credited?.drawRefusal) {
5747
5895
  return creditLedgerErrorResult(credited.drawRefusal, void 0, credited.deposit);
5748
5896
  }
5749
- if (requiredMsats && result.paidMsats < requiredMsats && !ledgerCtx?.fundOnly && !ledgerCtx?.creditEnvelope) {
5897
+ const heldRequired = requiredFor(result.paidMsats);
5898
+ if (heldRequired && result.paidMsats < heldRequired && !ledgerCtx?.fundOnly && !ledgerCtx?.creditEnvelope) {
5750
5899
  let forfeit;
5751
5900
  if (credited?.draw && ledgerCtx && !ledgerCtx.midJob) {
5752
5901
  const drawId = credited.draw.drawId;
@@ -5768,36 +5917,39 @@ async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
5768
5917
  });
5769
5918
  }
5770
5919
  }
5771
- const held = ledgerCtx?.midJob ? credited : void 0;
5920
+ const held2 = ledgerCtx?.midJob ? credited : void 0;
5772
5921
  return {
5773
5922
  paidMsats: 0,
5774
5923
  receivedProofs: [],
5775
5924
  error: {
5776
5925
  body: paymentErrorBody("payment_insufficient", {
5777
5926
  message: `Payment of ${result.paidMsats} msats is short of the ${requiredMsats} msats required.`,
5778
- ...held && {
5927
+ ...held2 && {
5779
5928
  // Per-code copy can't say where the money went, and this is the one
5780
5929
  // site where that is the caller's next action.
5781
- hint: `The ${result.paidMsats} msats already sent are held on credit ${held.creditId} and do not reduce what is owed \u2014 retry with a single payment of required_msats, or reclaim the held balance with op "drain" at POST /v1/credit.`
5930
+ hint: `The ${result.paidMsats} msats already sent are held on credit ${held2.creditId} and do not reduce what is owed \u2014 retry with a single payment of required_msats, or reclaim the held balance with op "drain" at POST /v1/credit.`
5931
+ },
5932
+ ...!held2 && headerless && {
5933
+ hint: "The proofs already sent are kept and do not count toward this request. Pay again with new proofs worth required_msats after the mint's input fee."
5782
5934
  },
5783
5935
  extra: {
5784
5936
  required_msats: requiredMsats,
5785
5937
  received_msats: result.paidMsats,
5786
5938
  mints,
5787
- ...held && {
5788
- credit_id: held.creditId,
5789
- credited_micro: held.deposit.amountMicro,
5790
- credit_currency: held.deposit.currency
5939
+ ...held2 && {
5940
+ credit_id: held2.creditId,
5941
+ credited_micro: held2.deposit.amountMicro,
5942
+ credit_currency: held2.deposit.currency
5791
5943
  }
5792
5944
  }
5793
5945
  }),
5794
- status: 402,
5946
+ status: headerless ? 400 : 402,
5795
5947
  forfeit,
5796
5948
  // Report the deposit on the failure path too, or the platform never
5797
5949
  // learns about a liability it is carrying. Mutually exclusive with
5798
5950
  // `forfeit` by construction: one is money still owed, the other is
5799
5951
  // money nothing is owed against.
5800
- ...held && { funding: held.deposit }
5952
+ ...held2 && { funding: held2.deposit }
5801
5953
  }
5802
5954
  };
5803
5955
  }
@@ -5807,13 +5959,11 @@ async function verifyAccumulatorReceipt(opts, mints, requiredMsats, ledgerCtx) {
5807
5959
  paymentMint: result.mintUrl,
5808
5960
  paymentRail: "cashu",
5809
5961
  receivedProofs: [],
5810
- // requestId is non-null here because acceptAccumulatorPayment returns
5811
- // kind:"rejected" with code "missing_request_id" when unset (handled by
5812
- // the rejected branch above). Thread it as the per-call settlement
5813
- // reference for revenue_events.tx_hash. Unique per
5814
- // (dvm_id, request_id) by the wallet_accumulator UNIQUE constraint, so
5815
- // it serves as a stable id even though it isn't an on-chain hash.
5816
- paymentTxHash: opts.requestId,
5962
+ // The request id the proofs were banked under, supplied or derived, is
5963
+ // the per-call settlement reference for revenue_events.tx_hash. Unique
5964
+ // per (dvm_id, request_id) by the wallet_accumulator UNIQUE constraint,
5965
+ // so it serves as a stable id even though it isn't an on-chain hash.
5966
+ paymentTxHash: result.requestId,
5817
5967
  cashuFlow: "p2pk_accumulator"
5818
5968
  },
5819
5969
  ledgerCtx,
@@ -5889,7 +6039,7 @@ async function resolveTopUpLedgerContext(opts, snapshot, binding) {
5889
6039
  draw_id: snapshot.drawId,
5890
6040
  ...extra,
5891
6041
  ...typeof opts.jobId === "string" && {
5892
- job_id_hash: createHash4("sha256").update(opts.jobId, "utf8").digest("hex")
6042
+ job_id_hash: createHash5("sha256").update(opts.jobId, "utf8").digest("hex")
5893
6043
  }
5894
6044
  }
5895
6045
  );
@@ -9592,7 +9742,7 @@ async function handle(c, opts, action) {
9592
9742
  }
9593
9743
 
9594
9744
  // src/sdk/server/x402-exact-settlement.ts
9595
- import { createHash as createHash5 } from "crypto";
9745
+ import { createHash as createHash6 } from "crypto";
9596
9746
  var X402ExactSettlementServer = class {
9597
9747
  constructor(opts) {
9598
9748
  this.opts = opts;
@@ -9918,7 +10068,7 @@ function settlementMayHaveMoved(response) {
9918
10068
  return reason.includes("authorization_already_used") || reason.includes("authorization already used") || /facilitator_status_5\d\d/.test(reason);
9919
10069
  }
9920
10070
  function hash(parts) {
9921
- return createHash5("sha256").update(JSON.stringify(parts)).digest("hex");
10071
+ return createHash6("sha256").update(JSON.stringify(parts)).digest("hex");
9922
10072
  }
9923
10073
  function safeError(err) {
9924
10074
  if (err instanceof Error) return err.name;
@@ -9926,7 +10076,7 @@ function safeError(err) {
9926
10076
  }
9927
10077
 
9928
10078
  // src/sdk/server/hosting-settlement-report-obligations.ts
9929
- import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
10079
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
9930
10080
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
9931
10081
  import { BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS3 } from "@x402/evm";
9932
10082
  import { computeChannelId as computeChannelId2 } from "@x402/evm/batch-settlement/client";
@@ -9999,7 +10149,7 @@ var TEMPO_CLOSED_EVENT = TempoSession.Precompile.escrowAbi.find(
9999
10149
  (item) => item.type === "event" && item.name === "ChannelClosed"
10000
10150
  );
10001
10151
  function hash2(value) {
10002
- return createHash6("sha256").update(JSON.stringify(value)).digest("hex");
10152
+ return createHash7("sha256").update(JSON.stringify(value)).digest("hex");
10003
10153
  }
10004
10154
  function hex(value, bytes) {
10005
10155
  return typeof value === "string" && new RegExp(`^0x[0-9a-fA-F]{${bytes * 2}}$`).test(value);
@@ -11387,7 +11537,7 @@ var HostingCleanupFence = class {
11387
11537
  };
11388
11538
 
11389
11539
  // src/sdk/server/hosting-channel-evidence.ts
11390
- import { createHash as createHash7 } from "crypto";
11540
+ import { createHash as createHash8 } from "crypto";
11391
11541
  import { Session as TempoSession2 } from "mppx/tempo";
11392
11542
  import { createPublicClient as createPublicClient5, http as http5 } from "viem";
11393
11543
  import { BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS4 } from "@x402/evm";
@@ -11438,7 +11588,7 @@ async function readHostedChannelSnapshot(query) {
11438
11588
  };
11439
11589
  const x402HistoryComplete = await history(names[6]);
11440
11590
  const tempoHistoryComplete = await history(names[7]);
11441
- const digest = createHash7("sha256").update(JSON.stringify({
11591
+ const digest = createHash8("sha256").update(JSON.stringify({
11442
11592
  present: [...exists].sort(),
11443
11593
  x402,
11444
11594
  x402Tombstones,
@@ -12588,9 +12738,9 @@ import { AsyncLocalStorage } from "async_hooks";
12588
12738
  import { randomBytes as randomBytes3 } from "crypto";
12589
12739
 
12590
12740
  // src/sdk/server/instrumented-kv.ts
12591
- import { createHash as createHash8 } from "crypto";
12741
+ import { createHash as createHash9 } from "crypto";
12592
12742
  function hashKey(key) {
12593
- return createHash8("sha256").update(key).digest("hex").slice(0, 16);
12743
+ return createHash9("sha256").update(key).digest("hex").slice(0, 16);
12594
12744
  }
12595
12745
  function instrumentKVStore(inner) {
12596
12746
  return {
@@ -12904,7 +13054,7 @@ function buildContext(job, opts) {
12904
13054
  let x402Nonce;
12905
13055
  let x402AmountUsdcMicro;
12906
13056
  const hasExplicitX402 = Boolean(payOpts?.x402);
12907
- if ((opts.pricingCurrency ?? "usd") === "usd") {
13057
+ if (!opts.cashuOnly && (opts.pricingCurrency ?? "usd") === "usd") {
12908
13058
  if (payOpts?.x402) {
12909
13059
  paymentRequestContent.x402 = payOpts.x402;
12910
13060
  } else if (opts.paymentMethods?.includes("x402") && opts.x402 && (opts.x402FacilitatorHealth?.support().available ?? true)) {
@@ -12943,7 +13093,8 @@ function buildContext(job, opts) {
12943
13093
  job.pendingX402Nonce = x402Nonce;
12944
13094
  job.pendingX402AmountUsdcMicro = x402AmountUsdcMicro;
12945
13095
  let mppChallengeIds;
12946
- if (payOpts?.mpp) {
13096
+ if (opts.cashuOnly) {
13097
+ } else if (payOpts?.mpp) {
12947
13098
  const challenges = payOpts.mpp.challenges.filter((c) => c.intent !== "session");
12948
13099
  if (challenges.length > 0) {
12949
13100
  paymentRequestContent.tempo = { challenges };
@@ -13351,6 +13502,39 @@ function resultHashFor(record) {
13351
13502
  return computeResultHash(summary, artifacts);
13352
13503
  }
13353
13504
 
13505
+ // src/sdk/server/unsigned-request.ts
13506
+ function takesUnsignedPath(auth, body) {
13507
+ if (!auth?.allowUnsignedPaid) return false;
13508
+ const raw = signedRequestInput(body);
13509
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return true;
13510
+ return !SIGNED_ENVELOPE_FIELDS.some((field) => field in raw);
13511
+ }
13512
+ function carriesCreditFields(raw) {
13513
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return false;
13514
+ return CREDIT_ENVELOPE_KEYS.some((field) => field in raw);
13515
+ }
13516
+ function unsignedInputSchema(schema) {
13517
+ if (narrowed.has(schema)) return narrowed.get(schema);
13518
+ const result = narrow(schema);
13519
+ narrowed.set(schema, result);
13520
+ return result;
13521
+ }
13522
+ var narrowed = /* @__PURE__ */ new WeakMap();
13523
+ function narrow(schema) {
13524
+ const candidate = schema;
13525
+ const def = candidate._zod?.def;
13526
+ if (def?.type !== "object" || typeof candidate.clone !== "function" || candidate.shape === null || typeof candidate.shape !== "object") {
13527
+ return void 0;
13528
+ }
13529
+ const shape = { ...candidate.shape };
13530
+ for (const field of SIGNED_ENVELOPE_FIELDS) delete shape[field];
13531
+ try {
13532
+ return candidate.clone({ ...def, shape });
13533
+ } catch {
13534
+ return void 0;
13535
+ }
13536
+ }
13537
+
13354
13538
  // src/lib/cashu/mint-info.ts
13355
13539
  async function getKeysetFinalExpiry(mintUrl, keysetId) {
13356
13540
  try {
@@ -13814,11 +13998,12 @@ var JobManager = class {
13814
13998
  * `"$X.XX"` is parsed as USD and converted via the SDK's shared fx fetcher.
13815
13999
  * Returns `undefined` for dynamic-priced capabilities (those that declare
13816
14000
  * `onQuote`) — the caller falls back to `computeDynamicPrice` in that path.
14001
+ * A caller that already holds the snapshot it prices at passes it as `fx`.
13817
14002
  */
13818
- async currentPriceMsats(capability) {
14003
+ async currentPriceMsats(capability, fx) {
13819
14004
  const cap = this.requireCapability(capability);
13820
14005
  if (cap.price === void 0) return void 0;
13821
- return resolveStaticPriceMsats(cap.price, this.opts.fxFetcher);
14006
+ return resolveStaticPriceMsats(cap.price, fx ?? await this.opts.fxFetcher.fetch());
13822
14007
  }
13823
14008
  /**
13824
14009
  * Translate a capability's static `price` into the fiat envelope used by
@@ -13851,12 +14036,16 @@ var JobManager = class {
13851
14036
  * explicit draw as an unknown key, before the envelope was even extracted.
13852
14037
  * The persisted `record.input` the reactivation path re-reads is the
13853
14038
  * pre-Zod wire form, so it comes through the second branch carrying them too.
14039
+ *
14040
+ * `unsigned` validates against the schema without its envelope fields; pass
14041
+ * it only when {@link unsignedPaidInput} accepted the same body.
13854
14042
  */
13855
- parseInput(body, capability) {
14043
+ parseInput(body, capability, unsigned2 = false) {
13856
14044
  const cap = this.requireCapability(capability);
13857
14045
  const rawSchema = cap.input;
13858
14046
  if (!rawSchema || !isZodSchema(rawSchema)) return body.input ?? "";
13859
- const schema = rawSchema;
14047
+ const schema = unsigned2 ? unsignedInputSchema(rawSchema) : rawSchema;
14048
+ if (!schema) throw new Error(`capability "${capability}" cannot validate unsigned input`);
13860
14049
  if (body.data !== void 0) {
13861
14050
  return schema.parse(stripProtocolEnvelope(schema, body.data));
13862
14051
  }
@@ -13866,6 +14055,16 @@ var JobManager = class {
13866
14055
  }
13867
14056
  throw new MissingStructuredInputError();
13868
14057
  }
14058
+ /**
14059
+ * Whether this body takes the unsigned paid path: the auth scheme allows it,
14060
+ * the raw input carries no envelope field, and the capability schema can be
14061
+ * narrowed to exclude the envelope. Otherwise the signed path applies.
14062
+ */
14063
+ unsignedPaidInput(body, capability) {
14064
+ if (!takesUnsignedPath(this.descriptor.auth, body)) return false;
14065
+ const rawSchema = this.requireCapability(capability).input;
14066
+ return isZodSchema(rawSchema) && unsignedInputSchema(rawSchema) !== void 0;
14067
+ }
13869
14068
  /**
13870
14069
  * Look up the per-capability descriptor by name. Throws when the
13871
14070
  * name doesn't exist — the route layer validates body.capability first, so
@@ -15352,8 +15551,9 @@ var JobManager = class {
15352
15551
  * it re-verified clean.
15353
15552
  */
15354
15553
  reactivateJob(record) {
15355
- const parsedInput = this.parseInput({ input: record.input }, record.capability);
15356
- if (this.descriptor.auth) {
15554
+ const unsigned2 = record.authRequestPath === void 0 && record.requesterPubkey === void 0 && this.unsignedPaidInput({ input: record.input }, record.capability);
15555
+ const parsedInput = this.parseInput({ input: record.input }, record.capability, unsigned2);
15556
+ if (this.descriptor.auth && !unsigned2) {
15357
15557
  const cap = this.requireCapability(record.capability);
15358
15558
  if (!cap.input || !isZodSchema(cap.input)) {
15359
15559
  throw new ReactivationAuthSchemaError(record.id, record.capability);
@@ -15897,6 +16097,7 @@ var JobManager = class {
15897
16097
  store: this.opts.store,
15898
16098
  env: this.opts.env,
15899
16099
  paymentMethods: this.opts.paymentMethods,
16100
+ cashuOnly: Boolean(this.descriptor.auth?.allowUnsignedPaid) && job.requesterPubkey === void 0,
15900
16101
  x402: this.opts.x402,
15901
16102
  x402FacilitatorHealth: this.opts.x402FacilitatorHealth,
15902
16103
  mpp: this.opts.mpp,
@@ -16701,9 +16902,8 @@ function unappliedCopy(reason, creditId) {
16701
16902
  }
16702
16903
  }[reason];
16703
16904
  }
16704
- async function resolveStaticPriceMsats(price, fxFetcher) {
16905
+ function resolveStaticPriceMsats(price, fx) {
16705
16906
  const usd = parseUsdPrice(price, "price");
16706
- const fx = await fxFetcher.fetch();
16707
16907
  return fiatToSatsCeil(usd, fxRateFor(fx, "usd")) * 1e3;
16708
16908
  }
16709
16909
  function deriveAuthFromParsedInput(descriptor, parsedInput) {
@@ -17769,7 +17969,7 @@ function withWallClockDeadline(promise, timeoutMs) {
17769
17969
  }
17770
17970
 
17771
17971
  // src/sdk/server/balance-payment.ts
17772
- import { createHash as createHash9 } from "crypto";
17972
+ import { createHash as createHash10 } from "crypto";
17773
17973
  import { z as z2 } from "zod";
17774
17974
  var BALANCE_PAYMENT_SCHEMA = z2.object({
17775
17975
  credit_id: z2.string().min(1).max(256),
@@ -17799,7 +17999,7 @@ async function commitBalancePayment(args) {
17799
17999
  args.auth.recheckSignature(BALANCE_PAYMENT_SCHEMA, args.raw, args.domain);
17800
18000
  const auth = BALANCE_PAYMENT_SCHEMA.parse(args.raw);
17801
18001
  const { pubkey, credit_id, draw_id, payment_id, request_seq, amount_micro, currency } = auth;
17802
- const fingerprint = createHash9("sha256").update(JSON.stringify([
18002
+ const fingerprint = createHash10("sha256").update(JSON.stringify([
17803
18003
  args.domain,
17804
18004
  args.jobId,
17805
18005
  pubkey,
@@ -17923,7 +18123,7 @@ async function commitBalancePayment(args) {
17923
18123
  }
17924
18124
 
17925
18125
  // src/sdk/server/app.ts
17926
- import { createHash as createHash11, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "crypto";
18126
+ import { createHash as createHash12, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "crypto";
17927
18127
  import { Hono } from "hono";
17928
18128
  import { getCookie } from "hono/cookie";
17929
18129
  import { cors } from "hono/cors";
@@ -20974,7 +21174,7 @@ initSchemaForm();
20974
21174
  }
20975
21175
 
20976
21176
  // src/sdk/server/request-fingerprint.ts
20977
- import { createHash as createHash10 } from "crypto";
21177
+ import { createHash as createHash11 } from "crypto";
20978
21178
  function requestFingerprint(req, opts) {
20979
21179
  const canonical = canonicalize({
20980
21180
  capability: req.capability,
@@ -20982,7 +21182,7 @@ function requestFingerprint(req, opts) {
20982
21182
  params: req.params ?? {},
20983
21183
  data: opts.stripAuthEnvelope ? stripEnvelope(req.data) : req.data
20984
21184
  });
20985
- return createHash10("sha256").update(canonical).digest("hex");
21185
+ return createHash11("sha256").update(canonical).digest("hex");
20986
21186
  }
20987
21187
  var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["pubkey", "signature", "timestamp", "nonce", "auth_statement"]);
20988
21188
  function stripEnvelope(data) {
@@ -21105,7 +21305,7 @@ async function createDVMServer(descriptor, opts) {
21105
21305
  }
21106
21306
  }
21107
21307
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
21108
- const { RevenueReporter } = await import("./revenue-reporter-2P3PWJJM.js");
21308
+ const { RevenueReporter } = await import("./revenue-reporter-6XARUEG6.js");
21109
21309
  const reporter = new RevenueReporter(
21110
21310
  opts.db,
21111
21311
  platformUrl,
@@ -21115,7 +21315,7 @@ async function createDVMServer(descriptor, opts) {
21115
21315
  revenueReporter = reporter;
21116
21316
  await reporter.init();
21117
21317
  reporter.startRetryLoop();
21118
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-P3652TLG.js");
21318
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-5QAHCYIO.js");
21119
21319
  payoutReporter = new PayoutReporter({
21120
21320
  dvmId,
21121
21321
  db: opts.db,
@@ -21396,7 +21596,9 @@ var DVMServer = class {
21396
21596
  this.consumedCredentialStore = opts.consumedCredentialStore ?? new MemoryConsumedCredentialStore();
21397
21597
  this.adminCashuNonceStore = opts.adminCashuNonceStore ?? createAdminCashuNonceStore();
21398
21598
  const fxEnv = opts.env;
21399
- this.fxFetcher = opts.fxFetcher ?? createFxFetcher({ source: resolveFxSourceFromEnv(fxEnv) });
21599
+ this.fxFetcher = withFxHistory(
21600
+ opts.fxFetcher ?? createFxFetcher({ source: resolveFxSourceFromEnv(fxEnv) })
21601
+ );
21400
21602
  const fxCurrencies = this.fxFetcher.supportedCurrencies();
21401
21603
  if (!fxCurrencies.includes(this.pricingCurrency)) {
21402
21604
  throw new Error(
@@ -21505,6 +21707,15 @@ var DVMServer = class {
21505
21707
  this.app = new Hono();
21506
21708
  try {
21507
21709
  this.registerRoutes();
21710
+ if (opts.mints?.length && !this.resolvePaymentMethods().includes("cashu")) {
21711
+ diagnosticConsole.warn({
21712
+ event: "cashu_not_advertised",
21713
+ cashu_explicitly_excluded: opts.paymentMethods !== void 0 && !opts.paymentMethods.includes("cashu"),
21714
+ cashu_mode_missing: opts.cashuMode !== "p2pk-accumulator",
21715
+ cashu_lock_pubkey_missing: !opts.lockPubkey,
21716
+ cashu_store_missing: !opts.db && !opts.disposableCashu
21717
+ });
21718
+ }
21508
21719
  if (opts.hostingControlInstance) {
21509
21720
  this.hostingSweepTimer = setInterval(() => {
21510
21721
  void this.sweepHostingDeadline().catch((error) => {
@@ -21714,7 +21925,7 @@ var DVMServer = class {
21714
21925
  })
21715
21926
  );
21716
21927
  if (this.opts.devMode) {
21717
- this.app.use("*", cors());
21928
+ this.app.use("*", cors({ exposeHeaders: ["X-Cashu"] }));
21718
21929
  this.app.get("/_dev", (c) => c.html(devPageHtml(this.descriptor.name)));
21719
21930
  this.app.all("/_proxy/*", (c) => this.handleProxy(c));
21720
21931
  }
@@ -22028,7 +22239,7 @@ var DVMServer = class {
22028
22239
  const dvmId = this.opts.dvmId ?? `dev:${this.descriptor.name}`;
22029
22240
  return {
22030
22241
  dvm_id: dvmId,
22031
- builder_pubkey: createHash11("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
22242
+ builder_pubkey: createHash12("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
22032
22243
  };
22033
22244
  }
22034
22245
  throw new AuthAudienceError();
@@ -22126,7 +22337,7 @@ var DVMServer = class {
22126
22337
  const methods = this.resolvePaymentMethods().filter(
22127
22338
  (method) => method !== "tempo" || creditMpp
22128
22339
  );
22129
- if (this.x402CurrencySupported() && this.x402BatchAvailable() && !methods.includes("x402")) {
22340
+ if ((this.opts.paymentMethods === void 0 || this.opts.paymentMethods.includes("x402")) && this.x402CurrencySupported() && this.x402BatchAvailable() && !methods.includes("x402")) {
22130
22341
  methods.push("x402");
22131
22342
  }
22132
22343
  if (this.opts.lightningReceive?.available()) methods.push("lightning");
@@ -22149,7 +22360,9 @@ var DVMServer = class {
22149
22360
  * distinction, and a reason it can relay.
22150
22361
  */
22151
22362
  creditMppSplit() {
22152
- if (!this.opts.mpp) return { offered: [], withheld: [] };
22363
+ if (!this.opts.mpp || !this.resolvePaymentMethods().includes("tempo")) {
22364
+ return { offered: [], withheld: [] };
22365
+ }
22153
22366
  const configured = advertisedMethods(this.opts.mpp).filter(
22154
22367
  (method) => method.intent === "charge" || method.intent === "session"
22155
22368
  ).sort(
@@ -22381,7 +22594,7 @@ var DVMServer = class {
22381
22594
  const paymentAuthorization = ctx.req.header("Authorization");
22382
22595
  const mppArtifact = paymentAuthorization ? (await import("mppx")).Credential.extractPaymentScheme(paymentAuthorization) : void 0;
22383
22596
  const attachedPayment = ctx.req.header("X-Cashu") ?? ctx.req.header("PAYMENT-SIGNATURE") ?? ctx.req.header("X-PAYMENT") ?? mppArtifact;
22384
- const paymentArtifactHash = attachedPayment ? createHash11("sha256").update(attachedPayment).digest("hex") : void 0;
22597
+ const paymentArtifactHash = attachedPayment ? createHash12("sha256").update(attachedPayment).digest("hex") : void 0;
22385
22598
  hostingFundingArtifactHash = paymentArtifactHash;
22386
22599
  if (!await control.admit(admissionId, workId, {
22387
22600
  creditId,
@@ -22481,7 +22694,7 @@ var DVMServer = class {
22481
22694
  name: this.descriptor.name,
22482
22695
  description: this.descriptor.description || void 0,
22483
22696
  tags: this.descriptor.tags,
22484
- auth: this.descriptor.auth ? [this.descriptor.auth.id] : ["none"],
22697
+ auth: !this.descriptor.auth ? ["none"] : this.descriptor.auth.allowUnsignedPaid ? [this.descriptor.auth.id, "none"] : [this.descriptor.auth.id],
22485
22698
  ...this.authAudience ? { auth_audience: this.authAudience } : {},
22486
22699
  protocol_version: 1,
22487
22700
  sdk_sha: this.opts.env.GIT_SHA ?? "unknown",
@@ -22500,7 +22713,7 @@ var DVMServer = class {
22500
22713
  if (this.opts.mints) info.mints = this.advertisedMints();
22501
22714
  info.capabilities = this.renderCapabilities();
22502
22715
  info.payment = { methods: this.resolvePaymentMethods() };
22503
- if (this.x402CurrencySupported() && this.opts.x402 && this.x402Support()?.available) {
22716
+ if (info.payment.methods.includes("x402") && this.opts.x402 && this.x402Support()?.available) {
22504
22717
  info.x402 = {
22505
22718
  pay_to: this.opts.x402.payTo,
22506
22719
  network: this.opts.x402.network,
@@ -22510,7 +22723,7 @@ var DVMServer = class {
22510
22723
  dual_serve: this.opts.x402.dualServe ?? true
22511
22724
  };
22512
22725
  }
22513
- if (this.opts.mpp) {
22726
+ if (info.payment.methods.includes("tempo") && this.opts.mpp) {
22514
22727
  const configured = advertisedMethods(this.opts.mpp);
22515
22728
  const withheld = configured.flatMap((method) => {
22516
22729
  const reason = this.tempoSessionWithheldReason(method);
@@ -22885,6 +23098,48 @@ var DVMServer = class {
22885
23098
  400
22886
23099
  );
22887
23100
  }
23101
+ /**
23102
+ * Refuse an x402 or Tempo mid-job payment on an unsigned paid job before it
23103
+ * is recorded or verified: those rails settle into credit owned by a caller
23104
+ * pubkey, which the job lacks. Signed jobs always store their pubkey.
23105
+ */
23106
+ unsignedJobRailRefusal(job, body) {
23107
+ if (!this.descriptor.auth?.allowUnsignedPaid || job.requesterPubkey !== void 0) return void 0;
23108
+ if (body.type !== "payment" || !body.content.x402_payment && !body.content.tempo_credential) {
23109
+ return void 0;
23110
+ }
23111
+ return buildPaymentErrorResponse(
23112
+ paymentErrorBody("payment_method_requires_signature", {
23113
+ message: "Unsigned jobs may be paid only with Cashu; an x402 or Tempo payment was sent."
23114
+ }),
23115
+ void 0,
23116
+ void 0,
23117
+ 402
23118
+ );
23119
+ }
23120
+ /** Whether this DVM takes Cashu, the only rail an unsigned payer may use. */
23121
+ unsignedCashuOffered() {
23122
+ return (this.opts.mints?.length ?? 0) > 0 && this.resolvePaymentMethods().includes("cashu");
23123
+ }
23124
+ /**
23125
+ * The error the signed path raises for this envelope-free body, so a refused
23126
+ * unsigned request reads exactly as it does on a DVM without the option.
23127
+ */
23128
+ unsignedRequestRefusal(c, schema, body, capabilityName) {
23129
+ try {
23130
+ if (this.descriptor.auth && isZodSchema(schema)) {
23131
+ this.descriptor.auth.checkAuth(
23132
+ schema,
23133
+ signedRequestInput(body),
23134
+ this.signedRequestDomain(c, capabilityName)
23135
+ );
23136
+ }
23137
+ } catch (err) {
23138
+ if (err instanceof SignedRequestError) return err;
23139
+ throw err;
23140
+ }
23141
+ return new SignedRequestError("signed request required", "signature_invalid");
23142
+ }
22888
23143
  /**
22889
23144
  * On authenticated DVMs, envelope-only parse failures defer to auth
22890
23145
  * (401 signature_invalid). Builder input issues remain 400 invalid_input.
@@ -22914,6 +23169,25 @@ var DVMServer = class {
22914
23169
  if (healthy.length > 0) return healthy;
22915
23170
  return all.filter((m) => !tracker.isNutIncompatible(m));
22916
23171
  }
23172
+ /**
23173
+ * This request's price at the rates `pricedAt` replaced, or `undefined` when
23174
+ * there are none or they were replaced longer ago than the hold window.
23175
+ * Computed the way the current price is, so the figure matches the one an
23176
+ * earlier 402 quoted.
23177
+ */
23178
+ previousPriceMsats(cap, dynamicUpfront, pricedAt) {
23179
+ const previous = pricedAt && this.fxFetcher.previousFor(pricedAt);
23180
+ if (!previous) return void 0;
23181
+ try {
23182
+ if (dynamicUpfront) {
23183
+ return fiatToSatsCeil(dynamicUpfront.amount, fxRateFor(previous, dynamicUpfront.currency)) * 1e3;
23184
+ }
23185
+ if (cap.price === void 0) return void 0;
23186
+ return fiatToSatsCeil(parseUsdPrice(cap.price, "price"), fxRateFor(previous, "usd")) * 1e3;
23187
+ } catch {
23188
+ return void 0;
23189
+ }
23190
+ }
22917
23191
  /** Authenticated, read-only recovery of a previously accepted public request id. */
22918
23192
  async handleRequestResult(c) {
22919
23193
  const requestId = c.req.param("requestId");
@@ -22984,6 +23258,14 @@ var DVMServer = class {
22984
23258
  400
22985
23259
  );
22986
23260
  }
23261
+ if (c.req.header("X-Cashu-Request-Id")?.startsWith(DERIVED_REQUEST_ID_PREFIX)) {
23262
+ return c.json(
23263
+ paymentErrorBody("reserved_request_id", {
23264
+ message: `X-Cashu-Request-Id may not start with "${DERIVED_REQUEST_ID_PREFIX}".`
23265
+ }),
23266
+ 400
23267
+ );
23268
+ }
22987
23269
  const publicRequestId = requestIdFromEnvelope(body.data);
22988
23270
  const recoveryStore = requestRecoveryStore(this.jobManager.store);
22989
23271
  const resumedRequestId = resumeOnly ? c.req.param("requestId") : void 0;
@@ -23002,6 +23284,17 @@ var DVMServer = class {
23002
23284
  const capabilityResult = this.requireCapabilityFromBody(c, body.capability);
23003
23285
  if ("error" in capabilityResult) return capabilityResult.error;
23004
23286
  const capabilityName = capabilityResult.name;
23287
+ const unsignedPaid = this.jobManager.unsignedPaidInput(body, capabilityName);
23288
+ if (unsignedPaid && publicRequestId) {
23289
+ return c.json(
23290
+ {
23291
+ error: "request_recovery_unsupported",
23292
+ message: "Public request ids require a signed request.",
23293
+ hint: "Sign the request to use request_id recovery, or omit request_id."
23294
+ },
23295
+ 400
23296
+ );
23297
+ }
23005
23298
  pinDimension("capability", capabilityName);
23006
23299
  const cap = this.descriptor.capabilities[capabilityName];
23007
23300
  let parsedInput;
@@ -23010,13 +23303,23 @@ var DVMServer = class {
23010
23303
  parsedInput = await loggers.isolate.span(
23011
23304
  "dvm.input.validate",
23012
23305
  { capability: capabilityName, stage: "input" },
23013
- () => this.jobManager.parseInput(body, capabilityName),
23306
+ () => this.jobManager.parseInput(body, capabilityName, unsignedPaid),
23014
23307
  { onError: () => ({ "error.code": "invalid_input" }) }
23015
23308
  );
23016
23309
  } catch (err) {
23017
- if (!this.deferToAuthGate(err)) return this.invalidInputResponse(c, err);
23310
+ if (unsignedPaid || !this.deferToAuthGate(err)) return this.invalidInputResponse(c, err);
23018
23311
  deferredInputError = err;
23019
23312
  }
23313
+ if (unsignedPaid && carriesCreditFields(signedRequestInput(body))) {
23314
+ return c.json(
23315
+ {
23316
+ error: "invalid_credit_fields",
23317
+ message: "credit_id, draw_id and fund require a signed request.",
23318
+ hint: "Sign the request to draw on or fund prepaid credit, or remove the credit fields and pay per call."
23319
+ },
23320
+ 400
23321
+ );
23322
+ }
23020
23323
  let creditEnvelope;
23021
23324
  try {
23022
23325
  creditEnvelope = extractCreditEnvelope(body.data);
@@ -23033,9 +23336,26 @@ var DVMServer = class {
23033
23336
  }
23034
23337
  throw err;
23035
23338
  }
23339
+ if (unsignedPaid && (cap.price === void 0 && !cap.onQuote || devModeSkipsPaymentVerification(this.opts) || !this.unsignedCashuOffered())) {
23340
+ return buildAuthErrorResponse(
23341
+ c,
23342
+ this.unsignedRequestRefusal(c, cap.input, body, capabilityName),
23343
+ this.publicOrigin
23344
+ );
23345
+ }
23346
+ if (unsignedPaid && await carriesNonCashuPayment(c)) {
23347
+ return buildPaymentErrorResponse(
23348
+ paymentErrorBody("payment_method_requires_signature", {
23349
+ message: "Unsigned requests may pay only with Cashu; an x402 or Tempo payment was attached."
23350
+ }),
23351
+ void 0,
23352
+ void 0,
23353
+ 402
23354
+ );
23355
+ }
23036
23356
  let authEnvelope;
23037
23357
  let authPubkey;
23038
- if (this.descriptor.auth) {
23358
+ if (this.descriptor.auth && !unsignedPaid) {
23039
23359
  const auth = this.descriptor.auth;
23040
23360
  try {
23041
23361
  const result = await loggers.isolate.span(
@@ -23083,14 +23403,14 @@ var DVMServer = class {
23083
23403
  { capability: capabilityName, input: body.input, params: body.params, data: body.data },
23084
23404
  { stripAuthEnvelope: this.descriptor.auth !== void 0 }
23085
23405
  );
23086
- const authFingerprint = this.descriptor.auth ? requestFingerprint(
23406
+ const authFingerprint = authEnvelope ? requestFingerprint(
23087
23407
  { capability: capabilityName, input: body.input, params: body.params, data: body.data },
23088
23408
  { stripAuthEnvelope: false }
23089
23409
  ) : void 0;
23090
23410
  const authorization = c.req.header("Authorization");
23091
23411
  const mppArtifact = authorization ? (await import("mppx")).Credential.extractPaymentScheme(authorization) : void 0;
23092
23412
  const attachedPayment = c.req.header("X-Cashu") ?? c.req.header("PAYMENT-SIGNATURE") ?? c.req.header("X-PAYMENT") ?? mppArtifact;
23093
- const hostingPaymentArtifactHash = attachedPayment ? createHash11("sha256").update(attachedPayment).digest("hex") : void 0;
23413
+ const hostingPaymentArtifactHash = attachedPayment ? createHash12("sha256").update(attachedPayment).digest("hex") : void 0;
23094
23414
  if (!resumeOnly && this.opts.hostingControlInstance && hostingPaymentArtifactHash) {
23095
23415
  const priorId = await this.opts.hostingControlInstance.acceptedJobByPaymentArtifactHash(
23096
23416
  hostingPaymentArtifactHash
@@ -23172,9 +23492,11 @@ var DVMServer = class {
23172
23492
  }
23173
23493
  const skipPayment = devModeSkipsPaymentVerification(this.opts);
23174
23494
  let priceMsats = resumeOnly ? payment.paidMsats || void 0 : void 0;
23495
+ let pricedAt;
23175
23496
  if (!resumeOnly && !skipPayment) {
23176
23497
  try {
23177
- priceMsats = await this.jobManager.currentPriceMsats(capabilityName);
23498
+ if (cap.price !== void 0) pricedAt = await this.fxFetcher.fetch();
23499
+ priceMsats = await this.jobManager.currentPriceMsats(capabilityName, pricedAt);
23178
23500
  } catch (err) {
23179
23501
  const fxErr = pickFxRateUnavailable(err);
23180
23502
  if (fxErr) return c.json({ error: fxErr }, 503);
@@ -23188,17 +23510,26 @@ var DVMServer = class {
23188
23510
  parsedInput,
23189
23511
  normalisedBody,
23190
23512
  authPubkey,
23191
- skipPayment
23513
+ skipPayment,
23514
+ unsignedPaid
23192
23515
  );
23193
23516
  if (dyn.error) return c.json(dyn.error.body, dyn.error.status);
23194
23517
  priceMsats = dyn.priceMsats;
23195
23518
  dynamicUpfront = dyn.upfront;
23519
+ pricedAt = dyn.pricedAt;
23196
23520
  }
23197
23521
  const priceFiat = resumeOnly ? void 0 : resolvePriceFiat({
23198
23522
  price: typeof cap.price === "string" ? cap.price : void 0,
23199
23523
  priceMsats,
23200
23524
  dynamicUpfront
23201
23525
  });
23526
+ if (unsignedPaid && !priceMsats) {
23527
+ return buildAuthErrorResponse(
23528
+ c,
23529
+ this.unsignedRequestRefusal(c, cap.input, body, capabilityName),
23530
+ this.publicOrigin
23531
+ );
23532
+ }
23202
23533
  if (!resumeOnly && creditEnvelope?.creditId) {
23203
23534
  await this.settleLightningFunding(authPubkey, creditEnvelope.creditId);
23204
23535
  }
@@ -23221,7 +23552,13 @@ var DVMServer = class {
23221
23552
  }
23222
23553
  }
23223
23554
  if (!skipPayment && !resumedAcceptedPayment) {
23224
- const railOpts = await this.railVerifyOpts(c);
23555
+ const railOpts = unsignedPaid ? cashuOnlyRailOpts(await this.railVerifyOpts(c)) : await this.railVerifyOpts(c);
23556
+ if (!this.descriptor.auth || unsignedPaid) {
23557
+ railOpts.nut24 = {
23558
+ mints: this.advertisedMints(),
23559
+ previousRequiredMsats: this.previousPriceMsats(cap, dynamicUpfront, pricedAt)
23560
+ };
23561
+ }
23225
23562
  hostingPaymentMayHaveLanded = Boolean(
23226
23563
  creditEnvelope || railOpts.cashuToken || railOpts.x402Payment || railOpts.mppCredential
23227
23564
  );
@@ -23259,7 +23596,7 @@ var DVMServer = class {
23259
23596
  this.reportShortPayForfeit(err.detail.forfeit);
23260
23597
  if (err.detail.body.error === "replay_rejected") {
23261
23598
  const recovered = await this.recoverReplayedJob(c, {
23262
- requestId: err.detail.replayPaymentId ?? c.req.header("X-Cashu-Request-Id"),
23599
+ requestId: err.detail.replayPaymentId,
23263
23600
  fingerprint,
23264
23601
  callerPubkey: authPubkey
23265
23602
  });
@@ -23287,15 +23624,17 @@ var DVMServer = class {
23287
23624
  }
23288
23625
  }
23289
23626
  if (!resumedAcceptedPayment) {
23290
- attachCreditMenu(
23291
- err.detail.body,
23292
- await this.creditMenu(this.creditCurrency(), authPubkey)
23293
- );
23627
+ if (!unsignedPaid) {
23628
+ attachCreditMenu(
23629
+ err.detail.body,
23630
+ await this.creditMenu(this.creditCurrency(), authPubkey)
23631
+ );
23632
+ }
23294
23633
  if (priceFiat) {
23295
23634
  err.detail.body.price_micro = priceFiat.amountMicro;
23296
23635
  err.detail.body.price_currency = priceFiat.currency;
23297
23636
  }
23298
- return buildPaymentErrorResponse(
23637
+ const response2 = buildPaymentErrorResponse(
23299
23638
  err.detail.body,
23300
23639
  err.detail.mppChallenges,
23301
23640
  err.detail.x402Requirements,
@@ -23305,6 +23644,11 @@ var DVMServer = class {
23305
23644
  this.x402Support()?.exact,
23306
23645
  err.detail.x402ErrorCode
23307
23646
  );
23647
+ if (err.detail.cashuPaymentRequest) {
23648
+ response2.headers.set("X-Cashu", err.detail.cashuPaymentRequest);
23649
+ response2.headers.set("Cache-Control", "no-store");
23650
+ }
23651
+ return response2;
23308
23652
  }
23309
23653
  }
23310
23654
  throw err;
@@ -23388,7 +23732,7 @@ var DVMServer = class {
23388
23732
  }
23389
23733
  const isAnonymous = requesterId === "anonymous" || requesterId === "dev-anonymous";
23390
23734
  const jobToken = isAnonymous ? randomBytes5(32).toString("hex") : void 0;
23391
- const requesterTokenHash = jobToken ? createHash11("sha256").update(jobToken).digest("hex") : void 0;
23735
+ const requesterTokenHash = jobToken ? createHash12("sha256").update(jobToken).digest("hex") : void 0;
23392
23736
  const job = jm.createJob(
23393
23737
  normalisedBody,
23394
23738
  requesterId,
@@ -23401,7 +23745,7 @@ var DVMServer = class {
23401
23745
  requestFingerprint: fingerprint,
23402
23746
  requesterPubkey: authPubkey,
23403
23747
  requestId: publicRequestId,
23404
- authRequestPath: this.descriptor.auth ? canonicalRequestPath(c.req.url) : void 0,
23748
+ authRequestPath: authEnvelope ? canonicalRequestPath(c.req.url) : void 0,
23405
23749
  jobId
23406
23750
  }
23407
23751
  );
@@ -23497,15 +23841,16 @@ var DVMServer = class {
23497
23841
  }
23498
23842
  /**
23499
23843
  * Recover a lost paid response only for matching request fingerprint,
23500
- * authenticated pubkey and requester identity. A spent bearer token alone
23501
- * is not proof of request ownership.
23844
+ * authenticated pubkey and requester identity. `requestId` must be the
23845
+ * payment reference the rail bound the presented payment to; a spent bearer
23846
+ * token alone is not proof of request ownership.
23502
23847
  */
23503
23848
  async recoverReplayedJob(c, opts) {
23504
23849
  if (!opts.requestId) return void 0;
23505
23850
  const prior = await this.jobManager.store.findJobByPaymentTxHash(opts.requestId);
23506
23851
  if (!prior) return void 0;
23507
23852
  const bodyMatches = prior.requestFingerprint !== void 0 && prior.requestFingerprint === opts.fingerprint;
23508
- const pubkeyMatches = this.descriptor.auth === void 0 || prior.requesterPubkey !== void 0 && prior.requesterPubkey === opts.callerPubkey;
23853
+ const pubkeyMatches = this.descriptor.auth === void 0 || prior.requesterPubkey === opts.callerPubkey;
23509
23854
  const requesterMatches = prior.requesterId === c.get("requesterId");
23510
23855
  if (!bodyMatches || !pubkeyMatches || !requesterMatches) {
23511
23856
  diagnosticConsole.warn(
@@ -23638,16 +23983,17 @@ var DVMServer = class {
23638
23983
  * schema separately. Refuse invalid input/results, currency mismatch or missing
23639
23984
  * FX when payment needs a price (400/500/503 as classified below).
23640
23985
  */
23641
- async computeDynamicPrice(onQuote, parsedInput, body, callerPubkey, skipRateConversion = false) {
23986
+ async computeDynamicPrice(onQuote, parsedInput, body, callerPubkey, skipRateConversion = false, unsigned2 = false) {
23642
23987
  let data = parsedInput;
23643
23988
  if (isZodSchema(onQuote.schema)) {
23644
- const parsed = onQuote.schema.safeParse(parsedInput);
23989
+ const schema = unsigned2 ? unsignedInputSchema(onQuote.schema) ?? onQuote.schema : onQuote.schema;
23990
+ const parsed = schema.safeParse(parsedInput);
23645
23991
  if (!parsed.success) {
23646
23992
  return {
23647
23993
  error: {
23648
23994
  body: {
23649
23995
  error: "quote_unavailable",
23650
- message: "DVM uses dynamic pricing but the job input does not satisfy onQuote.schema. Submit the same payload to /v1/quote to confirm pricing."
23996
+ message: unsigned2 ? "DVM uses dynamic pricing but the unsigned job input does not satisfy onQuote.schema. Sign the request, or send input that matches the published schema." : "DVM uses dynamic pricing but the job input does not satisfy onQuote.schema. Submit the same payload to /v1/quote to confirm pricing."
23651
23997
  },
23652
23998
  status: 400
23653
23999
  }
@@ -23677,7 +24023,8 @@ var DVMServer = class {
23677
24023
  const priceMsats = fiatToSatsCeil(validated.upfront.amount, fxRateFor(fx, upfrontCurrency)) * 1e3;
23678
24024
  return {
23679
24025
  priceMsats,
23680
- upfront: { amount: validated.upfront.amount, currency: upfrontCurrency }
24026
+ upfront: { amount: validated.upfront.amount, currency: upfrontCurrency },
24027
+ pricedAt: fx
23681
24028
  };
23682
24029
  } catch (err) {
23683
24030
  if (err instanceof InvalidCurrencyError || err instanceof UnsupportedCurrencyError) {
@@ -23959,6 +24306,8 @@ var DVMServer = class {
23959
24306
  this.opts.devMode === true
23960
24307
  );
23961
24308
  if (contentErr2) return c.json({ error: "invalid_message", message: contentErr2 }, 400);
24309
+ const railRefusal2 = this.unsignedJobRailRefusal(activeJob, body2);
24310
+ if (railRefusal2) return railRefusal2;
23962
24311
  const inboundSeq2 = await jm.recordInboundPending(activeJob, body2.type, body2.content);
23963
24312
  const lockPubkeys2 = await this.resolveLockPubkeysForReceive();
23964
24313
  let unappliedCredit2;
@@ -24019,6 +24368,8 @@ var DVMServer = class {
24019
24368
  }
24020
24369
  const contentErr = validateMessageContent(body.type, body.content, this.opts.devMode === true);
24021
24370
  if (contentErr) return c.json({ error: "invalid_message", message: contentErr }, 400);
24371
+ const railRefusal = this.unsignedJobRailRefusal(record, body);
24372
+ if (railRefusal) return railRefusal;
24022
24373
  if (isStreamableJobStore(jm.store)) {
24023
24374
  record.messages = await jm.store.getMessages(id, 0);
24024
24375
  record.seq = Math.max(record.seq, ...record.messages.map((message) => message.seq));
@@ -24218,7 +24569,7 @@ function isJobOwner(c, job) {
24218
24569
  if (job.requesterTokenHash) {
24219
24570
  const token = c.req.header("X-Job-Token");
24220
24571
  if (!token) return false;
24221
- const provided = createHash11("sha256").update(token).digest("hex");
24572
+ const provided = createHash12("sha256").update(token).digest("hex");
24222
24573
  return timingSafeEqualHex(provided, job.requesterTokenHash);
24223
24574
  }
24224
24575
  return c.get("requesterId") === job.requesterId;
@@ -24341,6 +24692,24 @@ function removeUnavailableContent(fields) {
24341
24692
  delete fields.payload_in;
24342
24693
  }
24343
24694
  }
24695
+ async function carriesNonCashuPayment(c) {
24696
+ if (c.req.header("PAYMENT-SIGNATURE") || c.req.header("X-PAYMENT")) return true;
24697
+ const authorization = c.req.header("Authorization");
24698
+ if (!authorization) return false;
24699
+ return Boolean((await import("mppx")).Credential.extractPaymentScheme(authorization));
24700
+ }
24701
+ function cashuOnlyRailOpts(opts) {
24702
+ return {
24703
+ ...opts,
24704
+ x402Payment: void 0,
24705
+ x402Config: void 0,
24706
+ x402ExactSettlement: void 0,
24707
+ x402ExactVersions: void 0,
24708
+ x402BatchSettlement: void 0,
24709
+ mppCredential: void 0,
24710
+ mpp: void 0
24711
+ };
24712
+ }
24344
24713
  function terminalResponseFields(job) {
24345
24714
  const fields = {};
24346
24715
  if (job.upfrontPayment !== void 0) fields.upfront_payment = job.upfrontPayment;