@dvmkit/sdk 0.6.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 (73) hide show
  1. package/README.md +39 -2
  2. package/dist/{chunk-PLG4XER6.js → chunk-2NUFE2PG.js} +47 -5
  3. package/dist/{chunk-7ZUKL4IV.js → chunk-3E4C6W7W.js} +16 -5
  4. package/dist/{chunk-HHBDBMEO.js → chunk-53SNRGQQ.js} +375 -158
  5. package/dist/{chunk-IWQHUSMO.js → chunk-62FI3NJF.js} +78 -2
  6. package/dist/chunk-6HJEVCVI.js +17 -0
  7. package/dist/chunk-A262WVYX.js +6 -0
  8. package/dist/{chunk-RLTSCKRI.js → chunk-BPYMEMQX.js} +50 -3
  9. package/dist/{chunk-IV6Q7NRW.js → chunk-C5RIQWG4.js} +1 -1
  10. package/dist/{chunk-GAOAYHZA.js → chunk-FBQVOOYF.js} +37 -6
  11. package/dist/{chunk-Z4ZT5LNT.js → chunk-GEI4MAXE.js} +3 -3
  12. package/dist/{chunk-FYRR2UEZ.js → chunk-GLIAR7RN.js} +1 -1
  13. package/dist/{chunk-X7EZ5CLI.js → chunk-HCATFJDD.js} +3 -3
  14. package/dist/{chunk-BYCEKO65.js → chunk-HPCXF3DV.js} +220 -32
  15. package/dist/{chunk-JH3SX3LY.js → chunk-IRXIH775.js} +6 -6
  16. package/dist/{chunk-QR2EF2NI.js → chunk-KCQDXIJU.js} +1 -42
  17. package/dist/chunk-MDW7ZR7V.js +47 -0
  18. package/dist/{chunk-KA6VN5FK.js → chunk-MQYM4T66.js} +3 -3
  19. package/dist/{chunk-P6QON7ER.js → chunk-NV7Z7377.js} +1 -1
  20. package/dist/{chunk-OQA7IHI4.js → chunk-OBBVDKLE.js} +484 -234
  21. package/dist/{chunk-EQLA4FFY.js → chunk-OHUXCAOM.js} +17 -6
  22. package/dist/{chunk-FI4CST2D.js → chunk-PZ5IAWFD.js} +5 -7
  23. package/dist/{chunk-IXR5RFK7.js → chunk-R3TQSXWT.js} +191 -5
  24. package/dist/{chunk-VHNPVGSA.js → chunk-T5DIXOLR.js} +3 -3
  25. package/dist/{chunk-RCM3P2HE.js → chunk-UKD3R2KU.js} +55 -4
  26. package/dist/chunk-WMGXJQI3.js +120 -0
  27. package/dist/{chunk-N5RZ5HVB.js → chunk-X6S2CHFM.js} +2 -2
  28. package/dist/{chunk-ETXDTUJO.js → chunk-ZCNPIILZ.js} +112 -1
  29. package/dist/{content-retention-BTGSQRfP.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
  30. package/dist/{credit-ledger-MI2H7NEC.js → credit-ledger-MFIIOSZH.js} +4 -5
  31. package/dist/{credit-menu-BAsrRu3z.d.ts → credit-menu-D2t1-Tf6.d.ts} +13 -6
  32. package/dist/{fx-BgDmyTdd.d.ts → fx-Ql4nvdbh.d.ts} +10 -3
  33. package/dist/index.d.ts +5 -5
  34. package/dist/index.js +248 -16
  35. package/dist/internal/caller.d.ts +4586 -7318
  36. package/dist/internal/caller.js +4431 -8127
  37. package/dist/internal/index.js +1 -1
  38. package/dist/internal/server.d.ts +7 -7
  39. package/dist/internal/server.js +30 -33
  40. package/dist/{job-store-BVeF5_Jq.d.ts → job-store-DCvJQUX0.d.ts} +1 -1
  41. package/dist/{lightning-backend-40fIzz1W.d.ts → lightning-backend-CY5ML3km.d.ts} +4 -2
  42. package/dist/memory-credit-ledger-44NE4PZC.js +11 -0
  43. package/dist/{mpp-setup-RF5FXGGV.js → mpp-setup-HRWUBULX.js} +4 -2
  44. package/dist/{payout-reporter-3Y2U55ZX.js → payout-reporter-7P66T77I.js} +6 -4
  45. package/dist/{postgres-consumed-credential-store-BUVCAPLS.js → postgres-consumed-credential-store-IDGPFKIL.js} +3 -3
  46. package/dist/{postgres-job-store-VHLQBJZC.js → postgres-job-store-UKNJ2F6Z.js} +2 -2
  47. package/dist/{postgres-replay-store-ARZSQ45X.js → postgres-replay-store-PMA4ADXN.js} +2 -2
  48. package/dist/{pricing-XT2IAPNE.js → pricing-52XJHJ6K.js} +12 -12
  49. package/dist/{revenue-reporter-MAERPYGK.js → revenue-reporter-HV7RMS54.js} +2 -2
  50. package/dist/server/index.d.ts +12 -11
  51. package/dist/server/index.js +49 -53
  52. package/dist/{step-cache-CPdH1hei.d.ts → step-cache-BOWAn_BH.d.ts} +84 -7
  53. package/dist/{tempo-charge-store-Q3D7KYMH.js → tempo-charge-store-P2Z4RMU7.js} +2 -2
  54. package/dist/{tempo-session-store-ZRNCOX7P.js → tempo-session-store-VSP7KLBS.js} +3 -3
  55. package/dist/testing/index.d.ts +3 -3
  56. package/dist/testing/index.js +7 -6
  57. package/dist/{usd-AWlrwlM5.d.ts → usd-MAtMp3DB.d.ts} +1 -1
  58. package/dist/{x402-R6HPBY5D.js → x402-3IQ3HXSG.js} +9 -29
  59. package/package.json +3 -2
  60. package/dist/chunk-AT6V3SY7.js +0 -102
  61. package/dist/chunk-B2IROPQF.js +0 -1844
  62. package/dist/chunk-CZULM42S.js +0 -143
  63. package/dist/chunk-GFY5FRX3.js +0 -218
  64. package/dist/chunk-HCLVYZUZ.js +0 -244
  65. package/dist/chunk-LE74UEV7.js +0 -1071
  66. package/dist/chunk-LIRAHP7B.js +0 -53
  67. package/dist/chunk-MZRJR2CW.js +0 -776
  68. package/dist/chunk-OFVV37XC.js +0 -57
  69. package/dist/chunk-PAIM66YA.js +0 -347
  70. package/dist/chunk-UOCZXO2Z.js +0 -1663
  71. package/dist/memory-credit-ledger-W2BAUYJP.js +0 -12
  72. package/dist/tempo-lifecycle-ZBBLFBMH.js +0 -15
  73. package/dist/tempo-wallet-B5XXV3YH.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-PLG4XER6.js";
19
+ verifyWithFacilitator,
20
+ withPaymentDiagnostics,
21
+ withPaymentStage
22
+ } from "./chunk-2NUFE2PG.js";
12
23
  import {
13
24
  MemoryCreditLedger
14
- } from "./chunk-FYRR2UEZ.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,23 +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,
83
+ signDrainReceipt,
84
+ signFundingReceipt,
85
+ signReceipt,
69
86
  toLockPubkey,
70
- verifyCashuToken,
71
87
  verifyChallenge
72
- } from "./chunk-OQA7IHI4.js";
88
+ } from "./chunk-OBBVDKLE.js";
73
89
  import {
74
90
  X402_BATCH_SETTLEMENT_SCHEME,
75
91
  X402_DEFAULT_FACILITATOR,
@@ -82,38 +98,28 @@ import {
82
98
  encodePaymentRequiredHeader,
83
99
  encodeSettleResponseHeader,
84
100
  exactEvmAuthorization,
85
- paymentRequiredV2FromV1,
86
- x402NetworkToCaip2
87
- } from "./chunk-IWQHUSMO.js";
88
- import {
89
- IMPLICIT_CREDIT_TTL_MS,
90
- computeResultHash,
91
- formatStaleness,
92
101
  msatsToUsd,
93
102
  msatsToUsdc,
94
- signDrainReceipt,
95
- signFundingReceipt,
96
- signReceipt,
97
- usdcToMsats
98
- } from "./chunk-PAIM66YA.js";
99
- import {
100
- resolveRpcOverride
101
- } from "./chunk-CZULM42S.js";
103
+ paymentRequiredV2FromV1,
104
+ resolveRpcOverride,
105
+ usdcToMsats,
106
+ x402NetworkToCaip2
107
+ } from "./chunk-62FI3NJF.js";
102
108
  import {
103
109
  RAIL_REFUNDABLE,
104
110
  canonicalRequestPath,
111
+ canonicalize,
105
112
  isZodSchema
106
- } from "./chunk-RCM3P2HE.js";
113
+ } from "./chunk-UKD3R2KU.js";
107
114
  import {
108
- canonicalize
109
- } from "./chunk-OFVV37XC.js";
115
+ createConsoleLogger
116
+ } from "./chunk-MDW7ZR7V.js";
110
117
  import {
111
- createConsoleLogger,
112
118
  loggers,
113
119
  pinDimension,
114
120
  withSpan,
115
121
  withTraceContext
116
- } from "./chunk-QR2EF2NI.js";
122
+ } from "./chunk-KCQDXIJU.js";
117
123
  import {
118
124
  StepCache
119
125
  } from "./chunk-BQJIFRAZ.js";
@@ -122,16 +128,15 @@ import {
122
128
  InvalidCurrencyError,
123
129
  UnsupportedCurrencyError,
124
130
  createFxFetcher,
125
- fxRateFor,
126
- resolveFxSourceFromEnv,
127
- validateCurrency
128
- } from "./chunk-BYCEKO65.js";
129
- import {
130
131
  fiatToSatsCeil,
132
+ fxRateFor,
131
133
  parseUsdPrice,
134
+ readFxSourceFailure,
135
+ resolveFxSourceFromEnv,
132
136
  roundUsd,
133
- satsToFiat
134
- } from "./chunk-AT6V3SY7.js";
137
+ satsToFiat,
138
+ validateCurrency
139
+ } from "./chunk-HPCXF3DV.js";
135
140
  import {
136
141
  CreditLedger,
137
142
  CreditLedgerError,
@@ -147,30 +152,26 @@ import {
147
152
  isNonChannelBitcoinRail,
148
153
  netOwedSats,
149
154
  x402SettlementPending
150
- } from "./chunk-JH3SX3LY.js";
151
- import {
152
- withSdkInitLock
153
- } from "./chunk-COYKJRMM.js";
155
+ } from "./chunk-IRXIH775.js";
154
156
  import {
155
157
  withPostgresRecoveryContext
156
- } from "./chunk-P6QON7ER.js";
158
+ } from "./chunk-NV7Z7377.js";
157
159
  import {
160
+ DvmError,
158
161
  x402NetworkByCaip2
159
- } from "./chunk-RLTSCKRI.js";
160
- import {
161
- TEMPO_MODERATO_CHAIN_ID,
162
- TEMPO_MODERATO_RPC,
163
- advertisedMethods,
164
- challengeMeta,
165
- installHostedTempoChargeGuard,
166
- installHostedTempoCloseGuard,
167
- isTempoInsufficientFundsError,
168
- resolveTempoNetwork
169
- } from "./chunk-IXR5RFK7.js";
162
+ } from "./chunk-BPYMEMQX.js";
170
163
  import {
171
164
  createDiagnosticCapabilityAuthority,
172
165
  diagnosticConsole
173
- } 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";
174
175
 
175
176
  // src/sdk/server/lock-pubkey-store.ts
176
177
  async function initLockPubkeyTable(db) {
@@ -1073,7 +1074,8 @@ async function resolveFxSnapshot(fetcher) {
1073
1074
  return {
1074
1075
  ok: false,
1075
1076
  reason: "rate_stale_refused",
1076
- detail: `snapshot ${err.knownStale.staleDays.toString()}d stale`
1077
+ detail: `snapshot ${err.knownStale.staleDays.toString()}d stale`,
1078
+ sourceFailure: readFxSourceFailure(err)
1077
1079
  };
1078
1080
  }
1079
1081
  const cached = fetcher.lastKnown?.();
@@ -1083,7 +1085,8 @@ async function resolveFxSnapshot(fetcher) {
1083
1085
  return {
1084
1086
  ok: false,
1085
1087
  reason: "rate_unavailable",
1086
- 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" }
1087
1090
  };
1088
1091
  }
1089
1092
  }
@@ -1110,7 +1113,10 @@ async function msatsToFiatMicro(args) {
1110
1113
  // A currency the snapshot never carried is the rate source narrowing, not
1111
1114
  // a currency this DVM doesn't speak — `fxRateFor` separates the two.
1112
1115
  reason: err instanceof FxRateUnavailableError ? "rate_unavailable" : "unsupported_currency",
1113
- 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
+ }
1114
1120
  };
1115
1121
  }
1116
1122
  const amountMicro = Math.round(amount * 1e6);
@@ -1975,9 +1981,10 @@ async function createX402BatchSettlementServer(opts) {
1975
1981
  throw new Error("x402 batch-settlement payTo must be a 20-byte EVM address");
1976
1982
  }
1977
1983
  const withdrawDelay = opts.config.withdrawDelay ?? X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS;
1978
- 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) {
1979
1986
  throw new Error(
1980
- `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`
1981
1988
  );
1982
1989
  }
1983
1990
  if (opts.config.autoSettlement !== false && opts.config.autoSettlement?.selectRefundChannels) {
@@ -2078,6 +2085,7 @@ async function createX402BatchSettlementServer(opts) {
2078
2085
  payTo: opts.payTo,
2079
2086
  network: opts.network,
2080
2087
  storage,
2088
+ earnedNative: opts.earnedNative,
2081
2089
  settleIntervalMs: (settlementConfig.settleIntervalSecs ?? X402_BATCH_AUTO_SETTLEMENT.settleIntervalSecs ?? 300) * 1e3,
2082
2090
  ...postgres ? { listWedgedRefunds: () => listWedgedRefunds(postgres) } : {}
2083
2091
  });
@@ -2832,22 +2840,19 @@ function claimCeilingSelector(earnedNative) {
2832
2840
  const charged = BigInt(channel.chargedCumulativeAmount);
2833
2841
  const claimed = BigInt(channel.totalClaimed);
2834
2842
  if (charged <= claimed) continue;
2835
- let earned;
2836
- try {
2837
- earned = await earnedNative(channel.channelId.toLowerCase());
2838
- } catch (error) {
2843
+ const resolved = await x402EarnedCeiling(channel, earnedNative);
2844
+ if (resolved.kind === "unresolved") {
2839
2845
  loggers.x402.warn("x402_claim_ceiling_unresolved", {
2840
2846
  "channel.id": channel.channelId,
2841
- error: error instanceof Error ? error.message : String(error)
2847
+ error: resolved.error instanceof Error ? resolved.error.message : String(resolved.error)
2842
2848
  });
2843
2849
  continue;
2844
2850
  }
2845
- if (earned === void 0) {
2851
+ if (resolved.kind === "unbound") {
2846
2852
  loggers.x402.warn("x402_claim_ceiling_unbound", { "channel.id": channel.channelId });
2847
2853
  continue;
2848
2854
  }
2849
- const wanted = BigInt(earned);
2850
- const ceiling = wanted < charged ? wanted : charged;
2855
+ const { ceiling } = resolved;
2851
2856
  if (ceiling <= claimed) continue;
2852
2857
  selected.push({ ...channel, chargedCumulativeAmount: ceiling.toString() });
2853
2858
  }
@@ -3115,6 +3120,10 @@ function decodeBatchPayment(header) {
3115
3120
  if (!isBatchSettlementDepositPayload2(parsed.payload) && !isBatchSettlementVoucherPayload(parsed.payload) && !isBatchSettlementRefundPayload2(parsed.payload)) {
3116
3121
  throw new Error("invalid batch-settlement payload");
3117
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
+ }
3118
3127
  return parsed;
3119
3128
  }
3120
3129
  function batchVoucher(payload) {
@@ -3163,10 +3172,66 @@ function isRecord(value) {
3163
3172
  return !!value && typeof value === "object" && !Array.isArray(value);
3164
3173
  }
3165
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
+
3166
3194
  // src/sdk/server/payment.ts
3167
- import { randomBytes, randomUUID as randomUUID2 } from "crypto";
3195
+ import { createHash as createHash4, randomBytes, randomUUID as randomUUID2 } from "crypto";
3168
3196
  import { Challenge } from "mppx";
3169
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
+
3170
3235
  // src/sdk/server/mpp-units.ts
3171
3236
  function decimalsFor(methodName) {
3172
3237
  switch (methodName) {
@@ -3221,6 +3286,22 @@ function methodNativeAmountToDecimal(amount, methodName) {
3221
3286
  return `${nativeAmount2 / scale}.${(nativeAmount2 % scale).toString().padStart(decimals, "0")}`;
3222
3287
  }
3223
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
+
3224
3305
  // src/sdk/server/wallet-accumulator-receive.ts
3225
3306
  import {
3226
3307
  Amount as Amount2,
@@ -3470,6 +3551,9 @@ async function issueUpfrontChallenges(mpp, requiredMsats, resourcePath, btcUsdRa
3470
3551
  );
3471
3552
  }
3472
3553
  async function verifyUpfrontPayment(opts) {
3554
+ return withPaymentDiagnostics(() => verifyUpfrontPaymentInScope(opts));
3555
+ }
3556
+ async function verifyUpfrontPaymentInScope(opts) {
3473
3557
  const {
3474
3558
  cashuToken,
3475
3559
  requiredMsats,
@@ -3735,7 +3819,10 @@ async function verifyUpfrontPayment(opts) {
3735
3819
  );
3736
3820
  } catch (err) {
3737
3821
  const mapped = mapLedgerCommitError(err, x402Requirements);
3738
- if (mapped) return mapped;
3822
+ if (mapped) {
3823
+ logCredentialRejected("x402", "upfront", err);
3824
+ return mapped;
3825
+ }
3739
3826
  logCredentialRejected("x402", "upfront", err);
3740
3827
  return {
3741
3828
  paidMsats: 0,
@@ -3796,15 +3883,15 @@ async function verifyUpfrontPayment(opts) {
3796
3883
  x402Requirements
3797
3884
  });
3798
3885
  }
3799
- const { verifyX402Payment } = await import("./x402-R6HPBY5D.js");
3800
- const receipt = await verifyX402Payment(
3886
+ const { verifyX402Payment } = await import("./x402-3IQ3HXSG.js");
3887
+ const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
3801
3888
  x402Payment,
3802
3889
  x402Config,
3803
3890
  requiredUsdcMicro,
3804
3891
  x402Resource ?? "/v1/job",
3805
3892
  rate ?? 0,
3806
3893
  x402ExactVersions
3807
- );
3894
+ ));
3808
3895
  if (receipt.verified) {
3809
3896
  const settledUsdcMicro = receipt.amountUsdcMicro;
3810
3897
  const paidMsats = settledUsdcMicro === void 0 ? receipt.amountMsats : x402SettledShare(requiredMsats, Number(requiredUsdcMicro), settledUsdcMicro);
@@ -3848,7 +3935,10 @@ async function verifyUpfrontPayment(opts) {
3848
3935
  });
3849
3936
  } catch (err) {
3850
3937
  const mapped = mapLedgerCommitError(err, x402Requirements, true);
3851
- if (mapped) return mapped;
3938
+ if (mapped) {
3939
+ logCredentialRejected("x402", "upfront", err);
3940
+ return mapped;
3941
+ }
3852
3942
  throw err;
3853
3943
  }
3854
3944
  }
@@ -3956,6 +4046,30 @@ async function verifyUpfrontPayment(opts) {
3956
4046
  }
3957
4047
  const expectedAmount = opts.priceFiat?.currency === "usd" ? fiatMicroToMethodAmount(opts.priceFiat.amountMicro, cred.challenge.method) : msatsToMethodAmount(requiredMsats, cred.challenge.method, rate);
3958
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
+ }
3959
4073
  let tempoReceipt;
3960
4074
  const broadcast = async () => {
3961
4075
  const receipt = await loggers.tempo.span(
@@ -3965,7 +4079,7 @@ async function verifyUpfrontPayment(opts) {
3965
4079
  challenge_id: cred.challenge.id,
3966
4080
  amount: expectedAmount
3967
4081
  },
3968
- () => mpp.broadcastCredential(mppCredential, {
4082
+ () => withPaymentStage("verifier_broadcast", () => mpp.broadcastCredential(mppCredential, {
3969
4083
  request: { amount: expectedAmount },
3970
4084
  ...opts.hostingAdmissionId && {
3971
4085
  hostedPaymentIntent: {
@@ -3984,7 +4098,8 @@ async function verifyUpfrontPayment(opts) {
3984
4098
  path: resourcePath ?? "/v1/job",
3985
4099
  nonce: challengeMeta(cred.challenge)?.nonce ?? ""
3986
4100
  }
3987
- })
4101
+ })),
4102
+ { onError: paymentFailureFields }
3988
4103
  );
3989
4104
  tempoReceipt = receipt;
3990
4105
  return receipt;
@@ -4041,7 +4156,10 @@ async function verifyUpfrontPayment(opts) {
4041
4156
  credited = accepted.result;
4042
4157
  } catch (err) {
4043
4158
  const mapped = mapLedgerCommitError(err, x402Requirements, true);
4044
- if (mapped) return mapped;
4159
+ if (mapped) {
4160
+ logCredentialRejected("tempo", "upfront", err);
4161
+ return mapped;
4162
+ }
4045
4163
  throw err;
4046
4164
  }
4047
4165
  } else {
@@ -4069,35 +4187,12 @@ async function verifyUpfrontPayment(opts) {
4069
4187
  }
4070
4188
  };
4071
4189
  }
4072
- if (consumedCredentialStore) {
4073
- const realm = mpp.realm;
4074
- const id = cred.challenge.id;
4075
- if (await consumedCredentialStore.has(realm, id)) {
4076
- const mppChallenges = await issueUpfrontChallenges(
4077
- mpp,
4078
- requiredMsats,
4079
- resourcePath ?? "/v1/job",
4080
- rate,
4081
- sessionIssuable,
4082
- opts.fundOnly?.allowOneShotStablecoin,
4083
- opts.priceFiat
4084
- );
4085
- return {
4086
- paidMsats: 0,
4087
- receivedProofs: [],
4088
- error: {
4089
- body: paymentErrorBody("payment_invalid", {
4090
- message: "MPP credential already used"
4091
- }),
4092
- status: 402,
4093
- mppChallenges,
4094
- ...x402Requirements && { x402Requirements }
4095
- }
4096
- };
4097
- }
4098
- if (!sessionBasis) {
4099
- await consumedCredentialStore.mark(realm, id, CONSUMED_CREDENTIAL_TTL_MS / 1e3);
4100
- }
4190
+ if (consumedCredentialStore && !sessionBasis) {
4191
+ await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
4192
+ replayRealm,
4193
+ cred.challenge.id,
4194
+ CONSUMED_CREDENTIAL_TTL_MS / 1e3
4195
+ ));
4101
4196
  }
4102
4197
  if (ledgerCtx) {
4103
4198
  try {
@@ -4111,16 +4206,27 @@ async function verifyUpfrontPayment(opts) {
4111
4206
  });
4112
4207
  } catch (err) {
4113
4208
  const mapped = mapLedgerCommitError(err, x402Requirements, true);
4114
- if (mapped) return mapped;
4209
+ if (mapped) {
4210
+ logCredentialRejected("tempo", "upfront", err);
4211
+ return mapped;
4212
+ }
4115
4213
  throw err;
4116
4214
  }
4117
4215
  }
4118
4216
  if (sessionBasis && consumedCredentialStore) {
4119
- await consumedCredentialStore.mark(
4120
- mpp.realm,
4217
+ await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
4218
+ replayRealm,
4121
4219
  cred.challenge.id,
4122
4220
  CONSUMED_CREDENTIAL_TTL_MS / 1e3
4123
- );
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
+ });
4124
4230
  }
4125
4231
  if (credited) {
4126
4232
  await mpp.completeHostedTempoCharge?.(cred.challenge.id).catch((error) => {
@@ -4329,6 +4435,22 @@ async function verifyTempoSessionManagementCredential(args) {
4329
4435
  throw new Error("Hosted Tempo close requires non-mutating credential validation");
4330
4436
  }
4331
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
+ }
4332
4454
  }
4333
4455
  await args.onBound?.();
4334
4456
  const receipt = await args.mpp.broadcastCredential(credential, verifyOptions);
@@ -4689,6 +4811,9 @@ function buildPaymentErrorResponse(body, challenges, x402Requirements, status, x
4689
4811
  return new Response(JSON.stringify(mergedBody), { status: status ?? 402, headers });
4690
4812
  }
4691
4813
  async function verifyIncomingPayment(body, opts, snapshot) {
4814
+ return withPaymentDiagnostics(() => verifyIncomingPaymentInScope(body, opts, snapshot));
4815
+ }
4816
+ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
4692
4817
  if (body.type !== "payment") return {};
4693
4818
  if (body.content.cashu_token) {
4694
4819
  if (opts.cashuMode === "p2pk-accumulator" && (opts.accumulatorDb || opts.disposableCashu) && opts.dvmId && opts.lockPubkeys && opts.lockPubkeys.length > 0) {
@@ -4771,15 +4896,15 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4771
4896
  snapshot
4772
4897
  });
4773
4898
  }
4774
- const { verifyX402Payment } = await import("./x402-R6HPBY5D.js");
4775
- const receipt = await verifyX402Payment(
4899
+ const { verifyX402Payment } = await import("./x402-3IQ3HXSG.js");
4900
+ const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4776
4901
  body.content.x402_payment,
4777
4902
  opts.x402Config,
4778
4903
  requiredUsdcMicro,
4779
4904
  opts.resourcePath ?? "/v1/job",
4780
4905
  rate,
4781
4906
  opts.x402ExactVersions
4782
- );
4907
+ ));
4783
4908
  if (!receipt.verified) {
4784
4909
  return {
4785
4910
  error: {
@@ -4828,6 +4953,7 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4828
4953
  } catch (err) {
4829
4954
  const mapped = mapLedgerCommitError(err, void 0, true);
4830
4955
  if (mapped?.error) {
4956
+ logCredentialRejected("x402", "mid-job", err);
4831
4957
  return { error: { body: mapped.error.body, status: mapped.error.status } };
4832
4958
  }
4833
4959
  throw err;
@@ -4935,13 +5061,14 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4935
5061
  challenge_id: credential.challenge.id,
4936
5062
  amount: expectedAmount
4937
5063
  },
4938
- () => mppServer.broadcastCredential(credential, {
5064
+ () => withPaymentStage("verifier_broadcast", () => mppServer.broadcastCredential(credential, {
4939
5065
  request: { amount: expectedAmount },
4940
5066
  ...opts.hostingPaymentSeq !== void 0 && {
4941
5067
  hostedPaymentIntent: { jobId: opts.jobId, paymentSeq: opts.hostingPaymentSeq }
4942
5068
  },
4943
5069
  ...expectedMeta && { meta: expectedMeta }
4944
- })
5070
+ })),
5071
+ { onError: paymentFailureFields }
4945
5072
  );
4946
5073
  let credited;
4947
5074
  if (ledgerCtx) {
@@ -4950,6 +5077,7 @@ async function verifyIncomingPayment(body, opts, snapshot) {
4950
5077
  } catch (err) {
4951
5078
  const mapped = mapLedgerCommitError(err, void 0, true);
4952
5079
  if (mapped?.error) {
5080
+ logCredentialRejected("tempo", "mid-job", err);
4953
5081
  return { error: { body: mapped.error.body, status: mapped.error.status } };
4954
5082
  }
4955
5083
  throw err;
@@ -5144,6 +5272,9 @@ function applyPaymentInfoToJob(job, info) {
5144
5272
  creditPendingPayment(job);
5145
5273
  }
5146
5274
  async function processIncomingPayment(job, body, opts) {
5275
+ return withPaymentDiagnostics(() => processIncomingPaymentInScope(job, body, opts));
5276
+ }
5277
+ async function processIncomingPaymentInScope(job, body, opts) {
5147
5278
  if (body.type !== "payment") return null;
5148
5279
  if (body.content.cashu_token) {
5149
5280
  if (opts.cashuMode === "p2pk-accumulator" && (opts.accumulatorDb || opts.disposableCashu) && opts.dvmId && opts.lockPubkeys && opts.lockPubkeys.length > 0) {
@@ -5196,15 +5327,15 @@ async function processIncomingPayment(job, body, opts) {
5196
5327
  try {
5197
5328
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5198
5329
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5199
- const { verifyX402Payment } = await import("./x402-R6HPBY5D.js");
5200
- const receipt = await verifyX402Payment(
5330
+ const { verifyX402Payment } = await import("./x402-3IQ3HXSG.js");
5331
+ const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5201
5332
  body.content.x402_payment,
5202
5333
  opts.x402Config,
5203
5334
  requiredUsdcMicro,
5204
5335
  opts.resourcePath ?? "/v1/job",
5205
5336
  rate,
5206
5337
  opts.x402ExactVersions
5207
- );
5338
+ ));
5208
5339
  if (receipt.verified) {
5209
5340
  if (job.pendingX402Nonce !== void 0) {
5210
5341
  const incomingNonce = decodePayment(body.content.x402_payment).payload.authorization.nonce;
@@ -5271,13 +5402,14 @@ async function processIncomingPayment(job, body, opts) {
5271
5402
  challenge_id: credential.challenge.id,
5272
5403
  amount: expectedAmount
5273
5404
  },
5274
- () => mppHandle.broadcastCredential(credential, {
5405
+ () => withPaymentStage("verifier_broadcast", () => mppHandle.broadcastCredential(credential, {
5275
5406
  request: { amount: expectedAmount },
5276
5407
  ...opts.hostingPaymentSeq !== void 0 && {
5277
5408
  hostedPaymentIntent: { jobId: opts.jobId, paymentSeq: opts.hostingPaymentSeq }
5278
5409
  },
5279
5410
  ...expectedMeta && { meta: expectedMeta }
5280
- })
5411
+ })),
5412
+ { onError: paymentFailureFields }
5281
5413
  );
5282
5414
  if (!job.pendingMppChallengeIds?.includes(credential.challenge.id)) {
5283
5415
  return {
@@ -5583,7 +5715,10 @@ async function resolveTopUpLedgerContext(opts, snapshot, binding) {
5583
5715
  job_id: opts.jobId,
5584
5716
  credit_id: snapshot.creditId,
5585
5717
  draw_id: snapshot.drawId,
5586
- ...extra
5718
+ ...extra,
5719
+ ...typeof opts.jobId === "string" && {
5720
+ job_id_hash: createHash4("sha256").update(opts.jobId, "utf8").digest("hex")
5721
+ }
5587
5722
  }
5588
5723
  );
5589
5724
  };
@@ -5646,7 +5781,10 @@ async function resolveTopUpLedgerContext(opts, snapshot, binding) {
5646
5781
  logSkip("denomination_unavailable", {
5647
5782
  currency,
5648
5783
  cause: denomination.reason,
5649
- ...denomination.detail !== void 0 && { detail: denomination.detail }
5784
+ ...denomination.sourceFailure && {
5785
+ source_failure: denomination.sourceFailure.category,
5786
+ source_status: denomination.sourceFailure.httpStatus
5787
+ }
5650
5788
  });
5651
5789
  return void 0;
5652
5790
  }
@@ -5795,6 +5933,9 @@ function withDrawBasis(info, ctx, committed) {
5795
5933
  };
5796
5934
  }
5797
5935
  async function fundAndDraw(ctx, funding, tx) {
5936
+ return withPaymentStage("funding_ledger", () => fundAndDrawInScope(ctx, funding, tx));
5937
+ }
5938
+ async function fundAndDrawInScope(ctx, funding, tx) {
5798
5939
  const env = ctx.creditEnvelope;
5799
5940
  const fundedMicro = funding.fundedMicro ?? fundedMicroFor(ctx.priceFiat, funding.paidMsats, ctx.requiredMsats);
5800
5941
  const shortMidJob = ctx.midJob === true && funding.paidMsats < ctx.requiredMsats;
@@ -5833,9 +5974,11 @@ async function fundAndDraw(ctx, funding, tx) {
5833
5974
  throw new Error("deposit outbox enqueue requires the rail's open transaction and a DVM id");
5834
5975
  }
5835
5976
  const commitDeposit = async (result, credit) => {
5977
+ const instrument = credit.rail ? storedCreditBinding(credit).instrument : void 0;
5836
5978
  const report = {
5837
5979
  ...deposit,
5838
- callerPubkey: credit.callerPubkey
5980
+ callerPubkey: credit.callerPubkey,
5981
+ ...instrument && { instrument }
5839
5982
  };
5840
5983
  if (createsDepositLiability && enqueue && tx) {
5841
5984
  await enqueue(tx, creditDepositPayload(ctx.dvmId, report));
@@ -6017,6 +6160,7 @@ function creditDepositPayload(dvmId, funding) {
6017
6160
  creditCurrency: funding.currency,
6018
6161
  fundedAt: funding.fundedAt,
6019
6162
  expiryMs: funding.expiryMs,
6163
+ ...funding.instrument && { instrument: funding.instrument },
6020
6164
  // label a session-funded deposit as one, whether or not the chain
6021
6165
  // binding below could be read. `rail: "tempo"` alone does not say — a
6022
6166
  // `tempo/charge` one-shot funds a credit with no channel at all — so this is
@@ -6221,7 +6365,7 @@ async function commitMarkedFunding(ctx, funding, outerTx) {
6221
6365
  const env = ctx.creditEnvelope;
6222
6366
  const creditId = ctx.fundOnly?.creditId ?? ctx.topUp?.creditId ?? env?.creditId ?? implicitCreditId(funding.rail, funding.paymentId);
6223
6367
  const drawId = ctx.fundOnly?.fundId ?? ctx.topUp?.drawId ?? env?.drawId ?? funding.paymentId;
6224
- await store.record({
6368
+ await withPaymentStage("replay_store", () => store.record({
6225
6369
  dvmId: ctx.dvmId,
6226
6370
  rail: funding.rail,
6227
6371
  paymentId: funding.paymentId,
@@ -6229,10 +6373,10 @@ async function commitMarkedFunding(ctx, funding, outerTx) {
6229
6373
  drawId,
6230
6374
  jobId: ctx.jobId,
6231
6375
  tx
6232
- });
6376
+ }));
6233
6377
  return fundAndDraw(ctx, funding, tx);
6234
6378
  };
6235
- return outerTx ? commit(outerTx) : store.withTx(commit);
6379
+ return outerTx ? commit(outerTx) : withPaymentStage("replay_store", () => store.withTx(commit));
6236
6380
  }
6237
6381
  async function commitMppFunding(ctx, credential, funding) {
6238
6382
  const session = mppSessionBasis(credential);
@@ -6594,15 +6738,14 @@ function x402NotOfferedResult(currency) {
6594
6738
  error: x402NotOfferedPaymentError(currency)
6595
6739
  };
6596
6740
  }
6597
- function logCredentialRejected(rail, phase, _err) {
6741
+ function logCredentialRejected(rail, phase, err) {
6598
6742
  diagnosticConsole.warn(
6599
6743
  {
6600
6744
  level: "warn",
6601
6745
  event: `${rail}_credential_rejected`,
6602
6746
  phase,
6603
6747
  rail,
6604
- error_name: void 0,
6605
- error_message: void 0
6748
+ ...paymentFailureFields(err)
6606
6749
  }
6607
6750
  );
6608
6751
  }
@@ -9060,7 +9203,7 @@ async function handle(c, opts, action) {
9060
9203
  }
9061
9204
 
9062
9205
  // src/sdk/server/x402-exact-settlement.ts
9063
- import { createHash as createHash4 } from "crypto";
9206
+ import { createHash as createHash5 } from "crypto";
9064
9207
  var X402ExactSettlementServer = class {
9065
9208
  constructor(opts) {
9066
9209
  this.opts = opts;
@@ -9386,7 +9529,7 @@ function settlementMayHaveMoved(response) {
9386
9529
  return reason.includes("authorization_already_used") || reason.includes("authorization already used") || /facilitator_status_5\d\d/.test(reason);
9387
9530
  }
9388
9531
  function hash(parts) {
9389
- return createHash4("sha256").update(JSON.stringify(parts)).digest("hex");
9532
+ return createHash5("sha256").update(JSON.stringify(parts)).digest("hex");
9390
9533
  }
9391
9534
  function safeError(err) {
9392
9535
  if (err instanceof Error) return err.name;
@@ -9560,7 +9703,7 @@ var HostingCleanupFence = class {
9560
9703
  };
9561
9704
 
9562
9705
  // src/sdk/server/hosting-settlement-report-obligations.ts
9563
- import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
9706
+ import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
9564
9707
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
9565
9708
  import { BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS3 } from "@x402/evm";
9566
9709
  import { computeChannelId as computeChannelId2 } from "@x402/evm/batch-settlement/client";
@@ -9623,7 +9766,7 @@ var TEMPO_CLOSED_EVENT = TempoSession.Precompile.escrowAbi.find(
9623
9766
  (item) => item.type === "event" && item.name === "ChannelClosed"
9624
9767
  );
9625
9768
  function hash2(value) {
9626
- return createHash5("sha256").update(JSON.stringify(value)).digest("hex");
9769
+ return createHash6("sha256").update(JSON.stringify(value)).digest("hex");
9627
9770
  }
9628
9771
  function hex(value, bytes) {
9629
9772
  return typeof value === "string" && new RegExp(`^0x[0-9a-fA-F]{${bytes * 2}}$`).test(value);
@@ -9950,6 +10093,18 @@ var HostedSettlementReportObligations = class {
9950
10093
  );
9951
10094
  return result.rows[0];
9952
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
+ }
9953
10108
  async submittedIds(rail, operation, txHash, batchId) {
9954
10109
  if (!hex(txHash, 32)) throw new Error("Hosted effect has no exact transaction hash");
9955
10110
  const result = await this.pool.query(
@@ -10089,6 +10244,10 @@ var HostedSettlementReportObligations = class {
10089
10244
  }));
10090
10245
  }
10091
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 [];
10092
10251
  const rows2 = await this.pool.query(
10093
10252
  `SELECT obligation.id,settlement.settle_response AS response
10094
10253
  FROM sdk_hosted_settlement_report_obligations obligation
@@ -10526,7 +10685,7 @@ var HostedSettlementReportObligations = class {
10526
10685
  };
10527
10686
 
10528
10687
  // src/sdk/server/hosting-channel-evidence.ts
10529
- import { createHash as createHash6 } from "crypto";
10688
+ import { createHash as createHash7 } from "crypto";
10530
10689
  import { Session as TempoSession2 } from "mppx/tempo";
10531
10690
  import { createPublicClient as createPublicClient5, http as http5 } from "viem";
10532
10691
  import { BATCH_SETTLEMENT_ADDRESS as BATCH_SETTLEMENT_ADDRESS4 } from "@x402/evm";
@@ -10577,7 +10736,7 @@ async function readHostedChannelSnapshot(query) {
10577
10736
  };
10578
10737
  const x402HistoryComplete = await history(names[6]);
10579
10738
  const tempoHistoryComplete = await history(names[7]);
10580
- const digest = createHash6("sha256").update(JSON.stringify({
10739
+ const digest = createHash7("sha256").update(JSON.stringify({
10581
10740
  present: [...exists].sort(),
10582
10741
  x402,
10583
10742
  x402Tombstones,
@@ -11659,7 +11818,8 @@ var HostingControl = class {
11659
11818
  pendingAdmissionCount: Number(pending.rows[0].pending),
11660
11819
  pendingAdmissions: inFlight.rows.map((row) => ({ admissionId: row.id, workId: row.job_id })),
11661
11820
  unresolvedAdmissionCount: Number(pending.rows[0].unresolved),
11662
- 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"
11663
11823
  };
11664
11824
  }
11665
11825
  };
@@ -11726,9 +11886,9 @@ import { AsyncLocalStorage } from "async_hooks";
11726
11886
  import { randomBytes as randomBytes3 } from "crypto";
11727
11887
 
11728
11888
  // src/sdk/server/instrumented-kv.ts
11729
- import { createHash as createHash7 } from "crypto";
11889
+ import { createHash as createHash8 } from "crypto";
11730
11890
  function hashKey(key) {
11731
- return createHash7("sha256").update(key).digest("hex").slice(0, 16);
11891
+ return createHash8("sha256").update(key).digest("hex").slice(0, 16);
11732
11892
  }
11733
11893
  function instrumentKVStore(inner) {
11734
11894
  return {
@@ -16504,7 +16664,7 @@ function withWallClockDeadline(promise, timeoutMs) {
16504
16664
  }
16505
16665
 
16506
16666
  // src/sdk/server/app.ts
16507
- 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";
16508
16668
  import { Hono } from "hono";
16509
16669
  import { getCookie } from "hono/cookie";
16510
16670
  import { cors } from "hono/cors";
@@ -16526,7 +16686,7 @@ import {
16526
16686
  Amount as Amount3,
16527
16687
  CheckStateEnum as CheckStateEnum3,
16528
16688
  getP2PKExpectedWitnessPubkeys as getP2PKExpectedWitnessPubkeys2,
16529
- getTokenMetadata
16689
+ getTokenMetadata as getTokenMetadata2
16530
16690
  } from "@cashu/cashu-ts";
16531
16691
  function installAdminCreditRoutes(app, opts) {
16532
16692
  app.get("/admin/credit/pending-drains", (c) => handlePendingDrains(c, opts));
@@ -16887,7 +17047,7 @@ async function handleParkDrain(c, opts) {
16887
17047
  }
16888
17048
  let parsed;
16889
17049
  try {
16890
- const meta = getTokenMetadata(token);
17050
+ const meta = getTokenMetadata2(token);
16891
17051
  parsed = {
16892
17052
  mint: meta.mint,
16893
17053
  faceSats: Amount3.from(meta.amount).toNumber(),
@@ -19293,7 +19453,7 @@ initSchemaForm();
19293
19453
  }
19294
19454
 
19295
19455
  // src/sdk/server/request-fingerprint.ts
19296
- import { createHash as createHash8 } from "crypto";
19456
+ import { createHash as createHash9 } from "crypto";
19297
19457
  function requestFingerprint(req, opts) {
19298
19458
  const canonical = canonicalize({
19299
19459
  capability: req.capability,
@@ -19301,7 +19461,7 @@ function requestFingerprint(req, opts) {
19301
19461
  params: req.params ?? {},
19302
19462
  data: opts.stripAuthEnvelope ? stripEnvelope(req.data) : req.data
19303
19463
  });
19304
- return createHash8("sha256").update(canonical).digest("hex");
19464
+ return createHash9("sha256").update(canonical).digest("hex");
19305
19465
  }
19306
19466
  var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["pubkey", "signature", "timestamp", "nonce", "auth_statement"]);
19307
19467
  function stripEnvelope(data) {
@@ -19424,7 +19584,7 @@ async function createDVMServer(descriptor, opts) {
19424
19584
  }
19425
19585
  }
19426
19586
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
19427
- const { RevenueReporter } = await import("./revenue-reporter-MAERPYGK.js");
19587
+ const { RevenueReporter } = await import("./revenue-reporter-HV7RMS54.js");
19428
19588
  const reporter = new RevenueReporter(
19429
19589
  opts.db,
19430
19590
  platformUrl,
@@ -19434,7 +19594,7 @@ async function createDVMServer(descriptor, opts) {
19434
19594
  revenueReporter = reporter;
19435
19595
  await reporter.init();
19436
19596
  reporter.startRetryLoop();
19437
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-3Y2U55ZX.js");
19597
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-7P66T77I.js");
19438
19598
  payoutReporter = new PayoutReporter({
19439
19599
  dvmId,
19440
19600
  db: opts.db,
@@ -19719,6 +19879,7 @@ var DVMServer = class {
19719
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.`
19720
19880
  );
19721
19881
  }
19882
+ assertCapabilityRates(descriptor.capabilities);
19722
19883
  if (opts.mintHealthTracker) {
19723
19884
  this.mintHealthTracker = opts.mintHealthTracker;
19724
19885
  this.ownsMintHealthTracker = false;
@@ -20299,7 +20460,7 @@ var DVMServer = class {
20299
20460
  const dvmId = this.opts.dvmId ?? `dev:${this.descriptor.name}`;
20300
20461
  return {
20301
20462
  dvm_id: dvmId,
20302
- builder_pubkey: createHash9("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
20463
+ builder_pubkey: createHash10("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
20303
20464
  };
20304
20465
  }
20305
20466
  throw new AuthAudienceError();
@@ -20647,7 +20808,7 @@ var DVMServer = class {
20647
20808
  const paymentAuthorization = ctx.req.header("Authorization");
20648
20809
  const mppArtifact = paymentAuthorization ? (await import("mppx")).Credential.extractPaymentScheme(paymentAuthorization) : void 0;
20649
20810
  const attachedPayment = ctx.req.header("X-Cashu") ?? ctx.req.header("PAYMENT-SIGNATURE") ?? ctx.req.header("X-PAYMENT") ?? mppArtifact;
20650
- const paymentArtifactHash = attachedPayment ? createHash9("sha256").update(attachedPayment).digest("hex") : void 0;
20811
+ const paymentArtifactHash = attachedPayment ? createHash10("sha256").update(attachedPayment).digest("hex") : void 0;
20651
20812
  hostingFundingArtifactHash = paymentArtifactHash;
20652
20813
  if (!await control.admit(admissionId, workId, {
20653
20814
  creditId,
@@ -20791,7 +20952,7 @@ var DVMServer = class {
20791
20952
  // sees only the shortened list cannot tell a DVM that never did
20792
20953
  // sessions from one whose observer is down for a minute.
20793
20954
  ...withheld.length && { withheld },
20794
- realm: this.opts.mpp.realm,
20955
+ ...this.opts.mpp.realm && { realm: this.opts.mpp.realm },
20795
20956
  ...this.opts.mpp.tempoConfiguration && {
20796
20957
  recipient: this.opts.mpp.tempoConfiguration.recipient,
20797
20958
  payment_token: this.opts.mpp.tempoConfiguration.paymentToken,
@@ -20831,17 +20992,20 @@ var DVMServer = class {
20831
20992
  entry.input_schema = toJSONSchema(cap.input, { io: "input" });
20832
20993
  }
20833
20994
  if (cap.example !== void 0) entry.example = cap.example;
20995
+ let pricing;
20834
20996
  if (cap.onQuote?.schema) {
20835
20997
  const qs = cap.onQuote.schema;
20836
- entry.pricing = {
20998
+ pricing = {
20837
20999
  quote: {
20838
21000
  schema: isZodSchema(qs) ? toJSONSchema(qs, { io: "input" }) : qs
20839
21001
  }
20840
21002
  };
20841
21003
  } else {
20842
21004
  const max = this.jobManager.currentPricingMax(name);
20843
- if (max) entry.pricing = { max };
21005
+ if (max) pricing = { max };
20844
21006
  }
21007
+ if (cap.rates) pricing = { ...pricing, rates: cap.rates };
21008
+ if (pricing) entry.pricing = pricing;
20845
21009
  out[name] = entry;
20846
21010
  }
20847
21011
  return out;
@@ -21353,7 +21517,7 @@ var DVMServer = class {
21353
21517
  const authorization = c.req.header("Authorization");
21354
21518
  const mppArtifact = authorization ? (await import("mppx")).Credential.extractPaymentScheme(authorization) : void 0;
21355
21519
  const attachedPayment = c.req.header("X-Cashu") ?? c.req.header("PAYMENT-SIGNATURE") ?? c.req.header("X-PAYMENT") ?? mppArtifact;
21356
- const hostingPaymentArtifactHash = attachedPayment ? createHash9("sha256").update(attachedPayment).digest("hex") : void 0;
21520
+ const hostingPaymentArtifactHash = attachedPayment ? createHash10("sha256").update(attachedPayment).digest("hex") : void 0;
21357
21521
  if (!resumeOnly && this.opts.hostingControlInstance && hostingPaymentArtifactHash) {
21358
21522
  const priorId = await this.opts.hostingControlInstance.acceptedJobByPaymentArtifactHash(
21359
21523
  hostingPaymentArtifactHash
@@ -21651,7 +21815,7 @@ var DVMServer = class {
21651
21815
  }
21652
21816
  const isAnonymous = requesterId === "anonymous" || requesterId === "dev-anonymous";
21653
21817
  const jobToken = isAnonymous ? randomBytes5(32).toString("hex") : void 0;
21654
- const requesterTokenHash = jobToken ? createHash9("sha256").update(jobToken).digest("hex") : void 0;
21818
+ const requesterTokenHash = jobToken ? createHash10("sha256").update(jobToken).digest("hex") : void 0;
21655
21819
  const job = jm.createJob(
21656
21820
  normalisedBody,
21657
21821
  requesterId,
@@ -22363,7 +22527,7 @@ function isJobOwner(c, job) {
22363
22527
  if (job.requesterTokenHash) {
22364
22528
  const token = c.req.header("X-Job-Token");
22365
22529
  if (!token) return false;
22366
- const provided = createHash9("sha256").update(token).digest("hex");
22530
+ const provided = createHash10("sha256").update(token).digest("hex");
22367
22531
  return timingSafeEqualHex(provided, job.requesterTokenHash);
22368
22532
  }
22369
22533
  return c.get("requesterId") === job.requesterId;
@@ -22381,7 +22545,7 @@ function buildSubmitResponse(c, job, jobToken, contentFields) {
22381
22545
  else if (job.status === "completed") res.status = "completed";
22382
22546
  Object.assign(res, terminalResponseFields(job), contentFields);
22383
22547
  removeUnavailableContent(res);
22384
- return c.json(res, job.status === "completed" ? 200 : 202);
22548
+ return c.json(res, job.status === "completed" ? 200 : 202, jobIdentityHeaders(job.id));
22385
22549
  }
22386
22550
  function requestRecoveryStore(store) {
22387
22551
  return store.findJobByRequestId && store.claimRequestId && store.resumeRequestId && store.releaseRequestIdClaim ? store : void 0;
@@ -22447,7 +22611,10 @@ function buildRequestResultResponse(c, requestId, job, contentFields) {
22447
22611
  if (job.paymentTransactionHash) res.transaction_hash = job.paymentTransactionHash;
22448
22612
  Object.assign(res, terminalResponseFields(job), contentFields);
22449
22613
  removeUnavailableContent(res);
22450
- 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 } : {};
22451
22618
  }
22452
22619
  function requestResultNotFound(c, requestId) {
22453
22620
  return c.json(
@@ -22638,6 +22805,55 @@ function validateMessageContent(type, content, devMode) {
22638
22805
  }
22639
22806
  return null;
22640
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
+ }
22641
22857
 
22642
22858
  // src/sdk/server/tempo-observer-health.ts
22643
22859
  var TEMPO_OBSERVER_HEALTH_REFRESH_TTL_MS = 60 * 1e3;
@@ -22850,6 +23066,7 @@ export {
22850
23066
  derivedFundCreditId,
22851
23067
  drawSettlementRef,
22852
23068
  rateUnavailablePaymentError,
23069
+ isTestMintUrl,
22853
23070
  X402_BATCH_SETTLEMENT_NETWORK,
22854
23071
  X402_BATCH_SETTLEMENT_MAINNET_NETWORK,
22855
23072
  X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS,