@dvmkit/sdk 0.3.4-rc.10 → 0.5.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{chunk-2KGG5ORW.js → chunk-7ZUKL4IV.js} +3 -3
  2. package/dist/{chunk-KXZUCCEY.js → chunk-A4YFWZ7R.js} +4 -55
  3. package/dist/{chunk-C2AGARLM.js → chunk-B2IROPQF.js} +450 -83
  4. package/dist/{chunk-MKI6OVW4.js → chunk-CZULM42S.js} +0 -51
  5. package/dist/{chunk-UZC4A4DM.js → chunk-EYTHFFFU.js} +2489 -213
  6. package/dist/{chunk-M4QDXD2A.js → chunk-FYRR2UEZ.js} +1 -1
  7. package/dist/{chunk-4UZN2GP5.js → chunk-GAOAYHZA.js} +209 -33
  8. package/dist/{chunk-YYQG4R7T.js → chunk-GFY5FRX3.js} +6 -4
  9. package/dist/{chunk-Z4BNLUZF.js → chunk-IWQHUSMO.js} +3 -105
  10. package/dist/{chunk-4J32DTLT.js → chunk-IXR5RFK7.js} +82 -4
  11. package/dist/{chunk-2SD2W2BG.js → chunk-JH3SX3LY.js} +252 -26
  12. package/dist/chunk-KA6VN5FK.js +245 -0
  13. package/dist/{chunk-YRDA7EWN.js → chunk-LE74UEV7.js} +61 -16
  14. package/dist/chunk-LIRAHP7B.js +53 -0
  15. package/dist/{chunk-MG67KXU7.js → chunk-MZRJR2CW.js} +4 -2
  16. package/dist/chunk-OFVV37XC.js +57 -0
  17. package/dist/{chunk-A4CM2BD3.js → chunk-PAIM66YA.js} +91 -2
  18. package/dist/{chunk-JDFLRNAZ.js → chunk-PB4FYZET.js} +67 -7
  19. package/dist/{chunk-BQ2NMWKE.js → chunk-PLG4XER6.js} +1 -1
  20. package/dist/chunk-RLTSCKRI.js +109 -0
  21. package/dist/{chunk-VAMSJRJT.js → chunk-TUA7PYH7.js} +27 -105
  22. package/dist/{chunk-O3QDFPKM.js → chunk-TZ2WKCDD.js} +1 -1
  23. package/dist/{chunk-Z3EHBW2G.js → chunk-UOCZXO2Z.js} +7 -5
  24. package/dist/{chunk-EAMZZ2B4.js → chunk-X7EZ5CLI.js} +59 -0
  25. package/dist/{chunk-CE7XEHYC.js → chunk-Z4KLECAC.js} +1 -1
  26. package/dist/{chunk-7X6UDVCQ.js → chunk-Z4ZT5LNT.js} +66 -2
  27. package/dist/{credit-ledger-A35WH2TI.js → credit-ledger-MI2H7NEC.js} +3 -1
  28. package/dist/{credit-menu-0-UtJ3hh.d.ts → credit-menu-CAfqe8IY.d.ts} +133 -7
  29. package/dist/{fx-DhyT1Cjh.d.ts → fx-D9nsN3n4.d.ts} +1 -1
  30. package/dist/index.d.ts +6 -6
  31. package/dist/index.js +4 -3
  32. package/dist/internal/caller.d.ts +80 -13
  33. package/dist/internal/caller.js +56 -54
  34. package/dist/internal/index.js +1 -1
  35. package/dist/internal/server.d.ts +11 -85
  36. package/dist/internal/server.js +21 -18
  37. package/dist/{job-store-D3CcZp_g.d.ts → job-store-Bx-VFRjg.d.ts} +1 -1
  38. package/dist/{lightning-backend-BhsyrBLa.d.ts → lightning-backend-BAx2Y8pP.d.ts} +1 -1
  39. package/dist/{memory-credit-ledger-2LBZVMKJ.js → memory-credit-ledger-W2BAUYJP.js} +4 -2
  40. package/dist/{mpp-setup-7CGBKNUP.js → mpp-setup-RF5FXGGV.js} +5 -1
  41. package/dist/{payout-reporter-ZMOA37GN.js → payout-reporter-3Y2U55ZX.js} +1 -1
  42. package/dist/{postgres-job-store-MSD2HZOI.js → postgres-job-store-VLXLWXUQ.js} +1 -1
  43. package/dist/{revenue-reporter-EKJOGQYR.js → revenue-reporter-MAERPYGK.js} +1 -1
  44. package/dist/server/index.d.ts +14 -10
  45. package/dist/server/index.js +57 -19
  46. package/dist/{step-cache-v2yhyhSZ.d.ts → step-cache-QG_h5-3G.d.ts} +1769 -1549
  47. package/dist/{tempo-charge-store-4WX7FPOV.js → tempo-charge-store-Q3D7KYMH.js} +1 -1
  48. package/dist/tempo-lifecycle-ZBBLFBMH.js +15 -0
  49. package/dist/{tempo-session-store-LCSDHB7G.js → tempo-session-store-ZRNCOX7P.js} +1 -1
  50. package/dist/{tempo-wallet-RCGBBAJH.js → tempo-wallet-B5XXV3YH.js} +4 -3
  51. package/dist/testing/index.d.ts +3 -3
  52. package/dist/{usd-Cu9G6xv2.d.ts → usd-EwRqCPF-.d.ts} +1 -1
  53. package/dist/{x402-S47Q77UE.js → x402-R6HPBY5D.js} +18 -14
  54. package/package.json +1 -1
  55. package/dist/chunk-YHN3WMMF.js +0 -122
  56. package/dist/tempo-lifecycle-TJ62A7UX.js +0 -13
@@ -131,6 +131,7 @@ var JOB_MESSAGES_CHANNEL = "job_messages";
131
131
  var JOB_DVM_ID = /* @__PURE__ */ Symbol("jobDvmId");
132
132
  var PostgresJobStore = class {
133
133
  pool;
134
+ hostingCleanupFence;
134
135
  listenChannel = null;
135
136
  messageSubscribers = /* @__PURE__ */ new Map();
136
137
  notifySubscribers = /* @__PURE__ */ new Map();
@@ -138,6 +139,9 @@ var PostgresJobStore = class {
138
139
  constructor(pool) {
139
140
  this.pool = pool;
140
141
  }
142
+ useHostingCleanupFence(fence) {
143
+ this.hostingCleanupFence = fence;
144
+ }
141
145
  /** Run the CREATE TABLE migration. Call once at startup. */
142
146
  async init() {
143
147
  await withSdkInitLock(this.pool, () => this.createTables());
@@ -298,9 +302,11 @@ var PostgresJobStore = class {
298
302
  seq INTEGER NOT NULL,
299
303
  data JSONB NOT NULL,
300
304
  status TEXT NOT NULL DEFAULT 'verified',
305
+ created_at_ms BIGINT,
301
306
  PRIMARY KEY (job_id, seq)
302
307
  );
303
308
  ALTER TABLE job_messages ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'verified';
309
+ ALTER TABLE job_messages ADD COLUMN IF NOT EXISTS created_at_ms BIGINT;
304
310
 
305
311
  -- per-DVM monotonic receipt counter. Single row by
306
312
  -- construction (the CHECK pins the key), and per-DVM by construction
@@ -356,6 +362,7 @@ var PostgresJobStore = class {
356
362
  const client = await this.pool.connect();
357
363
  const lockKey = requestIdAdvisoryLockKey(claim.requesterPubkey, claim.requestId);
358
364
  let locked = false;
365
+ let transactional = false;
359
366
  try {
360
367
  const lock = await client.query(
361
368
  "SELECT pg_try_advisory_lock($1::bigint) AS acquired",
@@ -363,6 +370,11 @@ var PostgresJobStore = class {
363
370
  );
364
371
  if (!lock.rows[0]?.acquired) return void 0;
365
372
  locked = true;
373
+ if (createBinding && this.hostingCleanupFence) {
374
+ await client.query("BEGIN");
375
+ transactional = true;
376
+ await this.hostingCleanupFence.assertNewIntent(client);
377
+ }
366
378
  const now = Date.now();
367
379
  const inserted = createBinding ? await client.query(
368
380
  `INSERT INTO request_id_claims (
@@ -395,6 +407,10 @@ var PostgresJobStore = class {
395
407
  result = { jobId: row.job_id, replayed: true };
396
408
  }
397
409
  }
410
+ if (transactional) {
411
+ await client.query("COMMIT");
412
+ transactional = false;
413
+ }
398
414
  if (!result) return void 0;
399
415
  this.requestIdClaimLocks.set(claim.claimToken, {
400
416
  client,
@@ -403,6 +419,9 @@ var PostgresJobStore = class {
403
419
  requestId: claim.requestId
404
420
  });
405
421
  return result;
422
+ } catch (error) {
423
+ if (transactional) await client.query("ROLLBACK").catch(() => void 0);
424
+ throw error;
406
425
  } finally {
407
426
  if (!this.requestIdClaimLocks.has(claim.claimToken)) {
408
427
  let discard = false;
@@ -452,7 +471,7 @@ var PostgresJobStore = class {
452
471
  }
453
472
  async saveRecord(record, dvmId) {
454
473
  const now = Date.now();
455
- const { rowCount } = await this.pool.query(
474
+ const write = (q) => q.query(
456
475
  `INSERT INTO jobs (
457
476
  id, tags, capability, input, params, requester_id, status, summary,
458
477
  messages, seq, paid_msats, payment_mint, payment_rail,
@@ -596,6 +615,33 @@ var PostgresJobStore = class {
596
615
  record.timingKnown ?? true
597
616
  ]
598
617
  );
618
+ let rowCount;
619
+ if (this.hostingCleanupFence) {
620
+ const tx = await this.pool.connect();
621
+ try {
622
+ await tx.query("BEGIN");
623
+ const existing = await tx.query(
624
+ "SELECT dvm_id FROM jobs WHERE id=$1 FOR UPDATE",
625
+ [record.id]
626
+ );
627
+ if (existing.rows[0]) {
628
+ if (existing.rows[0].dvm_id !== null && existing.rows[0].dvm_id !== this.hostingCleanupFence.dvmId) {
629
+ throw new Error("Hosted job belongs to another DVM");
630
+ }
631
+ } else {
632
+ await this.hostingCleanupFence.assertNewIntent(tx, { jobId: record.id });
633
+ }
634
+ ({ rowCount } = await write(tx));
635
+ await tx.query("COMMIT");
636
+ } catch (error) {
637
+ await tx.query("ROLLBACK").catch(() => void 0);
638
+ throw error;
639
+ } finally {
640
+ tx.release();
641
+ }
642
+ } else {
643
+ ({ rowCount } = await write(this.pool));
644
+ }
599
645
  if (rowCount === 0) {
600
646
  diagnosticConsole.warn(
601
647
  {
@@ -607,7 +653,7 @@ var PostgresJobStore = class {
607
653
  return;
608
654
  }
609
655
  if (record.status === "completed" || record.status === "failed" || record.status === "cancelled") {
610
- await this.pool.query("DELETE FROM job_messages WHERE job_id = $1", [record.id]).catch(() => void 0);
656
+ await this.deleteResolvedMessages(record.id).catch(() => void 0);
611
657
  }
612
658
  }
613
659
  /** Advance only after the reporter durably enqueued or deduplicated the event. */
@@ -658,8 +704,11 @@ var PostgresJobStore = class {
658
704
  return rowCount === 1;
659
705
  }
660
706
  async delete(id) {
707
+ if (this.hostingCleanupFence) {
708
+ throw new Error("Hosted job deletion cannot discard obligation evidence");
709
+ }
661
710
  await this.pool.query("DELETE FROM jobs WHERE id = $1", [id]);
662
- await this.pool.query("DELETE FROM job_messages WHERE job_id = $1", [id]).catch(() => void 0);
711
+ await this.deleteResolvedMessages(id).catch(() => void 0);
663
712
  }
664
713
  async listJobsForRetention(args) {
665
714
  const after = args.after;
@@ -710,8 +759,12 @@ var PostgresJobStore = class {
710
759
  WHERE id = $1
711
760
  AND last_activity_at = $2
712
761
  AND redacted_at IS NULL
713
- AND status IN ('completed', 'failed', 'cancelled')`,
714
- [args.jobId, args.expectedLastActivityAt, args.redactedAt]
762
+ AND status IN ('completed', 'failed', 'cancelled')
763
+ AND ($4::boolean = false OR NOT EXISTS (
764
+ SELECT 1 FROM job_messages m WHERE m.job_id=jobs.id
765
+ AND m.data->>'type'='payment'
766
+ AND m.status IN ('pending-verification','failed-verify')))`,
767
+ [args.jobId, args.expectedLastActivityAt, args.redactedAt, !!this.hostingCleanupFence]
715
768
  );
716
769
  if (rowCount === 1) {
717
770
  await client.query("DELETE FROM job_messages WHERE job_id = $1", [args.jobId]);
@@ -870,6 +923,7 @@ var PostgresJobStore = class {
870
923
  const client = await this.pool.connect();
871
924
  try {
872
925
  await client.query("BEGIN");
926
+ if (message.type === "payment") await this.hostingCleanupFence?.assertNewIntent(client);
873
927
  const { rows } = await client.query(
874
928
  `UPDATE jobs SET next_seq = next_seq + 1, last_activity_at = $2
875
929
  WHERE id = $1 AND redacted_at IS NULL
@@ -883,7 +937,9 @@ var PostgresJobStore = class {
883
937
  const seq = rows[0].seq;
884
938
  const fullMessage = { ...message, seq };
885
939
  await client.query(
886
- "INSERT INTO job_messages (job_id, seq, data, status) VALUES ($1, $2, $3, 'pending-verification')",
940
+ `INSERT INTO job_messages (job_id, seq, data, status, created_at_ms)
941
+ VALUES ($1, $2, $3, 'pending-verification',
942
+ FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint)`,
887
943
  [jobId, seq, JSON.stringify(fullMessage)]
888
944
  );
889
945
  await client.query("SELECT pg_notify('job_messages', $1)", [jobId]);
@@ -1272,9 +1328,13 @@ var PostgresJobStore = class {
1272
1328
  } finally {
1273
1329
  client.release();
1274
1330
  }
1275
- await this.pool.query("DELETE FROM job_messages WHERE job_id = $1", [jobId]).catch(() => void 0);
1331
+ await this.deleteResolvedMessages(jobId).catch(() => void 0);
1276
1332
  return true;
1277
1333
  }
1334
+ async deleteResolvedMessages(jobId) {
1335
+ await this.pool.query(this.hostingCleanupFence ? `DELETE FROM job_messages WHERE job_id=$1 AND NOT (
1336
+ data->>'type'='payment' AND status IN ('pending-verification','failed-verify'))` : "DELETE FROM job_messages WHERE job_id=$1", [jobId]);
1337
+ }
1278
1338
  async fetchCountersTx(client, jobId) {
1279
1339
  const { rows } = await client.query(
1280
1340
  "SELECT status, paid_msats, pending_payment_msats, summary FROM jobs WHERE id = $1",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  X402_DEFAULT_FACILITATOR
3
- } from "./chunk-Z4BNLUZF.js";
3
+ } from "./chunk-IWQHUSMO.js";
4
4
  import {
5
5
  callerLoggers
6
6
  } from "./chunk-66HGCPBU.js";
@@ -0,0 +1,109 @@
1
+ import {
2
+ DvmError
3
+ } from "./chunk-LIRAHP7B.js";
4
+
5
+ // src/lib/x402/networks.ts
6
+ var X402_NETWORKS = [
7
+ {
8
+ caip2: "eip155:8453",
9
+ slug: "base",
10
+ label: "Base",
11
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
12
+ domainName: "USD Coin",
13
+ domainVersion: "2",
14
+ viemChain: "base",
15
+ nativeGas: "ETH",
16
+ confusable: "USDbC"
17
+ },
18
+ {
19
+ caip2: "eip155:84532",
20
+ slug: "base-sepolia",
21
+ label: "Base Sepolia",
22
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
23
+ domainName: "USDC",
24
+ domainVersion: "2",
25
+ viemChain: "baseSepolia",
26
+ nativeGas: "ETH",
27
+ confusable: null
28
+ },
29
+ {
30
+ caip2: "eip155:43114",
31
+ slug: "avalanche",
32
+ label: "Avalanche",
33
+ usdc: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
34
+ domainName: "USD Coin",
35
+ domainVersion: "2",
36
+ viemChain: "avalanche",
37
+ nativeGas: "AVAX",
38
+ confusable: "USDC.e"
39
+ },
40
+ {
41
+ caip2: "eip155:43113",
42
+ slug: "avalanche-fuji",
43
+ label: "Avalanche Fuji",
44
+ usdc: "0x5425890298aed601595a70AB815c96711a31Bc65",
45
+ domainName: "USD Coin",
46
+ domainVersion: "2",
47
+ viemChain: "avalancheFuji",
48
+ nativeGas: "AVAX",
49
+ confusable: null
50
+ },
51
+ {
52
+ caip2: "eip155:137",
53
+ slug: "polygon",
54
+ label: "Polygon",
55
+ usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
56
+ domainName: "USD Coin (PoS)",
57
+ domainVersion: "2",
58
+ viemChain: "polygon",
59
+ nativeGas: "POL",
60
+ confusable: "USDC.e"
61
+ },
62
+ {
63
+ caip2: "eip155:80002",
64
+ slug: "polygon-amoy",
65
+ label: "Polygon Amoy",
66
+ usdc: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582",
67
+ domainName: "USDC",
68
+ domainVersion: "2",
69
+ viemChain: "polygonAmoy",
70
+ nativeGas: "POL",
71
+ confusable: null
72
+ }
73
+ ];
74
+ function x402NetworkByCaip2(caip2) {
75
+ return BY_CAIP2.get(caip2);
76
+ }
77
+ function x402NetworkBySlug(slug) {
78
+ return BY_SLUG.get(slug);
79
+ }
80
+ function x402NetworkLabel(network) {
81
+ const spec = x402NetworkByCaip2(network);
82
+ return spec ? `${spec.label} (${network})` : network;
83
+ }
84
+ function x402NetworkListForCopy() {
85
+ return X402_NETWORKS.map((n) => `${n.caip2} (${n.label})`).join(", ");
86
+ }
87
+ function assertSupportedX402Network(network) {
88
+ if (x402NetworkByCaip2(network)) return;
89
+ throw new DvmError(
90
+ "unsupported_network",
91
+ `Unsupported x402 network: ${network}.`,
92
+ `${x402SupportedNetworksHint()} If a wallet is connected, run 'dvm wallet x402-network <caip2>'. For a first connection, pass --network to 'dvm wallet x402-connect'.`
93
+ );
94
+ }
95
+ function x402SupportedNetworksHint() {
96
+ return `This rail settles native USDC on: ${x402NetworkListForCopy()}.`;
97
+ }
98
+ var BY_CAIP2 = new Map(X402_NETWORKS.map((n) => [n.caip2, n]));
99
+ var BY_SLUG = new Map(X402_NETWORKS.map((n) => [n.slug, n]));
100
+
101
+ export {
102
+ X402_NETWORKS,
103
+ x402NetworkByCaip2,
104
+ x402NetworkBySlug,
105
+ x402NetworkLabel,
106
+ x402NetworkListForCopy,
107
+ assertSupportedX402Network,
108
+ x402SupportedNetworksHint
109
+ };
@@ -1,23 +1,31 @@
1
1
  import {
2
+ computeResultHash,
2
3
  costAnchor,
3
4
  formatUsd,
4
- msatsToUsd
5
- } from "./chunk-A4CM2BD3.js";
5
+ isDrainReceipt,
6
+ isFundingReceipt,
7
+ msatsToUsd,
8
+ verifyDrainReceipt,
9
+ verifyFundingReceipt,
10
+ verifyReceipt
11
+ } from "./chunk-PAIM66YA.js";
6
12
  import {
7
13
  AGENT_MNEMONIC_FILE,
8
14
  BUILDER_CONFIG_DIR,
9
15
  BUILDER_FILE,
10
16
  BUILDER_KEY_FILE,
11
- DvmError,
12
17
  WALLET_FILE,
13
18
  ensureConfigDir
14
- } from "./chunk-MKI6OVW4.js";
19
+ } from "./chunk-CZULM42S.js";
15
20
  import {
16
21
  canonicaliseForSigning
17
- } from "./chunk-KXZUCCEY.js";
22
+ } from "./chunk-OFVV37XC.js";
18
23
  import {
19
24
  callerLoggers
20
25
  } from "./chunk-66HGCPBU.js";
26
+ import {
27
+ DvmError
28
+ } from "./chunk-LIRAHP7B.js";
21
29
  import {
22
30
  diagnosticConsole
23
31
  } from "./chunk-ETXDTUJO.js";
@@ -1165,82 +1173,6 @@ function hexToBytes2(hex) {
1165
1173
  return out;
1166
1174
  }
1167
1175
 
1168
- // src/lib/receipt.ts
1169
- import { schnorr as schnorr3 } from "@noble/curves/secp256k1.js";
1170
- import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
1171
- import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
1172
- function computeResultHash(summary, artifactContents) {
1173
- const artifactHashes = artifactContents.map(
1174
- (content) => bytesToHex3(sha2563(canonicaliseForSigning(content)))
1175
- );
1176
- return bytesToHex3(
1177
- sha2563(
1178
- canonicaliseForSigning({
1179
- summary: summary ?? null,
1180
- artifact_hashes: artifactHashes
1181
- })
1182
- )
1183
- );
1184
- }
1185
- function signReceipt(unsigned, secretHex) {
1186
- const bytes = canonicaliseForSigning(unsigned);
1187
- const signature = bytesToHex3(schnorr3.sign(bytes, hexToBytes3(secretHex)));
1188
- return { ...unsigned, signature };
1189
- }
1190
- function verifyReceipt(receipt) {
1191
- try {
1192
- const { signature, ...unsigned } = receipt;
1193
- const bytes = canonicaliseForSigning(unsigned);
1194
- return schnorr3.verify(hexToBytes3(signature), bytes, hexToBytes3(receipt.receipt_pubkey));
1195
- } catch {
1196
- return false;
1197
- }
1198
- }
1199
- function isSignedJobReceipt(value) {
1200
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1201
- const r = value;
1202
- const paid = r.paid;
1203
- return r.v === 1 && typeof r.job_id === "string" && typeof r.dvm_id === "string" && typeof r.dvm === "string" && typeof r.capability === "string" && (r.outcome === "completed" || r.outcome === "failed" || r.outcome === "cancelled") && typeof r.seq === "number" && typeof r.issued_at === "number" && typeof r.receipt_pubkey === "string" && typeof r.signature === "string" && !!paid && typeof paid === "object" && !Array.isArray(paid) && typeof paid.msats === "number";
1204
- }
1205
- function signDrainReceipt(unsigned, secretHex) {
1206
- const bytes = canonicaliseForSigning(unsigned);
1207
- const signature = bytesToHex3(schnorr3.sign(bytes, hexToBytes3(secretHex)));
1208
- return { ...unsigned, signature };
1209
- }
1210
- function signFundingReceipt(unsigned, secretHex) {
1211
- const bytes = canonicaliseForSigning(unsigned);
1212
- const signature = bytesToHex3(schnorr3.sign(bytes, hexToBytes3(secretHex)));
1213
- return { ...unsigned, signature };
1214
- }
1215
- function isFundingReceipt(value) {
1216
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1217
- const r = value;
1218
- return r.v === 1 && r.kind === "credit_funding" && typeof r.credit_id === "string" && typeof r.fund_id === "string" && typeof r.funded_micro === "number" && typeof r.balance_after_micro === "number" && typeof r.ledger_seq === "number" && typeof r.issued_at === "number" && typeof r.caller_pubkey === "string" && typeof r.dvm_id === "string" && typeof r.dvm === "string" && typeof r.receipt_pubkey === "string" && typeof r.signature === "string";
1219
- }
1220
- function verifyFundingReceipt(receipt) {
1221
- try {
1222
- const { signature, ...unsigned } = receipt;
1223
- const bytes = canonicaliseForSigning(unsigned);
1224
- return schnorr3.verify(hexToBytes3(signature), bytes, hexToBytes3(receipt.receipt_pubkey));
1225
- } catch {
1226
- return false;
1227
- }
1228
- }
1229
- function isDrainReceipt(value) {
1230
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1231
- const r = value;
1232
- return r.v === 1 && r.kind === "credit_drain" && typeof r.credit_id === "string" && typeof r.drain_id === "string" && (r.event === "requested" || r.event === "parked" || r.event === "picked_up" || r.event === "sent" || r.event === "released") && typeof r.method === "string" && typeof r.amount_micro === "number" && typeof r.issued_at === "number" && typeof r.caller_pubkey === "string" && typeof r.dvm_id === "string" && typeof r.dvm === "string" && typeof r.receipt_pubkey === "string" && typeof r.signature === "string";
1233
- }
1234
- function verifyDrainReceipt(receipt) {
1235
- try {
1236
- const { signature, ...unsigned } = receipt;
1237
- const bytes = canonicaliseForSigning(unsigned);
1238
- return schnorr3.verify(hexToBytes3(signature), bytes, hexToBytes3(receipt.receipt_pubkey));
1239
- } catch {
1240
- return false;
1241
- }
1242
- }
1243
-
1244
1176
  // src/lib/receipt-verify.ts
1245
1177
  function attestedDvmIdentity(builder) {
1246
1178
  const builderPubkey = authenticatedBuilderPubkey(builder);
@@ -1541,7 +1473,7 @@ function derivePubkeyFromPrivkey(privkey) {
1541
1473
  "Provide a valid secp256k1 private key as a 64-char hex string."
1542
1474
  );
1543
1475
  }
1544
- const bytes = hexToBytes4(privkey);
1476
+ const bytes = hexToBytes3(privkey);
1545
1477
  if (!secp256k12.utils.isValidSecretKey(bytes)) {
1546
1478
  throw new DvmError(
1547
1479
  "invalid_privkey",
@@ -1550,17 +1482,17 @@ function derivePubkeyFromPrivkey(privkey) {
1550
1482
  );
1551
1483
  }
1552
1484
  const pub = secp256k12.getPublicKey(bytes);
1553
- return bytesToHex4(pub);
1485
+ return bytesToHex3(pub);
1554
1486
  }
1555
- function bytesToHex4(bytes) {
1487
+ function bytesToHex3(bytes) {
1556
1488
  return Buffer.from(bytes).toString("hex");
1557
1489
  }
1558
- function hexToBytes4(hex) {
1490
+ function hexToBytes3(hex) {
1559
1491
  return Uint8Array.from(Buffer.from(hex, "hex"));
1560
1492
  }
1561
1493
 
1562
1494
  // src/lib/wallet-agent/sign.ts
1563
- import { schnorr as schnorr4 } from "@noble/curves/secp256k1.js";
1495
+ import { schnorr as schnorr3 } from "@noble/curves/secp256k1.js";
1564
1496
  var PUBKEY_COMPRESSED_HEX_RE = /^0[23][0-9a-f]{64}$/i;
1565
1497
  var PUBKEY_XONLY_HEX_RE = /^[0-9a-f]{64}$/i;
1566
1498
  var SIG_HEX_RE = /^[0-9a-f]{128}$/i;
@@ -1579,42 +1511,42 @@ function signChallenge(privkeyHex, challenge) {
1579
1511
  "Pass the SHA-256 digest output (32 bytes)."
1580
1512
  );
1581
1513
  }
1582
- const sig = schnorr4.sign(challenge, hexToBytes5(privkeyHex));
1583
- return bytesToHex5(sig);
1514
+ const sig = schnorr3.sign(challenge, hexToBytes4(privkeyHex));
1515
+ return bytesToHex4(sig);
1584
1516
  }
1585
1517
  function verifyChallenge(pubkeyHex, challenge, sigHex) {
1586
1518
  if (!SIG_HEX_RE.test(sigHex)) return false;
1587
1519
  if (challenge.length !== 32) return false;
1588
1520
  let xonly;
1589
1521
  if (PUBKEY_COMPRESSED_HEX_RE.test(pubkeyHex)) {
1590
- xonly = hexToBytes5(pubkeyHex.slice(2));
1522
+ xonly = hexToBytes4(pubkeyHex.slice(2));
1591
1523
  } else if (PUBKEY_XONLY_HEX_RE.test(pubkeyHex)) {
1592
- xonly = hexToBytes5(pubkeyHex);
1524
+ xonly = hexToBytes4(pubkeyHex);
1593
1525
  } else {
1594
1526
  return false;
1595
1527
  }
1596
1528
  try {
1597
- return schnorr4.verify(hexToBytes5(sigHex), challenge, xonly);
1529
+ return schnorr3.verify(hexToBytes4(sigHex), challenge, xonly);
1598
1530
  } catch {
1599
1531
  return false;
1600
1532
  }
1601
1533
  }
1602
- function hexToBytes5(hex) {
1534
+ function hexToBytes4(hex) {
1603
1535
  return Uint8Array.from(Buffer.from(hex, "hex"));
1604
1536
  }
1605
- function bytesToHex5(bytes) {
1537
+ function bytesToHex4(bytes) {
1606
1538
  return Buffer.from(bytes).toString("hex");
1607
1539
  }
1608
1540
 
1609
1541
  // src/lib/cashu/admin-auth.ts
1610
1542
  import { randomBytes } from "crypto";
1611
- import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
1543
+ import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
1612
1544
  var ADMIN_AUTH_NONCE_TTL_SECONDS = 300;
1613
1545
  var ADMIN_AUTH_CLOCK_SKEW_SECONDS = 30;
1614
1546
  var ADMIN_STATE_UNAVAILABLE = "admin_state_unavailable";
1615
1547
  function buildAdminChallenge(args) {
1616
1548
  const msg = `dvmkit-admin:${args.method}:${args.path}:${args.dvmId}:${args.nonceHex}:${args.unixSeconds}`;
1617
- return sha2564(new TextEncoder().encode(msg));
1549
+ return sha2563(new TextEncoder().encode(msg));
1618
1550
  }
1619
1551
  function buildAdminAuthHeader(args) {
1620
1552
  const nowMs = args.now ? args.now() : Date.now();
@@ -1737,16 +1669,6 @@ export {
1737
1669
  buildAttestation,
1738
1670
  signAttestation,
1739
1671
  verifyAttestation,
1740
- computeResultHash,
1741
- signReceipt,
1742
- verifyReceipt,
1743
- isSignedJobReceipt,
1744
- signDrainReceipt,
1745
- signFundingReceipt,
1746
- isFundingReceipt,
1747
- verifyFundingReceipt,
1748
- isDrainReceipt,
1749
- verifyDrainReceipt,
1750
1672
  attestedDvmIdentity,
1751
1673
  authenticatedBuilderPubkey,
1752
1674
  verifyJobReceipt,
@@ -4,7 +4,7 @@ import {
4
4
  DEFAULT_CREDIT_TTL_SECONDS,
5
5
  DEFAULT_JOB_RETENTION_DAYS,
6
6
  isZodSchema
7
- } from "./chunk-KXZUCCEY.js";
7
+ } from "./chunk-A4YFWZ7R.js";
8
8
  import {
9
9
  InvalidCurrencyError,
10
10
  validateCurrency
@@ -2,31 +2,33 @@ import {
2
2
  connectedTempoAccount,
3
3
  tempoBalanceCheckHint,
4
4
  tempoTokenReference
5
- } from "./chunk-YYQG4R7T.js";
5
+ } from "./chunk-GFY5FRX3.js";
6
6
  import {
7
7
  fundingReachableAt,
8
8
  listCredits,
9
9
  maxAdvertisedMicro,
10
10
  normalizeCreditEndpoint,
11
11
  withinAdvertisedTolerance
12
- } from "./chunk-C2AGARLM.js";
12
+ } from "./chunk-B2IROPQF.js";
13
13
  import {
14
14
  costAnchor,
15
15
  formatUsd,
16
16
  formatUsdcMicrounits
17
- } from "./chunk-A4CM2BD3.js";
17
+ } from "./chunk-PAIM66YA.js";
18
18
  import {
19
- DvmError,
20
19
  IDENTITIES_FILE,
21
20
  TEMPO_CHANNELS_FILE,
22
21
  ensureConfigDir,
23
22
  resolveRpcHttpTransportOptions,
24
23
  resolveRpcOverride
25
- } from "./chunk-MKI6OVW4.js";
24
+ } from "./chunk-CZULM42S.js";
26
25
  import {
27
26
  redactUrl,
28
27
  redactUrlsInText
29
28
  } from "./chunk-FUJ36YDV.js";
29
+ import {
30
+ DvmError
31
+ } from "./chunk-LIRAHP7B.js";
30
32
 
31
33
  // src/lib/cli-identity-store.ts
32
34
  import { schnorr } from "@noble/curves/secp256k1.js";
@@ -17,9 +17,16 @@ var PostgresTempoSessionStore = class {
17
17
  this.update = this.update.bind(this);
18
18
  }
19
19
  pool;
20
+ hostingCleanupFence;
21
+ useHostingCleanupFence(fence) {
22
+ this.hostingCleanupFence = fence;
23
+ }
20
24
  acceptance = new AsyncLocalStorage();
21
25
  async init() {
22
26
  await withSdkInitLock(this.pool, async () => {
27
+ const before = await this.pool.query(
28
+ "SELECT to_regclass('mpp_tempo_channels') IS NOT NULL AS existed"
29
+ );
23
30
  await this.pool.query(`
24
31
  CREATE TABLE IF NOT EXISTS mpp_tempo_channels (
25
32
  channel_key TEXT PRIMARY KEY,
@@ -31,6 +38,24 @@ var PostgresTempoSessionStore = class {
31
38
  `CREATE INDEX IF NOT EXISTS idx_mpp_tempo_channels_updated
32
39
  ON mpp_tempo_channels(updated_at)`
33
40
  );
41
+ await this.pool.query(`
42
+ CREATE TABLE IF NOT EXISTS mpp_tempo_channel_tombstones (
43
+ channel_key TEXT PRIMARY KEY,
44
+ state_json TEXT NOT NULL,
45
+ deleted_at_ms BIGINT NOT NULL
46
+ )
47
+ `);
48
+ await this.pool.query(`
49
+ CREATE TABLE IF NOT EXISTS mpp_tempo_channel_history (
50
+ singleton BOOLEAN PRIMARY KEY CHECK (singleton),
51
+ complete BOOLEAN NOT NULL
52
+ )
53
+ `);
54
+ await this.pool.query(
55
+ `INSERT INTO mpp_tempo_channel_history (singleton,complete) VALUES (TRUE,$1)
56
+ ON CONFLICT (singleton) DO NOTHING`,
57
+ [!before.rows[0].existed]
58
+ );
34
59
  await this.pool.query(`
35
60
  CREATE TABLE IF NOT EXISTS mpp_tempo_close_intents (
36
61
  channel_id TEXT PRIMARY KEY,
@@ -75,6 +100,17 @@ var PostgresTempoSessionStore = class {
75
100
  }
76
101
  /** Compose voucher acceptance and its ledger mutation under the channel-row lock. */
77
102
  async withVoucherAcceptance(voucher, commit, operation) {
103
+ if (this.hostingCleanupFence) {
104
+ if (!voucher.hostingFunding) throw new Error("Hosted Tempo voucher has no original funding admission");
105
+ const reservation = voucher.hostingFunding;
106
+ await this.hostingCleanupFence.withNewIntent({
107
+ admissionId: reservation.admissionId,
108
+ creditId: reservation.creditId,
109
+ fundId: reservation.fundId,
110
+ callerPubkey: reservation.callerPubkey,
111
+ fundingCommitment: reservation.commitment
112
+ }, (tx) => this.hostingCleanupFence.assertFundingReservation(tx, reservation));
113
+ }
78
114
  const context = {
79
115
  commit,
80
116
  channelId: voucher.channelId.toLowerCase(),
@@ -365,6 +401,16 @@ var PostgresTempoSessionStore = class {
365
401
  };
366
402
  }
367
403
  assertMonotonicTransition(current, value);
404
+ if (current === null) {
405
+ const prior = await client.query(
406
+ "SELECT state_json FROM mpp_tempo_channel_tombstones WHERE channel_key=$1 FOR UPDATE",
407
+ [key]
408
+ );
409
+ if (prior.rows[0]) assertMonotonicTransition(parseStoreValue(prior.rows[0].state_json), value);
410
+ }
411
+ if (!acceptance && this.hostingCleanupFence && (current === null || (asBigInt(value.highestVoucherAmount) ?? 0n) > (isRecord(current) ? asBigInt(current.highestVoucherAmount) ?? 0n : 0n))) {
412
+ await this.hostingCleanupFence.assertNewIntent(client);
413
+ }
368
414
  await client.query(
369
415
  `INSERT INTO mpp_tempo_channels (channel_key, state_json, updated_at)
370
416
  VALUES ($1, $2, $3)
@@ -382,6 +428,19 @@ var PostgresTempoSessionStore = class {
382
428
  });
383
429
  }
384
430
  } else if (change.op === "delete") {
431
+ if (current !== null) {
432
+ const prior = await client.query(
433
+ "SELECT state_json FROM mpp_tempo_channel_tombstones WHERE channel_key=$1 FOR UPDATE",
434
+ [key]
435
+ );
436
+ if (prior.rows[0]) assertMonotonicTransition(parseStoreValue(prior.rows[0].state_json), current);
437
+ await client.query(
438
+ `INSERT INTO mpp_tempo_channel_tombstones (channel_key,state_json,deleted_at_ms)
439
+ VALUES ($1,$2,$3) ON CONFLICT (channel_key) DO UPDATE
440
+ SET state_json=EXCLUDED.state_json,deleted_at_ms=EXCLUDED.deleted_at_ms`,
441
+ [key, rows[0].state_json, Date.now()]
442
+ );
443
+ }
385
444
  await client.query(`DELETE FROM mpp_tempo_channels WHERE channel_key = $1`, [key]);
386
445
  }
387
446
  return change.result;
@@ -4,7 +4,7 @@ import {
4
4
  signedRequestHeaderMatchesDomain,
5
5
  signedRequestStatementHeader,
6
6
  testOnlySignedRequestDomain
7
- } from "./chunk-KXZUCCEY.js";
7
+ } from "./chunk-A4YFWZ7R.js";
8
8
  import {
9
9
  diagnosticConsole
10
10
  } from "./chunk-ETXDTUJO.js";