@dvmkit/sdk 0.5.0-rc.1 → 0.7.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 (54) hide show
  1. package/README.md +100 -0
  2. package/dist/{chunk-JH3SX3LY.js → chunk-6XF3RVVX.js} +1 -1
  3. package/dist/chunk-AFV77S4C.js +258 -0
  4. package/dist/{chunk-RLTSCKRI.js → chunk-BPYMEMQX.js} +50 -3
  5. package/dist/chunk-BQJIFRAZ.js +36 -0
  6. package/dist/{chunk-IWQHUSMO.js → chunk-CXCQLOOK.js} +143 -2
  7. package/dist/{chunk-PB4FYZET.js → chunk-EQLA4FFY.js} +54 -4
  8. package/dist/{chunk-LE74UEV7.js → chunk-EZXAJ6EI.js} +64 -53
  9. package/dist/{chunk-FI4CST2D.js → chunk-FWGWLPXF.js} +4 -6
  10. package/dist/{chunk-EYTHFFFU.js → chunk-GY66THYI.js} +179 -56
  11. package/dist/{chunk-TZ2WKCDD.js → chunk-HCLVYZUZ.js} +2 -2
  12. package/dist/{chunk-O5AT6S5T.js → chunk-IV6Q7NRW.js} +25 -195
  13. package/dist/{chunk-UOCZXO2Z.js → chunk-JEVLNAO3.js} +10 -5
  14. package/dist/{chunk-TUA7PYH7.js → chunk-KMED3H3A.js} +134 -130
  15. package/dist/{chunk-Z4KLECAC.js → chunk-N5RZ5HVB.js} +1 -1
  16. package/dist/{chunk-PAIM66YA.js → chunk-NFMOPYBA.js} +47 -37
  17. package/dist/{chunk-B2IROPQF.js → chunk-O2OTZ5GP.js} +40 -15
  18. package/dist/{chunk-FYRR2UEZ.js → chunk-OML32F6I.js} +1 -1
  19. package/dist/{chunk-6DOGTAHA.js → chunk-QR2EF2NI.js} +9 -43
  20. package/dist/{chunk-7ZUKL4IV.js → chunk-RA2WIX7G.js} +5 -5
  21. package/dist/{chunk-A4YFWZ7R.js → chunk-RCM3P2HE.js} +1 -1
  22. package/dist/{chunk-PLG4XER6.js → chunk-S3OY533W.js} +1 -1
  23. package/dist/{chunk-GFY5FRX3.js → chunk-SZVXOQ75.js} +5 -12
  24. package/dist/{chunk-MZRJR2CW.js → chunk-WBOKPC37.js} +22 -3
  25. package/dist/content-retention-DHyLdA6b.d.ts +78 -0
  26. package/dist/{credit-ledger-MI2H7NEC.js → credit-ledger-MCUXYKSJ.js} +2 -3
  27. package/dist/{credit-menu-CAfqe8IY.d.ts → credit-menu-DjnkeEUU.d.ts} +98 -5
  28. package/dist/{fx-D9nsN3n4.d.ts → fx-zMWFzfrU.d.ts} +1 -1
  29. package/dist/index.d.ts +5 -5
  30. package/dist/index.js +3 -3
  31. package/dist/internal/caller.d.ts +1369 -860
  32. package/dist/internal/caller.js +1079 -991
  33. package/dist/internal/index.js +1 -1
  34. package/dist/internal/server.d.ts +8 -99
  35. package/dist/internal/server.js +19 -18
  36. package/dist/{job-store-Bx-VFRjg.d.ts → job-store-DdLXBpol.d.ts} +1 -1
  37. package/dist/{lightning-backend-BAx2Y8pP.d.ts → lightning-backend-Bw-qtAbt.d.ts} +4 -2
  38. package/dist/{memory-credit-ledger-W2BAUYJP.js → memory-credit-ledger-SBSMGMDX.js} +3 -4
  39. package/dist/{postgres-consumed-credential-store-CUQJKRSL.js → postgres-consumed-credential-store-BUVCAPLS.js} +13 -0
  40. package/dist/{postgres-job-store-VLXLWXUQ.js → postgres-job-store-VHLQBJZC.js} +3 -1
  41. package/dist/server/index.d.ts +28 -10
  42. package/dist/server/index.js +34 -24
  43. package/dist/{step-cache-QG_h5-3G.d.ts → step-cache-2MkvenJn.d.ts} +3 -3
  44. package/dist/tempo-lifecycle-GJ77YKVY.js +15 -0
  45. package/dist/{tempo-wallet-B5XXV3YH.js → tempo-wallet-X2GGAZ6X.js} +4 -4
  46. package/dist/testing/index.d.ts +9 -2
  47. package/dist/testing/index.js +6 -3
  48. package/dist/{usd-EwRqCPF-.d.ts → usd-DI3d3gTK.d.ts} +1 -1
  49. package/dist/{x402-R6HPBY5D.js → x402-FT4S4JS4.js} +8 -10
  50. package/package.json +5 -3
  51. package/recipes/postgres/financial-export-v1.sql +137 -0
  52. package/dist/chunk-CZULM42S.js +0 -143
  53. package/dist/chunk-LIRAHP7B.js +0 -53
  54. package/dist/tempo-lifecycle-ZBBLFBMH.js +0 -15
@@ -1,3 +1,7 @@
1
+ import {
2
+ ContentHoldError,
3
+ applyContentHoldMutation
4
+ } from "./chunk-AFV77S4C.js";
1
5
  import {
2
6
  withSdkInitLock
3
7
  } from "./chunk-COYKJRMM.js";
@@ -228,6 +232,18 @@ var PostgresJobStore = class {
228
232
  ALTER TABLE jobs ADD COLUMN IF NOT EXISTS receipt JSONB;
229
233
  ALTER TABLE jobs ADD COLUMN IF NOT EXISTS receipt_seq BIGINT;
230
234
  ALTER TABLE jobs ADD COLUMN IF NOT EXISTS redacted_at BIGINT;
235
+ CREATE TABLE IF NOT EXISTS job_content_holds (
236
+ id TEXT PRIMARY KEY, job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
237
+ data JSONB NOT NULL
238
+ );
239
+ CREATE INDEX IF NOT EXISTS job_content_holds_job_idx ON job_content_holds(job_id);
240
+ CREATE TABLE IF NOT EXISTS job_content_hold_events (
241
+ event_position BIGSERIAL UNIQUE,
242
+ id TEXT PRIMARY KEY, job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
243
+ data JSONB NOT NULL
244
+ );
245
+ CREATE INDEX IF NOT EXISTS job_content_hold_events_job_idx ON job_content_hold_events(job_id);
246
+
231
247
  -- credit-ledger linkage. Written at submission alongside the
232
248
  -- payment fields; the terminal funnel settles/releases the draw and the
233
249
  -- receipt countersigns the ReceiptCredit block from it.
@@ -536,7 +552,7 @@ var PostgresJobStore = class {
536
552
  required_msats = EXCLUDED.required_msats,
537
553
  step_cache = EXCLUDED.step_cache,
538
554
  state = EXCLUDED.state,
539
- last_activity_at = EXCLUDED.last_activity_at
555
+ last_activity_at = CASE WHEN jobs.status IN ('completed', 'failed', 'cancelled') THEN jobs.last_activity_at ELSE EXCLUDED.last_activity_at END
540
556
  -- terminal is terminal. On a multi-machine DVM a DELETE can
541
557
  -- land on a machine that isn't running the handler, which cancels the
542
558
  -- job in the store only; the handler's machine never learns of it and
@@ -737,6 +753,7 @@ var PostgresJobStore = class {
737
753
  const client = await this.pool.connect();
738
754
  try {
739
755
  await client.query("BEGIN");
756
+ await client.query("SELECT id FROM jobs WHERE id = $1 FOR UPDATE", [args.jobId]);
740
757
  const { rowCount } = await client.query(
741
758
  `UPDATE jobs SET
742
759
  input = '',
@@ -760,6 +777,7 @@ var PostgresJobStore = class {
760
777
  AND last_activity_at = $2
761
778
  AND redacted_at IS NULL
762
779
  AND status IN ('completed', 'failed', 'cancelled')
780
+ AND NOT EXISTS (SELECT 1 FROM job_content_holds h WHERE h.job_id=jobs.id AND h.data->'cleared_at' = 'null'::jsonb)
763
781
  AND ($4::boolean = false OR NOT EXISTS (
764
782
  SELECT 1 FROM job_messages m WHERE m.job_id=jobs.id
765
783
  AND m.data->>'type'='payment'
@@ -781,6 +799,38 @@ var PostgresJobStore = class {
781
799
  client.release();
782
800
  }
783
801
  }
802
+ async getContentSnapshot(jobId, dvmId) {
803
+ const { rows } = await this.pool.query(
804
+ `SELECT j.*, COALESCE((SELECT jsonb_agg(h.data ORDER BY h.data->>'created_at', h.id) FROM job_content_holds h WHERE h.job_id=j.id), '[]'::jsonb) AS content_holds,
805
+ COALESCE((SELECT jsonb_agg(e.data ORDER BY e.event_position) FROM job_content_hold_events e WHERE e.job_id=j.id), '[]'::jsonb) AS content_hold_events
806
+ FROM jobs j WHERE j.id=$1 AND ($2::text IS NULL OR j.dvm_id IS NULL OR j.dvm_id=$2)`,
807
+ [jobId, dvmId ?? null]
808
+ );
809
+ if (!rows[0]) return void 0;
810
+ return { job: rowToRecord(rows[0]), history: { holds: rows[0].content_holds, events: rows[0].content_hold_events } };
811
+ }
812
+ async mutateContentHold(jobId, mutation, dvmId) {
813
+ const client = await this.pool.connect();
814
+ try {
815
+ await client.query("BEGIN");
816
+ const { rows } = await client.query("SELECT * FROM jobs WHERE id=$1 AND ($2::text IS NULL OR dvm_id IS NULL OR dvm_id=$2) FOR UPDATE", [jobId, dvmId ?? null]);
817
+ if (!rows[0]) throw new ContentHoldError("job_not_found");
818
+ const holds = await client.query("SELECT data FROM job_content_holds WHERE job_id=$1 ORDER BY data->>'created_at', id", [jobId]);
819
+ const events = await client.query("SELECT data FROM job_content_hold_events WHERE job_id=$1 ORDER BY event_position", [jobId]);
820
+ const history = applyContentHoldMutation(rowToRecord(rows[0]), { holds: holds.rows.map((row) => row.data), events: events.rows.map((row) => row.data) }, mutation);
821
+ const event = history.events.at(-1);
822
+ const hold = history.holds.find((item) => item.id === event.hold_id);
823
+ await client.query(`INSERT INTO job_content_holds(id,job_id,data) VALUES($1,$2,$3) ON CONFLICT(id) DO UPDATE SET data=EXCLUDED.data`, [hold.id, jobId, JSON.stringify(hold)]);
824
+ await client.query("INSERT INTO job_content_hold_events(id,job_id,data) VALUES($1,$2,$3)", [event.id, jobId, JSON.stringify(event)]);
825
+ await client.query("COMMIT");
826
+ return history;
827
+ } catch (error) {
828
+ await client.query("ROLLBACK");
829
+ throw error;
830
+ } finally {
831
+ client.release();
832
+ }
833
+ }
784
834
  async claimReceiptSeq(jobId) {
785
835
  const client = await this.pool.connect();
786
836
  try {
@@ -828,7 +878,7 @@ var PostgresJobStore = class {
828
878
  try {
829
879
  await client.query("BEGIN");
830
880
  const { rows } = await client.query(
831
- `UPDATE jobs SET next_seq = next_seq + 1, last_activity_at = $2,
881
+ `UPDATE jobs SET next_seq = next_seq + 1, last_activity_at = CASE WHEN status IN ('completed','failed','cancelled') THEN last_activity_at ELSE $2 END,
832
882
  awaiting_input_at_ms = CASE WHEN $3::boolean AND status NOT IN ('completed', 'failed', 'cancelled')
833
883
  THEN COALESCE(awaiting_input_at_ms, $2) ELSE awaiting_input_at_ms END,
834
884
  status = CASE WHEN $3::boolean AND status NOT IN ('completed', 'failed', 'cancelled')
@@ -925,7 +975,7 @@ var PostgresJobStore = class {
925
975
  await client.query("BEGIN");
926
976
  if (message.type === "payment") await this.hostingCleanupFence?.assertNewIntent(client);
927
977
  const { rows } = await client.query(
928
- `UPDATE jobs SET next_seq = next_seq + 1, last_activity_at = $2
978
+ `UPDATE jobs SET next_seq = next_seq + 1, last_activity_at = CASE WHEN status IN ('completed','failed','cancelled') THEN last_activity_at ELSE $2 END
929
979
  WHERE id = $1 AND redacted_at IS NULL
930
980
  RETURNING (next_seq - 1) AS seq`,
931
981
  [jobId, Date.now()]
@@ -1024,7 +1074,7 @@ var PostgresJobStore = class {
1024
1074
  cashu_flow = COALESCE($9, cashu_flow),
1025
1075
  pending_x402_nonce = CASE WHEN $11::boolean THEN NULL ELSE pending_x402_nonce END,
1026
1076
  pending_x402_amount_usdc_micro = CASE WHEN $11::boolean THEN NULL ELSE pending_x402_amount_usdc_micro END,
1027
- last_activity_at = $12
1077
+ last_activity_at = CASE WHEN status IN ('completed','failed','cancelled') THEN last_activity_at ELSE $12 END
1028
1078
  WHERE id = $1
1029
1079
  RETURNING status, paid_msats, pending_payment_msats, credit_id, draw_id`,
1030
1080
  params
@@ -3,25 +3,14 @@ import {
3
3
  listCredits,
4
4
  maxAdvertisedMicro,
5
5
  withinAdvertisedTolerance
6
- } from "./chunk-B2IROPQF.js";
6
+ } from "./chunk-O2OTZ5GP.js";
7
7
  import {
8
8
  authorizeProtectedRemoval,
9
9
  isValidEvmPrivateKey,
10
10
  loadConfig,
11
- updateConfig
12
- } from "./chunk-MZRJR2CW.js";
13
- import {
14
- X402_DEFAULT_NETWORK,
15
- X402_V1_VERSION,
16
- X402_VERSION,
17
- buildPaymentRequirements,
18
- chainIdFromCaip2,
19
- encodePayment,
20
- usdcContractFor,
21
- usdcDomainNameFor,
22
- usdcDomainVersionFor,
23
- x402NetworkToCaip2
24
- } from "./chunk-IWQHUSMO.js";
11
+ updateConfig,
12
+ x402WalletFromConfig
13
+ } from "./chunk-WBOKPC37.js";
25
14
  import {
26
15
  costAnchor,
27
16
  fetchBtcUsdRate,
@@ -30,25 +19,33 @@ import {
30
19
  getLastKnownBtcRate,
31
20
  msatsToUsdc,
32
21
  usdcToMsats
33
- } from "./chunk-PAIM66YA.js";
22
+ } from "./chunk-NFMOPYBA.js";
34
23
  import {
35
24
  X402_CHANNELS_FILE,
25
+ X402_DEFAULT_NETWORK,
26
+ X402_V1_VERSION,
27
+ X402_VERSION,
28
+ buildPaymentRequirements,
29
+ chainIdFromCaip2,
30
+ encodePayment,
36
31
  ensureConfigDir,
37
32
  resolveRpcHttpTransportOptions,
38
- resolveRpcOverride
39
- } from "./chunk-CZULM42S.js";
33
+ resolveRpcOverride,
34
+ usdcContractFor,
35
+ usdcDomainNameFor,
36
+ usdcDomainVersionFor,
37
+ x402NetworkToCaip2
38
+ } from "./chunk-CXCQLOOK.js";
40
39
  import {
41
40
  redactUrl,
42
41
  redactUrlsInText
43
42
  } from "./chunk-FUJ36YDV.js";
44
43
  import {
44
+ DvmError,
45
45
  assertSupportedX402Network,
46
46
  x402NetworkByCaip2,
47
47
  x402SupportedNetworksHint
48
- } from "./chunk-RLTSCKRI.js";
49
- import {
50
- DvmError
51
- } from "./chunk-LIRAHP7B.js";
48
+ } from "./chunk-BPYMEMQX.js";
52
49
 
53
50
  // src/lib/x402/asset.ts
54
51
  function resolveX402Asset(network = X402_DEFAULT_NETWORK) {
@@ -166,6 +163,43 @@ function rateUnavailable(purpose) {
166
163
  var X402_EXACT_AUTHORIZATION_TTL_SECONDS = 600;
167
164
  var X402_REFUSAL_TRANSACTION_DISCOVERY_WINDOW_MS = 15 * 6e4;
168
165
 
166
+ // src/lib/x402/voucher-pending.ts
167
+ var X402_PENDING_STALE_MS = 24 * 60 * 60 * 1e3;
168
+ function x402PendingStale(pending, now = Date.now()) {
169
+ return now - pending.requestedAt >= X402_PENDING_STALE_MS;
170
+ }
171
+ function x402PendingRetired(pending) {
172
+ return pending.refusedAt !== void 0 || pending.supersededAt !== void 0;
173
+ }
174
+ function x402PendingBlockExpiresAt(pending) {
175
+ return x402PendingDepositExpiresAt(pending) ?? pending.requestedAt + X402_PENDING_STALE_MS;
176
+ }
177
+ function x402PendingDepositExpiresAt(pending) {
178
+ const seconds = pending.authorizationValidBefore ? Number(pending.authorizationValidBefore) : NaN;
179
+ return Number.isFinite(seconds) ? seconds * 1e3 : void 0;
180
+ }
181
+ function x402PendingUnsettled(pending, now = Date.now()) {
182
+ return !x402PendingRetired(pending) || now <= x402PendingBlockExpiresAt(pending);
183
+ }
184
+ function x402VoucherPendingState(blocking) {
185
+ const depositExpiresAt = x402PendingDepositExpiresAt(blocking);
186
+ return {
187
+ code: "x402_voucher_pending",
188
+ creditId: blocking.creditId,
189
+ fundId: blocking.fundId,
190
+ channelId: blocking.channelId,
191
+ endpoint: blocking.endpoint,
192
+ retired: x402PendingRetired(blocking),
193
+ blockedUntil: x402PendingBlockExpiresAt(blocking),
194
+ ...depositExpiresAt === void 0 ? {} : { depositExpiresAt }
195
+ };
196
+ }
197
+ function assertNoRivalX402Voucher(channelId, creditId, pending, now) {
198
+ const blocking = pending.filter((entry) => x402PendingUnsettled(entry, now));
199
+ const rival = blocking.find((entry) => entry.creditId !== creditId && !x402PendingRetired(entry)) ?? blocking.find((entry) => entry.creditId !== creditId || x402PendingRetired(entry));
200
+ if (rival) throw { ...x402VoucherPendingState(rival), channelId };
201
+ }
202
+
169
203
  // src/lib/x402/channel-store.ts
170
204
  import { AsyncLocalStorage } from "async_hooks";
171
205
  import { existsSync, readFileSync, renameSync, writeFileSync } from "fs";
@@ -203,23 +237,6 @@ function clearPendingX402ExactFunding(pending) {
203
237
  );
204
238
  saveFile(file);
205
239
  }
206
- var X402_PENDING_STALE_MS = 24 * 60 * 60 * 1e3;
207
- function x402PendingStale(pending, now = Date.now()) {
208
- return now - pending.requestedAt >= X402_PENDING_STALE_MS;
209
- }
210
- function x402PendingRetired(pending) {
211
- return pending.refusedAt !== void 0 || pending.supersededAt !== void 0;
212
- }
213
- function x402PendingBlockExpiresAt(pending) {
214
- return x402PendingDepositExpiresAt(pending) ?? pending.requestedAt + X402_PENDING_STALE_MS;
215
- }
216
- function x402PendingDepositExpiresAt(pending) {
217
- const seconds = pending.authorizationValidBefore ? Number(pending.authorizationValidBefore) : NaN;
218
- return Number.isFinite(seconds) ? seconds * 1e3 : void 0;
219
- }
220
- function x402PendingUnsettled(pending, now = Date.now()) {
221
- return !x402PendingRetired(pending) || now <= x402PendingBlockExpiresAt(pending);
222
- }
223
240
  function x402PendingFundingJson(entry) {
224
241
  return {
225
242
  credit_id: entry.creditId,
@@ -667,15 +684,7 @@ function saveFile(file) {
667
684
 
668
685
  // src/lib/x402/wallet.ts
669
686
  function loadX402Wallet() {
670
- const config = loadConfig();
671
- const x402 = config?.x402;
672
- const privateKey = x402?.privateKey;
673
- if (!privateKey || !isValidEvmPrivateKey(privateKey)) return null;
674
- return {
675
- privateKey,
676
- address: "",
677
- network: x402.network ?? X402_DEFAULT_NETWORK
678
- };
687
+ return x402WalletFromConfig(loadConfig());
679
688
  }
680
689
  async function ensureAddress(wallet) {
681
690
  if (!isValidEvmPrivateKey(wallet.privateKey)) throw invalidX402PrivateKey();
@@ -1032,17 +1041,19 @@ export {
1032
1041
  staleRateNotice,
1033
1042
  X402_EXACT_AUTHORIZATION_TTL_SECONDS,
1034
1043
  X402_REFUSAL_TRANSACTION_DISCOVERY_WINDOW_MS,
1035
- x402ExactFundingDead,
1036
- recordPendingX402ExactFunding,
1037
- loadResumableX402ExactFunding,
1038
- listResumableX402ExactFundings,
1039
- clearPendingX402ExactFunding,
1040
1044
  X402_PENDING_STALE_MS,
1041
1045
  x402PendingStale,
1042
1046
  x402PendingRetired,
1043
1047
  x402PendingBlockExpiresAt,
1044
1048
  x402PendingDepositExpiresAt,
1045
1049
  x402PendingUnsettled,
1050
+ x402VoucherPendingState,
1051
+ assertNoRivalX402Voucher,
1052
+ x402ExactFundingDead,
1053
+ recordPendingX402ExactFunding,
1054
+ loadResumableX402ExactFunding,
1055
+ listResumableX402ExactFundings,
1056
+ clearPendingX402ExactFunding,
1046
1057
  x402PendingFundingJson,
1047
1058
  withX402ChannelLock,
1048
1059
  DvmX402ChannelStorage,
@@ -388,12 +388,6 @@ var HttpTransport = class {
388
388
  }
389
389
  };
390
390
 
391
- // src/lib/nwc.ts
392
- import { decrypt, encrypt } from "nostr-tools/nip04";
393
- import { SimplePool, useWebSocketImplementation } from "nostr-tools/pool";
394
- import { finalizeEvent, getPublicKey } from "nostr-tools/pure";
395
- import WebSocket from "ws";
396
-
397
391
  // src/lib/hash.ts
398
392
  import { createHash } from "crypto";
399
393
  function sha256hex(input) {
@@ -401,6 +395,10 @@ function sha256hex(input) {
401
395
  }
402
396
 
403
397
  // src/lib/nwc.ts
398
+ import { decrypt, encrypt } from "nostr-tools/nip04";
399
+ import { SimplePool, useWebSocketImplementation } from "nostr-tools/pool";
400
+ import { finalizeEvent, getPublicKey } from "nostr-tools/pure";
401
+ import WebSocket from "ws";
404
402
  var webSocketImplementationConfigured = false;
405
403
  function ensureWebSocketImplementation() {
406
404
  if (webSocketImplementationConfigured) return;