@dvmkit/sdk 0.1.2-rc.7 → 0.1.4-rc.7
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.
- package/README.md +121 -8
- package/dist/chunk-BIFLRKMO.js +87 -0
- package/dist/chunk-BQ2NMWKE.js +160 -0
- package/dist/{chunk-LDTWX7JW.js → chunk-BTZY7VPH.js} +13 -1
- package/dist/{chunk-FJDCFHW5.js → chunk-C6JHBLMW.js} +3 -81
- package/dist/{chunk-EXHBXA4U.js → chunk-DBCLBYHP.js} +13 -1
- package/dist/chunk-E4EVGPDX.js +391 -0
- package/dist/chunk-EDDYHZ6W.js +1010 -0
- package/dist/{chunk-2ABMGUDS.js → chunk-EVBK675R.js} +88 -10
- package/dist/{chunk-P4RUVDU7.js → chunk-H2MEFVH6.js} +12 -141
- package/dist/chunk-KXZUCCEY.js +142 -0
- package/dist/{chunk-LWUR4CGG.js → chunk-MLRCSJYX.js} +11 -3
- package/dist/{chunk-N4VTG3KH.js → chunk-QK3VJNCK.js} +930 -3011
- package/dist/chunk-RW5LP57K.js +44 -0
- package/dist/{chunk-6JZIX5WW.js → chunk-SSSZUVWM.js} +178 -8
- package/dist/{chunk-TKA6ZP4M.js → chunk-U6M3ATSG.js} +56 -426
- package/dist/chunk-VRQDX5P4.js +1742 -0
- package/dist/{chunk-JGGI65I3.js → chunk-Z4BNLUZF.js} +1 -150
- package/dist/{credit-ledger-ED6JXKVD.js → credit-ledger-2DFQHNLB.js} +2 -2
- package/dist/{credit-menu-BM4qCD5U.d.ts → credit-menu-C1ezIFlJ.d.ts} +1699 -1941
- package/dist/{fx-C-liI3oY.d.ts → fx-C6dl2LVI.d.ts} +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.js +8 -4
- package/dist/internal/caller.d.ts +10441 -0
- package/dist/internal/caller.js +10078 -0
- package/dist/internal/index.d.ts +2 -10816
- package/dist/internal/index.js +2 -10130
- package/dist/internal/server.d.ts +404 -0
- package/dist/internal/server.js +202 -0
- package/dist/job-store-DHnW4Cg_.d.ts +591 -0
- package/dist/lightning-backend-Ci1nogk_.d.ts +367 -0
- package/dist/{memory-credit-ledger-XJ5VQEVP.js → memory-credit-ledger-OP24Z2KO.js} +3 -3
- package/dist/{postgres-job-store-J5F4GUWU.js → postgres-job-store-3RAXMNSY.js} +1 -1
- package/dist/{revenue-reporter-JIKUPXOK.js → revenue-reporter-ASZ7SHHH.js} +1 -1
- package/dist/server/index.d.ts +41 -11
- package/dist/server/index.js +93 -69
- package/dist/{job-store-C53VQ5uu.d.ts → step-cache-BLPZNizw.d.ts} +176 -585
- package/dist/{tempo-session-store-DALMRIWN.js → tempo-session-store-2JNOKJGX.js} +2 -2
- package/dist/testing/index.d.ts +25 -3
- package/dist/testing/index.js +36 -3
- package/dist/{usd-gLcJB1ps.d.ts → usd-BgOfZlk6.d.ts} +1 -1
- package/dist/wallet-CJC8lwxx.d.ts +29 -0
- package/dist/{x402-FTG2GRAQ.js → x402-T2C5MX3T.js} +6 -3
- package/package.json +11 -5
- package/dist/{chunk-RU7SXHLO.js → chunk-UP2F5RRT.js} +3 -3
|
@@ -6,12 +6,32 @@ import {
|
|
|
6
6
|
import { randomUUID } from "crypto";
|
|
7
7
|
var ENDPOINTS = {
|
|
8
8
|
revenue: "/_internal/job-revenue",
|
|
9
|
+
cost: "/_internal/job-cost",
|
|
9
10
|
deposit: "/_internal/credit-deposit",
|
|
10
11
|
release: "/_internal/credit-draw-release",
|
|
11
12
|
drain: "/_internal/credit-drain",
|
|
12
13
|
payout: "/_internal/payout",
|
|
13
14
|
expiry_release: "/_internal/credit-expiry-release"
|
|
14
15
|
};
|
|
16
|
+
var PENDING_DVM_REPORTS_TABLE = "pending_dvm_reports";
|
|
17
|
+
var LEGACY_PENDING_REPORTS_TABLE = "pending_revenue_reports";
|
|
18
|
+
var SDK_REPORT_TABLE_BASE_COLUMNS = [
|
|
19
|
+
"id",
|
|
20
|
+
"payload",
|
|
21
|
+
"retry_count",
|
|
22
|
+
"next_retry_at",
|
|
23
|
+
"stale",
|
|
24
|
+
"created_at"
|
|
25
|
+
];
|
|
26
|
+
var PLATFORM_REPORT_TABLE_COLUMNS = [
|
|
27
|
+
"id",
|
|
28
|
+
"payload",
|
|
29
|
+
"source",
|
|
30
|
+
"retry_count",
|
|
31
|
+
"next_retry_at",
|
|
32
|
+
"last_error",
|
|
33
|
+
"created_at"
|
|
34
|
+
];
|
|
15
35
|
var RevenueReporter = class _RevenueReporter {
|
|
16
36
|
// 24h of failed retries
|
|
17
37
|
constructor(db, platformUrl, platformToken) {
|
|
@@ -27,14 +47,14 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
27
47
|
static MAX_BACKOFF_MS = 3e5;
|
|
28
48
|
// 5 min
|
|
29
49
|
static ABANDON_AFTER_MS = 24 * 60 * 60 * 1e3;
|
|
30
|
-
/** Create the pending-reports table (idempotent). */
|
|
50
|
+
/** Create the pending-reports table and migrate the legacy SDK outbox (idempotent). */
|
|
31
51
|
async init() {
|
|
32
52
|
await withSdkInitLock(this.db, () => this.createTables());
|
|
33
53
|
}
|
|
34
54
|
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
35
55
|
async createTables() {
|
|
36
56
|
await this.db.query(`
|
|
37
|
-
CREATE TABLE IF NOT EXISTS
|
|
57
|
+
CREATE TABLE IF NOT EXISTS ${PENDING_DVM_REPORTS_TABLE} (
|
|
38
58
|
id TEXT PRIMARY KEY,
|
|
39
59
|
payload JSONB NOT NULL,
|
|
40
60
|
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
@@ -45,17 +65,75 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
45
65
|
kind TEXT NOT NULL DEFAULT 'revenue'
|
|
46
66
|
)
|
|
47
67
|
`);
|
|
68
|
+
await this.assertSdkTableShape(PENDING_DVM_REPORTS_TABLE);
|
|
48
69
|
await this.db.query(
|
|
49
|
-
|
|
70
|
+
`ALTER TABLE ${PENDING_DVM_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS stale_reason TEXT`
|
|
50
71
|
);
|
|
51
72
|
await this.db.query(
|
|
52
|
-
|
|
73
|
+
`ALTER TABLE ${PENDING_DVM_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'revenue'`
|
|
53
74
|
);
|
|
75
|
+
await this.migrateLegacySdkOutbox();
|
|
76
|
+
}
|
|
77
|
+
/** Copy rows from the old SDK-owned table without touching the platform's table of that name. */
|
|
78
|
+
async migrateLegacySdkOutbox() {
|
|
79
|
+
const columns = await this.tableColumns(LEGACY_PENDING_REPORTS_TABLE);
|
|
80
|
+
if (columns.size === 0) return;
|
|
81
|
+
if (PLATFORM_REPORT_TABLE_COLUMNS.every((column) => columns.has(column))) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.assertSdkColumns(LEGACY_PENDING_REPORTS_TABLE, columns);
|
|
85
|
+
await this.db.query(
|
|
86
|
+
`ALTER TABLE ${LEGACY_PENDING_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS stale_reason TEXT`
|
|
87
|
+
);
|
|
88
|
+
await this.db.query(
|
|
89
|
+
`ALTER TABLE ${LEGACY_PENDING_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'revenue'`
|
|
90
|
+
);
|
|
91
|
+
await this.db.query(`
|
|
92
|
+
WITH copied AS (
|
|
93
|
+
INSERT INTO ${PENDING_DVM_REPORTS_TABLE}
|
|
94
|
+
(id, payload, retry_count, next_retry_at, stale, stale_reason, created_at, kind)
|
|
95
|
+
SELECT id, payload, retry_count, next_retry_at, stale, stale_reason, created_at, kind
|
|
96
|
+
FROM ${LEGACY_PENDING_REPORTS_TABLE}
|
|
97
|
+
ON CONFLICT (id) DO NOTHING
|
|
98
|
+
RETURNING id
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
DELETE FROM ${LEGACY_PENDING_REPORTS_TABLE} AS legacy
|
|
102
|
+
WHERE EXISTS (SELECT 1 FROM copied WHERE copied.id = legacy.id)
|
|
103
|
+
OR EXISTS (
|
|
104
|
+
SELECT 1 FROM ${PENDING_DVM_REPORTS_TABLE} AS migrated
|
|
105
|
+
WHERE migrated.id = legacy.id
|
|
106
|
+
)
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
/** Refuse a pre-existing relation whose columns cannot safely serve as the SDK outbox. */
|
|
110
|
+
async assertSdkTableShape(tableName) {
|
|
111
|
+
this.assertSdkColumns(tableName, await this.tableColumns(tableName));
|
|
112
|
+
}
|
|
113
|
+
assertSdkColumns(tableName, columns) {
|
|
114
|
+
const matchesSdk = SDK_REPORT_TABLE_BASE_COLUMNS.every((column) => columns.has(column)) && !columns.has("source");
|
|
115
|
+
if (matchesSdk) return;
|
|
116
|
+
throw new Error(
|
|
117
|
+
`RevenueReporter.init(): table "${tableName}" is not the SDK revenue outbox shape. The SDK owns "${PENDING_DVM_REPORTS_TABLE}"; the platform owns "${LEGACY_PENDING_REPORTS_TABLE}". Refusing to boot before credit funding can reach a mismatched outbox. Found columns: ${[...columns].sort().join(", ") || "none"}.`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
async tableColumns(tableName) {
|
|
121
|
+
const { rows } = await this.db.query(
|
|
122
|
+
`SELECT column_name
|
|
123
|
+
FROM information_schema.columns
|
|
124
|
+
WHERE table_schema = current_schema() AND table_name = $1`,
|
|
125
|
+
[tableName]
|
|
126
|
+
);
|
|
127
|
+
return new Set(rows.map((row) => row.column_name));
|
|
54
128
|
}
|
|
55
129
|
/** Persist a revenue report and attempt immediate delivery. */
|
|
56
130
|
async report(payload) {
|
|
57
131
|
return this.enqueue("revenue", payload, payload.jobId);
|
|
58
132
|
}
|
|
133
|
+
/** Persist a zero-revenue job cost and attempt immediate delivery. */
|
|
134
|
+
async reportCost(payload) {
|
|
135
|
+
return this.enqueue("cost", payload, payload.jobId);
|
|
136
|
+
}
|
|
59
137
|
/**
|
|
60
138
|
* Join a credit deposit to the caller-owned rail transaction.
|
|
61
139
|
*
|
|
@@ -367,7 +445,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
367
445
|
async getQueueStats() {
|
|
368
446
|
const { rows } = await this.db.query(
|
|
369
447
|
`SELECT stale, stale_reason, COUNT(*)::int AS count
|
|
370
|
-
FROM
|
|
448
|
+
FROM ${PENDING_DVM_REPORTS_TABLE}
|
|
371
449
|
GROUP BY stale, stale_reason`
|
|
372
450
|
);
|
|
373
451
|
let liveRetry = 0;
|
|
@@ -394,7 +472,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
394
472
|
async drainPending() {
|
|
395
473
|
try {
|
|
396
474
|
const { rows } = await this.db.query(
|
|
397
|
-
`SELECT id, payload, retry_count, created_at, kind FROM
|
|
475
|
+
`SELECT id, payload, retry_count, created_at, kind FROM ${PENDING_DVM_REPORTS_TABLE}
|
|
398
476
|
WHERE stale = false AND next_retry_at <= $1
|
|
399
477
|
ORDER BY created_at LIMIT 10`,
|
|
400
478
|
[Date.now()]
|
|
@@ -428,7 +506,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
428
506
|
_RevenueReporter.MAX_BACKOFF_MS
|
|
429
507
|
);
|
|
430
508
|
await this.db.query(
|
|
431
|
-
`UPDATE
|
|
509
|
+
`UPDATE ${PENDING_DVM_REPORTS_TABLE}
|
|
432
510
|
SET retry_count = $1, next_retry_at = $2
|
|
433
511
|
WHERE id = $3`,
|
|
434
512
|
[retryCount, Date.now() + backoff, row.id]
|
|
@@ -449,7 +527,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
449
527
|
async insertPending(q, kind, payload) {
|
|
450
528
|
const id = randomUUID();
|
|
451
529
|
await q.query(
|
|
452
|
-
`INSERT INTO
|
|
530
|
+
`INSERT INTO ${PENDING_DVM_REPORTS_TABLE} (id, payload, created_at, kind)
|
|
453
531
|
VALUES ($1, $2, $3, $4)`,
|
|
454
532
|
[id, JSON.stringify(payload), Date.now(), kind]
|
|
455
533
|
);
|
|
@@ -476,7 +554,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
476
554
|
throw err;
|
|
477
555
|
}
|
|
478
556
|
if (res.ok) {
|
|
479
|
-
await this.db.query(
|
|
557
|
+
await this.db.query(`DELETE FROM ${PENDING_DVM_REPORTS_TABLE} WHERE id = $1`, [id]);
|
|
480
558
|
console.log(`[revenue-reporter] reported ${kind} ${label}`);
|
|
481
559
|
return;
|
|
482
560
|
}
|
|
@@ -500,7 +578,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
500
578
|
/** Mark a pending row terminal (stale) with a classification reason. */
|
|
501
579
|
async markStale(id, reason) {
|
|
502
580
|
await this.db.query(
|
|
503
|
-
|
|
581
|
+
`UPDATE ${PENDING_DVM_REPORTS_TABLE} SET stale = true, stale_reason = $1 WHERE id = $2`,
|
|
504
582
|
[reason, id]
|
|
505
583
|
);
|
|
506
584
|
}
|
|
@@ -1,125 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
tempo: false
|
|
9
|
-
};
|
|
10
|
-
var DEFAULT_CREDIT_MIN = "$0.10";
|
|
11
|
-
var DEFAULT_CREDIT_MAX = "$5.00";
|
|
12
|
-
var DEFAULT_CREDIT_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
13
|
-
|
|
14
|
-
// src/lib/cast-auth/canonical-json.ts
|
|
15
|
-
function canonicalize(value) {
|
|
16
|
-
return JSON.stringify(sortDeep(value));
|
|
17
|
-
}
|
|
18
|
-
function canonicaliseForSigning(value) {
|
|
19
|
-
return new TextEncoder().encode(canonicalize(value));
|
|
20
|
-
}
|
|
21
|
-
function sortDeep(value) {
|
|
22
|
-
if (value === null) return null;
|
|
23
|
-
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
24
|
-
if (typeof value === "number") {
|
|
25
|
-
if (!Number.isFinite(value)) {
|
|
26
|
-
throw new TypeError(`canonicalize: non-finite number is not JSON-serialisable`);
|
|
27
|
-
}
|
|
28
|
-
return value;
|
|
29
|
-
}
|
|
30
|
-
if (Array.isArray(value)) return value.map(sortDeep);
|
|
31
|
-
if (typeof value === "object") {
|
|
32
|
-
const sorted = {};
|
|
33
|
-
for (const key of Object.keys(value).sort()) {
|
|
34
|
-
const v = value[key];
|
|
35
|
-
if (v === void 0) continue;
|
|
36
|
-
sorted[key] = sortDeep(v);
|
|
37
|
-
}
|
|
38
|
-
return sorted;
|
|
39
|
-
}
|
|
40
|
-
throw new TypeError(`canonicalize: unsupported value of type ${typeof value}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/lib/cast-auth/verify.ts
|
|
44
|
-
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
45
|
-
function verifyCanonicalSignedRequest(payload, signatureHex, pubkeyHex) {
|
|
46
|
-
if (!/^[0-9a-f]{64}$/i.test(pubkeyHex) || !/^[0-9a-f]{128}$/i.test(signatureHex)) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
const data = canonicaliseForSigning(payload);
|
|
50
|
-
const signature = hexToBytes(signatureHex);
|
|
51
|
-
const pubkey = hexToBytes(pubkeyHex);
|
|
52
|
-
try {
|
|
53
|
-
return schnorr.verify(signature, data, pubkey);
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function hexToBytes(hex) {
|
|
59
|
-
const out = new Uint8Array(hex.length / 2);
|
|
60
|
-
for (let i = 0; i < out.length; i++) {
|
|
61
|
-
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
62
|
-
}
|
|
63
|
-
return out;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// src/lib/signed-request-statement.ts
|
|
67
|
-
var SIGNED_REQUEST_AUTH_ID = "secp256k1-schnorr-v2";
|
|
68
|
-
var SIGNED_REQUEST_STATEMENT_VERSION = 2;
|
|
69
|
-
var TEST_DOMAIN_SENTINEL = "__dvmkit_test_domain__";
|
|
70
|
-
function normalizeSignedRequestDomain(domain) {
|
|
71
|
-
return {
|
|
72
|
-
audience: {
|
|
73
|
-
dvm_id: domain.audience.dvm_id,
|
|
74
|
-
builder_pubkey: domain.audience.builder_pubkey.trim().toLowerCase()
|
|
75
|
-
},
|
|
76
|
-
method: domain.method.trim().toUpperCase(),
|
|
77
|
-
path: canonicalRequestPath(domain.path),
|
|
78
|
-
capability: domain.capability
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function signedRequestStatementHeader(domain) {
|
|
82
|
-
const normalized = normalizeSignedRequestDomain(domain);
|
|
83
|
-
return {
|
|
84
|
-
v: SIGNED_REQUEST_STATEMENT_VERSION,
|
|
85
|
-
protocol: "dvmkit",
|
|
86
|
-
protocol_version: 1,
|
|
87
|
-
auth: SIGNED_REQUEST_AUTH_ID,
|
|
88
|
-
audience: normalized.audience,
|
|
89
|
-
method: normalized.method,
|
|
90
|
-
path: normalized.path,
|
|
91
|
-
capability: normalized.capability
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function canonicalSignedRequestStatement(header, input) {
|
|
95
|
-
return canonicaliseForSigning({
|
|
96
|
-
...header,
|
|
97
|
-
input
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
function signedRequestHeaderMatchesDomain(header, domain) {
|
|
101
|
-
if (!header || typeof header !== "object" || Array.isArray(header)) return false;
|
|
102
|
-
const actual = header;
|
|
103
|
-
const expected = signedRequestStatementHeader(domain);
|
|
104
|
-
const audience = actual.audience;
|
|
105
|
-
if (process.env.NODE_ENV === "test" && actual.method === TEST_DOMAIN_SENTINEL.toUpperCase() && actual.path === `/${TEST_DOMAIN_SENTINEL}` && actual.capability === TEST_DOMAIN_SENTINEL && audience?.dvm_id === TEST_DOMAIN_SENTINEL) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
return actual.v === expected.v && actual.protocol === expected.protocol && actual.protocol_version === expected.protocol_version && actual.auth === expected.auth && actual.method === expected.method && actual.path === expected.path && actual.capability === expected.capability && !!audience && typeof audience === "object" && audience.dvm_id === expected.audience.dvm_id && audience.builder_pubkey === expected.audience.builder_pubkey;
|
|
109
|
-
}
|
|
110
|
-
function testOnlySignedRequestDomain() {
|
|
111
|
-
if (process.env.NODE_ENV !== "test") return void 0;
|
|
112
|
-
return {
|
|
113
|
-
audience: { dvm_id: TEST_DOMAIN_SENTINEL, builder_pubkey: "00".repeat(32) },
|
|
114
|
-
method: TEST_DOMAIN_SENTINEL,
|
|
115
|
-
path: TEST_DOMAIN_SENTINEL,
|
|
116
|
-
capability: TEST_DOMAIN_SENTINEL
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
function canonicalRequestPath(pathOrUrl) {
|
|
120
|
-
const parsed = new URL(pathOrUrl, "https://signed-request.invalid");
|
|
121
|
-
return parsed.pathname.startsWith("/") ? parsed.pathname : `/${parsed.pathname}`;
|
|
122
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
canonicalSignedRequestStatement,
|
|
3
|
+
isZodSchema,
|
|
4
|
+
signedRequestHeaderMatchesDomain,
|
|
5
|
+
signedRequestStatementHeader,
|
|
6
|
+
testOnlySignedRequestDomain
|
|
7
|
+
} from "./chunk-KXZUCCEY.js";
|
|
123
8
|
|
|
124
9
|
// src/sdk/server/zod-error.ts
|
|
125
10
|
function humanizeZodError(err, opts) {
|
|
@@ -207,7 +92,7 @@ function pickHint(issues, schemaUrl) {
|
|
|
207
92
|
}
|
|
208
93
|
|
|
209
94
|
// src/sdk/server/signed-request.ts
|
|
210
|
-
import { schnorr
|
|
95
|
+
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
211
96
|
var SIGNED_ENVELOPE_TYPES = {
|
|
212
97
|
pubkey: "string",
|
|
213
98
|
signature: "string",
|
|
@@ -317,7 +202,7 @@ function createSignedRequestVerifier(schema, opts = {}) {
|
|
|
317
202
|
}
|
|
318
203
|
}
|
|
319
204
|
function signRequest(privkey, body, signOpts, domain) {
|
|
320
|
-
const pubkey = bytesToHex(
|
|
205
|
+
const pubkey = bytesToHex(schnorr.getPublicKey(privkey));
|
|
321
206
|
const timestamp = signOpts?.timestamp ?? now();
|
|
322
207
|
const nonce = signOpts?.nonce ?? randomNonce();
|
|
323
208
|
const unsigned = {
|
|
@@ -328,7 +213,7 @@ function createSignedRequestVerifier(schema, opts = {}) {
|
|
|
328
213
|
};
|
|
329
214
|
const authStatement = signedRequestStatementHeader(requiredDomain(domain));
|
|
330
215
|
const signature = bytesToHex(
|
|
331
|
-
|
|
216
|
+
schnorr.sign(canonicalBytes({ ...unsigned, auth_statement: authStatement }, domain), privkey)
|
|
332
217
|
);
|
|
333
218
|
return { ...unsigned, auth_statement: authStatement, signature };
|
|
334
219
|
}
|
|
@@ -370,12 +255,12 @@ function statementHeader(input) {
|
|
|
370
255
|
}
|
|
371
256
|
function verifyBytes(bytes, signatureHex, pubkeyHex) {
|
|
372
257
|
try {
|
|
373
|
-
return
|
|
258
|
+
return schnorr.verify(hexToBytes(signatureHex), bytes, hexToBytes(pubkeyHex));
|
|
374
259
|
} catch {
|
|
375
260
|
return false;
|
|
376
261
|
}
|
|
377
262
|
}
|
|
378
|
-
function
|
|
263
|
+
function hexToBytes(hex) {
|
|
379
264
|
const out = new Uint8Array(hex.length / 2);
|
|
380
265
|
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
381
266
|
return out;
|
|
@@ -427,20 +312,6 @@ function createDefaultReplayStore() {
|
|
|
427
312
|
var SATURATION_WARN_INTERVAL_SECONDS = 60;
|
|
428
313
|
|
|
429
314
|
export {
|
|
430
|
-
isZodSchema,
|
|
431
|
-
RAIL_REFUNDABLE,
|
|
432
|
-
DEFAULT_CREDIT_MIN,
|
|
433
|
-
DEFAULT_CREDIT_MAX,
|
|
434
|
-
DEFAULT_CREDIT_TTL_SECONDS,
|
|
435
|
-
canonicalize,
|
|
436
|
-
canonicaliseForSigning,
|
|
437
|
-
verifyCanonicalSignedRequest,
|
|
438
|
-
SIGNED_REQUEST_AUTH_ID,
|
|
439
|
-
SIGNED_REQUEST_STATEMENT_VERSION,
|
|
440
|
-
signedRequestStatementHeader,
|
|
441
|
-
canonicalSignedRequestStatement,
|
|
442
|
-
testOnlySignedRequestDomain,
|
|
443
|
-
canonicalRequestPath,
|
|
444
315
|
humanizeZodError,
|
|
445
316
|
humanizeZodIssues,
|
|
446
317
|
zodIssuesOf,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/sdk/types.ts
|
|
2
|
+
function isZodSchema(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && "_zod" in value && "parse" in value && "safeParse" in value;
|
|
4
|
+
}
|
|
5
|
+
var RAIL_REFUNDABLE = {
|
|
6
|
+
cashu: false,
|
|
7
|
+
x402: false,
|
|
8
|
+
tempo: false
|
|
9
|
+
};
|
|
10
|
+
var DEFAULT_CREDIT_MIN = "$0.10";
|
|
11
|
+
var DEFAULT_CREDIT_MAX = "$5.00";
|
|
12
|
+
var DEFAULT_CREDIT_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
13
|
+
var DEFAULT_JOB_RETENTION_DAYS = 90;
|
|
14
|
+
|
|
15
|
+
// src/lib/cast-auth/canonical-json.ts
|
|
16
|
+
function canonicalize(value) {
|
|
17
|
+
return JSON.stringify(sortDeep(value));
|
|
18
|
+
}
|
|
19
|
+
function canonicaliseForSigning(value) {
|
|
20
|
+
return new TextEncoder().encode(canonicalize(value));
|
|
21
|
+
}
|
|
22
|
+
function sortDeep(value) {
|
|
23
|
+
if (value === null) return null;
|
|
24
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
25
|
+
if (typeof value === "number") {
|
|
26
|
+
if (!Number.isFinite(value)) {
|
|
27
|
+
throw new TypeError(`canonicalize: non-finite number is not JSON-serialisable`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) return value.map(sortDeep);
|
|
32
|
+
if (typeof value === "object") {
|
|
33
|
+
const sorted = {};
|
|
34
|
+
for (const key of Object.keys(value).sort()) {
|
|
35
|
+
const v = value[key];
|
|
36
|
+
if (v === void 0) continue;
|
|
37
|
+
sorted[key] = sortDeep(v);
|
|
38
|
+
}
|
|
39
|
+
return sorted;
|
|
40
|
+
}
|
|
41
|
+
throw new TypeError(`canonicalize: unsupported value of type ${typeof value}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/lib/cast-auth/verify.ts
|
|
45
|
+
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
46
|
+
function verifyCanonicalSignedRequest(payload, signatureHex, pubkeyHex) {
|
|
47
|
+
if (!/^[0-9a-f]{64}$/i.test(pubkeyHex) || !/^[0-9a-f]{128}$/i.test(signatureHex)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const data = canonicaliseForSigning(payload);
|
|
51
|
+
const signature = hexToBytes(signatureHex);
|
|
52
|
+
const pubkey = hexToBytes(pubkeyHex);
|
|
53
|
+
try {
|
|
54
|
+
return schnorr.verify(signature, data, pubkey);
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function hexToBytes(hex) {
|
|
60
|
+
const out = new Uint8Array(hex.length / 2);
|
|
61
|
+
for (let i = 0; i < out.length; i++) {
|
|
62
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/lib/signed-request-statement.ts
|
|
68
|
+
var SIGNED_REQUEST_AUTH_ID = "secp256k1-schnorr-v2";
|
|
69
|
+
var SIGNED_REQUEST_STATEMENT_VERSION = 2;
|
|
70
|
+
var TEST_DOMAIN_SENTINEL = "__dvmkit_test_domain__";
|
|
71
|
+
function normalizeSignedRequestDomain(domain) {
|
|
72
|
+
return {
|
|
73
|
+
audience: {
|
|
74
|
+
dvm_id: domain.audience.dvm_id,
|
|
75
|
+
builder_pubkey: domain.audience.builder_pubkey.trim().toLowerCase()
|
|
76
|
+
},
|
|
77
|
+
method: domain.method.trim().toUpperCase(),
|
|
78
|
+
path: canonicalRequestPath(domain.path),
|
|
79
|
+
capability: domain.capability
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function signedRequestStatementHeader(domain) {
|
|
83
|
+
const normalized = normalizeSignedRequestDomain(domain);
|
|
84
|
+
return {
|
|
85
|
+
v: SIGNED_REQUEST_STATEMENT_VERSION,
|
|
86
|
+
protocol: "dvmkit",
|
|
87
|
+
protocol_version: 1,
|
|
88
|
+
auth: SIGNED_REQUEST_AUTH_ID,
|
|
89
|
+
audience: normalized.audience,
|
|
90
|
+
method: normalized.method,
|
|
91
|
+
path: normalized.path,
|
|
92
|
+
capability: normalized.capability
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function canonicalSignedRequestStatement(header, input) {
|
|
96
|
+
return canonicaliseForSigning({
|
|
97
|
+
...header,
|
|
98
|
+
input
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function signedRequestHeaderMatchesDomain(header, domain) {
|
|
102
|
+
if (!header || typeof header !== "object" || Array.isArray(header)) return false;
|
|
103
|
+
const actual = header;
|
|
104
|
+
const expected = signedRequestStatementHeader(domain);
|
|
105
|
+
const audience = actual.audience;
|
|
106
|
+
if (process.env.NODE_ENV === "test" && actual.method === TEST_DOMAIN_SENTINEL.toUpperCase() && actual.path === `/${TEST_DOMAIN_SENTINEL}` && actual.capability === TEST_DOMAIN_SENTINEL && audience?.dvm_id === TEST_DOMAIN_SENTINEL) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return actual.v === expected.v && actual.protocol === expected.protocol && actual.protocol_version === expected.protocol_version && actual.auth === expected.auth && actual.method === expected.method && actual.path === expected.path && actual.capability === expected.capability && !!audience && typeof audience === "object" && audience.dvm_id === expected.audience.dvm_id && audience.builder_pubkey === expected.audience.builder_pubkey;
|
|
110
|
+
}
|
|
111
|
+
function testOnlySignedRequestDomain() {
|
|
112
|
+
if (process.env.NODE_ENV !== "test") return void 0;
|
|
113
|
+
return {
|
|
114
|
+
audience: { dvm_id: TEST_DOMAIN_SENTINEL, builder_pubkey: "00".repeat(32) },
|
|
115
|
+
method: TEST_DOMAIN_SENTINEL,
|
|
116
|
+
path: TEST_DOMAIN_SENTINEL,
|
|
117
|
+
capability: TEST_DOMAIN_SENTINEL
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function canonicalRequestPath(pathOrUrl) {
|
|
121
|
+
const parsed = new URL(pathOrUrl, "https://signed-request.invalid");
|
|
122
|
+
return parsed.pathname.startsWith("/") ? parsed.pathname : `/${parsed.pathname}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
isZodSchema,
|
|
127
|
+
RAIL_REFUNDABLE,
|
|
128
|
+
DEFAULT_CREDIT_MIN,
|
|
129
|
+
DEFAULT_CREDIT_MAX,
|
|
130
|
+
DEFAULT_CREDIT_TTL_SECONDS,
|
|
131
|
+
DEFAULT_JOB_RETENTION_DAYS,
|
|
132
|
+
canonicalize,
|
|
133
|
+
canonicaliseForSigning,
|
|
134
|
+
verifyCanonicalSignedRequest,
|
|
135
|
+
SIGNED_REQUEST_AUTH_ID,
|
|
136
|
+
SIGNED_REQUEST_STATEMENT_VERSION,
|
|
137
|
+
signedRequestStatementHeader,
|
|
138
|
+
canonicalSignedRequestStatement,
|
|
139
|
+
signedRequestHeaderMatchesDomain,
|
|
140
|
+
testOnlySignedRequestDomain,
|
|
141
|
+
canonicalRequestPath
|
|
142
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
applyRetryToPool
|
|
3
|
-
} from "./chunk-C3MTFLC6.js";
|
|
4
1
|
import {
|
|
5
2
|
withSdkInitLock
|
|
6
3
|
} from "./chunk-S3XAHZQY.js";
|
|
4
|
+
import {
|
|
5
|
+
applyRetryToPool
|
|
6
|
+
} from "./chunk-C3MTFLC6.js";
|
|
7
7
|
|
|
8
8
|
// src/sdk/server/credit-ledger.ts
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
@@ -2736,6 +2736,14 @@ var CreditLedger = class {
|
|
|
2736
2736
|
);
|
|
2737
2737
|
return rows.length === 1 ? drawRecordFromRow(rows[0], rows[0]) : void 0;
|
|
2738
2738
|
}
|
|
2739
|
+
/** Read every draw linked to a job, including already-resolved rows. */
|
|
2740
|
+
async listDrawsByJobId(jobId) {
|
|
2741
|
+
const { rows } = await this.pool.query(
|
|
2742
|
+
`${DRAW_WITH_BASIS_SELECT} WHERE d.job_id = $1 ORDER BY d.created_at, d.credit_id, d.ledger_seq`,
|
|
2743
|
+
[jobId]
|
|
2744
|
+
);
|
|
2745
|
+
return rows.map((row) => drawRecordFromRow(row, row));
|
|
2746
|
+
}
|
|
2739
2747
|
/**
|
|
2740
2748
|
* All pending holds on a credit, in `ledger_seq` order (no lock). Read
|
|
2741
2749
|
* surface for the internal-review sweeper wiring — a worker that dies mid-job
|