@elisym/sdk 0.6.0 → 0.8.0

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/dist/index.js CHANGED
@@ -4,6 +4,10 @@ import Decimal2 from 'decimal.js-light';
4
4
  import { z } from 'zod';
5
5
  import { verifyEvent, finalizeEvent, getPublicKey, nip19, generateSecretKey, SimplePool } from 'nostr-tools';
6
6
  import * as nip44 from 'nostr-tools/nip44';
7
+ import { readFile, mkdir, writeFile, rename } from 'node:fs/promises';
8
+ import YAML2 from 'yaml';
9
+ import { randomBytes } from 'node:crypto';
10
+ import { dirname } from 'node:path';
7
11
 
8
12
  // src/constants.ts
9
13
  var RELAYS = [
@@ -2331,6 +2335,129 @@ var ElisymClient = class {
2331
2335
  this.pool.close();
2332
2336
  }
2333
2337
  };
2338
+
2339
+ // src/payment/assets.ts
2340
+ var NATIVE_SOL = {
2341
+ chain: "solana",
2342
+ token: "sol",
2343
+ decimals: 9,
2344
+ symbol: "SOL"
2345
+ };
2346
+ var KNOWN_ASSETS = [NATIVE_SOL];
2347
+ function assetKey(a) {
2348
+ return a.mint ? `${a.chain}:${a.token}:${a.mint}` : `${a.chain}:${a.token}`;
2349
+ }
2350
+ function resolveKnownAsset(chain, token, mint) {
2351
+ const key = mint ? `${chain}:${token}:${mint}` : `${chain}:${token}`;
2352
+ return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
2353
+ }
2354
+ function assetByKey(key) {
2355
+ return KNOWN_ASSETS.find((asset) => assetKey(asset) === key);
2356
+ }
2357
+ var DECIMAL_RE = /^(\d+\.\d*|\d*\.\d+|\d+)$/;
2358
+ function parseAssetAmount(asset, human) {
2359
+ const trimmed = human.trim();
2360
+ if (!trimmed) {
2361
+ throw new Error(`${asset.symbol} amount is empty`);
2362
+ }
2363
+ if (trimmed.startsWith("-")) {
2364
+ throw new Error(`${asset.symbol} amount cannot be negative`);
2365
+ }
2366
+ if (!DECIMAL_RE.test(trimmed)) {
2367
+ throw new Error(
2368
+ `${asset.symbol} amount must be a non-negative decimal (e.g. "0.5", "1"); got "${human}"`
2369
+ );
2370
+ }
2371
+ const dotPos = trimmed.indexOf(".");
2372
+ let wholePart;
2373
+ if (dotPos === -1) {
2374
+ wholePart = trimmed;
2375
+ } else if (dotPos === 0) {
2376
+ wholePart = "0";
2377
+ } else {
2378
+ wholePart = trimmed.slice(0, dotPos);
2379
+ }
2380
+ const fracPart = dotPos === -1 ? "" : trimmed.slice(dotPos + 1);
2381
+ if (fracPart.length > asset.decimals) {
2382
+ throw new Error(
2383
+ `${asset.symbol} amount has too many decimals (max ${asset.decimals}); got "${human}"`
2384
+ );
2385
+ }
2386
+ const unit = 10n ** BigInt(asset.decimals);
2387
+ const whole = BigInt(wholePart);
2388
+ const frac = fracPart ? BigInt(fracPart.padEnd(asset.decimals, "0")) : 0n;
2389
+ const raw = whole * unit + frac;
2390
+ if (raw === 0n) {
2391
+ throw new Error(`${asset.symbol} amount must be positive; got "${human}"`);
2392
+ }
2393
+ if (raw > BigInt(Number.MAX_SAFE_INTEGER)) {
2394
+ throw new Error(
2395
+ `${asset.symbol} amount exceeds safe range (max ${Number.MAX_SAFE_INTEGER} subunits)`
2396
+ );
2397
+ }
2398
+ return raw;
2399
+ }
2400
+ function formatAssetAmount(asset, raw) {
2401
+ const sign = raw < 0n ? "-" : "";
2402
+ const abs = raw < 0n ? -raw : raw;
2403
+ const unit = 10n ** BigInt(asset.decimals);
2404
+ const whole = abs / unit;
2405
+ const frac = abs % unit;
2406
+ if (asset.decimals === 0) {
2407
+ return `${sign}${whole} ${asset.symbol}`;
2408
+ }
2409
+ return `${sign}${whole}.${frac.toString().padStart(asset.decimals, "0")} ${asset.symbol}`;
2410
+ }
2411
+ async function writeFileAtomic(path, data, mode) {
2412
+ await mkdir(dirname(path), { recursive: true });
2413
+ const tmpPath = `${path}.tmp.${randomBytes(6).toString("hex")}`;
2414
+ await writeFile(tmpPath, data, { mode });
2415
+ try {
2416
+ await rename(tmpPath, path);
2417
+ } catch (e) {
2418
+ try {
2419
+ const { unlink } = await import('node:fs/promises');
2420
+ await unlink(tmpPath);
2421
+ } catch {
2422
+ }
2423
+ throw e;
2424
+ }
2425
+ }
2426
+
2427
+ // src/config/global.ts
2428
+ var SessionSpendLimitEntrySchema = z.object({
2429
+ chain: z.enum(["solana"]),
2430
+ token: z.string().min(1).max(16).regex(/^[a-z0-9]+$/, "token must be lowercase alphanumeric"),
2431
+ mint: z.string().min(1).max(64).optional(),
2432
+ amount: z.number().positive().finite()
2433
+ }).strict();
2434
+ var GlobalConfigSchema = z.object({
2435
+ session_spend_limits: z.array(SessionSpendLimitEntrySchema).max(16).optional()
2436
+ }).strict();
2437
+ function isEnoent(e) {
2438
+ return typeof e === "object" && e !== null && "code" in e && e.code === "ENOENT";
2439
+ }
2440
+ async function loadGlobalConfig(path) {
2441
+ let raw;
2442
+ try {
2443
+ raw = await readFile(path, "utf-8");
2444
+ } catch (e) {
2445
+ if (isEnoent(e)) {
2446
+ return {};
2447
+ }
2448
+ throw e;
2449
+ }
2450
+ if (raw.trim() === "") {
2451
+ return {};
2452
+ }
2453
+ const parsed = YAML2.parse(raw);
2454
+ return GlobalConfigSchema.parse(parsed ?? {});
2455
+ }
2456
+ async function writeGlobalConfig(path, config) {
2457
+ const validated = GlobalConfigSchema.parse(config);
2458
+ const body = YAML2.stringify(validated);
2459
+ await writeFileAtomic(path, body, 420);
2460
+ }
2334
2461
  function formatSol(lamports) {
2335
2462
  const sol = new Decimal2(lamports).div(LAMPORTS_PER_SOL);
2336
2463
  if (sol.gte(1e6)) {
@@ -2387,6 +2514,141 @@ function validateAgentName(name) {
2387
2514
  }
2388
2515
  }
2389
2516
 
2390
- export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DiscoveryService, ElisymClient, ElisymIdentity, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SolanaPaymentStrategy, assertExpiry, assertLamports, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, estimatePriorityFeeMicroLamports, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, nip44Decrypt, nip44Encrypt, parsePaymentRequest, pickPercentileFee, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry };
2517
+ // src/primitives/rateLimiter.ts
2518
+ function createSlidingWindowLimiter(options) {
2519
+ const { windowMs, maxPerWindow, maxKeys } = options;
2520
+ if (windowMs <= 0) {
2521
+ throw new RangeError("windowMs must be > 0");
2522
+ }
2523
+ if (maxPerWindow <= 0) {
2524
+ throw new RangeError("maxPerWindow must be > 0");
2525
+ }
2526
+ if (maxKeys <= 0) {
2527
+ throw new RangeError("maxKeys must be > 0");
2528
+ }
2529
+ const entries = /* @__PURE__ */ new Map();
2530
+ function evictIfNeeded() {
2531
+ while (entries.size > maxKeys) {
2532
+ const oldestKey = entries.keys().next().value;
2533
+ if (oldestKey === void 0) {
2534
+ return;
2535
+ }
2536
+ entries.delete(oldestKey);
2537
+ }
2538
+ }
2539
+ return {
2540
+ peek(key, now = Date.now()) {
2541
+ const entry = entries.get(key);
2542
+ if (!entry) {
2543
+ return { allowed: true, resetAt: now + windowMs, count: 0 };
2544
+ }
2545
+ const cutoff = now - windowMs;
2546
+ const fresh = entry.hits.filter((timestamp) => timestamp > cutoff);
2547
+ return {
2548
+ allowed: fresh.length < maxPerWindow,
2549
+ resetAt: (fresh[0] ?? now) + windowMs,
2550
+ count: fresh.length
2551
+ };
2552
+ },
2553
+ check(key, now = Date.now()) {
2554
+ const entry = entries.get(key) ?? { hits: [] };
2555
+ const cutoff = now - windowMs;
2556
+ const fresh = entry.hits.filter((timestamp) => timestamp > cutoff);
2557
+ if (fresh.length >= maxPerWindow) {
2558
+ entries.delete(key);
2559
+ entries.set(key, { hits: fresh });
2560
+ return {
2561
+ allowed: false,
2562
+ resetAt: (fresh[0] ?? now) + windowMs,
2563
+ count: fresh.length
2564
+ };
2565
+ }
2566
+ fresh.push(now);
2567
+ entries.delete(key);
2568
+ entries.set(key, { hits: fresh });
2569
+ evictIfNeeded();
2570
+ return {
2571
+ allowed: true,
2572
+ resetAt: (fresh[0] ?? now) + windowMs,
2573
+ count: fresh.length
2574
+ };
2575
+ },
2576
+ prune(now = Date.now()) {
2577
+ const cutoff = now - windowMs;
2578
+ for (const [key, entry] of entries) {
2579
+ const fresh = entry.hits.filter((timestamp) => timestamp > cutoff);
2580
+ if (fresh.length === 0) {
2581
+ entries.delete(key);
2582
+ } else if (fresh.length !== entry.hits.length) {
2583
+ entry.hits = fresh;
2584
+ }
2585
+ }
2586
+ },
2587
+ size() {
2588
+ return entries.size;
2589
+ },
2590
+ reset() {
2591
+ entries.clear();
2592
+ }
2593
+ };
2594
+ }
2595
+
2596
+ // src/primitives/logRedact.ts
2597
+ var SECRET_REDACT_PATHS = [
2598
+ "*.ELISYM_NOSTR_PRIVATE_KEY",
2599
+ "*.ELISYM_SOLANA_PRIVATE_KEY",
2600
+ "*.nostrPrivateKeyHex",
2601
+ "*.solanaPrivateKeyBase58",
2602
+ "*.secretKey",
2603
+ "*.secret",
2604
+ "ELISYM_NOSTR_PRIVATE_KEY",
2605
+ "ELISYM_SOLANA_PRIVATE_KEY",
2606
+ // Canonical on-disk `.secrets.json` field names. Logging the whole
2607
+ // `secrets` object, or any single field directly, must not leak.
2608
+ "llm_api_key",
2609
+ "nostr_secret_key",
2610
+ "solana_secret_key",
2611
+ "*.llm_api_key",
2612
+ "*.nostr_secret_key",
2613
+ "*.solana_secret_key",
2614
+ "secrets",
2615
+ "*.secrets"
2616
+ ];
2617
+ var INPUT_REDACT_PATHS = [
2618
+ "content",
2619
+ "input",
2620
+ "prompt",
2621
+ "*.content",
2622
+ "*.input",
2623
+ "*.prompt",
2624
+ "event.content",
2625
+ "*.event.content",
2626
+ // JobLedger entries carry the raw Nostr event JSON (which embeds
2627
+ // `event.content`) and the full LLM `resultContent` - both are
2628
+ // customer-confidential and must never land in a structured log.
2629
+ "rawEventJson",
2630
+ "resultContent",
2631
+ "*.rawEventJson",
2632
+ "*.resultContent"
2633
+ ];
2634
+ var DEFAULT_REDACT_PATHS = [...SECRET_REDACT_PATHS, ...INPUT_REDACT_PATHS];
2635
+ var INPUT_REDACT_LEAVES = /* @__PURE__ */ new Set([
2636
+ "content",
2637
+ "input",
2638
+ "prompt",
2639
+ "rawEventJson",
2640
+ "resultContent"
2641
+ ]);
2642
+ function makeCensor() {
2643
+ return (_value, path) => {
2644
+ const last = path[path.length - 1];
2645
+ if (last !== void 0 && INPUT_REDACT_LEAVES.has(last)) {
2646
+ return "[INPUT REDACTED]";
2647
+ }
2648
+ return "[REDACTED]";
2649
+ };
2650
+ }
2651
+
2652
+ export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, PROTOCOL_FEE_BPS, PROTOCOL_PROGRAM_ID_DEVNET, PROTOCOL_TREASURY, PaymentRequestSchema, PingService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, clearPriorityFeeCache, clearProtocolConfigCache, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, estimatePriorityFeeMicroLamports, formatAssetAmount, formatSol, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, loadGlobalConfig, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveKnownAsset, timeAgo, toDTag, truncateKey, validateAgentName, validateExpiry, writeGlobalConfig };
2391
2653
  //# sourceMappingURL=index.js.map
2392
2654
  //# sourceMappingURL=index.js.map