@dvmkit/sdk 0.1.0-rc.2 → 0.1.0-rc.4
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 +10 -2
- package/dist/{revenue-reporter-GB4WKLDC.js → chunk-2ABMGUDS.js} +1 -0
- package/dist/chunk-2K6UXDAN.js +1557 -0
- package/dist/chunk-4UVRXDNY.js +21745 -0
- package/dist/chunk-66HGCPBU.js +25 -0
- package/dist/{chunk-DCNT4PJS.js → chunk-AAJNGQMC.js} +6 -258
- package/dist/{chunk-KQAJVVZT.js → chunk-AZBXSXQT.js} +27 -287
- package/dist/{chunk-H25M54MI.js → chunk-C3MTFLC6.js} +16 -0
- package/dist/chunk-EDU6COY2.js +1057 -0
- package/dist/{chunk-OJ5WFIB2.js → chunk-EXHBXA4U.js} +1 -1
- package/dist/chunk-F2L6KIMD.js +380 -0
- package/dist/{chunk-KXWROQGK.js → chunk-FUJ36YDV.js} +1 -24
- package/dist/chunk-GJD7PVWY.js +1090 -0
- package/dist/{tempo-charge-store-6GJEMNUU.js → chunk-JZWELPFH.js} +1 -0
- package/dist/{chunk-365P52XQ.js → chunk-LWUR4CGG.js} +2 -1
- package/dist/{tempo-session-store-FTEEGZXA.js → chunk-RU7SXHLO.js} +2 -1
- package/dist/{chunk-7IH5SG2A.js → chunk-TKA6ZP4M.js} +62 -41
- package/dist/chunk-TVI4V7GF.js +283 -0
- package/dist/{payout-reporter-4TNWRS5F.js → chunk-X3IKFWJA.js} +3 -2
- package/dist/chunk-XYTSDAPH.js +232 -0
- package/dist/{credit-ledger-RO4FGSHG.js → credit-ledger-ED6JXKVD.js} +2 -2
- package/dist/credit-menu-ClA1JyYW.d.ts +5076 -0
- package/dist/{ssrf-DZi-xJyn.d.ts → fx-ptKVFOwq.d.ts} +37 -119
- package/dist/index.d.ts +8 -66
- package/dist/index.js +11 -219
- package/dist/internal/index.d.ts +5896 -0
- package/dist/internal/index.js +6407 -0
- package/dist/{job-store-6gR4pZRP.d.ts → job-store-DxFqDPYq.d.ts} +1498 -12
- package/dist/{memory-credit-ledger-I2G64DDK.js → memory-credit-ledger-XJ5VQEVP.js} +3 -3
- package/dist/payout-reporter-3UB5WRCV.js +13 -0
- package/dist/revenue-reporter-JIKUPXOK.js +7 -0
- package/dist/server/index.d.ts +14 -3590
- package/dist/server/index.js +155 -20850
- package/dist/ssrf-DbFkpDv0.d.ts +118 -0
- package/dist/tempo-charge-store-RIFTALZK.js +8 -0
- package/dist/tempo-session-store-DALMRIWN.js +11 -0
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/usd-Cha_j80I.d.ts +97 -0
- package/dist/x402-XXFQQAAD.js +81 -0
- package/package.json +6 -2
- package/dist/x402-35VLYFKZ.js +0 -1272
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/lib/caller-loggers.ts
|
|
2
|
+
var callerLoggers = buildNoopLoggers();
|
|
3
|
+
function initCallerLoggers(nextLoggers) {
|
|
4
|
+
callerLoggers = nextLoggers;
|
|
5
|
+
}
|
|
6
|
+
function buildNoopLoggers() {
|
|
7
|
+
const noopLogger = {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- standalone caller has no exporter
|
|
9
|
+
info() {
|
|
10
|
+
},
|
|
11
|
+
async span(_name, _attrs, fn) {
|
|
12
|
+
return fn();
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
cashu: noopLogger,
|
|
17
|
+
lightning: noopLogger,
|
|
18
|
+
x402: noopLogger
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
callerLoggers,
|
|
24
|
+
initCallerLoggers
|
|
25
|
+
};
|
|
@@ -1,28 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DvmError
|
|
3
|
+
} from "./chunk-F2L6KIMD.js";
|
|
1
4
|
import {
|
|
2
5
|
callerLoggers
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-66HGCPBU.js";
|
|
4
7
|
|
|
5
8
|
// src/lib/x402/constants.ts
|
|
6
9
|
var X402_DEFAULT_FACILITATOR = "https://x402.org/facilitator";
|
|
7
10
|
|
|
8
|
-
// src/lib/errors.ts
|
|
9
|
-
var DvmError = class extends Error {
|
|
10
|
-
constructor(code, message, hint, data, display, proseSources) {
|
|
11
|
-
super(message);
|
|
12
|
-
this.code = code;
|
|
13
|
-
this.hint = hint;
|
|
14
|
-
this.data = data;
|
|
15
|
-
this.display = display;
|
|
16
|
-
this.proseSources = proseSources;
|
|
17
|
-
this.name = "DvmError";
|
|
18
|
-
}
|
|
19
|
-
code;
|
|
20
|
-
hint;
|
|
21
|
-
data;
|
|
22
|
-
display;
|
|
23
|
-
proseSources;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
11
|
// src/lib/x402/networks.ts
|
|
27
12
|
var X402_NETWORKS = [
|
|
28
13
|
{
|
|
@@ -459,231 +444,13 @@ function isOptionalRecord(value) {
|
|
|
459
444
|
return value === void 0 || value === null || isRecord(value);
|
|
460
445
|
}
|
|
461
446
|
|
|
462
|
-
// src/lib/credit-implicit.ts
|
|
463
|
-
var IMPLICIT_CREDIT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
464
|
-
|
|
465
|
-
// src/lib/currency.ts
|
|
466
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
467
|
-
import { dirname } from "path";
|
|
468
|
-
|
|
469
|
-
// src/defaults.ts
|
|
470
|
-
import { homedir } from "os";
|
|
471
|
-
import { join } from "path";
|
|
472
|
-
var CONFIG_DIR = process.env.DVM_CONFIG_DIR ?? join(homedir(), ".dvm");
|
|
473
|
-
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
474
|
-
var CONFIG_BACKUP_FILE = join(CONFIG_DIR, "config.json.bak");
|
|
475
|
-
var CONFIG_AUDIT_FILE = join(CONFIG_DIR, "config-audit.jsonl");
|
|
476
|
-
var CONFIG_PINNED_FILE = join(CONFIG_DIR, "config.json.pinned");
|
|
477
|
-
var JOBS_FILE = join(CONFIG_DIR, "jobs.json");
|
|
478
|
-
var REQUEST_INTENTS_DIR = join(CONFIG_DIR, "request-intents");
|
|
479
|
-
var FAVORITES_FILE = join(CONFIG_DIR, "favorites.json");
|
|
480
|
-
var MESSAGES_DIR = join(CONFIG_DIR, "messages");
|
|
481
|
-
var RECEIPTS_FILE = join(CONFIG_DIR, "receipts.jsonl");
|
|
482
|
-
var CREDITS_FILE = join(CONFIG_DIR, "credits.json");
|
|
483
|
-
var X402_CHANNELS_FILE = join(CONFIG_DIR, "x402-channels.json");
|
|
484
|
-
var TEMPO_CHANNELS_FILE = join(CONFIG_DIR, "tempo-channels.json");
|
|
485
|
-
var FEEDBACK_NUDGES_FILE = join(CONFIG_DIR, "feedback-nudges.json");
|
|
486
|
-
var RECOVERY_MNEMONIC_FILE = join(CONFIG_DIR, "recovery.mnemonic");
|
|
487
|
-
var WALLET_FILE = join(CONFIG_DIR, "wallet.json");
|
|
488
|
-
var AGENT_MNEMONIC_FILE = join(CONFIG_DIR, "wallet.mnemonic");
|
|
489
|
-
var IDENTITIES_FILE = join(CONFIG_DIR, "identities.json");
|
|
490
|
-
var PAIRINGS_DIR = join(CONFIG_DIR, "pairings");
|
|
491
|
-
var BUDGET_FILE = join(CONFIG_DIR, "budget.json");
|
|
492
|
-
var BTC_RATE_FILE = join(CONFIG_DIR, "btc-rate.json");
|
|
493
|
-
var ADMIN_DIR = join(CONFIG_DIR, "admin");
|
|
494
|
-
var PROXY_STATE_FILE = join(ADMIN_DIR, "proxy.json");
|
|
495
|
-
var BUILDER_CONFIG_DIR = process.env.DVMKIT_CONFIG_DIR ?? join(homedir(), ".dvmkit");
|
|
496
|
-
var BUILDER_FILE = join(BUILDER_CONFIG_DIR, "builder.json");
|
|
497
|
-
var BUILDER_MNEMONIC_FILE = join(BUILDER_CONFIG_DIR, "recovery.mnemonic");
|
|
498
|
-
var BUILDER_KEY_FILE = join(BUILDER_CONFIG_DIR, "builder.key");
|
|
499
|
-
var MELT_PENDING_LOG_FILE = join(BUILDER_CONFIG_DIR, "melt-pending.log");
|
|
500
|
-
var DRAIN_PARK_LOG_FILE = join(BUILDER_CONFIG_DIR, "drain-park.log");
|
|
501
|
-
var DRAIN_HOLD_STATE_FILE = join(BUILDER_CONFIG_DIR, "drain-hold.json");
|
|
502
|
-
var MELT_SUCCESS_FILE = join(BUILDER_CONFIG_DIR, "melt-success.json");
|
|
503
|
-
|
|
504
|
-
// src/lib/config-dir.ts
|
|
505
|
-
import { chmodSync, mkdirSync, statSync } from "fs";
|
|
506
|
-
var CONFIG_DIR_MODE = 448;
|
|
507
|
-
function ensureConfigDir() {
|
|
508
|
-
ensureConfigSubdir(CONFIG_DIR);
|
|
509
|
-
}
|
|
510
|
-
function ensureConfigSubdir(path) {
|
|
511
|
-
mkdirSync(path, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// src/lib/fx-coingecko.ts
|
|
515
|
-
var COINGECKO_BTC_USD_URL = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd";
|
|
516
|
-
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
517
|
-
var DEFAULT_USER_AGENT = "dvmkit-platform/1.0 (+https://dvmkit.com)";
|
|
518
|
-
async function fetchCoinGeckoBtcUsd(opts) {
|
|
519
|
-
const body = await fetchCoinGeckoBody(opts, COINGECKO_BTC_USD_URL);
|
|
520
|
-
const btc = body.bitcoin?.usd;
|
|
521
|
-
if (!isPositiveNumber(btc)) {
|
|
522
|
-
throw new Error("coingecko response missing bitcoin.usd");
|
|
523
|
-
}
|
|
524
|
-
return { btc_usd: btc, as_of: (/* @__PURE__ */ new Date()).toISOString() };
|
|
525
|
-
}
|
|
526
|
-
async function fetchCoinGeckoBody(opts, defaultUrl) {
|
|
527
|
-
const url = opts?.url ?? defaultUrl;
|
|
528
|
-
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
529
|
-
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
530
|
-
const userAgent = opts?.userAgent ?? DEFAULT_USER_AGENT;
|
|
531
|
-
const res = await fetchImpl(url, {
|
|
532
|
-
headers: { "User-Agent": userAgent, Accept: "application/json" },
|
|
533
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
534
|
-
});
|
|
535
|
-
if (!res.ok) {
|
|
536
|
-
throw new Error(`coingecko returned HTTP ${res.status.toString()}`);
|
|
537
|
-
}
|
|
538
|
-
return await res.json();
|
|
539
|
-
}
|
|
540
|
-
function isPositiveNumber(v) {
|
|
541
|
-
return typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// src/lib/currency.ts
|
|
545
|
-
function msatsToUsd(msats, rate) {
|
|
546
|
-
return msats / 1e11 * rate;
|
|
547
|
-
}
|
|
548
|
-
function usdToMsats(usd, rate) {
|
|
549
|
-
return Math.round(usd / rate * 1e11);
|
|
550
|
-
}
|
|
551
|
-
function msatsToUsdc(msats, btcUsdRate) {
|
|
552
|
-
const usd = msatsToUsd(msats, btcUsdRate);
|
|
553
|
-
return Math.ceil(usd * 1e6);
|
|
554
|
-
}
|
|
555
|
-
function usdcToMsats(usdc, btcUsdRate) {
|
|
556
|
-
return usdToMsats(usdc, btcUsdRate);
|
|
557
|
-
}
|
|
558
|
-
var COST_ANCHOR_BANDS = {
|
|
559
|
-
usd: [0.01, 0.1, 1, 10],
|
|
560
|
-
eur: [0.01, 0.1, 1, 10],
|
|
561
|
-
gbp: [0.01, 0.1, 1, 10],
|
|
562
|
-
jpy: [1, 10, 100, 1500]
|
|
563
|
-
};
|
|
564
|
-
function costAnchor(amount, currency = "usd") {
|
|
565
|
-
const bands = COST_ANCHOR_BANDS[currency] ?? COST_ANCHOR_BANDS.usd;
|
|
566
|
-
if (amount < bands[0]) return "fraction of a cent";
|
|
567
|
-
if (amount <= bands[1]) return "comparable to an API call";
|
|
568
|
-
if (amount <= bands[2]) return "comparable to a ChatGPT query";
|
|
569
|
-
if (amount <= bands[3]) return "comparable to a coffee";
|
|
570
|
-
return "significant purchase \u2014 review carefully";
|
|
571
|
-
}
|
|
572
|
-
function formatUsd(usd) {
|
|
573
|
-
if (usd === 0) return "0.00";
|
|
574
|
-
if (usd >= 0.01) return usd.toFixed(2);
|
|
575
|
-
const digits = Math.max(2, -Math.floor(Math.log10(usd)) + 1);
|
|
576
|
-
return usd.toFixed(digits);
|
|
577
|
-
}
|
|
578
|
-
async function fetchBtcUsdRate() {
|
|
579
|
-
if (memRate && Date.now() - memTime < CACHE_TTL) {
|
|
580
|
-
return memRate;
|
|
581
|
-
}
|
|
582
|
-
const disk = readDiskCache();
|
|
583
|
-
if (disk) {
|
|
584
|
-
memRate = disk;
|
|
585
|
-
memTime = Date.now();
|
|
586
|
-
return disk;
|
|
587
|
-
}
|
|
588
|
-
const rate = await fetchFromApi();
|
|
589
|
-
memRate = rate;
|
|
590
|
-
memTime = Date.now();
|
|
591
|
-
writeDiskCache(rate);
|
|
592
|
-
return rate;
|
|
593
|
-
}
|
|
594
|
-
function getLastKnownBtcRate() {
|
|
595
|
-
if (memRate && memTime > 0) return { rate: memRate, cachedAt: memTime };
|
|
596
|
-
return readDiskCacheStale();
|
|
597
|
-
}
|
|
598
|
-
function formatStaleness(cachedAt, now = Date.now()) {
|
|
599
|
-
const ageMs = Math.max(0, now - cachedAt);
|
|
600
|
-
const seconds = Math.floor(ageMs / 1e3);
|
|
601
|
-
if (seconds < 60) return `${seconds}s ago`;
|
|
602
|
-
const minutes = Math.floor(seconds / 60);
|
|
603
|
-
if (minutes < 60) return `${minutes} min ago`;
|
|
604
|
-
const hours = Math.floor(minutes / 60);
|
|
605
|
-
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
606
|
-
const days = Math.floor(hours / 24);
|
|
607
|
-
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
608
|
-
}
|
|
609
|
-
var CACHE_TTL = 5 * 60 * 1e3;
|
|
610
|
-
var CACHE_FILE = BTC_RATE_FILE;
|
|
611
|
-
var memRate = null;
|
|
612
|
-
var memTime = 0;
|
|
613
|
-
function readDiskCache() {
|
|
614
|
-
try {
|
|
615
|
-
if (!existsSync(CACHE_FILE)) return null;
|
|
616
|
-
const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
|
|
617
|
-
if (Date.now() - data.timestamp >= CACHE_TTL) return null;
|
|
618
|
-
if (!isPlausibleBtcUsdRate(data.rate)) return null;
|
|
619
|
-
return data.rate;
|
|
620
|
-
} catch {
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
function readDiskCacheStale() {
|
|
625
|
-
try {
|
|
626
|
-
if (!existsSync(CACHE_FILE)) return null;
|
|
627
|
-
const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
|
|
628
|
-
if (!isPlausibleBtcUsdRate(data.rate)) return null;
|
|
629
|
-
return { rate: data.rate, cachedAt: data.timestamp };
|
|
630
|
-
} catch {
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
function writeDiskCache(rate) {
|
|
635
|
-
try {
|
|
636
|
-
ensureConfigSubdir(dirname(CACHE_FILE));
|
|
637
|
-
writeFileSync(CACHE_FILE, JSON.stringify({ rate, timestamp: Date.now() }));
|
|
638
|
-
} catch {
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
var MAX_PLAUSIBLE_BTC_USD = 1e7;
|
|
642
|
-
function isPlausibleBtcUsdRate(rate) {
|
|
643
|
-
return typeof rate === "number" && Number.isFinite(rate) && rate > 0 && rate < MAX_PLAUSIBLE_BTC_USD;
|
|
644
|
-
}
|
|
645
|
-
async function fetchFromApi() {
|
|
646
|
-
const spot = await fetchCoinGeckoBtcUsd();
|
|
647
|
-
const rate = spot.btc_usd;
|
|
648
|
-
if (!isPlausibleBtcUsdRate(rate)) {
|
|
649
|
-
throw new Error(`implausible BTC/USD rate from upstream: ${String(rate)}`);
|
|
650
|
-
}
|
|
651
|
-
return rate;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// src/lib/payment-rails/rpc.ts
|
|
655
|
-
function resolveRpcOverride(envValue, bundled) {
|
|
656
|
-
const override = envValue?.trim() ?? "";
|
|
657
|
-
return override.length > 0 ? { url: override, source: "override" } : { url: bundled, source: "bundled" };
|
|
658
|
-
}
|
|
659
|
-
function resolveRpcHttpTransportOptions(source, envValue) {
|
|
660
|
-
if (source !== "override" || !envValue?.trim()) return void 0;
|
|
661
|
-
let parsed;
|
|
662
|
-
try {
|
|
663
|
-
parsed = JSON.parse(envValue);
|
|
664
|
-
} catch {
|
|
665
|
-
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
666
|
-
}
|
|
667
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
668
|
-
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
669
|
-
const headers = {};
|
|
670
|
-
for (const [name, value] of Object.entries(parsed)) {
|
|
671
|
-
if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || typeof value !== "string" || value.length === 0 || /[\r\n]/.test(value))
|
|
672
|
-
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
673
|
-
headers[name] = value;
|
|
674
|
-
}
|
|
675
|
-
if (Object.keys(headers).length === 0)
|
|
676
|
-
throw new Error("RPC headers must be a JSON object of non-empty string values.");
|
|
677
|
-
return { fetchOptions: { headers } };
|
|
678
|
-
}
|
|
679
|
-
|
|
680
447
|
export {
|
|
681
448
|
X402_DEFAULT_FACILITATOR,
|
|
682
449
|
createX402FacilitatorAuthHeaders,
|
|
683
450
|
createX402FacilitatorClient,
|
|
684
|
-
DvmError,
|
|
685
451
|
X402_NETWORKS,
|
|
686
452
|
x402NetworkByCaip2,
|
|
453
|
+
x402NetworkBySlug,
|
|
687
454
|
x402NetworkLabel,
|
|
688
455
|
x402NetworkListForCopy,
|
|
689
456
|
assertSupportedX402Network,
|
|
@@ -710,24 +477,5 @@ export {
|
|
|
710
477
|
exactEvmAuthorization,
|
|
711
478
|
encodeSettleResponseHeader,
|
|
712
479
|
verifyWithFacilitator,
|
|
713
|
-
settleWithFacilitator
|
|
714
|
-
IMPLICIT_CREDIT_TTL_MS,
|
|
715
|
-
CONFIG_FILE,
|
|
716
|
-
CONFIG_BACKUP_FILE,
|
|
717
|
-
CONFIG_AUDIT_FILE,
|
|
718
|
-
CONFIG_PINNED_FILE,
|
|
719
|
-
X402_CHANNELS_FILE,
|
|
720
|
-
WALLET_FILE,
|
|
721
|
-
AGENT_MNEMONIC_FILE,
|
|
722
|
-
ensureConfigDir,
|
|
723
|
-
msatsToUsd,
|
|
724
|
-
msatsToUsdc,
|
|
725
|
-
usdcToMsats,
|
|
726
|
-
costAnchor,
|
|
727
|
-
formatUsd,
|
|
728
|
-
fetchBtcUsdRate,
|
|
729
|
-
getLastKnownBtcRate,
|
|
730
|
-
formatStaleness,
|
|
731
|
-
resolveRpcOverride,
|
|
732
|
-
resolveRpcHttpTransportOptions
|
|
480
|
+
settleWithFacilitator
|
|
733
481
|
};
|
|
@@ -11,11 +11,6 @@ var DEFAULT_CREDIT_MIN = "$0.10";
|
|
|
11
11
|
var DEFAULT_CREDIT_MAX = "$5.00";
|
|
12
12
|
var DEFAULT_CREDIT_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
13
13
|
|
|
14
|
-
// src/sdk/env.ts
|
|
15
|
-
function envFlag(value) {
|
|
16
|
-
return value === "1" || value === "true";
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
// src/lib/cast-auth/canonical-json.ts
|
|
20
15
|
function canonicalize(value) {
|
|
21
16
|
return JSON.stringify(sortDeep(value));
|
|
@@ -47,6 +42,26 @@ function sortDeep(value) {
|
|
|
47
42
|
|
|
48
43
|
// src/lib/cast-auth/verify.ts
|
|
49
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
|
+
}
|
|
50
65
|
|
|
51
66
|
// src/lib/signed-request-statement.ts
|
|
52
67
|
var SIGNED_REQUEST_AUTH_ID = "secp256k1-schnorr-v2";
|
|
@@ -106,9 +121,6 @@ function canonicalRequestPath(pathOrUrl) {
|
|
|
106
121
|
return parsed.pathname.startsWith("/") ? parsed.pathname : `/${parsed.pathname}`;
|
|
107
122
|
}
|
|
108
123
|
|
|
109
|
-
// src/sdk/server/signed-request.ts
|
|
110
|
-
import { schnorr as schnorr2 } from "@noble/curves/secp256k1.js";
|
|
111
|
-
|
|
112
124
|
// src/sdk/server/zod-error.ts
|
|
113
125
|
function humanizeZodError(err, opts) {
|
|
114
126
|
return humanizeZodIssues(err.issues, opts);
|
|
@@ -195,6 +207,7 @@ function pickHint(issues, schemaUrl) {
|
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
// src/sdk/server/signed-request.ts
|
|
210
|
+
import { schnorr as schnorr2 } from "@noble/curves/secp256k1.js";
|
|
198
211
|
var SIGNED_ENVELOPE_TYPES = {
|
|
199
212
|
pubkey: "string",
|
|
200
213
|
signature: "string",
|
|
@@ -357,12 +370,12 @@ function statementHeader(input) {
|
|
|
357
370
|
}
|
|
358
371
|
function verifyBytes(bytes, signatureHex, pubkeyHex) {
|
|
359
372
|
try {
|
|
360
|
-
return schnorr2.verify(
|
|
373
|
+
return schnorr2.verify(hexToBytes2(signatureHex), bytes, hexToBytes2(pubkeyHex));
|
|
361
374
|
} catch {
|
|
362
375
|
return false;
|
|
363
376
|
}
|
|
364
377
|
}
|
|
365
|
-
function
|
|
378
|
+
function hexToBytes2(hex) {
|
|
366
379
|
const out = new Uint8Array(hex.length / 2);
|
|
367
380
|
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
368
381
|
return out;
|
|
@@ -413,300 +426,27 @@ function createDefaultReplayStore() {
|
|
|
413
426
|
}
|
|
414
427
|
var SATURATION_WARN_INTERVAL_SECONDS = 60;
|
|
415
428
|
|
|
416
|
-
// src/sdk/server/ssrf.ts
|
|
417
|
-
import { lookup } from "dns/promises";
|
|
418
|
-
import { isIPv4, isIPv6 } from "net";
|
|
419
|
-
import { Agent } from "undici";
|
|
420
|
-
var SSRFError = class extends Error {
|
|
421
|
-
code = "ssrf_blocked";
|
|
422
|
-
reason;
|
|
423
|
-
constructor(reason, message) {
|
|
424
|
-
super(message);
|
|
425
|
-
this.reason = reason;
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
async function assertSafeUrl(url, opts) {
|
|
429
|
-
await resolveAndValidate(url, opts);
|
|
430
|
-
}
|
|
431
|
-
async function createPinnedFetch(url, opts) {
|
|
432
|
-
const validated = await resolveAndValidate(url, opts);
|
|
433
|
-
const baseFetch = opts?.baseFetch ?? fetch;
|
|
434
|
-
const initialUrl = new URL(url);
|
|
435
|
-
const agents = [];
|
|
436
|
-
const targets = /* @__PURE__ */ new Map();
|
|
437
|
-
const createTarget = (next) => {
|
|
438
|
-
if (next.kind === "literal") return {};
|
|
439
|
-
const agent = new Agent({
|
|
440
|
-
connect: { lookup: buildPinnedLookup(next.addresses) }
|
|
441
|
-
});
|
|
442
|
-
agents.push(agent);
|
|
443
|
-
return { agent };
|
|
444
|
-
};
|
|
445
|
-
targets.set(initialUrl.origin, createTarget(validated));
|
|
446
|
-
const targetFor = async (requestUrl) => {
|
|
447
|
-
const parsed = new URL(requestUrl);
|
|
448
|
-
const existing = targets.get(parsed.origin);
|
|
449
|
-
if (existing) return existing;
|
|
450
|
-
const target = createTarget(await resolveAndValidate(parsed.href, opts));
|
|
451
|
-
targets.set(parsed.origin, target);
|
|
452
|
-
return target;
|
|
453
|
-
};
|
|
454
|
-
const pinnedFetch = async (input, init) => {
|
|
455
|
-
let request = new Request(input, init);
|
|
456
|
-
let redirects = 0;
|
|
457
|
-
for (; ; ) {
|
|
458
|
-
const target = await targetFor(request.url);
|
|
459
|
-
const response = await baseFetch(
|
|
460
|
-
request,
|
|
461
|
-
target.agent ? { redirect: "manual", dispatcher: target.agent } : { redirect: "manual" }
|
|
462
|
-
);
|
|
463
|
-
if (!isRedirect(response.status) || request.redirect === "manual") return response;
|
|
464
|
-
const location = response.headers.get("location");
|
|
465
|
-
if (location === null) return response;
|
|
466
|
-
await discardResponse(response);
|
|
467
|
-
if (request.redirect === "error") {
|
|
468
|
-
throw new TypeError("fetch failed: redirect mode is set to error");
|
|
469
|
-
}
|
|
470
|
-
if (redirects >= MAX_PINNED_REDIRECTS) {
|
|
471
|
-
throw new TypeError("fetch failed: maximum redirect count exceeded");
|
|
472
|
-
}
|
|
473
|
-
request = requestForRedirect(request, new URL(location, request.url), response.status);
|
|
474
|
-
redirects += 1;
|
|
475
|
-
opts?.onRedirectFollowed?.();
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
return {
|
|
479
|
-
fetch: pinnedFetch,
|
|
480
|
-
address: validated.address,
|
|
481
|
-
family: validated.family,
|
|
482
|
-
close: async () => {
|
|
483
|
-
await Promise.all(agents.map((agent) => agent.close()));
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
var MAX_PINNED_REDIRECTS = 20;
|
|
488
|
-
function isRedirect(status) {
|
|
489
|
-
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
490
|
-
}
|
|
491
|
-
async function discardResponse(response) {
|
|
492
|
-
await response.body?.cancel().catch(() => void 0);
|
|
493
|
-
}
|
|
494
|
-
function requestForRedirect(request, target, status) {
|
|
495
|
-
const headers = new Headers(request.headers);
|
|
496
|
-
if (new URL(request.url).origin !== target.origin) {
|
|
497
|
-
headers.delete("authorization");
|
|
498
|
-
headers.delete("cookie");
|
|
499
|
-
headers.delete("proxy-authorization");
|
|
500
|
-
headers.delete("host");
|
|
501
|
-
}
|
|
502
|
-
const method = request.method.toUpperCase();
|
|
503
|
-
const switchToGet = status === 303 && method !== "HEAD" || (status === 301 || status === 302) && method === "POST";
|
|
504
|
-
if (switchToGet) {
|
|
505
|
-
headers.delete("content-length");
|
|
506
|
-
headers.delete("content-type");
|
|
507
|
-
return redirectRequest(target, request, headers, "GET");
|
|
508
|
-
}
|
|
509
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
510
|
-
throw new TypeError("fetch failed: cannot safely replay a request body across this redirect");
|
|
511
|
-
}
|
|
512
|
-
return redirectRequest(target, request, headers, method);
|
|
513
|
-
}
|
|
514
|
-
function redirectRequest(target, request, headers, method) {
|
|
515
|
-
return new Request(target, {
|
|
516
|
-
method,
|
|
517
|
-
headers,
|
|
518
|
-
signal: request.signal,
|
|
519
|
-
redirect: request.redirect,
|
|
520
|
-
cache: request.cache,
|
|
521
|
-
credentials: request.credentials,
|
|
522
|
-
integrity: request.integrity,
|
|
523
|
-
keepalive: request.keepalive,
|
|
524
|
-
mode: request.mode,
|
|
525
|
-
referrer: request.referrer,
|
|
526
|
-
referrerPolicy: request.referrerPolicy
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
function buildPinnedLookup(addresses) {
|
|
530
|
-
return ((_hostname, options, cb) => {
|
|
531
|
-
if (options.all === false) {
|
|
532
|
-
const first = addresses[0];
|
|
533
|
-
cb(
|
|
534
|
-
null,
|
|
535
|
-
first.address,
|
|
536
|
-
first.family
|
|
537
|
-
);
|
|
538
|
-
} else {
|
|
539
|
-
cb(
|
|
540
|
-
null,
|
|
541
|
-
addresses
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
async function resolveAndValidate(url, opts) {
|
|
547
|
-
const resolver = opts?.resolver ?? defaultResolver;
|
|
548
|
-
let parsed;
|
|
549
|
-
try {
|
|
550
|
-
parsed = new URL(url);
|
|
551
|
-
} catch {
|
|
552
|
-
throw new SSRFError("invalid_url", `url is not a valid URL: ${url}`);
|
|
553
|
-
}
|
|
554
|
-
if (parsed.protocol !== "https:") {
|
|
555
|
-
throw new SSRFError("insecure_scheme", `url must be https; got ${parsed.protocol}`);
|
|
556
|
-
}
|
|
557
|
-
const host = parsed.hostname.toLowerCase();
|
|
558
|
-
if (host === "") {
|
|
559
|
-
throw new SSRFError("invalid_url", "url has no host");
|
|
560
|
-
}
|
|
561
|
-
if (HOSTNAME_DENYLIST_EXACT.has(host) || matchesExtraExact(host, opts?.extraHostnamesExact)) {
|
|
562
|
-
throw new SSRFError("private_address", `url host "${host}" is on the SSRF denylist`);
|
|
563
|
-
}
|
|
564
|
-
for (const suffix of HOSTNAME_DENYLIST_SUFFIX) {
|
|
565
|
-
if (host === suffix.slice(1) || host.endsWith(suffix)) {
|
|
566
|
-
throw new SSRFError(
|
|
567
|
-
"private_address",
|
|
568
|
-
`url host "${host}" matches denied suffix "${suffix}"`
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
if (opts?.extraHostnameSuffixes) {
|
|
573
|
-
for (const suffix of opts.extraHostnameSuffixes) {
|
|
574
|
-
const s = suffix.toLowerCase();
|
|
575
|
-
if (host === s.slice(1) || host.endsWith(s)) {
|
|
576
|
-
throw new SSRFError("private_address", `url host "${host}" matches denied suffix "${s}"`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
const stripped = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
581
|
-
if (isIPv4(stripped) || isIPv6(stripped)) {
|
|
582
|
-
if (isUnsafeAddress(stripped, opts)) {
|
|
583
|
-
throw new SSRFError(
|
|
584
|
-
"private_address",
|
|
585
|
-
`url host "${stripped}" is in a blocked address range`
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
return { kind: "literal", address: stripped, family: isIPv4(stripped) ? 4 : 6 };
|
|
589
|
-
}
|
|
590
|
-
let addresses;
|
|
591
|
-
try {
|
|
592
|
-
addresses = await resolver(host);
|
|
593
|
-
} catch (err) {
|
|
594
|
-
throw new SSRFError(
|
|
595
|
-
"dns_failed",
|
|
596
|
-
`url host "${host}" failed DNS resolution: ${err instanceof Error ? err.message : String(err)}`
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
if (addresses.length === 0) {
|
|
600
|
-
throw new SSRFError("dns_failed", `url host "${host}" resolved to no addresses`);
|
|
601
|
-
}
|
|
602
|
-
for (const { address } of addresses) {
|
|
603
|
-
if (isUnsafeAddress(address, opts)) {
|
|
604
|
-
throw new SSRFError(
|
|
605
|
-
"private_address",
|
|
606
|
-
`url host "${host}" resolves to blocked address "${address}"`
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const validated = addresses.map((a) => ({ address: a.address, family: a.family }));
|
|
611
|
-
const first = validated[0];
|
|
612
|
-
return { kind: "resolved", address: first.address, family: first.family, addresses: validated };
|
|
613
|
-
}
|
|
614
|
-
var HOSTNAME_DENYLIST_EXACT = /* @__PURE__ */ new Set([
|
|
615
|
-
"localhost",
|
|
616
|
-
"ip6-localhost",
|
|
617
|
-
"ip6-loopback",
|
|
618
|
-
"metadata.google.internal",
|
|
619
|
-
"metadata"
|
|
620
|
-
]);
|
|
621
|
-
var HOSTNAME_DENYLIST_SUFFIX = [".internal", ".local", ".localhost", ".lan"];
|
|
622
|
-
async function defaultResolver(host) {
|
|
623
|
-
return lookup(host, { all: true });
|
|
624
|
-
}
|
|
625
|
-
function matchesExtraExact(host, extras) {
|
|
626
|
-
if (!extras) return false;
|
|
627
|
-
for (const entry of extras) {
|
|
628
|
-
if (entry.toLowerCase() === host) return true;
|
|
629
|
-
}
|
|
630
|
-
return false;
|
|
631
|
-
}
|
|
632
|
-
function isUnsafeAddress(address, opts) {
|
|
633
|
-
if (isIPv4(address)) {
|
|
634
|
-
if (isUnsafeIPv4(address)) return true;
|
|
635
|
-
return opts?.extraUnsafeIPv4?.(address) === true;
|
|
636
|
-
}
|
|
637
|
-
if (isIPv6(address)) {
|
|
638
|
-
if (isUnsafeIPv6(address)) return true;
|
|
639
|
-
return opts?.extraUnsafeIPv6?.(address) === true;
|
|
640
|
-
}
|
|
641
|
-
return true;
|
|
642
|
-
}
|
|
643
|
-
function isUnsafeIPv4(address) {
|
|
644
|
-
const parts = address.split(".").map(Number);
|
|
645
|
-
if (parts.length !== 4 || parts.some((p) => !Number.isFinite(p) || p < 0 || p > 255)) {
|
|
646
|
-
return true;
|
|
647
|
-
}
|
|
648
|
-
const [a, b, c] = parts;
|
|
649
|
-
if (a === 0) return true;
|
|
650
|
-
if (a === 10) return true;
|
|
651
|
-
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
652
|
-
if (a === 127) return true;
|
|
653
|
-
if (a === 169 && b === 254) return true;
|
|
654
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
655
|
-
if (a === 192 && b === 0 && (c === 0 || c === 2)) return true;
|
|
656
|
-
if (a === 192 && b === 168) return true;
|
|
657
|
-
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
658
|
-
if (a >= 224 && a <= 239) return true;
|
|
659
|
-
if (a >= 240) return true;
|
|
660
|
-
return false;
|
|
661
|
-
}
|
|
662
|
-
function isUnsafeIPv6(address) {
|
|
663
|
-
const lower = address.toLowerCase();
|
|
664
|
-
if (lower === "::1") return true;
|
|
665
|
-
if (lower === "::") return true;
|
|
666
|
-
if (lower.startsWith("fe8") || lower.startsWith("fe9") || lower.startsWith("fea") || lower.startsWith("feb")) {
|
|
667
|
-
return true;
|
|
668
|
-
}
|
|
669
|
-
if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
|
|
670
|
-
if (lower.startsWith("::ffff:")) {
|
|
671
|
-
const tail = lower.slice("::ffff:".length);
|
|
672
|
-
if (isIPv4(tail)) return isUnsafeIPv4(tail);
|
|
673
|
-
const m = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(tail);
|
|
674
|
-
if (m) {
|
|
675
|
-
const high = parseInt(m[1], 16);
|
|
676
|
-
const low = parseInt(m[2], 16);
|
|
677
|
-
const a = high >> 8 & 255;
|
|
678
|
-
const b = high & 255;
|
|
679
|
-
const c = low >> 8 & 255;
|
|
680
|
-
const d = low & 255;
|
|
681
|
-
return isUnsafeIPv4(`${a.toString()}.${b.toString()}.${c.toString()}.${d.toString()}`);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
if (lower.startsWith("ff")) return true;
|
|
685
|
-
return false;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
429
|
export {
|
|
689
430
|
isZodSchema,
|
|
690
431
|
RAIL_REFUNDABLE,
|
|
691
432
|
DEFAULT_CREDIT_MIN,
|
|
692
433
|
DEFAULT_CREDIT_MAX,
|
|
693
434
|
DEFAULT_CREDIT_TTL_SECONDS,
|
|
694
|
-
envFlag,
|
|
695
435
|
canonicalize,
|
|
696
436
|
canonicaliseForSigning,
|
|
437
|
+
verifyCanonicalSignedRequest,
|
|
697
438
|
SIGNED_REQUEST_AUTH_ID,
|
|
698
439
|
SIGNED_REQUEST_STATEMENT_VERSION,
|
|
699
440
|
signedRequestStatementHeader,
|
|
441
|
+
canonicalSignedRequestStatement,
|
|
700
442
|
canonicalRequestPath,
|
|
701
443
|
humanizeZodError,
|
|
702
444
|
humanizeZodIssues,
|
|
703
445
|
zodIssuesOf,
|
|
446
|
+
SIGNED_ENVELOPE_TYPES,
|
|
704
447
|
SIGNED_ENVELOPE_FIELDS,
|
|
705
448
|
SignedRequestError,
|
|
706
449
|
createSignedRequestVerifier,
|
|
707
450
|
signedRequestInput,
|
|
708
|
-
createDefaultReplayStore
|
|
709
|
-
SSRFError,
|
|
710
|
-
assertSafeUrl,
|
|
711
|
-
createPinnedFetch
|
|
451
|
+
createDefaultReplayStore
|
|
712
452
|
};
|