@dvmkit/sdk 0.3.4-rc.10 → 0.4.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 (50) hide show
  1. package/dist/{chunk-C2AGARLM.js → chunk-2VGWYG4P.js} +5 -3
  2. package/dist/{chunk-A4CM2BD3.js → chunk-3MHE725Z.js} +1 -1
  3. package/dist/{chunk-VAMSJRJT.js → chunk-5LR6P7WN.js} +5 -3
  4. package/dist/{chunk-YYQG4R7T.js → chunk-74PUEZJ4.js} +6 -4
  5. package/dist/{chunk-MKI6OVW4.js → chunk-CZULM42S.js} +0 -51
  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-Z4BNLUZF.js → chunk-IWQHUSMO.js} +3 -105
  9. package/dist/{chunk-4J32DTLT.js → chunk-IXR5RFK7.js} +82 -4
  10. package/dist/{chunk-2SD2W2BG.js → chunk-JH3SX3LY.js} +252 -26
  11. package/dist/{chunk-UZC4A4DM.js → chunk-K2YX2VRA.js} +2480 -206
  12. package/dist/chunk-KA6VN5FK.js +245 -0
  13. package/dist/chunk-LIRAHP7B.js +53 -0
  14. package/dist/{chunk-MG67KXU7.js → chunk-MZRJR2CW.js} +4 -2
  15. package/dist/{chunk-JDFLRNAZ.js → chunk-PB4FYZET.js} +67 -7
  16. package/dist/{chunk-BQ2NMWKE.js → chunk-PLG4XER6.js} +1 -1
  17. package/dist/{chunk-Z3EHBW2G.js → chunk-R4DQZFAL.js} +7 -5
  18. package/dist/chunk-RLTSCKRI.js +109 -0
  19. package/dist/{chunk-2KGG5ORW.js → chunk-TXX4GOUB.js} +3 -3
  20. package/dist/{chunk-YRDA7EWN.js → chunk-W2EQGDF5.js} +14 -10
  21. package/dist/{chunk-EAMZZ2B4.js → chunk-X7EZ5CLI.js} +59 -0
  22. package/dist/{chunk-7X6UDVCQ.js → chunk-Z4ZT5LNT.js} +66 -2
  23. package/dist/{credit-ledger-A35WH2TI.js → credit-ledger-MI2H7NEC.js} +3 -1
  24. package/dist/{credit-menu-0-UtJ3hh.d.ts → credit-menu-B8_p_vUz.d.ts} +133 -7
  25. package/dist/{fx-DhyT1Cjh.d.ts → fx-DuaYHvVn.d.ts} +1 -1
  26. package/dist/index.d.ts +6 -6
  27. package/dist/internal/caller.d.ts +6 -6
  28. package/dist/internal/caller.js +28 -24
  29. package/dist/internal/server.d.ts +11 -85
  30. package/dist/internal/server.js +18 -16
  31. package/dist/{job-store-D3CcZp_g.d.ts → job-store-D6Rjj5Oi.d.ts} +1 -1
  32. package/dist/{lightning-backend-BhsyrBLa.d.ts → lightning-backend-BaeCPmx7.d.ts} +1 -1
  33. package/dist/{memory-credit-ledger-2LBZVMKJ.js → memory-credit-ledger-W2BAUYJP.js} +4 -2
  34. package/dist/{mpp-setup-7CGBKNUP.js → mpp-setup-RF5FXGGV.js} +5 -1
  35. package/dist/{payout-reporter-ZMOA37GN.js → payout-reporter-3Y2U55ZX.js} +1 -1
  36. package/dist/{postgres-job-store-MSD2HZOI.js → postgres-job-store-VLXLWXUQ.js} +1 -1
  37. package/dist/{revenue-reporter-EKJOGQYR.js → revenue-reporter-MAERPYGK.js} +1 -1
  38. package/dist/server/index.d.ts +14 -10
  39. package/dist/server/index.js +54 -17
  40. package/dist/{step-cache-v2yhyhSZ.d.ts → step-cache-Bi43A0JN.d.ts} +1769 -1549
  41. package/dist/{tempo-charge-store-4WX7FPOV.js → tempo-charge-store-Q3D7KYMH.js} +1 -1
  42. package/dist/tempo-lifecycle-OTHV757Y.js +14 -0
  43. package/dist/{tempo-session-store-LCSDHB7G.js → tempo-session-store-ZRNCOX7P.js} +1 -1
  44. package/dist/{tempo-wallet-RCGBBAJH.js → tempo-wallet-32JWQZKQ.js} +4 -3
  45. package/dist/testing/index.d.ts +3 -3
  46. package/dist/{usd-Cu9G6xv2.d.ts → usd-AZPVq4eU.d.ts} +1 -1
  47. package/dist/{x402-S47Q77UE.js → x402-CEDHGZDI.js} +17 -14
  48. package/package.json +1 -1
  49. package/dist/chunk-YHN3WMMF.js +0 -122
  50. package/dist/tempo-lifecycle-TJ62A7UX.js +0 -13
@@ -0,0 +1,245 @@
1
+ import {
2
+ parseStoreValue,
3
+ stringifyStoreValue
4
+ } from "./chunk-YG7G4DPZ.js";
5
+ import {
6
+ withSdkInitLock
7
+ } from "./chunk-COYKJRMM.js";
8
+ import {
9
+ diagnosticConsole
10
+ } from "./chunk-ETXDTUJO.js";
11
+
12
+ // src/sdk/server/tempo-charge-store.ts
13
+ import { Credential } from "mppx";
14
+ import { createHash } from "crypto";
15
+ import { keccak256 } from "viem";
16
+ var DEFAULT_TABLE_NAME = "mpp_tempo_charge_replays";
17
+ var DEFAULT_RETENTION_MS = 60 * 60 * 1e3;
18
+ var DEFAULT_GC_SAMPLE_RATE = 0.01;
19
+ var VALID_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
20
+ function replayKeyFor(credential) {
21
+ const payload = credential.payload;
22
+ if (payload.type === "proof") return `mppx:charge:proof:${credential.challenge.id}`;
23
+ if (payload.type === "hash" && /^0x[0-9a-fA-F]{64}$/.test(payload.hash ?? "")) {
24
+ return `mppx:charge:${payload.hash.toLowerCase()}`;
25
+ }
26
+ if (payload.type === "transaction" && /^0x[0-9a-fA-F]+$/.test(payload.signature ?? "")) {
27
+ return `mppx:charge:${keccak256(payload.signature).toLowerCase()}`;
28
+ }
29
+ throw new Error("Hosted Tempo charge credential has no supported replay key");
30
+ }
31
+ function canonicalCredential(credential) {
32
+ const serialized = Credential.serialize(credential);
33
+ const encoded = serialized.slice(serialized.indexOf(" ") + 1);
34
+ const wire = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
35
+ const sorted = (value) => {
36
+ if (Array.isArray(value)) return value.map(sorted);
37
+ if (value && typeof value === "object") {
38
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, field]) => [key, sorted(field)]));
39
+ }
40
+ return value;
41
+ };
42
+ return JSON.stringify(sorted(wire));
43
+ }
44
+ var PostgresTempoChargeStore = class {
45
+ hostingCleanupFence;
46
+ useHostingCleanupFence(fence) {
47
+ this.hostingCleanupFence = fence;
48
+ }
49
+ async reserveHostedCharge(serializedCredential, intent) {
50
+ if (!this.hostingCleanupFence) return;
51
+ const credential = Credential.deserialize(serializedCredential);
52
+ if (credential.challenge.intent !== "charge" || credential.challenge.method !== "tempo") {
53
+ throw new Error("Hosted Tempo charge credential is not a Tempo charge");
54
+ }
55
+ const challengeId = credential.challenge.id;
56
+ const replayKey = replayKeyFor(credential);
57
+ const artifactHash = createHash("sha256").update(serializedCredential).digest("hex");
58
+ await this.hostingCleanupFence.withNewIntent({
59
+ ...intent,
60
+ challengeId,
61
+ paymentArtifactHash: artifactHash
62
+ }, async (tx) => {
63
+ if (intent.paymentSeq !== void 0 && intent.jobId) {
64
+ const original = await tx.query(
65
+ `SELECT data->'content'->'tempo_credential' AS credential FROM job_messages
66
+ WHERE job_id=$1 AND seq=$2 AND status='pending-verification'`,
67
+ [intent.jobId, intent.paymentSeq]
68
+ );
69
+ const recorded = original.rows[0]?.credential;
70
+ if (!recorded || canonicalCredential(recorded) !== canonicalCredential(credential)) {
71
+ throw new Error("Hosted Tempo charge differs from the original inbound payment");
72
+ }
73
+ }
74
+ const prior = await tx.query(
75
+ `SELECT admission_id,job_id,payment_seq,replay_key,payment_artifact_hash FROM sdk_hosted_tempo_charge_intents
76
+ WHERE challenge_id=$1 FOR UPDATE`,
77
+ [challengeId]
78
+ );
79
+ if (prior.rows[0]) {
80
+ const row = prior.rows[0];
81
+ if (row.admission_id !== (intent.admissionId ?? null) || row.job_id !== (intent.jobId ?? null) || row.payment_seq !== (intent.paymentSeq ?? null) || row.replay_key !== replayKey || row.payment_artifact_hash !== artifactHash) {
82
+ throw new Error("Hosted Tempo charge identity conflicts with the original payment");
83
+ }
84
+ return;
85
+ }
86
+ await tx.query(
87
+ `INSERT INTO sdk_hosted_tempo_charge_intents
88
+ (challenge_id,admission_id,job_id,payment_seq,replay_key,payment_artifact_hash,status)
89
+ VALUES ($1,$2,$3,$4,$5,$6,'pending')`,
90
+ [
91
+ challengeId,
92
+ intent.admissionId ?? null,
93
+ intent.jobId ?? null,
94
+ intent.paymentSeq ?? null,
95
+ replayKey,
96
+ artifactHash
97
+ ]
98
+ );
99
+ });
100
+ }
101
+ async completeHostedCharge(challengeId) {
102
+ if (!this.hostingCleanupFence) return;
103
+ await this.pool.query(
104
+ `UPDATE sdk_hosted_tempo_charge_intents SET status='complete'
105
+ WHERE challenge_id=$1 AND status='pending' AND (
106
+ EXISTS (SELECT 1 FROM processed_payments p
107
+ WHERE p.payment_id=$1 AND p.rail='tempo' AND p.dvm_id=$2
108
+ AND p.job_id IS NOT DISTINCT FROM sdk_hosted_tempo_charge_intents.job_id)
109
+ )`,
110
+ [challengeId, this.hostingCleanupFence.dvmId]
111
+ );
112
+ }
113
+ pool;
114
+ tableName;
115
+ retentionMs;
116
+ gcSampleRate;
117
+ now;
118
+ constructor(pool, opts) {
119
+ const tableName = opts?.tableName ?? DEFAULT_TABLE_NAME;
120
+ if (!VALID_IDENTIFIER.test(tableName)) {
121
+ throw new Error(
122
+ `PostgresTempoChargeStore: tableName must match ${VALID_IDENTIFIER.source}, got "${tableName}"`
123
+ );
124
+ }
125
+ this.pool = pool;
126
+ this.tableName = tableName;
127
+ this.retentionMs = opts?.retentionMs ?? DEFAULT_RETENTION_MS;
128
+ this.gcSampleRate = opts?.gcSampleRate ?? DEFAULT_GC_SAMPLE_RATE;
129
+ this.now = opts?.now ?? (() => Date.now());
130
+ this.get = this.get.bind(this);
131
+ this.put = this.put.bind(this);
132
+ this.delete = this.delete.bind(this);
133
+ this.update = this.update.bind(this);
134
+ }
135
+ /** Run the CREATE TABLE migration. Idempotent — safe to call repeatedly. */
136
+ async init() {
137
+ await withSdkInitLock(this.pool, () => this.createTables());
138
+ }
139
+ /** Read one stored value, or `null` when absent — mppx's miss sentinel. */
140
+ async get(key) {
141
+ const { rows } = await this.pool.query(
142
+ `SELECT value_json FROM ${this.tableName} WHERE store_key = $1`,
143
+ [key]
144
+ );
145
+ return rows[0] ? parseStoreValue(rows[0].value_json) : null;
146
+ }
147
+ async put(key, value) {
148
+ await this.update(key, () => ({ op: "set", value, result: void 0 }));
149
+ }
150
+ /** Delete one stored value — mppx's compensating release on a failed broadcast. */
151
+ async delete(key) {
152
+ await this.update(key, () => ({ op: "delete", result: void 0 }));
153
+ }
154
+ /**
155
+ * Advisory locking covers the absent-row first write before the row lock.
156
+ * Both competing presentations of one credential and the shared sponsor
157
+ * budget need this serialization. The transaction contains no chain call.
158
+ */
159
+ async update(key, fn) {
160
+ const client = await this.pool.connect();
161
+ try {
162
+ await client.query("BEGIN");
163
+ await client.query(`SELECT pg_advisory_xact_lock(hashtextextended($1, 2036))`, [key]);
164
+ const { rows } = await client.query(
165
+ `SELECT value_json FROM ${this.tableName} WHERE store_key = $1 FOR UPDATE`,
166
+ [key]
167
+ );
168
+ const current = rows[0] ? parseStoreValue(rows[0].value_json) : null;
169
+ const change = fn(current);
170
+ const now = this.now();
171
+ if (change.op === "set") {
172
+ if (this.hostingCleanupFence) {
173
+ const pending = await client.query(
174
+ `SELECT EXISTS (
175
+ SELECT 1 FROM sdk_hosted_tempo_charge_intents i
176
+ LEFT JOIN sdk_hosting_admissions a ON a.id=i.admission_id
177
+ LEFT JOIN jobs j ON j.id=i.job_id
178
+ WHERE i.status='pending' AND i.replay_key=$3 AND
179
+ ((a.dvm_id=$1 AND a.scope=$2) OR j.dvm_id=$1)
180
+ ) AS reserved`,
181
+ [this.hostingCleanupFence.dvmId, this.hostingCleanupFence.scope, key]
182
+ );
183
+ if (!pending.rows[0]?.reserved) {
184
+ await this.hostingCleanupFence.assertNewIntent(client);
185
+ }
186
+ }
187
+ await client.query(
188
+ `INSERT INTO ${this.tableName} (store_key, value_json, expires_at)
189
+ VALUES ($1, $2, $3)
190
+ ON CONFLICT (store_key) DO UPDATE
191
+ SET value_json = EXCLUDED.value_json, expires_at = EXCLUDED.expires_at`,
192
+ [
193
+ key,
194
+ stringifyStoreValue(change.value, "Tempo charge store value"),
195
+ now + this.retentionMs
196
+ ]
197
+ );
198
+ } else if (change.op === "delete") {
199
+ await client.query(`DELETE FROM ${this.tableName} WHERE store_key = $1`, [key]);
200
+ }
201
+ await client.query("COMMIT");
202
+ if (change.op !== "noop") this.maybeSweep(now);
203
+ return change.result;
204
+ } catch (error) {
205
+ await client.query("ROLLBACK").catch(() => void 0);
206
+ throw error;
207
+ } finally {
208
+ client.release();
209
+ }
210
+ }
211
+ async createTables() {
212
+ await this.pool.query(`
213
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
214
+ store_key TEXT PRIMARY KEY,
215
+ value_json TEXT NOT NULL,
216
+ expires_at BIGINT NOT NULL
217
+ );
218
+ CREATE INDEX IF NOT EXISTS ${this.tableName}_expires_at_idx
219
+ ON ${this.tableName} (expires_at);
220
+ CREATE TABLE IF NOT EXISTS sdk_hosted_tempo_charge_intents (
221
+ challenge_id TEXT PRIMARY KEY,
222
+ admission_id TEXT,
223
+ job_id TEXT,
224
+ payment_seq INTEGER,
225
+ replay_key TEXT,
226
+ payment_artifact_hash TEXT,
227
+ status TEXT NOT NULL CHECK (status IN ('pending','complete')),
228
+ created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
229
+ );
230
+ ALTER TABLE sdk_hosted_tempo_charge_intents
231
+ ADD COLUMN IF NOT EXISTS replay_key TEXT,
232
+ ADD COLUMN IF NOT EXISTS payment_artifact_hash TEXT;
233
+ `);
234
+ }
235
+ maybeSweep(now) {
236
+ if (this.gcSampleRate <= 0 || Math.random() >= this.gcSampleRate) return;
237
+ this.pool.query(`DELETE FROM ${this.tableName} WHERE expires_at < $1`, [now]).catch((err) => {
238
+ diagnosticConsole.error("PostgresTempoChargeStore GC sweep failed:", err);
239
+ });
240
+ }
241
+ };
242
+
243
+ export {
244
+ PostgresTempoChargeStore
245
+ };
@@ -0,0 +1,53 @@
1
+ // src/lib/errors.ts
2
+ var DvmError = class extends Error {
3
+ constructor(code, message, hint, data, display, proseSources) {
4
+ super(message);
5
+ this.code = code;
6
+ this.hint = hint;
7
+ this.data = data;
8
+ this.display = display;
9
+ this.proseSources = proseSources;
10
+ this.name = "DvmError";
11
+ }
12
+ code;
13
+ hint;
14
+ data;
15
+ display;
16
+ proseSources;
17
+ };
18
+ function providerProseSources(...sources) {
19
+ const provenance = {};
20
+ for (const source of sources) {
21
+ if (!source) continue;
22
+ for (const field of ["message", "hint", "display"]) {
23
+ if (typeof source[field] === "string") provenance[field] = "provider";
24
+ }
25
+ }
26
+ return Object.keys(provenance).length > 0 ? provenance : void 0;
27
+ }
28
+ function isAbortError(err) {
29
+ return err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
30
+ }
31
+ function transportErrorCode(err) {
32
+ let current = err;
33
+ const seen = /* @__PURE__ */ new Set();
34
+ for (let depth = 0; depth < 5 && isErrorLike(current) && !seen.has(current); depth++) {
35
+ seen.add(current);
36
+ const code = current.code;
37
+ if (typeof code === "string" && /^(?:E[A-Z0-9_]{1,63}|UND_ERR_[A-Z0-9_]{1,63})$/.test(code)) {
38
+ return code;
39
+ }
40
+ current = current.cause;
41
+ }
42
+ return void 0;
43
+ }
44
+ function isErrorLike(value) {
45
+ return (typeof value === "object" || typeof value === "function") && value !== null;
46
+ }
47
+
48
+ export {
49
+ DvmError,
50
+ providerProseSources,
51
+ isAbortError,
52
+ transportErrorCode
53
+ };
@@ -3,9 +3,11 @@ import {
3
3
  CONFIG_BACKUP_FILE,
4
4
  CONFIG_FILE,
5
5
  CONFIG_PINNED_FILE,
6
- DvmError,
7
6
  ensureConfigDir
8
- } from "./chunk-MKI6OVW4.js";
7
+ } from "./chunk-CZULM42S.js";
8
+ import {
9
+ DvmError
10
+ } from "./chunk-LIRAHP7B.js";
9
11
 
10
12
  // src/lib/evm-private-key.ts
11
13
  import { secp256k1 } from "@noble/curves/secp256k1.js";
@@ -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";
@@ -2,31 +2,33 @@ import {
2
2
  connectedTempoAccount,
3
3
  tempoBalanceCheckHint,
4
4
  tempoTokenReference
5
- } from "./chunk-YYQG4R7T.js";
5
+ } from "./chunk-74PUEZJ4.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-2VGWYG4P.js";
13
13
  import {
14
14
  costAnchor,
15
15
  formatUsd,
16
16
  formatUsdcMicrounits
17
- } from "./chunk-A4CM2BD3.js";
17
+ } from "./chunk-3MHE725Z.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";
@@ -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
+ };
@@ -2,16 +2,16 @@ import {
2
2
  createX402FacilitatorAuthHeaders,
3
3
  settleWithFacilitator,
4
4
  verifyWithFacilitator
5
- } from "./chunk-BQ2NMWKE.js";
5
+ } from "./chunk-PLG4XER6.js";
6
6
  import {
7
7
  buildPaymentRequirements,
8
8
  decodePayment,
9
9
  encodeSettleResponseHeader,
10
10
  exactEvmAuthorization
11
- } from "./chunk-Z4BNLUZF.js";
11
+ } from "./chunk-IWQHUSMO.js";
12
12
  import {
13
13
  usdcToMsats
14
- } from "./chunk-A4CM2BD3.js";
14
+ } from "./chunk-3MHE725Z.js";
15
15
  import {
16
16
  callerLoggers
17
17
  } from "./chunk-66HGCPBU.js";