@dvmkit/sdk 0.7.0-rc.1 → 0.7.0-rc.10

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 (69) hide show
  1. package/README.md +39 -2
  2. package/dist/{chunk-S3OY533W.js → chunk-2NUFE2PG.js} +47 -5
  3. package/dist/{chunk-RA2WIX7G.js → chunk-3E4C6W7W.js} +17 -6
  4. package/dist/{chunk-GY66THYI.js → chunk-53SNRGQQ.js} +372 -153
  5. package/dist/{chunk-CXCQLOOK.js → chunk-62FI3NJF.js} +42 -107
  6. package/dist/chunk-6HJEVCVI.js +17 -0
  7. package/dist/chunk-A262WVYX.js +6 -0
  8. package/dist/{chunk-IV6Q7NRW.js → chunk-C5RIQWG4.js} +1 -1
  9. package/dist/{chunk-GAOAYHZA.js → chunk-FBQVOOYF.js} +37 -6
  10. package/dist/{chunk-Z4ZT5LNT.js → chunk-GEI4MAXE.js} +3 -3
  11. package/dist/{chunk-OML32F6I.js → chunk-GLIAR7RN.js} +1 -1
  12. package/dist/{chunk-X7EZ5CLI.js → chunk-HCATFJDD.js} +3 -3
  13. package/dist/{chunk-BYCEKO65.js → chunk-HPCXF3DV.js} +220 -32
  14. package/dist/{chunk-6XF3RVVX.js → chunk-IRXIH775.js} +5 -5
  15. package/dist/{chunk-QR2EF2NI.js → chunk-KCQDXIJU.js} +1 -42
  16. package/dist/chunk-MDW7ZR7V.js +47 -0
  17. package/dist/{chunk-KA6VN5FK.js → chunk-MQYM4T66.js} +3 -3
  18. package/dist/{chunk-P6QON7ER.js → chunk-NV7Z7377.js} +1 -1
  19. package/dist/{chunk-KMED3H3A.js → chunk-OBBVDKLE.js} +476 -230
  20. package/dist/{chunk-EQLA4FFY.js → chunk-OHUXCAOM.js} +17 -6
  21. package/dist/{chunk-FWGWLPXF.js → chunk-PZ5IAWFD.js} +1 -1
  22. package/dist/{chunk-IXR5RFK7.js → chunk-R3TQSXWT.js} +191 -5
  23. package/dist/{chunk-VHNPVGSA.js → chunk-T5DIXOLR.js} +3 -3
  24. package/dist/{chunk-RCM3P2HE.js → chunk-UKD3R2KU.js} +55 -4
  25. package/dist/chunk-WMGXJQI3.js +120 -0
  26. package/dist/{chunk-N5RZ5HVB.js → chunk-X6S2CHFM.js} +2 -2
  27. package/dist/{chunk-ETXDTUJO.js → chunk-ZCNPIILZ.js} +112 -1
  28. package/dist/{content-retention-DHyLdA6b.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
  29. package/dist/{credit-ledger-MCUXYKSJ.js → credit-ledger-MFIIOSZH.js} +4 -4
  30. package/dist/{credit-menu-DjnkeEUU.d.ts → credit-menu-D2t1-Tf6.d.ts} +13 -6
  31. package/dist/{fx-zMWFzfrU.d.ts → fx-Ql4nvdbh.d.ts} +10 -3
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +248 -16
  34. package/dist/internal/caller.d.ts +5032 -8272
  35. package/dist/internal/caller.js +4302 -8084
  36. package/dist/internal/index.js +1 -1
  37. package/dist/internal/server.d.ts +7 -7
  38. package/dist/internal/server.js +30 -31
  39. package/dist/{job-store-DdLXBpol.d.ts → job-store-DCvJQUX0.d.ts} +1 -1
  40. package/dist/{lightning-backend-Bw-qtAbt.d.ts → lightning-backend-CY5ML3km.d.ts} +2 -2
  41. package/dist/{memory-credit-ledger-SBSMGMDX.js → memory-credit-ledger-44NE4PZC.js} +5 -5
  42. package/dist/{mpp-setup-RF5FXGGV.js → mpp-setup-HRWUBULX.js} +4 -2
  43. package/dist/{payout-reporter-3Y2U55ZX.js → payout-reporter-7P66T77I.js} +6 -4
  44. package/dist/{postgres-consumed-credential-store-BUVCAPLS.js → postgres-consumed-credential-store-IDGPFKIL.js} +3 -3
  45. package/dist/{postgres-job-store-VHLQBJZC.js → postgres-job-store-UKNJ2F6Z.js} +2 -2
  46. package/dist/{postgres-replay-store-ARZSQ45X.js → postgres-replay-store-PMA4ADXN.js} +2 -2
  47. package/dist/{pricing-XT2IAPNE.js → pricing-52XJHJ6K.js} +12 -12
  48. package/dist/{revenue-reporter-MAERPYGK.js → revenue-reporter-HV7RMS54.js} +2 -2
  49. package/dist/server/index.d.ts +12 -11
  50. package/dist/server/index.js +47 -48
  51. package/dist/{step-cache-2MkvenJn.d.ts → step-cache-BOWAn_BH.d.ts} +84 -7
  52. package/dist/{tempo-charge-store-Q3D7KYMH.js → tempo-charge-store-P2Z4RMU7.js} +2 -2
  53. package/dist/{tempo-session-store-ZRNCOX7P.js → tempo-session-store-VSP7KLBS.js} +3 -3
  54. package/dist/testing/index.d.ts +3 -3
  55. package/dist/testing/index.js +7 -6
  56. package/dist/{usd-DI3d3gTK.d.ts → usd-MAtMp3DB.d.ts} +1 -1
  57. package/dist/{x402-FT4S4JS4.js → x402-3IQ3HXSG.js} +8 -26
  58. package/package.json +1 -1
  59. package/dist/chunk-AT6V3SY7.js +0 -102
  60. package/dist/chunk-EZXAJ6EI.js +0 -1082
  61. package/dist/chunk-HCLVYZUZ.js +0 -244
  62. package/dist/chunk-JEVLNAO3.js +0 -1668
  63. package/dist/chunk-NFMOPYBA.js +0 -357
  64. package/dist/chunk-O2OTZ5GP.js +0 -1869
  65. package/dist/chunk-OFVV37XC.js +0 -57
  66. package/dist/chunk-SZVXOQ75.js +0 -211
  67. package/dist/chunk-WBOKPC37.js +0 -795
  68. package/dist/tempo-lifecycle-GJ77YKVY.js +0 -15
  69. package/dist/tempo-wallet-X2GGAZ6X.js +0 -40
@@ -1,20 +1,31 @@
1
1
  import {
2
- MemoryProcessedPaymentStore,
3
- PostgresProcessedPaymentStore,
4
- ProcessedPaymentReplayError
5
- } from "./chunk-XRLKGCAW.js";
2
+ TEMPO_MODERATO_CHAIN_ID,
3
+ TEMPO_MODERATO_RPC,
4
+ advertisedMethods,
5
+ challengeMeta,
6
+ installHostedTempoChargeGuard,
7
+ installHostedTempoCloseGuard,
8
+ isTempoInsufficientFundsError,
9
+ resolveTempoNetwork
10
+ } from "./chunk-R3TQSXWT.js";
11
+ import {
12
+ x402EarnedCeiling
13
+ } from "./chunk-FBQVOOYF.js";
6
14
  import {
7
15
  createX402FacilitatorAuthHeaders,
8
16
  createX402FacilitatorClient,
17
+ paymentFailureStage,
9
18
  settleWithFacilitator,
10
- verifyWithFacilitator
11
- } from "./chunk-S3OY533W.js";
19
+ verifyWithFacilitator,
20
+ withPaymentDiagnostics,
21
+ withPaymentStage
22
+ } from "./chunk-2NUFE2PG.js";
12
23
  import {
13
24
  MemoryCreditLedger
14
- } from "./chunk-OML32F6I.js";
25
+ } from "./chunk-GLIAR7RN.js";
15
26
  import {
16
27
  PostgresJobStore
17
- } from "./chunk-EQLA4FFY.js";
28
+ } from "./chunk-OHUXCAOM.js";
18
29
  import {
19
30
  parseStoreValue
20
31
  } from "./chunk-YG7G4DPZ.js";
@@ -25,7 +36,7 @@ import {
25
36
  humanizeZodIssues,
26
37
  signedRequestInput,
27
38
  zodIssuesOf
28
- } from "./chunk-N5RZ5HVB.js";
39
+ } from "./chunk-X6S2CHFM.js";
29
40
  import {
30
41
  MemoryJobStore,
31
42
  UNCAPPABLE_ASK_TOTAL,
@@ -34,7 +45,7 @@ import {
34
45
  isJobRetentionStore,
35
46
  isReceiptIssuingStore,
36
47
  isStreamableJobStore
37
- } from "./chunk-IV6Q7NRW.js";
48
+ } from "./chunk-C5RIQWG4.js";
38
49
  import {
39
50
  ContentHoldError,
40
51
  JobCancelledError,
@@ -53,34 +64,28 @@ import {
53
64
  ADMIN_AUTH_CLOCK_SKEW_SECONDS,
54
65
  ADMIN_AUTH_NONCE_TTL_SECONDS,
55
66
  ADMIN_STATE_UNAVAILABLE,
67
+ IMPLICIT_CREDIT_TTL_MS,
68
+ JOB_IDENTITY_HEADER,
56
69
  TEMPO_SESSION_UPDATED_REASON,
57
70
  X402_BATCH_CHANNEL_ABI,
58
71
  attestedDvmIdentity,
59
72
  buildAdminChallenge,
60
73
  checkMintHealth,
74
+ computeResultHash,
61
75
  createMonotonicClock,
76
+ formatStaleness,
62
77
  getWallet,
78
+ isJobIdentityValue,
63
79
  isPublicRequestId,
64
- isTestMintUrl,
65
80
  isX402SettlementReconciliationReason,
66
- normaliseMintUrl,
67
81
  parseTraceparent,
68
82
  receiptPubkeyFromSecret,
69
- toLockPubkey,
70
- verifyCashuToken,
71
- verifyChallenge
72
- } from "./chunk-KMED3H3A.js";
73
- import {
74
- IMPLICIT_CREDIT_TTL_MS,
75
- computeResultHash,
76
- formatStaleness,
77
- msatsToUsd,
78
- msatsToUsdc,
79
83
  signDrainReceipt,
80
84
  signFundingReceipt,
81
85
  signReceipt,
82
- usdcToMsats
83
- } from "./chunk-NFMOPYBA.js";
86
+ toLockPubkey,
87
+ verifyChallenge
88
+ } from "./chunk-OBBVDKLE.js";
84
89
  import {
85
90
  X402_BATCH_SETTLEMENT_SCHEME,
86
91
  X402_DEFAULT_FACILITATOR,
@@ -93,25 +98,28 @@ import {
93
98
  encodePaymentRequiredHeader,
94
99
  encodeSettleResponseHeader,
95
100
  exactEvmAuthorization,
101
+ msatsToUsd,
102
+ msatsToUsdc,
96
103
  paymentRequiredV2FromV1,
97
104
  resolveRpcOverride,
105
+ usdcToMsats,
98
106
  x402NetworkToCaip2
99
- } from "./chunk-CXCQLOOK.js";
107
+ } from "./chunk-62FI3NJF.js";
100
108
  import {
101
109
  RAIL_REFUNDABLE,
102
110
  canonicalRequestPath,
111
+ canonicalize,
103
112
  isZodSchema
104
- } from "./chunk-RCM3P2HE.js";
113
+ } from "./chunk-UKD3R2KU.js";
105
114
  import {
106
- canonicalize
107
- } from "./chunk-OFVV37XC.js";
115
+ createConsoleLogger
116
+ } from "./chunk-MDW7ZR7V.js";
108
117
  import {
109
- createConsoleLogger,
110
118
  loggers,
111
119
  pinDimension,
112
120
  withSpan,
113
121
  withTraceContext
114
- } from "./chunk-QR2EF2NI.js";
122
+ } from "./chunk-KCQDXIJU.js";
115
123
  import {
116
124
  StepCache
117
125
  } from "./chunk-BQJIFRAZ.js";
@@ -120,16 +128,15 @@ import {
120
128
  InvalidCurrencyError,
121
129
  UnsupportedCurrencyError,
122
130
  createFxFetcher,
123
- fxRateFor,
124
- resolveFxSourceFromEnv,
125
- validateCurrency
126
- } from "./chunk-BYCEKO65.js";
127
- import {
128
131
  fiatToSatsCeil,
132
+ fxRateFor,
129
133
  parseUsdPrice,
134
+ readFxSourceFailure,
135
+ resolveFxSourceFromEnv,
130
136
  roundUsd,
131
- satsToFiat
132
- } from "./chunk-AT6V3SY7.js";
137
+ satsToFiat,
138
+ validateCurrency
139
+ } from "./chunk-HPCXF3DV.js";
133
140
  import {
134
141
  CreditLedger,
135
142
  CreditLedgerError,
@@ -145,30 +152,26 @@ import {
145
152
  isNonChannelBitcoinRail,
146
153
  netOwedSats,
147
154
  x402SettlementPending
148
- } from "./chunk-6XF3RVVX.js";
149
- import {
150
- withSdkInitLock
151
- } from "./chunk-COYKJRMM.js";
155
+ } from "./chunk-IRXIH775.js";
152
156
  import {
153
157
  withPostgresRecoveryContext
154
- } from "./chunk-P6QON7ER.js";
158
+ } from "./chunk-NV7Z7377.js";
155
159
  import {
160
+ DvmError,
156
161
  x402NetworkByCaip2
157
162
  } from "./chunk-BPYMEMQX.js";
158
- import {
159
- TEMPO_MODERATO_CHAIN_ID,
160
- TEMPO_MODERATO_RPC,
161
- advertisedMethods,
162
- challengeMeta,
163
- installHostedTempoChargeGuard,
164
- installHostedTempoCloseGuard,
165
- isTempoInsufficientFundsError,
166
- resolveTempoNetwork
167
- } from "./chunk-IXR5RFK7.js";
168
163
  import {
169
164
  createDiagnosticCapabilityAuthority,
170
165
  diagnosticConsole
171
- } from "./chunk-ETXDTUJO.js";
166
+ } from "./chunk-ZCNPIILZ.js";
167
+ import {
168
+ MemoryProcessedPaymentStore,
169
+ PostgresProcessedPaymentStore,
170
+ ProcessedPaymentReplayError
171
+ } from "./chunk-XRLKGCAW.js";
172
+ import {
173
+ withSdkInitLock
174
+ } from "./chunk-COYKJRMM.js";
172
175
 
173
176
  // src/sdk/server/lock-pubkey-store.ts
174
177
  async function initLockPubkeyTable(db) {
@@ -1071,7 +1074,8 @@ async function resolveFxSnapshot(fetcher) {
1071
1074
  return {
1072
1075
  ok: false,
1073
1076
  reason: "rate_stale_refused",
1074
- detail: `snapshot ${err.knownStale.staleDays.toString()}d stale`
1077
+ detail: `snapshot ${err.knownStale.staleDays.toString()}d stale`,
1078
+ sourceFailure: readFxSourceFailure(err)
1075
1079
  };
1076
1080
  }
1077
1081
  const cached = fetcher.lastKnown?.();
@@ -1081,7 +1085,8 @@ async function resolveFxSnapshot(fetcher) {
1081
1085
  return {
1082
1086
  ok: false,
1083
1087
  reason: "rate_unavailable",
1084
- detail: err instanceof Error ? err.message : String(err)
1088
+ detail: err instanceof Error ? err.message : String(err),
1089
+ sourceFailure: err instanceof FxRateUnavailableError ? readFxSourceFailure(err) ?? { category: "unknown" } : { category: "unknown" }
1085
1090
  };
1086
1091
  }
1087
1092
  }
@@ -1108,7 +1113,10 @@ async function msatsToFiatMicro(args) {
1108
1113
  // A currency the snapshot never carried is the rate source narrowing, not
1109
1114
  // a currency this DVM doesn't speak — `fxRateFor` separates the two.
1110
1115
  reason: err instanceof FxRateUnavailableError ? "rate_unavailable" : "unsupported_currency",
1111
- detail: err instanceof Error ? err.message : String(err)
1116
+ detail: err instanceof Error ? err.message : String(err),
1117
+ ...err instanceof FxRateUnavailableError && {
1118
+ sourceFailure: readFxSourceFailure(err) ?? { category: "unknown" }
1119
+ }
1112
1120
  };
1113
1121
  }
1114
1122
  const amountMicro = Math.round(amount * 1e6);
@@ -1973,9 +1981,10 @@ async function createX402BatchSettlementServer(opts) {
1973
1981
  throw new Error("x402 batch-settlement payTo must be a 20-byte EVM address");
1974
1982
  }
1975
1983
  const withdrawDelay = opts.config.withdrawDelay ?? X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS;
1976
- if (!Number.isInteger(withdrawDelay) || withdrawDelay < X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS) {
1984
+ const minimumWithdrawDelay = opts.network === X402_BATCH_SETTLEMENT_NETWORK ? 900 : X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS;
1985
+ if (!Number.isInteger(withdrawDelay) || withdrawDelay < minimumWithdrawDelay) {
1977
1986
  throw new Error(
1978
- `x402 batch-settlement withdrawDelay must be at least ${String(X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS)} seconds`
1987
+ `x402 batch-settlement withdrawDelay must be at least ${String(minimumWithdrawDelay)} seconds`
1979
1988
  );
1980
1989
  }
1981
1990
  if (opts.config.autoSettlement !== false && opts.config.autoSettlement?.selectRefundChannels) {
@@ -2076,6 +2085,7 @@ async function createX402BatchSettlementServer(opts) {
2076
2085
  payTo: opts.payTo,
2077
2086
  network: opts.network,
2078
2087
  storage,
2088
+ earnedNative: opts.earnedNative,
2079
2089
  settleIntervalMs: (settlementConfig.settleIntervalSecs ?? X402_BATCH_AUTO_SETTLEMENT.settleIntervalSecs ?? 300) * 1e3,
2080
2090
  ...postgres ? { listWedgedRefunds: () => listWedgedRefunds(postgres) } : {}
2081
2091
  });
@@ -2830,22 +2840,19 @@ function claimCeilingSelector(earnedNative) {
2830
2840
  const charged = BigInt(channel.chargedCumulativeAmount);
2831
2841
  const claimed = BigInt(channel.totalClaimed);
2832
2842
  if (charged <= claimed) continue;
2833
- let earned;
2834
- try {
2835
- earned = await earnedNative(channel.channelId.toLowerCase());
2836
- } catch (error) {
2843
+ const resolved = await x402EarnedCeiling(channel, earnedNative);
2844
+ if (resolved.kind === "unresolved") {
2837
2845
  loggers.x402.warn("x402_claim_ceiling_unresolved", {
2838
2846
  "channel.id": channel.channelId,
2839
- error: error instanceof Error ? error.message : String(error)
2847
+ error: resolved.error instanceof Error ? resolved.error.message : String(resolved.error)
2840
2848
  });
2841
2849
  continue;
2842
2850
  }
2843
- if (earned === void 0) {
2851
+ if (resolved.kind === "unbound") {
2844
2852
  loggers.x402.warn("x402_claim_ceiling_unbound", { "channel.id": channel.channelId });
2845
2853
  continue;
2846
2854
  }
2847
- const wanted = BigInt(earned);
2848
- const ceiling = wanted < charged ? wanted : charged;
2855
+ const { ceiling } = resolved;
2849
2856
  if (ceiling <= claimed) continue;
2850
2857
  selected.push({ ...channel, chargedCumulativeAmount: ceiling.toString() });
2851
2858
  }
@@ -3113,6 +3120,10 @@ function decodeBatchPayment(header) {
3113
3120
  if (!isBatchSettlementDepositPayload2(parsed.payload) && !isBatchSettlementVoucherPayload(parsed.payload) && !isBatchSettlementRefundPayload2(parsed.payload)) {
3114
3121
  throw new Error("invalid batch-settlement payload");
3115
3122
  }
3123
+ const voucher = parsed.payload.voucher;
3124
+ if (typeof voucher.channelId !== "string" || typeof voucher.maxClaimableAmount !== "string" || typeof voucher.signature !== "string") {
3125
+ throw new Error("invalid batch-settlement voucher");
3126
+ }
3116
3127
  return parsed;
3117
3128
  }
3118
3129
  function batchVoucher(payload) {
@@ -3161,10 +3172,66 @@ function isRecord(value) {
3161
3172
  return !!value && typeof value === "object" && !Array.isArray(value);
3162
3173
  }
3163
3174
 
3175
+ // src/sdk/server/payment-diagnostics.ts
3176
+ import { Errors } from "mppx";
3177
+ function paymentFailureFields(error) {
3178
+ let category = "unknown";
3179
+ try {
3180
+ if (error instanceof ProcessedPaymentReplayError) category = "replay_detected";
3181
+ else if (error instanceof CreditLedgerError) category = "ledger_refused";
3182
+ else if (error instanceof Errors.InvalidChallengeError) category = "invalid_challenge";
3183
+ else if (error instanceof Errors.MalformedCredentialError || error instanceof Errors.InvalidPayloadError) category = "malformed_credential";
3184
+ else if (error instanceof Errors.PaymentExpiredError) category = "payment_expired";
3185
+ else if (error instanceof Errors.VerificationFailedError) category = "verification_failed";
3186
+ else if (error instanceof Errors.PaymentInsufficientError || error instanceof Errors.InsufficientBalanceError) category = "insufficient_funds";
3187
+ else if (error instanceof Errors.InvalidSignatureError) category = "invalid_signature";
3188
+ else if (error instanceof Errors.PaymentMethodUnsupportedError) category = "unsupported_method";
3189
+ } catch {
3190
+ }
3191
+ return { failure_category: category, failure_stage: paymentFailureStage(error) };
3192
+ }
3193
+
3164
3194
  // src/sdk/server/payment.ts
3165
- import { randomBytes, randomUUID as randomUUID2 } from "crypto";
3195
+ import { createHash as createHash4, randomBytes, randomUUID as randomUUID2 } from "crypto";
3166
3196
  import { Challenge } from "mppx";
3167
3197
 
3198
+ // src/lib/cashu/verify.ts
3199
+ import { getTokenMetadata } from "@cashu/cashu-ts";
3200
+ function verifyCashuToken(token, acceptedMints) {
3201
+ let mint, unit, totalMsats;
3202
+ try {
3203
+ const meta = getTokenMetadata(token);
3204
+ mint = meta.mint;
3205
+ unit = meta.unit;
3206
+ totalMsats = meta.amount.toNumber() * 1e3;
3207
+ } catch {
3208
+ throw new DvmError(
3209
+ "cashu_invalid_token",
3210
+ "The Cashu token could not be decoded.",
3211
+ "Ensure the token is a valid cashuA... or cashuB... string."
3212
+ );
3213
+ }
3214
+ const normalised = acceptedMints.map(normaliseMintUrl);
3215
+ if (!normalised.includes(normaliseMintUrl(mint))) {
3216
+ throw new DvmError(
3217
+ "cashu_mint_not_accepted",
3218
+ `Mint ${mint} is not accepted by this provider.`,
3219
+ `Accepted mints: ${acceptedMints.join(", ")}`
3220
+ );
3221
+ }
3222
+ return { mint, totalMsats, unit };
3223
+ }
3224
+ function normaliseMintUrl(url) {
3225
+ try {
3226
+ const u = new URL(url);
3227
+ u.hostname = u.hostname.toLowerCase();
3228
+ u.pathname = u.pathname.replace(/\/+/g, "/").replace(/\/+$/, "") || "/";
3229
+ return u.toString().replace(/\/$/, "");
3230
+ } catch {
3231
+ return url.toLowerCase().replace(/\/+$/, "");
3232
+ }
3233
+ }
3234
+
3168
3235
  // src/sdk/server/mpp-units.ts
3169
3236
  function decimalsFor(methodName) {
3170
3237
  switch (methodName) {
@@ -3219,6 +3286,22 @@ function methodNativeAmountToDecimal(amount, methodName) {
3219
3286
  return `${nativeAmount2 / scale}.${(nativeAmount2 % scale).toString().padStart(decimals, "0")}`;
3220
3287
  }
3221
3288
 
3289
+ // src/lib/mints.ts
3290
+ function isTestMintUrl(url) {
3291
+ let host;
3292
+ try {
3293
+ host = new URL(url).hostname.toLowerCase();
3294
+ } catch {
3295
+ return false;
3296
+ }
3297
+ if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1);
3298
+ if (LOOPBACK_HOSTS.has(host)) return true;
3299
+ if (host.endsWith(".localhost")) return true;
3300
+ return TEST_MINT_HOSTS.has(host);
3301
+ }
3302
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]);
3303
+ var TEST_MINT_HOSTS = /* @__PURE__ */ new Set(["testnut.cashu.space", "dvmkit-testmint.fly.dev"]);
3304
+
3222
3305
  // src/sdk/server/wallet-accumulator-receive.ts
3223
3306
  import {
3224
3307
  Amount as Amount2,
@@ -3468,6 +3551,9 @@ async function issueUpfrontChallenges(mpp, requiredMsats, resourcePath, btcUsdRa
3468
3551
  );
3469
3552
  }
3470
3553
  async function verifyUpfrontPayment(opts) {
3554
+ return withPaymentDiagnostics(() => verifyUpfrontPaymentInScope(opts));
3555
+ }
3556
+ async function verifyUpfrontPaymentInScope(opts) {
3471
3557
  const {
3472
3558
  cashuToken,
3473
3559
  requiredMsats,
@@ -3733,7 +3819,10 @@ async function verifyUpfrontPayment(opts) {
3733
3819
  );
3734
3820
  } catch (err) {
3735
3821
  const mapped = mapLedgerCommitError(err, x402Requirements);
3736
- if (mapped) return mapped;
3822
+ if (mapped) {
3823
+ logCredentialRejected("x402", "upfront", err);
3824
+ return mapped;
3825
+ }
3737
3826
  logCredentialRejected("x402", "upfront", err);
3738
3827
  return {
3739
3828
  paidMsats: 0,
@@ -3794,15 +3883,15 @@ async function verifyUpfrontPayment(opts) {
3794
3883
  x402Requirements
3795
3884
  });
3796
3885
  }
3797
- const { verifyX402Payment } = await import("./x402-FT4S4JS4.js");
3798
- const receipt = await verifyX402Payment(
3886
+ const { verifyX402Payment } = await import("./x402-3IQ3HXSG.js");
3887
+ const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
3799
3888
  x402Payment,
3800
3889
  x402Config,
3801
3890
  requiredUsdcMicro,
3802
3891
  x402Resource ?? "/v1/job",
3803
3892
  rate ?? 0,
3804
3893
  x402ExactVersions
3805
- );
3894
+ ));
3806
3895
  if (receipt.verified) {
3807
3896
  const settledUsdcMicro = receipt.amountUsdcMicro;
3808
3897
  const paidMsats = settledUsdcMicro === void 0 ? receipt.amountMsats : x402SettledShare(requiredMsats, Number(requiredUsdcMicro), settledUsdcMicro);
@@ -3846,7 +3935,10 @@ async function verifyUpfrontPayment(opts) {
3846
3935
  });
3847
3936
  } catch (err) {
3848
3937
  const mapped = mapLedgerCommitError(err, x402Requirements, true);
3849
- if (mapped) return mapped;
3938
+ if (mapped) {
3939
+ logCredentialRejected("x402", "upfront", err);
3940
+ return mapped;
3941
+ }
3850
3942
  throw err;
3851
3943
  }
3852
3944
  }
@@ -3954,6 +4046,30 @@ async function verifyUpfrontPayment(opts) {
3954
4046
  }
3955
4047
  const expectedAmount = opts.priceFiat?.currency === "usd" ? fiatMicroToMethodAmount(opts.priceFiat.amountMicro, cred.challenge.method) : msatsToMethodAmount(requiredMsats, cred.challenge.method, rate);
3956
4048
  const sessionBasis = mppSessionBasis(cred);
4049
+ const replayRealm = mpp.realm ?? cred.challenge.realm;
4050
+ if (consumedCredentialStore && await withPaymentStage("replay_store", () => consumedCredentialStore.has(replayRealm, cred.challenge.id))) {
4051
+ const mppChallenges = await issueUpfrontChallenges(
4052
+ mpp,
4053
+ requiredMsats,
4054
+ resourcePath ?? "/v1/job",
4055
+ rate,
4056
+ sessionIssuable,
4057
+ opts.fundOnly?.allowOneShotStablecoin,
4058
+ opts.priceFiat
4059
+ );
4060
+ return {
4061
+ paidMsats: 0,
4062
+ receivedProofs: [],
4063
+ error: {
4064
+ body: paymentErrorBody("payment_invalid", {
4065
+ message: "MPP credential already used"
4066
+ }),
4067
+ status: 402,
4068
+ mppChallenges,
4069
+ ...x402Requirements && { x402Requirements }
4070
+ }
4071
+ };
4072
+ }
3957
4073
  let tempoReceipt;
3958
4074
  const broadcast = async () => {
3959
4075
  const receipt = await loggers.tempo.span(
@@ -3963,7 +4079,7 @@ async function verifyUpfrontPayment(opts) {
3963
4079
  challenge_id: cred.challenge.id,
3964
4080
  amount: expectedAmount
3965
4081
  },
3966
- () => mpp.broadcastCredential(mppCredential, {
4082
+ () => withPaymentStage("verifier_broadcast", () => mpp.broadcastCredential(mppCredential, {
3967
4083
  request: { amount: expectedAmount },
3968
4084
  ...opts.hostingAdmissionId && {
3969
4085
  hostedPaymentIntent: {
@@ -3982,7 +4098,8 @@ async function verifyUpfrontPayment(opts) {
3982
4098
  path: resourcePath ?? "/v1/job",
3983
4099
  nonce: challengeMeta(cred.challenge)?.nonce ?? ""
3984
4100
  }
3985
- })
4101
+ })),
4102
+ { onError: paymentFailureFields }
3986
4103
  );
3987
4104
  tempoReceipt = receipt;
3988
4105
  return receipt;
@@ -4039,7 +4156,10 @@ async function verifyUpfrontPayment(opts) {
4039
4156
  credited = accepted.result;
4040
4157
  } catch (err) {
4041
4158
  const mapped = mapLedgerCommitError(err, x402Requirements, true);
4042
- if (mapped) return mapped;
4159
+ if (mapped) {
4160
+ logCredentialRejected("tempo", "upfront", err);
4161
+ return mapped;
4162
+ }
4043
4163
  throw err;
4044
4164
  }
4045
4165
  } else {
@@ -4067,35 +4187,12 @@ async function verifyUpfrontPayment(opts) {
4067
4187
  }
4068
4188
  };
4069
4189
  }
4070
- if (consumedCredentialStore) {
4071
- const realm = mpp.realm;
4072
- const id = cred.challenge.id;
4073
- if (await consumedCredentialStore.has(realm, id)) {
4074
- const mppChallenges = await issueUpfrontChallenges(
4075
- mpp,
4076
- requiredMsats,
4077
- resourcePath ?? "/v1/job",
4078
- rate,
4079
- sessionIssuable,
4080
- opts.fundOnly?.allowOneShotStablecoin,
4081
- opts.priceFiat
4082
- );
4083
- return {
4084
- paidMsats: 0,
4085
- receivedProofs: [],
4086
- error: {
4087
- body: paymentErrorBody("payment_invalid", {
4088
- message: "MPP credential already used"
4089
- }),
4090
- status: 402,
4091
- mppChallenges,
4092
- ...x402Requirements && { x402Requirements }
4093
- }
4094
- };
4095
- }
4096
- if (!sessionBasis) {
4097
- await consumedCredentialStore.mark(realm, id, CONSUMED_CREDENTIAL_TTL_MS / 1e3);
4098
- }
4190
+ if (consumedCredentialStore && !sessionBasis) {
4191
+ await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
4192
+ replayRealm,
4193
+ cred.challenge.id,
4194
+ CONSUMED_CREDENTIAL_TTL_MS / 1e3
4195
+ ));
4099
4196
  }
4100
4197
  if (ledgerCtx) {
4101
4198
  try {
@@ -4109,16 +4206,27 @@ async function verifyUpfrontPayment(opts) {
4109
4206
  });
4110
4207
  } catch (err) {
4111
4208
  const mapped = mapLedgerCommitError(err, x402Requirements, true);
4112
- if (mapped) return mapped;
4209
+ if (mapped) {
4210
+ logCredentialRejected("tempo", "upfront", err);
4211
+ return mapped;
4212
+ }
4113
4213
  throw err;
4114
4214
  }
4115
4215
  }
4116
4216
  if (sessionBasis && consumedCredentialStore) {
4117
- await consumedCredentialStore.mark(
4118
- mpp.realm,
4217
+ await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
4218
+ replayRealm,
4119
4219
  cred.challenge.id,
4120
4220
  CONSUMED_CREDENTIAL_TTL_MS / 1e3
4121
- );
4221
+ )).catch((error) => {
4222
+ diagnosticConsole.warn({
4223
+ level: "warn",
4224
+ event: "tempo_replay_mark_failed",
4225
+ phase: "upfront",
4226
+ rail: "tempo",
4227
+ ...paymentFailureFields(error)
4228
+ });
4229
+ });
4122
4230
  }
4123
4231
  if (credited) {
4124
4232
  await mpp.completeHostedTempoCharge?.(cred.challenge.id).catch((error) => {
@@ -4327,6 +4435,22 @@ async function verifyTempoSessionManagementCredential(args) {
4327
4435
  throw new Error("Hosted Tempo close requires non-mutating credential validation");
4328
4436
  }
4329
4437
  await args.mpp.validateCredential(credential, verifyOptions);
4438
+ if (args.action === "close") {
4439
+ if (!args.mpp.assertTempoSessionCloseVoucher) {
4440
+ throw new Error("Hosted Tempo close requires stored-channel voucher validation");
4441
+ }
4442
+ const { cumulativeAmount, signature } = credential.payload;
4443
+ if (typeof cumulativeAmount !== "string" || typeof signature !== "string") {
4444
+ throw new Error("Expected a Tempo session close voucher");
4445
+ }
4446
+ await args.mpp.assertTempoSessionCloseVoucher({
4447
+ channelId: payload.channelId,
4448
+ cumulativeAmount: BigInt(cumulativeAmount),
4449
+ signature,
4450
+ descriptor: credential.payload.descriptor,
4451
+ request: credential.challenge.request
4452
+ });
4453
+ }
4330
4454
  }
4331
4455
  await args.onBound?.();
4332
4456
  const receipt = await args.mpp.broadcastCredential(credential, verifyOptions);
@@ -4687,6 +4811,9 @@ function buildPaymentErrorResponse(body, challenges, x402Requirements, status, x
4687
4811
  return new Response(JSON.stringify(mergedBody), { status: status ?? 402, headers });
4688
4812
  }
4689
4813
  async function verifyIncomingPayment(body, opts, snapshot) {
4814
+ return withPaymentDiagnostics(() => verifyIncomingPaymentInScope(body, opts, snapshot));
4815
+ }
4816
+ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
4690
4817
  if (body.type !== "payment") return {};
4691
4818
  if (body.content.cashu_token) {
4692
4819
  if (opts.cashuMode === "p2pk-accumulator" && (opts.accumulatorDb || opts.disposableCashu) && opts.dvmId && opts.lockPubkeys && opts.lockPubkeys.length > 0) {
@@ -4769,15 +4896,15 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4769
4896
  snapshot
4770
4897
  });
4771
4898
  }
4772
- const { verifyX402Payment } = await import("./x402-FT4S4JS4.js");
4773
- const receipt = await verifyX402Payment(
4899
+ const { verifyX402Payment } = await import("./x402-3IQ3HXSG.js");
4900
+ const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4774
4901
  body.content.x402_payment,
4775
4902
  opts.x402Config,
4776
4903
  requiredUsdcMicro,
4777
4904
  opts.resourcePath ?? "/v1/job",
4778
4905
  rate,
4779
4906
  opts.x402ExactVersions
4780
- );
4907
+ ));
4781
4908
  if (!receipt.verified) {
4782
4909
  return {
4783
4910
  error: {
@@ -4826,6 +4953,7 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4826
4953
  } catch (err) {
4827
4954
  const mapped = mapLedgerCommitError(err, void 0, true);
4828
4955
  if (mapped?.error) {
4956
+ logCredentialRejected("x402", "mid-job", err);
4829
4957
  return { error: { body: mapped.error.body, status: mapped.error.status } };
4830
4958
  }
4831
4959
  throw err;
@@ -4933,13 +5061,14 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4933
5061
  challenge_id: credential.challenge.id,
4934
5062
  amount: expectedAmount
4935
5063
  },
4936
- () => mppServer.broadcastCredential(credential, {
5064
+ () => withPaymentStage("verifier_broadcast", () => mppServer.broadcastCredential(credential, {
4937
5065
  request: { amount: expectedAmount },
4938
5066
  ...opts.hostingPaymentSeq !== void 0 && {
4939
5067
  hostedPaymentIntent: { jobId: opts.jobId, paymentSeq: opts.hostingPaymentSeq }
4940
5068
  },
4941
5069
  ...expectedMeta && { meta: expectedMeta }
4942
- })
5070
+ })),
5071
+ { onError: paymentFailureFields }
4943
5072
  );
4944
5073
  let credited;
4945
5074
  if (ledgerCtx) {
@@ -4948,6 +5077,7 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4948
5077
  } catch (err) {
4949
5078
  const mapped = mapLedgerCommitError(err, void 0, true);
4950
5079
  if (mapped?.error) {
5080
+ logCredentialRejected("tempo", "mid-job", err);
4951
5081
  return { error: { body: mapped.error.body, status: mapped.error.status } };
4952
5082
  }
4953
5083
  throw err;
@@ -5142,6 +5272,9 @@ function applyPaymentInfoToJob(job, info) {
5142
5272
  creditPendingPayment(job);
5143
5273
  }
5144
5274
  async function processIncomingPayment(job, body, opts) {
5275
+ return withPaymentDiagnostics(() => processIncomingPaymentInScope(job, body, opts));
5276
+ }
5277
+ async function processIncomingPaymentInScope(job, body, opts) {
5145
5278
  if (body.type !== "payment") return null;
5146
5279
  if (body.content.cashu_token) {
5147
5280
  if (opts.cashuMode === "p2pk-accumulator" && (opts.accumulatorDb || opts.disposableCashu) && opts.dvmId && opts.lockPubkeys && opts.lockPubkeys.length > 0) {
@@ -5194,15 +5327,15 @@ async function processIncomingPayment(job, body, opts) {
5194
5327
  try {
5195
5328
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5196
5329
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5197
- const { verifyX402Payment } = await import("./x402-FT4S4JS4.js");
5198
- const receipt = await verifyX402Payment(
5330
+ const { verifyX402Payment } = await import("./x402-3IQ3HXSG.js");
5331
+ const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5199
5332
  body.content.x402_payment,
5200
5333
  opts.x402Config,
5201
5334
  requiredUsdcMicro,
5202
5335
  opts.resourcePath ?? "/v1/job",
5203
5336
  rate,
5204
5337
  opts.x402ExactVersions
5205
- );
5338
+ ));
5206
5339
  if (receipt.verified) {
5207
5340
  if (job.pendingX402Nonce !== void 0) {
5208
5341
  const incomingNonce = decodePayment(body.content.x402_payment).payload.authorization.nonce;
@@ -5269,13 +5402,14 @@ async function processIncomingPayment(job, body, opts) {
5269
5402
  challenge_id: credential.challenge.id,
5270
5403
  amount: expectedAmount
5271
5404
  },
5272
- () => mppHandle.broadcastCredential(credential, {
5405
+ () => withPaymentStage("verifier_broadcast", () => mppHandle.broadcastCredential(credential, {
5273
5406
  request: { amount: expectedAmount },
5274
5407
  ...opts.hostingPaymentSeq !== void 0 && {
5275
5408
  hostedPaymentIntent: { jobId: opts.jobId, paymentSeq: opts.hostingPaymentSeq }
5276
5409
  },
5277
5410
  ...expectedMeta && { meta: expectedMeta }
5278
- })
5411
+ })),
5412
+ { onError: paymentFailureFields }
5279
5413
  );
5280
5414
  if (!job.pendingMppChallengeIds?.includes(credential.challenge.id)) {
5281
5415
  return {
@@ -5581,7 +5715,10 @@ async function resolveTopUpLedgerContext(opts, snapshot, binding) {
5581
5715
  job_id: opts.jobId,
5582
5716
  credit_id: snapshot.creditId,
5583
5717
  draw_id: snapshot.drawId,
5584
- ...extra
5718
+ ...extra,
5719
+ ...typeof opts.jobId === "string" && {
5720
+ job_id_hash: createHash4("sha256").update(opts.jobId, "utf8").digest("hex")
5721
+ }
5585
5722
  }
5586
5723
  );
5587
5724
  };
@@ -5644,7 +5781,10 @@ async function resolveTopUpLedgerContext(opts, snapshot, binding) {
5644
5781
  logSkip("denomination_unavailable", {
5645
5782
  currency,
5646
5783
  cause: denomination.reason,
5647
- ...denomination.detail !== void 0 && { detail: denomination.detail }
5784
+ ...denomination.sourceFailure && {
5785
+ source_failure: denomination.sourceFailure.category,
5786
+ source_status: denomination.sourceFailure.httpStatus
5787
+ }
5648
5788
  });
5649
5789
  return void 0;
5650
5790
  }
@@ -5793,6 +5933,9 @@ function withDrawBasis(info, ctx, committed) {
5793
5933
  };
5794
5934
  }
5795
5935
  async function fundAndDraw(ctx, funding, tx) {
5936
+ return withPaymentStage("funding_ledger", () => fundAndDrawInScope(ctx, funding, tx));
5937
+ }
5938
+ async function fundAndDrawInScope(ctx, funding, tx) {
5796
5939
  const env = ctx.creditEnvelope;
5797
5940
  const fundedMicro = funding.fundedMicro ?? fundedMicroFor(ctx.priceFiat, funding.paidMsats, ctx.requiredMsats);
5798
5941
  const shortMidJob = ctx.midJob === true && funding.paidMsats < ctx.requiredMsats;
@@ -5831,9 +5974,11 @@ async function fundAndDraw(ctx, funding, tx) {
5831
5974
  throw new Error("deposit outbox enqueue requires the rail's open transaction and a DVM id");
5832
5975
  }
5833
5976
  const commitDeposit = async (result, credit) => {
5977
+ const instrument = credit.rail ? storedCreditBinding(credit).instrument : void 0;
5834
5978
  const report = {
5835
5979
  ...deposit,
5836
- callerPubkey: credit.callerPubkey
5980
+ callerPubkey: credit.callerPubkey,
5981
+ ...instrument && { instrument }
5837
5982
  };
5838
5983
  if (createsDepositLiability && enqueue && tx) {
5839
5984
  await enqueue(tx, creditDepositPayload(ctx.dvmId, report));
@@ -6015,6 +6160,7 @@ function creditDepositPayload(dvmId, funding) {
6015
6160
  creditCurrency: funding.currency,
6016
6161
  fundedAt: funding.fundedAt,
6017
6162
  expiryMs: funding.expiryMs,
6163
+ ...funding.instrument && { instrument: funding.instrument },
6018
6164
  // label a session-funded deposit as one, whether or not the chain
6019
6165
  // binding below could be read. `rail: "tempo"` alone does not say — a
6020
6166
  // `tempo/charge` one-shot funds a credit with no channel at all — so this is
@@ -6219,7 +6365,7 @@ async function commitMarkedFunding(ctx, funding, outerTx) {
6219
6365
  const env = ctx.creditEnvelope;
6220
6366
  const creditId = ctx.fundOnly?.creditId ?? ctx.topUp?.creditId ?? env?.creditId ?? implicitCreditId(funding.rail, funding.paymentId);
6221
6367
  const drawId = ctx.fundOnly?.fundId ?? ctx.topUp?.drawId ?? env?.drawId ?? funding.paymentId;
6222
- await store.record({
6368
+ await withPaymentStage("replay_store", () => store.record({
6223
6369
  dvmId: ctx.dvmId,
6224
6370
  rail: funding.rail,
6225
6371
  paymentId: funding.paymentId,
@@ -6227,10 +6373,10 @@ async function commitMarkedFunding(ctx, funding, outerTx) {
6227
6373
  drawId,
6228
6374
  jobId: ctx.jobId,
6229
6375
  tx
6230
- });
6376
+ }));
6231
6377
  return fundAndDraw(ctx, funding, tx);
6232
6378
  };
6233
- return outerTx ? commit(outerTx) : store.withTx(commit);
6379
+ return outerTx ? commit(outerTx) : withPaymentStage("replay_store", () => store.withTx(commit));
6234
6380
  }
6235
6381
  async function commitMppFunding(ctx, credential, funding) {
6236
6382
  const session = mppSessionBasis(credential);
@@ -6592,15 +6738,14 @@ function x402NotOfferedResult(currency) {
6592
6738
  error: x402NotOfferedPaymentError(currency)
6593
6739
  };
6594
6740
  }
6595
- function logCredentialRejected(rail, phase, _err) {
6741
+ function logCredentialRejected(rail, phase, err) {
6596
6742
  diagnosticConsole.warn(
6597
6743
  {
6598
6744
  level: "warn",
6599
6745
  event: `${rail}_credential_rejected`,
6600
6746
  phase,
6601
6747
  rail,
6602
- error_name: void 0,
6603
- error_message: void 0
6748
+ ...paymentFailureFields(err)
6604
6749
  }
6605
6750
  );
6606
6751
  }
@@ -9058,7 +9203,7 @@ async function handle(c, opts, action) {
9058
9203
  }
9059
9204
 
9060
9205
  // src/sdk/server/x402-exact-settlement.ts
9061
- import { createHash as createHash4 } from "crypto";
9206
+ import { createHash as createHash5 } from "crypto";
9062
9207
  var X402ExactSettlementServer = class {
9063
9208
  constructor(opts) {
9064
9209
  this.opts = opts;
@@ -9384,7 +9529,7 @@ function settlementMayHaveMoved(response) {
9384
9529
  return reason.includes("authorization_already_used") || reason.includes("authorization already used") || /facilitator_status_5\d\d/.test(reason);
9385
9530
  }
9386
9531
  function hash(parts) {
9387
- return createHash4("sha256").update(JSON.stringify(parts)).digest("hex");
9532
+ return createHash5("sha256").update(JSON.stringify(parts)).digest("hex");
9388
9533
  }
9389
9534
  function safeError(err) {
9390
9535
  if (err instanceof Error) return err.name;
@@ -9558,7 +9703,7 @@ var HostingCleanupFence = class {
9558
9703
  };
9559
9704
 
9560
9705
  // src/sdk/server/hosting-settlement-report-obligations.ts
9561
- import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
9706
+ import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
9562
9707
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
9563
9708
  import { BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS3 } from "@x402/evm";
9564
9709
  import { computeChannelId as computeChannelId2 } from "@x402/evm/batch-settlement/client";
@@ -9621,7 +9766,7 @@ var TEMPO_CLOSED_EVENT = TempoSession.Precompile.escrowAbi.find(
9621
9766
  (item) => item.type === "event" && item.name === "ChannelClosed"
9622
9767
  );
9623
9768
  function hash2(value) {
9624
- return createHash5("sha256").update(JSON.stringify(value)).digest("hex");
9769
+ return createHash6("sha256").update(JSON.stringify(value)).digest("hex");
9625
9770
  }
9626
9771
  function hex(value, bytes) {
9627
9772
  return typeof value === "string" && new RegExp(`^0x[0-9a-fA-F]{${bytes * 2}}$`).test(value);
@@ -9948,6 +10093,18 @@ var HostedSettlementReportObligations = class {
9948
10093
  );
9949
10094
  return result.rows[0];
9950
10095
  }
10096
+ /** The original close obligation reserved for this drain, while its report is outstanding. */
10097
+ async pendingTempoClose(channelId, drainId) {
10098
+ const result = await this.pool.query(
10099
+ `SELECT id,tx_hash,created_at_ms FROM sdk_hosted_settlement_report_obligations
10100
+ WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
10101
+ AND original->>'drainId'=$4 AND state NOT IN ('delivered','no_report')
10102
+ ORDER BY created_at_ms DESC LIMIT 1`,
10103
+ [this.dvmId, this.scope, channelId.toLowerCase(), drainId]
10104
+ );
10105
+ const row = result.rows[0];
10106
+ return row && { id: row.id, txHash: row.tx_hash, reservedAtMs: Number(row.created_at_ms) };
10107
+ }
9951
10108
  async submittedIds(rail, operation, txHash, batchId) {
9952
10109
  if (!hex(txHash, 32)) throw new Error("Hosted effect has no exact transaction hash");
9953
10110
  const result = await this.pool.query(
@@ -10087,6 +10244,10 @@ var HostedSettlementReportObligations = class {
10087
10244
  }));
10088
10245
  }
10089
10246
  async recoverOriginalX402Refunds() {
10247
+ const table = await this.pool.query(
10248
+ "SELECT to_regclass('x402_batch_settlements') IS NOT NULL AS present"
10249
+ );
10250
+ if (!table.rows[0]?.present) return [];
10090
10251
  const rows2 = await this.pool.query(
10091
10252
  `SELECT obligation.id,settlement.settle_response AS response
10092
10253
  FROM sdk_hosted_settlement_report_obligations obligation
@@ -10524,7 +10685,7 @@ var HostedSettlementReportObligations = class {
10524
10685
  };
10525
10686
 
10526
10687
  // src/sdk/server/hosting-channel-evidence.ts
10527
- import { createHash as createHash6 } from "crypto";
10688
+ import { createHash as createHash7 } from "crypto";
10528
10689
  import { Session as TempoSession2 } from "mppx/tempo";
10529
10690
  import { createPublicClient as createPublicClient5, http as http5 } from "viem";
10530
10691
  import { BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS4 } from "@x402/evm";
@@ -10575,7 +10736,7 @@ async function readHostedChannelSnapshot(query) {
10575
10736
  };
10576
10737
  const x402HistoryComplete = await history(names[6]);
10577
10738
  const tempoHistoryComplete = await history(names[7]);
10578
- const digest = createHash6("sha256").update(JSON.stringify({
10739
+ const digest = createHash7("sha256").update(JSON.stringify({
10579
10740
  present: [...exists].sort(),
10580
10741
  x402,
10581
10742
  x402Tombstones,
@@ -11657,7 +11818,8 @@ var HostingControl = class {
11657
11818
  pendingAdmissionCount: Number(pending.rows[0].pending),
11658
11819
  pendingAdmissions: inFlight.rows.map((row) => ({ admissionId: row.id, workId: row.job_id })),
11659
11820
  unresolvedAdmissionCount: Number(pending.rows[0].unresolved),
11660
- unresolvedAdmissions: unresolved.rows.map((row) => ({ admissionId: row.id, jobId: row.job_id }))
11821
+ unresolvedAdmissions: unresolved.rows.map((row) => ({ admissionId: row.id, jobId: row.job_id })),
11822
+ jobIdentityHeader: "v1"
11661
11823
  };
11662
11824
  }
11663
11825
  };
@@ -11724,9 +11886,9 @@ import { AsyncLocalStorage } from "async_hooks";
11724
11886
  import { randomBytes as randomBytes3 } from "crypto";
11725
11887
 
11726
11888
  // src/sdk/server/instrumented-kv.ts
11727
- import { createHash as createHash7 } from "crypto";
11889
+ import { createHash as createHash8 } from "crypto";
11728
11890
  function hashKey(key) {
11729
- return createHash7("sha256").update(key).digest("hex").slice(0, 16);
11891
+ return createHash8("sha256").update(key).digest("hex").slice(0, 16);
11730
11892
  }
11731
11893
  function instrumentKVStore(inner) {
11732
11894
  return {
@@ -16502,7 +16664,7 @@ function withWallClockDeadline(promise, timeoutMs) {
16502
16664
  }
16503
16665
 
16504
16666
  // src/sdk/server/app.ts
16505
- import { createHash as createHash9, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "crypto";
16667
+ import { createHash as createHash10, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "crypto";
16506
16668
  import { Hono } from "hono";
16507
16669
  import { getCookie } from "hono/cookie";
16508
16670
  import { cors } from "hono/cors";
@@ -16524,7 +16686,7 @@ import {
16524
16686
  Amount as Amount3,
16525
16687
  CheckStateEnum as CheckStateEnum3,
16526
16688
  getP2PKExpectedWitnessPubkeys as getP2PKExpectedWitnessPubkeys2,
16527
- getTokenMetadata
16689
+ getTokenMetadata as getTokenMetadata2
16528
16690
  } from "@cashu/cashu-ts";
16529
16691
  function installAdminCreditRoutes(app, opts) {
16530
16692
  app.get("/admin/credit/pending-drains", (c) => handlePendingDrains(c, opts));
@@ -16885,7 +17047,7 @@ async function handleParkDrain(c, opts) {
16885
17047
  }
16886
17048
  let parsed;
16887
17049
  try {
16888
- const meta = getTokenMetadata(token);
17050
+ const meta = getTokenMetadata2(token);
16889
17051
  parsed = {
16890
17052
  mint: meta.mint,
16891
17053
  faceSats: Amount3.from(meta.amount).toNumber(),
@@ -19291,7 +19453,7 @@ initSchemaForm();
19291
19453
  }
19292
19454
 
19293
19455
  // src/sdk/server/request-fingerprint.ts
19294
- import { createHash as createHash8 } from "crypto";
19456
+ import { createHash as createHash9 } from "crypto";
19295
19457
  function requestFingerprint(req, opts) {
19296
19458
  const canonical = canonicalize({
19297
19459
  capability: req.capability,
@@ -19299,7 +19461,7 @@ function requestFingerprint(req, opts) {
19299
19461
  params: req.params ?? {},
19300
19462
  data: opts.stripAuthEnvelope ? stripEnvelope(req.data) : req.data
19301
19463
  });
19302
- return createHash8("sha256").update(canonical).digest("hex");
19464
+ return createHash9("sha256").update(canonical).digest("hex");
19303
19465
  }
19304
19466
  var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["pubkey", "signature", "timestamp", "nonce", "auth_statement"]);
19305
19467
  function stripEnvelope(data) {
@@ -19422,7 +19584,7 @@ async function createDVMServer(descriptor, opts) {
19422
19584
  }
19423
19585
  }
19424
19586
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
19425
- const { RevenueReporter } = await import("./revenue-reporter-MAERPYGK.js");
19587
+ const { RevenueReporter } = await import("./revenue-reporter-HV7RMS54.js");
19426
19588
  const reporter = new RevenueReporter(
19427
19589
  opts.db,
19428
19590
  platformUrl,
@@ -19432,7 +19594,7 @@ async function createDVMServer(descriptor, opts) {
19432
19594
  revenueReporter = reporter;
19433
19595
  await reporter.init();
19434
19596
  reporter.startRetryLoop();
19435
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-3Y2U55ZX.js");
19597
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-7P66T77I.js");
19436
19598
  payoutReporter = new PayoutReporter({
19437
19599
  dvmId,
19438
19600
  db: opts.db,
@@ -19717,6 +19879,7 @@ var DVMServer = class {
19717
19879
  `createDVMServer: this DVM prices in ${JSON.stringify(this.pricingCurrency)} (DVMConfig.currency) but its fx fetcher only quotes [${fxCurrencies.join(", ")}]. Wire a fetcher that carries it \u2014 createFxFetcher({ currencies: [..., ${JSON.stringify(this.pricingCurrency)}] }) \u2014 or price in one of the above.`
19718
19880
  );
19719
19881
  }
19882
+ assertCapabilityRates(descriptor.capabilities);
19720
19883
  if (opts.mintHealthTracker) {
19721
19884
  this.mintHealthTracker = opts.mintHealthTracker;
19722
19885
  this.ownsMintHealthTracker = false;
@@ -20297,7 +20460,7 @@ var DVMServer = class {
20297
20460
  const dvmId = this.opts.dvmId ?? `dev:${this.descriptor.name}`;
20298
20461
  return {
20299
20462
  dvm_id: dvmId,
20300
- builder_pubkey: createHash9("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
20463
+ builder_pubkey: createHash10("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
20301
20464
  };
20302
20465
  }
20303
20466
  throw new AuthAudienceError();
@@ -20645,7 +20808,7 @@ var DVMServer = class {
20645
20808
  const paymentAuthorization = ctx.req.header("Authorization");
20646
20809
  const mppArtifact = paymentAuthorization ? (await import("mppx")).Credential.extractPaymentScheme(paymentAuthorization) : void 0;
20647
20810
  const attachedPayment = ctx.req.header("X-Cashu") ?? ctx.req.header("PAYMENT-SIGNATURE") ?? ctx.req.header("X-PAYMENT") ?? mppArtifact;
20648
- const paymentArtifactHash = attachedPayment ? createHash9("sha256").update(attachedPayment).digest("hex") : void 0;
20811
+ const paymentArtifactHash = attachedPayment ? createHash10("sha256").update(attachedPayment).digest("hex") : void 0;
20649
20812
  hostingFundingArtifactHash = paymentArtifactHash;
20650
20813
  if (!await control.admit(admissionId, workId, {
20651
20814
  creditId,
@@ -20789,7 +20952,7 @@ var DVMServer = class {
20789
20952
  // sees only the shortened list cannot tell a DVM that never did
20790
20953
  // sessions from one whose observer is down for a minute.
20791
20954
  ...withheld.length && { withheld },
20792
- realm: this.opts.mpp.realm,
20955
+ ...this.opts.mpp.realm && { realm: this.opts.mpp.realm },
20793
20956
  ...this.opts.mpp.tempoConfiguration && {
20794
20957
  recipient: this.opts.mpp.tempoConfiguration.recipient,
20795
20958
  payment_token: this.opts.mpp.tempoConfiguration.paymentToken,
@@ -20829,17 +20992,20 @@ var DVMServer = class {
20829
20992
  entry.input_schema = toJSONSchema(cap.input, { io: "input" });
20830
20993
  }
20831
20994
  if (cap.example !== void 0) entry.example = cap.example;
20995
+ let pricing;
20832
20996
  if (cap.onQuote?.schema) {
20833
20997
  const qs = cap.onQuote.schema;
20834
- entry.pricing = {
20998
+ pricing = {
20835
20999
  quote: {
20836
21000
  schema: isZodSchema(qs) ? toJSONSchema(qs, { io: "input" }) : qs
20837
21001
  }
20838
21002
  };
20839
21003
  } else {
20840
21004
  const max = this.jobManager.currentPricingMax(name);
20841
- if (max) entry.pricing = { max };
21005
+ if (max) pricing = { max };
20842
21006
  }
21007
+ if (cap.rates) pricing = { ...pricing, rates: cap.rates };
21008
+ if (pricing) entry.pricing = pricing;
20843
21009
  out[name] = entry;
20844
21010
  }
20845
21011
  return out;
@@ -21351,7 +21517,7 @@ var DVMServer = class {
21351
21517
  const authorization = c.req.header("Authorization");
21352
21518
  const mppArtifact = authorization ? (await import("mppx")).Credential.extractPaymentScheme(authorization) : void 0;
21353
21519
  const attachedPayment = c.req.header("X-Cashu") ?? c.req.header("PAYMENT-SIGNATURE") ?? c.req.header("X-PAYMENT") ?? mppArtifact;
21354
- const hostingPaymentArtifactHash = attachedPayment ? createHash9("sha256").update(attachedPayment).digest("hex") : void 0;
21520
+ const hostingPaymentArtifactHash = attachedPayment ? createHash10("sha256").update(attachedPayment).digest("hex") : void 0;
21355
21521
  if (!resumeOnly && this.opts.hostingControlInstance && hostingPaymentArtifactHash) {
21356
21522
  const priorId = await this.opts.hostingControlInstance.acceptedJobByPaymentArtifactHash(
21357
21523
  hostingPaymentArtifactHash
@@ -21649,7 +21815,7 @@ var DVMServer = class {
21649
21815
  }
21650
21816
  const isAnonymous = requesterId === "anonymous" || requesterId === "dev-anonymous";
21651
21817
  const jobToken = isAnonymous ? randomBytes5(32).toString("hex") : void 0;
21652
- const requesterTokenHash = jobToken ? createHash9("sha256").update(jobToken).digest("hex") : void 0;
21818
+ const requesterTokenHash = jobToken ? createHash10("sha256").update(jobToken).digest("hex") : void 0;
21653
21819
  const job = jm.createJob(
21654
21820
  normalisedBody,
21655
21821
  requesterId,
@@ -22361,7 +22527,7 @@ function isJobOwner(c, job) {
22361
22527
  if (job.requesterTokenHash) {
22362
22528
  const token = c.req.header("X-Job-Token");
22363
22529
  if (!token) return false;
22364
- const provided = createHash9("sha256").update(token).digest("hex");
22530
+ const provided = createHash10("sha256").update(token).digest("hex");
22365
22531
  return timingSafeEqualHex(provided, job.requesterTokenHash);
22366
22532
  }
22367
22533
  return c.get("requesterId") === job.requesterId;
@@ -22379,7 +22545,7 @@ function buildSubmitResponse(c, job, jobToken, contentFields) {
22379
22545
  else if (job.status === "completed") res.status = "completed";
22380
22546
  Object.assign(res, terminalResponseFields(job), contentFields);
22381
22547
  removeUnavailableContent(res);
22382
- return c.json(res, job.status === "completed" ? 200 : 202);
22548
+ return c.json(res, job.status === "completed" ? 200 : 202, jobIdentityHeaders(job.id));
22383
22549
  }
22384
22550
  function requestRecoveryStore(store) {
22385
22551
  return store.findJobByRequestId && store.claimRequestId && store.resumeRequestId && store.releaseRequestIdClaim ? store : void 0;
@@ -22445,7 +22611,10 @@ function buildRequestResultResponse(c, requestId, job, contentFields) {
22445
22611
  if (job.paymentTransactionHash) res.transaction_hash = job.paymentTransactionHash;
22446
22612
  Object.assign(res, terminalResponseFields(job), contentFields);
22447
22613
  removeUnavailableContent(res);
22448
- return c.json(res, isTerminal(job.status) ? 200 : 202);
22614
+ return c.json(res, isTerminal(job.status) ? 200 : 202, jobIdentityHeaders(job.id));
22615
+ }
22616
+ function jobIdentityHeaders(jobId) {
22617
+ return isJobIdentityValue(jobId) ? { [JOB_IDENTITY_HEADER]: jobId } : {};
22449
22618
  }
22450
22619
  function requestResultNotFound(c, requestId) {
22451
22620
  return c.json(
@@ -22636,6 +22805,55 @@ function validateMessageContent(type, content, devMode) {
22636
22805
  }
22637
22806
  return null;
22638
22807
  }
22808
+ var MAX_RATES = 5;
22809
+ var MAX_RATE_AMOUNT_LENGTH = 12;
22810
+ var MAX_RATE_TEXT_LENGTH = 24;
22811
+ var RATE_AMOUNT_RE = /^\d+(\.\d+)?$/;
22812
+ var RATE_KEYS = /* @__PURE__ */ new Set(["label", "amount", "currency", "per"]);
22813
+ function assertCapabilityRates(capabilities) {
22814
+ for (const [name, cap] of Object.entries(capabilities)) {
22815
+ if (cap.rates === void 0) continue;
22816
+ const refuse = (detail) => {
22817
+ throw new Error(`createDVMServer: capability ${JSON.stringify(name)}: ${detail}`);
22818
+ };
22819
+ const rates = cap.rates;
22820
+ if (!Array.isArray(rates) || rates.length === 0 || rates.length > MAX_RATES) {
22821
+ refuse(`rates must be an array of 1 to ${MAX_RATES} entries`);
22822
+ }
22823
+ if (cap.price === void 0 && cap.onQuote === void 0) {
22824
+ refuse("rates is declared on a free capability; set `price` or `onQuote`, or remove `rates`");
22825
+ }
22826
+ rates.forEach((rate, i) => {
22827
+ const at = `rates[${i}]`;
22828
+ if (typeof rate !== "object" || rate === null || Array.isArray(rate)) {
22829
+ refuse(`${at} must be an object with amount, currency and per`);
22830
+ }
22831
+ const entry = rate;
22832
+ for (const key of Object.keys(entry)) {
22833
+ if (!RATE_KEYS.has(key)) refuse(`${at}.${key} is not a rate field (label, amount, currency, per)`);
22834
+ }
22835
+ const amount = entry.amount;
22836
+ if (typeof amount !== "string" || amount.length > MAX_RATE_AMOUNT_LENGTH || !RATE_AMOUNT_RE.test(amount) || // Above zero without parsing to a float: some digit is non-zero.
22837
+ !/[1-9]/.test(amount)) {
22838
+ refuse(
22839
+ `${at}.amount must be a decimal dollar string above zero of at most ${MAX_RATE_AMOUNT_LENGTH} characters, like "0.00015" (got ${JSON.stringify(amount)})`
22840
+ );
22841
+ }
22842
+ if (entry.currency !== "USD") {
22843
+ refuse(`${at}.currency must be "USD" (got ${JSON.stringify(entry.currency)})`);
22844
+ }
22845
+ assertRateText(entry.per, `${at}.per`, refuse);
22846
+ if (entry.label !== void 0) assertRateText(entry.label, `${at}.label`, refuse);
22847
+ });
22848
+ }
22849
+ }
22850
+ function assertRateText(value, field, refuse) {
22851
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_RATE_TEXT_LENGTH || value.trim() !== value) {
22852
+ refuse(
22853
+ `${field} must be a string of 1 to ${MAX_RATE_TEXT_LENGTH} characters with no leading or trailing whitespace (got ${JSON.stringify(value)})`
22854
+ );
22855
+ }
22856
+ }
22639
22857
 
22640
22858
  // src/sdk/server/tempo-observer-health.ts
22641
22859
  var TEMPO_OBSERVER_HEALTH_REFRESH_TTL_MS = 60 * 1e3;
@@ -22848,6 +23066,7 @@ export {
22848
23066
  derivedFundCreditId,
22849
23067
  drawSettlementRef,
22850
23068
  rateUnavailablePaymentError,
23069
+ isTestMintUrl,
22851
23070
  X402_BATCH_SETTLEMENT_NETWORK,
22852
23071
  X402_BATCH_SETTLEMENT_MAINNET_NETWORK,
22853
23072
  X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS,