@dvmkit/sdk 0.10.1 → 0.11.0-rc.1

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 (57) hide show
  1. package/dist/{chunk-3Q6FTZDB.js → chunk-3YFFZLW7.js} +2 -2
  2. package/dist/{chunk-YOSZIWC3.js → chunk-55DVLBCD.js} +135 -8
  3. package/dist/{chunk-VMHYGKWT.js → chunk-566CFXGU.js} +9 -1
  4. package/dist/chunk-5H7P3ORL.js +92 -0
  5. package/dist/{chunk-3R7NQA3Z.js → chunk-7KQLPVV7.js} +1 -1
  6. package/dist/{chunk-SYBFMHXQ.js → chunk-AIQGF52I.js} +242 -38
  7. package/dist/{chunk-JNECYURT.js → chunk-D4FFS3IS.js} +1 -1
  8. package/dist/{chunk-VTHYQCDB.js → chunk-E7ZBCT5U.js} +1 -1
  9. package/dist/{chunk-MMGSYMIC.js → chunk-EP7OVWI5.js} +1 -1
  10. package/dist/{chunk-LDIPVPKI.js → chunk-FFRTAQUW.js} +1 -1
  11. package/dist/chunk-H3GGFEE5.js +57 -0
  12. package/dist/{chunk-M3T5JYDJ.js → chunk-HNXMWHLU.js} +1 -1
  13. package/dist/{chunk-2OGBZPPJ.js → chunk-HQPQ4PFI.js} +1 -1
  14. package/dist/{chunk-UKD3R2KU.js → chunk-HW2UCOOC.js} +4 -55
  15. package/dist/{chunk-FFXXGCVG.js → chunk-ITRSITFP.js} +1 -1
  16. package/dist/{chunk-MU3IE434.js → chunk-JLT2YIBS.js} +267 -52
  17. package/dist/{chunk-STJBBVNL.js → chunk-K2ZNIQZP.js} +458 -69
  18. package/dist/{chunk-KFCEXCZM.js → chunk-KJZVTWMX.js} +1 -1
  19. package/dist/{chunk-W3VGCNG6.js → chunk-LDKV4UHI.js} +1 -1
  20. package/dist/{chunk-AJ6UHXUA.js → chunk-M4NFYEEY.js} +1 -1
  21. package/dist/{chunk-MB6R4XPZ.js → chunk-NS2OMIU7.js} +15 -3
  22. package/dist/{chunk-VUGEEMLT.js → chunk-RPUFWIT4.js} +11 -89
  23. package/dist/{chunk-EWZFANHJ.js → chunk-VWIMQ5H5.js} +1 -1
  24. package/dist/{chunk-2XQLL6N6.js → chunk-WFCVS6QB.js} +2 -2
  25. package/dist/{chunk-QGNXYVBO.js → chunk-Z4FOPROB.js} +17 -0
  26. package/dist/{content-retention-CrILCNxk.d.ts → content-retention-ySTD1wwZ.d.ts} +1 -1
  27. package/dist/{credit-ledger-4PZNAMDT.js → credit-ledger-4CBYLSLY.js} +6 -3
  28. package/dist/{credit-menu-BWsVqMh-.d.ts → credit-menu-BYySnZBk.d.ts} +4 -4
  29. package/dist/{fx-BM5h4o5S.d.ts → fx-DVTNs9q1.d.ts} +1 -1
  30. package/dist/index.d.ts +5 -5
  31. package/dist/index.js +6 -5
  32. package/dist/internal/caller.d.ts +6 -6
  33. package/dist/internal/caller.js +26 -22
  34. package/dist/internal/server.d.ts +7 -7
  35. package/dist/internal/server.js +24 -22
  36. package/dist/{job-store-92v8e1LQ.d.ts → job-store-DRwXCJDI.d.ts} +1 -1
  37. package/dist/{lightning-backend-9oky_wJi.d.ts → lightning-backend-BX5YvjnM.d.ts} +1 -1
  38. package/dist/memory-credit-ledger-DKCN7FY2.js +14 -0
  39. package/dist/{mpp-setup-FURD45LI.js → mpp-setup-JU3MT7QX.js} +3 -3
  40. package/dist/{payout-reporter-HVJ6WUF3.js → payout-reporter-NXWQVKXW.js} +2 -2
  41. package/dist/{postgres-consumed-credential-store-WO6OCFXZ.js → postgres-consumed-credential-store-WXJLM6J7.js} +1 -1
  42. package/dist/{postgres-job-store-H45PRBLA.js → postgres-job-store-WIIVRZPD.js} +2 -2
  43. package/dist/{postgres-replay-store-XXJSA457.js → postgres-replay-store-QJ4FG6AR.js} +2 -2
  44. package/dist/{pricing-X4XU5HXY.js → pricing-KCLENT2C.js} +3 -3
  45. package/dist/{revenue-reporter-EV3K5L6B.js → revenue-reporter-HD4GMXGD.js} +2 -2
  46. package/dist/server/index.d.ts +28 -12
  47. package/dist/server/index.js +31 -29
  48. package/dist/{step-cache--7yA5Ihr.d.ts → step-cache-d-VKen4K.d.ts} +147 -13
  49. package/dist/{tempo-charge-store-5JOIYUIH.js → tempo-charge-store-7LTUAQHQ.js} +2 -2
  50. package/dist/{tempo-session-store-S6QMLQUA.js → tempo-session-store-LYAIDR2B.js} +3 -3
  51. package/dist/testing/index.d.ts +3 -3
  52. package/dist/testing/index.js +6 -6
  53. package/dist/{usd-C7lvLvLk.d.ts → usd-DSroMJ--.d.ts} +1 -1
  54. package/dist/{x402-EN5LSOVM.js → x402-RUJ4LUKF.js} +3 -3
  55. package/package.json +1 -1
  56. package/recipes/postgres/financial-export-v1.sql +19 -5
  57. package/dist/memory-credit-ledger-RP7DT5IY.js +0 -11
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-XRLKGCAW.js";
6
6
  import {
7
7
  x402EarnedCeiling
8
- } from "./chunk-FFXXGCVG.js";
8
+ } from "./chunk-ITRSITFP.js";
9
9
  import {
10
10
  createX402FacilitatorAuthHeaders,
11
11
  createX402FacilitatorClient,
@@ -14,16 +14,13 @@ import {
14
14
  verifyWithFacilitator,
15
15
  withPaymentDiagnostics,
16
16
  withPaymentStage
17
- } from "./chunk-VTHYQCDB.js";
17
+ } from "./chunk-E7ZBCT5U.js";
18
18
  import {
19
19
  MemoryCreditLedger
20
- } from "./chunk-YOSZIWC3.js";
20
+ } from "./chunk-55DVLBCD.js";
21
21
  import {
22
22
  PostgresJobStore
23
- } from "./chunk-2OGBZPPJ.js";
24
- import {
25
- parseStoreValue
26
- } from "./chunk-YG7G4DPZ.js";
23
+ } from "./chunk-HQPQ4PFI.js";
27
24
  import {
28
25
  SIGNED_ENVELOPE_FIELDS,
29
26
  SignedRequestError,
@@ -31,7 +28,7 @@ import {
31
28
  humanizeZodIssues,
32
29
  signedRequestInput,
33
30
  zodIssuesOf
34
- } from "./chunk-2XQLL6N6.js";
31
+ } from "./chunk-WFCVS6QB.js";
35
32
  import {
36
33
  MemoryJobStore,
37
34
  UNCAPPABLE_ASK_TOTAL,
@@ -40,7 +37,7 @@ import {
40
37
  isJobRetentionStore,
41
38
  isReceiptIssuingStore,
42
39
  isStreamableJobStore
43
- } from "./chunk-KFCEXCZM.js";
40
+ } from "./chunk-KJZVTWMX.js";
44
41
  import {
45
42
  ContentHoldError,
46
43
  JobCancelledError,
@@ -66,7 +63,6 @@ import {
66
63
  attestedDvmIdentity,
67
64
  buildAdminChallenge,
68
65
  checkMintHealth,
69
- computeResultHash,
70
66
  createMonotonicClock,
71
67
  formatStaleness,
72
68
  getWallet,
@@ -75,12 +71,9 @@ import {
75
71
  isX402SettlementReconciliationReason,
76
72
  parseTraceparent,
77
73
  receiptPubkeyFromSecret,
78
- signDrainReceipt,
79
- signFundingReceipt,
80
- signReceipt,
81
74
  toLockPubkey,
82
75
  verifyChallenge
83
- } from "./chunk-VUGEEMLT.js";
76
+ } from "./chunk-RPUFWIT4.js";
84
77
  import {
85
78
  X402_BATCH_SETTLEMENT_SCHEME,
86
79
  X402_DEFAULT_FACILITATOR,
@@ -103,12 +96,11 @@ import {
103
96
  import {
104
97
  RAIL_REFUNDABLE,
105
98
  canonicalRequestPath,
106
- canonicalize,
107
99
  isZodSchema
108
- } from "./chunk-UKD3R2KU.js";
100
+ } from "./chunk-HW2UCOOC.js";
109
101
  import {
110
102
  createConsoleLogger
111
- } from "./chunk-3R7NQA3Z.js";
103
+ } from "./chunk-7KQLPVV7.js";
112
104
  import {
113
105
  StepCache
114
106
  } from "./chunk-BQJIFRAZ.js";
@@ -125,7 +117,7 @@ import {
125
117
  roundUsd,
126
118
  satsToFiat,
127
119
  validateCurrency
128
- } from "./chunk-W3VGCNG6.js";
120
+ } from "./chunk-LDKV4UHI.js";
129
121
  import {
130
122
  CreditLedger,
131
123
  CreditLedgerError,
@@ -141,17 +133,29 @@ import {
141
133
  isNonChannelBitcoinRail,
142
134
  netOwedSats,
143
135
  x402SettlementPending
144
- } from "./chunk-STJBBVNL.js";
136
+ } from "./chunk-K2ZNIQZP.js";
137
+ import {
138
+ parseStoreValue
139
+ } from "./chunk-YG7G4DPZ.js";
145
140
  import {
146
141
  withSdkInitLock
147
142
  } from "./chunk-COYKJRMM.js";
143
+ import {
144
+ computeResultHash,
145
+ signDrainReceipt,
146
+ signFundingReceipt,
147
+ signReceipt
148
+ } from "./chunk-5H7P3ORL.js";
148
149
  import {
149
150
  withPostgresRecoveryContext
150
- } from "./chunk-MMGSYMIC.js";
151
+ } from "./chunk-EP7OVWI5.js";
151
152
  import {
152
153
  DvmError,
153
154
  x402NetworkByCaip2
154
155
  } from "./chunk-BPYMEMQX.js";
156
+ import {
157
+ canonicalize
158
+ } from "./chunk-H3GGFEE5.js";
155
159
  import {
156
160
  TEMPO_MODERATO_CHAIN_ID,
157
161
  TEMPO_MODERATO_RPC,
@@ -162,17 +166,17 @@ import {
162
166
  installHostedTempoCloseGuard,
163
167
  isTempoInsufficientFundsError,
164
168
  resolveTempoNetwork
165
- } from "./chunk-MU3IE434.js";
169
+ } from "./chunk-JLT2YIBS.js";
166
170
  import {
167
171
  loggers,
168
172
  pinDimension,
169
173
  withSpan,
170
174
  withTraceContext
171
- } from "./chunk-M3T5JYDJ.js";
175
+ } from "./chunk-HNXMWHLU.js";
172
176
  import {
173
177
  createDiagnosticCapabilityAuthority,
174
178
  diagnosticConsole
175
- } from "./chunk-QGNXYVBO.js";
179
+ } from "./chunk-Z4FOPROB.js";
176
180
 
177
181
  // src/sdk/server/lock-pubkey-store.ts
178
182
  async function initLockPubkeyTable(db) {
@@ -3884,7 +3888,7 @@ async function verifyUpfrontPaymentInScope(opts) {
3884
3888
  x402Requirements
3885
3889
  });
3886
3890
  }
3887
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
3891
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
3888
3892
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
3889
3893
  x402Payment,
3890
3894
  x402Config,
@@ -4908,7 +4912,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
4908
4912
  snapshot
4909
4913
  });
4910
4914
  }
4911
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
4915
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
4912
4916
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4913
4917
  body.content.x402_payment,
4914
4918
  opts.x402Config,
@@ -5339,7 +5343,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
5339
5343
  try {
5340
5344
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5341
5345
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5342
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
5346
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
5343
5347
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5344
5348
  body.content.x402_payment,
5345
5349
  opts.x402Config,
@@ -6943,13 +6947,37 @@ async function fundingReceiptFor(ledger, issuer, funding) {
6943
6947
 
6944
6948
  // src/sdk/server/tempo-drain-leg.ts
6945
6949
  async function completeTempoChannelDrain(args) {
6946
- const settlementEvidence = refundFloorEvidence({
6947
- rail: "tempo",
6948
- refundedNative: args.refundedNative,
6949
- debitedNative: args.debitedNative,
6950
- creditId: args.creditId,
6951
- drainId: args.drainId
6952
- });
6950
+ if (!args.tx && args.refundedNative !== void 0 && /^\d{1,78}$/.test(String(args.refundedNative))) {
6951
+ try {
6952
+ if (args.finalization?.outcome === "payer_exit" || args.finalization?.outcome === "payer_exit_unbacked") {
6953
+ if (BigInt(args.refundedNative) > 0n) await args.deps.ledger.recordTempoDrainPayment?.({ creditId: args.creditId, drainId: args.drainId });
6954
+ } else {
6955
+ await args.deps.ledger.recordTempoDrainRefund?.({ creditId: args.creditId, drainId: args.drainId, refundedNative: args.refundedNative });
6956
+ }
6957
+ } catch (error) {
6958
+ loggers.tempo.warn("tempo_drain_refund_evidence_write_failed", {});
6959
+ throw error;
6960
+ }
6961
+ }
6962
+ let settlementEvidence;
6963
+ try {
6964
+ settlementEvidence = refundFloorEvidence({
6965
+ rail: "tempo",
6966
+ refundedNative: args.refundedNative,
6967
+ debitedNative: args.debitedNative,
6968
+ creditId: args.creditId,
6969
+ drainId: args.drainId
6970
+ });
6971
+ } catch (error) {
6972
+ if (error instanceof DrainRefundFloorError && !args.tx) {
6973
+ try {
6974
+ await args.deps.ledger.recordTempoDrainHold?.({ creditId: args.creditId, drainId: args.drainId, reason: "refund_short" });
6975
+ } catch {
6976
+ loggers.tempo.warn("tempo_drain_hold_reason_write_failed", {});
6977
+ }
6978
+ }
6979
+ throw error;
6980
+ }
6953
6981
  return commitTerminalDrain(
6954
6982
  args.deps.drainReport,
6955
6983
  async (innerTx) => {
@@ -7690,6 +7718,11 @@ async function recoverTempoDrain(deps, mpp, record, channelId) {
7690
7718
  }
7691
7719
  if (!result) return null;
7692
7720
  if (!/^0x[0-9a-fA-F]{64}$/.test(result)) {
7721
+ try {
7722
+ await deps.ledger.recordTempoDrainHold?.({ creditId: record.creditId, drainId: record.drainId, reason: "close_evidence_missing" });
7723
+ } catch {
7724
+ loggers.tempo.warn("tempo_drain_hold_reason_write_failed", { amount_micro: record.amountMicro });
7725
+ }
7693
7726
  loggers.tempo.warn("tempo_session_drain_refund_unproven", {
7694
7727
  channel_id: channelId,
7695
7728
  credit_id: record.creditId,
@@ -10130,6 +10163,7 @@ var HostedSettlementReportObligations = class {
10130
10163
  FROM sdk_hosted_settlement_report_obligations target
10131
10164
  WHERE target.dvm_id=$1 AND target.scope=$2 AND target.rail='tempo' AND target.operation='close'
10132
10165
  AND target.state IN ('unmatched','no_report')
10166
+ AND ($7::text IS NULL OR target.channel_id=$7)
10133
10167
  )
10134
10168
  SELECT * FROM candidates
10135
10169
  WHERE ($6::boolean OR (state='unmatched' AND (retryable OR write_off_allowed)))
@@ -10141,7 +10175,8 @@ var HostedSettlementReportObligations = class {
10141
10175
  args.limit,
10142
10176
  args.after?.createdAtMs ?? null,
10143
10177
  args.after?.obligationId ?? null,
10144
- args.all ?? false
10178
+ args.all ?? false,
10179
+ args.channelId ?? null
10145
10180
  ]
10146
10181
  );
10147
10182
  return result.rows.map((row) => ({
@@ -17170,6 +17205,8 @@ function shortSha(sha) {
17170
17205
  }
17171
17206
 
17172
17207
  // src/sdk/server/admin-credit.ts
17208
+ import { sha256 } from "@noble/hashes/sha2.js";
17209
+ import { bytesToHex } from "@noble/hashes/utils.js";
17173
17210
  import {
17174
17211
  Amount as Amount3,
17175
17212
  CheckStateEnum as CheckStateEnum3,
@@ -17177,6 +17214,7 @@ import {
17177
17214
  getTokenMetadata as getTokenMetadata2
17178
17215
  } from "@cashu/cashu-ts";
17179
17216
  function installAdminCreditRoutes(app, opts) {
17217
+ app.post("/admin/credit/refund-draw", (c) => handleRefundDraw(c, opts));
17180
17218
  app.get("/admin/credit/pending-drains", (c) => handlePendingDrains(c, opts));
17181
17219
  app.get("/admin/credit/bitcoin-liability", (c) => handleBitcoinLiability(c, opts));
17182
17220
  app.post("/admin/credit/park-drain", (c) => handleParkDrain(c, opts));
@@ -17197,9 +17235,68 @@ function installAdminCreditRoutes(app, opts) {
17197
17235
  app.post("/admin/credit/write-off-tempo-close-report", (c) => handleTempoCloseReportAction(c, opts, "write-off"));
17198
17236
  app.get("/admin/credit/tempo-drains", (c) => handleTempoDrains(c, opts));
17199
17237
  app.get("/admin/credit/tempo-losses", (c) => handleTempoLosses(c, opts));
17238
+ app.post("/admin/credit/book-tempo-drain", (c) => handleTempoDrainDecision(c, opts, "book_refunded"));
17239
+ app.post("/admin/credit/release-tempo-drain", (c) => handleTempoDrainDecision(c, opts, "release"));
17200
17240
  app.post("/admin/credit/reconcile-tempo-drain", (c) => handleReconcileTempo(c, opts));
17201
17241
  app.post("/admin/credit/write-off-tempo-drain", (c) => handleWriteOffTempo(c, opts));
17202
17242
  }
17243
+ async function handleRefundDraw(c, opts) {
17244
+ const auth = await requireAuth(c, opts);
17245
+ if ("response" in auth) return auth.response;
17246
+ const body = await c.req.json().catch(() => null);
17247
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
17248
+ return c.json({ error: "invalid_body", message: "Request body must be a JSON object." }, 400);
17249
+ }
17250
+ const value = body;
17251
+ const nonempty = (item) => typeof item === "string" && item.length > 0;
17252
+ const byJob = nonempty(value.jobId) && value.creditId === void 0 && value.drawId === void 0;
17253
+ const byDraw = value.jobId === void 0 && nonempty(value.creditId) && nonempty(value.drawId);
17254
+ if (!byJob && !byDraw || !nonempty(value.refundId) || value.amountMicro !== void 0 && (typeof value.amountMicro !== "number" || !Number.isSafeInteger(value.amountMicro) || value.amountMicro <= 0) || value.reason !== void 0 && (typeof value.reason !== "string" || value.reason.length > 280)) {
17255
+ return c.json({ error: "invalid_body", message: "Provide jobId or creditId + drawId, refundId, an optional positive safe integer amountMicro, and an optional reason of at most 280 characters." }, 400);
17256
+ }
17257
+ if (!opts.ledger.durable || !opts.enqueueCreditDrawRefund) {
17258
+ return c.json({ error: "credit_refund_unavailable", hint: "Configure the persistent credit store and platform refund reporter, then retry with the original refund ID." }, 503);
17259
+ }
17260
+ try {
17261
+ const result = await opts.ledger.refundDraw({
17262
+ ...byJob ? { jobId: value.jobId } : { creditId: value.creditId, drawId: value.drawId },
17263
+ refundId: value.refundId,
17264
+ ...value.amountMicro === void 0 ? {} : { amountMicro: value.amountMicro },
17265
+ ...value.reason === void 0 ? {} : { reason: value.reason },
17266
+ outbox: { dvmId: opts.dvmId, enqueue: opts.enqueueCreditDrawRefund },
17267
+ ...opts.receiptIssuer ? { trustedReceiptPubkey: opts.receiptIssuer.pubkey } : {},
17268
+ ...opts.now ? { nowMs: opts.now() } : {}
17269
+ });
17270
+ return c.json({
17271
+ status: "refunded",
17272
+ refund_id: result.refundId,
17273
+ credit_id: result.creditId,
17274
+ draw_id: result.drawId,
17275
+ job_id: result.jobId,
17276
+ amount_micro: result.amountMicro,
17277
+ currency: result.currency,
17278
+ ledger_seq: result.ledgerSeq,
17279
+ refunded_at: result.refundedAt,
17280
+ balance_after_micro: result.balanceAfterMicro,
17281
+ replayed: result.replayed
17282
+ });
17283
+ } catch (error) {
17284
+ if (!(error instanceof CreditLedgerError)) throw error;
17285
+ const hints = {
17286
+ draw_not_found: "Check the job ID or credit/draw IDs. If they are correct, this DVM may lack trusted ownership evidence; ask an operator to inspect that evidence. Retrying alone cannot establish ownership.",
17287
+ draw_not_completed: "Wait for the job to complete. Failed or cancelled jobs release their pending hold automatically.",
17288
+ refund_exceeds_draw: "Refund no more than the remaining unrefunded charge. Omit amountMicro to refund that remainder.",
17289
+ credit_not_open: "The original credit is closed or expired. Ask an operator to inspect the original payment before arranging a refund.",
17290
+ credit_refund_channel_unsupported: "This channel returns unused collateral through its normal exit. Ask an operator about refunding completed spending.",
17291
+ credit_refund_basis_unavailable: "The original payment value cannot be proven automatically. Ask an operator to inspect independent original-payment evidence before arranging a repair. Retrying alone cannot establish that evidence.",
17292
+ credit_refund_state_unavailable: "The original credit reopening order cannot be proven automatically. Ask an operator to inspect independent funding and drain evidence before arranging a repair. Retrying alone cannot establish that evidence.",
17293
+ refund_conflict: "Repeat the original arguments, or deliberately choose a new refund ID for a different refund."
17294
+ };
17295
+ const hint = hints[error.code];
17296
+ if (!hint) throw error;
17297
+ return c.json({ error: error.code, hint }, error.code === "draw_not_found" ? 404 : error.code === "refund_conflict" ? 409 : 422);
17298
+ }
17299
+ }
17203
17300
  async function handleX402ExactSettlements(c, opts) {
17204
17301
  const auth = await requireAuth(c, opts);
17205
17302
  if ("response" in auth) return auth.response;
@@ -17318,6 +17415,9 @@ async function handleTempoLosses(c, opts) {
17318
17415
  currency: loss.currency,
17319
17416
  former_balance_micro: loss.formerBalanceMicro,
17320
17417
  settled_on_chain_native: loss.settledOnChainNative.toString(),
17418
+ refunded_to_payer_native: loss.refundedToPayerNative?.toString() ?? null,
17419
+ loss_native: loss.lossNative?.toString() ?? null,
17420
+ loss_basis: loss.lossBasis ?? null,
17321
17421
  highest_voucher_native: loss.highestVoucherNative.toString(),
17322
17422
  consumed_service_micro: loss.consumedServiceMicro,
17323
17423
  consumed_native: loss.consumedNative.toString(),
@@ -18304,13 +18404,18 @@ async function handleTempoDrains(c, opts) {
18304
18404
  ...after ? { after } : {},
18305
18405
  createdBeforeMs: nowMs - minAge,
18306
18406
  rail: "tempo",
18307
- includeResolved: c.req.query("include_resolved") === "true"
18407
+ includeResolved: c.req.query("include_resolved") === "true",
18408
+ includeWrittenOff: true
18308
18409
  });
18309
18410
  const wantsEvidence = c.req.query("evidence") !== "false";
18310
18411
  const rows2 = [];
18311
18412
  for (const drain of drains) {
18312
18413
  const evidence = wantsEvidence && drain.channelId ? await classifyTempoCloseEvidence(rail.rail, drain.channelId) : void 0;
18313
- rows2.push(tempoDrainBody(drain, evidence));
18414
+ const closeReports = drain.channelId && opts.hostedSettlementReports ? await opts.hostedSettlementReports.store.listTempoCloseReports({ limit: 50, all: true, channelId: drain.channelId }) : [];
18415
+ rows2.push({
18416
+ ...tempoDrainBody(drain, evidence),
18417
+ close_reports: closeReports.map((row) => ({ obligation_id: row.id, status: row.status, written_off: row.writtenOff }))
18418
+ });
18314
18419
  }
18315
18420
  const last = drains.length === limit ? drains[drains.length - 1] : void 0;
18316
18421
  return c.json({
@@ -18324,6 +18429,90 @@ async function handleTempoDrains(c, opts) {
18324
18429
  } : {}
18325
18430
  });
18326
18431
  }
18432
+ async function handleTempoDrainDecision(c, opts, decision) {
18433
+ const auth = await requireAuth(c, opts);
18434
+ if ("response" in auth) return auth.response;
18435
+ const body = await c.req.json().catch(() => null);
18436
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
18437
+ return c.json({ error: "invalid_body", message: "Request body must be a JSON object." }, 400);
18438
+ }
18439
+ const value = body;
18440
+ const text = (item, max) => typeof item === "string" && item.trim().length > 0 && item.trim().length <= max;
18441
+ if (Object.keys(value).some((key) => !["credit_id", "drain_id", "reason", "evidence"].includes(key)) || typeof value.credit_id !== "string" || value.credit_id.length === 0 || typeof value.drain_id !== "string" || value.drain_id.length === 0 || !text(value.reason, 2e3) || value.evidence !== void 0 && !text(value.evidence, 4e3)) {
18442
+ return c.json({ error: "invalid_body", message: "Provide credit_id, drain_id, a reason of 1\u20132000 characters, and optional evidence of 1\u20134000 characters." }, 400);
18443
+ }
18444
+ if (!opts.ledger.durable || !opts.ledger.decideTempoDrain) {
18445
+ return c.json({ error: "tempo_drain_decision_unavailable", message: "A durable Tempo decision store is required." }, 503);
18446
+ }
18447
+ const args = {
18448
+ creditId: value.credit_id,
18449
+ drainId: value.drain_id,
18450
+ decision,
18451
+ reason: value.reason.trim(),
18452
+ ...value.evidence === void 0 ? {} : { evidence: value.evidence.trim() },
18453
+ nowMs: opts.now ? opts.now() : Date.now()
18454
+ };
18455
+ if (decision === "book_refunded" && opts.requireDrainReport && !opts.enqueueCreditDrain) {
18456
+ const existing = await opts.ledger.getDrain(args);
18457
+ if (existing && !existing.operatorDecision && (existing.status === "pending" || existing.status === "exit_required")) {
18458
+ return c.json({ error: "tempo_drain_decision_unavailable", message: "Hosted booking requires a transactional drain reporter. Restore its configuration, then retry the original decision." }, 503);
18459
+ }
18460
+ }
18461
+ const client = await opts.db.connect();
18462
+ let result;
18463
+ try {
18464
+ await client.query("BEGIN");
18465
+ let decided;
18466
+ const transition = async () => {
18467
+ decided = await opts.ledger.decideTempoDrain({ ...args, tx: client });
18468
+ return decided;
18469
+ };
18470
+ if (decision === "book_refunded") {
18471
+ await commitTerminalDrain({ db: opts.db, dvmId: opts.dvmId, enqueueCreditDrain: opts.enqueueCreditDrain }, transition, client);
18472
+ } else {
18473
+ await transition();
18474
+ }
18475
+ result = decided;
18476
+ await client.query("COMMIT");
18477
+ } catch (error) {
18478
+ await client.query("ROLLBACK").catch(() => void 0);
18479
+ if (error instanceof CreditLedgerError) {
18480
+ return c.json(
18481
+ { error: error.code, message: error.message },
18482
+ error.code === "drain_not_found" || error.code === "credit_not_found" ? 404 : ["drain_decision_conflict", "tempo_drain_close_pending", "tempo_drain_refund_paid", "tempo_drain_refund_unknown"].includes(error.code) ? 409 : 422
18483
+ );
18484
+ }
18485
+ throw error;
18486
+ } finally {
18487
+ client.release();
18488
+ }
18489
+ if (!result.replayed) {
18490
+ diagnosticConsole.warn({
18491
+ dataset: "tempo",
18492
+ event: decision === "book_refunded" ? "tempo_drain_booked_as_refunded" : "tempo_drain_released",
18493
+ credit_id: result.drain.creditId,
18494
+ drain_id: result.drain.drainId,
18495
+ channel_id: result.drain.channelId,
18496
+ drain_reference: `sha256:${bytesToHex(sha256(new TextEncoder().encode(JSON.stringify([result.drain.creditId, result.drain.drainId]))))}`,
18497
+ amount_micro: result.drain.amountMicro,
18498
+ amount_native: result.drain.drainedNative?.toString() ?? null,
18499
+ currency: result.drain.currency
18500
+ });
18501
+ }
18502
+ return c.json({
18503
+ status: result.drain.status,
18504
+ credit_id: result.drain.creditId,
18505
+ drain_id: result.drain.drainId,
18506
+ channel_id: result.drain.channelId,
18507
+ amount_micro: result.drain.amountMicro,
18508
+ amount_native: result.drain.drainedNative,
18509
+ currency: result.drain.currency,
18510
+ decision: result.drain.operatorDecision,
18511
+ balance_micro: result.credit.balanceMicro,
18512
+ spendable_micro: result.spendableMicro,
18513
+ replayed: result.replayed
18514
+ });
18515
+ }
18327
18516
  async function handleReconcileTempo(c, opts) {
18328
18517
  const auth = await requireAuth(c, opts);
18329
18518
  if ("response" in auth) return auth.response;
@@ -18337,6 +18526,12 @@ async function handleReconcileTempo(c, opts) {
18337
18526
  if (record.status === "sent") return tempoReconciledBody(c, record, channelId, true);
18338
18527
  const evidence = await classifyTempoCloseEvidence(rail.rail, channelId);
18339
18528
  if (evidence.chain !== "found") return tempoEvidenceRefusal(c, evidence);
18529
+ try {
18530
+ await opts.ledger.recordTempoDrainRefund?.({ creditId: record.creditId, drainId: record.drainId, refundedNative: evidence.refundedToPayer });
18531
+ } catch (error) {
18532
+ diagnosticConsole.warn({ dataset: "tempo", event: "tempo_drain_refund_evidence_write_failed", amount_micro: record.amountMicro });
18533
+ throw error;
18534
+ }
18340
18535
  const client = await opts.db.connect();
18341
18536
  let booked;
18342
18537
  try {
@@ -18825,6 +19020,10 @@ function tempoDrainBody(drain, evidence) {
18825
19020
  created_at: drain.createdAt,
18826
19021
  write_off_note: drain.writeOffNote,
18827
19022
  written_off_at: drain.writtenOffAt,
19023
+ decision: drain.operatorDecision ?? null,
19024
+ proven_refund_native: drain.provenRefundNative ?? null,
19025
+ refund_payment_proven: drain.refundPaymentProven ?? false,
19026
+ held_reason: drain.status === "pending" || drain.status === "exit_required" ? drain.holdReason ?? (evidence?.chain === "found" && drain.drainedNative !== null && BigInt(evidence.refundedToPayer) < BigInt(drain.drainedNative) ? "refund_short" : drain.status === "exit_required" ? "exit_required" : "refund_unproven") : null,
18828
19027
  ...evidence ? { chain: evidence } : {}
18829
19028
  };
18830
19029
  }
@@ -20170,7 +20369,7 @@ async function createDVMServer(descriptor, opts) {
20170
20369
  }
20171
20370
  }
20172
20371
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
20173
- const { RevenueReporter } = await import("./revenue-reporter-EV3K5L6B.js");
20372
+ const { RevenueReporter } = await import("./revenue-reporter-HD4GMXGD.js");
20174
20373
  const reporter = new RevenueReporter(
20175
20374
  opts.db,
20176
20375
  platformUrl,
@@ -20180,7 +20379,7 @@ async function createDVMServer(descriptor, opts) {
20180
20379
  revenueReporter = reporter;
20181
20380
  await reporter.init();
20182
20381
  reporter.startRetryLoop();
20183
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-HVJ6WUF3.js");
20382
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-NXWQVKXW.js");
20184
20383
  payoutReporter = new PayoutReporter({
20185
20384
  dvmId,
20186
20385
  db: opts.db,
@@ -20208,6 +20407,7 @@ async function createDVMServer(descriptor, opts) {
20208
20407
  const effectiveOnRevenueSkippedNoRail = opts.onRevenueSkippedNoRail ?? (activeRevenueReporter ? (info) => activeRevenueReporter.reportRevenueSkippedNoRail(info) : void 0);
20209
20408
  const enqueueCreditDeposit = !opts.onCreditFunded && activeRevenueReporter ? activeRevenueReporter.enqueueDeposit.bind(activeRevenueReporter) : void 0;
20210
20409
  const enqueueCreditDrain = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrain.bind(activeRevenueReporter) : void 0;
20410
+ const enqueueCreditDrawRefund = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrawRefund.bind(activeRevenueReporter) : void 0;
20211
20411
  const enqueueCreditDrawRelease = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrawRelease.bind(activeRevenueReporter) : void 0;
20212
20412
  unsubscribeTempoSettlementReadiness = opts.tempoSettlementReadiness?.subscribe({
20213
20413
  onBalance: (observation) => activeRevenueReporter && dvmId ? activeRevenueReporter.reportTempoSettlementBalance({
@@ -20363,6 +20563,7 @@ async function createDVMServer(descriptor, opts) {
20363
20563
  } : void 0;
20364
20564
  server = new DVMServer(descriptor, {
20365
20565
  ...opts,
20566
+ platformUrl,
20366
20567
  hostingControlInstance: hostingControl,
20367
20568
  hostedSettlementReports,
20368
20569
  hostingClosureSupport,
@@ -20377,6 +20578,7 @@ async function createDVMServer(descriptor, opts) {
20377
20578
  enqueueCreditDeposit,
20378
20579
  enqueueCreditDrain,
20379
20580
  enqueueCreditDrawRelease,
20581
+ enqueueCreditDrawRefund,
20380
20582
  x402BatchSettlement,
20381
20583
  x402BatchSettlementFactory,
20382
20584
  x402ExactSettlement,
@@ -20832,8 +21034,10 @@ var DVMServer = class {
20832
21034
  receiptIssuer: this.receiptIssuer,
20833
21035
  creditTtlMs: () => this.creditTtlMs(),
20834
21036
  creditCurrency: () => this.creditCurrency(),
21037
+ enqueueCreditDrawRefund: this.opts.enqueueCreditDrawRefund,
20835
21038
  enqueueCreditDeposit: this.opts.enqueueCreditDeposit,
20836
21039
  enqueueCreditDrain: this.opts.enqueueCreditDrain,
21040
+ requireDrainReport: !!this.opts.platformUrl,
20837
21041
  lightningReceive: this.opts.lightningReceive,
20838
21042
  // x402 repair queue rides the same surface, and resolves
20839
21043
  // the rail the same lazy way the verify path does — constructing the
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  validateCurrency
3
- } from "./chunk-W3VGCNG6.js";
3
+ } from "./chunk-LDKV4UHI.js";
4
4
 
5
5
  // src/sdk/pricing/upfront.ts
6
6
  function buildUpfront(input) {
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-66HGCPBU.js";
7
7
  import {
8
8
  diagnosticConsole
9
- } from "./chunk-QGNXYVBO.js";
9
+ } from "./chunk-Z4FOPROB.js";
10
10
 
11
11
  // src/observability/payment-diagnostics.ts
12
12
  import { AsyncLocalStorage } from "async_hooks";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  diagnosticConsole
3
- } from "./chunk-QGNXYVBO.js";
3
+ } from "./chunk-Z4FOPROB.js";
4
4
 
5
5
  // src/lib/pg-retry.ts
6
6
  import { AsyncLocalStorage } from "async_hooks";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-COYKJRMM.js";
8
8
  import {
9
9
  diagnosticConsole
10
- } from "./chunk-QGNXYVBO.js";
10
+ } from "./chunk-Z4FOPROB.js";
11
11
 
12
12
  // src/sdk/server/tempo-charge-store.ts
13
13
  import { Credential } from "mppx";
@@ -0,0 +1,57 @@
1
+ // src/lib/cast-auth/canonical-json.ts
2
+ function canonicalize(value) {
3
+ return JSON.stringify(sortDeep(value));
4
+ }
5
+ function canonicaliseForSigning(value) {
6
+ return new TextEncoder().encode(canonicalize(value));
7
+ }
8
+ function sortDeep(value) {
9
+ if (value === null) return null;
10
+ if (typeof value === "string" || typeof value === "boolean") return value;
11
+ if (typeof value === "number") {
12
+ if (!Number.isFinite(value)) {
13
+ throw new TypeError(`canonicalize: non-finite number is not JSON-serialisable`);
14
+ }
15
+ return value;
16
+ }
17
+ if (Array.isArray(value)) return value.map(sortDeep);
18
+ if (typeof value === "object") {
19
+ const sorted = {};
20
+ for (const key of Object.keys(value).sort()) {
21
+ const v = value[key];
22
+ if (v === void 0) continue;
23
+ sorted[key] = sortDeep(v);
24
+ }
25
+ return sorted;
26
+ }
27
+ throw new TypeError(`canonicalize: unsupported value of type ${typeof value}`);
28
+ }
29
+
30
+ // src/lib/cast-auth/verify.ts
31
+ import { schnorr } from "@noble/curves/secp256k1.js";
32
+ function verifyCanonicalSignedRequest(payload, signatureHex, pubkeyHex) {
33
+ if (!/^[0-9a-f]{64}$/i.test(pubkeyHex) || !/^[0-9a-f]{128}$/i.test(signatureHex)) {
34
+ return false;
35
+ }
36
+ const data = canonicaliseForSigning(payload);
37
+ const signature = hexToBytes(signatureHex);
38
+ const pubkey = hexToBytes(pubkeyHex);
39
+ try {
40
+ return schnorr.verify(signature, data, pubkey);
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ function hexToBytes(hex) {
46
+ const out = new Uint8Array(hex.length / 2);
47
+ for (let i = 0; i < out.length; i++) {
48
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ export {
54
+ canonicalize,
55
+ canonicaliseForSigning,
56
+ verifyCanonicalSignedRequest
57
+ };
@@ -7,7 +7,7 @@ import {
7
7
  extendDiagnosticIdentityAuthority,
8
8
  projectDiagnosticFields,
9
9
  projectSpanRow
10
- } from "./chunk-QGNXYVBO.js";
10
+ } from "./chunk-Z4FOPROB.js";
11
11
 
12
12
  // src/observability/context.ts
13
13
  import { AsyncLocalStorage } from "async_hooks";
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-COYKJRMM.js";
8
8
  import {
9
9
  diagnosticConsole
10
- } from "./chunk-QGNXYVBO.js";
10
+ } from "./chunk-Z4FOPROB.js";
11
11
 
12
12
  // src/sdk/server/postgres-job-store.ts
13
13
  import { createHash } from "crypto";