@dvmkit/sdk 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/{chunk-ILBSZMJL.js → chunk-4VV7WACY.js} +1 -1
  2. package/dist/{chunk-JIZ4ZIXX.js → chunk-73C4JN2E.js} +421 -233
  3. package/dist/{chunk-ALWVJ2RB.js → chunk-BAQRUYRL.js} +3 -3
  4. package/dist/{chunk-AVV5SYGO.js → chunk-GAZBBMAA.js} +1 -1
  5. package/dist/{chunk-G3MPHW5I.js → chunk-GODOH2AF.js} +3 -3
  6. package/dist/{chunk-2U6LAASN.js → chunk-HC4I7LAV.js} +1 -1
  7. package/dist/{chunk-7PM4R4GD.js → chunk-IBX2ORBV.js} +1 -1
  8. package/dist/{chunk-NA2KOR6G.js → chunk-IYQQXEHV.js} +3 -3
  9. package/dist/{chunk-J5J63WDC.js → chunk-K2MSIKMM.js} +3 -3
  10. package/dist/{chunk-RSNC5T6C.js → chunk-KGL3YMZA.js} +1 -1
  11. package/dist/{chunk-UVS7MRJX.js → chunk-LCCG5HQM.js} +2 -2
  12. package/dist/{chunk-QKE5NDPF.js → chunk-LFVIZH2G.js} +167 -25
  13. package/dist/{chunk-ZEPJF4JC.js → chunk-LW7KZQHG.js} +4 -4
  14. package/dist/{chunk-BFGQG4WY.js → chunk-OIP44KBB.js} +5 -0
  15. package/dist/{chunk-UWGBI5WK.js → chunk-Q3VYRU5R.js} +3 -3
  16. package/dist/{chunk-IOQIXRGM.js → chunk-QSZW2I5Y.js} +1 -1
  17. package/dist/{chunk-IHQUBATH.js → chunk-TFDR2YP2.js} +3 -3
  18. package/dist/{chunk-OP7VCGQR.js → chunk-TRY7BJA4.js} +1 -1
  19. package/dist/{chunk-NU5RWJ5B.js → chunk-WB3E7KE7.js} +3 -3
  20. package/dist/{chunk-UDJNZ3UG.js → chunk-WLPLHXNP.js} +1 -1
  21. package/dist/{chunk-FM6QAGJC.js → chunk-YYRY32NR.js} +24 -8
  22. package/dist/{chunk-AGE5XZKE.js → chunk-ZBQKXAYK.js} +17 -4
  23. package/dist/{content-retention-B7ddtJ2y.d.ts → content-retention-CVBYehRS.d.ts} +1 -1
  24. package/dist/{credit-ledger-KIQIE6I6.js → credit-ledger-CZQ56DYI.js} +4 -4
  25. package/dist/{credit-menu-BH3g9GQa.d.ts → credit-menu-QntEhZJS.d.ts} +4 -4
  26. package/dist/{fx-DN26_J7o.d.ts → fx-CMyNFenZ.d.ts} +1 -1
  27. package/dist/index.d.ts +5 -5
  28. package/dist/index.js +4 -4
  29. package/dist/internal/caller.d.ts +6 -6
  30. package/dist/internal/caller.js +16 -16
  31. package/dist/internal/server.d.ts +7 -7
  32. package/dist/internal/server.js +26 -26
  33. package/dist/{job-store-Cq21JT37.d.ts → job-store-qOD-FrQF.d.ts} +1 -1
  34. package/dist/{lightning-backend-S8F8nf8q.d.ts → lightning-backend-DtdG6UT5.d.ts} +1 -1
  35. package/dist/{memory-credit-ledger-ATYFAYBG.js → memory-credit-ledger-EMN7G3MO.js} +5 -5
  36. package/dist/{mpp-setup-ZTORW3VF.js → mpp-setup-VW53KFPO.js} +5 -3
  37. package/dist/{payout-reporter-NN6K2IVQ.js → payout-reporter-TZX2S7KG.js} +2 -2
  38. package/dist/{postgres-consumed-credential-store-XJTE5PCA.js → postgres-consumed-credential-store-V464OAJB.js} +3 -3
  39. package/dist/{postgres-job-store-AR6ZJUQV.js → postgres-job-store-27VOCSPO.js} +2 -2
  40. package/dist/{postgres-replay-store-33K33QV7.js → postgres-replay-store-7GJFAQXY.js} +2 -2
  41. package/dist/{pricing-G5IN5YJE.js → pricing-NNMBNP7N.js} +3 -3
  42. package/dist/{revenue-reporter-KDUZLD5F.js → revenue-reporter-4EAVPWXS.js} +2 -2
  43. package/dist/server/index.d.ts +14 -10
  44. package/dist/server/index.js +49 -45
  45. package/dist/{step-cache-CR12EZ1Q.d.ts → step-cache-B7svmPz3.d.ts} +54 -9
  46. package/dist/{tempo-charge-store-XEGJYM3G.js → tempo-charge-store-2RPOJBM2.js} +2 -2
  47. package/dist/{tempo-session-store-3WTDOO7H.js → tempo-session-store-LI3Q3A3U.js} +3 -3
  48. package/dist/testing/index.d.ts +3 -3
  49. package/dist/testing/index.js +7 -7
  50. package/dist/{usd-s_VZ_eS7.d.ts → usd-D3izFz71.d.ts} +1 -1
  51. package/dist/{x402-HQ5GRQZU.js → x402-SL36LSOD.js} +4 -4
  52. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
- import {
2
- diagnosticConsole
3
- } from "./chunk-BFGQG4WY.js";
4
1
  import {
5
2
  withSdkInitLock
6
3
  } from "./chunk-COYKJRMM.js";
4
+ import {
5
+ diagnosticConsole
6
+ } from "./chunk-OIP44KBB.js";
7
7
 
8
8
  // src/sdk/server/postgres-replay-store.ts
9
9
  var DEFAULT_TABLE_NAME = "signed_request_replays";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-66HGCPBU.js";
7
7
  import {
8
8
  diagnosticConsole
9
- } from "./chunk-BFGQG4WY.js";
9
+ } from "./chunk-OIP44KBB.js";
10
10
 
11
11
  // src/observability/payment-diagnostics.ts
12
12
  import { AsyncLocalStorage } from "async_hooks";
@@ -2,12 +2,12 @@ import {
2
2
  ContentHoldError,
3
3
  applyContentHoldMutation
4
4
  } from "./chunk-AFV77S4C.js";
5
- import {
6
- diagnosticConsole
7
- } from "./chunk-BFGQG4WY.js";
8
5
  import {
9
6
  withSdkInitLock
10
7
  } from "./chunk-COYKJRMM.js";
8
+ import {
9
+ diagnosticConsole
10
+ } from "./chunk-OIP44KBB.js";
11
11
 
12
12
  // src/sdk/server/postgres-job-store.ts
13
13
  import { createHash } from "crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  diagnosticConsole
3
- } from "./chunk-BFGQG4WY.js";
3
+ } from "./chunk-OIP44KBB.js";
4
4
 
5
5
  // src/sdk/pricing/currency.ts
6
6
  var InvalidCurrencyError = class extends Error {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  diagnosticConsole
3
- } from "./chunk-BFGQG4WY.js";
3
+ } from "./chunk-OIP44KBB.js";
4
4
 
5
5
  // src/lib/pg-retry.ts
6
6
  import { AsyncLocalStorage } from "async_hooks";
@@ -2,12 +2,12 @@ import {
2
2
  parseStoreValue,
3
3
  stringifyStoreValue
4
4
  } from "./chunk-YG7G4DPZ.js";
5
- import {
6
- diagnosticConsole
7
- } from "./chunk-BFGQG4WY.js";
8
5
  import {
9
6
  withSdkInitLock
10
7
  } from "./chunk-COYKJRMM.js";
8
+ import {
9
+ diagnosticConsole
10
+ } from "./chunk-OIP44KBB.js";
11
11
 
12
12
  // src/sdk/server/tempo-charge-store.ts
13
13
  import { Credential } from "mppx";
@@ -1,9 +1,9 @@
1
- import {
2
- diagnosticConsole
3
- } from "./chunk-BFGQG4WY.js";
4
1
  import {
5
2
  withSdkInitLock
6
3
  } from "./chunk-COYKJRMM.js";
4
+ import {
5
+ diagnosticConsole
6
+ } from "./chunk-OIP44KBB.js";
7
7
 
8
8
  // src/sdk/server/payout-reporter.ts
9
9
  import { createHash, randomUUID } from "crypto";
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  diagnosticConsole,
6
6
  projectSpanRow
7
- } from "./chunk-BFGQG4WY.js";
7
+ } from "./chunk-OIP44KBB.js";
8
8
 
9
9
  // src/lib/x402/env.ts
10
10
  import { isAddress } from "viem";
@@ -2,7 +2,7 @@ import {
2
2
  createX402FacilitatorAuthHeaders,
3
3
  settleWithFacilitator,
4
4
  verifyWithFacilitator
5
- } from "./chunk-AVV5SYGO.js";
5
+ } from "./chunk-GAZBBMAA.js";
6
6
  import {
7
7
  buildPaymentRequirements,
8
8
  decodePayment,
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-66HGCPBU.js";
16
16
  import {
17
17
  diagnosticConsole
18
- } from "./chunk-BFGQG4WY.js";
18
+ } from "./chunk-OIP44KBB.js";
19
19
 
20
20
  // src/lib/x402/verify.ts
21
21
  async function verifyX402Payment(paymentHeader, config, requiredUsdcMicro, resource, btcUsdRate, exactVersions = { v1: true, v2: true }) {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  loggers
3
- } from "./chunk-IOQIXRGM.js";
3
+ } from "./chunk-QSZW2I5Y.js";
4
4
  import {
5
5
  diagnosticConsole
6
- } from "./chunk-BFGQG4WY.js";
6
+ } from "./chunk-OIP44KBB.js";
7
7
 
8
8
  // src/sdk/server/mpp-setup.ts
9
9
  import { createHash, randomUUID } from "crypto";
@@ -313,6 +313,15 @@ function logStateFailure(_error) {
313
313
  }
314
314
 
315
315
  // src/sdk/server/mpp-setup.ts
316
+ var TempoDrainRefundUnprovenError = class extends Error {
317
+ constructor(reason, message) {
318
+ super(message);
319
+ this.reason = reason;
320
+ }
321
+ reason;
322
+ };
323
+ var TempoCloseDescriptorError = class extends MppxErrors.VerificationFailedError {
324
+ };
316
325
  var MPPX_HMAC_MISMATCH_REASON = "challenge was not issued by this server";
317
326
  var TEMPO_SETTLEMENT_LEASE_FIELD = "settlementLeaseUntil";
318
327
  var TEMPO_SETTLEMENT_LEASE_OWNER_FIELD = "settlementLeaseOwner";
@@ -717,9 +726,11 @@ var DEFAULT_TEMPO_SESSION_CHAIN_OPS = {
717
726
  };
718
727
  function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
719
728
  let creditLedger;
729
+ let finalizeDrain;
720
730
  Object.assign(mppx, {
721
- useTempoCreditLedger: (ledger) => {
731
+ useTempoCreditLedger: (ledger, finalizer) => {
722
732
  creditLedger = ledger;
733
+ finalizeDrain = finalizer;
723
734
  }
724
735
  });
725
736
  const chainOps = {
@@ -737,7 +748,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
737
748
  assertTempoSessionCloseVoucher: async (voucher) => {
738
749
  const descriptor = readChannelDescriptor(voucher.descriptor);
739
750
  if (!descriptor) {
740
- throw new MppxErrors.VerificationFailedError({
751
+ throw new TempoCloseDescriptorError({
741
752
  reason: "descriptor required for TIP-1034 session action"
742
753
  });
743
754
  }
@@ -755,22 +766,22 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
755
766
  escrow
756
767
  });
757
768
  if (computed.toLowerCase() !== voucher.channelId.toLowerCase()) {
758
- throw new MppxErrors.VerificationFailedError({
769
+ throw new TempoCloseDescriptorError({
759
770
  reason: "channel descriptor does not match channelId"
760
771
  });
761
772
  }
762
773
  if (!sameHex(descriptor.payee, voucher.request.recipient)) {
763
- throw new MppxErrors.VerificationFailedError({
774
+ throw new TempoCloseDescriptorError({
764
775
  reason: "channel descriptor payee does not match server destination"
765
776
  });
766
777
  }
767
778
  if (!sameHex(descriptor.token, voucher.request.currency)) {
768
- throw new MppxErrors.VerificationFailedError({
779
+ throw new TempoCloseDescriptorError({
769
780
  reason: "channel descriptor token does not match server token"
770
781
  });
771
782
  }
772
783
  if (!sameHex(descriptor.operator, details.operator ?? ZERO_ADDRESS)) {
773
- throw new MppxErrors.VerificationFailedError({
784
+ throw new TempoCloseDescriptorError({
774
785
  reason: "channel descriptor operator does not match server operator"
775
786
  });
776
787
  }
@@ -784,7 +795,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
784
795
  }
785
796
  const storedDescriptor = readChannelDescriptor(stored.descriptor);
786
797
  if (!storedDescriptor || !sameDescriptor(descriptor, storedDescriptor)) {
787
- throw new MppxErrors.VerificationFailedError({
798
+ throw new TempoCloseDescriptorError({
788
799
  reason: "credential descriptor does not match stored channel"
789
800
  });
790
801
  }
@@ -886,6 +897,9 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
886
897
  value: {
887
898
  ...value,
888
899
  finalized: active.finalized || chainFinalized,
900
+ ...chainFinalized && !active.finalized && {
901
+ depositBeforeFinalization: active.deposit
902
+ },
889
903
  closeRequestedAt: chainFinalized ? 0n : BigInt(chainState.closeRequestedAt),
890
904
  deposit: chainFinalized ? 0n : maxBigInt(active.deposit, chainState.deposit),
891
905
  settledOnChain: maxBigInt(active.settledOnChain, chainState.settled)
@@ -914,6 +928,9 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
914
928
  value: {
915
929
  ...current,
916
930
  finalized: active.finalized || chainFinalized,
931
+ ...chainFinalized && !active.finalized && {
932
+ depositBeforeFinalization: active.deposit
933
+ },
917
934
  closeRequestedAt: chainFinalized ? 0n : BigInt(chainState.closeRequestedAt),
918
935
  deposit: chainFinalized ? 0n : maxBigInt(active.deposit, chainState.deposit),
919
936
  settledOnChain: maxBigInt(active.settledOnChain, chainState.settled)
@@ -1151,7 +1168,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1151
1168
  }
1152
1169
  }
1153
1170
  }
1154
- await reconcileUnsubmittedCloses();
1171
+ await reconcileTempoSessionCloses();
1155
1172
  return { scanned: rows.length, closeRequested, settled, inFlight };
1156
1173
  };
1157
1174
  const recoverTempoSessionClose = async (channelId, drainId) => {
@@ -1177,7 +1194,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1177
1194
  const pending = await config.hostedObligations?.pendingTempoClose(channelId, drainId);
1178
1195
  const selfHostedReport = !config.hostedObligations && !!config.onSessionSettlement && !!store.claimCloseReference && !!store.getCloseRecovery;
1179
1196
  let reference = pending?.txHash ?? void 0;
1180
- if (pending && !reference || selfHostedReport) {
1197
+ if (pending && pending.state !== "unmatched" && !reference || selfHostedReport) {
1181
1198
  const recovery = await store.getCloseRecovery?.(channelId);
1182
1199
  const notBeforeMs = pending?.reservedAtMs ?? recovery?.updatedAtMs;
1183
1200
  if (notBeforeMs === void 0) throw new Error(`Tempo session close intent ${channelId} not found`);
@@ -1192,15 +1209,18 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1192
1209
  if (pending) {
1193
1210
  reference = await settleUnsubmittedClose(current.channelId, drainId, pending.id, found);
1194
1211
  } else if (found.outcome === "ours") {
1212
+ const client = await getClient({ chainId: current.chainId });
1213
+ const { settledToPayee } = await chainOps.readChannelClose(client, current.channelId, found.txHash);
1195
1214
  const claimed = await store.claimCloseReference(channelId, drainId, found.txHash);
1196
1215
  if (claimed.claimed) {
1197
- await reportRecoveredClose(current, found.txHash, recovery?.settledBeforeClose ?? null);
1216
+ await reportRecoveredClose(current, found.txHash, recovery?.settledBeforeClose ?? null, settledToPayee);
1198
1217
  }
1199
1218
  return claimed.reference;
1200
1219
  } else if (found.outcome === "none") {
1201
1220
  loggers.tempo.warn("tempo_session_close_recovery_unmatched", {
1202
1221
  channel_id: current.channelId,
1203
- drain_id: drainId
1222
+ drain_id: drainId,
1223
+ amount_at_stake_native: (current.highestVoucherAmount - (recovery?.settledBeforeClose ?? current.settledOnChain)).toString()
1204
1224
  });
1205
1225
  }
1206
1226
  }
@@ -1234,7 +1254,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1234
1254
  }
1235
1255
  if (search.outcome === "exit_requested") return { outcome: "exit" };
1236
1256
  if (search.stoppedAt !== void 0) await cursor.save(search.stoppedAt);
1237
- return { outcome: "none" };
1257
+ return { outcome: "none", terminal: search.stoppedAt !== void 0 };
1238
1258
  };
1239
1259
  const settleUnsubmittedClose = async (channelId, drainId, obligationId, found) => {
1240
1260
  const obligations = config.hostedObligations;
@@ -1247,16 +1267,20 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1247
1267
  loggers.tempo.warn("tempo_session_close_exit_resolved", {
1248
1268
  channel_id: channelId,
1249
1269
  drain_id: drainId,
1250
- obligation_id: obligationId
1270
+ obligation_id: obligationId,
1271
+ amount_at_stake_native: await obligations.tempoCloseAmountAtStake(obligationId)
1251
1272
  });
1252
1273
  }
1253
1274
  return void 0;
1254
1275
  }
1255
- loggers.tempo.warn("tempo_session_close_recovery_unmatched", {
1256
- channel_id: channelId,
1257
- drain_id: drainId,
1258
- obligation_id: obligationId
1259
- });
1276
+ if (found.terminal && await obligations.markTempoCloseUnmatched(obligationId)) {
1277
+ loggers.tempo.warn("tempo_session_close_recovery_unmatched", {
1278
+ channel_id: channelId,
1279
+ drain_id: drainId,
1280
+ obligation_id: obligationId,
1281
+ amount_at_stake_native: await obligations.tempoCloseAmountAtStake(obligationId)
1282
+ });
1283
+ }
1260
1284
  return void 0;
1261
1285
  };
1262
1286
  const reconcileUnsubmittedCloses = async () => {
@@ -1292,13 +1316,129 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1292
1316
  }
1293
1317
  }
1294
1318
  };
1295
- const reportRecoveredClose = async (channel, txHash, settledBeforeClose) => {
1296
- const client = await getClient({ chainId: channel.chainId });
1297
- const { settledToPayee } = await chainOps.readChannelClose(client, channel.channelId, txHash);
1319
+ const reconcileFinalizedDrains = async () => {
1320
+ const ledger = creditLedger;
1321
+ if (!ledger || !finalizeDrain) return;
1322
+ let after;
1323
+ do {
1324
+ const page = await ledger.listChannelDrains({
1325
+ limit: 100,
1326
+ after,
1327
+ createdBeforeMs: Date.now(),
1328
+ rail: "tempo",
1329
+ includeResolved: true
1330
+ });
1331
+ for (const drain of page) {
1332
+ if (drain.status !== "exit_required" || !drain.channelId) continue;
1333
+ try {
1334
+ const stored = await store.get(drain.channelId);
1335
+ const current = readTempoSessionState(stored);
1336
+ if (!current) throw new Error("Channel history is unavailable");
1337
+ const client = await getClient({ chainId: current.chainId });
1338
+ const [chainState] = await chainOps.getChannelStatesBatch(client, [current.channelId], current.escrowContract);
1339
+ if (!chainState || chainState.deposit !== 0n) continue;
1340
+ await terminalizeLostBacking(current, chainState);
1341
+ await writeBackChainState(current, chainState);
1342
+ const pending = await config.hostedObligations?.pendingTempoClose(drain.channelId, drain.drainId);
1343
+ if (pending && !pending.txHash) {
1344
+ if (pending.state === "unmatched") throw new TempoDrainRefundUnprovenError("close_history_unmatched", "Close history requires an operator retry");
1345
+ continue;
1346
+ }
1347
+ const recovery = await store.getCloseRecovery?.(drain.channelId);
1348
+ const found = pending?.txHash ? { outcome: "ours", txHash: pending.txHash } : await findFinalizedClose(current, stored, drain.drainId, drain.createdAt, {
1349
+ from: recovery?.searchBefore ?? null,
1350
+ save: (resumeAt) => store.recordCloseSearch?.(drain.channelId, drain.drainId, resumeAt)
1351
+ });
1352
+ let refundedNative;
1353
+ let reference;
1354
+ if (found.outcome === "ours") {
1355
+ reference = found.txHash;
1356
+ const receipt = await chainOps.readChannelClose(client, current.channelId, found.txHash);
1357
+ refundedNative = receipt.refundedToPayer;
1358
+ if (!config.hostedObligations && config.onSessionSettlement && store.claimCloseReference) {
1359
+ const claimed = await store.claimCloseReference(drain.channelId, drain.drainId, found.txHash);
1360
+ if (claimed.claimed) {
1361
+ await reportRecoveredClose(current, found.txHash, recovery?.settledBeforeClose ?? null, receipt.settledToPayee);
1362
+ }
1363
+ }
1364
+ } else if (found.outcome === "exit") {
1365
+ const snapshot = stored;
1366
+ const deposit = typeof snapshot.depositBeforeFinalization === "bigint" ? snapshot.depositBeforeFinalization : current.deposit;
1367
+ if (deposit <= 0n) throw new TempoDrainRefundUnprovenError("collateral_missing", "The pre-exit collateral is unavailable");
1368
+ refundedNative = deposit - maxBigInt(current.settledOnChain, chainState.settled);
1369
+ reference = `channel:${current.channelId}`;
1370
+ } else {
1371
+ throw new TempoDrainRefundUnprovenError("close_evidence_missing", "Channel finalization has no matching refund evidence");
1372
+ }
1373
+ if (drain.drainedNative === null || refundedNative < BigInt(drain.drainedNative)) {
1374
+ throw new TempoDrainRefundUnprovenError("refund_short", "Proven refund is below the drain debit");
1375
+ }
1376
+ const creditUnbacked = (await ledger.get(drain.creditId))?.status === "unbacked";
1377
+ await store.confirmClose?.(drain.channelId, drain.drainId, reference);
1378
+ const changed = await finalizeDrain(drain, {
1379
+ reference,
1380
+ refundedNative,
1381
+ outcome: found.outcome === "ours" ? "cooperative_close" : "payer_exit",
1382
+ creditUnbacked
1383
+ });
1384
+ if (changed) {
1385
+ loggers.tempo.warn(creditUnbacked ? "tempo_session_drain_unbacked_resolved" : "tempo_session_drain_finalized", {
1386
+ channel_id: drain.channelId,
1387
+ credit_id: drain.creditId,
1388
+ drain_id: drain.drainId,
1389
+ amount_at_stake_native: drain.drainedNative.toString(),
1390
+ refund_native: refundedNative.toString(),
1391
+ builder_loss_native: (current.highestVoucherAmount > maxBigInt(current.settledOnChain, chainState.settled) ? current.highestVoucherAmount - maxBigInt(current.settledOnChain, chainState.settled) : 0n).toString()
1392
+ });
1393
+ }
1394
+ } catch (error) {
1395
+ let failure = error;
1396
+ if (error instanceof TempoDrainRefundUnprovenError) {
1397
+ try {
1398
+ await store.update(drain.channelId, (current) => {
1399
+ if (!isStateRecord(current)) throw new Error("Channel history is unavailable");
1400
+ const key = JSON.stringify([drain.creditId, drain.drainId, error.reason]);
1401
+ const seen = Array.isArray(current.drainRefundWarnings) ? current.drainRefundWarnings : [];
1402
+ if (seen.includes(key)) return { op: "noop", result: false };
1403
+ loggers.tempo.warn("tempo_session_drain_refund_unproven", {
1404
+ channel_id: drain.channelId,
1405
+ credit_id: drain.creditId,
1406
+ drain_id: drain.drainId,
1407
+ amount_at_stake_native: drain.drainedNative?.toString() ?? null,
1408
+ amount_at_stake_micro: drain.amountMicro,
1409
+ currency: drain.currency,
1410
+ reason: error.reason,
1411
+ error: error.message
1412
+ });
1413
+ return { op: "set", value: { ...current, drainRefundWarnings: [...seen, key] }, result: true };
1414
+ });
1415
+ continue;
1416
+ } catch (claimError) {
1417
+ failure = claimError;
1418
+ }
1419
+ }
1420
+ loggers.tempo.warn("tempo_session_close_reconcile_failed", {
1421
+ channel_id: drain.channelId,
1422
+ credit_id: drain.creditId,
1423
+ drain_id: drain.drainId,
1424
+ error: failure instanceof Error ? failure.message : String(failure)
1425
+ });
1426
+ }
1427
+ }
1428
+ const last = page.at(-1);
1429
+ after = page.length === 100 && last ? { createdAt: last.createdAt, creditId: last.creditId, drainId: last.drainId } : void 0;
1430
+ } while (after);
1431
+ };
1432
+ const reconcileTempoSessionCloses = async () => {
1433
+ await reconcileUnsubmittedCloses();
1434
+ await reconcileFinalizedDrains();
1435
+ };
1436
+ const reportRecoveredClose = async (channel, txHash, settledBeforeClose, settledToPayee) => {
1298
1437
  if (settledBeforeClose === null || settledToPayee < settledBeforeClose) {
1299
1438
  loggers.tempo.warn("tempo_session_close_settlement_unreported", {
1300
1439
  channel_id: channel.channelId,
1301
- tx_hash: txHash
1440
+ tx_hash: txHash,
1441
+ amount_at_stake_native: settledToPayee.toString()
1302
1442
  });
1303
1443
  return;
1304
1444
  }
@@ -1314,6 +1454,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1314
1454
  loggers.tempo.warn("tempo_session_close_settlement_callback_failed", {
1315
1455
  channel_id: channel.channelId,
1316
1456
  tx_hash: txHash,
1457
+ amount_at_stake_native: (settledToPayee - settledBeforeClose).toString(),
1317
1458
  error: error instanceof Error ? error.message : String(error)
1318
1459
  });
1319
1460
  }
@@ -1326,7 +1467,7 @@ function attachTempoSessionRuntime(mppx, config, chainOpOverrides = {}) {
1326
1467
  const confirmClose = store.confirmClose?.bind(store);
1327
1468
  return Object.assign(mppx, {
1328
1469
  sweepTempoSessions,
1329
- ...config?.hostedObligations && { reconcileTempoSessionCloses: reconcileUnsubmittedCloses },
1470
+ ...config?.hostedObligations && { reconcileTempoSessionCloses },
1330
1471
  ...beginClose && {
1331
1472
  beginTempoSessionClose: async (channelId, drainId) => {
1332
1473
  await beginClose(channelId, drainId);
@@ -1417,6 +1558,7 @@ export {
1417
1558
  TEMPO_SETTLEMENT_DEFAULT_LOW_BALANCE_MICRO,
1418
1559
  TempoSettlementReadiness,
1419
1560
  isTempoInsufficientFundsError,
1561
+ TempoCloseDescriptorError,
1420
1562
  MPPX_HMAC_MISMATCH_REASON,
1421
1563
  TEMPO_SETTLEMENT_LEASE_FIELD,
1422
1564
  TEMPO_SETTLEMENT_LEASE_OWNER_FIELD,
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  canonicaliseForSigning
3
3
  } from "./chunk-UKD3R2KU.js";
4
- import {
5
- callerLoggers
6
- } from "./chunk-66HGCPBU.js";
7
4
  import {
8
5
  DvmError
9
6
  } from "./chunk-BPYMEMQX.js";
7
+ import {
8
+ callerLoggers
9
+ } from "./chunk-66HGCPBU.js";
10
10
  import {
11
11
  diagnosticConsole
12
- } from "./chunk-BFGQG4WY.js";
12
+ } from "./chunk-OIP44KBB.js";
13
13
 
14
14
  // src/lib/cashu/lock-pubkey.ts
15
15
  function toLockPubkey(raw) {
@@ -418,6 +418,7 @@ var diagnostic_catalogue_v1_default = {
418
418
  "tempo_session_advertised_refresh_failed",
419
419
  "tempo_session_close_attempts_exhausted",
420
420
  "tempo_session_close_challenge_failed",
421
+ "tempo_session_close_descriptor_refused",
421
422
  "tempo_session_close_exit_resolved",
422
423
  "tempo_session_close_failed",
423
424
  "tempo_session_close_failure_reconcile_failed",
@@ -425,9 +426,13 @@ var diagnostic_catalogue_v1_default = {
425
426
  "tempo_session_close_recovery_failed",
426
427
  "tempo_session_close_recovery_search_paused",
427
428
  "tempo_session_close_recovery_unmatched",
429
+ "tempo_session_close_report_written_off",
428
430
  "tempo_session_close_reservation_released",
429
431
  "tempo_session_close_settlement_callback_failed",
430
432
  "tempo_session_close_settlement_unreported",
433
+ "tempo_session_drain_finalized",
434
+ "tempo_session_drain_refund_unproven",
435
+ "tempo_session_drain_unbacked_resolved",
431
436
  "tempo_session_open_refused",
432
437
  "tempo_session_settled",
433
438
  "tempo_session_withheld",
@@ -2,12 +2,12 @@ import {
2
2
  parseStoreValue,
3
3
  stringifyStoreValue
4
4
  } from "./chunk-YG7G4DPZ.js";
5
- import {
6
- withPostgresTransactionRecovery
7
- } from "./chunk-7PM4R4GD.js";
8
5
  import {
9
6
  withSdkInitLock
10
7
  } from "./chunk-COYKJRMM.js";
8
+ import {
9
+ withPostgresTransactionRecovery
10
+ } from "./chunk-IBX2ORBV.js";
11
11
 
12
12
  // src/sdk/server/tempo-session-store.ts
13
13
  import { AsyncLocalStorage } from "async_hooks";
@@ -7,7 +7,7 @@ import {
7
7
  extendDiagnosticIdentityAuthority,
8
8
  projectDiagnosticFields,
9
9
  projectSpanRow
10
- } from "./chunk-BFGQG4WY.js";
10
+ } from "./chunk-OIP44KBB.js";
11
11
 
12
12
  // src/observability/context.ts
13
13
  import { AsyncLocalStorage } from "async_hooks";
@@ -3,12 +3,12 @@ import {
3
3
  applyContentHoldMutation,
4
4
  isTerminal
5
5
  } from "./chunk-AFV77S4C.js";
6
- import {
7
- loggers
8
- } from "./chunk-IOQIXRGM.js";
9
6
  import {
10
7
  redactUrl
11
8
  } from "./chunk-FUJ36YDV.js";
9
+ import {
10
+ loggers
11
+ } from "./chunk-QSZW2I5Y.js";
12
12
 
13
13
  // src/sdk/server/job-store.ts
14
14
  function accumulateAskedTopUp(prior, ask) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  emitDiagnostic,
3
3
  projectDiagnosticFields
4
- } from "./chunk-BFGQG4WY.js";
4
+ } from "./chunk-OIP44KBB.js";
5
5
 
6
6
  // src/sdk/logger.ts
7
7
  function createConsoleLogger(_jobId) {
@@ -1,9 +1,9 @@
1
- import {
2
- diagnosticConsole
3
- } from "./chunk-BFGQG4WY.js";
4
1
  import {
5
2
  withSdkInitLock
6
3
  } from "./chunk-COYKJRMM.js";
4
+ import {
5
+ diagnosticConsole
6
+ } from "./chunk-OIP44KBB.js";
7
7
 
8
8
  // src/sdk/server/revenue-reporter.ts
9
9
  import { randomUUID } from "crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  validateCurrency
3
- } from "./chunk-2U6LAASN.js";
3
+ } from "./chunk-HC4I7LAV.js";
4
4
 
5
5
  // src/sdk/pricing/upfront.ts
6
6
  function buildUpfront(input) {
@@ -1,15 +1,15 @@
1
+ import {
2
+ withSdkInitLock
3
+ } from "./chunk-COYKJRMM.js";
1
4
  import {
2
5
  applyRetryToPool
3
- } from "./chunk-7PM4R4GD.js";
6
+ } from "./chunk-IBX2ORBV.js";
4
7
  import {
5
8
  x402NetworkByCaip2
6
9
  } from "./chunk-BPYMEMQX.js";
7
10
  import {
8
11
  diagnosticConsole
9
- } from "./chunk-BFGQG4WY.js";
10
- import {
11
- withSdkInitLock
12
- } from "./chunk-COYKJRMM.js";
12
+ } from "./chunk-OIP44KBB.js";
13
13
 
14
14
  // src/sdk/server/credit-ledger.ts
15
15
  import { randomUUID } from "crypto";
@@ -3153,7 +3153,7 @@ var CreditLedger = class {
3153
3153
  const { rows } = await this.pool.query(
3154
3154
  `SELECT d.*, ${DRAIN_CREDIT_COLUMNS} FROM credit_drains d
3155
3155
  JOIN credits c ON c.credit_id = d.credit_id
3156
- WHERE d.status = 'pending' ORDER BY d.created_at, d.credit_id, d.drain_id`
3156
+ WHERE d.status IN ('pending','exit_required') ORDER BY d.created_at, d.credit_id, d.drain_id`
3157
3157
  );
3158
3158
  return rows.map(drainRecordFromJoinRow);
3159
3159
  }
@@ -3173,7 +3173,7 @@ var CreditLedger = class {
3173
3173
  const { rows } = await this.pool.query(
3174
3174
  `SELECT d.*, ${DRAIN_CREDIT_COLUMNS} FROM credit_drains d
3175
3175
  JOIN credits c ON c.credit_id = d.credit_id
3176
- WHERE d.status = 'pending'
3176
+ WHERE d.status IN ('pending','exit_required')
3177
3177
  AND d.method = 'tempo'
3178
3178
  AND c.tempo_channel_id IS NOT NULL
3179
3179
  AND d.created_at <= $1
@@ -3225,6 +3225,22 @@ var CreditLedger = class {
3225
3225
  [args.creditId, args.drainId]
3226
3226
  );
3227
3227
  }
3228
+ /** Keep the debit and close intent while the caller uses its timed channel exit. */
3229
+ async requireTempoDrainExit(args) {
3230
+ const q = args.tx ?? this.pool;
3231
+ const { rows } = await q.query(
3232
+ `UPDATE credit_drains d SET status='exit_required'
3233
+ FROM credits c WHERE d.credit_id=c.credit_id AND d.credit_id=$1 AND d.drain_id=$2
3234
+ AND d.status='pending' AND d.method='tempo' AND c.tempo_channel_id IS NOT NULL
3235
+ RETURNING d.*`,
3236
+ [args.creditId, args.drainId]
3237
+ );
3238
+ if (rows.length) return { drain: await this.rejoin(q, rows[0]), replayed: false };
3239
+ return this.refuseTransition(q, args.creditId, args.drainId, {
3240
+ idempotentWhen: (row) => row.status === "exit_required" || row.status === "sent",
3241
+ verb: "require timed exit"
3242
+ });
3243
+ }
3228
3244
  /**
3229
3245
  * Cancel an unfulfilled Tempo cooperative drain and restore the exact
3230
3246
  * balance plus rail basis it removed (`pending -> released`). This is only
@@ -3366,7 +3382,7 @@ var CreditLedger = class {
3366
3382
  const nowMs = args.nowMs ?? Date.now();
3367
3383
  const { rows } = await q.query(
3368
3384
  `UPDATE credit_drains SET status = 'sent', sent_ref = $3, sent_at = $4
3369
- WHERE credit_id = $1 AND drain_id = $2 AND status = 'pending' AND method <> 'cashu'
3385
+ WHERE credit_id = $1 AND drain_id = $2 AND status IN ('pending','exit_required') AND method <> 'cashu'
3370
3386
  RETURNING *`,
3371
3387
  [args.creditId, args.drainId, JSON.stringify(args.sentRef), nowMs]
3372
3388
  );