@dvmkit/sdk 0.7.0-rc.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +57 -261
  3. package/dist/{chunk-HG2CZPRJ.js → chunk-2I3OHMTY.js} +1 -1
  4. package/dist/{chunk-BDHJVTWA.js → chunk-55NKZQOC.js} +2 -2
  5. package/dist/{chunk-LQQ6H4UB.js → chunk-5CQKSLAU.js} +1 -1
  6. package/dist/{chunk-X5RY2G3C.js → chunk-5K5E6ME6.js} +1 -1
  7. package/dist/chunk-66HGCPBU.js +25 -0
  8. package/dist/{chunk-6RYCZLCK.js → chunk-6FEHRZO7.js} +1 -1
  9. package/dist/{chunk-XEOTTFLH.js → chunk-ATXDKX6F.js} +2 -2
  10. package/dist/{chunk-SX4WCPW7.js → chunk-BL6LRWXD.js} +2 -43
  11. package/dist/{chunk-FXDQMJQ2.js → chunk-DZQYQN2X.js} +31 -4
  12. package/dist/{chunk-FOJKZ4E7.js → chunk-EBCLWOKF.js} +1 -1
  13. package/dist/{chunk-KXWROQGK.js → chunk-FUJ36YDV.js} +1 -24
  14. package/dist/{chunk-KYCLHWKO.js → chunk-GB5XPAA5.js} +191 -5
  15. package/dist/{chunk-Z6EJOVIU.js → chunk-GSPV3UIM.js} +98 -51
  16. package/dist/{chunk-MYJOV4R4.js → chunk-I6PHA54S.js} +2 -2
  17. package/dist/chunk-IFWUWHTW.js +47 -0
  18. package/dist/{chunk-GDCIHJAR.js → chunk-P5WEZXJ2.js} +1 -1
  19. package/dist/{chunk-YK54BJ2L.js → chunk-PRSKC7AD.js} +1 -1
  20. package/dist/{chunk-KB7MEWBE.js → chunk-QPXI7YPH.js} +1 -1
  21. package/dist/{chunk-XO5EUHCW.js → chunk-RTFBVXCN.js} +2 -2
  22. package/dist/{chunk-BQP3LU2O.js → chunk-TFYY5APM.js} +1 -1
  23. package/dist/{chunk-WP7UX6RF.js → chunk-VYP6ADCS.js} +2 -2
  24. package/dist/{chunk-OFSOLHRX.js → chunk-WMGXJQI3.js} +1 -1
  25. package/dist/{chunk-3VFEX2Y3.js → chunk-X3JEME6P.js} +1 -1
  26. package/dist/{chunk-NUPEAWRM.js → chunk-XXLUIXPO.js} +1 -1
  27. package/dist/{chunk-BIZR5ORK.js → chunk-YPSJU3OH.js} +3 -3
  28. package/dist/{content-retention-U7dJM7Ph.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
  29. package/dist/{credit-ledger-SW4BGMNH.js → credit-ledger-AOU6S34N.js} +3 -3
  30. package/dist/{credit-menu-CUJdCfpt.d.ts → credit-menu-C-8g2cHT.d.ts} +5 -5
  31. package/dist/{fx-BvG2Wnjk.d.ts → fx-Ql4nvdbh.d.ts} +1 -1
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +4 -4
  34. package/dist/internal/caller.d.ts +10 -8
  35. package/dist/internal/caller.js +12 -9
  36. package/dist/internal/server.d.ts +7 -7
  37. package/dist/internal/server.js +22 -20
  38. package/dist/{job-store-DjgN2AOk.d.ts → job-store-DCvJQUX0.d.ts} +1 -1
  39. package/dist/{lightning-backend-DeasHNOP.d.ts → lightning-backend-CY5ML3km.d.ts} +1 -1
  40. package/dist/{memory-credit-ledger-E4C3VQE4.js → memory-credit-ledger-4HWYINKJ.js} +4 -4
  41. package/dist/{mpp-setup-BYKGEYFA.js → mpp-setup-2BKTPEK6.js} +4 -2
  42. package/dist/{payout-reporter-3UVWPGMO.js → payout-reporter-HQSIIBFL.js} +2 -2
  43. package/dist/{postgres-consumed-credential-store-EZ4KETWH.js → postgres-consumed-credential-store-R7DNU6CM.js} +1 -1
  44. package/dist/{postgres-job-store-3YBSGLYU.js → postgres-job-store-6VGFL6XE.js} +2 -2
  45. package/dist/{postgres-replay-store-GIYZS4LJ.js → postgres-replay-store-SY26NUCI.js} +2 -2
  46. package/dist/{pricing-DG5IJ5QF.js → pricing-H2RA4X2R.js} +3 -3
  47. package/dist/{revenue-reporter-E56J3NZK.js → revenue-reporter-X52GPZ5U.js} +2 -2
  48. package/dist/server/index.d.ts +10 -10
  49. package/dist/server/index.js +30 -28
  50. package/dist/{step-cache-C9JRSFhM.d.ts → step-cache-BOWAn_BH.d.ts} +32 -4
  51. package/dist/{tempo-charge-store-WTBIX2JX.js → tempo-charge-store-NKXJ2AXE.js} +2 -2
  52. package/dist/{tempo-session-store-AWIFUAA6.js → tempo-session-store-ZSDQIWSU.js} +3 -3
  53. package/dist/testing/index.d.ts +3 -3
  54. package/dist/testing/index.js +8 -6
  55. package/dist/{usd-DXDkHn_8.d.ts → usd-MAtMp3DB.d.ts} +1 -1
  56. package/dist/{x402-BN26TQZ6.js → x402-Q4S36NHQ.js} +6 -5
  57. package/package.json +18 -3
@@ -45,30 +45,7 @@ function isOpaqueToken(segment) {
45
45
  return false;
46
46
  }
47
47
 
48
- // src/lib/caller-loggers.ts
49
- var callerLoggers = buildNoopLoggers();
50
- function initCallerLoggers(nextLoggers) {
51
- callerLoggers = nextLoggers;
52
- }
53
- function buildNoopLoggers() {
54
- const noopLogger = {
55
- // eslint-disable-next-line @typescript-eslint/no-empty-function -- standalone caller has no exporter
56
- info() {
57
- },
58
- async span(_name, _attrs, fn) {
59
- return fn();
60
- }
61
- };
62
- return {
63
- cashu: noopLogger,
64
- lightning: noopLogger,
65
- x402: noopLogger
66
- };
67
- }
68
-
69
48
  export {
70
49
  redactUrl,
71
- redactUrlsInText,
72
- callerLoggers,
73
- initCallerLoggers
50
+ redactUrlsInText
74
51
  };
@@ -1,6 +1,9 @@
1
+ import {
2
+ loggers
3
+ } from "./chunk-BL6LRWXD.js";
1
4
  import {
2
5
  diagnosticConsole
3
- } from "./chunk-FXDQMJQ2.js";
6
+ } from "./chunk-DZQYQN2X.js";
4
7
 
5
8
  // src/sdk/server/mpp-setup.ts
6
9
  import { createHash, randomUUID } from "crypto";
@@ -533,7 +536,7 @@ function buildMppMethods(opts, env) {
533
536
  })
534
537
  );
535
538
  if (opts.tempoSession) {
536
- methods.push(
539
+ methods.push(withAuthenticationOnlyValidation(
537
540
  tempo.session({
538
541
  recipient: opts.tempoRecipient,
539
542
  account: opts.tempoSession.account,
@@ -557,12 +560,25 @@ function buildMppMethods(opts, env) {
557
560
  onSessionSettlement: opts.tempoSession.onSessionSettlement
558
561
  }
559
562
  })
560
- );
563
+ ));
561
564
  }
562
565
  }
563
566
  if (methods.length === 0 || !tempoConfiguration) return void 0;
564
567
  return { methods, tempoConfiguration };
565
568
  }
569
+ function withAuthenticationOnlyValidation(method) {
570
+ if (method.validate) return method;
571
+ const validate = async ({ credential, request }) => ({
572
+ challenge: credential.challenge,
573
+ credential,
574
+ details: void 0,
575
+ intent: method.intent,
576
+ method: method.name,
577
+ request,
578
+ ...credential.source !== void 0 && { source: credential.source }
579
+ });
580
+ return { ...method, validate };
581
+ }
566
582
  function resolveTempoNetwork(env) {
567
583
  const testnet = env.DVMKIT_TEMPO_TESTNET === "1" || env.DVMKIT_TEMPO_TESTNET === "true";
568
584
  const rawChainId = env.DVMKIT_TEMPO_CHAIN_ID?.trim();
@@ -612,6 +628,9 @@ function wrapMppx(mppx) {
612
628
  };
613
629
  return Object.assign(server, { issueChallenge });
614
630
  }
631
+ var TEMPO_CLOSE_LOG_WINDOW = 5000n;
632
+ var TEMPO_CLOSE_LOG_SLACK_MS = 10 * 60 * 1e3;
633
+ var TEMPO_CLOSE_LOG_MAX_WINDOWS = 500;
615
634
  var DEFAULT_TEMPO_SESSION_CHAIN_OPS = {
616
635
  getChannelStatesBatch: TempoSession.Precompile.Chain.getChannelStatesBatch,
617
636
  settle: TempoSession.Server.settle,
@@ -621,7 +640,32 @@ var DEFAULT_TEMPO_SESSION_CHAIN_OPS = {
621
640
  "ChannelClosed",
622
641
  channelId
623
642
  )
624
- )
643
+ ),
644
+ findChannelClose: async (client, channelId, escrowContract, notBeforeMs) => {
645
+ const { getBlock, getBlockNumber, getLogs } = await import("viem/actions");
646
+ const event = TempoSession.Precompile.escrowAbi.find(
647
+ (item) => item.type === "event" && item.name === "ChannelClosed"
648
+ );
649
+ if (!event || event.type !== "event") throw new Error("Tempo escrow ABI has no ChannelClosed event");
650
+ let toBlock = await getBlockNumber(client);
651
+ for (let window = 0; window < TEMPO_CLOSE_LOG_MAX_WINDOWS; window += 1) {
652
+ const fromBlock = toBlock >= TEMPO_CLOSE_LOG_WINDOW ? toBlock - TEMPO_CLOSE_LOG_WINDOW + 1n : 0n;
653
+ const logs = await getLogs(client, {
654
+ address: escrowContract,
655
+ event,
656
+ args: { channelId },
657
+ fromBlock,
658
+ toBlock
659
+ });
660
+ const closed = logs.find((log) => log.transactionHash);
661
+ if (closed?.transactionHash) return closed.transactionHash;
662
+ if (fromBlock === 0n) return null;
663
+ const start = await getBlock(client, { blockNumber: fromBlock });
664
+ if (Number(start.timestamp) * 1e3 < notBeforeMs - TEMPO_CLOSE_LOG_SLACK_MS) return null;
665
+ toBlock = fromBlock - 1n;
666
+ }
667
+ throw new Error(`Tempo close search for channel ${channelId} exceeded its block budget`);
668
+ }
625
669
  };
626
670
  function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
627
671
  let creditLedger;
@@ -642,6 +686,102 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
642
686
  }
643
687
  if (store) {
644
688
  Object.assign(mppx, {
689
+ assertTempoSessionCloseVoucher: async (voucher) => {
690
+ const descriptor = readChannelDescriptor(voucher.descriptor);
691
+ if (!descriptor) {
692
+ throw new MppxErrors.VerificationFailedError({
693
+ reason: "descriptor required for TIP-1034 session action"
694
+ });
695
+ }
696
+ const details = isStateRecord(voucher.request.methodDetails) ? voucher.request.methodDetails : {};
697
+ const stored = await store.get(voucher.channelId.toLowerCase());
698
+ const state = readTempoSessionState(stored);
699
+ const chainId = typeof details.chainId === "number" ? details.chainId : state?.chainId;
700
+ const escrow = typeof details.escrowContract === "string" ? details.escrowContract : state?.escrowContract;
701
+ if (chainId === void 0 || escrow === void 0) {
702
+ throw new MppxErrors.VerificationFailedError({ reason: "invalid session request" });
703
+ }
704
+ const computed = TempoSession.Precompile.Channel.computeId({
705
+ ...descriptor,
706
+ chainId,
707
+ escrow
708
+ });
709
+ if (computed.toLowerCase() !== voucher.channelId.toLowerCase()) {
710
+ throw new MppxErrors.VerificationFailedError({
711
+ reason: "channel descriptor does not match channelId"
712
+ });
713
+ }
714
+ if (!sameHex(descriptor.payee, voucher.request.recipient)) {
715
+ throw new MppxErrors.VerificationFailedError({
716
+ reason: "channel descriptor payee does not match server destination"
717
+ });
718
+ }
719
+ if (!sameHex(descriptor.token, voucher.request.currency)) {
720
+ throw new MppxErrors.VerificationFailedError({
721
+ reason: "channel descriptor token does not match server token"
722
+ });
723
+ }
724
+ if (!sameHex(descriptor.operator, details.operator ?? ZERO_ADDRESS)) {
725
+ throw new MppxErrors.VerificationFailedError({
726
+ reason: "channel descriptor operator does not match server operator"
727
+ });
728
+ }
729
+ if (!state || !isStateRecord(stored)) {
730
+ throw new MppxErrors.ChannelNotFoundError({ reason: "channel not found" });
731
+ }
732
+ if (stored.backend !== "precompile") {
733
+ throw new MppxErrors.VerificationFailedError({
734
+ reason: "channel is not precompile-backed"
735
+ });
736
+ }
737
+ const storedDescriptor = readChannelDescriptor(stored.descriptor);
738
+ if (!storedDescriptor || !sameDescriptor(descriptor, storedDescriptor)) {
739
+ throw new MppxErrors.VerificationFailedError({
740
+ reason: "credential descriptor does not match stored channel"
741
+ });
742
+ }
743
+ const signer = stored.authorizedSigner;
744
+ if (typeof signer !== "string" || !HEX_ADDRESS_RE.test(signer)) {
745
+ throw new MppxErrors.ChannelNotFoundError({ reason: "channel not found" });
746
+ }
747
+ if (state.finalized) {
748
+ throw new MppxErrors.ChannelClosedError({ reason: "channel is already finalized" });
749
+ }
750
+ if (state.closeRequestedAt !== 0n) {
751
+ throw new MppxErrors.ChannelClosedError({ reason: "channel has a pending close request" });
752
+ }
753
+ if (state.deposit === 0n && (voucher.cumulativeAmount !== 0n || state.spent !== 0n)) {
754
+ throw new MppxErrors.ChannelClosedError({ reason: "channel deposit is zero (settled)" });
755
+ }
756
+ if (voucher.cumulativeAmount < state.spent) {
757
+ throw new MppxErrors.VerificationFailedError({
758
+ reason: `close voucher amount must be >= ${state.spent} (spent)`
759
+ });
760
+ }
761
+ if (voucher.cumulativeAmount < state.settledOnChain) {
762
+ throw new MppxErrors.VerificationFailedError({
763
+ reason: `close voucher amount must be >= ${state.settledOnChain} (on-chain settled)`
764
+ });
765
+ }
766
+ const valid = TempoSession.Precompile.Voucher.verifyVoucher(
767
+ escrow,
768
+ chainId,
769
+ {
770
+ channelId: voucher.channelId,
771
+ cumulativeAmount: voucher.cumulativeAmount,
772
+ signature: voucher.signature
773
+ },
774
+ signer
775
+ );
776
+ if (!valid) {
777
+ throw new MppxErrors.InvalidSignatureError({ reason: "invalid voucher signature" });
778
+ }
779
+ if (maxBigInt(state.spent, state.settledOnChain) > state.deposit) {
780
+ throw new MppxErrors.AmountExceedsDepositError({
781
+ reason: "close capture amount exceeds on-chain deposit"
782
+ });
783
+ }
784
+ },
645
785
  getTempoSessionSpent: async (channelId) => {
646
786
  const state = readTempoSessionState(await store.get(channelId.toLowerCase()));
647
787
  if (!state) throw new Error(`Tempo session channel ${channelId} not found`);
@@ -984,7 +1124,32 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
984
1124
  finalized = chainState.deposit === 0n;
985
1125
  }
986
1126
  if (!finalized) return null;
987
- const recovered = await store.confirmClose(channelId, drainId, `channel:${channelId}`);
1127
+ const pending = await config.hostedObligations?.pendingTempoClose(channelId, drainId);
1128
+ let reference = pending?.txHash ?? void 0;
1129
+ if (pending && !reference) {
1130
+ const client = await getClient({ chainId: current.chainId });
1131
+ const txHash = await chainOps.findChannelClose(
1132
+ client,
1133
+ current.channelId,
1134
+ current.escrowContract,
1135
+ pending.reservedAtMs
1136
+ );
1137
+ if (txHash) {
1138
+ await config.hostedObligations.recordSubmitted(pending.id, txHash);
1139
+ reference = txHash;
1140
+ } else {
1141
+ loggers.tempo.warn("tempo_session_close_recovery_unmatched", {
1142
+ channel_id: current.channelId,
1143
+ drain_id: drainId,
1144
+ obligation_id: pending.id
1145
+ });
1146
+ }
1147
+ }
1148
+ const recovered = await store.confirmClose(
1149
+ channelId,
1150
+ drainId,
1151
+ reference ?? `channel:${channelId}`
1152
+ );
988
1153
  return recovered.reference;
989
1154
  };
990
1155
  const getClose = store.getClose?.bind(store) ?? (() => Promise.resolve(null));
@@ -1028,6 +1193,27 @@ function maxBigInt(left, right) {
1028
1193
  function isStateRecord(value) {
1029
1194
  return !!value && typeof value === "object" && !Array.isArray(value);
1030
1195
  }
1196
+ var ZERO_ADDRESS = `0x${"00".repeat(20)}`;
1197
+ var DESCRIPTOR_ADDRESSES = ["payer", "payee", "operator", "token", "authorizedSigner"];
1198
+ var DESCRIPTOR_WORDS = ["salt", "expiringNonceHash"];
1199
+ function readChannelDescriptor(value) {
1200
+ if (!isStateRecord(value)) return void 0;
1201
+ for (const field of DESCRIPTOR_ADDRESSES) {
1202
+ if (typeof value[field] !== "string" || !HEX_ADDRESS_RE.test(value[field])) return void 0;
1203
+ }
1204
+ for (const field of DESCRIPTOR_WORDS) {
1205
+ if (typeof value[field] !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(value[field])) {
1206
+ return void 0;
1207
+ }
1208
+ }
1209
+ return value;
1210
+ }
1211
+ function sameHex(left, right) {
1212
+ return typeof left === "string" && typeof right === "string" && left.toLowerCase() === right.toLowerCase();
1213
+ }
1214
+ function sameDescriptor(left, right) {
1215
+ return DESCRIPTOR_ADDRESSES.every((field) => sameHex(left[field], right[field])) && DESCRIPTOR_WORDS.every((field) => sameHex(left[field], right[field]));
1216
+ }
1031
1217
  function tempoSettlementDue(args) {
1032
1218
  const { channel, chainSettled, forcedClose, intervalMs } = args;
1033
1219
  const settlementBoundary = Date.parse(channel.lastSettlementAt ?? channel.createdAt ?? "");
@@ -7,10 +7,10 @@ import {
7
7
  installHostedTempoCloseGuard,
8
8
  isTempoInsufficientFundsError,
9
9
  resolveTempoNetwork
10
- } from "./chunk-KYCLHWKO.js";
10
+ } from "./chunk-GB5XPAA5.js";
11
11
  import {
12
12
  x402EarnedCeiling
13
- } from "./chunk-3VFEX2Y3.js";
13
+ } from "./chunk-X3JEME6P.js";
14
14
  import {
15
15
  createX402FacilitatorAuthHeaders,
16
16
  createX402FacilitatorClient,
@@ -19,13 +19,13 @@ import {
19
19
  verifyWithFacilitator,
20
20
  withPaymentDiagnostics,
21
21
  withPaymentStage
22
- } from "./chunk-XEOTTFLH.js";
22
+ } from "./chunk-ATXDKX6F.js";
23
23
  import {
24
24
  MemoryCreditLedger
25
- } from "./chunk-FOJKZ4E7.js";
25
+ } from "./chunk-EBCLWOKF.js";
26
26
  import {
27
27
  PostgresJobStore
28
- } from "./chunk-KB7MEWBE.js";
28
+ } from "./chunk-QPXI7YPH.js";
29
29
  import {
30
30
  parseStoreValue
31
31
  } from "./chunk-YG7G4DPZ.js";
@@ -36,7 +36,7 @@ import {
36
36
  humanizeZodIssues,
37
37
  signedRequestInput,
38
38
  zodIssuesOf
39
- } from "./chunk-X5RY2G3C.js";
39
+ } from "./chunk-5K5E6ME6.js";
40
40
  import {
41
41
  MemoryJobStore,
42
42
  UNCAPPABLE_ASK_TOTAL,
@@ -45,7 +45,7 @@ import {
45
45
  isJobRetentionStore,
46
46
  isReceiptIssuingStore,
47
47
  isStreamableJobStore
48
- } from "./chunk-MYJOV4R4.js";
48
+ } from "./chunk-I6PHA54S.js";
49
49
  import {
50
50
  ContentHoldError,
51
51
  JobCancelledError,
@@ -85,7 +85,7 @@ import {
85
85
  signReceipt,
86
86
  toLockPubkey,
87
87
  verifyChallenge
88
- } from "./chunk-XO5EUHCW.js";
88
+ } from "./chunk-RTFBVXCN.js";
89
89
  import {
90
90
  X402_BATCH_SETTLEMENT_SCHEME,
91
91
  X402_DEFAULT_FACILITATOR,
@@ -112,12 +112,14 @@ import {
112
112
  isZodSchema
113
113
  } from "./chunk-UKD3R2KU.js";
114
114
  import {
115
- createConsoleLogger,
115
+ createConsoleLogger
116
+ } from "./chunk-IFWUWHTW.js";
117
+ import {
116
118
  loggers,
117
119
  pinDimension,
118
120
  withSpan,
119
121
  withTraceContext
120
- } from "./chunk-SX4WCPW7.js";
122
+ } from "./chunk-BL6LRWXD.js";
121
123
  import {
122
124
  StepCache
123
125
  } from "./chunk-BQJIFRAZ.js";
@@ -134,7 +136,7 @@ import {
134
136
  roundUsd,
135
137
  satsToFiat,
136
138
  validateCurrency
137
- } from "./chunk-LQQ6H4UB.js";
139
+ } from "./chunk-5CQKSLAU.js";
138
140
  import {
139
141
  CreditLedger,
140
142
  CreditLedgerError,
@@ -150,10 +152,10 @@ import {
150
152
  isNonChannelBitcoinRail,
151
153
  netOwedSats,
152
154
  x402SettlementPending
153
- } from "./chunk-WP7UX6RF.js";
155
+ } from "./chunk-VYP6ADCS.js";
154
156
  import {
155
157
  withPostgresRecoveryContext
156
- } from "./chunk-6RYCZLCK.js";
158
+ } from "./chunk-6FEHRZO7.js";
157
159
  import {
158
160
  DvmError,
159
161
  x402NetworkByCaip2
@@ -161,7 +163,7 @@ import {
161
163
  import {
162
164
  createDiagnosticCapabilityAuthority,
163
165
  diagnosticConsole
164
- } from "./chunk-FXDQMJQ2.js";
166
+ } from "./chunk-DZQYQN2X.js";
165
167
  import {
166
168
  MemoryProcessedPaymentStore,
167
169
  PostgresProcessedPaymentStore,
@@ -3118,6 +3120,10 @@ function decodeBatchPayment(header) {
3118
3120
  if (!isBatchSettlementDepositPayload2(parsed.payload) && !isBatchSettlementVoucherPayload(parsed.payload) && !isBatchSettlementRefundPayload2(parsed.payload)) {
3119
3121
  throw new Error("invalid batch-settlement payload");
3120
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
+ }
3121
3127
  return parsed;
3122
3128
  }
3123
3129
  function batchVoucher(payload) {
@@ -3877,7 +3883,7 @@ async function verifyUpfrontPaymentInScope(opts) {
3877
3883
  x402Requirements
3878
3884
  });
3879
3885
  }
3880
- const { verifyX402Payment } = await import("./x402-BN26TQZ6.js");
3886
+ const { verifyX402Payment } = await import("./x402-Q4S36NHQ.js");
3881
3887
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
3882
3888
  x402Payment,
3883
3889
  x402Config,
@@ -4040,6 +4046,30 @@ async function verifyUpfrontPaymentInScope(opts) {
4040
4046
  }
4041
4047
  const expectedAmount = opts.priceFiat?.currency === "usd" ? fiatMicroToMethodAmount(opts.priceFiat.amountMicro, cred.challenge.method) : msatsToMethodAmount(requiredMsats, cred.challenge.method, rate);
4042
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
+ }
4043
4073
  let tempoReceipt;
4044
4074
  const broadcast = async () => {
4045
4075
  const receipt = await loggers.tempo.span(
@@ -4157,35 +4187,12 @@ async function verifyUpfrontPaymentInScope(opts) {
4157
4187
  }
4158
4188
  };
4159
4189
  }
4160
- if (consumedCredentialStore) {
4161
- const realm = mpp.realm;
4162
- const id = cred.challenge.id;
4163
- if (await withPaymentStage("replay_store", () => consumedCredentialStore.has(realm, id))) {
4164
- const mppChallenges = await issueUpfrontChallenges(
4165
- mpp,
4166
- requiredMsats,
4167
- resourcePath ?? "/v1/job",
4168
- rate,
4169
- sessionIssuable,
4170
- opts.fundOnly?.allowOneShotStablecoin,
4171
- opts.priceFiat
4172
- );
4173
- return {
4174
- paidMsats: 0,
4175
- receivedProofs: [],
4176
- error: {
4177
- body: paymentErrorBody("payment_invalid", {
4178
- message: "MPP credential already used"
4179
- }),
4180
- status: 402,
4181
- mppChallenges,
4182
- ...x402Requirements && { x402Requirements }
4183
- }
4184
- };
4185
- }
4186
- if (!sessionBasis) {
4187
- await withPaymentStage("replay_store", () => consumedCredentialStore.mark(realm, id, CONSUMED_CREDENTIAL_TTL_MS / 1e3));
4188
- }
4190
+ if (consumedCredentialStore && !sessionBasis) {
4191
+ await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
4192
+ replayRealm,
4193
+ cred.challenge.id,
4194
+ CONSUMED_CREDENTIAL_TTL_MS / 1e3
4195
+ ));
4189
4196
  }
4190
4197
  if (ledgerCtx) {
4191
4198
  try {
@@ -4208,10 +4215,18 @@ async function verifyUpfrontPaymentInScope(opts) {
4208
4215
  }
4209
4216
  if (sessionBasis && consumedCredentialStore) {
4210
4217
  await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
4211
- mpp.realm,
4218
+ replayRealm,
4212
4219
  cred.challenge.id,
4213
4220
  CONSUMED_CREDENTIAL_TTL_MS / 1e3
4214
- ));
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
+ });
4215
4230
  }
4216
4231
  if (credited) {
4217
4232
  await mpp.completeHostedTempoCharge?.(cred.challenge.id).catch((error) => {
@@ -4420,6 +4435,22 @@ async function verifyTempoSessionManagementCredential(args) {
4420
4435
  throw new Error("Hosted Tempo close requires non-mutating credential validation");
4421
4436
  }
4422
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
+ }
4423
4454
  }
4424
4455
  await args.onBound?.();
4425
4456
  const receipt = await args.mpp.broadcastCredential(credential, verifyOptions);
@@ -4865,7 +4896,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
4865
4896
  snapshot
4866
4897
  });
4867
4898
  }
4868
- const { verifyX402Payment } = await import("./x402-BN26TQZ6.js");
4899
+ const { verifyX402Payment } = await import("./x402-Q4S36NHQ.js");
4869
4900
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4870
4901
  body.content.x402_payment,
4871
4902
  opts.x402Config,
@@ -5296,7 +5327,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
5296
5327
  try {
5297
5328
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5298
5329
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5299
- const { verifyX402Payment } = await import("./x402-BN26TQZ6.js");
5330
+ const { verifyX402Payment } = await import("./x402-Q4S36NHQ.js");
5300
5331
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5301
5332
  body.content.x402_payment,
5302
5333
  opts.x402Config,
@@ -10062,6 +10093,18 @@ var HostedSettlementReportObligations = class {
10062
10093
  );
10063
10094
  return result.rows[0];
10064
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
+ }
10065
10108
  async submittedIds(rail, operation, txHash, batchId) {
10066
10109
  if (!hex(txHash, 32)) throw new Error("Hosted effect has no exact transaction hash");
10067
10110
  const result = await this.pool.query(
@@ -10201,6 +10244,10 @@ var HostedSettlementReportObligations = class {
10201
10244
  }));
10202
10245
  }
10203
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 [];
10204
10251
  const rows2 = await this.pool.query(
10205
10252
  `SELECT obligation.id,settlement.settle_response AS response
10206
10253
  FROM sdk_hosted_settlement_report_obligations obligation
@@ -19537,7 +19584,7 @@ async function createDVMServer(descriptor, opts) {
19537
19584
  }
19538
19585
  }
19539
19586
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
19540
- const { RevenueReporter } = await import("./revenue-reporter-E56J3NZK.js");
19587
+ const { RevenueReporter } = await import("./revenue-reporter-X52GPZ5U.js");
19541
19588
  const reporter = new RevenueReporter(
19542
19589
  opts.db,
19543
19590
  platformUrl,
@@ -19547,7 +19594,7 @@ async function createDVMServer(descriptor, opts) {
19547
19594
  revenueReporter = reporter;
19548
19595
  await reporter.init();
19549
19596
  reporter.startRetryLoop();
19550
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-3UVWPGMO.js");
19597
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-HQSIIBFL.js");
19551
19598
  payoutReporter = new PayoutReporter({
19552
19599
  dvmId,
19553
19600
  db: opts.db,
@@ -20905,7 +20952,7 @@ var DVMServer = class {
20905
20952
  // sees only the shortened list cannot tell a DVM that never did
20906
20953
  // sessions from one whose observer is down for a minute.
20907
20954
  ...withheld.length && { withheld },
20908
- realm: this.opts.mpp.realm,
20955
+ ...this.opts.mpp.realm && { realm: this.opts.mpp.realm },
20909
20956
  ...this.opts.mpp.tempoConfiguration && {
20910
20957
  recipient: this.opts.mpp.tempoConfiguration.recipient,
20911
20958
  payment_token: this.opts.mpp.tempoConfiguration.paymentToken,
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-AFV77S4C.js";
6
6
  import {
7
7
  loggers
8
- } from "./chunk-SX4WCPW7.js";
8
+ } from "./chunk-BL6LRWXD.js";
9
9
  import {
10
10
  redactUrl
11
- } from "./chunk-KXWROQGK.js";
11
+ } from "./chunk-FUJ36YDV.js";
12
12
 
13
13
  // src/sdk/server/job-store.ts
14
14
  function accumulateAskedTopUp(prior, ask) {
@@ -0,0 +1,47 @@
1
+ import {
2
+ emitDiagnostic,
3
+ projectDiagnosticFields
4
+ } from "./chunk-DZQYQN2X.js";
5
+
6
+ // src/sdk/logger.ts
7
+ function createConsoleLogger(_jobId) {
8
+ return {
9
+ debug(_message, data) {
10
+ emitDiagnostic("debug", "diagnostic.message", data);
11
+ },
12
+ info(_message, data) {
13
+ emitDiagnostic("info", "diagnostic.message", data);
14
+ },
15
+ warn(_message, data) {
16
+ emitDiagnostic("warn", "diagnostic.message", data);
17
+ },
18
+ error(_message, data) {
19
+ emitDiagnostic("error", "diagnostic.message", data);
20
+ }
21
+ };
22
+ }
23
+ function createStdoutLogger(context) {
24
+ const emit = (level) => (_message, data) => {
25
+ emitDiagnostic(level, "diagnostic.message", {
26
+ ...projectDiagnosticFields(context),
27
+ ...projectDiagnosticFields(data)
28
+ });
29
+ };
30
+ return {
31
+ debug: emit("debug"),
32
+ info: emit("info"),
33
+ warn: emit("warn"),
34
+ error: emit("error")
35
+ };
36
+ }
37
+ function createNoopLogger() {
38
+ const noop = () => {
39
+ };
40
+ return { debug: noop, info: noop, warn: noop, error: noop };
41
+ }
42
+
43
+ export {
44
+ createConsoleLogger,
45
+ createStdoutLogger,
46
+ createNoopLogger
47
+ };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YG7G4DPZ.js";
5
5
  import {
6
6
  withPostgresTransactionRecovery
7
- } from "./chunk-6RYCZLCK.js";
7
+ } from "./chunk-6FEHRZO7.js";
8
8
  import {
9
9
  withSdkInitLock
10
10
  } from "./chunk-COYKJRMM.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  diagnosticConsole
3
- } from "./chunk-FXDQMJQ2.js";
3
+ } from "./chunk-DZQYQN2X.js";
4
4
  import {
5
5
  withSdkInitLock
6
6
  } from "./chunk-COYKJRMM.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-AFV77S4C.js";
5
5
  import {
6
6
  diagnosticConsole
7
- } from "./chunk-FXDQMJQ2.js";
7
+ } from "./chunk-DZQYQN2X.js";
8
8
  import {
9
9
  withSdkInitLock
10
10
  } from "./chunk-COYKJRMM.js";